公司最近项目app需要做苹果订阅支付,开始觉得和微信的支付流程差不多,做起来还是有点麻烦,主要是网上的
文章很少,不能直接硬抄。自己做完总结一下,希望对小伙伴们有帮助。
首先讲一下我的业务逻辑,也就是php服务端需要做什么事情。
先上图:
下面我详细的讲一下我的做法,并贴出来相应的代码供大家参考。
其实支付是有两种(我理解的分两种)
1》第一种是主动直接购买,app创建订单,我们将订单信息保存,然后app调用SDK发起支付,这个时候会走苹果自
己的支付,不管支付结果咋样,客户端会获取到一个苹果给到的receipt(俗称票据),此时客户端将票据和我们
自己创建的订单号传给服务端进行验证,验证完成之后再进行相应的发货逻辑处理
票据其实就是一串很长的字符串,我们拿它到苹果服务器验证完成之后,会解析成一堆明文数据,我们在进行相应
的处理
下面我贴一下代码:
/**
* @title 验证支付票据 完成订单接口
*/
public function ios_pay()
{
$receipt_data = $_REQUEST['ios_billon'] ?? '';
$order_id = $_REQUEST['order_id'] ?? '';
$result = array('status' => false, 'message' => '非法参数');
if (empty($receipt_data) || empty($order_id)) {
return_json($result);
}
// 请求验证
$appkey = "这里是你自己的苹果支付参数";
$html = $this->acurl($receipt_data, $appkey);#这里封装方法去请求苹果验证票据
$data = json_decode($html, true);
// 如果是沙盒数据 则验证沙盒模式
if ($data['status'] == '21007') {
// 请求验证
$html = $this->acurl($receipt_data, $appkey, 1);
$data = json_decode($html, true);
$data['sandbox'] = '1';
}
$str = "**************下面是票证信息".date("Y-m-d H:i:s")."*****************". "\n\r";
file_put_contents("a.txt", $str.json_encode($data) . "\n\r", FILE_APPEND);
// 判断是否购买成功
if (intval($data['status']) === 0) {
if ($data['receipt']['bundle_id'] != IOS_BUNDLE_ID || empty($data
['receipt']['in_app'])) {
return_json($result);
}
// 找出时间最大的凭据数组
$ks['status'] = $data['status'];
$ks['receipt'] = $data['receipt'];
unset($ks['receipt']['in_app']);
$data['receipt']['in_app'][0] = $data['receipt']['latest_receipt_info'][0] ?? $data['receipt']['in_app'][0];
//取in_app信息
$k = array_merge($ks, $data['receipt']['in_app'][0]);
$pay_date = date('Y-m-d H:i:s', $k['purchase_date_ms']/1000); // 购买日期
$transaction_id = $k['transaction_id'];
··········
这里是自己逻辑处理
因为我们还有一个免费试用功能
··········
if ($k['is_trial_period'] == 'true') { // 享受了免费试用
$update_data['money'] = 0;
$update_data['ios_status'] = 1;//1代表免费试用中
$is_try = 1;//1代表免费试用中
}
·······
处理完成
·······
} else {
$result = array('status' => false, 'message' => $this->err_msg[$data['status']] ?? $data['status']);
}
return_json($result);
}
public function acurl($receipt_data, $appkey = "", $sandbox = 0)
{
//小票信息
$POSTFIELDS = array("receipt-data" => $receipt_data);
if (!empty($appkey)) {
$POSTFIELDS = array("receipt-data" => $receipt_data, 'password' =>
$appkey);
}
$POSTFIELDS = json_encode($POSTFIELDS);
//正式购买地址 沙盒购买地址
$url_buy = "https://buy.itunes.apple.com/verifyReceipt";
$url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";
$url = $sandbox ? $url_sandbox : $url_buy;
//简单的curl
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $POSTFIELDS);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
验证之前的票据
验证之后的信息
验证完返回的状态,处理订单业务逻辑,处理订单,修改订单信息,original_transaction_id 把这个
单号和存入订单信息,在异步回调的时候根据这个单号来查找订单信息来判断是哪个用户,苹果票据里面
也有产品product_id,是 app_store的产品id,不是自己业务的产品id,可以在后台把自己的产品ID和
product_id 关联,处理业务逻辑。这样主动购买逻辑就完成了。
接下来是第二种:
2》通过订阅的方式购买,因为苹果订阅不一样,例如你购买一个会员之后,会在你的订阅中心有一条订阅记录,
如果你不取消,到期之后,他会自动扣款进行续费,你也可以提前进行取消订阅,到期之后,就不会再进行一个
扣款续费了。这里我建议这里与上面的接口分开,因为这个接口需要配置在苹果开发者后台的,等待他那边进行回
调。了解逻辑之后,我们再来看代码,
通知类型: notification_type 有很多种情况,有些通知接收存入日志,不用处理,我们要根据自己的需求进
行相关业务处理,我自己只处理了RENEWAL,DID_RENEW,INTERACTIVE_RENEWAL这些状态
public function ios_continu_pay()
{
$input_data = trim(file_get_contents("php://input"));
$str = "**************下面是IOS最新信息".date("Y-m-d H:i:s")."****************
*". "\n\r";
file_put_contents("b.txt", $str . $input_data . "\n\r", FILE_APPEND);
$resp_str = json_decode($input_data, true);
if (!empty($resp_str)) {
$data = $resp_str['unified_receipt'];
//有时候苹果那边会传空数据调用
// notification_type 几种状态描述
// INITIAL_BUY 初次购买订阅。latest_receipt通过在App Store中验证,可以随时将您的服务器存储在服务器上以验证用户的订阅状态。
// CANCEL Apple客户支持取消了订阅。检查Cancellation Date以了解订阅取消的日期和时间。
// RENEWAL 已过期订阅的自动续订成功。检查Subscription Expiration Date以确定下一个续订日期和时间。
// INTERACTIVE_RENEWAL 客户通过使用用App Store中的App Store以交互方式续订订阅。服务立即可用。
// DID_CHANGE_RENEWAL_PREF 客户更改了在下次续订时生效的计划。当前的有效计划不受影响。
$notification_type = $resp_str['notification_type'];//通知类型
$password = $resp_str['password']; // 共享秘钥
if ($password == "你的苹果支付秘钥") {
//latest_expired_receipt_info 好像只有更改续订状态才有
$receipt = $data['latest_receipt_info'] ?? $data['latest_expired_receipt_info'];
//找出来最近的那一组信息
$receipt = self::arraySort($receipt, 'purchase_date', 'desc');
$original_transaction_id = $receipt['original_transaction_id']; // //原始交易ID
$transaction_id = $receipt['transaction_id']; // //交易的标识
$purchaseDate = str_replace(' America/Los_Angeles', '', $receipt['purchase_date_pst']);
·········
进行你的业务处理逻辑
·········
} else {
NewLog::log('通知传递的密码不正确--password:' . $password, 'ios_pay_continu_password');
}
}
}
//查找最新数据的方法
public static function arraySort($arr,$key,$type='asc'){
$keyArr = []; // 初始化存放数组将要排序的字段值
foreach ($arr as $k=>$v){
$keyArr[$k] = $v[$key]; // 循环获取到将要排序的字段值
}
if($type == 'asc'){
asort($keyArr); // 排序方式,将一维数组进行相应排序
}else{
arsort($keyArr);
}
foreach ($keyArr as $k=>$v){
$newArray[$k] = $arr[$k]; // 循环将配置的值放入响应的下标下
}
$newArray = array_merge($newArray); // 重置下标
return $newArray[0]; // 数据返回
}
好了,上面是我写的苹果支付所有流程和代码,业务代码部分我去掉了,大家自己写自己的业务逻辑就可以。大家
在对接过程,可能会遇到与我不一样的一些地方,因为我们做了连续订阅,免费试用等等相关功能,所以只要思路
一样就是可以了,希望这篇文章对你有所帮助。