支付宝PC沙箱支付流程详解

支付宝PC扫码沙箱支付流程,使用新版的RSA2支付

框架我使用了ThinkPHP5

1.首先登陆蚂蚁金服https://openhome.alipay.com/developmentDocument.htm

登陆之后 点开头像 点击账户管理(需要申请沙箱环境,此处省略)

2.点击开发中心-研发服务

3.获取支付参数 APPID 支付宝网关 支付宝公钥和商户私钥

第一次没有支付宝公钥的,我先要用支付宝的生成工具生成一个。

生成方法:

3.1.下载生成工具(https://docs.open.alipay.com/291/105971/)

3.2.解压之后双击运行 RSA签名验签工具.bat 

3.3.因为我是PHP程序,所以勾了非JAVA使用,为了提高安全,密钥长度我用了2048的

3.4复制生成的商户应用公钥,到支付宝后台 点击 RSA2(SHA256)密钥(推荐) 把复制的应用公钥粘贴进去,点击保存

3.5.保存之后点击 [查看支付宝公钥],把支付宝公钥复制下来,把商户应用私钥(3.3图)和支付宝公钥 写进程序的配置文件 

4.熟悉一下支付流程 https://docs.open.alipay.com/270/105898

5. 查看支付接口 https://docs.open.alipay.com/api_1/alipay.trade.page.pay/

6.按照支付接口开发文档编写请求参数

如:

 $alipayConfigs = config('alipay');
         $params = [
             'app_id'        =>  $alipayConfigs['APPID'],
             'method'        =>  'alipay.trade.page.pay',       //接口名称 固定值alipay.trade.page.pay
             'format'        =>  'JSON',                        //目前仅支持JSON
             'return_url'    =>  $alipayConfigs['return_url'],  //同步返回地址
             'charset'       =>  'UTF-8',
             'sign_type'     =>  'RSA2',                        //签名方式
             'sign'          =>  '',                            //签名
             'timestamp'     =>  date('Y-m-d h:i:s'),           //发送时间
             'version'       =>  '1.0',                         //固定1.0
             'notify_url'    =>  $alipayConfigs['notify_url'],  //异步通知地址
             'biz_content'   =>  '',                            //业务请求参数的集合
         ];
        
         $defaultParam = [
             'out_trade_no'  => $orderInfo['orderid'],          //商户订单号
             'product_code'  => 'FAST_INSTANT_TRADE_PAY',       //销售产品码.固定值
             'total_amount'  => 0.01,                           //总价 单位为元
             'subject'       => '罗小罗商城',                    //订单标题
         ];
        $params['biz_content'] =  json_encode($defaultParam,JSON_UNESCAPED_UNICODE);

值得注意的是文档中的 biz_content参数,官方说的是请求参数的集合,除公共参数外所有请求参数都必须放在这个参数中传递。

于是我就把defaultParam参数数组转json之后放到biz_content上。

7.获得参数params的签名

签名步骤参考官方文档(https://docs.open.alipay.com/291/106118):

7.1筛选并排序

获取所有请求参数,不包括字节类型参数,如文件、字节流,剔除sign字段,剔除值为空的参数,并按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。

unset($params['sign']); //剔除sign
ksort($params);         //进行排序

7.2拼接

将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。

再进行urldecode

$queryUrl = urldecode(http_build_query($params));

 

7.3此时生成的字符串为待签名字符串,然后调用签名函数

使用各自语言对应的SHA256WithRSA(对应sign_type为RSA2)或SHA1WithRSA(对应sign_type为RSA)签名函数利用商户私钥对待签名字符串进行签名,并进行Base64编码

$sign = createSign($queryUrl);



//createSign方法声明
function createSign($data = '')
{
    if (!is_string($data)) {
        return null;
    }
      $res  = getPrivateKey();
      $sign = openssl_sign($data, $sign, $res,OPENSSL_ALGO_SHA256 ) ? base64_encode($sign) : null;
      return $sign;
}



//getPrivateKey方法声明
function getPrivateKey()
{
    $alipayConfigs = config('alipay');
    $privKey = $alipayConfigs['APPPRI_KEY'];   //这个是商户私钥
    $search = [
        "-----BEGIN RSA PRIVATE KEY-----",
        "-----END RSA PRIVATE KEY-----",
        "\n",
        "\r",
        "\r\n"
    ];

     $privKey       = str_replace($search,"",$privKey);
     $private_key   = $search[0] . PHP_EOL . wordwrap($privKey, 64, "\n", true) . PHP_EOL . $search[1];
     $res           = openssl_pkey_get_private($private_key);
     return $res;
}


得到签名之后,要把得到的签名加入到params数组中

$params['sign'] = $sign;

8.获得一个带签名的params请求参数之后,构造一个请求URL

//alipayConfigs['GATEWAG'] 是支付网关 沙箱调试的网关        
//是:https://openapi.alipaydev.com/gateway.do
$url = $alipayConfigs['GATEWAG']. http_build_query($params);

9.访问这个URL就可以发起支付

 

部分代码:

    public function addOrderByWangye($orderInfo)
    {
         $alipayConfigs = config('alipay');
         $params = [
             'app_id'        =>  $alipayConfigs['APPID'],
             'method'        =>  'alipay.trade.page.pay',       //接口名称 固定值alipay.trade.page.pay
             'format'        =>  'JSON',                        //目前仅支持JSON
             'return_url'    =>  $alipayConfigs['return_url'],  //同步返回地址
             'charset'       =>  'UTF-8',
             'sign_type'     =>  'RSA2',                        //签名方式
             'sign'          =>  '',                            //签名
             'timestamp'     =>  date('Y-m-d h:i:s'),           //发送时间
             'version'       =>  '1.0',                         //固定1.0
             'notify_url'    =>  $alipayConfigs['notify_url'],  //异步通知地址
             'biz_content'   =>  '',                            //业务请求参数的集合
         ];
        
         $defaultParam = [
             'out_trade_no'  => $orderInfo['orderid'],          //商户订单号
             'product_code'  => 'FAST_INSTANT_TRADE_PAY',       //销售产品码.固定值
             'total_amount'  => 0.01,                           //总价 单位为元
             'subject'       => '罗小罗商城',                    //订单标题
         ];
        $params['biz_content'] =  json_encode($defaultParam,JSON_UNESCAPED_UNICODE);
        $params = setSign($params);
        //和微信支付不同 支付宝生成的链接不用进行URL编码 直接传输即可
        //alipayConfigs['GATEWAG'] 是支付网关 沙箱调试的网关        
        //是:https://openapi.alipaydev.com/gateway.do
         $url = $alipayConfigs['GATEWAG']. http_build_query($params);
         echo json_encode([
             'url'=>$url
         ]);
         exit;
    }




//返回一个带签名的数组
function setSign($params)
{
    unset($params['sign']);
    ksort($params);
    $queryUrl = urldecode(http_build_query($params));
    $sign = createSign($queryUrl);
    $params['sign'] = $sign;
    return $params;
}




//生成签名
function createSign($data = '')
{
    if (!is_string($data)) {
        return null;
    }
      $res  = getPrivateKey();
      $sign = openssl_sign($data, $sign, $res,OPENSSL_ALGO_SHA256 ) ? base64_encode($sign) : null;
      return $sign;
}


//获取私钥
function getPrivateKey()
{
    $alipayConfigs = config('alipay');
    $privKey = $alipayConfigs['APPPRI_KEY'];    //商户私钥
    $search = [
        "-----BEGIN RSA PRIVATE KEY-----",
        "-----END RSA PRIVATE KEY-----",
        "\n",
        "\r",
        "\r\n"
    ];

     $privKey       = str_replace($search,"",$privKey);
     $private_key   = $search[0] . PHP_EOL . wordwrap($privKey, 64, "\n", true) . PHP_EOL . $search[1];
     $res           = openssl_pkey_get_private($private_key);
     return $res;
}

10.支付成功操作

   10.1.用户确认支付后,支付宝get请求returnUrl(商户入参传入),返回同步返回参数。

   10.2.交易成功后,支付宝post请求notifyUrl(商户入参传入),返回异步通知参数。

值得注意的是:

  由于同步返回的不可靠性,支付结果必须以异步通知或查询接口返回为准,不能依赖同步跳转.

11.异步通知处理

11.1获取异步通知参数

$postData = Request()->post();

11.2获得异步通知参数里的签名

 $sign = $postData['sign'];

11.3进行验签(签名之前需要删除签名和签名类型这个要十分注意,如果不删除会导致签名不一样)

unset($postData['sign']);
unset($postData['sign_type']);

11.4对异步通知参数进行排序

ksort($postData);

11.5.得到验签字符串

  $str = urldecode(http_build_query($postData));

11.6调用验签算法

/******RSA2验签算法****** */
/*
   data 需要验签的字符串 
   public_key 支付宝公钥
   sign  异步通知支付宝的签名
   返回 bool
   true : 验签失败
   false : 验签成功
 */
function rsaCheck($data, $public_key, $sign,$type = 'RSA2')
{
    $search = [
       "-----BEGIN PUBLIC KEY-----",
       "-----END PUBLIC KEY-----",
       "\n",
       "\r",
       "\r\n"
    ];
    $public_key     =   str_replace($search,"",$public_key);
    $public_key     =   $search[0] . PHP_EOL . wordwrap($public_key, 64, "\n", true) . PHP_EOL . $search[1];
    $res            =   openssl_get_publickey($public_key);
    if($res)
    {
        if($type == 'RSA'){
            $result = (bool)openssl_verify($data, base64_decode($sign), $res);
        }elseif($type == 'RSA2'){
            $result = (bool)openssl_verify($data, base64_decode($sign), $res,OPENSSL_ALGO_SHA256);
        }
            openssl_free_key($res);
    }else{
            exit("公钥格式有误!");
    }
    return $result; 
}

11.7验证签名通过之后,验证是否是支付宝发来的通知(https://docs.open.alipay.com/58/103597),这个步骤我觉得还是很有必要验证的,万一是恶意用户发来的假通知呢?

获取支付宝通知回来的参数notify_id

$notify_id = $postData['notify_id'];

请求示例:

https://mapi.alipay.com/gateway.do?service=notify_verify&partner=2088002396712354&notify_id=RqPnCoPT3K9%252Fvwbh3I%252BFioE227%252BPfNMl8jwyZqMIiXQWxhOCmQ5MQO%252FWd93rvCB%252BaiGg

为了方便我使用file_get_contents请求请求成功后得到,线上的话建议使用curl get。如果以上url请求成功,则

处理结果有两种:

a.成功时:true

b.不成功时:报对应错误

需要注意的是,当通知是支付宝发来的通知,请求返回的true是字符串类型的true,并不是布尔型的true

/*******判断支付通知是否真的来自支付宝********* */
    //check_url:https://mapi.alipay.com/gateway.do?    
    //service=notify_verify&partner=&notify_id=
    public function isAlipayNotiy($notify_id)
    {
        $checkUrl  = config('alipay.check_url') . $notify_id;
        $res = file_get_contents($checkUrl);
        Db::name('error')->insert(['error'=>$checkUrl]);
        return $res == 'true';
    }

11.8 验签和通知来源合法之后,验证订单状态和金额 判断交易状态(https://docs.open.alipay.com/270/105902/)

以下代码,只为说明

if($postData['trade_status'] == 'TRADE_SUCCESS' || $postData['trade_status'] == 'TRADE_FINISHED')
{
    //用户确实已经支付成功
    //取出支付宝订单号 拿着这个订单号去查我的内部订单
    $aliOrderid = $postData['out_trade_no'];
    //内部订单结果
    $pay = Db::name('Order')->where('orderid',$aliOrderid)->value('pay');
    //假设 pay=1 为支付成功 0 是用户未支付
      if($pay || $pay == null){
        //输出success 告诉支付宝 这个订单我已经处理过了 或 这个订单在我的数据库里面找不到 不要再        
        //通知我了
            echo 'success';
            exit;
     }
    //处理内部逻辑
    $upData['paytime']  = date('Y-m-d H:i:s');
    $upData['pay']      = 1;
    if(Db::name('Order')->where('orderid',$aliOrderid)->update($upData)){
         //输出success 告诉支付宝 这个订单我处理好了 不要再通知我了
             echo 'success';
             exit;
       }
}

注意事项:

  • 必须保证服务器异步通知页面(notify_url)上无任何字符,如空格、HTML标签、开发系统自带抛出的异常提示信息等;
  • 支付宝是用POST方式发送通知信息,因此该页面中获取参数的方式,如:request.Form(“out_trade_no”)、$_POST[‘out_trade_no’];
  • 支付宝主动发起通知,该方式才会被启用;
  • 只有在支付宝的交易管理中存在该笔交易,且发生了交易状态的改变,支付宝才会通过该方式发起服务器通知(即时到账交易状态为“等待买家付款”的状态默认是不会发送通知的);
  • 服务器间的交互,不像页面跳转同步通知可以在页面上显示出来,这种交互方式是不可见的;
  • 第一次交易状态改变(即时到账中此时交易状态是交易完成)时,不仅会返回同步处理结果,而且服务器异步通知页面也会收到支付宝发来的处理结果通知;
  • 程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);
  • 程序执行完成后,该页面不能执行页面跳转。如果执行页面跳转,支付宝会收不到success字符,会被支付宝服务器判定为该页面程序运行出现异常,而重发处理结果通知;
  • cookies、session等在此页面会失效,即无法获取这些数据;
  • 该方式的调试与运行必须在服务器上,即互联网上能访问;
  • 该方式的作用主要防止订单丢失,即页面跳转同步通知没有处理订单更新,它则去处理;
  • 当商户收到服务器异步通知并打印出success时,服务器异步通知参数notify_id才会失效。也就是说在支付宝发送同一条异步通知时(包含商户并未成功打印出success导致支付宝重发数次通知),服务器异步通知参数notify_id是不变的。  

 个人博客:https://www.521bug.cn

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值