美文网首页
Swift之内购血泪史

Swift之内购血泪史

作者: Janise001 | 来源:发表于2019-09-29 18:51 被阅读0次

提起内购,有多少小朋友要干呕的?我要举个爪,呜呜呜呜呜。。。
前段时间公司要做一个知识付费项目,这意味着有新的功能和产品结构,团队摩拳擦掌准备好一切提交审核,Android端一切perfect~,iOS端静待审核,经过两天的等待,我终于在一个周末早上的五点半收到了被拒信息,真的是晴天霹雳,接入了支付宝和微信,支付方式是没有问题的,关键是知识付费要使用内购支付,说实话做项目之前没听过内购(原谅我一个iOS端用的是Android机),团队没人做过这个东西,于是顶着鸟窝头搜了下内购,任何在程序内可完成的付费商品在iOS端付费时都必须走内购,发现代码实现其实挺简单,但是网上很多人写帖子吐槽内购,主要原因如下:
1.内购要求严苛,不得涉及可触碰实体的任何一部分
2.抽取30%的金额,所以商家为了能挣到钱就把这30%放到了消费者身上,而提升后的价格和市场实际价格不符,很容易流失客源
3.内购只允许设置具体的等级价格,可设置某段时间内的价格波动,不能随心所欲地设置
4.内购类型的选择和使用,如选错内购类型你又不联系他们,你就会不断地体会什么叫“怀疑人生”,什么叫“绝望”,哈哈哈哈。。。呜呜呜呜呜。。。。
下面说正经的,不逼逼,上新鲜的代码

import UIKit

class DataTools: NSObject {
    class func getGoodsList(_ result: (Set<String>)->()) {
        result([内购产品id数组,字符串])
    }
}
内购产品id
import UIKit
import StoreKit
class TempViewController: UIViewController {
    var products: [SKProduct] = [SKProduct]()

       override func viewDidLoad() {
        super.viewDidLoad()
        DataTools.getGoodsList { (ids: Set<String>) -> () in//从我们自己的服务器上获取需要销售的额商品
            let request: SKProductsRequest = SKProductsRequest(productIdentifiers: ids)//上面的商品还要到苹果服务器进行验证, 看下哪些商品是可以真正被销售的(创建一个商品请求并设置请求的代理,由代理告知结果)
            request.delegate = self//设置代理, 接收可以被销售的商品列表数据
            request.start()//转到extension ViewController: SKProductsRequestDelegate {
        }
    }
    deinit {
        SKPaymentQueue.default().remove(self)
    }
/// 调起内购
// 检测是否有未完成的交易
func start() {
                    let transactions = SKPaymentQueue.default().transactions
                    if transactions.count > 0 {
                        let transaction = transactions.first
                        if let firstTransaction = transaction,firstTransaction.transactionState == SKPaymentTransactionState.purchased || firstTransaction.transactionState == SKPaymentTransactionState.purchasing {
                            SKPaymentQueue.default().finishTransaction(firstTransaction)
                            SKPaymentQueue.default().remove(self)
                        }
                    }
                    if SKPaymentQueue.canMakePayments() {//判断当前的支付环境, 是否可以支付
//                        self.showMessage("")
                        let productModel = self.products.first(where: { (model) -> Bool in
                            return model.productIdentifier == (你本地的产品内购id)
                        })
                        guard let product = productModel else {
                            self.hideMessage("")
                            HUD.showError("该产品不支持内购")
                            return
                        }
                        let payment = SKPayment(product: product)
                        SKPaymentQueue.default().restoreCompletedTransactions()
                        SKPaymentQueue.default().add(payment)//添加到支付队列
                        SKPaymentQueue.default().add(self)//监听交易状态
                    }
}
extension TempViewController: SKProductsRequestDelegate {
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {// 当请求完毕之后, 从苹果服务器获取到数据之后调用
        print(response.products)
        print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\(response.invalidProductIdentifiers)")
        self.products = response.products// 给数据源进行赋值
    }
}
extension TempViewController: SKPaymentTransactionObserver {// 交易队列监听
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {// 当交易队列里面添加的每一笔交易状态发生变化的时候调用
            switch transaction.transactionState {
            case .deferred:
                print("延迟处理")
            case .failed:
                print("支付失败")
                queue.finishTransaction(transaction)
                SKPaymentQueue.default().remove(self)
            case .purchased:
                HUD.showInfo("支付成功")
                // 本地验证凭证
                verifyPurchaseWithPaymentTrasaction()
                self.hideMessage("")
                // 验证凭证
                self.getReceipt()
                queue.finishTransaction(transaction)
                SKPaymentQueue.default().remove(self)
            case .purchasing:
                print("正在支付")
            case .restored:
                self.hideMessage("")
                print("已购买过此商品")
                SKPaymentQueue.default().remove(self)
            default: break
            }
        }
    }
    /// 获取票据
    func getReceipt() {
        let receiptUrl = Bundle.main.appStoreReceiptURL
        if let url = receiptUrl {
            do {
                let receiptData = try Data(contentsOf: url)
                logDebug("票据地址\(url)")
                let base64String = receiptData.base64EncodedString(options: .endLineWithLineFeed)
                // 将凭证传给后台由后台确认
                self.checkAppstorePayResultWithBase64String(base64String)
            }catch let error {
                logDebug(error)
            }
        }
    }
/// 验证购买
    func verifyPurchaseWithPaymentTrasaction() {
        //测试验证地址
        let SANDBOX: String = "https://sandbox.itunes.apple.com/verifyReceipt"
        //正式验证地址
        let AppStore: String = "https://buy.itunes.apple.com/verifyReceipt"
        let receiptUrl = Bundle.main.appStoreReceiptURL
        if let url = receiptUrl {
            do {
                let receiptData = try Data(contentsOf: url)
                logDebug("票据地址\(url)")
                let receiptURLString = receiptUrl?.absoluteString
                var vertifyAddress: String = SANDBOX
                vertifyAddress = (receiptURLString?.contains("sandbox") ?? true) ? SANDBOX : AppStore
                // 发送网络POST请求,对购买凭据进行验证
                if let sandBoxUrl = URL(string: vertifyAddress) {
                    var urlRequest = URLRequest(url: sandBoxUrl, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
                    urlRequest.httpMethod = "POST"
                    let encodeStr = receiptData.base64EncodedString(options: .endLineWithLineFeed)
                    let payload = "{\"receipt-data\" : \"\(encodeStr)\"}"
                    let payloadData = payload.data(using: .utf8)
                    urlRequest.httpBody = payloadData
                    do {
                        let result = try NSURLConnection.sendSynchronousRequest(urlRequest as URLRequest, returning: nil)
                        if result.isEmpty {
                            HUD.showError("收据验证失败")
                            return
                        }
                        do {
                            _ = try JSONSerialization.jsonObject(with: result, options: .allowFragments)
                            HUD.showInfo("收据验证成功")
                        }catch let error {
                            logDebug(error)
                        }
                    }
                }
            }catch let error {
                logDebug(error)
            }
        }
    }

在纯知识付费中不能够含有知识付费以外的任何实体的展示和描述,包括地址和教具,由于触及具体的金额消费所以内购不允许使用优惠券,只能搞活动促销了,是不是很悲伤😢😢😢😢😢?


价格调整

祝每一位在内购路上奋战的小伙伴可以尽快战胜内购💪💪💪💪💪💪!!!

后续:
今天是2020年9月21日,我今天回顾了之前写的这篇关于内购的帖子有些问题,是代码上的问题.

1.有些语法已被弃用,为不影响以后的使用,在这边做些修改, image.png

修改为

                        let session = URLSession.shared.dataTask(with: urlRequest) { (recvData, response, err) in
                            if err != nil {
                                print("无法连接服务器,购买校验失败")
                                return
                            }
                            if let data = recvData {
                                do {
                                    _ = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
                                    print("收据验证成功")
//                                    self.hideMessage("收据验证")
                                    HUD.showInfo("收据验证成功")
//                                    self.hideMessage("收据验证")
                                }catch let error {
                                    logDebug(error)
                                }
                            }
                        }
                        session.resume()
                    }

NSURLConnection.sendSynchronousRequest已被弃用,显改为URLSession.shared.dataTask。

2.当队列中存在事务,需要确认事务的状态,状态为SKPaymentTransactionState.purchasing表示在支付当中,此状态不可被finish,否则挂断,所以在start方法中 image.png
此处去除purchasing状态。

如有其它有误地方,欢迎指正,共同进步。

相关文章

  • Swift之内购血泪史

    提起内购,有多少小朋友要干呕的?我要举个爪,呜呜呜呜呜。。。前段时间公司要做一个知识付费项目,这意味着有新的功能和...

  • iOS开发之内购(Swift)

    关于协议税务和银行业务的资料填写以及申请我就不写了,自己Google+百度吧。毕竟各位大神写的也是蛮详细的。直接上...

  • iOS 开发之内购 – AppStore

    iOS 开发之内购 – AppStore

  • Swift进阶之内存模型和方法调度

    参考链接: Swift进阶之内存模型和方法调度 Swift 3必看:sizeof移进MemoryLayout 前言...

  • iOS 苹果内购流程

    本文参考: iOS开发之内购完全笔记 iOS开发内购全套图文教程 iOS应用程序内购/内付费(一) 代码...

  • iOS开发之内购-AppStore

    所谓内购就是在App内购买商品,如在游戏App中的购买道具、皮肤等;在电商App中的购买衣食住行的各种商品,如淘宝...

  • swift之内存布局

    struct和tuple内存布局 结构体和元组当前共享相同的布局算法,在编译器实现中称为“通用”布局算法。算法如下...

  • 付费下载转免费下载内购版本升级方案

    造个轮子,因为有个swift版本SwiftyStoreKit,没有找到oc版本。 swift内购很强大库:http...

  • swift 内购

    1.0 内购类型 a. 消耗型商品:只可以使用一次的产品,使用以后即失效,必须再次购买。示例:小说App购买的书币...

  • Swift学习之内存管理

    一、引用管理 和OC一样,Swift也是采取基于引用计数的ARC内存管理 强引用:默认情况下都是强引用 弱引用:通...

网友评论

      本文标题:Swift之内购血泪史

      本文链接:https://www.haomeiwen.com/subject/ylldyctx.html