业务逻辑
注意:
-
同一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编码实现。
所以需要解码,方法如下:
<?phpclass 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/notificationtypeCONSUMPTION_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_RENEWPRICE_INCREASE 一种通知类型,连同其subtype指示系统已通知用户订阅价格上涨REFUND 表示 App Store 成功为消耗性应用内购买、非消耗性应用内购买、自动续订订阅或非续订订阅的交易退款REFUND_DECLINED 表示 App Store 拒绝了应用开发者发起的退款请求RENEWAL_EXTENDED 表示 App Store 延长了开发者要求的订阅续订日期REVOKE表示 用户有权通过家庭共享获得的应用内购买不再通过共享获得SUBSCRIBED 一种通知类型,连同其subtype指示用户订阅了产品1. 用户主动取消订阅notificationType:DID_CHANGE_RENEWAL_STATUS2. 用户取消订阅,又重新开通连续订阅notificationType: SUBSCRIBED subtype: RESUBSCRIBE3. 用户首次开通订阅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//查询原始交易绑定的用户IDif (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解析 - 简书