Share
Embed
詳解 Auto-Renewable Subscription / Detailed Auto-Renewable Subscription
iOSDC2019におけるトークです。
https://fortee.jp/iosdc-japan-2019/proposal/723b458a-2b5d-41a1-bec5-2cd5ccf722c0
rockname
September 06, 2019
Transcript
139 slides
-
ࣗݾհ • גࣜձࣾϛΫγΟ • ՈΞϧόϜΈͯͶ / ΞϓϦ։ൃάϧʔϓ iOS(Swift, obj-c) >
Android(Kotlin, Java) == Rails(ruby) • VTuber͕͖ • WWDC 2019 ࢀՃ͠·ͨ͠ ✈ ϩΫωϜ@rockname -
ࠓ͢͜ͱ 1. Auto-Renewable Subscriptionsͱʁ 2. ΈͯͶʹ͓͚ΔಋೖͷܦҢ 3. جຊతͳ࣮ํ๏ 4. αʔόʔؒ௨ʹ͍ͭͯ
5. Status Pollingʹ͍ͭͯ 6. Sandboxڥʹ͓͚Δಈ࡞֬ೝ 7. ϦδΣΫτʹ͍ͭͯؾΛ͚͍ͭͨϙΠϯτ 8. ·ͱΊ -
ࠓ͞ͳ͍͜ͱ • Subscription Offers • Promoted In-App Purchases • Sales
and TrendsͷϝτϦΫεͷݟํ -
લ४උ • App՝ۚΛ࡞ • Reference Name / Product ID •
Price / Duration • Subscription Display Name • Subscription GroupΛ࡞ • Reference Name • Subscription Group Display Name ʮ"QQ4UPSF$POOFDUϔϧϓʯΑΓ IUUQTIFMQBQQMFDPNBQQTUPSFDPOOFDUEFWGDF App Store Connect্ͰϓϩμΫτΛ࡞ -
Transaction ObserverͷηοτΞοϓ • transactionΛॲཧ͢ΔͨΊʹ·ͣObserverΛੜ ͯ͠payment queueʹՃ͢Δඞཁ͕͋Δ • payment queue͔ΒͷΠϕϯτΛऔΓ͜΅͞ͳ͍ ͨΊʹObserverΞϓϦىಈޙʹՃ͢Δ
IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOTUPSFLJUJO BQQ@QVSDIBTF TFUUJOH@VQ@UIF@USBOTBDUJPO@PCTFSWFS@BOE@QBZNFOU@RVFVF -
class PaymentTransactionObserver: NSObject, SKPaymentTransactionObserver { static let shared = PaymentTransactionObserver()
private let queue = SKPaymentQueue.default() private override init() { super.init() NotificationCenter.default.addObserver( self, selector: #selector(self.didFinishLaunchingNotification), name: UIApplication.didFinishLaunchingNotification, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(self.willTerminate), name: UIApplication.willTerminateNotification, object: nil ) } deinit { NotificationCenter.default.removeObserver(self) } @objc private func didFinishLaunchingNotification() { queue.add(self) } @objc private func willTerminate() { queue.remove(self) } func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { // Handle transaction states here. } } -
class PaymentTransactionObserver: NSObject, SKPaymentTransactionObserver { static let shared = PaymentTransactionObserver()
private let queue = SKPaymentQueue.default() private override init() { super.init() NotificationCenter.default.addObserver( self, selector: #selector(self.didFinishLaunchingNotification), name: UIApplication.didFinishLaunchingNotification, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(self.willTerminate), name: UIApplication.willTerminateNotification, object: nil ) } deinit { NotificationCenter.default.removeObserver(self) } @objc private func didFinishLaunchingNotification() { queue.add(self) } @objc private func willTerminate() { queue.remove(self) } func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { // Handle transaction states here. } } ObserverγϯάϧτϯͰ࡞Δ -
class PaymentTransactionObserver: NSObject, SKPaymentTransactionObserver { static let shared = PaymentTransactionObserver()
private let queue = SKPaymentQueue.default() private override init() { super.init() NotificationCenter.default.addObserver( self, selector: #selector(self.didFinishLaunchingNotification), name: UIApplication.didFinishLaunchingNotification, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(self.willTerminate), name: UIApplication.willTerminateNotification, object: nil ) } deinit { NotificationCenter.default.removeObserver(self) } @objc private func didFinishLaunchingNotification() { queue.add(self) } @objc private func willTerminate() { queue.remove(self) } func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { // Handle transaction states here. } } ΞϓϦىಈ࣌ʹpayment queueʹՃ -
class PaymentTransactionObserver: NSObject, SKPaymentTransactionObserver { static let shared = PaymentTransactionObserver()
private let queue = SKPaymentQueue.default() private override init() { super.init() NotificationCenter.default.addObserver( self, selector: #selector(self.didFinishLaunchingNotification), name: UIApplication.didFinishLaunchingNotification, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(self.willTerminate), name: UIApplication.willTerminateNotification, object: nil ) } deinit { NotificationCenter.default.removeObserver(self) } @objc private func didFinishLaunchingNotification() { queue.add(self) } @objc private func willTerminate() { queue.remove(self) } func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { // Handle transaction states here. } } ΞϓϦऴྃ࣌ʹpayment queue͔Βআ -
App Store͔ΒProduct InformationΛऔಘ • SKPaymentQueue.canMakePayments() ͰϢʔβʔ͕ߪೖͰ͖Δ ͔֬ೝ • App Store
ConnectͰొͨ͠product identifierΛ༻ͯ͠App Store͔ΒϓϩμΫτใΛऔಘ͢Δ • product identifierͷཧʹ2ύλʔϯ͋Δ 1. BundleʹؚΊΔ 2. αʔόʔͰཧ͢Δ -
App Store͔ΒProduct InformationΛऔಘ • SKPaymentQueue.canMakePayments() ͰϢʔβʔ͕ߪೖͰ͖Δ ͔֬ೝ • App Store
ConnectͰొͨ͠product identifierΛ༻ͯ͠App Store͔ΒϓϩμΫτใΛऔಘ͢Δ • product identifierͷཧʹ2ύλʔϯ͋Δ 1. BundleʹؚΊΔ 2. αʔόʔͰཧ͢Δ ΈͯͶͰ ʮ2. αʔόʔͰཧ͢ΔʯͰ࣮ -
// Keep a strong reference to the product request. var
request: SKProductsRequest! func requestProductInformation(with productIdentifiers: [String]) { let productIdentifiers = Set(productIdentifiers) request = SKProductsRequest(productIdentifiers: productIdentifiers) request.delegate = self request.start() } -
// Keep a strong reference to the product request. var
request: SKProductsRequest! func requestProductInformation(with productIdentifiers: [String]) { let productIdentifiers = Set(productIdentifiers) request = SKProductsRequest(productIdentifiers: productIdentifiers) request.delegate = self request.start() } ϦΫΤετڧࢀরͰอ࣋͢Δඞཁ͕͋Δ -
// Keep a strong reference to the product request. var
request: SKProductsRequest! func requestProductInformation(with productIdentifiers: [String]) { let productIdentifiers = Set(productIdentifiers) request = SKProductsRequest(productIdentifiers: productIdentifiers) request.delegate = self request.start() } delegateΛηοτͯ͠ϦΫΤετΛ։࢝ -
// SKProductsRequestDelegate protocol method. func productsRequest(_ request: SKProductsRequest, didReceive response:
SKProductsResponse) { if !response.products.isEmpty { let products = response.products // Custom method. displayStore(products) } for invalidIdentifier in response.invalidProductIdentifiers { // Handle any invalid product identifiers as appropriate. } } -
// SKProductsRequestDelegate protocol method. func productsRequest(_ request: SKProductsRequest, didReceive response:
SKProductsResponse) { if !response.products.isEmpty { let products = response.products // Custom method. displayStore(products) } for invalidIdentifier in response.invalidProductIdentifiers { // Handle any invalid product identifiers as appropriate. } } delegateϝιουͰϓϩμΫτͷྻ Λड͚औͬͯUIʹදࣔ -
// SKProductsRequestDelegate protocol method. func productsRequest(_ request: SKProductsRequest, didReceive response:
SKProductsResponse) { if !response.products.isEmpty { let products = response.products // Custom method. displayStore(products) } for invalidIdentifier in response.invalidProductIdentifiers { // Handle any invalid product identifiers as appropriate. } } ΈͯͶͰinvalidIdentifierΛ೦ͷҝ Crashlyticsʹඈ͍ͯ͠Δ -
UIʹϓϩμΫτใΛදࣔ • Human Interface GuidelineΛ ख़ಡͯ͠దͳใΛදࣔ͢Δ • ใ͕࿙Ε͍ͯͨΒଈϦδΣΫ τͳͷͰཁҙ (ৄ͘͠ޙ΄
Ͳ͠·͢) IUUQTEFWFMPQFSBQQMFDPNEFTJHOIVNBOJOUFSGBDFHVJEFMJOFT TVCTDSJQUJPOTPWFSWJFX -
App StoreߪೖΛϦΫΤετ • Ϣʔβʔͷબͨ͠ϓϩμΫτ͔Βࢧ͍Ϧ ΫΤετΛ࡞ • ࢧ͍ϦΫΤετΛpayment queueʹՃ let payment
= SKPayment(product: selectedProduct) SKPaymentQueue.default().add(payment) -
TransactionΛॲཧ͢Δ • SKPaymentTransactionObserver#paymentQueue(_:updatedTransactio ns:) ʹtransaction͕ྲྀΕͯ͘Δ • transactionͷ SKPaymentTransactionState ʹԠͯ͡దʹॲཧ •
transaction finishTransaction() ͠ͳ͍͔͗ΓqueueʹΓଓ͚Δ • ߪೖ్தʹΞϓϦΛऴྃͤͯ͞͠·ͬͨϢʔβʔ࣍ճىಈ࣌ʹ෮ ؼͰ͖Δ • updatedTransactions Λ͍ͭͰॲཧͯ݁͠ՌΛϢʔβʔʹөͤ͞ Δ͜ͱ͕Ͱ͖Δঢ়ଶΛߏங͢Δඞཁ͕͋Δ -
4,1BZNFOU 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ
ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹޭ લճͷߪೖͷ෮ݩʹ ޭ 6*ͷө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ pOJTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO -
4,1BZNFOU 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ
ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹޭ લճͷߪೖͷ෮ݩʹ ޭ 6*ͷө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ pOJTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO -
4,1BZNFOU 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ
ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹޭ લճͷߪೖͷ෮ݩʹ ޭ 6*ͷө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ pOJTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO -
4,1BZNFOU 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ
ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹޭ લճͷߪೖͷ෮ݩʹ ޭ 6*ͷө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ pOJTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO -
4,1BZNFOU 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ
ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹޭ લճͷߪೖͷ෮ݩʹ ޭ 6*ͷө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ pOJTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO -
4,1BZNFOU 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ
ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹޭ લճͷߪೖͷ෮ݩʹ ޭ 6*ͷө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ pOJTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO -
4,1BZNFOU 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ
ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹޭ લճͷߪೖͷ෮ݩʹ ޭ 6*ͷө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ pOJTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO WBMJEBUJPOͷޭ͋Δ ͍Ϩγʔτ͕ظݶ Εͷ߹ pOTI5SBOTBDUJPO ΈͯͶͰ ʮUIͷөʯͱʮtransactionͷॲཧʯ શʹಠཱ࣮ͯ͠ߦ͍ͯ͠Δ -
ϨγʔτͷόϦσʔγϣϯ (Appଆ) • ߪೖ͕ྃ͢ΔͱϨγʔτ͕ൃߦ͞ΕΔ • Auto-Renewable Subscriptionͷ߹ɺ̍ͭͷϨγʔ τʹߪೖใ͕ه͞Ε͍ͯ͘ • Bundle.main.appStoreReceiptURL
ʹϨγʔτͷσʔλ ͕ଘࡏ͢ΔͷͰऔಘ • base64Encodingͨ͠ϨγʔτͷDataΛαʔόʔʹPOST -
// Get receipt if available if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { do { let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped) let receiptString = receiptData.base64EncodedString(options: []) // Send ReceiptData to your server with user information } catch { print("Couldn't read receipt data with error: " + error.localizedDescription) } } -
// Get receipt if available if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { do { let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped) let receiptString = receiptData.base64EncodedString(options: []) // Send ReceiptData to your server with user information } catch { print("Couldn't read receipt data with error: " + error.localizedDescription) } } Ϩγʔτ͕ଘࡏ͍ͯ͠Εऔಘ -
// Get receipt if available if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { do { let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped) let receiptString = receiptData.base64EncodedString(options: []) // Send ReceiptData to your server with user information } catch { print("Couldn't read receipt data with error: " + error.localizedDescription) } } base64encodingͨ͠ϨγʔτΛ ϢʔβʔใͱڞʹServerPOST -
/verifyReceipt ͷϨεϙϯε • environment: Sandbox͔PROD͕ͱͯ͠ೖ͍ͬͯΔɻ • status: ਖ਼ৗͳΒ0ɺͦ͏Ͱͳ͚Ε20000ͷΤϥʔίʔυ͕ೖ͍ͬͯΔɻ • latest_receipt:
Base64Τϯίʔυ͞Εͨ࠷৽Ϩγʔτσʔλ • latest_receipt_info: ࠷৽Ϩγʔτใ͕ೖ͍ͬͯΔɻجຊͪ͜ΒͷใΛͱʹdbΛߋ৽ ͍ͯ͘͠ɻ cancellation_at ϑΟʔϧυ͕ଘࡏ͢Δ͔Ͳ͏͔ΛݟͯApple ΧελϚʔαϙʔτ ʹΑͬͯΩϟϯηϧ͞Ε͔ͨͲ͏͔Λه͓ͯ͘͠ͱྑ͍ɻ • pending_renewal_info: ࣗಈߋ৽͕༧ఆ͞Ε͍ͯΔɺ͋Δ͍աڈʹࣗಈߋ৽ʹࣦഊͨ͠Ϩ γʔτ͕ೖ͍ͬͯΔɻ is_in_billing_retry_period ϑΟʔϧυΛݟͯϦτϥΠத͔Ͳ͏͔Λه expiration_intent ϑΟʔϧυΛݟͯظݶΕͷཧ༝Λه͓ͯ͘͠ͱྑ͍ɻ • is-retryable: Apple෦ͷΤϥʔʹΑͬͯϨγʔτͷvalidationʹࣦഊͨ͠߹ʹtrueͱͳ Δɺͦͷ߹retry͢Δͱྑ͍ɻ -
൪֎ฤ: ΤϥʔϋϯυϦϯά • ൃੜ͠͏ΔΤϥʔͷछྨ 1. transactionState ͕ .failed ͷঢ়ଶ 2.
transactionState .purchased ͕ͩϨ γʔτͷόϦσʔγϣϯʹࣦഊ -
1. transactionState͕ .failed ͷঢ়ଶ • AppStoreଆͷΤϥʔͳͷͰɺ localizedDescription ΛAlertʹ දࣔͯ͋͛͠Δ •
ͨͩ͠ɺߪೖ్தʹΩϟϯηϧΛԡͯ͠transaction .failed ѻ͍ͱͳΔͷͰguardͯ͋͛͠Δඞཁ͕͋Δ -
1. transactionState͕ .failed ͷঢ়ଶ • AppStoreଆͷΤϥʔͳͷͰɺ localizedDescription ΛAlertʹ දࣔͯ͋͛͠Δ •
ͨͩ͠ɺߪೖ్தʹΩϟϯηϧΛԡͯ͠transaction .failed ѻ͍ͱͳΔͷͰguardͯ͋͛͠Δඞཁ͕͋Δ if let error = error as? SKError, error.code != .paymentCancelled { DispatchQueue.main.async { self.delegate?.presentAlert(error.localizedDescription) } } -
2. transactionState .purchased ͕ͩϨγʔ τͷόϦσʔγϣϯʹࣦഊ • ΈͯͶͷ߹2ύλʔϯͷΤϥʔΛϋϯυϦϯά 1. αʔόʔଆͰͷ༧ଌෆೳͳΤϥʔ 2.
ड͚औͬͨϨγʔτ͕͢ͰʹϢʔβʔ(Ո)ͱ ඥ͍͍ͮͯΔ߹ͷΤϥʔ • restoreʹΑͬͯαϒεΫϦϓγϣϯΛ෮ݩ͢Δࡍ ʹൃੜ͠͏Δ (restoreʹ͍ͭͯޙ΄Ͳ͠·͢) -
αʔόʔؒ௨ͱʁ • App Store͔ΒϦΞϧλΠϜʹsubscriptionͷঢ়ଶมԽͷ௨ ΛHTTP POSTͰड͚औΔΈ (statusUpdateNotifications ͱݺΕͯͨ) • Ϩεϙϯεʹ200Λฦ͞ͳ͍ݶΓ3ճ·ͰϦτϥΠͯ͘͠ΕΔ
• ࣮optional͕ͩϨγʔτͷঢ়ଶΛ࠷৽ʹอͬͨΓϢʔ βʔʹରͯ͠offerΛग़ͤΔΑ͏ʹ͢ΔͨΊʹ͓͍ͬͯͨ ํ͕͍͍ -
αʔόʔؒ௨Λड͚औΔ • POST͞ΕΔJSON͔Βجຊతʹ͏ϑΟʔϧυҎԼͷ௨Γ • environment: Sandbox͔PROD͕ͱͯ͠ೖ͍ͬͯΔ • notification_type: αʔόʔؒ௨ͷtype(ৄ͘͠ޙड़͠·͢) •
password: /verifyReceiptͷࡍʹ༻ͨ͠shared secret • cancellation_date: AppleͷαϙʔτΛ௨ͯ͠ฦۚॲཧ͕ͳ͞ΕͯΩϟϯηϧ͞Εͨ࣌ • latest_receipt: Base64Τϯίʔυ͞Εͨ࠷৽Ϩγʔτσʔλ • latest_receipt_info: latest_receiptΛJSONͰදݱͨ͠ͷ • latest_expired_receipt: Base64Τϯίʔυ͞ΕͨظݶͷΕ͍ͯΔϨγʔτɺ • latest_expired_receipt_info: latest_expired_receiptΛJSONͰදݱͨ͠ͷ • auto_renew_status: ݱࡏࣗಈߋ৽ઃఆத͔Ͳ͏͔ͷϑϥά • auto_renew_status_change_date: ࣗಈߋ৽ͷঢ়ଶΛߋ৽ͨ࣌͠ -
αʔόʔؒ௨Λड͚औΔ • POST͞ΕΔJSON͔Βجຊతʹ͏ϑΟʔϧυҎԼͷ௨Γ • environment: Sandbox͔PROD͕ͱͯ͠ೖ͍ͬͯΔ • notification_type: αʔόʔؒ௨ͷtype(ৄ͘͠ޙड़͠·͢) •
password: /verifyReceiptͷࡍʹ༻ͨ͠shared secret • cancellation_date: AppleͷαϙʔτΛ௨ͯ͠ฦۚॲཧ͕ͳ͞ΕͯΩϟϯηϧ͞Εͨ࣌ • latest_receipt: Base64Τϯίʔυ͞Εͨ࠷৽Ϩγʔτσʔλ • latest_receipt_info: latest_receiptΛJSONͰදݱͨ͠ͷ • latest_expired_receipt: Base64Τϯίʔυ͞ΕͨظݶͷΕ͍ͯΔϨγʔτɺ • latest_expired_receipt_info: latest_expired_receiptΛJSONͰදݱͨ͠ͷ • auto_renew_status: ݱࡏࣗಈߋ৽ઃఆத͔Ͳ͏͔ͷϑϥά • auto_renew_status_change_date: ࣗಈߋ৽ͷঢ়ଶΛߋ৽ͨ࣌͠ ΈͯͶͰ latest_receipt ͷσʔλΛ /verifyReceiptʹ͛ΔϑϩʔʹͤΔ Α͏ʹ࣮ -
ͦΕͧΕͷ௨λΠϓͷछྨ */*5*"-@#6: αϒεΫϦϓγϣϯΛ࠷ॳʹߪೖͨ͠ͱ͖ʹड͚औΔɻ ͍ͭͰWFSJGZͯ͠࠷৽ϨγʔτΛऔͬͯདྷΕΔΑ͏ʹɺϨγʔτσʔλΛอଘ͠ ͓ͯ͘ɻ $"/$&- "QQMFͷαϙʔτܦ༝ɺ͋Δ͍"QQ4UPSFͰαϒεΫϦϓγϣϯΛΞοϓάϨʔ υͨ͠ࡍʹड͚औΔɻ TVCTDSJQUJPO@EBUFϑΟʔϧυ͕͍ͭΩϟϯηϧ͞Ε͔ͨΛ࣋ͭɻ 3&/&8"-
Ұࣗಈߋ৽ʹࣦഊͨ͠ظݶΕͷαϒεΫϦϓγϣϯ͕ͦͷޙແࣄʹߋ৽Ͱ͖ ͨͱ͖ʹड͚औΔɻ FYQJSFT@EBUFΛݟͯ࣍ͷߋ৽࣌ࠁΛ֬ఆͤ͞Δɻ */5&3"$5*7& @3&/&8"- ΞϓϦܦ༝ɺ͋Δ͍"QQ4UPSFͷΞΧϯτઃఆ͔ΒαϒεΫϦϓγϣϯΛߋ৽ ͞Εͨࡍʹड͚औΔɻ αʔϏεΛ͙͢ʹར༻Ͱ͖Δঢ়ଶʹͯ͋͛͠Δඞཁ͕͋Δɻ %*%@$)"/(& @3&/&8"-@13&' ϓϥϯͷάϨʔυΛมߋ͞Εͨࡍʹड͚औΔɻ ݱߦͷϓϥϯʹಛʹӨڹͳ͍ɻ %*%@$)"/(& @3&/&8"-@45"564 ࣗಈߋ৽ͷઃఆʹมߋ͕͋ͬͨࡍʹड͚औΔɻ BVUP@SFOFX@TUBUVT@DIBOHF@EBUFͱBVUP@SFOFX@TUBUVTΛݟ͍ͯͭมߋ͕͋ͬ ͨͷ͔ɺݱࡏͷࣗಈߋ৽ͷঢ়ଶΛ֬ೝ͢Δɻ -
ࠓळ͔ΒՃ͞ΕΔ৽͍͠௨λΠϓ • WWDC19 ʮIn-App Purchases and Using Server-to-Server Notificationsʯʹͯൃද •
ҎԼͷ3͕ͭ৽͘͠ՃΘΔ • DID_FAIL_TO_RENEW • DID_RECOVER • PRICE_INCREASE_CONSENT • DID_RECOVERطଘͷRENEWAL௨λΠϓͱஔ͖ΘΔ New -
INITIAL_BUY "QQ4UPSF 1VSDIBTF 4FSWFS CBTFFODPEFE SFDFJQU */*5*"-@#6: WFSJGZ@SFDFJQU INITIAL_BUYͷใͱϢʔβʔͷใΛoriginal_transaction_identifier, web_order_line_item_idΛΩʔͱͯͭ͠ͳ͗߹ΘͤΔ
ݱঢ়ΈͯͶͰड͚औͬͨINITIAL_BUYΛ อଘ͓͍ͯͯ͠ޙ͔Βಥ͖߹ΘͤΔͱ͍͏ ͜ͱ͍ͯ͠·ͤΜ -
ࠓळ͔Β৽͘͠Ճ͞ΕΔunified_receipt • WWDC19ʮIn-App Purchases and Using Server-to-Server Notificationsʯ ʹͯൃද •
unified_receipt ͱ͍͏৽͍͠ϑΟʔϧυ͕Ճ͞ΕΔ • /verifyReceipt Λୟ͔ͳ͍ͱಘΒΕͳ͔ͬͨϑΟʔϧυΛऔಘͰ͖Δ • ͏ latest_receipt ͱ latest_receipt_info Λࢀর͢Δඞཁ͕ͳ͘ɺ / verifyReceiptΛୟ͘ඞཁͳ͍ • (receiptِ͕͞ΕΔՄೳੑΛߟ͑Δͱ/verifyReceiptΛୟ͍͓͖͍ͯͨ ؾ࣋ͪʹͳΔ͕ɺՌͨͯ͠…) New -
ࠓळ͔Β৽͘͠Ճ͞ΕΔGrace Period • WWDC19 ʮIn-App Purchases and Using Server-to-Server Notificationsʯʹͯൃද
• Billing ErrorʹΑΔղϢʔβʔΛݮΒ͘͢ɺ༗ޮظݶʹରͯ࣋ͨͤ͠Δ༛༧ظؒ • ࣮WWDC18ͷηογϣϯʮEngineering SubscriptionsʯͰࣗલͰͷ࣮Λ ਪ͞Ε͍ͯͨ • ΈͯͶͰࣗલͰ࣮ࡁΈʂ • Grace PeriodظؒதαʔϏεΛఏڙͯ͋͛͠Δ͜ͱͰɺղΛݮΒͭͭ͠ɺͦ ͷظؒͷՃใुಘΔ͜ͱ͕Ͱ͖Δ • Appleͷ౷ܭͱͯ͠ 16ؒ ͕ߋ৽ʹࣦഊͨ͠Ϣʔβʔͷ80%ΛऔΓͤΔظؒͷ Α͏Ͱɺͦ͜ΛGrace Periodͱͯ͠ఆΊ͍ͯΔ New -
Grace Periodͷ࣮ํ๏ • WWDCͷηογϣϯͰड़ΒΕ͍ͯͨͷҎԼͷ3Step ͚ͩͰ͋Δ 1. App Store ConnectͰGrace PeriodΛߏ
2. /verifyReceipt ͷ৽͍͠ϑΟʔϧυ grace_period_expires_date ΛϋϯυϦϯά 3. Graceظؒ·ͰαʔϏεΛఏڙ͠ଓ͚ΔΑ͏ʹ࣮ -
Status Pollingͱʁ • ظݶΕલޙͷϨγʔτΛ /verifyReceipt ʹରͯ͠tokenͷΑ͏ʹ͛Δ͜ ͱͰ࠷৽ϨγʔτΛऔಘͯ͠dbͷঢ়ଶΛߋ৽͢Δ͜ͱΛࢦ͢ • ϨγʔτΞϓϦͷىಈ(͋Δ͍αʔόʔؒ௨)ʹΑͬͯͷΈߋ৽͞ΕΔ ͷͰɺ͠WebͰར༻Ͱ͖ΔαʔϏεΛఏڙ͍ͯ͠Δ߹ɺStatus
Polling Ͱ࠷৽ͷঢ়ଶΛҡ࣋͢Δඞཁ͕͋Δ • ΈͯͶϓϨϛΞϜͰWeb্ͷػೳΛఏڙ͍ͯͨͨ͠Ί ࣮͓ͯ͘͠ඞཁ͕͋ͬͨ • ͪͳΈʹ͜ͷख๏WWDC18 ʮEngineering SubscriptionsʯʹΑͬͯޠΒ ΕΔͷΈͰυΩϡϝϯτԽ͞Ε͍ͯͳ͍ -
͍ͭStatus PollingΛ࣮ߦ͖͔͢ • WWDC2018 ʮEngineering SubscriptionsʯͰҎԼͷΑ͏ʹظݶ͕ΕΔલޙͷλ ΠϛϯάͰ͢ΔΑ͏ʹݴΘΕ͍ͯΔ • ΈͯͶͰGrace Periodத͓ΑͼGrace
Period͕ऴྃͯ͠1ޙͷϢʔβʔʹରͯ͠ຖ ே࣮ߦ͍ͯ͠Δ IUUQTEFWFMPQFSBQQMFDPNWJEFPTQMBZXXED -
Sandboxڥ • App Store ConnectͰςελʔΞΧϯτΛ࡞͢ Δ͜ͱͰςετڥͰಈ࡞֬ೝ͕Ͱ͖Δ • ʮUsers and Accessʯͷϖʔδ͔Β؆୯ʹ࡞Մೳ
• ֤SandboxϢʔβʔʹରͯ͠طଘͷApple IDʹඥͮ ͚ΒΕ͍ͯͳ͍ϝʔϧΞυϨεΛઃఆ • νʔϜͰڞ༗͓ͯ͘͠ͱΑ͍ -
Sandboxڥʁຊ൪ڥʁʁ • αʔόʔؒ௨ • ઃఆͰ͖ΔURL̍ͭͳͷͰɺຊ൪αʔόʔʹͷΈ௨POST͞ΕΔ • POST͞Εͨ௨ͷenvironmentΛݟͯ `Sandbox` Ͱ͋Ε։ൃαʔόʔϦμ ΠϨΫτ͢Δඞཁ͕͋Δ
• /verifyReceipt • ຊ൪: https://buy.itunes.apple.com/verifyReceipt Sandbox: https://sandbox.itunes.apple.com/verifyReceipt • Appleͷ৹ࠪSandboxڥͰߦΘΕΔ •ʮΈͯͶ: ຊ൪ + Receipt: Sandboxʯ ͷঢ়گ͕ੜ·ΕΔ • ҰϨγʔτΛຊ൪ʹPOSTͯ͠20007ͷΤϥʔίʔυ͕ฦ͖ͬͯͨΒSandbox ʹ͛͢ඞཁ͕͋Δ -
Promotion CodeΛͬͯຊ൪֬ೝ • ਃͷ௨ͬͨΞϓϦͳͲΛެ։લͰ͢Δ͜ͱ͕Ͱ ͖Δͷ͕Promotion Code • ʮજࡏސ٬ϨϏϡʔهࣄ࡞ऀͷͳͲϓϩϞʔ γϣϯͷͨΊͷ༻ʯ͕ఆ͞Ε͍ͯΔ •
͕ɺ͜ΕͳΒຊ൪ڥͷϨγʔτ͕खʹೖΔʂ • ΈͯͶͰ͜ΕʹΑΓαʔόʔଆͷͪΐͬͱͨ͠όάΛ ϦϦʔεલʹચ͍ग़ͨ͠ -
RestoreϘλϯඞਢ • Auto-Renewable(or Non-Renewing or Non-Consumable) ͷ՝ۚͷ߹RestoreػೳΛ࣮͢Δඞཁ͕͋Δ • ResotreϘλϯ͕ͳ͍ͱϦδΣΫτ͞ΕΔ •
Restoreͷ࣮ʹ2ύλʔϯ͋Δ 1. Restore Completed Transactions 2. Refresh Receipt -
1. Restore Completed Transactions • finishTransaction() ʹΑͬͯྃͤͯ͞͠·ͬ ͨtransactionΛ෮ݩ͢Δํ๏ SKPaymentQueue.default().restoreCompletedTransactions() •
restore͕ྃͨ͠ΒϨγʔτΛόϦσʔγϣ ϯʹ௨্ͨ͠ͰfinishTransaction() ͯ͋͛͠Δ -
2. Refresh Receipt • Apple͔Β࠷৽ͷϨγʔτΛཁٻͯ͠ϩʔΧϧʹอଘ͢Δ • ৽͘͠transactionΛൃߦ͠ͳ͍ // Keep a
strong reference to the product request. var request: SKProductsRequest! func requestProductInformation(with productIdentifiers: [String]) { let productIdentifiers = Set(productIdentifiers) request = SKReceiptRefreshRequest( receiptProperties: [SKReceiptPropertyIsExpired: false] ) request.delegate = self request.start() } -
2. Refresh Receipt • Apple͔Β࠷৽ͷϨγʔτΛཁٻͯ͠ϩʔΧϧʹอଘ͢Δ • ৽͘͠transactionΛൃߦ͠ͳ͍ // Keep a
strong reference to the product request. var request: SKProductsRequest! func requestProductInformation(with productIdentifiers: [String]) { let productIdentifiers = Set(productIdentifiers) request = SKReceiptRefreshRequest( receiptProperties: [SKReceiptPropertyIsExpired: false] ) request.delegate = self request.start() } validationʹޭ͠ͳ͍ݶΓfinishTransaction() Λݺͼग़͢͜ͱͳ͍ͷͰɺΈͯͶͰRefresh ReceiptʹΑͬͯRestoreΛ࣮͍ͯ͠Δ -
ߪೖγʔϯʹද͖ࣔ͢ใΛ࿙Εͳ͘ ྻڍ͢Δ • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπα ʔϏε • Ϣʔβʔͷٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛཧ͢Δํ๏ • ߪೖ֬ఆ࣌ʹɺࢧ͍ϢʔβʔͷApple IDΞΧϯτʹ՝ۚ͞ΕΔ͜ͱ
• ݱࡏͷظ͕ؒऴྃ͢Δগͳ͘ͱ24࣌ؒલʹϢʔβʔ͕Ωϟϯηϧ͠ͳ͍ݶΓɺαϒεΫ Ϧϓγϣϯࣗಈతʹߋ৽͞ΕΔ͜ͱ -
ߪೖγʔϯʹද͖ࣔ͢ใΛ࿙Εͳ͘ ྻڍ͢Δ • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπα ʔϏε • Ϣʔβʔͷٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛཧ͢Δํ๏ • ߪೖ֬ఆ࣌ʹɺࢧ͍ϢʔβʔͷApple IDΞΧϯτʹ՝ۚ͞ΕΔ͜ͱ
• ݱࡏͷظ͕ؒऴྃ͢Δগͳ͘ͱ24࣌ؒલʹϢʔβʔ͕Ωϟϯηϧ͠ͳ͍ݶΓɺαϒεΫ Ϧϓγϣϯࣗಈతʹߋ৽͞ΕΔ͜ͱ • ݱࡏͷظ͕ؒऴྃ͢Δલͷ24࣌ؒҎʹɺΞΧϯτʹߋ৽ֹ͕ۚ՝ۚ͞ΕΔ͜ͱ -
ߪೖγʔϯʹද͖ࣔ͢ใΛ࿙Εͳ͘ ྻڍ͢Δ • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπα ʔϏε • Ϣʔβʔͷٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛཧ͢Δํ๏ • ߪೖ֬ఆ࣌ʹɺࢧ͍ϢʔβʔͷApple IDΞΧϯτʹ՝ۚ͞ΕΔ͜ͱ
• ݱࡏͷظ͕ؒऴྃ͢Δগͳ͘ͱ24࣌ؒલʹϢʔβʔ͕Ωϟϯηϧ͠ͳ͍ݶΓɺαϒεΫ Ϧϓγϣϯࣗಈతʹߋ৽͞ΕΔ͜ͱ • ݱࡏͷظ͕ؒऴྃ͢Δલͷ24࣌ؒҎʹɺΞΧϯτʹߋ৽ֹ͕ۚ՝ۚ͞ΕΔ͜ͱ • ϢʔβʔApp StoreͷΞΧϯτઃఆͰαϒεΫϦϓγϣϯΛཧ͓ΑͼΩϟϯηϧͰ ͖Δ͜ͱ -
ߪೖγʔϯʹද͖ࣔ͢ใΛ࿙Εͳ͘ ྻڍ͢Δ • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπα ʔϏε • Ϣʔβʔͷٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛཧ͢Δํ๏ • ߪೖ֬ఆ࣌ʹɺࢧ͍ϢʔβʔͷApple IDΞΧϯτʹ՝ۚ͞ΕΔ͜ͱ
• ݱࡏͷظ͕ؒऴྃ͢Δগͳ͘ͱ24࣌ؒલʹϢʔβʔ͕Ωϟϯηϧ͠ͳ͍ݶΓɺαϒεΫ Ϧϓγϣϯࣗಈతʹߋ৽͞ΕΔ͜ͱ • ݱࡏͷظ͕ؒऴྃ͢Δલͷ24࣌ؒҎʹɺΞΧϯτʹߋ৽ֹ͕ۚ՝ۚ͞ΕΔ͜ͱ • ϢʔβʔApp StoreͷΞΧϯτઃఆͰαϒεΫϦϓγϣϯΛཧ͓ΑͼΩϟϯηϧͰ ͖Δ͜ͱ • Appͷར༻نͷϦϯΫ -
ߪೖγʔϯʹද͖ࣔ͢ใΛ࿙Εͳ͘ ྻڍ͢Δ • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπα ʔϏε • Ϣʔβʔͷٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛཧ͢Δํ๏ • ߪೖ֬ఆ࣌ʹɺࢧ͍ϢʔβʔͷApple IDΞΧϯτʹ՝ۚ͞ΕΔ͜ͱ
• ݱࡏͷظ͕ؒऴྃ͢Δগͳ͘ͱ24࣌ؒલʹϢʔβʔ͕Ωϟϯηϧ͠ͳ͍ݶΓɺαϒεΫ Ϧϓγϣϯࣗಈతʹߋ৽͞ΕΔ͜ͱ • ݱࡏͷظ͕ؒऴྃ͢Δલͷ24࣌ؒҎʹɺΞΧϯτʹߋ৽ֹ͕ۚ՝ۚ͞ΕΔ͜ͱ • ϢʔβʔApp StoreͷΞΧϯτઃఆͰαϒεΫϦϓγϣϯΛཧ͓ΑͼΩϟϯηϧͰ ͖Δ͜ͱ • Appͷར༻نͷϦϯΫ • ݱଘͷར༻ऀ͕αΠϯΠϯͨ͠ΓߪೖΛ෮ݩͨ͠Γ͢Δํ๏ -
ߪೖγʔϯʹද͖ࣔ͢ใΛ࿙Εͳ͘ ྻڍ͢Δ • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπα ʔϏε • Ϣʔβʔͷٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛཧ͢Δํ๏ • ߪೖ֬ఆ࣌ʹɺࢧ͍ϢʔβʔͷApple IDΞΧϯτʹ՝ۚ͞ΕΔ͜ͱ
• ݱࡏͷظ͕ؒऴྃ͢Δগͳ͘ͱ24࣌ؒલʹϢʔβʔ͕Ωϟϯηϧ͠ͳ͍ݶΓɺαϒεΫ Ϧϓγϣϯࣗಈతʹߋ৽͞ΕΔ͜ͱ • ݱࡏͷظ͕ؒऴྃ͢Δલͷ24࣌ؒҎʹɺΞΧϯτʹߋ৽ֹ͕ۚ՝ۚ͞ΕΔ͜ͱ • ϢʔβʔApp StoreͷΞΧϯτઃఆͰαϒεΫϦϓγϣϯΛཧ͓ΑͼΩϟϯηϧͰ ͖Δ͜ͱ • Appͷར༻نͷϦϯΫ • ݱଘͷར༻ऀ͕αΠϯΠϯͨ͠ΓߪೖΛ෮ݩͨ͠Γ͢Δํ๏ • ϓϥΠόγʔϙϦγʔͷϦϯΫ -
·ͱΊ • ෳࡶͳཁ͕݅͋ΔΘ͚Ͱͳ͍ͷʹʮAuto-Renewable SubscriptionsʯͰ͋Δͱ͍͏͚ͩͰ࣮͢Δ͜ͱ݁ߏଟ͍ • αʔόʔؒ௨ʹ৽͍͠௨λΠϓ͕Ճ͞Εɺ࣮optional ͱݴΘΕ͍ͯΔ͕ɺ΄΅mustʹͳΓͭͭ͋Δ (ओ؍) • Grace
PeriodղվળͱͦͷظؒใुΛΒ͑ΔͷͰɺ࣮ ͠ͳ͍खͳ͍ • ϦδΣΫτඞͣ͞ΕΔͷͱࢥͬͯϦϦʔεϓϥϯχϯά͢Δ