一、什么是iOS内购
通过app store来购买应用程序或应用程序内的虚拟商品
iOS内购只用于销售虚拟物品,如你的App里的高级内容,以及订阅数字内容
既然提到购买,那必然有“商品”,商品也有一些描述信息(meta data),比如名称,价格,优惠信息等。和微信支付宝等方式不同,内购的商品需要到iTunes Connect后台建立,并且通过App Store的审核后才能售卖。
内购商品可以分为四种类型:
- 消耗型。可以购买多次,多次结果累加,并且在App内作为“货币”消耗。典型的是虚拟货币
- 非消耗型。只能购买一次,可跨设备使用,业务场景较少。典型的是图书App中的一本电子书,或者游戏中的一个关卡。
- 自动续期订阅。和时间相关的服务,在有效期内用户可享受服务,要到期的时候自动扣费。典型的是连续包月的会员。
- 非续期订阅。和时间相关的服务,但是不会自动扣费。典型的是一个月会员
二、ios内购流程
支付宝、微信的支付流程
第一步:我们的 APP 发起一笔支付交易,此时,第一件事,我们要去我们自己的服务器上创建一个订单信息。同时服务器会组装好一笔交易交给我们。关于组装交易信息,有两种做法,第一种就是支付宝推荐我们做的,由我们服务器来组装交易信息,服务器加密交易信息,并保存签名信息;另一种做法是,服务器返回商品信息给 APP,由 APP 来组装交易信息,并进行加密处理等操作。显然我们应该采用第一种方式。
第二步:服务器创建好交易信息以后,返回给 APP,APP 不对交易信息做处理。
第三步:APP 拿到交易信息,开始调起支付宝的 SDK,支付宝的 SDK 把交易信息传给支付宝的服务器。
第四步:验证通过以后,支付宝服务器会告诉支付宝 SDK 验证通过。
第五步:验证通过以后,我们的 APP 会调起支付宝 APP,跳转到支付宝 APP。
第六步:在支付宝 APP 里,用户输入密码进行交易,和支付宝服务器进行通讯。
第七步:支付成功,支付宝服务器回调支付宝 APP。
第八步:支付宝回到我们自己的 APP,对应的进行刷新 UI 等操作。
第九步:支付宝服务器会回调我们的服务器并把收据传给我们服务器,如果我们的服务器没有确认已经收到支付宝的收据信息,那么支付宝服务器就会一直回调我们的服务器,只是回调时间间隔会越来越久。
第十步:我们的服务器收到支付宝的回调,并回调支付宝,确认已经收到收据信息,此时就完成了交易。
ios内购的大概流程:用户在App内点击购买,app通过StoreKit向app store发起购买请求,接着App Store扣款产生一个receipt(票据)给App,App把收据发送给服务端,服务端验证收据后向用户交付对应的虚拟产品
第一步:用户开始购买,首先会去我们自己的服务器创建一个交易订单,返回给 APP。
第二步:APP 拿到交易信息,然后开始调起 app store 服务创建订单,并把订单推入支付队列。
第三步:store kit 会和 apple store 服务器通讯,让用户确认购买,输入密码。
第四步:apple store服务器回调 APP,通知购买成功,并把收据写入到 APP 沙盒中。
第五步:此时,APP 应该去获取沙盒中的收据信息(一段 Base 64 编码的数据),并将收据信息上传给服务器。
第六步:服务器拿到收据以后,就应该去 apple 服务器查询这个收据对应的已付款的订单号。
第七步:我们自己的服务器拿到这个收据对应的已付款的订单号以后,就去校验当前的已付款订单中是否有要查询的那一笔,如果有,就告诉 APP。
第八步:APP 拿到查询结果,然后把这笔交易给 finish 掉。
Store Kit代表App和App Store之间进行通信
内购的枢纽是App,这也是让无数开发者头疼的地方。实践证明,这种架构设计容易发生丢单(花钱不到账)或者无法购买
由于移动端所处的网络环境远远比服务端要复杂,所以,最大可能出现问题的是与移动端的通讯上。对于支付宝,只要移动端确实付款完成,那么接下来的验证工作都是服务器于服务器之间的通讯。这样一来,只要用户确实产生了一笔交易,那么接下来的验证就变得可靠的多,而且支付宝服务器会一直回调我们的服务器,交易的可靠性得到了极大的保证。
获取商品列表
app可以向服务端获取商品列表,也可以从apple store获取所有商品列表,但是鉴于国内访问apple store很慢。现在都是直接从服务端获取商品列表。
// 于LLPayModule的 '- (void)didLogin' 协议方法内获取商品列表。
[[LLPaymentManager sharedInstance] prepareAppleProductList];
// 先请求服务器获取productIds,然后调用RMStore方法请求商品列表方法
-(void)requestProducts:(NSSet*)identifiers success:(void (^)(NSArray *products, NSArray *invalidProductIdentifiers))successBlock failure:(void (^)(NSError *error))failureBlock;
// RMStore方法内部调用StoreKit的api,代理回调返回结果后保存,如未获取,创建订单时会有重新请求机制
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers];
productsRequest.delegate = delegate;
[productsRequest start];
下图展示为消耗型订阅-钻石充值:
获取商品详情
当选定要购买哪个商品后,app可以向apple store查询此商品的详细信息用来验证商品是否有效。
在向apple server 获取商品详情,apple server会返回:商品IDproductID,商品价格price,商品优惠信息discount等
SKProduct *productInfo;
for (SKProduct *product in productArr) {
NSLog(@"商品描述 %@", [product description]);
NSLog(@"商品标题 %@", [product localizedTitle]);
NSLog(@"商品本地化描述 %@", [product localizedDescription]);
NSLog(@"商品价格 %@", [product price]);
NSLog(@"商品ID %@", [product productIdentifier]);
if ([product.productIdentifier isEqualToString:self.productID]) {
productInfo = product;
}
}
创建订单
通常的支付系统的设计是:生成订单 -> 支付成功 → 完成订单。
而由于iOS内购服务端在创建订单时无法与iOS交互,导致创建订单都是在app上进行的,然后app在iOS下单时又无法把创建的订单与支付订单完全绑定。
因此iOS内购购买流程又可以是:支付成功 -> 生成订单 ->完成订单,服务端只有购买已经发生了,才会参与到流程里。
插入到支付队列
把获取到的商品信息对象插入支付队列,store kit就会执行接下来支付流程
如果没有商品信息,只有商品ID,也可以插入到支付队列。
SKPayment *payment = [SKPayment paymentWithProduct:productInfo];
[[SKPaymentQueue defaultQueue] addPayment:payment];
# iOS5.``0``之前的方法,目前不推荐使用
SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier];
[[SKPaymentQueue defaultQueue] addPayment:payment];
支付结果通知
和微信支付宝支付完通知方式相同,ios也会以异步的方式通知,但是ios是直接给app通知支付结果,对于前期创建的订单信息无法保证能对应上
9.0.10之前版本,在StoreKit回调内,支付成功和失败都将直接调用 finishTransaction结束订单,易发生掉单问题;
9.0.20版本,StoreKit回调内,
StoreKit返回成功时:
票据验证成功,执行finishTransaction方法结束订单;
票据验证失败时,则不执行finishTransaction方法,直到Order缓存到期,才会调用finishTransaction结束订单;
StoreKit返回失败时:
主动取消则调用finishTransaction结束订单;
当发生其它错误时,并未立即调用finishTransaction方法,而会在订单缓存失效时,再进行调用finishTransaction结束订单;
当一次支付交易后,未调用finishTransaction方法时,StoreKit内部队列会在App启动后,再次执行内购回调方法;
内购订单回调
//监听购买结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
NSLog(@"购买成功");
[self didPurchaseTransaction:transaction queue:queue];
break;
case SKPaymentTransactionStatePurchasing:
NSLog(@"商品正在购买中。。。");
break;
case SKPaymentTransactionStateRestored:
NSLog(@"已经购买过商品");
[self didRestoreTransaction:transaction queue:queue];
break;
case SKPaymentTransactionStateFailed:
NSLog(@"购买失败");
[self didFailTransaction:transaction queue:queue error:transaction.error];
break;
case SKPaymentTransactionStateDeferred:
NSLog(@"儿童模式,需要询问家长同意");
[self didDeferTransaction:transaction];
break;
default:
break;
}
}
}
// 完成购买
- (void)finishTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue
{
SKPayment *payment = transaction.payment;
NSString* productIdentifier = payment.productIdentifier;
RMAddPaymentParameters *wrapper = [self popAddPaymentParametersForIdentifier:productIdentifier];
if (wrapper.successBlock != nil)
{
wrapper.successBlock(transaction);
}
[self postNotificationWithName:RMSKPaymentTransactionFinished transaction:transaction userInfoExtras:nil];
if (transaction.transactionState == SKPaymentTransactionStateRestored)
{
[self notifyRestoreTransactionFinishedIfApplicableAfterTransaction:transaction];
}
}
支付完通知信息 SKPaymentTransaction *transaction:
SKPaymentTransaction
NSError *error; // 仅当SKPaymentTransactionFailed时有值
SKPaymentTransaction *originalTransaction;// 自动续期订阅原始订单,恢复购买时用到
SKPayment *payment; // 内购请求,内部包含productIdentifier、requestData等信息
NSString *transactionIdentifier; // 交易id,唯一标识
SKPaymentTransactionState transactionState;
NSDate *transactionDate;
支付票据上报服务端验证
app向服务端上报支付成功的票据,同时也会携带用户信息及订单信息(有可能没有用户信息与订单信息)
服务端验证流程:
- 服务端会将票据发给苹果服务器去验证票据的有效性,苹果在返回的票据信息是包含了所有未被完成的交易信息及所有订阅类型的历史票据信息
- 获取交易数组里有效的交易票据
- 通过交易ID(transactionID或webOrderLineItemID)检查是否处理过此交易
- 未处理过的交易则更新订单状态,保存交易记录
- 返回给客户端校验结果
app端对于每次的票据验证结果只需要三种:有效的票据、无效的票据、需要重试的票据
完成交易
票据在服务端验证完成后,会通知app本次支付的有效性,验证成功后,app就会将此次交易调用finishTransaction方法通知ios,这才算完成交易。
同一个商品,如果上次支付用户支付成功SKPaymentTransactionStatePurchased,但是没有调用finishTransaction,再次下单购买的时候,会提示恢复购买,用户不会扣钱,如果重新打开App,会收到多条回调,并且对应的transactionId一样,也就是同一个商品,再未完成前,不会重复扣款,只有上一个订单完成后,才会继续支付扣款。
为了防止当用户购买成功,但未获取票据情况时,无法继续购买同一产品情况,采用在购买内购商品时,采用遍历SKPaymentQueue队列,调用finishTransaction结束异常订单;
开始购买时,异常的Transaction处理
for (SKPaymentTransaction *transaction in [SKPaymentQueue defaultQueue].transactions)
{
if ([transaction.payment.productIdentifier isEqualToString:pid] &&
(transaction.transactionState == SKPaymentTransactionStatePurchased ||
transaction.transactionState == SKPaymentTransactionStateRestored))
{
[[RMStore defaultStore] finishTransaction:transaction];
}
}
票据的结构
{
"status":0,
"environment":"Sandbox",
"receipt":{
"receipt_type":"ProductionSandbox",
"adam_id":0,
"app_item_id":0,
"bundle_id":"com.iksocial.queen",
"application_version":"0.2005181800",
"download_id":0,
"version_external_identifier":0,
"receipt_creation_date":"2020-05-19 09:06:19 Etc/GMT",
"receipt_creation_date_ms":"1589879179000",
"receipt_creation_date_pst":"2020-05-19 02:06:19 America/Los_Angeles",
"request_date":"2020-05-19 09:06:24 Etc/GMT",
"request_date_ms":"1589879184300",
"request_date_pst":"2020-05-19 02:06:24 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":"queen.gold.42c.6yuan",
"transaction_id":"1000000666751111",
"original_transaction_id":"1000000666751111",
"purchase_date":"2020-05-19 09:06:19 Etc/GMT",
"purchase_date_ms":"1589879179000",
"purchase_date_pst":"2020-05-19 02:06:19 America/Los_Angeles",
"original_purchase_date":"2020-05-19 09:06:19 Etc/GMT",
"original_purchase_date_ms":"1589879179000",
"original_purchase_date_pst":"2020-05-19 02:06:19 America/Los_Angeles",
"is_trial_period":"false"
},
{
"quantity":"1",
"product_id":"queen.plan1.super.1m.25yuan",
"transaction_id":"1000000666268121",
"original_transaction_id":"1000000666265459",
"purchase_date":"2020-05-18 10:42:17 Etc/GMT",
"purchase_date_ms":"1589798537000",
"purchase_date_pst":"2020-05-18 03:42:17 America/Los_Angeles",
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 10:47:17 Etc/GMT",
"expires_date_ms":"1589798837000",
"expires_date_pst":"2020-05-18 03:47:17 America/Los_Angeles",
"web_order_line_item_id":"1000000052560747",
"is_trial_period":"false",
"is_in_intro_offer_period":"false"
},
{
"quantity":"1",
"product_id":"queen.plan1.super.1m.25yuan",
"transaction_id":"1000000666271337",
"original_transaction_id":"1000000666265459",
"purchase_date":"2020-05-18 10:48:56 Etc/GMT",
"purchase_date_ms":"1589798936000",
"purchase_date_pst":"2020-05-18 03:48:56 America/Los_Angeles",
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 10:53:56 Etc/GMT",
"expires_date_ms":"1589799236000",
"expires_date_pst":"2020-05-18 03:53:56 America/Los_Angeles",
"web_order_line_item_id":"1000000052560865",
"is_trial_period":"false",
"is_in_intro_offer_period":"false"
},
{
"quantity":"1",
"product_id":"queen.plan1.super.1m.25yuan",
"transaction_id":"1000000666273486",
"original_transaction_id":"1000000666265459",
"purchase_date":"2020-05-18 10:53:56 Etc/GMT",
"purchase_date_ms":"1589799236000",
"purchase_date_pst":"2020-05-18 03:53:56 America/Los_Angeles",
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 10:58:56 Etc/GMT",
"expires_date_ms":"1589799536000",
"expires_date_pst":"2020-05-18 03:58:56 America/Los_Angeles",
"web_order_line_item_id":"1000000052561122",
"is_trial_period":"false",
"is_in_intro_offer_period":"false"
},
{
"quantity":"1",
"product_id":"queen.plan1.super.1m.25yuan",
"transaction_id":"1000000666276646",
"original_transaction_id":"1000000666265459",
"purchase_date":"2020-05-18 10:58:56 Etc/GMT",
"purchase_date_ms":"1589799536000",
"purchase_date_pst":"2020-05-18 03:58:56 America/Los_Angeles",
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 11:03:56 Etc/GMT",
"expires_date_ms":"1589799836000",
"expires_date_pst":"2020-05-18 04:03:56 America/Los_Angeles",
"web_order_line_item_id":"1000000052561261",
"is_trial_period":"false",
"is_in_intro_offer_period":"false"
},
{
"quantity":"1",
"product_id":"queen.plan1.super.1m.25yuan",
"transaction_id":"1000000666280122",
"original_transaction_id":"1000000666265459",
"purchase_date":"2020-05-18 11:03:56 Etc/GMT",
"purchase_date_ms":"1589799836000",
"purchase_date_pst":"2020-05-18 04:03:56 America/Los_Angeles",
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 11:08:56 Etc/GMT",
"expires_date_ms":"1589800136000",
"expires_date_pst":"2020-05-18 04:08:56 America/Los_Angeles",
"web_order_line_item_id":"1000000052561435",
"is_trial_period":"false",
"is_in_intro_offer_period":"false"
},
{
"quantity":"1",
"product_id":"queen.plan1.super.1m.25yuan",
"transaction_id":"1000000666265459",
"original_transaction_id":"1000000666265459",
"purchase_date":"2020-05-18 10:37:17 Etc/GMT",
"purchase_date_ms":"1589798237000",
"purchase_date_pst":"2020-05-18 03:37:17 America/Los_Angeles",
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 10:42:17 Etc/GMT",
"expires_date_ms":"1589798537000",
"expires_date_pst":"2020-05-18 03:42:17 America/Los_Angeles",
"web_order_line_item_id":"1000000052560746",
"is_trial_period":"false",
"is_in_intro_offer_period":"true"
}
]
},
"latest_receipt_info":[
{
"quantity":"1",// 交易数量
"product_id":"queen.plan1.super.1m.25yuan",//商品ID
"transaction_id":"1000000666265459",// 交易ID
"original_transaction_id":"1000000666265459",// 原始交易ID
"purchase_date":"2020-05-18 10:37:17 Etc/GMT",// 交易时间,标准时区
"purchase_date_ms":"1589798237000",// unix时间戳
"purchase_date_pst":"2020-05-18 03:37:17 America/Los_Angeles",// 美国时间
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",// 原始交易时间,恢复票据时用
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 10:42:17 Etc/GMT",// 过期时间
"expires_date_ms":"1589798537000",
"expires_date_pst":"2020-05-18 03:42:17 America/Los_Angeles",
"web_order_line_item_id":"1000000052560746",// 跨设备交易唯一ID
"is_trial_period":"false",// 免费试用期
"is_in_intro_offer_period":"true",// 价格打折期
"subscription_group_identifier":"20563228"
},
{
"quantity":"1",
"product_id":"queen.plan1.super.1m.25yuan",
"transaction_id":"1000000666268121",
"original_transaction_id":"1000000666265459",
"purchase_date":"2020-05-18 10:42:17 Etc/GMT",
"purchase_date_ms":"1589798537000",
"purchase_date_pst":"2020-05-18 03:42:17 America/Los_Angeles",
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 10:47:17 Etc/GMT",
"expires_date_ms":"1589798837000",
"expires_date_pst":"2020-05-18 03:47:17 America/Los_Angeles",
"web_order_line_item_id":"1000000052560747",
"is_trial_period":"false",
"is_in_intro_offer_period":"false",
"subscription_group_identifier":"20563228"
},
{
"quantity":"1",
"product_id":"queen.plan1.super.1m.25yuan",
"transaction_id":"1000000666271337",
"original_transaction_id":"1000000666265459",
"purchase_date":"2020-05-18 10:48:56 Etc/GMT",
"purchase_date_ms":"1589798936000",
"purchase_date_pst":"2020-05-18 03:48:56 America/Los_Angeles",
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 10:53:56 Etc/GMT",
"expires_date_ms":"1589799236000",
"expires_date_pst":"2020-05-18 03:53:56 America/Los_Angeles",
"web_order_line_item_id":"1000000052560865",
"is_trial_period":"false",
"is_in_intro_offer_period":"false",
"subscription_group_identifier":"20563228"
},
{
"quantity":"1",
"product_id":"queen.plan1.super.1m.25yuan",
"transaction_id":"1000000666273486",
"original_transaction_id":"1000000666265459",
"purchase_date":"2020-05-18 10:53:56 Etc/GMT",
"purchase_date_ms":"1589799236000",
"purchase_date_pst":"2020-05-18 03:53:56 America/Los_Angeles",
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 10:58:56 Etc/GMT",
"expires_date_ms":"1589799536000",
"expires_date_pst":"2020-05-18 03:58:56 America/Los_Angeles",
"web_order_line_item_id":"1000000052561122",
"is_trial_period":"false",
"is_in_intro_offer_period":"false",
"subscription_group_identifier":"20563228"
},
{
"quantity":"1",
"product_id":"queen.plan1.super.1m.25yuan",
"transaction_id":"1000000666276646",
"original_transaction_id":"1000000666265459",
"purchase_date":"2020-05-18 10:58:56 Etc/GMT",
"purchase_date_ms":"1589799536000",
"purchase_date_pst":"2020-05-18 03:58:56 America/Los_Angeles",
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 11:03:56 Etc/GMT",
"expires_date_ms":"1589799836000",
"expires_date_pst":"2020-05-18 04:03:56 America/Los_Angeles",
"web_order_line_item_id":"1000000052561261",
"is_trial_period":"false",
"is_in_intro_offer_period":"false",
"subscription_group_identifier":"20563228"
},
{
"quantity":"1",
"product_id":"queen.plan1.super.1m.25yuan",
"transaction_id":"1000000666280122",
"original_transaction_id":"1000000666265459",
"purchase_date":"2020-05-18 11:03:56 Etc/GMT",
"purchase_date_ms":"1589799836000",
"purchase_date_pst":"2020-05-18 04:03:56 America/Los_Angeles",
"original_purchase_date":"2020-05-18 10:37:19 Etc/GMT",
"original_purchase_date_ms":"1589798239000",
"original_purchase_date_pst":"2020-05-18 03:37:19 America/Los_Angeles",
"expires_date":"2020-05-18 11:08:56 Etc/GMT",
"expires_date_ms":"1589800136000",
"expires_date_pst":"2020-05-18 04:08:56 America/Los_Angeles",
"web_order_line_item_id":"1000000052561435",
"is_trial_period":"false",
"is_in_intro_offer_period":"false",
"subscription_group_identifier":"20563228"
}
],
"latest_receipt":"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",
"pending_renewal_info":[
{
"expiration_intent":"1",
"auto_renew_product_id":"queen.plan1.super.1m.25yuan",
"original_transaction_id":"1000000666265459",
"is_in_billing_retry_period":"0",
"product_id":"queen.plan1.super.1m.25yuan",
"auto_renew_status":"0"
}
]
}
非订阅型的票据:取recipt.InApp下的所有有效票据并通过product_id在本地的商品信息过滤出非订阅类型的票据。
订阅型的票据:取pending_renewal_info下所有的票据,结合客户端上传的original_transaction_id找到要处理的票据。再通过这个票据的product_id和original_tansaction_id在latest_receipt_info找到最新的票据(最新:取支付时间最新的)。
自动续期订阅
订阅是一种包月或包年的商品,到期后会自动扣款续期,能增加用户粘性,同时提高收入的业务模式
一个订阅商品要处在一个群组里,一个群组里,用户一次只能订阅一个商品,以腾讯视频为例:
可以为订阅配置等级,类似会员的订阅等级往往都是相同的,因为他们提供的服务是一样的。相同等级切换的时候,会在当前订阅周期结束的时候生效。比如,1月10日订阅的连续包月,1月15日从包月切换到了包年,那么在2月10日的时候会扣掉一年的钱。
那么什么时候订阅等级不同呢?当提供的服务内容不同时,可用不同的订阅等级。比如:黄金会员,铂金会员,钻石会员。不同等级之间的订阅切换有所不同:
- 升级。用户购买服务级别高于当前订阅的订阅。他们的订阅服务会立即升级,并会获得原始订阅的按比例退款。
- 降级。用户选择服务级别低于当前订阅的订阅。订阅会继续保持不变,直到下一个续订日期,然后以较低级别和价格续订。
- 跨级。用户切换到相同级别的新订阅。如果两个订阅的持续时间相同,新订阅会立即生效。如果持续时间不同,新订阅会在下一个续订日期生效。
每个订阅群组的新顾客和重新订阅的顾客均可享受一次折扣价或免费试用。
折扣优惠:是为那些已经订阅过的用户提供优惠,并且为哪些用户提供优惠是开发者自己可控的,这样开发者就可以自定策略来提高留存,或者赢回已经取消订阅的用户
免费试用:具体免费商品是在iTunes Connect后台配置,而哪些用户可以享受是app store根据appleid决定的(一个appleid只能享受一次)
服务端验证收据后,以下两个字断有一个为true的时候,表示用户正在享受对应的优惠:
is_in_intro_offer_period 是否在享受折扣价
is_trial_period 是否是免费试用
首次购买
购买流程上,自动续费订阅与普通购买没有区别
主要的区别在于:除了第一次购买行为是用户主动触发的。后续续费都是Apple自动完成的,一般在要过期的前24小时开始,苹果会尝试扣费,扣费成功的话 会在APP下次启动的时候将支付票据主动推送给APP
续订
支付宝和微信的续订是通过服务端定期拿签约ID去检查订阅状态,到扣费时间再主动向支付宝或微信发起扣费,而ios的续订全是apple自动完成的。服务端对于扣费没有任何主动权,只有事后验证的方法。
-
客户端主动上报
apple每期自动扣款后, 会生成一笔新的receipt, 客户端获取后发送给server校验, 成功后开通下一期会员权益
-
状态变更通知(正常续订不会通知)
用于自动续订订阅的服务器到服务器通知服务, 可以在苹果后台配置通知地址, 状态变更时, server会收到通知
-
server轮询
自动续订类型的收据, 每一期的latest_receipt_info中都会记录所有的交易(包含历史和新增), 可以轮询上一期(任意一期都可以)receipt, 通过latest_receipt_info 解析出用户最新的订阅状态
方案 | 优势 | 缺点 |
---|---|---|
客户端主动上报 | 首次购买收据只能通过这种方式获取 | 1.续费收据需要用户打开app才会上传, 时效性不够好 2.无法获取关闭订阅的行为 |
状态变更通知 | 可以获取到用户取消订阅的消息(退款) | 1.不够可靠, 可能会丢失通知(看大家评论得出, 并未亲自尝试) 2.无法获取关闭订阅的行为 |
server轮询 | 只要发起轮询, 就可以随时获取用户的订阅状态(续费, 退款, 关闭) | 1.无法获取首次购买收据 2.成本较高, 需要对历史收据进行轮询 |
苹果服务器会在订阅过期的前一天,对用户进行自动扣费,如果扣费成功了,苹果服务器并不会通知我们的服务器,这是重点。不过有个特例,如果苹果订阅过期前一天扣费失败了,苹果服务器后面几天还会尝试对用户自动扣费,如果后面扣费成功了,苹果会通知我们的服务端的,其中notification_type 对应值为 RENEWAL,对于RENEWAL我们还是需要给用户更新为正在订阅的状态。
所以,对于自动续订订阅,我们自己的服务器完全可以与apple store的交互应对用户的订阅状态
三、iOS内购会出现的坑
1、谁支付的钱
iOS支付只认appleID,即用户在进行购买时,与apple store交互间是通过appleid做用户身份识别的,并没有app的账号信息,这样在用户支付完成到apple store向app通知支付票据的时间段内用户有切换账号的操作,这会导致实际支付的账号与真正获得商品的账号是不一致的
解决方案:(客户端)
- 尝试从applicationName中读取uid,如果uid为nil,则继续下一步(applicationName文档上已经说明了,不可靠,会丢失)
- 尝试从内存中根据productId来恢复uid,如果恢复失败,则继续下一步(卸载重装导致内存中的数据丢失)
- 尝试从keyChain中恢复uid,检查transactionDate和keyChain里记录的购买开始时间戳在允许范围内,如果恢复失败,则继续下一步(Keychain 是苹果公司 Mac OS(也包含 Mac OSX) 中的密码管理系统)
- 认为当前用户的uid是发生购买的uid,如果当前用户已退出登录,那么下一个登陆的uid认为是购买的uid
2、漏单,掉单
由于支付完的通知是直接通知到app的,而移动端所处的网络环境比较弱,或者用户购买完之后很久不打开app,无法向服务端上报票据,这样都会造成票据的丢失。
解决方案:(客户端)
- finishTransaction不要在StoreKit回调后立即执行,需要延后其执行时机。
- finishTransaction延后执行时,需处理同一商品,因上一次购买未finishTransaction,而不能继续购买的情况。
- 当获取票据后或者用户主动取消时,进行finishTransaction,其它情况则延后处理,超时未获取到票据或者订单StoreKit二次回调失败时,进行finishTransaction操作。
- 尚未进行finishTransaction操作的交易,apple store会在每次app启动时都会通知,直到进行finishTransaction操作。
(服务端):
- 对于自动续期的交易,服务端可以拿首次订阅的票据去向apple服务端验证,如果有新的续期交易就会体现在票据里
- 在向apple服务器验证票据时,apple服务器返回的票据信息中会携带本交易相关appleid以前尚未进行finish操作的交易(对于这种方法,无法确定交易原本的用户信息)
3、本地订单与ios交易订单无法对应
对于微信支付宝的交易信息,在于微信支付宝交互时都会带有本地订单信息,而在与iOS进行支付交互时,无法带本地订单的信息(有个变量applicationName,但是iOS明确说了不稳定,会丢失)
客户端在用户付款前向服务端请求创建的订单,由于客户端的不稳定(断网,用户删除app等)导致创建的订单与后期app store通知过来的交易票据无法对应,甚至找不到订单记录。这样就无法用正常的订单去查交易与做幂等
解决方案(客户端)
- 交易信息的持久化保存,同上面解决支付账号的方法一样
(服务端):
- 不用本地的订单做交易幂等判断,使用ios票据里的交易ID(或商品ID+支付时间+uid)作为每次交易的唯一判断。
- 当某次交易客户端无法找到创建的本地订单,服务端自己重新创建一个(只做交易查询)
4、退款
2020年6月24日前,开发者完全不知道用户退款了,iOS对于用户申请的退款完全没有通知,只有订阅类型的交易会在票据里有个退款时间(需要服务端经常主动拿票据去ios服务端验证检查),消耗类型的交易根本没通知,没记录,只有每个月的账单里能看到退款的一个总数量。
2020年6月24日起,ios服务端到服务端的通知增加了所有类型的退款通知
四、苹果服务器主动通知
2019年11月29日苹果在开发者网站发文表示,App Store的服务端对服务端通知为开发人员推送订阅状态的实时更新,方便开发人员为订阅者提供个性化体验
2020年6月24日起,ios服务端到服务端的通知增加了所有类型的退款通知
NOTIFICATION_TYPE | 描述。 |
---|---|
CANCEL | Apple客户支持取消了订阅。检查Cancellation Date以了解订阅取消的日期和时间。 |
INITIAL_BUY | 初次购买订阅。latest_receipt通过在App Store中验证,可以随时将您的服务器存储在服务器上以验证用户的订阅状态。 |
INTERACTIVE_RENEWAL | 客户通过使用应用程序界面或在App Store中的App Store中以交互方式续订订阅。服务立即可用。 |
RENEWAL | 已过期订阅的自动续订成功。检查Subscription Expiration Date以确定下一个续订日期和时间。 |
{
"latest_receipt":"",
"auto_renew_product_id":"Hitup.LikeMe.Plan4.1M.58Yuan",
"auto_renew_status_change_date_pst":"2020-07-07 09:01:50 America/Los_Angeles",
"unified_receipt":{
"latest_receipt":"",
"pending_renewal_info":[
{
"original_transaction_id":"70000766673140",
"product_id":"Hitup.LikeMe.Plan4.1M.58Yuan",
"auto_renew_status":"1",
"auto_renew_product_id":"Hitup.LikeMe.Plan4.1M.58Yuan"
}
],
"environment":"Production",
"status":0,
"latest_receipt_info":[
{
"expires_date_pst":"2020-08-07 09:01:47 America/Los_Angeles",
"purchase_date":"2020-07-07 16:01:47 Etc/GMT",
"purchase_date_ms":"1594137707000",
"original_purchase_date_ms":"1586882244000",
"transaction_id":"70000814509468",
"original_transaction_id":"70000766673140",
"quantity":"1",
"expires_date_ms":"1596816107000",
"original_purchase_date_pst":"2020-04-14 09:37:24 America/Los_Angeles",
"product_id":"Hitup.LikeMe.Plan4.1M.58Yuan",
"subscription_group_identifier":"20608819",
"web_order_line_item_id":"70000289454104",
"expires_date":"2020-08-07 16:01:47 Etc/GMT",
"is_in_intro_offer_period":"false",
"original_purchase_date":"2020-04-14 16:37:24 Etc/GMT",
"purchase_date_pst":"2020-07-07 09:01:47 America/Los_Angeles",
"is_trial_period":"false"
},
{
"expires_date_pst":"2020-06-14 09:37:21 America/Los_Angeles",
"purchase_date":"2020-05-14 16:37:21 Etc/GMT",
"purchase_date_ms":"1589474241000",
"original_purchase_date_ms":"1586882244000",
"transaction_id":"70000783553257",
"original_transaction_id":"70000766673140",
"quantity":"1",
"expires_date_ms":"1592152641000",
"original_purchase_date_pst":"2020-04-14 09:37:24 America/Los_Angeles",
"product_id":"Hitup.LikeMe.Plan4.1M.58Yuan",
"subscription_group_identifier":"20608819",
"web_order_line_item_id":"70000279828194",
"expires_date":"2020-06-14 16:37:21 Etc/GMT",
"is_in_intro_offer_period":"false",
"original_purchase_date":"2020-04-14 16:37:24 Etc/GMT",
"purchase_date_pst":"2020-05-14 09:37:21 America/Los_Angeles",
"is_trial_period":"false"
},
{
"expires_date_pst":"2020-05-14 09:37:21 America/Los_Angeles",
"purchase_date":"2020-04-14 16:37:21 Etc/GMT",
"purchase_date_ms":"1586882241000",
"original_purchase_date_ms":"1586882244000",
"transaction_id":"70000766673140",
"original_transaction_id":"70000766673140",
"quantity":"1",
"expires_date_ms":"1589474241000",
"original_purchase_date_pst":"2020-04-14 09:37:24 America/Los_Angeles",
"product_id":"Hitup.LikeMe.Plan4.1M.58Yuan",
"subscription_group_identifier":"20608819",
"web_order_line_item_id":"70000279828193",
"expires_date":"2020-05-14 16:37:21 Etc/GMT",
"is_in_intro_offer_period":"true",
"original_purchase_date":"2020-04-14 16:37:24 Etc/GMT",
"purchase_date_pst":"2020-04-14 09:37:21 America/Los_Angeles",
"is_trial_period":"false"
}
]
},
"auto_renew_status_change_date_ms":"1594137710000",
"latest_receipt_info":{
"original_purchase_date_pst":"2020-04-14 09:37:24 America/Los_Angeles",
"quantity":"1",
"subscription_group_identifier":"20608819",
"unique_vendor_identifier":"BE323F6D-92DF-47D6-AB2F-896B7093F19E",
"original_purchase_date_ms":"1586882244000",
"expires_date_formatted":"2020-08-07 16:01:47 Etc/GMT",
"is_in_intro_offer_period":"false",
"purchase_date_ms":"1594137707000",
"expires_date_formatted_pst":"2020-08-07 09:01:47 America/Los_Angeles",
"is_trial_period":"false",
"item_id":"1502984425",
"unique_identifier":"a294c4f793d29795147262680daae267dda6777a",
"original_transaction_id":"70000766673140",
"expires_date":"1596816107000",
"app_item_id":"1094615747",
"transaction_id":"70000814509468",
"bvrs":"6",
"web_order_line_item_id":"70000289454104",
"version_external_identifier":"836559398",
"bid":"com.blueberry.Gmu",
"product_id":"Hitup.LikeMe.Plan4.1M.58Yuan",
"purchase_date":"2020-07-07 16:01:47 Etc/GMT",
"purchase_date_pst":"2020-07-07 09:01:47 America/Los_Angeles",
"original_purchase_date":"2020-04-14 16:37:24 Etc/GMT"
},
"notification_type":"DID_CHANGE_RENEWAL_STATUS",
"auto_renew_status_change_date":"2020-07-07 16:01:50 Etc/GMT",
"environment":"PROD",
"auto_renew_status":"true",
"bvrs":"4.3.41.4",
"password":"10bdf782f97049f196a0595bd20fbf10",
"bid":"com.blueberry.Gmu"
}
由此可以看出并没有用户正常续订的通知,这块就和安卓不一样了,安卓是会有续订的通知的。苹果是默认就续订上了,取消才会有通知。