苹果支付流程以及服务端php验证

苹果支付流程以及服务端php验证

苹果支付和常规国内的支付流程完全不一样,流程如下:
苹果支付流程以及服务端php验证

步骤如下:

1.上架产品

首先需要在苹果网站上架对应的app产品,有对应的id和价格,名称等数据

2.前端拉起商品列表

用户登录app后,进入商品购买页面,前端拉起app在苹果网站上的商品列表信息

3.下单

用户点击'购买'操作,发送给服务端一条当前商品信息,服务端保存该商品的相关订单数据,并返回给前端一个内部业务订单号order_id

4. 购买

用户接收到服务端返回的内部订单号后发起苹果支付,支付完成后苹果会返回一个购买凭证信息,{"receipt-data" : "MIIT9wYJKoSL2JSDFsd……"},大概有8000+的长度,发送到客户端

5.发送凭证信息给服务器

前端拿到苹果返回的购买凭证信息后,将购买凭证、内部业务订单号order_id等信息传至服务端进行购买校验

6. 购买凭证校验以及后续业务逻辑处理

服务端向苹果发送校验购买凭证的请求,根据返回的结果,进行业务处理(改变订单状态等操作),最后返回校验结果给前端
校验结果状态码如下:
0 正常
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环境的验证服务

7.返回客户端

前端拿到服务端返回的结果,视结果处理商品,比如: 返回校验成功的结果,前端需向苹果发送一条修改对应产品支付状态的消息,这样才能进行金额的结算操作

8.客服端代码

php以yii2框架为参考
    /**
* 苹果手机支付回调处理: 用于处理商家订单状态等逻辑操作
*/
public function actionNotify()
{
//$input_data = '{"buddleId":"recharge_01","receiptData":"MIIULgYJKoZIhvcNAQcCoIIUHzCCFBsCAQExC...bV3L15A JAbVFEk7CLOJ7 oQr8WqvpIGu8v5rUkzEzXglAPwkg9BQZdfsew==","transactionId":"10423434320297269213"}';
//获取回调通知数据
$input_data = post();
//判断数据是否json数据
if (!isJsonString($input_data)) {
jsonFail();
}
//转换通知的JSON文本消息为PHP Array数组
$input_data = ArrayHelper::toArray(json_decode($input_data));
//获取购买凭证
$receiptDate = trim($input_data['receiptData']);
if (!$receiptDate) {
jsonFail('购买凭证不存在');
}
if (strlen($receiptDate) < 20) {
jsonFail('购买凭证长度错误');
}
$trans = Yii::$app->db->beginTransaction();
try {
//验证购买凭证
$config = Yii::$app->params['applet']; // 获取配置
$url = $config['is_sandbox'] ? $config['url_sandbox'] : $config['url_buy']; //获取验证请求地址
//todo 注意:返回的购买凭证里面如果有空格,把空格转换成+,不然校验不通过
$receiptDate = str_replace(" ", "+", $receiptDate);
            //拼接校验信息
$data = [
"receipt-data" => $receiptDate
];
            //转换成json字符串格式
$data = json_encode($data);
            //发送验证请求
$result = curl_post($url, $data);
if (!$result) {
throw new \Exception('验证失败');
}
$result = json_decode($result, true);
/**
* 0 正常
* 21000: "App Store 的请求不是使用 HTTP POST 请求方法发出的。", //·21000 对 App Store 的请求不是使用 HTTP POST 请求方法发出的。
* 21001: "App Store 不再发送此状态代码。",                 //·21001 App Store 不再发送此状态代码。
* 21002: "属性中的数据receipt-data格式不正确或服务遇到临时问题。",    //·21002 属性中的数据receipt-data格式不正确或服务遇到临时问题。再试一次。
* 21003: "无法验证收据。",                              //·21003 无法验证收据。
* 21004: "您提供的共享密钥与您帐户的文件共享密钥不匹配。",              //·21004 您提供的共享密钥与您帐户的文件共享密钥不匹配。
* 21005: "收据服务器暂时无法提供收据。",                       //·21005 收据服务器暂时无法提供收据。再试一次
* 21006: "此收据有效,但订阅已过期。",                        //·21006 此收据有效,但订阅已过期。当此状态代码返回到您的服务器时,收据数据也会被解码并作为响应的一部分返回。仅针对自动续订订阅的 iOS 6 样式交易收据返回。
* 21007: "这条回执是来自测试环境,但它是发送到生产环境进行验证的。",         //·21007 这条回执是来自测试环境,但它是发送到生产环境进行验证的。
* 21008: "这条回执来自生产环境,但它被发送到测试环境进行验证。",           //·21008 这条回执来自生产环境,但它被发送到测试环境进行验证。
* 21009: "内部数据访问错误。",                            //·21009 内部数据访问错误。稍后再试。
* 21010: "用户帐户找不到或已被删除。",                        //·21010 用户帐户找不到或已被删除。
*/
// 如果是沙盒数据 则验证沙盒模式
if ($result['status'] == '21007') {
// 请求验证
$url = $config['url_sandbox']; //沙盒购买地址
$result = curl_post($url, $data);
$result = json_decode($result, true);
}
if ($result['status'] != 0) {
throw new \Exception("验证失败,订单购买凭证状态为" . $result['status']);
}
//获取内部订单号
$orderId = $input_data['orderId'];
// 处理业务逻辑
$platform_order_number = $input_data["transactionId"];  //获取苹果订单号
// 订单状态
$status = UserOrder::STATUS_BUY;
            //支付时间
$paid_at = $result['receipt']['in_app']['purchase_date'];
//判断订单是否存在
$user_order = UserOrder::findOne(['order_id' => $orderId]);
if (!$user_order) {
throw new \Exception("订单不存在");
}
if ($user_order->status == $status) {
throw new \Exception("商品已支付");
}
// 变更订单属性,修改订单状态以及支付时间等数据
UserOrder::changeAttribute(
['order_id' => $orderId],
[
'status' => $status,
'paid_at' => strtotime($paid_at),
'platform_order_number' => $platform_order_number,
]
);
$trans->commit();
$msg = '支付成功后回调: ' . $ono . '订单更新成功';
jsonSuccess($msg);
} catch (\Exception $e) {
$msg = 'error: ' . $e->getMessage() . ' (file: ' . $e->getFile() . ' [' . $e->getLine() . '])';
$trans->rollBack();
jsonFail($msg);
}
}

苹果凭证校验返回结果案例展示如下:

{
"receipt": {
"receipt_type": "ProductionSandbox",
"adam_id": 0,
"app_item_id": 0,
"bundle_id": "xxx",
"application_version": "0",
"download_id": 0,
"version_external_identifier": 0,
"receipt_creation_date": "2023-03-16 06:49:00 Etc/GMT",
"receipt_creation_date_ms": "1678949340000",
"receipt_creation_date_pst": "2023-03-15 23:49:00 xxx",
"request_date": "2023-03-17 09:57:55 xxx",
"request_date_ms": "xxx",
"request_date_pst": "2023-03-17 02:57:55 xxx",
"original_purchase_date": "2013-08-01 07:00:00 xxx",
"original_purchase_date_ms": "xxx",
"original_purchase_date_pst": "2013-08-01 00:00:00 xxx",
"original_application_version": "1.0",
"in_app": [
{
"quantity": "1",
"product_id": "recharge_01",
"transaction_id": "1424324229453534",
"original_transaction_id": "1424324229453534",
"purchase_date": "2023-03-16 06:49:00 xxx",
"purchase_date_ms": "xxx",
"purchase_date_pst": "2023-03-15 23:49:00 xxx",
"original_purchase_date": "2023-03-16 06:49:00 xxx",
"original_purchase_date_ms": "xxx",
"original_purchase_date_pst": "2023-03-15 23:49:00 xxx",
"is_trial_period": "false",
"in_app_ownership_type": "PURCHASED"
}
]
},
"environment": "Sandbox",
"status": 0
}
上面需要使用到的公共方法
/**
* 校验一个字符串是不是json字符串
* @param string $data
* @param bool $assoc
* @return bool
*/
function isJsonString($data = '', $assoc = true) {
$data = json_decode($data, $assoc);
if(($data && is_object($data)) || (is_array($data) && !empty($data))){
return true;
}
return false;
}
//post请求
function curl_post($url, $data, $headers = array())
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
//设置header头
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// POST数据
curl_setopt($ch, CURLOPT_POST, 1);
// 把post的变量加上
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
/**
* 操作成功
* @example1 jsonSuccess();
*/
function jsonSuccess($data = NULL, $message = '操作成功')
{
header("Content-Type:application/json");
$json = array();
$json['code'] = 0;
$json['data'] = $data;
$json['message'] = $message;
$json['request_time'] = now();
echo Json::encode($json);
exit();
}
/**
* 错误信息
* @example1 jsonFail();
* @example1 jsonFail("删除失败!");
* @example2 jsonFail($model->getErrors());
*/
function jsonFail($message = '请求失败', $code = 1, $data = null)
{
header("Content-Type:application/json");
$json = array();
$json['code'] = $code;
$json['data'] = $data;
$json['message'] = $message;
$json['request_time'] = now();
echo Json::encode($json);
exit();
}
Yii2苹果相关配置:params-local.php
<?php
return [
//applet配置
'applet' => [
'app_id' => '', //应用ID
'secret' => '', //支付密钥
//沙箱,现网
'is_sandbox' => 0,
'url_buy'     => "https://buy.itunes.apple.com/verifyReceipt", // 正式购买地址
'url_sandbox' => "https://sandbox.itunes.apple.com/verifyReceipt",// 沙盒购买地址
'pay_test' => 1,  //是否支付测试
'is_screen_pay' => 0, //是否屏蔽支付
],
];
                       

点击阅读全文

上一篇 2023年 6月 12日 am10:37
下一篇 2023年 6月 12日 am10:40