iOS 苹果内购 IPA

最近写的两个项目都涉及到了购买虚拟商品,根据苹果的要求,所以项目中集成了苹果内购功能。 网上关于苹果内购的资料很全面,写在此处只不过对于项目的一些总计而已。废话不多说,直接开始。

一、首先


image.png

1.登录 AppStoreConnect 选择 协议、税务和银行业务选项。


image.png

2.协议、税务、银行业务、联系信息这些如实填写就好。其中需要注意的是,地址和联系人、银行看准是让填汉字还是英文。税务选择美国,银行也是选择的不过都是英文的,需要将银行卡的开户行翻译成英文然后去列表里找到对应的总行或支行即可。
image.png

二、设置商品

1.回到 APP 选项,找到要添加内购的应用选择管理。


image.png

image.png

添加新的内购产品,这里要选择产品类型,主要类型有以下几种,根据自己需要选择对应类型即可。


image.png

填写内购产品的名称,如“包月会员” “会员充值”等, 后面产品ID为自己设置的一串数字,用来标识该产品的唯一性,如果这个产品下架了或是没有这个产品了,这个产品ID也会失效,再创建新的产品这个ID值也不能再用了。
image.png

image.png

这里的审核信息是必传项,截图需要是你在项目中测试内购支付的截图。这里测试的话需要用到一个沙河测试账号。账号怎么来的,看下面。


image.png

三、 沙盒测试账号

选择用户和访问->沙盒->添加测试员

选择用户和访问

image.png

新测试员,按图填写信息即可,其中电子邮件随便填即可,不一定非得是正确的邮箱,只要是是邮箱格式正确即可,如([email protected]),不过一定要记住邮箱地址和密码,在添加好测试员之后,项目里点击支付唤起苹果内购弹框后需要填写的就是这个邮箱和密码。而不是自己Apple ID 的邮箱。填写过之后,可以在手机(iPhone)上设置里 -> App Store 选项里可以看到沙盒账户,可以对该账号进行退出登录等操作。

image.png
image.png

四、以上信息都配置好之后,就是代码了。

  1. 个人建议将苹果内购写成工具类,这样以后再遇到支付项目,可以直接拿来用。

    1.1 先引入 import StoreKit
    1.2 声明一个代理
     protocol ZIAPManagerDelegate: class {
         /**
         支付成功回调
         */
         func rechargeSuccess(rechargeInfo: [String: AnyObject])
          /**
         支付失败回调
         */
         func rechargeFailed()
    }
    
    class ZIAPManager: NSObject {
       // MARK: - Public
       public weak var managerDelegate: ZIAPManagerDelegate?
        /**
        添加/移除支付观察者(直接在支付 VC 里调用即可,千万记得要移除观察者)
         */
       public func addPaymentObserver() {
              SKPaymentQueue.default().add(self)
       }
    
       public func removePaymentObserver() {
               SKPaymentQueue.default().remove(self)
       }
    
        /**
            购买商品,传入商品id 和 商品价格
         */
        public func buyWithProductId(_ pId: String, pPrice: String) {
              self.productId = pId
              self.pPrice = pPrice
              // 判断程序是否支持苹果内购
              if SKPaymentQueue.canMakePayments() {
                    SVProgressHUD.show(withStatus: "支付中...")
                     self.getProductData(pId)
              } else {
                     SVProgressHUD.showMessage("不允许程序内付费")
             }
         }
    
       // MARK: - Private Method
       private func getProductData(_ productId: String) {
             let set = NSSet.init(object: productId)
             let request = SKProductsRequest(productIdentifiers: set as! Set)
             request.delegate = self
             request.start()
      }
    
      // MARK: - Property Private
      fileprivate var productId: String = ""
      fileprivate var pPrice: String = ""
      fileprivate var retryCount: Int = 0
      fileprivate var verifySuccess: Bool = false
      /**
      AppStoreUrl
      /// 沙盒测试验证地址
      public let SANDBOXUrl: String = "https://sandbox.itunes.apple.com/verifyReceipt"
      /// 正式验证地址
      public let AppStoreUrl: String = "https://buy.itunes.apple.com/verifyReceipt"
      */
      fileprivate var verifyAddress: String = AppStoreUrl
      
      // MARK: - Sigleton
      private static let instance = ZIAPManager()
      class var shareInstance: ZIAPManager {
           return instance
      }
      private override init() {}
      }
    

    1.2 实现 SKProductsRequestDelegate 代理方法

 extension ZIAPManager: SKProductsRequestDelegate {

  func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    let products = response.products
    guard products.count > 0 else { return }
    var requestProduct: SKProduct?
    for pro in products {
        if pro.productIdentifier == self.productId {
            requestProduct = pro
        }
    }
    if let pro = requestProduct {
        let payment = SKPayment(product: pro)
        SKPaymentQueue.default().add(payment)
    }
}

func requestDidFinish(_ request: SKRequest) {
    SVProgressHUD.show(withStatus: "支付中...")
}

func request(_ request: SKRequest, didFailWithError error: Error) {
    SVProgressHUD.dismiss()
}

}

1.3 实现 SKPaymentTransactionObserver 回调方法

extension ZIAPManager: SKPaymentTransactionObserver {

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    SVProgressHUD.dismiss()
    for transaction in transactions {// 当交易队列里面添加的每一笔交易状态发生变化的时候调用
        switch transaction.transactionState {
            case .purchased:
                print("支付成功")
                SVProgressHUD.show(withStatus: "支付中...")
                // 本地验证凭证
                verifyPurchaseWithPaymentTrasaction()
                finishTransaction(queue, transaction: transaction)
            case .failed:
                    print("支付失败")
                    var errMsg = "支付失败"
                    if let error = transaction.error {
                        if error.localizedDescription.length > 0 {
                            errMsg = error.localizedDescription
                        }
                    }
                SVProgressHUD.showMessage(errMsg)
                finishTransaction(queue, transaction: transaction)
            case .restored:
                print("已购买过此商品")
                SVProgressHUD.showMessage("已购买过此商品")
                finishTransaction(queue, transaction: transaction)
            case .deferred:
                print("延迟处理")
            case .purchasing:
                print("商品添加进列表")
                SVProgressHUD.show(withStatus: "支付中...")
            default: break
        }
    }
}

private func finishTransaction(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction) {
    SKPaymentQueue.default().finishTransaction(transaction)
}

/// 重复提交验证
private func retryVerify() {
    if self.retryCount < 3 && self.verifySuccess == false {
        verifyPurchaseWithPaymentTrasaction()
        self.retryCount += 1
    }
}

/// 验证购买
private func verifyPurchaseWithPaymentTrasaction() {
    if let url = Bundle.main.appStoreReceiptURL {
        do {
            let receiptData = try Data(contentsOf: url)
            // 发送网络POST请求,对购买凭据进行验证
            if let sandBoxUrl = URL(string: verifyAddress) {
                var urlRequest = URLRequest(url: sandBoxUrl, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 30.0)
                urlRequest.httpMethod = "POST"
                let encodeStr = receiptData.base64EncodedString(options: .endLineWithLineFeed)
                let payload = "{\"receipt-data\" : \"\(encodeStr)\"}"
                urlRequest.httpBody = payload.data(using: .utf8)
                let task = URLSession.shared.dataTask(with: urlRequest, completionHandler: { [weak self] (data, response, error) in
                    guard let strongSelf = self else { return }
                    if error == nil {
                        do {
                            if let dict = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: AnyObject] {
                                print("支付收据验证结果===\(dict)")
                                if let status = dict["status"] as? NSNumber {
                                    if status.intValue == 0 {
                                        strongSelf.verifySuccess = true
                                        strongSelf.retryCount = 0
                                        print("支付收据验证成功")
                                        if let delegate = strongSelf.managerDelegate {
                                            // 这里可以将苹果返回的票据证明传给自己的服务器,由自己的服务验证是否支付成功,也可以不走服务器验证。走服务器验证是为了更加准确。
                                            delegate.rechargeSuccess(rechargeInfo: ["receipt-data": encodeStr])
                                        }
                                    } else {
                                        // 这个 21007 错误码判断,是因为默认验证地址是AppStore线上验证地址,所以在测试的时候会报这个错误,所以要将验证地址改为测试线。也可以自己在测试的时候写测试地址,上线时候改为线上地址,就不会报这个错误了。
                                        if status.intValue == 21007 {
                                            strongSelf.verifyAddress = SANDBOXUrl
                                            strongSelf.verifyPurchaseWithPaymentTrasaction()
                                        } else {
                                            print("支付收据验证失败")
                                            if let delegate = strongSelf.managerDelegate {
                                                delegate.rechargeFailed()
                                            }
                                        }
                                    }
                                }
                            }
                        } catch let error {
                            print(error)
                        }
                    } else {
                        print("支付收据验证失败")
                        strongSelf.verifySuccess = false
                        strongSelf.retryVerify()
                    }
                })
                task.resume()
            }
        } catch let error {
            print(error)
        }
    }
 }
}

好了,以上就是关于苹果内购的全部流程。仅供参考。

你可能感兴趣的:(iOS 苹果内购 IPA)