支付宝APP支付开发过程中,除了客户端的SDK包,还需要服务端返回orderInfo字符串,给客户端调用支付宝的SDK。
客户端支付宝SDK调用时,由于服务端返回的字符串不正确。如下提示:支付宝 订单处理失败,请稍后再试 ALI38173
错误原因:
1.请求参数问题 2.密钥问题 3.乱码问题 4.没有签约该接口
该报错提示给的信息量太少。后来自己排查,才发现是请求参数的格式错误。biz_content 字段内容在格式转化的时候,自作主张加了addslashes()处理,给json字符串加了反斜杠。实际并不需要。
$request = new \AlipayTradeAppPayRequest();
$orderInfoArr = [
'body' => 'goods data',
'subject' => 'order title',
// 'timeout_express' => '90m',
// 'product_code' => 'QUICK_MSECURITY_PAY',
'out_trade_no' => "20889879879231923132",
'total_amount' => "25.6"
];
$bizcontent = json_encode($orderInfoArr); // 正确
// $bizcontent = addslashes(json_encode($orderInfoArr)); // 错误的写法
$request->setBizContent($bizcontent);
官方文档中 APP支付请求参数说明
与 app支付接口2.0
有不一致的地方。
如 请求参数字段biz_content 的 product_code 参数。 请求参数说明,为必填项。支付接口,为选填。
biz_content:公共参数中的必填字段,为请求参数的集合
product_code:请求参数字段。销售产品码,商家和支付宝签约的产品码,APP支付功能中该值固定为: QUICK_MSECURITY_PAY
开发流程简介
1. 支付宝签名配置
签名教程:https://blog.catmes.com/archives/alipay-sign.html
2. 整合APP客户端SDK包,使用服务端返回的orderInfo字符串,用来调用SDK的支付请求。
3. 服务端编写供客户端请求调用的orderInfo字符串。
供APP的SDK调用的orderInfo字符串,为本地应用rsa私钥加密请求参数生成签名字段而得。orderInfo实际上是一个本地生成的,包含私钥签名字段的,供APP的支付宝SDK调用的,请求参数字符串。
服务端的SDK以PHP版本为例。主要为2个文件
AlipayTradeAppPayRequest.php 请求参数文件
AopClient.php 逻辑处理文件
AopClient.php内容节选:
......
/**
* 生成用于调用收银台SDK的字符串
* @param $request SDK接口的请求参数对象
* @return string
* @author guofa.tgf
*/
public function sdkExecute($request) {
$this->setupCharsets($request);
$params['app_id'] = $this->appId;
$params['method'] = $request->getApiMethodName();
$params['format'] = $this->format;
$params['sign_type'] = $this->signType;
$params['timestamp'] = date("Y-m-d H:i:s");
$params['alipay_sdk'] = $this->alipaySdkVersion;
$params['charset'] = $this->postCharset;
$version = $request->getApiVersion();
$params['version'] = $this->checkEmpty($version) ? $this->apiVersion : $version;
if ($notify_url = $request->getNotifyUrl()) {
$params['notify_url'] = $notify_url;
}
$dict = $request->getApiParas();
$params['biz_content'] = $dict['biz_content'];
ksort($params);
$params['sign'] = $this->generateSign($params, $this->signType); // 生成签名字符串
foreach ($params as &$value) {
$value = $this->characet($value, $params['charset']);
}
return http_build_query($params);
}
......
protected function sign($data, $signType = "RSA") {
if($this->checkEmpty($this->rsaPrivateKeyFilePath)){
$priKey=$this->rsaPrivateKey;
$res = "-----BEGIN RSA PRIVATE KEY-----\n" .
wordwrap($priKey, 64, "\n", true) .
"\n-----END RSA PRIVATE KEY-----";
}else {
$priKey = file_get_contents($this->rsaPrivateKeyFilePath);
$res = openssl_get_privatekey($priKey);
}
($res) or die('您使用的私钥格式错误,请检查RSA私钥配置');
if ("RSA2" == $signType) {
openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
} else {
openssl_sign($data, $sign, $res);
}
if(!$this->checkEmpty($this->rsaPrivateKeyFilePath)){
openssl_free_key($res);
}
$sign = base64_encode($sign);
return $sign;
}
下面是实际应用中服务端的代码示例:
require "alipay/aop/AopClient.php";
require "alipay/aop/request/AlipayTradeAppPayRequest.php";
......
$aop = new \AopClient;
$aop->gatewayUrl = "https://openapi.alipay.com/gateway.do";
$aop->appId = $appid;
$aop->rsaPrivateKey = '应用rsa私钥';
$aop->format = "json";
$aop->charset = "UTF-8";
$aop->signType = "RSA2";
$aop->alipayrsaPublicKey = '支付宝RSA公钥';
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
$request = new \AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数
$orderInfoArr = [
'body' => 'goods data',
'subject' => '订单标题',
// 'timeout_express' => '90m',
// 'product_code' => 'QUICK_MSECURITY_PAY',
'out_trade_no' => $outTradeNo,
'total_amount' => $totalAmount
];
$bizcontent = json_encode($orderInfoArr); // 千万别加addslashes()函数处理json字符串
$request->setNotifyUrl($notifyUrl);
$request->setBizContent($bizcontent);
// 这里和普通的接口调用不同,使用的是sdkExecute
$response = $aop->sdkExecute($request);
// 不需要 htmlspecialchars 转义
return $response;
4. 服务端接收异步回调通知的代码。
程序执行完后必须打印输出“success”(不包含引号)。
如果商户反馈给支付宝的字符不是 success 这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。
一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);
require "alipay/aop/AopClient.php";
$aop = new \AopClient;
$aop->alipayrsaPublicKey = "支付宝公钥";
$flag = $aop->rsaCheckV1($_POST, NULL, "RSA2");
if($flag==false){
exit('sign error');
}
$sysTradeNo = $_POST['out_trade_no'];
$tradeNo = $_POST['trade_no'];
$appid = $_POST['app_id'];
$totalAmount = $_POST['total_amount'];
$payAt = $_POST['notify_time'];
if(isset($_POST['trade_status']) && $_POST['trade_status']!='TRADE_SUCCESS'){
exit('order status error');
}
// 根据 $sysTradeNo 查找未付款的订单号, 变更订单状态
......
exit('success');
异步通知验签
在步骤四验证签名正确后,必须再严格按照如下描述校验通知数据的正确性。
- 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号.
- 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额).
- 校验通知中的 seller_id(或者 seller_email ) 是否为 out_trade_no 这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email).
- 验证 app_id 是否为该商户本身。
上述 1、2、3、4有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
ALI38173-排查方案 https://openclub.alipay.com/club/history/read/3546
支付宝APP支付 请求参数说明 https://opendocs.alipay.com/open/204/105465/
客户端调试工具使用教程 https://openclub.alipay.com/club/history/read/7695
app 支付接口 2.0 https://opendocs.alipay.com/apis/api_1/alipay.trade.app.pay
app支付SDK和demo https://opendocs.alipay.com/open/54/104509
支付宝APP支付 通知参数说明 https://opendocs.alipay.com/open/204/105301