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

StoreKitのこれまでとこれから / StoreKit 2 from 1

uzzu
September 17, 2021

StoreKitのこれまでとこれから / StoreKit 2 from 1

uzzu

September 17, 2021
Tweet

More Decks by uzzu

Other Decks in Technology

Transcript

  1. ࿩͞ͳ͍ࣄ • Promoting IAPɺSubscription Offerɺ 
 Family Sharing SubscriptionͳͲͷOptionalͳܾࡁػೳ •

    ΞϓϦ಺՝ۚҎ֎ͷStoreKitͷػೳ(SKOverlay౳) • App Store Server APIͷৄࡉ • async/awaitͷৄࡉ • StoreKitͱStoreKit2͕ڞଘͨ͠৔߹ͷڍಈ
  2. ஫ҙࣄ߲ ⚠ ɾStoreKit2͸ݱࡏsandbox՝ۚ΋ࣦഊ͢Δ(iOS15 beta8) 
 ɹStoreKit TestingΛར༻͢ΔࣄͰಈ࡞͠·͢ 
 ɹhttps://developer.apple.com/forums/thread/681904 ɾStoreKit

    Testing ར༻ʹ͓͍ͯXcode 13 beta 5࣌఺Ͱ 
 ɹچStoreKitͱͷڠௐಈ࡞ͷ֬ೝ͕औΕͳ͍ 
 ɹ=> ڞଘͨ͠৔߹ͷಈ࡞͕Ͳ͏ͳΔͷ͔౳ࡉ͔͍ॴ͸ݱঢ়෼͔Βͳ͍ 
 ɾ࢖͑ΔΑ͏ʹͳΔͷ͸ iOS15 ʙ (async/await backport ૣ͘དྷͯ͘Εʙ) 
 ɾಈ͘Α͏ʹͳͬͨΒ࠶ݕূ͠·͠ΐ͏
  3. ΞϓϦ಺՝ۚͷ࣮૷ͷ֓ཁ(1/2) ɾͦΕͧΕఏڙ͢Δҝʹ࣮૷ඞਢͳػೳ 
 ɹߪೖ 
 ɹ෮ݩ 
 ɾͦͷଞɺඞਢͰ͸ͳ͍͕ඞཁʹԠ࣮ͯ͡૷ΛٻΊΒΕΔػೳ 
 ɹPromoting

    IAP .. App Store ΞϓϦ্Ͱͷ঎඼ͷϓϩϞʔγϣϯɺߪೖ 
 ɹSubscription Offer .. ೚ҙͷϢʔβʹ௿Ձ֨Ͱఏڙ͢Δ 
 ɹFamily Sharing Subscription .. ϑΝϛϦʔڞ༗ͷػೳͰఆظߪೖΛڞ༗͢Δ
  4. class AppDelegate: UIResponder, UIApplicationDelegate { private var iapObserver: SKPaymentTransactionObserver =

    IAPObserver() func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil ) -> Bool { SKPaymentQueue.default().add(iapObserver) return true } func applicationWillTerminate(_ application: UIApplication) { SKPaymentQueue.default().remove(iapObserver) } }
  5. 購⼊処理フロー 購⼊ボタンを押す 注⽂番号を発⾏ 商品情報取得 決済 レシート送信 
 + 
 注⽂を有効にして

    
 購⼊した商品を 
 付与する レシート検証 
 最新レシート 
 取得 購⼊情報を 
 更新
  6. 購⼊処理フロー 購⼊ボタンを押す 注⽂番号を発⾏ 商品情報取得 決済 レシート送信 
 + 
 注⽂を有効にして

    
 購⼊した商品を 
 付与する レシート検証 
 最新レシート 
 取得 購⼊情報を 
 更新
  7. 購⼊処理フロー/商品情報の取得 .. 旧StoreKitの場合 public typealias ProductsRequestResult = Result<SKProductsResponse, Error> public

    typealias ProductsRequestCompletion = (ProductsRequestResult) -> Void public class ProductsRequest: NSObject, SKProductsRequestDelegate { private var completion: ProductsRequestCompletion? private var request: SKProductsRequest init(_ productIDs: Set<String>) { self.request = SKProductsRequest(productIdentifiers: productIDs) } public func send(completion: @escaping ProductsRequestCompletion) { self.completion = completion self.request.delegate = self self.request.start() } public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { if let completion = completion { self.completion = nil completion(.success(response)) } } public func request(_ request: SKRequest, didFailWithError error: Error) { if let completion = completion { self.completion = nil completion(.failure(error)) } } }
  8. 購⼊処理フロー/商品情報の取得 .. 旧StoreKitの場合 public typealias ProductsRequestResult = Result<SKProductsResponse, Error> public

    typealias ProductsRequestCompletion = (ProductsRequestResult) -> Void public class ProductsRequest: NSObject, SKProductsRequestDelegate { private var completion: ProductsRequestCompletion? private var request: SKProductsRequest init(_ productIDs: Set<String>) { self.request = SKProductsRequest(productIdentifiers: productIDs) } public func send(completion: @escaping ProductsRequestCompletion) { self.completion = completion self.request.delegate = self self.request.start() } public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { if let completion = completion { self.completion = nil completion(.success(response)) } } public func request(_ request: SKRequest, didFailWithError error: Error) { if let completion = completion { self.completion = nil completion(.failure(error)) } } } ɹɾdelegate method͕ݺ͹ΕΔεϨου͸อূ͞Ε͍ͯͳ͍ 
 ɹɹ=>ϝΠϯεϨουͰݺ͹ΕΔલఏͰ͍Δͱ 
 ɹɹɹޙଓॲཧ͕Ϋϥογϡ͢Δ(iOS 13.1ʙ) ɹɾ঎඼λΠϓ͕෼͔Βͳ͍ 
 ɹɹ=>productIdentifierͷ໋໊ΛϧʔϧԽ͢Δ౳ɺରࡦ͕ඞཁ ɹɾ঎඼৘ใऔಘ͢Δ͚ͩͳͷʹ 
 ɹɹίʔυྔ͕ଟ͍(هࡌͷίʔυͰ͸࣮ࡍ͸଍Γͳ͍)
  9. 購⼊処理フロー/商品情報の取得 .. StoreKit2の場合 let products = try await Product.products(for: ["productId"])

    ΋ͪΖΜ঎඼λΠϓ΋औಘՄೳ (Product.type) 🥳🥳🥳🥳🥳🥳🥳🥳🥳 🥳🥳🥳🥳🥳🥳🥳🥳🥳
  10. 購⼊処理フロー/決済 .. 旧StoreKitの場合 let payment = SKMutablePayment(product: product) // 様々オプションを指定

    SKPaymentQueue.default().add(payment) ↓ SKPaymentTransactionObserverの以下のdelegateが呼ばれる func paymentQueue( 
 _ queue: SKPaymentQueue, 
 updatedTransactions transactions: [SKPaymentTransaction]) { // : } ↓SKPaymentTransaction.transactionState == .purchasing でやってくる ↓SKPaymentTransactionObserverの同じdelegateが呼ばれる ↓IAP決済ダイアログが出てくる。ユーザが決済を⾏う ↓SKPaymentTransaction.transactionState == .purchased でやってくる ↓レシート送信、商品付与 ↓商品付与後、SKPaymentQueue.default().finishTransaction(transaction)
  11. ↓商品付与後、 購⼊処理フロー/決済 .. 旧StoreKitの場合 let payment = SKMutablePayment(product: product) //

    様々オプションを指定 SKPaymentQueue.default().add(payment) ↓ SKPaymentTransactionObserverの以下のdelegateが呼ばれる func paymentQueue( 
 _ queue: SKPaymentQueue, 
 updatedTransactions transactions: [SKPaymentTransaction]) { // : } ↓SKPaymentTransaction.transactionState == .purchasing でやってくる ↓SKPaymentTransactionObserverの同じdelegateが呼ばれる ↓IAP決済ダイアログが出てくる。ユーザが決済を⾏う ↓SKPaymentTransaction.transactionState == .purchased でやってくる ↓レシート送信、商品付与 SKPaymentQueue.default().finishTransaction(transaction) ɹɾupdatedTransactionsʹ͸ 
 ɹɹfinish͞Ε͍ͯͳ͍શͯͷTransaction͕΍ͬͯ͘Δ 
 ɹɹ=>ݱࡏߪೖதͷ঎඼͕Կ͔ΛͲ͔͜͠ΒͰ͓࣋ͬͯ͘ 
 ɹɹɹ ඞཁ͕͋Δ 
 ɹɾAsk To Buy(ϖΞϨϯλϧίϯτϩʔϧػೳ)ͷΑ͏ʹ 
 ɹɹܾࡁ׬ྃ௨஌͕όοΫάϥ΢ϯυ͔Β΍ͬͯ͘Δέʔε 
 ɹɹ͕͋ΔͷͰɺͦͷߟྀ΋ඞཁ ɹɾ΋ͪΖΜɺ঎඼λΠϓશͯͷTransaction͕΍ͬͯ͘Δ 
 ɹɹ=>঎඼λΠϓΛෳ਺ѻ͏Α͏ʹͳͬͨΒ 
 ɹɹɹ Observer͕େมͳࣄʹͳΔ 
 ɹɾ্هΛ౿·্͑ͨͰObserverͱ(Rx|Combine)ͱܨ͗͜Ή 
 ɹɹͷ͸Ͱ͖ͳ͘͸ͳ͍͕େม
  12. 購⼊処理フロー/レシート検証、最新レシート取得 .. 旧StoreKitの場合 let base64EncodedReceipt: String? if let appStoreReceiptURL =

    Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { base64EncodedReceipt = try? Data( contentsOf: appStoreReceiptURL, options: .alwaysMapped) .base64EncodedString() } else { base64EncodedReceipt = "" } ↓注⽂番号と合わせてサーバに送信 ↓POST https://buy.itunes.apple.com/verifyReceipt ↓最新レシート情報を元に商品付与、注⽂を有効にする ↓後続処理へ…
  13. 購⼊処理フロー/レシート検証、最新レシート取得 .. 旧StoreKitの場合 let base64EncodedReceipt: String? if let appStoreReceiptURL =

    Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { base64EncodedReceipt = try? Data( contentsOf: appStoreReceiptURL, options: .alwaysMapped) .base64EncodedString() } else { base64EncodedReceipt = "" } ↓注⽂番号と合わせてサーバに送信 ↓POST https://buy.itunes.apple.com/verifyReceipt ↓最新レシート情報を元に商品付与、注⽂を有効にする ↓後続処理へ… ɹɾϨγʔτݕূࣗମ͸ΞϓϦ୯ମͰ΋Ͱ͖Δ͕ 
 ɹɹ؆қͳ΋ͷͳͷͰجຊతʹ͸࢖Θͳ͍ 
 ɹɾաڈͷಉΞϓϦͰͷߪೖ৘ใ͕શؚͯ·ΕΔҝ 
 ɹɹϖΠϩʔυαΠζ͕૿Ճ 
 ɹɹࣗಈߋ৽αϒεΫϦϓγϣϯͳΒߋ৽݄෼૿͑Δ 
 ɹɾϖΠϩʔυ͕େ͖͘ͳΔ΄ͲɺverifyReceiptͷ 
 ɹɹϨεϙϯε΋஗͘ͳΔɻΑ͘500Λు͘ ɹɹ=>exclude_old_transactions ͳΦϓγϣϯͰ 
 ɹɹɹߴ଎Խ͕ਤΕΔ͕ 
 ɹɹɹݹ͍ߪೖ৘ใ΋ඞཁͳϢʔεέʔε͸ 
 ɹɹɹ࣮ӡ༻্΅ͪ΅ͪ͋Δ
  14. ෮ݩͱ͸…ʁ ɾIAPܾࡁ͸ߦΘΕ͍ͯΔ͕঎඼͕෇༩͞Ε͍ͯͳ͍ 
 ɹϢʔβ΁ͷٹࡁાஔ 
 ɹ- ߪೖΤϥʔ͕ൃੜͨ͠··ܾࡁը໘͔Β཭୤ͯ͠͠·ͬͨ 
 ɹ- ػछมߋޙʹݩʑར༻͍ͯͨ͠ঢ়ଶʹ໭͍ͨ͠

    
 ɹ- ߪೖࡁΈͷ঎඼Λෳ਺୺຤Ͱར༻͍ͨ͠ ɾܾࡁ৘ใΛ෮ݩ͢Δػೳ͕ఏڙ͞Ε͍ͯΔͷͰ 
 ɹͦΕΛར༻ͯ͠෮ݩॲཧΛߦ͏ 

  15. ෮ݩͱ͸…ʁ ɾফ໣ܕΞΠςϜͷ৔߹ 
 ɹ=> ະॲཧͳܾࡁ৘ใ͕͋Ε͹ɺͦΕΛߪೖॲཧʹԊͬͯॲཧͯ͠঎඼Λ෇༩ 
 ɾඇফ໣ܕΞΠςϜɺࣗಈߋ৽αϒεΫϦϓγϣϯ 
 ɹඇࣗಈߋ৽αϒεΫϦϓγϣϯͷ৔߹ 


    ɹ=> ະॲཧͳܾࡁ৘ใ͕͋Ε͹ɺͦΕΛߪೖॲཧʹԊͬͯॲཧͯ͠঎඼Λ෇༩ 
 ɹ=> ॲཧࡁΈͰ͋Ε͹ 
 ɹɹϩάΠϯ͍ͯ͠ΔϢʔβΛݩʑར༻͍ͯͨ͠Ϣʔβʹ੾Γସ͑Δ 
 ɹɹ·ͨ͸ ݩʑར༻͍ͯͨ͠Ϣʔβʹ౷߹ͭͭ͠ϩάΠϯϢʔβΛ੾Γସ͑Δ 
 ɹɹ·ͨ͸ ߪೖ৘ใʹ߹ΘͤͯݱࡏͷϢʔβʹ঎඼Λ෇͚ସ͑Δ 
 ɹɹͳͲɺཁ݅ʹ͋Θͤͯ…
  16. 復元処理フロー 復元操作をする 
 or ⾃動で復元 レシート送信 
 + 
 購⼊情報を

    
 復元する為の 
 あれこれ 
 決済情報を 
 復元する レシート検証 
 最新レシート 
 取得 購⼊情報を 
 復元する為の 
 あれこれ
  17. 復元処理フロー 復元操作をする 
 or ⾃動で復元 レシート送信 
 + 
 購⼊情報を

    
 復元する為の 
 あれこれ 
 決済情報を 
 復元する レシート検証 
 最新レシート 
 取得 購⼊情報を 
 復元する為の 
 あれこれ
  18. 復元処理フロー/決済情報の復元 .. 旧StoreKitの場合 SKPaymentQueue.default().restoreCompletedTransactions() ↓ SKPaymentTransactionObserverの以下のdelegateが呼ばれる func paymentQueue( 
 _

    queue: SKPaymentQueue, 
 updatedTransactions transactions: [SKPaymentTransaction]) { // : } ↓SKPaymentTransaction.transactionState == .restored でやってくる ↓アプリとして必要な復元処理を実施 ↓復元処理を実施後SKPaymentQueue.default().finishTransaction(transaction) ↓ SKPaymentTransactionObserverの以下のdelegateが呼ばれる func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { // : }
  19. 復元処理フロー/決済情報の復元 .. 旧StoreKitの場合 SKPaymentQueue.default().restoreCompletedTransactions() ↓ SKPaymentTransactionObserverの以下のdelegateが呼ばれる func paymentQueue( 
 _

    queue: SKPaymentQueue, 
 updatedTransactions transactions: [SKPaymentTransaction]) { // : } ↓SKPaymentTransaction.transactionState == .restored でやってくる ↓アプリとして必要な復元処理を実施 ↓復元処理を実施後SKPaymentQueue.default().finishTransaction(transaction) ↓ SKPaymentTransactionObserverの以下のdelegateが呼ばれる func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { // : } ɹɾ·ͨupdatedTransactions… 
 ɹɹ=>ߪೖॲཧͱ෮ݩॲཧ͸ผ΋ͷͳͷͰ 
 ɹɹɹͪΌΜͱtransactionStateΛݟΔ 
 ɹɾະॲཧͳܾࡁͷ৔߹͸ߪೖॲཧͱಉ౳ͷॲཧΛ࣮ࢪ͢Δ 
 ɹɹඞཁ͕͋Δ ɹɾ΋ͪΖΜɺ঎඼λΠϓશͯͷTransaction͕΍ͬͯ͘Δ 
 ɹɾRxɺCombineɺ΢ο…
  20. 復元処理フロー/決済情報の復元 .. StoreKit2の場合 let transaction = await product.latestTransaction let result

    = await product.currentEntitlement let result = await Transaction.latest(for: "productId") let results = await Transaction.currentEntitlements // etc… 👏👏👏👏👏👏 👏👏👏👏👏👏 SKReceiptRefreshRequest૬౰ͷ΋ͷ͸ 
 await AppStore.sync()
  21. ɾiOS15ະຬ 
 ɹ=>async/await backport ͕དྷͨΒมΘΔ͔΋… 
 ɾApple School Manager΍Apple Business

    Manager্Ͱ 
 ɹVolume Purchase ProgramΛར༻͍ͯ͠Δ৔߹ 
 ɾΞϓϦͷ༧໿ൢചػೳΛར༻͢Δ৔߹ 
 ɾաڈʹ༗ྉΞϓϦ͕ͩͬͨϑϦʔϛΞϜϞσϧʹ 
 ɹҠߦͨ͠ΞϓϦͷ৔߹ɺ·ͨ͸ͦͷٯͷ৔߹ 
 ɹ=>StoreKit2ʹ·ͩAPI (preorder_date) ͕ଘࡏ͠ͳ͍ StoreKit2͕࢖͑ͳ͍έʔε
  22. ·ͱΊ • ͨͩͰܾ͑͞ࡁॲཧ͸ෳࡶͳॴʹ 
 چStoreKitͷSKPaymentTransactionObserverΛத৺ͱͨ͠ઃܭ͕ 
 ͞Βʹෆඞཁͳෳࡶ͞ΛੜΜͰ͠·͍ͬͯͨ 
 - ߪೖɺ෮ݩॲཧ࣮૷ʹ͓͚ΔόοΫάϥ΢ϯυॲཧͷߟྀ

    
 - ঎඼ͷछผຖͷద੾ͳॲཧͷҧ͍ 
 - ߋʹ௥Ճػೳ(Promoting IAP, Subscription Offer, etc…)Λར༻͢Δ 
 શ෦ࡌͤͨঢ়ଶͷSKPaymentTransactionObserverΛ૝૾ͯ͠Έ·͠ΐ͏ 
 ɹͱͯ΋͔ΜͨΜͱ͸ݴ͍೉͍ɻਅ໘໨ʹ޲͖߹͏ͱઐ໳৬͕ඞཁͳϨϕϧ 
 ɹ3rd partyϥΠϒϥϦΛ࢖͍ͨ͘ͳΔؾ࣋ͪ΋෼͔Δ • StoreKit2Ͱ͸Observerύλʔϯ͸ഇࢭ͞Εɺasync/await͕෼͔Ε͹෼͔Δ͙Β͍ͷ 
 ۙ୅Խ͕͞Ε͍ͯΔ 
 ࣮ʹ…࣮ʹγϯϓϧͳAPIʹͳͬͨ 
 ։ൃ͢ΔΞϓϦʹඞཁͳܾࡁॲཧͷ࣮૷ʹूதͰ͖ΔΑ͏ʹͳͬͨɻૣ͘࢖͍͍ͨ…ɻ • App Store Server΋(·ͩ࢖͑ͳ͍ͷͰͲ͏ͳΔ͔෼͔Βͳ͍͚Ͳ)ૣ͘࢖͍͍ͨ…ɻ
  23. ࢀߟ ɾMeet StoreKit 2 @ WWDC21 
 ɹhttps://developer.apple.com/videos/play/wwdc2021/10114/ ɾManage in-app

    purchases on your server @ WWDC21 
 ɹhttps://developer.apple.com/videos/play/wwdc2021/10174/ ɾSupport customers and handle refunds @ WWDC21 
 ɹhttps://developer.apple.com/videos/play/wwdc2021/10175 ɾIn-App Purchase 
 ɹhttps://developer.apple.com/documentation/storekit/in-app_purchase ɾImplementing a Store In Your App Using the StoreKit API 
 ɹhttps://developer.apple.com/documentation/storekit/in-app_purchase/implementing_a_store_in_your_app_using_the_storekit_api ɾApp Store Server API 
 ɹhttps://developer.apple.com/documentation/appstoreserverapi