$30 off During Our Annual Pro Sale. View Details »

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

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. ৄղ
    Auto-Renewable
    Subscription
    iOSDC Japan 2019
    2019/09/06
    גࣜձࣾϛΫγΟ ΈͯͶࣄۀ෦
    ϩΫωϜ @rockname

    View Slide

  2. ࣗݾ঺հ
    • גࣜձࣾϛΫγΟ
    • Ո଒ΞϧόϜΈͯͶ / ΞϓϦ։ൃάϧʔϓ

    iOS(Swift, obj-c) > Android(Kotlin, Java) == Rails(ruby)
    • VTuber͕޷͖
    • WWDC 2019 ࢀՃ͠·ͨ͠ ✈
    ϩΫωϜ@rockname

    View Slide

  3. ࠓ೔࿩͢͜ͱ
    1. Auto-Renewable Subscriptionsͱ͸ʁ
    2. ΈͯͶʹ͓͚ΔಋೖͷܦҢ
    3. جຊతͳ࣮૷ํ๏
    4. αʔόʔؒ௨஌ʹ͍ͭͯ
    5. Status Pollingʹ͍ͭͯ
    6. Sandbox؀ڥʹ͓͚Δಈ࡞֬ೝ
    7. ϦδΣΫτʹ͍ͭͯؾΛ͚͍ͭͨϙΠϯτ
    8. ·ͱΊ

    View Slide

  4. ࠓ೔࿩͞ͳ͍͜ͱ
    • Subscription Offers
    • Promoted In-App Purchases
    • Sales and TrendsͷϝτϦΫεͷݟํ

    View Slide

  5. Auto-Renewable
    Subscriptionsͱ͸ʁ
    1.

    View Slide

  6. Auto-Renewable Subscription
    • ఆظతʹߋ৽͞ΕΔίϯςϯπʹରͯ͠ఆظ
    ՝ۚ͢ΔɺIn-App Purchasesʹ͓͚ΔҰͭͷ
    ՝ۚํ๏

    View Slide

  7. Auto-Renewable Subscription
    • ఆظతʹߋ৽͞ΕΔίϯςϯπʹରͯ͠ఆظ
    ՝ۚ͢ΔɺIn-App Purchasesʹ͓͚ΔҰͭͷ
    ՝ۚํ๏
    αϒεΫϦϓγϣϯ
    ߪೖ
    Time

    View Slide

  8. Auto-Renewable Subscription
    • ఆظతʹߋ৽͞ΕΔίϯςϯπʹରͯ͠ఆظ
    ՝ۚ͢ΔɺIn-App Purchasesʹ͓͚ΔҰͭͷ
    ՝ۚํ๏
    αϒεΫϦϓγϣϯ αϒεΫϦϓγϣϯ
    ߪೖ ߋ৽
    Time

    View Slide

  9. Auto-Renewable Subscription
    • ఆظతʹߋ৽͞ΕΔίϯςϯπʹରͯ͠ఆظ
    ՝ۚ͢ΔɺIn-App Purchasesʹ͓͚ΔҰͭͷ
    ՝ۚํ๏
    αϒεΫϦϓγϣϯ αϒεΫϦϓγϣϯ αϒεΫϦϓγϣϯ
    ߪೖ ߋ৽ ߋ৽
    Time

    View Slide

  10. ΈͯͶʹ͓͚ΔಋೖͷܦҢ
    2.

    View Slide

  11. ͦ΋ͦ΋ʮΈͯͶʯͬͯԿʁ
    • εϚϗͰࡱͬͨࢠͲ΋ͷࣸਅ΍ಈըΛ؆୯ʹڞ༗ɺ੔ཧͰ͖ΔΞϓϦ
    • ڞ༗͞ΕͨϝσΟΞΛݟͯՈ଒Ͱָ͘͠ձ࿩ͨ͠ΓɺࢠͲ΋ͷ੒௕Λৼ
    ΓฦΔ͜ͱ͕Ͱ͖Δ

    View Slide

  12. ΈͯͶϓϨϛΞϜɺ࢝ಈ
    • Ո଒ͷ͏ͪ1ਓͰ΋՝ۚͨ͠Βར༻Ͱ͖Δఆظߪಡ
    ܕͷϓϨϛΞϜϓϥϯΛ 2019/04/09 ʹϦϦʔε

    View Slide

  13. View Slide

  14. View Slide

  15. View Slide

  16. 8/15ʹϦϦʔε

    View Slide

  17. جຊతͳ࣮૷ํ๏
    3.

    View Slide

  18. લ४උ
    • App಺՝ۚΛ࡞੒
    • Reference Name / Product ID
    • Price / Duration
    • Subscription Display Name
    • Subscription GroupΛ࡞੒
    • Reference Name
    • Subscription Group Display Name
    ʮ"QQ4UPSF$POOFDUϔϧϓʯΑΓ
    IUUQTIFMQBQQMFDPNBQQTUPSFDPOOFDUEFWGDF
    App Store Connect্ͰϓϩμΫτΛ࡞੒

    View Slide

  19. ߪಡ։࢝·Ͱͷγʔέϯε

    View Slide

  20. View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. ֤εςοϓͷ࣮૷ํ๏ʹ͍ͭͯ

    View Slide

  30. View Slide

  31. Transaction ObserverͷηοτΞοϓ
    • transactionΛॲཧ͢ΔͨΊʹ͸·ͣObserverΛੜ
    ੒ͯ͠payment queueʹ௥Ճ͢Δඞཁ͕͋Δ
    • payment queue͔ΒͷΠϕϯτΛऔΓ͜΅͞ͳ͍
    ͨΊʹ΋Observer͸ΞϓϦىಈ௚ޙʹ௥Ճ͢Δ
    IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOTUPSFLJUJO
    BQQ@QVSDIBTF
    TFUUJOH@VQ@UIF@USBOTBDUJPO@PCTFSWFS@BOE@QBZNFOU@RVFVF

    View Slide

  32. 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.
    }
    }

    View Slide

  33. 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͸γϯάϧτϯͰ࡞Δ

    View Slide

  34. 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ʹ௥Ճ

    View Slide

  35. 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͔Β࡟আ

    View Slide

  36. View Slide

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

    View Slide

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

    View Slide

  39. // 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()
    }

    View Slide

  40. // 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()
    }
    ϦΫΤετ͸ڧࢀরͰอ࣋͢Δඞཁ͕͋Δ

    View Slide

  41. // 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Ληοτͯ͠ϦΫΤετΛ։࢝

    View Slide

  42. // 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.
    }
    }

    View Slide

  43. // 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ʹදࣔ

    View Slide

  44. // 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ʹඈ͹͍ͯ͠Δ

    View Slide

  45. View Slide

  46. UIʹϓϩμΫτ৘ใΛදࣔ
    • Human Interface GuidelineΛ
    ख़ಡͯ͠ద੾ͳ৘ใΛදࣔ͢Δ
    • ৘ใ͕࿙Ε͍ͯͨΒଈϦδΣΫ
    τͳͷͰཁ஫ҙ (ৄ͘͠͸ޙ΄
    Ͳ࿩͠·͢)
    IUUQTEFWFMPQFSBQQMFDPNEFTJHOIVNBOJOUFSGBDFHVJEFMJOFT
    TVCTDSJQUJPOTPWFSWJFX

    View Slide

  47. View Slide

  48. App Store΁ߪೖΛϦΫΤετ
    • Ϣʔβʔͷબ୒ͨ͠ϓϩμΫτ͔Βࢧ෷͍Ϧ
    ΫΤετΛ࡞੒
    • ࢧ෷͍ϦΫΤετΛpayment queueʹ௥Ճ

    View Slide

  49. App Store΁ߪೖΛϦΫΤετ
    • Ϣʔβʔͷબ୒ͨ͠ϓϩμΫτ͔Βࢧ෷͍Ϧ
    ΫΤετΛ࡞੒
    • ࢧ෷͍ϦΫΤετΛpayment queueʹ௥Ճ
    let payment = SKPayment(product: selectedProduct)
    SKPaymentQueue.default().add(payment)

    View Slide

  50. View Slide

  51. TransactionΛॲཧ͢Δ
    • SKPaymentTransactionObserver#paymentQueue(_:updatedTransactio
    ns:) ʹtransaction͕ྲྀΕͯ͘Δ
    • transactionͷ SKPaymentTransactionState ʹԠͯ͡ద੾ʹॲཧ
    • transaction͸ finishTransaction() ͠ͳ͍͔͗Γqueueʹ࢒Γଓ͚Δ
    • ߪೖ్தʹΞϓϦΛऴྃͤͯ͞͠·ͬͨϢʔβʔ͸࣍ճىಈ࣌ʹ෮
    ؼͰ͖Δ
    • updatedTransactions Λ͍ͭͰ΋ॲཧͯ݁͠ՌΛϢʔβʔʹ൓өͤ͞
    Δ͜ͱ͕Ͱ͖Δঢ়ଶΛߏங͢Δඞཁ͕͋Δ

    View Slide

  52. 4,1BZNFOU

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

    View Slide

  53. 4,1BZNFOU

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

    View Slide

  54. 4,1BZNFOU

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

    View Slide

  55. 4,1BZNFOU

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

    View Slide

  56. 4,1BZNFOU

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

    View Slide

  57. 4,1BZNFOU

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

    View Slide

  58. 4,1BZNFOU

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

    View Slide

  59. View Slide

  60. ϨγʔτͷόϦσʔγϣϯ (Appଆ)
    • ߪೖ͕׬ྃ͢ΔͱϨγʔτ͕ൃߦ͞ΕΔ
    • Auto-Renewable Subscriptionͷ৔߹ɺ̍ͭͷϨγʔ
    τʹߪೖ৘ใ͕௥ه͞Ε͍ͯ͘
    • Bundle.main.appStoreReceiptURL ʹϨγʔτͷσʔλ
    ͕ଘࡏ͢ΔͷͰऔಘ
    • base64Encodingͨ͠ϨγʔτͷDataΛαʔόʔʹPOST

    View Slide

  61. // 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) }
    }

    View Slide

  62. // 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) }
    }
    Ϩγʔτ͕ଘࡏ͍ͯ͠Ε͹औಘ

    View Slide

  63. // 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

    View Slide

  64. ϨγʔτͷόϦσʔγϣϯ(Serverଆ)
    • Appleͷఏڙ͢ΔAPIͷ /verifyReceipt ʹରͯ͠ઌͷ
    receipt-data ͱ password , exclude-old-transactionsΩʔΛ
    JSONͰPOST
    password͸ͪ͜Β͔Βऔಘ

    View Slide

  65. /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͢Δͱྑ͍ɻ

    View Slide

  66. View Slide

  67. ߪೖ͋Γ͕ͱ͏γʔϯΛදࣔ
    • όϦσʔγϣϯʹ੒ޭ͠
    ͨΒʮߪೖ͋Γ͕ͱ͏ʯ
    γʔϯΛදࣔ͢Δ
    • ΞϓϦ෮ؼ࣌ʹόϦσʔ
    γϣϯʹ੒ޭ͢Δέʔε
    ΋͋ΔͨΊɺಠཱ͍ͯ͠
    ͭͰ΋ग़ͤΔΑ͏ʹͯ͠
    ͓͘

    View Slide

  68. ߪೖ͋Γ͕ͱ͏γʔϯΛදࣔ
    • όϦσʔγϣϯʹ੒ޭ͠
    ͨΒʮߪೖ͋Γ͕ͱ͏ʯ
    γʔϯΛදࣔ͢Δ
    • ΞϓϦ෮ؼ࣌ʹόϦσʔ
    γϣϯʹ੒ޭ͢Δέʔε
    ΋͋ΔͨΊɺಠཱ͍ͯ͠
    ͭͰ΋ग़ͤΔΑ͏ʹͯ͠
    ͓͘
    ͍͍࣮ͩͨ૷Ͱ͖ͨ

    View Slide

  69. ൪֎ฤ: ΤϥʔϋϯυϦϯά
    • ൃੜ͠͏ΔΤϥʔͷछྨ
    1. transactionState ͕ .failed ͷঢ়ଶ
    2. transactionState ͸ .purchased ͕ͩϨ
    γʔτͷόϦσʔγϣϯʹࣦഊ

    View Slide

  70. 1. transactionState͕ .failed ͷঢ়ଶ
    • AppStoreଆͷΤϥʔͳͷͰɺ localizedDescription ΛAlertʹ
    දࣔͯ͋͛͠Δ
    • ͨͩ͠ɺߪೖ్தʹΩϟϯηϧΛԡͯ͠΋transaction
    ͸ .failed ѻ͍ͱͳΔͷͰguardͯ͋͛͠Δඞཁ͕͋Δ

    View Slide

  71. 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)
    }
    }

    View Slide

  72. 2. transactionState͸ .purchased ͕ͩϨγʔ
    τͷόϦσʔγϣϯʹࣦഊ
    • ΈͯͶͷ৔߹2ύλʔϯͷΤϥʔΛϋϯυϦϯά
    1. αʔόʔଆͰͷ༧ଌෆೳͳΤϥʔ
    2. ड͚औͬͨϨγʔτ͕͢ͰʹϢʔβʔ(Ո଒)ͱ

    ඥ͍͍ͮͯΔ৔߹ͷΤϥʔ
    • restoreʹΑͬͯαϒεΫϦϓγϣϯΛ෮ݩ͢Δࡍ
    ʹൃੜ͠͏Δ (restoreʹ͍ͭͯ͸ޙ΄Ͳ࿩͠·͢)

    View Slide

  73. • ӈͷΑ͏ͳAlertΛදࣔ͠ɺ͓໰
    ͍߹Θͤ΁༠ಋͰ͖ΔΑ͏ʹ͠
    ͍ͯΔ
    • ϝʔϥʔͷbodyʹ͸֘౰ͷ
    transactionIdentifierΛ෇༩ͯ͠
    ϢʔβʔΛdb͔ΒḷΕΔΑ͏ʹ
    ͍ͯ͠Δ

    View Slide

  74. αʔόʔؒ௨஌ʹ͍ͭͯ
    4.

    View Slide

  75. αʔόʔؒ௨஌ͱ͸ʁ
    • App Store͔ΒϦΞϧλΠϜʹsubscriptionͷঢ়ଶมԽͷ௨஌
    ΛHTTP POSTͰड͚औΔ࢓૊Έ (statusUpdateNotifications
    ͱݺ͹Εͯͨ)
    • Ϩεϙϯεʹ200Λฦ͞ͳ͍ݶΓ3ճ·ͰϦτϥΠͯ͘͠ΕΔ
    • ࣮૷͸optional͕ͩϨγʔτͷঢ়ଶΛ࠷৽ʹอͬͨΓϢʔ
    βʔʹରͯ͠offerΛग़ͤΔΑ͏ʹ͢ΔͨΊʹ΋΍͓͍ͬͯͨ
    ํ͕͍͍

    View Slide

  76. AppStoreConnectͰURLΛઃఆ
    • ͪ͜ΒͰ༻ҙ͓͍ͯͨ͠POSTΛड͚औΔURLΛ
    AppStoreConnectͰೖྗ
    IUUQTIFMQBQQMFDPNBQQTUPSFDPOOFDUEFWBC

    View Slide

  77. αʔόʔؒ௨஌Λड͚औΔ
    • 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: ࣗಈߋ৽ͷঢ়ଶΛߋ৽ͨ͠೔࣌

    View Slide

  78. αʔόʔؒ௨஌Λड͚औΔ
    • 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ʹ౤͛Δϑϩʔʹ৐ͤΔ

    Α͏ʹ࣮૷

    View Slide

  79. ͦΕͧΕͷ௨஌λΠϓͷछྨ
    */*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Λݟ͍ͯͭมߋ͕͋ͬ
    ͨͷ͔ɺݱࡏͷࣗಈߋ৽ͷঢ়ଶΛ֬ೝ͢Δɻ

    View Slide

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

    View Slide

  81. ͦΕͧΕͷ௨஌λΠϓͷछྨ
    %*%@'"*-@50@3&/&8 ࣗಈߋ৽͠Α͏ͱ͕ࣦͨ͠ഊͨ͠৔߹ʹड͚औΔɻ
    %*%@3&$07&3
    #JMMJOH3FUSZʹΑͬͯαϒεΫϦϓγϣϯͷߋ৽͕

    ੒ޭͨ͠ͱ͖ʹड͚औΔɻ
    13*$&@*/$3&"4&@$0/4&/5
    طଘϢʔβʔʹରͯ͠αϒεΫϦϓγϣϯͷՁ֨ΛҾ্͖͛ͨͱ͖ɺ

    ͓ΑͼͦΕʹϢʔβʔ͕ಉҙͨ͠ͱ͖ʹड͚औΔɻ

    View Slide

  82. ֤௨஌λΠϓͷλΠϛϯά

    View Slide

  83. INITIAL_BUY
    "QQ4UPSF
    1VSDIBTF
    4FSWFS

    View Slide

  84. INITIAL_BUY
    "QQ4UPSF
    1VSDIBTF
    4FSWFS

    View Slide

  85. INITIAL_BUY
    "QQ4UPSF
    1VSDIBTF
    4FSWFS
    */*5*"-@#6:

    View Slide

  86. INITIAL_BUY
    "QQ4UPSF
    1VSDIBTF
    4FSWFS
    CBTFFODPEFE
    SFDFJQU
    */*5*"-@#6:
    WFSJGZ@SFDFJQU

    View Slide

  87. INITIAL_BUY
    "QQ4UPSF
    1VSDIBTF
    4FSWFS
    CBTFFODPEFE
    SFDFJQU
    */*5*"-@#6:
    WFSJGZ@SFDFJQU
    INITIAL_BUYͷ৘ใͱϢʔβʔͷ৘ใΛoriginal_transaction_identifier,
    web_order_line_item_idΛΩʔͱͯͭ͠ͳ͗߹ΘͤΔ

    View Slide

  88. INITIAL_BUY
    "QQ4UPSF
    1VSDIBTF
    4FSWFS
    CBTFFODPEFE
    SFDFJQU
    */*5*"-@#6:
    WFSJGZ@SFDFJQU
    INITIAL_BUYͷ৘ใͱϢʔβʔͷ৘ใΛoriginal_transaction_identifier,
    web_order_line_item_idΛΩʔͱͯͭ͠ͳ͗߹ΘͤΔ
    ݱঢ়ΈͯͶͰ͸ड͚औͬͨINITIAL_BUYΛ
    อଘ͓͍ͯͯ͠ޙ͔Βಥ͖߹ΘͤΔͱ͍͏
    ͜ͱ͸͍ͯ͠·ͤΜ

    View Slide

  89. CANCEL (Upgrade)
    "QQ4UPSF
    6QHSBEF
    4FSWFS

    View Slide

  90. CANCEL (Upgrade)
    "QQ4UPSF
    6QHSBEF
    4FSWFS
    $"/$&-

    View Slide

  91. CANCEL (Cancel Subscription)
    "QQ4UPSF
    $BODFM4VCTDSJQUJPO
    4FSWFS

    View Slide

  92. CANCEL (Cancel Subscription)
    "QQ4UPSF
    $BODFM4VCTDSJQUJPO
    4FSWFS
    $"/$&-

    View Slide

  93. CANCEL (Cancel Subscription)
    "QQ4UPSF
    $BODFM4VCTDSJQUJPO
    4FSWFS
    $"/$&-
    ͲͷϓϩμΫτʹର͍ͯͭ͠Ωϟϯηϧͨ͠ͷ͔ͱ͍͏৘ใΛ
    ֘౰Ϣʔβʔʹରͯ͠ߋ৽͢Δ

    View Slide

  94. INTERACTIVE_RENEWAL
    "QQ4UPSF
    6QHSBEF
    4FSWFS

    View Slide

  95. INTERACTIVE_RENEWAL
    "QQ4UPSF
    6QHSBEF
    4FSWFS
    */5&3"$5*7&
    @3&/&8"-

    View Slide

  96. INTERACTIVE_RENEWAL
    "QQ4UPSF
    6QHSBEF
    4FSWFS
    ͲͷϓϩμΫτʹର͍ͯͭ͠Upgradeͨ͠ͷ͔ͱ͍͏৘ใΛ
    ֘౰Ϣʔβʔʹରͯ͠ߋ৽͢Δ
    */5&3"$5*7&
    @3&/&8"-

    View Slide

  97. INTERACTIVE_RENEWAL
    "QQ4UPSF
    6QHSBEF
    4FSWFS
    ͲͷϓϩμΫτʹର͍ͯͭ͠Upgradeͨ͠ͷ͔ͱ͍͏৘ใΛ
    ֘౰Ϣʔβʔʹରͯ͠ߋ৽͢Δ
    */5&3"$5*7&
    @3&/&8"-
    ݱঢ়ΈͯͶͰ͸ϓϥϯͷGrade͸
    ͔̍ͭ͠ͳ͍ͷͰ
    ͜ͷ௨஌λΠϓ͸ແࢹ͍ͯ͠Δ

    View Slide

  98. DID_CHANGE_RENEWAL_STATUS
    "QQ4UPSF
    4XJUDIBVUPSFOFXJOH
    4FSWFS

    View Slide

  99. DID_CHANGE_RENEWAL_STATUS
    "QQ4UPSF
    4XJUDIBVUPSFOFXJOH
    4FSWFS
    %*%@$)"/(&
    @3&/&8"-

    @45"564

    View Slide

  100. DID_CHANGE_RENEWAL_STATUS
    "QQ4UPSF
    4XJUDIBVUPSFOFXJOH
    4FSWFS
    ࣗಈߋ৽͔Ͳ͏͔ͷϑϥάΛ֘౰Ϣʔβʔʹରͯ͠ߋ৽͢Δ
    %*%@$)"/(&
    @3&/&8"-

    @45"564

    View Slide

  101. DID_FAIL_TO_RENEW
    "QQ4UPSF
    #JMMJOH&SSPS
    4FSWFS

    View Slide

  102. DID_FAIL_TO_RENEW
    "QQ4UPSF
    #JMMJOH&SSPS
    4FSWFS
    %*%@'"*-@50
    @3&/&8

    View Slide

  103. DID_FAIL_TO_RENEW
    "QQ4UPSF
    #JMMJOH&SSPS
    4FSWFS
    %*%@'"*-@50
    @3&/&8
    ֘౰ϢʔβʔͷαϒεΫϦϓγϣϯͷঢ়ଶΛߋ৽͢Δ

    View Slide

  104. DID_RECOVER
    "QQ4UPSF
    #JMMJOH3FUSZ
    4FSWFS

    View Slide

  105. DID_RECOVER
    "QQ4UPSF
    #JMMJOH3FUSZ
    4FSWFS
    %*%
    @3&$07&3

    View Slide

  106. DID_RECOVER
    "QQ4UPSF
    #JMMJOH3FUSZ
    4FSWFS
    ༗ޮظݶͱαϒεΫϦϓγϣϯͷঢ়ଶΛߋ৽͢Δ
    %*%
    @3&$07&3

    View Slide

  107. ࠓळ͔Β৽͘͠௥Ճ͞ΕΔunified_receipt
    • WWDC19ʮIn-App Purchases and Using Server-to-Server Notificationsʯ
    ʹͯൃද
    • unified_receipt ͱ͍͏৽͍͠ϑΟʔϧυ͕௥Ճ͞ΕΔ
    • /verifyReceipt Λୟ͔ͳ͍ͱಘΒΕͳ͔ͬͨϑΟʔϧυΛऔಘͰ͖Δ
    • ΋͏ latest_receipt ͱ latest_receipt_info Λࢀর͢Δඞཁ͕ͳ͘ɺ /
    verifyReceiptΛୟ͘ඞཁ΋ͳ͍
    • (receiptِ͕૷͞ΕΔՄೳੑΛߟ͑Δͱ/verifyReceiptΛୟ͍͓͖͍ͯͨ
    ؾ࣋ͪʹͳΔ͕ɺՌͨͯ͠…)
    New

    View Slide

  108. ࠓळ͔Β৽͘͠௥Ճ͞ΕΔ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

    View Slide

  109. Grace Periodͷ࣮૷ํ๏
    • WWDCͷηογϣϯͰड़΂ΒΕ͍ͯͨͷ͸ҎԼͷ3Step
    ͚ͩͰ͋Δ
    1. App Store ConnectͰGrace PeriodΛߏ੒
    2. /verifyReceipt ͷ৽͍͠ϑΟʔϧυ
    grace_period_expires_date ΛϋϯυϦϯά
    3. Graceظؒ·ͰαʔϏεΛఏڙ͠ଓ͚ΔΑ͏ʹ࣮૷

    View Slide

  110. Status Pollingʹ͍ͭͯ
    5.

    View Slide

  111. Status Pollingͱ͸ʁ
    • ظݶ੾ΕલޙͷϨγʔτΛ /verifyReceipt ʹରͯ͠tokenͷΑ͏ʹ౤͛Δ͜
    ͱͰ࠷৽ϨγʔτΛऔಘͯ͠dbͷঢ়ଶΛߋ৽͢Δ͜ͱΛࢦ͢
    • Ϩγʔτ͸ΞϓϦͷىಈ(͋Δ͍͸αʔόʔؒ௨஌)ʹΑͬͯͷΈߋ৽͞ΕΔ
    ͷͰɺ΋͠WebͰར༻Ͱ͖ΔαʔϏεΛఏڙ͍ͯ͠Δ৔߹ɺStatus Polling
    Ͱ࠷৽ͷঢ়ଶΛҡ࣋͢Δඞཁ͕͋Δ
    • ΈͯͶϓϨϛΞϜͰ͸Web্ͷػೳΛఏڙ͍ͯͨͨ͠Ί

    ࣮૷͓ͯ͘͠ඞཁ͕͋ͬͨ
    • ͪͳΈʹ͜ͷख๏͸WWDC18 ʮEngineering SubscriptionsʯʹΑͬͯޠΒ
    ΕΔͷΈͰυΩϡϝϯτԽ͞Ε͍ͯͳ͍

    View Slide

  112. ͍ͭStatus PollingΛ࣮ߦ͢΂͖͔
    • WWDC2018 ʮEngineering SubscriptionsʯͰ͸ҎԼͷΑ͏ʹظݶ͕੾ΕΔલޙͷλ
    ΠϛϯάͰ͢ΔΑ͏ʹݴΘΕ͍ͯΔ
    • ΈͯͶͰ͸Grace Periodத͓ΑͼGrace Period͕ऴྃͯ͠1೔ޙͷϢʔβʔʹରͯ͠ຖ
    ே࣮ߦ͍ͯ͠Δ
    IUUQTEFWFMPQFSBQQMFDPNWJEFPTQMBZXXED

    View Slide

  113. Sandbox؀ڥʹ͓͚Δ

    ಈ࡞֬ೝ
    6.

    View Slide

  114. Sandbox؀ڥ
    • App Store ConnectͰςελʔΞΧ΢ϯτΛ࡞੒͢
    Δ͜ͱͰςετ؀ڥͰಈ࡞֬ೝ͕Ͱ͖Δ
    • ʮUsers and Accessʯͷϖʔδ͔Β؆୯ʹ࡞੒Մೳ
    • ֤SandboxϢʔβʔʹରͯ͠طଘͷApple IDʹඥͮ
    ͚ΒΕ͍ͯͳ͍ϝʔϧΞυϨεΛઃఆ
    • νʔϜͰڞ༗͓ͯ͘͠ͱΑ͍

    View Slide

  115. SandboxͰൃߦ͞ΕΔϨγʔτ
    • SandboxͰൃߦ͞ΕΔϨγʔτ͸ҎԼͷରԠ
    දͷΑ͏ʹొ࿥ظ͕ؒ୹ॖ͞ΕΔ
    IUUQTIFMQBQQMFDPNBQQTUPSFDPOOFDUEFWFFE

    View Slide

  116. Sandbox؀ڥʁຊ൪؀ڥʁʁ
    • αʔόʔؒ௨஌
    • ઃఆͰ͖ΔURL͸̍ͭͳͷͰɺຊ൪αʔόʔʹͷΈ௨஌͸POST͞ΕΔ
    • POST͞Εͨ௨஌ͷenvironmentΛݟͯ `Sandbox` Ͱ͋Ε͹։ൃαʔόʔ΁Ϧμ
    ΠϨΫτ͢Δඞཁ͕͋Δ

    View Slide

  117. 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
    ʹ౤͛௚͢ඞཁ͕͋Δ

    View Slide

  118. ެ։લʹຊ൪؀ڥͷϨγʔτΛऔಘ͍ͨ͠
    • TestflightͰ഑৴ͯ͠΋Ϩγʔτ͸SandboxͰ
    ൃߦ͞Εͨ΋ͷʹͳΔ
    • લड़ͷ௨ΓɺAppleͷ৹ࠪ΋SandboxͰൃߦ
    ͞ΕͨϨγʔτΛ࢖༻
    • ͭ·ΓɺϦϦʔε͢Δ·Ͱຊ൪ͷϨγʔτΛ
    ॲཧͰ͖ͳ͍

    View Slide

  119. Promotion CodeΛ࢖ͬͯຊ൪֬ೝ
    • ਃ੥ͷ௨ͬͨΞϓϦͳͲΛެ։લͰ΋഑෍͢Δ͜ͱ͕Ͱ
    ͖Δͷ͕Promotion Code
    • ʮજࡏސ٬΍ϨϏϡʔهࣄ࡞੒ऀ΁ͷ഑෍ͳͲϓϩϞʔ
    γϣϯͷͨΊͷ࢖༻ʯ͕૝ఆ͞Ε͍ͯΔ
    • ͕ɺ͜ΕͳΒຊ൪؀ڥͷϨγʔτ͕खʹೖΔʂ
    • ΈͯͶͰ͸͜ΕʹΑΓαʔόʔଆͷͪΐͬͱͨ͠όάΛ
    ϦϦʔεલʹચ͍ग़ͨ͠

    View Slide

  120. ϦδΣΫτʹ͍ͭͯ

    ؾΛ͚͍ͭͨϙΠϯτ
    7.

    View Slide

  121. RestoreϘλϯ͸ඞਢ
    • Auto-Renewable(or Non-Renewing or Non-Consumable)
    ͷ՝ۚͷ৔߹͸RestoreػೳΛ࣮૷͢Δඞཁ͕͋Δ
    • ResotreϘλϯ͕ͳ͍ͱϦδΣΫτ͞ΕΔ
    • Restoreͷ࣮૷ʹ͸2ύλʔϯ͋Δ
    1. Restore Completed Transactions
    2. Refresh Receipt

    View Slide

  122. 1. Restore Completed Transactions
    • finishTransaction() ʹΑͬͯ׬ྃͤͯ͞͠·ͬ
    ͨtransactionΛ෮ݩ͢Δํ๏
    SKPaymentQueue.default().restoreCompletedTransactions()
    • restore͕׬ྃͨ͠ΒϨγʔτΛόϦσʔγϣ
    ϯʹ௨্ͨ͠ͰfinishTransaction() ͯ͋͛͠Δ

    View Slide

  123. 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()
    }

    View Slide

  124. 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Λ࣮૷͍ͯ͠Δ

    View Slide

  125. ߪೖγʔϯʹදࣔ͢΂͖৘ใΛ࿙Εͳ͘
    ྻڍ͢Δ

    View Slide

  126. ߪೖγʔϯʹදࣔ͢΂͖৘ใΛ࿙Εͳ͘
    ྻڍ͢Δ
    • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπ΍α
    ʔϏε

    View Slide

  127. ߪೖγʔϯʹදࣔ͢΂͖৘ใΛ࿙Εͳ͘
    ྻڍ͢Δ
    • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπ΍α
    ʔϏε
    • Ϣʔβʔ΁ͷ੥ٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛ؅ཧ͢Δํ๏

    View Slide

  128. ߪೖγʔϯʹදࣔ͢΂͖৘ใΛ࿙Εͳ͘
    ྻڍ͢Δ
    • αϒεΫϦϓγϣϯͷ໊લɺظؒɺαϒεΫϦϓγϣϯͷ֤ظؒʹఏڙ͞ΕΔίϯςϯπ΍α
    ʔϏε
    • Ϣʔβʔ΁ͷ੥ٻํ๏ɺϢʔβʔ͕αϒεΫϦϓγϣϯΛ؅ཧ͢Δํ๏
    • ߪೖ֬ఆ࣌ʹɺࢧ෷͍͸ϢʔβʔͷApple IDΞΧ΢ϯτʹ՝ۚ͞ΕΔ͜ͱ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  135. ࠷ऴతͳΈͯͶͷߪೖϖʔδ

    View Slide

  136. ϦϦʔεલʹ༨༟Λ࣋ͬͯਃ੥͓ͯ͜͠͏
    • ϦδΣΫτ͸ඞͣ͞ΕΔ৺࣋ͪͰϦϦʔεͷ
    εέδϡʔϦϯά͸͓ͯ͘͠
    • ΈͯͶͰ͸Ծਃ੥ΛϦϦʔε༧ఆΑΓ

    2, 3िؒલʹ͸࣮ࢪ͍ͯͨ͠

    View Slide

  137. ·ͱΊ
    8.

    View Slide

  138. ·ͱΊ
    • ෳࡶͳཁ͕݅͋ΔΘ͚Ͱ΋ͳ͍ͷʹʮAuto-Renewable
    SubscriptionsʯͰ͋Δͱ͍͏͚ͩͰ࣮૷͢Δ͜ͱ͸݁ߏଟ͍
    • αʔόʔؒ௨஌ʹ৽͍͠௨஌λΠϓ͕௥Ճ͞Εɺ࣮૷͸optional
    ͱݴΘΕ͍ͯΔ͕ɺ΄΅mustʹͳΓͭͭ͋Δ (ओ؍)
    • Grace Period͸ղ໿཰վળͱͦͷظؒใुΛ΋Β͑ΔͷͰɺ࣮
    ૷͠ͳ͍ख͸ͳ͍
    • ϦδΣΫτ͸ඞͣ͞ΕΔ΋ͷͱࢥͬͯϦϦʔεϓϥϯχϯά͢Δ

    View Slide

  139. Thank you!!!

    View Slide