沙滩星空的博客沙滩星空的博客

支付宝APP支付开发笔记

支付宝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');

异步通知验签

在步骤四验证签名正确后,必须再严格按照如下描述校验通知数据的正确性。

  1. 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号.
  2. 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额).
  3. 校验通知中的 seller_id(或者 seller_email ) 是否为 out_trade_no 这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email).
  4. 验证 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
未经允许不得转载:沙滩星空的博客 » 支付宝APP支付开发笔记

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址