StoreKit In App Purchase 内购天坑指南

因公司业务要求需要接入iOS内购,期间采坑无数,但也积累了一些经验吧.鉴于网上太多资料都已经过时,本篇文章也有可能过时,所以仅供大家参考

语言环境

Swift4.2

参考资料

最好是苹果的官方文档,另外,这有一份关于收据验证的中文文档也非常实用 -- 收据验证编程指南

内购商品种类

实现需求包括基本内购商品的购买,以及特殊的订阅商品的购买. 首先是商品的种类


根据需要选择商品类型,然后记下productId

内购代码书写

大致逻辑是iOS端发起支付, 通过添加观察者同步交易的状态. 最后用户付款成功之后验证.
验证的方式分为两种: 客户端向苹果发起验证 或者 客户端向后端发起请求,让后端去向苹果验证.
无论是哪种验证方式核心都是把存储在本地的收据发送到苹果的服务器校验
基础代码是这样的:

    /// 向苹果发起支付请求
    func requestIAP(productId: String) {        
        if SKPaymentQueue.canMakePayments() {
            let set = Set([productId])
            let purchaseRequest = SKProductsRequest(productIdentifiers: set)
            purchaseRequest.delegate = self
            purchaseRequest.start()
        } else {
            CustomToast.showDialog("用户禁止App内购买")
        }
    }

SKProductsRequestDelegate的回调中添加观察者

    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        guard response.products.count == 1 && self.currentProductId != nil else {
            dealToast(with: .productIdError)
            return
        }
        let receiveId = response.products[0].productIdentifier
        guard receiveId == currentProductId! else {
            dealToast(with: .receiveIdError)
            return
        }
        SKPaymentQueue.default().add(SKPayment(product: response.products[0]))
    }

观察交易的状态

    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for tran in transactions {
            switch tran.transactionState {
            case .purchased:
                // 用户购买成功
                break
            case .restored:
                // 用户恢复购买
                break
            case .failed:
                if tran.error?.isCancelled {
                    // 用户取消
                }
                else {
                    // 其他情况失败
                }
            case .deferred:
                // 延迟支付
                break
            case .purchasing:
                // 正在支付
                break
            }
        }
    }  

购买完成之后苹果就完成对用户的扣款,这时我们需要对用户的购买进行验证. 无论是客户端验证服务器端验证都是发送收据到苹果的服务器去验证收据的有效性. 客户端需要获取到存储在本地的收据,代码如下:

        guard let receiptUrl = Bundle.main.appStoreReceiptURL, let receiveData = NSData(contentsOf: receiptUrl) else {
            throw KRIAPError.authServerError
        }
        let receipt = String(data: receiveData.base64EncodedData(options: NSData.Base64EncodingOptions.endLineWithLineFeed), encoding: .utf8) ?? "ERROR"

苹果的服务器分为沙盒和正式,只有上线App Store(也就是从App Store里下载的)才走正式的验证,其他统统走沙盒.而且苹果审核的时候也是走的沙盒验证,这点需要注意一下,否则会因为苹果走沙盒支付不通而导致审核被拒.
验证就是发送receipt到两个苹果的URL,分别是

https://sandbox.itunes.apple.com/verifyReceipt   // 沙盒
https://buy.itunes.apple.com/verifyReceipt //正式

用Postman验证收据如图,客户端和服务器同理. 其中password是订阅时需要用到的App专用共享密匙,在iTunes Connect - 内购 - App 专用共享密钥能够找到

Postman验证收据

苹果那边会返回一个JSON,具体参见收据验证编程指南

开发者根据苹果返回的结果,决定下面的操作.

以后就是苹果内购的基本流程了,下面说遇到的一些坑.

内购填坑指南

为什么IAP不好用!? 首先是因为苹果的网不好,现在貌似国内正式环境还挺快的,但是沙盒依然巨慢无比!!! 而且整个IAP的设计思路其实是让用户通过苹果付款,让开发者通过苹果验证该用户是否付款以决定是否开放付费功能! 但是国内的互联网产品基本都有自己的账号, 用户付费是付费到这个产品的账号(比如微信号XXX)而不是Apple Id(xx@gmail.com)上面. 鉴于验证的耗时也一般不会每次都向苹果服务器发消息做用户是否付费的验证.再加上客户端验证本身容易被破解,所以一般而言大家都是

订阅问题

仅仅用苹果支付,验证通过之后就把用户已付费计入服务器端,下次直接通过服务器端来判断该用户是否付费! 微信和支付宝都是这样的,仅仅是一个支付工具. 但是苹果就想全都管.

明白苹果的思路之后就知道怎么在苹果思路之后做基于我们自己账号的付费了.消耗型项目其实没什么问题.主要是订阅,这个服务器端有自己的订阅时间,而苹果所给的订阅过期时间是需要验证收据才能得到的. 这导致的一个问题,我在做自动续订(连续包月)时在想怎么收到苹果的通知说用户次月续费成功了!? 其实通过在AppDelegate添加一个观察者是可以收到续订的回调的. 通过transaction.original不为空来判断是不是次月的续订,当然这样判断是有问题的. 首先是沙盒情况下回调非常不稳定,其次貌似回复购买或者忘记结束交易也会回调. 如果一回调就判定是续费成功显然是不对的

抛弃续费这个概念,我们采取的是每天服务器端验证收据以确定用户的订阅是否可用. 沙盒情况下也相对好测试. 关于怎么测自动续期可以参照这个的时间

更坑的是用户取消订阅,变更订阅类型(包月到包年,包月到单月),自己写逻辑会乱死而且跟苹果对不上,还是根据苹果返回的JSON来吧.

漏单问题

用户点了购买按钮就马上把用户的购买信息存储下来(productId,购买类型,时间戳什么的),如果用户在苹果那购买成功了但是突然断网我们没有去后端验证导致漏单. 用户重进(或者恢复购买)需要结合本地的购买信息和本地收据再次向服务器端验证,验证通过后删除本地购买信息

沙盒测试

添加的沙盒账号一定要是不存在的Apple Id

审核

  • 审核是沙盒环境!
  • Apple Store简介里面需要有内购信息,订阅需要告诉用户怎么取消,一些项目需要添加恢复购买
  • 提交审核的时候需要把IAP项目添加进去,否则只会审APP不会审IAP

暂时写什么这么多吧,有新坑继续填,有问题可以留在评论区.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容