iOS內(nèi)購-從客戶端到服務(wù)器分析

一、財(cái)務(wù)配置

image.png

登錄到蘋果哦itunsconnect后臺后,可以到協(xié)議、稅務(wù)和銀行業(yè)務(wù)這里交給財(cái)務(wù)配置就行

二、itunes后臺商品配置

進(jìn)入到我們的iTunes后臺后,在App Store 下面 有一個(gè)子欄目App 內(nèi)購買項(xiàng)目這里點(diǎn)擊管理我們就能看到我們進(jìn)行商品配置的入口了
我們可以添加的商品類型

  • Consumable 消耗品: 可以多次購買,適合游戲內(nèi)貨幣
  • Non-Consumable 非消耗品: 購買一次,永久有效,適合解鎖永久功能;
  • Non-Renewing Subscription 非續(xù)訂訂閱: 在固定時(shí)間段內(nèi)可用的內(nèi)容,如vip
  • Auto-Renewing Subscription 自動續(xù)費(fèi)訂閱:到期會自動扣款的訂閱,適用按月的vip;
    詳細(xì)內(nèi)容請查看:https://developer.apple.com/support/app-store-connect/#//apple_ref/doc/uid/TP40013727-CH3-SW1
image.png

三、客戶端集成(integration)

1、支付流程

image.png

2、iOS- swift代碼

import StoreKit

class InAppPurchaseManager: NSObject,SKPaymentTransactionObserver, SKProductsRequestDelegate {
    //登錄的時(shí)候調(diào)用
    func addIAPObserver() -> Void {
        SKPaymentQueue.default().remove(self)
        SKPaymentQueue.default().add(self)
    }
    //登出的時(shí)候調(diào)用,  防止發(fā)貨到未知用戶
    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)
        //發(fā)起購買請求
        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發(fā)送給服務(wù)器,去驗(yàn)證票據(jù)的正確性
        //todo
    }
    //交易失敗
    func fail(_ transaction: SKPaymentTransaction) {
        //todo
    }
    //交易restored
    func restored(_ transaction:SKPaymentTransaction) {
        //todo
    }
    
    //交易完成記得finish掉,不然會出現(xiàn)卡單,無法進(jìn)行下次支付
    func finishTransactionWith(transactionId transctionId:String) {
        let transactions = SKPaymentQueue.default().transactions
        for transaction in transactions {
            if transaction.transactionIdentifier == transctionId {
                SKPaymentQueue.default().finishTransaction(transaction)
                break
            }
        }
    }
}

四、簡單的服務(wù)器驗(yàn)票邏輯(nodejs)

整個(gè)服務(wù)邏輯牽扯太多,我用nodejs整理出單純的驗(yàn)票部分,簡單的校驗(yàn)和借鑒是沒有問題的。

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)) {
                // 指向沙盒測試環(huán)境再次驗(yàn)證
                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 驗(yàn)證客戶端傳過來的transactionIdentifier,是否存在于"in_app"中
});

參考鏈接:
http://www.itdecent.cn/p/2f98b7937b6f

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容