前言
咱们接着上次的内容来进行讨论和相互学习
前期的配置
appstoreContent中配置商品 --> appstoreContent中配置沙盒测试人员
这里的流程无需过多赘叙,基本都是照着文档一步一步来即可。参考<APP内购买项目>
内购流程
获取商品信息 --> 客户端发起内购请求 --> 将凭证发给服务器做二次验证 --> 服务器修改用户数据库商品信息并回调给客户端 --> 客户端刷新本地数据以及更新UI
获取商品信息
/**
@param productIdentifiers = 商品的productID
*/
NSSet * set = [NSSet setWithArray:productIdentifiers];
SKProductsRequest * request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
request.delegate = self;
[request start];
执行完该操作之后可以通过代理的回调接受结果
/**
@abstract 获取商品信息的成功回调
@discussion 在ios13之后,该回调会在异步线程中执行,需要特别注意!!!
*/
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSLog(@"有效的商品信息 %@",response.products);
NSLog(@"无效的商品ID %@",response.invalidProductIdentifiers);
SKProduct *product = response.products.firstObject;
NSNumberFormatter *clearFormatter = [[NSNumberFormatter alloc] init];
CGFloat price = [[clearFormatter stringFromNumber:product.price] floatValue];
NSLog(@"商品价格 %f",price);
NSLog(@"商品的货币国家 %@",product.priceLocale.countryCode);
}
/**
@abstract 获取商品信息的失败回调
@discussion 在ios13之后,该回调会在异步线程中执行,需要特别注意!!!
*/
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
NSLog(@"失败 %@",error);
}
客户端发起内购请求
/**
@abstract 发起内购请求
@param product:商品
*/
- (void)startPaymentWithProduct:(SKProduct *)product {
// 根据SKProduct来创建一个交易请求
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
//applicationUsername是一个透传的字段,当支付成功之后会原封不动的回给到我们。一般我们能够传递我们订单号过去。
payment.applicationUsername = @"myOrderID";
// 将交易请求放入交易队列
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
接受内购回调,并将凭证发给服务器做二次验证
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
SKPayment *payment = transaction.payment;
NSString *productIdentifier = payment.productIdentifier;
NSLog(@"transactions. ProductID:%@ --- TransactionIdentifier:%@", productIdentifier,transaction.transactionIdentifier);
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(@"购买中");
break;
case SKPaymentTransactionStatePurchased:
NSLog("购买请求回调成功,开始处理...");
//获取透传字段
NSString *orderID = transation.payment.applicationUsername;
//transactionIdentifier:相当于Apple的订单号
NSString *transationId = transation.transactionIdentifier;
NSLog(@"orderID = %@, 交易ID = %@", orderNo, transationId);
//从沙盒中获取交易凭证
NSData *reciptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
//转化成Base64字符串(用于校验)
NSString *reciptString = [reciptData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
//传给后台做二次验证
[self checkReceipt:reciptString];
break;
case SKPaymentTransactionStateFailed:
NSLog("购买请求回调失败,开始处理...");
break;
case SKPaymentTransactionStateRestored:
NSLog("恢复购买请求回调成功,开始处理...");
break;
case SKPaymentTransactionStateDeferred:
NSLog("恢复购买请求回调失败,开始处理...");
break;
case SKPaymentTransactionStateDeferred:
NSLog(@"交易推迟, 等待外部操作");
//交易推迟
//官方解释是:交易已经加入队列,但是需要等待外部操作
//主要用于儿童模式,需要询问家长同意。这种情况下不能关闭订单(完成交易),否则这类充值将无法处理。
break;
default:
break;
}
}
}
服务器二次验证
文档传送门 使用App Store验证收据
- 我们把recipt经过Base64编码之后,传给Apple的验证服务器进行验证(格式如下:)
{"receipt-data": 你编码过的recipt}
- 服务器验证的URL
sandbox.itunes.apple.com/verifyRecei… 是沙盒环境的验证地址。 buy.itunes.apple.com/verifyRecei… 是正式环境的验证地址
- Apple返回的完整数据如下
响应参数含义传送门 响应参数
{
"status": 0, // 状态码
"environment": "Sandbox" // 收据环境
"receipt": {
"receipt_type": "ProductionSandbox", // 收据类型。这里是沙盒的收据
"adam_id": 0,
"app_item_id": 0,
"bundle_id": "com.tes.buildID", // 应用buildID
"application_version": "1.5.0", // 应用版本号
"download_id": 0,
"version_external_identifier": 0,
"receipt_creation_date": "2018-06-28 14:08:26 Etc/GMT", // 本次收据创建时间
"receipt_creation_date_ms": "1530194906000",
"receipt_creation_date_pst": "2018-06-28 07:08:26 America/Los_Angeles",
"request_date": "2018-08-05 04:50:58 Etc/GMT", // 请求时间
"request_date_ms": "1533444658147",
"request_date_pst": "2018-08-04 21:50:58 America/Los_Angeles",
"original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
"original_purchase_date_ms": "1375340400000",
"original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
"original_application_version": "1.0",
"in_app": [
{
"quantity": "1",//购买商品的数量
"product_id": "*******",
"transaction_id": "1000000404314890", //这个苹果的交易唯一标识符,不会随着恢复购买和重现购买而改变
"original_transaction_id": "1000000404314890", //原始交易ID
"purchase_date": "2018-06-04 09:58:41 Etc/GMT", // 购买商品的时间,不会因为恢复购买或者重现购买而刷新时间
"purchase_date_ms": "1528106321000", //购买时间毫秒
"purchase_date_pst": "2018-06-04 02:58:41 America/Los_Angeles", //太平洋标准时间
"original_purchase_date": "2018-06-04 09:58:41 Etc/GMT", //原始购买时间
"original_purchase_date_ms": "1528106321000", //毫秒
"original_purchase_date_pst": "2018-06-04 02:58:41 America/Los_Angeles", //购买时间,太平洋标准时间
"is_trial_period": "false"
},
{
"quantity": "1",
"product_id": "*******",
"transaction_id": "1000000404523773",
"original_transaction_id": "1000000404523773",
"purchase_date": "2018-06-05 02:21:26 Etc/GMT",
"purchase_date_ms": "1528165286000",
"purchase_date_pst": "2018-06-04 19:21:26 America/Los_Angeles",
"original_purchase_date": "2018-06-05 02:21:26 Etc/GMT",
"original_purchase_date_ms": "1528165286000",
"original_purchase_date_pst": "2018-06-04 19:21:26 America/Los_Angeles",
"is_trial_period": "false"
}
]
}
}
- 收据返回的状态码-status
文档传送门 状态码
21000 App Store 不能读取你提供的JSON对象
21002 receipt-data 域的数据有问题
21003 receipt 无法通过验证
21004 提供的 shared secret 不匹配你账号中的 shared secret
21005 receipt 服务器当前不可用
21006 receipt 合法, 但是订阅已过期. 服务器接收到这个状态码时, receipt 数据仍然会解码并一起发送
21007 receipt 是 Sandbox receipt, 但却发送至生产系统的验证服务
21008 receipt 是生产 receipt, 但却发送至 Sandbox 环境的验证服务
项目中遇到的问题
- 发起内购前记得确认下内购权限
if ([SKPaymentQueue canMakePayments]) {
// 开始内购的操作
}else {
NSLog(@"没有内购权限");
}
-
交易完成后,需要在完成本次交易事务
这里需要注意的是,苹果有一个补单的措施。当你发起一起交易事务之后,当你每一次重现开启APP的时候,都会自动进行之前的交易请求。从而出发内购的回调:- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
所以我们要做的就是,当交易完成之后对改次交易进行finish操作。[[SKPaymentQueue defaultQueue] finishTransaction:transation] -
有时候我们可能在本地获取到内购凭证,那么就需要我们进行收到刷新
- (void)refreshPurchaseCertificate
{
FCLogInfo(@"开始刷新本地支付凭证");
SKReceiptRefreshRequest *request = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
request.delegate = self;
[request start];
}
- (void)requestDidFinish:(SKRequest *)request API_AVAILABLE(ios(3.0), macos(10.7))
{
NCLog(@"刷新成功本地支付凭证");
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
NSString *base64String = [receipt base64EncodedStringWithOptions:0];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error API_AVAILABLE(ios(3.0), macos(10.7))
{
NCLog(@"刷新失败本地支付凭证");
}
- 在购买商品的页面,一定要有恢复购买的代码,不然会被拒审
- (void)restoreCompletedTransactions
{
// 购买成功后,也会进行内购的回调
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
-
如何避免刷单
当服务器在进行凭证的二次校验时,服务器可以在数据库中查询transaction_id是否未曾入库,或者该id的拥有者账号为此次发起请求的账号,那么我们就认为这是一次成功的内购。 -
app至少过审一次
我们app第一次上架的时候就有内购的功能,但是在TestFlight中死活买不了上面。然后经过咨询有内购经验的同事,最后得出的结论是:app一定要过一次苹果审核,商品才能够过审核,才能进行内购。我们直接提审后,发现真的就可以了。所以大家在做内购的时候要有这个准备。
最后向大家推荐一个内购的库,大家可以参考学习,有需要的也可以直接使用该库。RMSotre
END
-------------------------------------------------------想要保护的人多了,所以想要变得更强