StoreKit2实战

4,360 阅读3分钟

前言

StoreKit2对比之前方便了很多,但是API需要iOS15以上才能支持,开发的话xcode13

  • 后台能查看订单信息
  • 后台能查看退单信息
  • 后台能收到退订的消息
  • APP能直接拉取苹果未完成订单信息
  • APP能申请退款

代码

直接上代码吧,比较简单,参考苹果的官方demo做的

//
//  iosStoreKit2.swift
//  ios_storekit
//
//  Created by iOS on 2023/5/23.
//

import Foundation
import StoreKit

typealias Transaction = StoreKit.Transaction
typealias RenewalInfo = StoreKit.Product.SubscriptionInfo.RenewalInfo
typealias RenewalState = StoreKit.Product.SubscriptionInfo.RenewalState

public enum StoreError: Error { // 错误回调枚举
    case failedVerification
    case noProduct
}

public enum StoreState: Int64 { // 支付状态
    case start              // 开始
    case pay                // 进行苹果支付
    case verifiedServer     // 服务器校验
    case userCancelled      // 用户取消
    case pending            // 等待(家庭用户才有的状态)
    case unowned
}

class Store: ObservableObject {
    
    typealias KStateBlock = (_ state :StoreState,_ param:Dictionary<String,Any>?) ->()
    var stateBlock: KStateBlock! // 状态回调
    
    var updateListenerTask: Task<Void, Error>? = nil // 支付事件监听
    
    var transactionMap :[String:Transaction] // 用于完成Id的缓存map
    
    var name: String = "iosStore" // 单例的写法
    static let shared = {
        let instance = Store()
        return instance
    }()

    private init() { // 单例需要保证private的私有性质
        transactionMap = [:] // 初始化
        Task {
            updateListenerTask = listenForTransactions()
        }
    }
    
    // 退订
    func refunRequest(for transactionId: UInt64, scene: UIWindowScene) async{
        do {
           try await Transaction.beginRefundRequest(for: transactionId, in: scene)
        }catch{
            print("iap error")
        }
    }
    
    // 购买某个产品
    func requestBuyProduct(productId:String) async throws -> Transaction?{
        if(stateBlock != nil ){
            stateBlock(StoreState.start,nil)
        }
        do {
            let list:[String] = [productId]
            let storeProducts = try await Product.products(for: Set.init(list))
            
            if storeProducts.count > 0 {
              return try await purchase(storeProducts[0])
            }else {
                print("iap: no found product")
                throw StoreError.noProduct // 没有该产品
            }
        } catch {
            print("Failed product request from the App Store server: \(error)")
            throw StoreError.noProduct // 没有该产品
        }
    }
    
    // 购买
    private func purchase(_ product: Product) async throws -> Transaction? {
        if(stateBlock != nil ){
            stateBlock(StoreState.pay,nil)
        }
        
        let result = try await product.purchase()
        
        switch result {
        case .success(let verification): // 用户购买完成
            let transaction = try await verifiedAndFinish(verification)
            return transaction
        case .userCancelled: // 用户取消
            if(stateBlock != nil ){
                stateBlock(StoreState.userCancelled,nil)
            }
            return nil
        case .pending: // 此次购买被挂起
            if(stateBlock != nil ){
                stateBlock(StoreState.pending,nil)
            }
            return nil
        default:
            if(stateBlock != nil ){
                stateBlock(StoreState.unowned,nil)
            }
            return nil
        }
    }
    
    // 校验
    func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
        //Check whether the JWS passes StoreKit verification.
        switch result {
        case .unverified:
            //StoreKit parses the JWS, but it fails verification.
            throw StoreError.failedVerification
        case .verified(let safe):
            //The result is verified. Return the unwrapped value.
            print("iap: verified success")
            return safe
        }
    }
    
    // 校验&完成后传给服务器
    func verifiedAndFinish(_ verification:VerificationResult<Transaction>) async throws -> Transaction?{
        //Check whether the transaction is verified. If it isn't,
        //this function rethrows the verification error.
        let transaction = try checkVerified(verification)
        
        // 这里将订单提交给服务器进行验证 ~~~
        let transactionId = try verification.payloadValue.id
        
        // 添加进入待完成map
        let key = String(transactionId)
        transactionMap[key] = transaction
        await uploadServer(for: transactionId)
        
        // 这里不触发完成,等服务器验证再触发完成逻辑
//        await transaction.finish()
        print("iap: finish")
        return transaction
    }
    
    // 事件完成处理

    func transactionFinish(transaction:String) async{
        if(transactionMap[transaction] != nil){
           await transactionMap[transaction]!.finish()
            print("transactionFinish end")
        }else {
            print("transaction不存在,参数不正确,Id=\(transaction)")
        }
    }
    
    @MainActor
    func uploadServer(for transactionId:UInt64) async {
        let dic :Dictionary<String,Any> = ["transactionId":transactionId]
        if(stateBlock != nil ){
            stateBlock(StoreState.verifiedServer,dic)
        }
    }
    
    // 支付监听事件
    func listenForTransactions() -> Task<Void, Error> {
        return Task.detached {
            //Iterate through any transactions that don't come from a direct call to `purchase()`.
            // 修改update 为 unfinished?
            for await result in Transaction.updates { //会导致二次校验?
                do {
                    print("iap: updates")
                    print("result:\(result)")
                    let transaction = try await self.verifiedAndFinish(result)
                } catch {
                    //StoreKit has a transaction that fails verification. Don't deliver content to the user.
                    print("Transaction failed verification")
                }
            }
        }
    }
    
    // 销毁调用
    deinit {
        updateListenerTask?.cancel()
    }
}

使用方法差不多就下面那样,根据自己的修改吧

tips:我做的是flutter的插件

import Flutter
import UIKit
import ObjectMapper

public class SwiftIosStorekitPlugin: NSObject, FlutterPlugin {

  static var channel:FlutterMethodChannel! // 通信信道
    
  public static func register(with registrar: FlutterPluginRegistrar) {
   
    channel = FlutterMethodChannel(name: "ios_storekit", binaryMessenger: registrar.messenger())
        
    let instance = SwiftIosStorekitPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
      
  }
    
    public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        if call.method == "getPlatformVersion" {
            storeKitPay()
        }else if call.method == "storeKitLaunch" {
            storeKitLaunch()
        }else if call.method == "storeKitPay" {
            storeKitPay()
        }else if call.method == "storeKitRefun" {
            let context = Context()
            let model = Mapper<StoreKitTransactionIdModel>(context: context).map(JSONObject: call.arguments)
            storeKitRefun(Id: model?.transactionId ?? "")
        }else if call.method == "storeKitFinish" {
            let context = Context()
            let model = Mapper<StoreKitTransactionIdModel>(context: context).map(JSONObject: call.arguments)
            storeKitFinish(Id: model?.transactionId ?? "")
        }else {
          result(nil)
      }
  }
    
    // 开始进行内购
    private func storeKitPay(){
        let store = Store.shared
        Task {
            do {
                if try await store.requestBuyProduct(productId:"ZB60M1M") != nil {
                   print("完成了")
                }
            } catch StoreError.failedVerification,StoreError.noProduct {
                print("error")
            }catch {
                print("Failed fuel purchase: \(error)")
            }
        }
    }
    
    // 请求退款
    private func storeKitRefun(Id:String){
        let store = Store.shared
        if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
//            if let keyWindow = windowScene.windows.first(where: \.isKeyWindow) {
                Task{
                    let transId = UInt64(Id)
                    await store.refunRequest(for: transId ?? 0, scene: windowScene)
                }
//            }
        }
    }
    
    // 启动自动监听事件
    private func storeKitLaunch(){
        print("storeKitLaunch")
        let store = Store.shared
        store.stateBlock = {(state:StoreState,param:Dictionary<String,Any>?)->Void in
          print("state block:\(state)")
          if state == StoreState.verifiedServer {
              SwiftIosStorekitPlugin.channel.invokeMethod("verifiedServer", arguments: param)
          }
        }
    }
    
    // 完成事件
    private func storeKitFinish(Id:String) {
        let store = Store.shared
        Task{
           await store.transactionFinish(transaction: Id)
        }
    }

    func requestServer() async{
        let dic :Dictionary<String,Any> = ["transactionId":"123456"]
        SwiftIosStorekitPlugin.channel.invokeMethod("verifiedServer", arguments: dic)
    }
}

参考链接

苹果官方iOS内文档

官方视频教程

storekit2 实际解析

内购讲的比较详细的

服务器校验相关