iOS - IAP总结
2021-06-09 本文已影响0人
lieon
IAP的实现的流程

- app端通过产品Id发送创建
SKProductsRequest
请求获取产品 - 获取到产品后创建
SKPayment
发起支付请求 - 监听支付回调
- 支付成功获取支付凭证
- 通过支付凭证,向服务端发起请求,校验凭证的正确性,创建订单
- app端接收校验后的服务器响应,接收数据回调
IAP自动续期订阅代码实战
- 以设计一个
IAPService
模块举例说明
IAPService的对外接口
- 直接购买
-
注意:怎么才叫购买成功:Apple端支付成功,自己的服务端校验成功,订单生产成功,才算完成了购买,所以
SKPaymentQueue.default().finishTransaction(lastTansation)
要在这些流程都跑通之后,才去调用,如果不调这个finish方法,则可以从SKPaymentQueue.default().transactions
获取到这相关的交易,唤醒操作校验未完成的订单或者订阅续费成功之后,都可以在这个transactions
中获取到,但是有可能有延迟,所以要在每次app启动的时候,执行一个唤醒操作
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 { /// 购买成功过,直接去校验
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")
}
}
}
- 冷启动校验(唤醒操作)
- 如果有在苹果服务器购买成功,但是在自己的服务器都没有创建好订单,则冷启动重新校验未完成的订单
- 如果没有未完成的订单,则获取一下当前凭证的最新信息,保持信息的同步,比如订阅过期了,app端有可能没有接收到回调信息,这样就会导致APP端一直处于订阅状态,当然注册了交易状态的监听也可以知道,但是信息有可能会延迟,这样做,相当于一个保险操作
- 唤醒操作的目的:1.初始化
IAPService
单例,注册监听,2.校验续费或者已经在Apple购买成功,但是没有在自己服务端生成订单的交易
/// 冷启动校验未完成的订单
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)
}
}
- 恢复购买
- 发起回复购买
-
paymentQueueRestoreCompletedTransactionsFinished
中接收回调的结果 - 如果凭证有效,直接调取服务端校验接口,返回IAP数据信息
- 如果凭证无效, 发起刷新凭证请求, 监听请求回调结果
- 再次尝试判断凭证是否为空,若不为空,则进行凭证校验,返回IAP数据信息
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) {
/// 如果是恢复购买时刷新凭证的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
}
}
- 预先拉取商品
func prefetchProducts(_ productIds: Set<String>) {
guard SKPaymentQueue.canMakePayments() else { return }
preFetchRequest = SKProductsRequest(productIdentifiers: productIds)
preFetchRequest?.delegate = self
preFetchRequest?.start()
}
- 信息回调(通知的形式)
内部接口
- 在
IAPService
模块创建时添加支付状态的监听
private override init() {
super.init()
SKPaymentQueue.default().add(self)
}
- 处理产品请求,交易状态更新的回调
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) {
/// 如果是恢复购买时刷新凭证的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)
}
}
- 校验凭证,生成订单,获取当前凭证的最新信息
/// 校验购买时使用
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 { /// 续费
provider.request(.checkReceipt(reciepString)) { response in
paraseReponse(response, success: success)
}
}
}
/// 校验恢复时使用
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)
}
}
}
///获取当前凭证的最新信息:比如订阅过期,Apple知道订阅过期了,但是app端有可能不知道,所以需要主动获取一次
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
}
}
}
如何防止凭证被伪造?(面试经常问)
-
app端采用的是将凭证发送给我们自己的服务器,通过自己的服务器再向苹果验证,苹果返回的信息会决定这个凭证是否有效,如果无效则自己的服务器则返回错误给客户端。即把验证凭证这个操作,交由我们自己的服务器,我们和自己的服务器交互
-
自己服务器的数据安全由Https(对称加密和非对称加密)和一些自己约定的通信方式来保证(对称加密)