美文网首页知识图谱
iOS内购-从客户端到服务器分析

iOS内购-从客户端到服务器分析

作者: Johnny_Z | 来源:发表于2021-01-18 16:33 被阅读0次

    一、财务配置

    image.png

    登录到苹果哦itunsconnect后台后,可以到协议、税务和银行业务这里交给财务配置就行

    二、itunes后台商品配置

    进入到我们的iTunes后台后,在App Store 下面 有一个子栏目App 内购买项目这里点击管理我们就能看到我们进行商品配置的入口了
    我们可以添加的商品类型

    image.png

    三、客户端集成(integration)

    1、支付流程

    image.png

    2、iOS- swift代码

    import StoreKit
    
    class InAppPurchaseManager: NSObject,SKPaymentTransactionObserver, SKProductsRequestDelegate {
        //登录的时候调用
        func addIAPObserver() -> Void {
            SKPaymentQueue.default().remove(self)
            SKPaymentQueue.default().add(self)
        }
        //登出的时候调用,  防止发货到未知用户
        func removeIAPObserver() -> Void {
            SKPaymentQueue.default().remove(self)
        }
        
        func addPayment(_ productId:String) {
            let product = NSSet(array: [productId] as [AnyObject])
            let request = SKProductsRequest(productIdentifiers: product as! Set<String>)
            request.delegate = self
            request.start()
        }
        
        
        func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
            let avaliableProducts = response.products
            guard let product = avaliableProducts.first else {
                return
            }
            let mutablePayment = SKMutablePayment.init(product: product)
            //发起购买请求
            SKPaymentQueue.default().add(mutablePayment)
        }
        
        func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
            for transaction in transactions {
                switch transaction.transactionState {
                case .purchased:
                    self.complete(transaction)
                    break
                case .restored:
                    self.restored(transaction)
                    break
                case .failed:
                    self.fail(transaction)
                    break
                default:
                    //其他情况
                    break
                }
            }
        }
        
        //交易完成
        func complete(_ transaction: SKPaymentTransaction) -> Void {
            guard let receiptUrl = Bundle.main.appStoreReceiptURL, let receiveData:NSData = NSData(contentsOf: receiptUrl) else {
                return
            }
            let receiptString = receiveData.base64EncodedString(options: .endLineWithLineFeed)
            let transactionIdentifier = transaction.transactionIdentifier
            //将transactionIdentifier & receiptString发送给服务器,去验证票据的正确性
            //todo
        }
        //交易失败
        func fail(_ transaction: SKPaymentTransaction) {
            //todo
        }
        //交易restored
        func restored(_ transaction:SKPaymentTransaction) {
            //todo
        }
        
        //交易完成记得finish掉,不然会出现卡单,无法进行下次支付
        func finishTransactionWith(transactionId transctionId:String) {
            let transactions = SKPaymentQueue.default().transactions
            for transaction in transactions {
                if transaction.transactionIdentifier == transctionId {
                    SKPaymentQueue.default().finishTransaction(transaction)
                    break
                }
            }
        }
    }
    

    四、简单的服务器验票逻辑(nodejs)

    整个服务逻辑牵扯太多,我用nodejs整理出单纯的验票部分,简单的校验和借鉴是没有问题的。

    const https = require("https")
    var postData = "客户端传过来receiptString";
    
    var IAPVerifier = function(){};
    
    IAPVerifier.verifyWithRetry = function(receipt, isBase64, cb) {
        var encoded = null, receiptData = {};
        if (isBase64) {
            encoded = receipt;
        } else {
            encoded = new Buffer(receipt).toString('base64');
        }
        receiptData['receipt-data'] = encoded;
        var options = this.requestOptions();
        return this.verify(receiptData, options, (function(_this) {
            return function(error, data) {
                if (error) return cb(error);
                if ((21007 === (data != null ? data.status : void 0)) && (_this.productionHost == _this.host)) {
                    // 指向沙盒测试环境再次验证
                    options.host = 'sandbox.itunes.apple.com';
                    return _this.verify(receiptData, options, function(err, data) {
                        return cb(err, data);
                    });
                } else {
                    return cb(error, data);
                }
            };
        })(this));
    };
    
    
    /*
      verify the receipt data
     */
    
    IAPVerifier.verify = function(data, options, cb) {
        var post_data = JSON.stringify(data);
        var request = https.request(options, (function(_this) {
            return function(response) {
                var response_chunk = [];
                response.on('data', function(data) {
                    if (response.statusCode !== 200) {
                        return cb(new Error("response.statusCode != 200"));
                    }
                    response_chunk.push(data);
                });
                return response.on('end', function() {
                    var responseData, totalData;
                    totalData = response_chunk.join('');
                    try {
                        responseData = JSON.parse(totalData);
                    } catch (_error) {
                        return cb(_error);
                    }
                    return cb(null, responseData);
                });
            };
        })(this));
        request.write(post_data);
        request.end();
        request.on('error', function (exp) {
            console.log('problem with request: ' + exp.message);
        });
    };
    
    
    IAPVerifier.requestOptions = function() {
        return options = {
            host: 'buy.itunes.apple.com',
            port: 443,
            path: '/verifyReceipt',
            method: "POST",
            rejectUnauthorized: false/*不加:返回证书不受信任CERT_UNTRUSTED*/
        };
    };
    
    
    IAPVerifier.verifyWithRetry(postData, true, function (e, responseData) {
        console.log("responseData:",JSON.stringify(responseData,null,'\t'));
        //todo 验证客户端传过来的transactionIdentifier,是否存在于"in_app"中
    });
    

    参考链接:
    https://www.jianshu.com/p/2f98b7937b6f

    相关文章

      网友评论

        本文标题:iOS内购-从客户端到服务器分析

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