前言 ​​​​​​​​​​​​​​

最近又重新写了很久之前写的内购,该项目中没有订阅,而另一个项目中包含了订阅和消费型的购买。重新整理了一下,项目中用的是SwiftyStoreKit。

我们先来看一下内购类型:

由于项目中有购买虚拟币和订阅,这里就选择了消费型和自动续期订阅,其他的配置网上一搜一大堆这里就省略了。。。

1. 购买

先看一下购买方法

* Purchase a product * - Parameter productId: productId as specified in iTunes Connect * - Parameter quantity: quantity of the product to be purchased * - Parameter atomically: whether the product is purchased atomically (e.g. finishTransaction is called immediately) * - Parameter applicationUsername: an opaque identifier for the user’s account on your system * - Parameter completion: handler for result public class func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", simulatesAskToBuyInSandbox: Bool = false, completion: @escaping (PurchaseResult) -> Void) { sharedInstance.purchaseProduct(productId, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, simulatesAskToBuyInSandbox: simulatesAskToBuyInSandbox, completion: completion)

参数1:productId,在iTunes Connect中指定的productId;

参数2:  quantity,购买数量;

参数3:  atomically,设置为ture,不管商品有没有验证成功,都会调用finishTransaction,项目里我设置为false,等客户端向服务端校验成功手动调用finishTransaction;

参数4:applicationUsername,系统上用户帐户的不透明标识符。看了好多文章都是传入用户id,这样就能将该订单充到固定账号上,由于我们项目中并没有暴露给我们用户id,所以我这里并没有传入任何数据;

参数5: simulatesAskToBuyInSandbox,是否是沙盒模拟支付;默认不填为false。

2.购买成功获取receipt-data

这个票据是我们支付成功后苹果返回给我们的。

参数:forceRefresh,如果为true,则刷新收据,即使收据已经存在。我这里用了false

* Fetch application receipt * - Parameter forceRefresh: If true, refreshes the receipt even if one already exists. * - Parameter completion: handler for result public class func fetchReceipt(forceRefresh: Bool, completion: @escaping (FetchReceiptResult) -> Void) { sharedInstance.receiptVerificator.fetchReceipt(forceRefresh: forceRefresh, completion: completion)

将返回的结果给服务端校验时需要将返回的data转成base64。

3.向服务端校验,验证成功调用finishTransaction

以上三个步骤就是购买的过程,接下来看一下代码

  //1.购买
    func pay(purchaseProductId:String,completeHandle:@escaping (Bool) -> Void) {
        if !SwiftyStoreKit.canMakePayments {
            print("您的手机没有打开程序内付费购买")
            completeHandle(false)
            return
        SwiftyStoreKit.purchaseProduct(purchaseProductId, quantity: 1, atomically: false) { purchaseResult in
            switch purchaseResult {
            case .success(let purchase):
                //处理交易
                self.handleTransaction(transaction: purchase.transaction) { result in
                    completeHandle(result)
            case .error(let error):
   switch error.code {
                case .unknown:
                    print("Unknown error. Please contact support")
                case .clientInvalid: 
                    print("Not allowed to make the payment")
                case .paymentCancelled:
                    break
                case .paymentInvalid:
                    print("The purchase identifier was invalid")
                case .paymentNotAllowed: 
                    print("The device is not allowed to make the payment")
                case .storeProductNotAvailable: 
                    print("The product is not available in the current storefront")
                case .cloudServicePermissionDenied: 
                    print("Access to cloud service information is not allowed")
                case .cloudServiceNetworkConnectionFailed: 
                    print("Could not connect to the network")
                case .cloudServiceRevoked: 
                    print("User has revoked permission to use this cloud service")
                default :
                    print("其他错误")
                completeHandle(false)
    //2.处理交易
    func handleTransaction(transaction:PaymentTransaction,completeHandle:@escaping ((Bool) -> Void)) {
        //获取receipt
        SwiftyStoreKit.fetchReceipt(forceRefresh:false) { result in
            switch result {
            case .success(let receiptData):
                let encryptedReceipt = receiptData.base64EncodedString(options: [])
                print("获取校验字符串Fetch receipt success:\n\(encryptedReceipt)")
                //3.服务端校验
                if 验证成功 {
                    SwiftyStoreKit.finishTransaction(transaction)
                    completeHandle(true)
                }else{
                    completeHandle(false)
            case .error(let error):
                print(" --- Fetch receipt failed: \(error)")
                completeHandle(false)

当然也可以本地验证啦。

//本地校验苹果数据
func verifyReceipt(service: AppleReceiptValidator.VerifyReceiptURLType) {
     let receiptValidator = AppleReceiptValidator(service: service, sharedSecret: sharedSecret)
     SwiftyStoreKit.verifyReceipt(using: receiptValidator) { (result) in
        switch result {
         case .success (let receipt):
             let status: Int = receipt["status"] as! Int
              //沙盒测试
               if status == 21007 {
                print("receipt:\(receipt)")
              case .error(let error):
                   print("error:\(error)")

sharedSecret获取方式,点击App 专用共享密钥,可以看到

二.监听完成支付的队列

AppDelegate添加以下代码,在启动时添加应用程序的观察者可确保在应用程序的所有启动过程中都会持续,从而允许应用程序接收所有支付队列通知,如果有任何未完成的处理,将会触发block,以便我们更新UI等。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        SwiftyStoreKit.completeTransactions(atomically: false) { [weak self] (purchases) in
            guard let `self` = self else {return}
            for purchase in purchases {
                //未登录不处理交易
                if 未登录 {
                    return
                switch purchase.transaction.transactionState {
                case .purchased,.restored:
                    print("已完成订单")
                    //处理订单交易
                    handleTransaction(transaction: purchase.transaction) { bool in }
                case .failed,.purchasing,.deferred:
                    break

三.恢复购买

 恢复购买用来让用户访问已经购买过的内容,比如,当用户换一台新手机,为了保证用户不丢失已经在旧手机上购买过的产品等。

func restorePurchases(_ completeHolder: ((Bool)->Void)? = nil) {
            SwiftyStoreKit.restorePurchases(atomically: false) { results in
                if results.restoreFailedPurchases.count > 0 {
                    print("Restore Failed: \(results.restoreFailedPurchases)")
                else if results.restoredPurchases.count > 0 {
                    for purchase in results.restoredPurchases {
                        handleTransaction(transaction: purchase) { dialog, code in }
                    print("Restore Success: \(results.restoredPurchases)")
                else {
                    print("Nothing to Restore")
                if completeHolder != nil {
                    completeHolder!(true)

好了,以上就是购买的过程,接下来就说说丢单的问题。

四.丢单及解决思路

为什么会丢单呢? 我们向苹果服务器支付并且拿到票据,但是我们向服务端去校验的时候,网络出现问题,就会出现丢单,当然这只是其中一种网络原因造成的丢单,所以在项目里等向服务器校验成功了再调用finishTransaction。这样才会在队列里移除。

还有一个就是在支付过程中人为退出app.

1.当前客户端登录了用户A,现在支付校验(即客户端向服务器端校验)失败,这时A退出登录,换成B登录,此时我们重新启动App,可能将该给A的服务都给了B,这让A很吃亏啊。

解决方法(1):在本地存一个用户分离的表,每一个用户对应一个数组,数组中存放我们的票据,每次app启动时处理跟本用户相关的校验,但是如果真的给了B,A找了过来,也是个麻烦。

解决方法(2):每次购买成功都调用finishTransaction。依照解决方法(1)存起来,在每次购买前判断本地是否存有没有处理的订单。

2.上面的方法没有解决根本,当然了,确实没有,如果我们卸载了app重新安装了app,那么我们存在本地的表是没有用的,上述问题依旧会存在。那么还有一个方法就是将这些数据存储在钥匙串里,不清空钥匙串还是可以的。

3.关于多个苹果账号订阅一个客户端用户,后端处理比较麻烦,我们采取的措施是当有一个苹果账号为该用户订阅过了就不允许其他用户继续订阅。

当然了,丢单必不可免,我们只能尽量减少丢单,实在不行就只能找客服。

前言最近又重新写了很久之前写的内购,该项目中没有订阅,而另一个项目中包含了订阅和消费型的购买。重新整理了一下,项目中用的是SwiftyStoreKit。我们先来看一下内购类型:由于项目中有购买虚拟币和订阅,这里就选择了消费型和自动续期订阅,其他的配置网上一搜一大堆这里就省略了。。。一.购买1. 购买先看一下购买方法 /** * Purchase a product * - Parameter productId: productId as ...
SwiftyStoreKit是适用于iOS,tvOS,watchOS,macOS和Mac Catalyst的轻量级In App Purchases框架。 超级易于使用的基于块的API 支持可消费和不可消费的应用内购买 支持免费,可自动更新和不可更新的订阅 在App StoreiOS 11)中开始支持应用程序内购买 支持订阅折扣和优惠 远程收据验证 验证购买,订阅,订阅组 下载Apple托管的内容 兼容iOS,tvOS,watchOS,macOS和Catalyst 想要的贡献 SwiftyStoreKit使大量开发人员可以轻松地集成应用内购买。 但是,该项目现在由社区主导。 我们需要帮助来构建功能并编写测试(请参阅 )。 维护者通缉 作者不再维护这个项目。 如果您想成为维护者,请并进入频道。 展望未来,SwiftyStoreKit应该由社区来为社区制作。 更多信息。 如果您在过去五年中交付了一个应用程序,那么您可能会很高兴。 某些功能(如折扣)仅在新的OS版本上可用,但大多数功能可追溯到: 的iOS watchOS Mac催化剂 Easy Podfile ! PodfileKit将github上常见的iOS(Swift)第三方框架进行了汇总,并且将框架进行了分类,为用户管理第三方框架提供了方便。 # github网址:https://github.com/adong666666/PodfileKit - [设置平台](#设置平台) - [指定第三方框架](#指定第三方框架) - [框架分类](#框架分类) - [子框架](#子框架) - [建立分组](#建
填写其中的付费 App,填写点公司名称、公司简介、公司帐号、公司之类的。 由于已经填写过了无法截图,可以去搜搜其他人的分享。 银行账户: 填的的时候注意银行帐号选择中国之后就是国内银行的代码了,选择每个地区下边的银行代码都是会变的。另外银行的受益人需要填写 英文 这个比较坑,名在前姓在后。 报税表: 参照别人的截图勾选一下就行,就是本着能不填就不填的原则,稍微写了点。另外那个报税表不需要下载填写就行。 联系信息:
【课程概括】包含306节超多互动教程,基于新版的Swift和Xcode。手把手讲解大量实用的iOS开发开源类库:BonMot、PKHUD、DZNEmptyDataSet、Alamofire、Moya、Promise、Kingfisher、SnapKit、组件化编程、RxSwift响应式编程、Lottie动画、Hero转场动画、app主题更换、强大的幻灯片、GPUImage图像视频处理、Realm数据库、二维码创建和读取、模拟网络fake数据、自动化压力测试。手把手学习iOS开发中的强大的第三方类库,详细讲解Github中的热门的iOS开发开源项目。助您快速、优雅地解决iOS开发中棘手的业务需求!【课程特点】 1、代码逐行讲解2、语言简洁、精练、瞄准问题的核心所在,减少对思维的干扰,并节省您宝贵的时间3、完美贴心的操作提示,让您的眼睛始终处于操作的焦点位置,不用再满屏找光标4、每个视频都很短小精悍,即方便于您的学习和记忆,也方便日后对功能的检索 【福利来了】获取306节所有课程源码及加入学习群!
class CDIAPManager: NSObject { class func getPurchaseProductList(retur:@escaping (_ productArr:[CDAppBuyInfo]) ->Void){ SwiftyStoreKit.retrieveProductsInfo(["xxx","xxx","xxx"]) { result in
Session: WWDC2018 Best Practices and What’s New with In-App Purchases 对于一个标准的 IAP 流程,大致如下图所示 翻译成中文就是 设置商品 ID(跟 ITC 上面的 ID 一致) 根据商品 ID 去苹果后台获取商品信息,这时会获得商品名字、价格等信息 把 IAP 的购买界面展示给用户,用户可以同意购买并点击购买按钮。