IAP的實(shí)現(xiàn)的流程

IAP流程
- app端通過產(chǎn)品Id發(fā)送創(chuàng)建
SKProductsRequest請(qǐng)求獲取產(chǎn)品 - 獲取到產(chǎn)品后創(chuàng)建
SKPayment發(fā)起支付請(qǐng)求 - 監(jiān)聽支付回調(diào)
- 支付成功獲取支付憑證
- 通過支付憑證,向服務(wù)端發(fā)起請(qǐng)求,校驗(yàn)憑證的正確性,創(chuàng)建訂單
- app端接收校驗(yàn)后的服務(wù)器響應(yīng),接收數(shù)據(jù)回調(diào)
IAP自動(dòng)續(xù)期訂閱代碼實(shí)戰(zhàn)
- 以設(shè)計(jì)一個(gè)
IAPService模塊舉例說明
IAPService的對(duì)外接口
- 直接購買
-
注意:怎么才叫購買成功:Apple端支付成功,自己的服務(wù)端校驗(yàn)成功,訂單生產(chǎn)成功,才算完成了購買,所以
SKPaymentQueue.default().finishTransaction(lastTansation)要在這些流程都跑通之后,才去調(diào)用,如果不調(diào)這個(gè)finish方法,則可以從SKPaymentQueue.default().transactions獲取到這相關(guān)的交易,喚醒操作校驗(yàn)未完成的訂單或者訂閱續(xù)費(fèi)成功之后,都可以在這個(gè)transactions中獲取到,但是有可能有延遲,所以要在每次app啟動(dòng)的時(shí)候,執(zhí)行一個(gè)喚醒操作
func buyProduct(_ productId: String) {
if SKPaymentQueue.canMakePayments() {
let prefetched = prefetchProducts[productId]
let lastTansation = SKPaymentQueue.default().transactions.filter { $0.payment.productIdentifier == productId }.first
if let lastTansation = lastTansation, lastTansation.transactionState == .purchased { /// 購買成功過,直接去校驗(yàn)
let productId = lastTansation.payment.productIdentifier
let isFirstBuy = lastTansation.original == nil
verifyBuySession(productId, isFirstBuy: isFirstBuy) {
SKPaymentQueue.default().finishTransaction(lastTansation)
}
} else if let prefetched = prefetched {
let payment = SKPayment(product: prefetched)
SKPaymentQueue.default().add(payment)
} else {
var products = Set<String>()
products.insert(productId)
let request = SKProductsRequest(productIdentifiers: products)
request.delegate = self
request.start()
}
} else {
buyFailure.call(nil)
NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if response.products.isEmpty {
return
}
if request == preFetchRequest {
/// 注意線程安全
lock.lock()
response.products.forEach { product in
prefetchProducts[product.productIdentifier] = product
}
lock.unlock()
return
}
if let product = response.products.first {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
} else {
buyFailure.call(nil)
NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
transactions.forEach { (transation) in
switch transation.transactionState {
/// 購買成功
case .purchased:
let productId = transation.payment.productIdentifier
let isFirstBuy = transation.original == nil
verifyBuySession(productId, isFirstBuy: isFirstBuy) {
SKPaymentQueue.default().finishTransaction(transation)
}
case .failed:
handleFailed(transation)
case .restored:
handleRestored(transation)
case .purchasing:
break
case .deferred:
break
@unknown default:
debugPrint("IAPService - @unknown default")
}
}
}
- 冷啟動(dòng)校驗(yàn)(喚醒操作)
- 如果有在蘋果服務(wù)器購買成功,但是在自己的服務(wù)器都沒有創(chuàng)建好訂單,則冷啟動(dòng)重新校驗(yàn)未完成的訂單
- 如果沒有未完成的訂單,則獲取一下當(dāng)前憑證的最新信息,保持信息的同步,比如訂閱過期了,app端有可能沒有接收到回調(diào)信息,這樣就會(huì)導(dǎo)致APP端一直處于訂閱狀態(tài),當(dāng)然注冊(cè)了交易狀態(tài)的監(jiān)聽也可以知道,但是信息有可能會(huì)延遲,這樣做,相當(dāng)于一個(gè)保險(xiǎn)操作
- 喚醒操作的目的:1.初始化
IAPService單例,注冊(cè)監(jiān)聽,2.校驗(yàn)續(xù)費(fèi)或者已經(jīng)在Apple購買成功,但是沒有在自己服務(wù)端生成訂單的交易
/// 冷啟動(dòng)校驗(yàn)未完成的訂單
func wakeUp() {
guard SKPaymentQueue.canMakePayments() else {
return
}
guard let lastTansition = SKPaymentQueue.default().transactions.filter({ $0.transactionState == .purchased}).first else {
return
}
let productId = lastTansition.payment.productIdentifier
let isFirstBuy = lastTansition.original == nil
verifyBuySession(productId, isFirstBuy: isFirstBuy) {
SKPaymentQueue.default().finishTransaction(lastTansition)
}
}
- 恢復(fù)購買
- 發(fā)起回復(fù)購買
-
paymentQueueRestoreCompletedTransactionsFinished中接收回調(diào)的結(jié)果 - 如果憑證有效,直接調(diào)取服務(wù)端校驗(yàn)接口,返回IAP數(shù)據(jù)信息
- 如果憑證無效, 發(fā)起刷新憑證請(qǐng)求, 監(jiān)聽請(qǐng)求回調(diào)結(jié)果
- 再次嘗試判斷憑證是否為空,若不為空,則進(jìn)行憑證校驗(yàn),返回IAP數(shù)據(jù)信息
func restore() {
guard SKPaymentQueue.canMakePayments() else {
return
}
SKPaymentQueue.default().restoreCompletedTransactions()
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
/// 憑證無效則刷新一下
if reciepString == nil {
let refresh = SKReceiptRefreshRequest()
refresh.delegate = self
refresh.start()
} else {
verifyRestoreSession()
}
}
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
restoreFailure.call(error)
NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
}
func requestDidFinish(_ request: SKRequest) {
/// 如果是恢復(fù)購買時(shí)刷新憑證的request
if request is SKReceiptRefreshRequest {
if reciepString == nil {
restoreFailure.call(AppError(message: "error.SKReceiptRefreshRequest", code: ErrorCode(rawValue: -100) ?? .none))
NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
} else {
verifyRestoreSession()
}
}
if request == preFetchRequest {
preFetchRequest = nil
}
}
- 預(yù)先拉取商品
func prefetchProducts(_ productIds: Set<String>) {
guard SKPaymentQueue.canMakePayments() else { return }
preFetchRequest = SKProductsRequest(productIdentifiers: productIds)
preFetchRequest?.delegate = self
preFetchRequest?.start()
}
- 信息回調(diào)(通知的形式)
內(nèi)部接口
- 在
IAPService模塊創(chuàng)建時(shí)添加支付狀態(tài)的監(jiān)聽
private override init() {
super.init()
SKPaymentQueue.default().add(self)
}
- 處理產(chǎn)品請(qǐng)求,交易狀態(tài)更新的回調(diào)
extension IAPService: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if response.products.isEmpty {
return
}
if request == preFetchRequest {
/// 注意線程安全
lock.lock()
response.products.forEach { product in
prefetchProducts[product.productIdentifier] = product
}
lock.unlock()
return
}
if let product = response.products.first {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
} else {
buyFailure.call(nil)
NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
buyFailure.call(error)
NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
}
func requestDidFinish(_ request: SKRequest) {
/// 如果是恢復(fù)購買時(shí)刷新憑證的request
if request is SKReceiptRefreshRequest {
if reciepString == nil {
restoreFailure.call(AppError(message: "error.SKReceiptRefreshRequest", code: ErrorCode(rawValue: -100) ?? .none))
NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
} else {
verifyRestoreSession()
}
}
if request == preFetchRequest {
preFetchRequest = nil
}
}
}
extension IAPService: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
transactions.forEach { (transation) in
switch transation.transactionState {
/// 購買成功
case .purchased:
let productId = transation.payment.productIdentifier
let isFirstBuy = transation.original == nil
verifyBuySession(productId, isFirstBuy: isFirstBuy) {
SKPaymentQueue.default().finishTransaction(transation)
}
case .failed:
handleFailed(transation)
case .restored:
handleRestored(transation)
case .purchasing:
break
case .deferred:
break
@unknown default:
debugPrint("IAPService - @unknown default")
}
}
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
/// 憑證無效則刷新一下
if reciepString == nil {
let refresh = SKReceiptRefreshRequest()
refresh.delegate = self
refresh.start()
} else {
verifyRestoreSession()
}
}
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
restoreFailure.call(error)
NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
}
}
- 校驗(yàn)憑證,生成訂單,獲取當(dāng)前憑證的最新信息
/// 校驗(yàn)購買時(shí)使用
fileprivate func verifyBuySession(_ productId: String? = nil, isFirstBuy: Bool, success: (() -> Void)? = nil) {
let provider = MoyaProvider<IAPNetWorkTarget>()
guard let reciepString = reciepString else {
return
}
func paraseReponse(_ response: Result<Response, MoyaError>, success: (() -> Void)? = nil) {
switch response {
case .success(let res):
guard let resModel = try? res.model(IAPResponse.self) else {
return
}
if let data = resModel.data {
self.buySuccess.call(data)
NotificationCenter.default.post(name: NSNotification.Name.iapBuySuccess, object: data)
success?()
} else {
let error = AppError(message: resModel.message ?? "", code: ErrorCode(rawValue: resModel.status) ?? .none)
self.buyFailure.call(error)
NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
}
case .failure(let error):
self.buyFailure.call(AppError(message: error.localizedDescription, code: ErrorCode(rawValue: -100) ?? .none))
NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
}
}
if isFirstBuy, let productId = productId { /// 訂閱
provider.request(.purchase(productId, reciepString)) { response in
paraseReponse(response, success: success)
}
} else { /// 續(xù)費(fèi)
provider.request(.checkReceipt(reciepString)) { response in
paraseReponse(response, success: success)
}
}
}
/// 校驗(yàn)恢復(fù)時(shí)使用
fileprivate func verifyRestoreSession(_ success: (() -> Void)? = nil) {
let provider = MoyaProvider<IAPNetWorkTarget>()
guard let reciepString = reciepString else {
return
}
provider.request(.checkReceipt(reciepString)) { response in
switch response {
case .success(let res):
guard let resModel = try? res.model(IAPResponse.self) else {
return
}
if let data = resModel.data {
self.restoreSuccess.call(data)
NotificationCenter.default.post(name: NSNotification.Name.iapRestoreSuccess, object: data)
success?()
} else {
let error = AppError(message: resModel.message ?? "", code: ErrorCode(rawValue: resModel.status) ?? .none)
self.restoreFailure.call(error)
NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
}
case .failure(let error):
self.restoreFailure.call(AppError(message: error.localizedDescription, code: ErrorCode(rawValue: -100) ?? .none))
NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
}
}
}
///獲取當(dāng)前憑證的最新信息:比如訂閱過期,Apple知道訂閱過期了,但是app端有可能不知道,所以需要主動(dòng)獲取一次
fileprivate func checkCurrentReciept() {
let provider = MoyaProvider<IAPNetWorkTarget>()
guard let reciepString = reciepString else {
return
}
provider.request(.checkReceipt(reciepString)) { response in
switch response {
case .success(let res):
guard let resModel = try? res.model(IAPResponse.self) else {
return
}
if let data = resModel.data {
NotificationCenter.default.post(name: NSNotification.Name.iapInfoUpdated, object: data)
}
case .failure:
break
}
}
}
如何防止憑證被偽造?(面試經(jīng)常問)
app端采用的是將憑證發(fā)送給我們自己的服務(wù)器,通過自己的服務(wù)器再向蘋果驗(yàn)證,蘋果返回的信息會(huì)決定這個(gè)憑證是否有效,如果無效則自己的服務(wù)器則返回錯(cuò)誤給客戶端。即把驗(yàn)證憑證這個(gè)操作,交由我們自己的服務(wù)器,我們和自己的服務(wù)器交互
自己服務(wù)器的數(shù)據(jù)安全由Https(對(duì)稱加密和非對(duì)稱加密)和一些自己約定的通信方式來保證(對(duì)稱加密)