admin

详解支付宝沙箱PC支付(RSA2签名算法)
支付宝PC扫码沙箱支付,使用新版的RSA2支付1.首先登陆蚂蚁金服https://openhome.alipay....
扫描右侧二维码阅读全文
11
2019/01

详解支付宝沙箱PC支付(RSA2签名算法)

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

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

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

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

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

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

生成方法:

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

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

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

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

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

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

  1. 查看支付接口 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'] 是支付网关 沙箱调试的网关是: 
$url = $alipayConfigs['GATEWAG']. http_build_query($params);

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

部分代码:

    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请求请求成功后得到

处理结果有两种:

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/)
c6.png

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是不变的。

最后修改:2019 年 05 月 10 日 03 : 48 PM
如果觉得我的文章对你有用,请随意赞赏

1 条评论

  1. admin

    ヾ(≧∇≦*)ゝ

发表评论