Upgrade to Pro — share decks privately, control downloads, hide ads and more …

詳解 Auto-Renewable Subscription / Detailed Auto-...

rockname
September 06, 2019

詳解 Auto-Renewable Subscription / Detailed Auto-Renewable Subscription

rockname

September 06, 2019
Tweet

More Decks by rockname

Other Decks in Programming

Transcript

  1. ࣗݾ঺հ • גࣜձࣾϛΫγΟ • Ո଒ΞϧόϜΈͯͶ / ΞϓϦ։ൃάϧʔϓ
 iOS(Swift, obj-c) >

    Android(Kotlin, Java) == Rails(ruby) • VTuber͕޷͖ • WWDC 2019 ࢀՃ͠·ͨ͠ ✈ ϩΫωϜ@rockname
  2. ࠓ೔࿩͢͜ͱ 1. Auto-Renewable Subscriptionsͱ͸ʁ 2. ΈͯͶʹ͓͚ΔಋೖͷܦҢ 3. جຊతͳ࣮૷ํ๏ 4. αʔόʔؒ௨஌ʹ͍ͭͯ

    5. Status Pollingʹ͍ͭͯ 6. Sandbox؀ڥʹ͓͚Δಈ࡞֬ೝ 7. ϦδΣΫτʹ͍ͭͯؾΛ͚͍ͭͨϙΠϯτ 8. ·ͱΊ
  3. લ४උ • App಺՝ۚΛ࡞੒ • Reference Name / Product ID •

    Price / Duration • Subscription Display Name • Subscription GroupΛ࡞੒ • Reference Name • Subscription Group Display Name ʮ"QQ4UPSF$POOFDUϔϧϓʯΑΓ IUUQTIFMQBQQMFDPNBQQTUPSFDPOOFDUEFWGDF App Store Connect্ͰϓϩμΫτΛ࡞੒
  4. 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. } }
  5. 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͸γϯάϧτϯͰ࡞Δ
  6. 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ʹ௥Ճ
  7. 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͔Β࡟আ
  8. App Store͔ΒProduct InformationΛऔಘ • SKPaymentQueue.canMakePayments() ͰϢʔβʔ͕ߪೖͰ͖Δ ͔֬ೝ • App Store

    ConnectͰొ࿥ͨ͠product identifierΛ࢖༻ͯ͠App Store͔ΒϓϩμΫτ৘ใΛऔಘ͢Δ • product identifierͷ؅ཧʹ͸2ύλʔϯ͋Δ 1. BundleʹؚΊΔ 2. αʔόʔͰ؅ཧ͢Δ
  9. App Store͔ΒProduct InformationΛऔಘ • SKPaymentQueue.canMakePayments() ͰϢʔβʔ͕ߪೖͰ͖Δ ͔֬ೝ • App Store

    ConnectͰొ࿥ͨ͠product identifierΛ࢖༻ͯ͠App Store͔ΒϓϩμΫτ৘ใΛऔಘ͢Δ • product identifierͷ؅ཧʹ͸2ύλʔϯ͋Δ 1. BundleʹؚΊΔ 2. αʔόʔͰ؅ཧ͢Δ ΈͯͶͰ͸ ʮ2. αʔόʔͰ؅ཧ͢ΔʯͰ࣮૷
  10. // 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() }
  11. // 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() } ϦΫΤετ͸ڧࢀরͰอ࣋͢Δඞཁ͕͋Δ
  12. // 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Ληοτͯ͠ϦΫΤετΛ։࢝
  13. // 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. } }
  14. // 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ʹදࣔ
  15. // 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ʹඈ͹͍ͯ͠Δ
  16. TransactionΛॲཧ͢Δ • SKPaymentTransactionObserver#paymentQueue(_:updatedTransactio ns:) ʹtransaction͕ྲྀΕͯ͘Δ • transactionͷ SKPaymentTransactionState ʹԠͯ͡ద੾ʹॲཧ •

    transaction͸ finishTransaction() ͠ͳ͍͔͗Γqueueʹ࢒Γଓ͚Δ • ߪೖ్தʹΞϓϦΛऴྃͤͯ͞͠·ͬͨϢʔβʔ͸࣍ճىಈ࣌ʹ෮ ؼͰ͖Δ • updatedTransactions Λ͍ͭͰ΋ॲཧͯ݁͠ՌΛϢʔβʔʹ൓өͤ͞ Δ͜ͱ͕Ͱ͖Δঢ়ଶΛߏங͢Δඞཁ͕͋Δ
  17. 4,1BZNFOU
 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ

    ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹ੒ޭ લճͷߪೖͷ෮ݩʹ ੒ޭ 6*΁ͷ൓ө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹ΋൓ө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ   pOJTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO
  18. 4,1BZNFOU
 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ

    ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹ੒ޭ લճͷߪೖͷ෮ݩʹ ੒ޭ 6*΁ͷ൓ө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹ΋൓ө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ   pOJTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO
  19. 4,1BZNFOU
 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ

    ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹ੒ޭ લճͷߪೖͷ෮ݩʹ ੒ޭ 6*΁ͷ൓ө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹ΋൓ө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ   pOJTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO
  20. 4,1BZNFOU
 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ

    ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹ੒ޭ લճͷߪೖͷ෮ݩʹ ੒ޭ 6*΁ͷ൓ө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹ΋൓ө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ   pOJTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO
  21. 4,1BZNFOU
 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ

    ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹ੒ޭ લճͷߪೖͷ෮ݩʹ ੒ޭ 6*΁ͷ൓ө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹ΋൓ө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ   pOJTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO
  22. 4,1BZNFOU
 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ

    ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹ੒ޭ લճͷߪೖͷ෮ݩʹ ੒ޭ 6*΁ͷ൓ө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹ΋൓ө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ   pOJTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO
  23. 4,1BZNFOU
 5SBOTBDUJPO4UBUF QVSDIBTJOH EFGFSSFE GBJMFE QVSDIBTFE SFTUPSFE ঢ়ଶ ߪೖத ʮ"TLUP#VZʯʹΑ

    ͬͯࢠڙ͕ߪೖΛ੍ ݶ͞Ε͍ͯΔঢ়ଶ ߪೖʹࣦഊ ߪೖʹ੒ޭ લճͷߪೖͷ෮ݩʹ ੒ޭ 6*΁ͷ൓ө ߪೖதͷϓϩάϨε Λදࣔ͢Δ 6*ʹ΋൓ө FSSPSʹΑͬͯϝοη ʔδΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ ϨγʔτͷWBMJEBUJPO ݁ՌΛදࣔ USBOTBDUJPOͷॲཧ   pOJTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO WBMJEBUJPOͷ੒ޭ͋Δ ͍͸Ϩγʔτ͕ظݶ ੾Εͷ৔߹͸ pOTI5SBOTBDUJPO ΈͯͶͰ͸ ʮUI΁ͷ൓өʯͱʮtransactionͷॲཧʯ͸ ׬શʹಠཱ࣮ͯ͠ߦ͍ͯ͠Δ
  24. // 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) } }
  25. // 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) } } Ϩγʔτ͕ଘࡏ͍ͯ͠Ε͹औಘ
  26. // 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ͨ͠ϨγʔτΛ Ϣʔβʔ৘ใͱڞʹServer΁POST
  27. /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͢Δͱྑ͍ɻ
  28. ൪֎ฤ: ΤϥʔϋϯυϦϯά • ൃੜ͠͏ΔΤϥʔͷछྨ 1. transactionState ͕ .failed ͷঢ়ଶ 2.

    transactionState ͸ .purchased ͕ͩϨ γʔτͷόϦσʔγϣϯʹࣦഊ
  29. 1. transactionState͕ .failed ͷঢ়ଶ • AppStoreଆͷΤϥʔͳͷͰɺ localizedDescription ΛAlertʹ දࣔͯ͋͛͠Δ •

    ͨͩ͠ɺߪೖ్தʹΩϟϯηϧΛԡͯ͠΋transaction ͸ .failed ѻ͍ͱͳΔͷͰguardͯ͋͛͠Δඞཁ͕͋Δ
  30. 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) } }
  31. 2. transactionState͸ .purchased ͕ͩϨγʔ τͷόϦσʔγϣϯʹࣦഊ • ΈͯͶͷ৔߹2ύλʔϯͷΤϥʔΛϋϯυϦϯά 1. αʔόʔଆͰͷ༧ଌෆೳͳΤϥʔ 2.

    ड͚औͬͨϨγʔτ͕͢ͰʹϢʔβʔ(Ո଒)ͱ
 ඥ͍͍ͮͯΔ৔߹ͷΤϥʔ • restoreʹΑͬͯαϒεΫϦϓγϣϯΛ෮ݩ͢Δࡍ ʹൃੜ͠͏Δ (restoreʹ͍ͭͯ͸ޙ΄Ͳ࿩͠·͢)
  32. αʔόʔؒ௨஌Λड͚औΔ • 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: ࣗಈߋ৽ͷঢ়ଶΛߋ৽ͨ͠೔࣌
  33. αʔόʔؒ௨஌Λड͚औΔ • 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ʹ౤͛Δϑϩʔʹ৐ͤΔ
 Α͏ʹ࣮૷
  34. ͦΕͧΕͷ௨஌λΠϓͷछྨ */*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Λݟ͍ͯͭมߋ͕͋ͬ ͨͷ͔ɺݱࡏͷࣗಈߋ৽ͷঢ়ଶΛ֬ೝ͢Δɻ
  35. ࠓळ͔Β௥Ճ͞ΕΔ৽͍͠௨஌λΠϓ • WWDC19 ʮIn-App Purchases and Using Server-to-Server Notificationsʯʹͯൃද •

    ҎԼͷ3͕ͭ৽͘͠ՃΘΔ • DID_FAIL_TO_RENEW • DID_RECOVER • PRICE_INCREASE_CONSENT • DID_RECOVER͸طଘͷRENEWAL௨஌λΠϓͱஔ͖׵ΘΔ New
  36. ࠓळ͔Β৽͘͠௥Ճ͞ΕΔunified_receipt • WWDC19ʮIn-App Purchases and Using Server-to-Server Notificationsʯ ʹͯൃද •

    unified_receipt ͱ͍͏৽͍͠ϑΟʔϧυ͕௥Ճ͞ΕΔ • /verifyReceipt Λୟ͔ͳ͍ͱಘΒΕͳ͔ͬͨϑΟʔϧυΛऔಘͰ͖Δ • ΋͏ latest_receipt ͱ latest_receipt_info Λࢀর͢Δඞཁ͕ͳ͘ɺ / verifyReceiptΛୟ͘ඞཁ΋ͳ͍ • (receiptِ͕૷͞ΕΔՄೳੑΛߟ͑Δͱ/verifyReceiptΛୟ͍͓͖͍ͯͨ ؾ࣋ͪʹͳΔ͕ɺՌͨͯ͠…) New
  37. ࠓळ͔Β৽͘͠௥Ճ͞ΕΔ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
  38. Grace Periodͷ࣮૷ํ๏ • WWDCͷηογϣϯͰड़΂ΒΕ͍ͯͨͷ͸ҎԼͷ3Step ͚ͩͰ͋Δ 1. App Store ConnectͰGrace PeriodΛߏ੒

    2. /verifyReceipt ͷ৽͍͠ϑΟʔϧυ grace_period_expires_date ΛϋϯυϦϯά 3. Graceظؒ·ͰαʔϏεΛఏڙ͠ଓ͚ΔΑ͏ʹ࣮૷
  39. Status Pollingͱ͸ʁ • ظݶ੾ΕલޙͷϨγʔτΛ /verifyReceipt ʹରͯ͠tokenͷΑ͏ʹ౤͛Δ͜ ͱͰ࠷৽ϨγʔτΛऔಘͯ͠dbͷঢ়ଶΛߋ৽͢Δ͜ͱΛࢦ͢ • Ϩγʔτ͸ΞϓϦͷىಈ(͋Δ͍͸αʔόʔؒ௨஌)ʹΑͬͯͷΈߋ৽͞ΕΔ ͷͰɺ΋͠WebͰར༻Ͱ͖ΔαʔϏεΛఏڙ͍ͯ͠Δ৔߹ɺStatus

    Polling Ͱ࠷৽ͷঢ়ଶΛҡ࣋͢Δඞཁ͕͋Δ • ΈͯͶϓϨϛΞϜͰ͸Web্ͷػೳΛఏڙ͍ͯͨͨ͠Ί
 ࣮૷͓ͯ͘͠ඞཁ͕͋ͬͨ • ͪͳΈʹ͜ͷख๏͸WWDC18 ʮEngineering SubscriptionsʯʹΑͬͯޠΒ ΕΔͷΈͰυΩϡϝϯτԽ͞Ε͍ͯͳ͍
  40. Sandbox؀ڥ • App Store ConnectͰςελʔΞΧ΢ϯτΛ࡞੒͢ Δ͜ͱͰςετ؀ڥͰಈ࡞֬ೝ͕Ͱ͖Δ • ʮUsers and Accessʯͷϖʔδ͔Β؆୯ʹ࡞੒Մೳ

    • ֤SandboxϢʔβʔʹରͯ͠طଘͷApple IDʹඥͮ ͚ΒΕ͍ͯͳ͍ϝʔϧΞυϨεΛઃఆ • νʔϜͰڞ༗͓ͯ͘͠ͱΑ͍
  41. 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 ʹ౤͛௚͢ඞཁ͕͋Δ
  42. 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() }
  43. 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Λ࣮૷͍ͯ͠Δ
  44. ߪೖγʔϯʹදࣔ͢΂͖৘ใΛ࿙Εͳ͘ ྻڍ͢Δ • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπ΍α ʔϏε • Ϣʔβʔ΁ͷ੥ٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛ؅ཧ͢Δํ๏ • ߪೖ֬ఆ࣌ʹɺࢧ෷͍͸ϢʔβʔͷApple IDΞΧ΢ϯτʹ՝ۚ͞ΕΔ͜ͱ

    • ݱࡏͷظ͕ؒऴྃ͢Δগͳ͘ͱ΋24࣌ؒલʹϢʔβʔ͕Ωϟϯηϧ͠ͳ͍ݶΓɺαϒεΫ Ϧϓγϣϯ͸ࣗಈతʹߋ৽͞ΕΔ͜ͱ • ݱࡏͷظ͕ؒऴྃ͢Δલͷ24࣌ؒҎ಺ʹɺΞΧ΢ϯτʹߋ৽ֹ͕ۚ՝ۚ͞ΕΔ͜ͱ
  45. ߪೖγʔϯʹදࣔ͢΂͖৘ใΛ࿙Εͳ͘ ྻڍ͢Δ • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπ΍α ʔϏε • Ϣʔβʔ΁ͷ੥ٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛ؅ཧ͢Δํ๏ • ߪೖ֬ఆ࣌ʹɺࢧ෷͍͸ϢʔβʔͷApple IDΞΧ΢ϯτʹ՝ۚ͞ΕΔ͜ͱ

    • ݱࡏͷظ͕ؒऴྃ͢Δগͳ͘ͱ΋24࣌ؒલʹϢʔβʔ͕Ωϟϯηϧ͠ͳ͍ݶΓɺαϒεΫ Ϧϓγϣϯ͸ࣗಈతʹߋ৽͞ΕΔ͜ͱ • ݱࡏͷظ͕ؒऴྃ͢Δલͷ24࣌ؒҎ಺ʹɺΞΧ΢ϯτʹߋ৽ֹ͕ۚ՝ۚ͞ΕΔ͜ͱ • Ϣʔβʔ͸App StoreͷΞΧ΢ϯτઃఆͰαϒεΫϦϓγϣϯΛ؅ཧ͓ΑͼΩϟϯηϧͰ ͖Δ͜ͱ
  46. ߪೖγʔϯʹදࣔ͢΂͖৘ใΛ࿙Εͳ͘ ྻڍ͢Δ • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπ΍α ʔϏε • Ϣʔβʔ΁ͷ੥ٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛ؅ཧ͢Δํ๏ • ߪೖ֬ఆ࣌ʹɺࢧ෷͍͸ϢʔβʔͷApple IDΞΧ΢ϯτʹ՝ۚ͞ΕΔ͜ͱ

    • ݱࡏͷظ͕ؒऴྃ͢Δগͳ͘ͱ΋24࣌ؒલʹϢʔβʔ͕Ωϟϯηϧ͠ͳ͍ݶΓɺαϒεΫ Ϧϓγϣϯ͸ࣗಈతʹߋ৽͞ΕΔ͜ͱ • ݱࡏͷظ͕ؒऴྃ͢Δલͷ24࣌ؒҎ಺ʹɺΞΧ΢ϯτʹߋ৽ֹ͕ۚ՝ۚ͞ΕΔ͜ͱ • Ϣʔβʔ͸App StoreͷΞΧ΢ϯτઃఆͰαϒεΫϦϓγϣϯΛ؅ཧ͓ΑͼΩϟϯηϧͰ ͖Δ͜ͱ • Appͷར༻ن໿΁ͷϦϯΫ
  47. ߪೖγʔϯʹදࣔ͢΂͖৘ใΛ࿙Εͳ͘ ྻڍ͢Δ • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπ΍α ʔϏε • Ϣʔβʔ΁ͷ੥ٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛ؅ཧ͢Δํ๏ • ߪೖ֬ఆ࣌ʹɺࢧ෷͍͸ϢʔβʔͷApple IDΞΧ΢ϯτʹ՝ۚ͞ΕΔ͜ͱ

    • ݱࡏͷظ͕ؒऴྃ͢Δগͳ͘ͱ΋24࣌ؒલʹϢʔβʔ͕Ωϟϯηϧ͠ͳ͍ݶΓɺαϒεΫ Ϧϓγϣϯ͸ࣗಈతʹߋ৽͞ΕΔ͜ͱ • ݱࡏͷظ͕ؒऴྃ͢Δલͷ24࣌ؒҎ಺ʹɺΞΧ΢ϯτʹߋ৽ֹ͕ۚ՝ۚ͞ΕΔ͜ͱ • Ϣʔβʔ͸App StoreͷΞΧ΢ϯτઃఆͰαϒεΫϦϓγϣϯΛ؅ཧ͓ΑͼΩϟϯηϧͰ ͖Δ͜ͱ • Appͷར༻ن໿΁ͷϦϯΫ • ݱଘͷར༻ऀ͕αΠϯΠϯͨ͠ΓߪೖΛ෮ݩͨ͠Γ͢Δํ๏
  48. ߪೖγʔϯʹදࣔ͢΂͖৘ใΛ࿙Εͳ͘ ྻڍ͢Δ • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπ΍α ʔϏε • Ϣʔβʔ΁ͷ੥ٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛ؅ཧ͢Δํ๏ • ߪೖ֬ఆ࣌ʹɺࢧ෷͍͸ϢʔβʔͷApple IDΞΧ΢ϯτʹ՝ۚ͞ΕΔ͜ͱ

    • ݱࡏͷظ͕ؒऴྃ͢Δগͳ͘ͱ΋24࣌ؒલʹϢʔβʔ͕Ωϟϯηϧ͠ͳ͍ݶΓɺαϒεΫ Ϧϓγϣϯ͸ࣗಈతʹߋ৽͞ΕΔ͜ͱ • ݱࡏͷظ͕ؒऴྃ͢Δલͷ24࣌ؒҎ಺ʹɺΞΧ΢ϯτʹߋ৽ֹ͕ۚ՝ۚ͞ΕΔ͜ͱ • Ϣʔβʔ͸App StoreͷΞΧ΢ϯτઃఆͰαϒεΫϦϓγϣϯΛ؅ཧ͓ΑͼΩϟϯηϧͰ ͖Δ͜ͱ • Appͷར༻ن໿΁ͷϦϯΫ • ݱଘͷར༻ऀ͕αΠϯΠϯͨ͠ΓߪೖΛ෮ݩͨ͠Γ͢Δํ๏ • ϓϥΠόγʔϙϦγʔ΁ͷϦϯΫ