カテゴリー
iOS Swift

StoreKit2での購入フロー

ProductリストをUIに表示させるバックエンドフローは以下

// array of fetched product ids
var identifiers: [String] = []
// array of Product
var products: [Products] = []

// fetch from server and store data in Keychain
func fetchProductsFromServer() async {
  let url = URL(string: "https://jamapp.me/test_getInAppProducts")!
  do {
    let (data, _) = try await URLSession.shared.data(from: url)
    KeychainWrapper.standard.set(data, forKey: dataKey)
  } catch {
    print(error)
  }
}

// decode from keychain and fill identifier array
func decodeProductsFromKeychain() async {
  guard let data = KeychainWrapper.standard.data(forKey: self.dataKey) else { return }		
  do {
    let decode = try JSONDecoder().decode(ServerResponse.self, from: data)
    decodedProducts = Set(decode.products)
    for product in decode.products {
      identifiers.insert(product.identifier)
    }
  } catch {
    print(error)
  }
}

// fetch from AppStore
func fetchProductsFromAppStore() async {
  do {
    products = try await Product.products(for: identifiers)
    for product in products {				
      if await product.currentEntitlement != nil {
	// storing currentEntitlement in Keychain for off-line use...				    
        KeychainWrapper.standard.set(true, forKey: product.id)
	// storing in isPurchasedDict to update UI
        await MainActor.run {						   
          self.isPurchasedDict[product.id] =   KeychainWrapper.standard.bool(forKey: product.id)
        }
      } else {
        KeychainWrapper.standard.set(false, forKey: product.id)
        await MainActor.run {
          self.isPurchasedDict[product.id] = KeychainWrapper.standard.bool(forKey: product.id)
        }
      }
    }
  } catch {
    print(error)
  }
}

in order to handle any unhandled transactions, listen for Transaction.updates in appDelegate

extension AppDelegate: UIApplicationDelegate {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    // listening for transaction updates
    Task {
      await listenForTransactions()
    }
    return true
    }	

  func listenForTransactions() async {
    for await verificationResult in Transaction.updates {
      switch verificationResult {
        case .verified(let transaction):
          KeychainWrapper.standard.set(true, forKey: transaction.productID)
          await transaction.finish()
        case .unverified(let transaction, _):
          await transaction.finish()
      }
    }
  }
}

in order to buy a product, do below.

func buyProduct(product: Product) {
  Task {
    do {
      let result = try await product.purchase()
      switch result {
        case .success(let verification):
          switch verification {
            case .verified(let transaction):
	      await updatePurchase(transaction: transaction)
	      await transaction.finish()
	    case .unverified(let transaction, _):
	      await transaction.finish()
          }
	case .userCancelled:
	  ()
	case .pending:
	  ()
	@unknown default:
	  ()
      }
    } catch {
      print(error)
    }
  }
}
	
func updatePurchase(transaction: Transaction) async {
  KeychainWrapper.standard.set(true, forKey: transaction.productID)
  await MainActor.run {
    isPurchasedDict[transaction.productID] = true
  }
}