业务逻辑
?注意:
-
同一Apple 账号生成续订订单的原始交易ID(original_transaction_id)一致 -
服务端处理交易过程 :要确定一个交易ID(transaction_id)只能完成一笔订单,处理完该交易的订单之后,该交易ID记录标识为处理完成状态 -
服务端可以通过用户购买凭证(receipt_data)查询用户所有交易记录 查询到的数据有in_app、latest_receipt_info、pending_renewal_info -
连续订阅主要用到数据是latest_receipt_info,里面有所有的续订记录。 -
如果里面的交易有cancellation_date字段,说明该交易已经被退款。 -
pending_renewal_info里面的auto_renew_status字段用于标识用户是否开通自动订阅;0:已关闭;1:已开通。
在接收?App Store的续订、取消、退款通知时,因为选择的版本2(version 2 notification)的通知,版本二通知是jwt编码实现。
所以需要解码,方法如下:
<?php
class IapNotifyController extends Controller
{
public function notify(Request $request){
$post_data = $request->getContent();
$data = json_decode($post_data,true);
$data = $data['signedPayload'];
$data = $this->verifyToken($data);
$data['signedTransactionInfo'] = $this->verifyToken($data['data']['signedTransactionInfo']);
$data['signedRenewalInfo'] = $this->verifyToken($data['data']['signedRenewalInfo']);
if($data) {
/*通知类型
https://developer.apple.com/documentation/appstoreservernotifications/notificationtype
CONSUMPTION_REQUEST 表示客户针对消耗品内购发起退款申请
DID_CHANGE_RENEWAL_PREF 对其订阅计划进行了更改 如果subtype是UPGRADE,则用户升级了他们的订阅;如果subtype是DOWNGRADE,则用户将其订阅降级或交叉分级
DID_CHANGE_RENEWAL_STATUS 通知类型及其subtype指示用户对订阅续订状态进行了更改
DID_FAIL_TO_RENEW 一种通知类型及其subtype指示订阅由于计费问题而未能续订
DID_RENEW 一种通知类型,连同其subtype指示订阅成功续订
EXPIRED 一种通知类型及其subtype指示订阅已过期
GRACE_PERIOD_EXPIRED 表示计费宽限期已结束,无需续订,因此您可以关闭对服务或内容的访问
OFFER_REDEEMED 一种通知类型,连同其subtype指示用户兑换了促销优惠或优惠代码。 subtype DID_RENEW
PRICE_INCREASE 一种通知类型,连同其subtype指示系统已通知用户订阅价格上涨
REFUND 表示 App Store 成功为消耗性应用内购买、非消耗性应用内购买、自动续订订阅或非续订订阅的交易退款
REFUND_DECLINED 表示 App Store 拒绝了应用开发者发起的退款请求
RENEWAL_EXTENDED 表示 App Store 延长了开发者要求的订阅续订日期
REVOKE表示 用户有权通过家庭共享获得的应用内购买不再通过共享获得
SUBSCRIBED 一种通知类型,连同其subtype指示用户订阅了产品
1. 用户主动取消订阅notificationType:DID_CHANGE_RENEWAL_STATUS
2. 用户取消订阅,又重新开通连续订阅notificationType: SUBSCRIBED subtype: RESUBSCRIBE
3. 用户首次开通订阅notificationType: SUBSCRIBED subtype: INITIAL_BUY
*/
$notification_type = $data['notificationType'];
$transactionData = $data['signedTransactionInfo'];
$product_id = $transactionData['productId'];
$sub_type = isset($data['subtype']) ? $data['subtype'] : '';
$original_transaction_id = $transactionData['originalTransactionId']; // //原始交易ID
$transaction_id = $transactionData['transactionId']; // //交易的标识
$expires_date = date('Y-m-d H:i:s',$transactionData['expiresDate']/1000);
//todo 记录通知log
//查询原始交易绑定的用户ID
if (in_array($notification_type, ['DID_RENEW','SUBSCRIBED'])) {
//开通成功以及续订成功处理交易
}
//用户退款处理交易
if (in_array($notification_type, ['REFUND'])) {
}
//用户取消订阅或者订阅过期
if (in_array($notification_type, ['EXPIRED','DID_FAIL_TO_RENEW'])
|| ($notification_type == 'DID_CHANGE_RENEWAL_STATUS')) {
$is_renew = 0;
if(($notification_type == 'DID_CHANGE_RENEWAL_STATUS') && $sub_type == 'AUTO_RENEW_ENABLED')
{
//开通订阅成功
}elseif (($notification_type == 'DID_CHANGE_RENEWAL_STATUS') && $sub_type == 'AUTO_RENEW_DISABLED'){
//取消订阅成功
}
//更新用户订阅状态
}
}
}
/**
* 验证token是否有效,默认验证exp,nbf,iat时间
* @param string $Token 需要验证的token
* @return bool|string
*/
public static function verifyToken($Token)
{
$tokens = explode('.', $Token);
if (count($tokens) != 3)
return false;
list($base64header, $base64payload) = $tokens;
//获取jwt算法
$base64decodeheader = json_decode(self::base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);
if (empty($base64decodeheader['alg']) || $base64decodeheader['alg'] != 'ES256')
return false;
$payload = json_decode(self::base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);
return $payload;
}
/**
* base64UrlEncode https://jwt.io/ 中base64UrlEncode编码实现
* @param string $input 需要编码的字符串
* @return string
*/
private static function base64UrlEncode($input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}
/**
* base64UrlEncode https://jwt.io/ 中base64UrlEncode解码实现
* @param string $input 需要解码的字符串
* @return bool|string
*/
private static function base64UrlDecode($input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$addlen = 4 - $remainder;
$input .= str_repeat('=', $addlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
}
参考文章:IAP 自动续费后端接入指南_theCrucian的博客-CSDN博客
iOS自动续订订阅开发----验证收据和状态回调JSON解析 - 简书
|