第一次对接支付宝的报关接口,害!不明白这个流程是很难接上的,搞了一天半,终于给搞出来了。在这里留下一点文字吧。
代码前工作:
- 用支付宝的密钥生成工具生成一对2048的公私钥,这对公私钥用来完成支付宝支付的加签和验签。
把公钥上传到 开放平台密钥中对应使用的应用接口加签方式中,私钥是用在支付接口中加签的,保存好!
- 用支付宝的密钥生成工具生成一对1024的公私钥,这对公私钥用来完成报关流程(1024位是技术客服推荐的),把公钥上传至 mapi网关产品密钥和老板wap支付密钥 的开发者公钥中去,密钥保存好,用来给报关文档中的sign字段加签。
现在开始写代码
看文档可以知道,要确认海关申报是否成功要接上两个接口:报关接口和报关查询接口,当看到报关查询接口的返回参数中的status==succ时,才算报关成功。那就一步一步来吧,先接报关接口:
- 把文档中所需要的参数查询出来,放到一个数组里,注意:sign和sign_type字段先别写进去。
//推送支付宝
if($_REQUEST['act']=='push_ali'){
//需要进行加签的数据
$data = array(
'amount' => $amount, //报关金额
'buyer_name' => $buyer_name, //订购人姓名
'buyer_id_no' => $buyer_id_no, //订购人身份证
'customs_place' => 'zongshu', //海关编号
'merchant_customs_code' => 'xxxxx', //商户海关备案号
'merchant_customs_name' => 'xxxxx有限公司', //商户海关备案名称
'out_request_no' => $out_request_no, //报关流水号(订单号)
'partner' => '2088xxxxxxxxxxxx', //合作者身份ID,2088开头
'service' => 'alipay.acquire.customs', //接口名称
'trade_no' => $trade_no, //支付宝流水号
'_input_charset' => 'utf-8', //参数编码字符集
);
$data = argSort($data);//对数组排序
//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
$sign_data= createLinkstring($data);
//生成sign参数
$sign = urlencode(rsaSign($sign_data));
//签名结果与签名方式 加入请求提交参数组中
$data['sign_type'] = 'RSA';
$data['sign'] = $sign;
$url = 'https://mapi.alipay.com/gateway.do?';
$data = createLinkstring($data);
//发送get请求的url
$getUrl = $url.$data;
$response = getHttpResponseGET($getUrl);
//接受报关返回的xml,转换成数组
$xml = simplexml_load_string($response);
$data = json_decode(json_encode($xml),TRUE);
/判断报关是否成功,成功则进行报关查询
if($data['is_success'] == 'T'){
//继续写报关查询接口
}else{
//返回错误代码
}
argSort()、 createLinkstring()、rsaSign()、getHttpResponseGET()是支付宝官方提供的demo中的方法,你不想用他的也可以自己写,简单看一下吧
/**
* 对数组排序
* @param $para 排序前的数组
* return 排序后的数组
*/
function argSort($para) {
ksort($para);
reset($para);
return $para;
}
/**
* 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
* @param $para 需要拼接的数组
* return 拼接完成以后的字符串
*/
function createLinkstring($para) {
$arg = "";
while (list ($key, $val) = each ($para)) {
$arg.=$key."=".$val."&";
}
//去掉最后一个&字符
$arg = substr($arg,0,count($arg)-2);
//如果存在转义字符,那么去掉转义
if(get_magic_quotes_gpc()){$arg = stripslashes($arg);}
return $arg;
}
/**
* RSA签名
* @param $data 待签名数据
* @param $private_key 商户私钥字符串
* return 签名结果
*/
function rsaSign($data) {
//商户私钥
$private_key = "上面生成的1024位的密钥写在这里";
//以下为了初始化私钥,保证在您填写私钥时不管是带格式还是不带格式都可以通过验证。
$private_key=str_replace("-----BEGIN RSA PRIVATE KEY-----","",$private_key);
$private_key=str_replace("-----END RSA PRIVATE KEY-----","",$private_key);
$private_key=str_replace("\n","",$private_key);
$private_key="-----BEGIN RSA PRIVATE KEY-----".PHP_EOL .wordwrap($private_key, 64, "\n", true). PHP_EOL."-----END RSA PRIVATE KEY-----";
$res=openssl_get_privatekey($private_key);
if($res)
{
openssl_sign($data, $sign,$res);
}
else {
echo "您的私钥格式不正确!"."<br/>"."The format of your private_key is incorrect!";
exit();
}
openssl_free_key($res);
//base64编码
$sign = base64_encode($sign);
return $sign;
}
/**
* 远程获取数据,GET模式
* 注意:
* 1.使用Crul需要修改服务器中php.ini文件的设置,找到php_curl.dll去掉前面的";"就行了
* 2.文件夹中cacert.pem是SSL证书请保证其路径有效,目前默认路径是:getcwd().'\\cacert.pem'
* @param $url 指定URL完整路径地址
* @param $cacert_url 指定当前工作目录绝对路径
* return 远程输出的数据
*/
function getHttpResponseGET($url) {
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头
curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 显示输出结果
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证
$responseText = curl_exec($curl);
//var_dump( curl_error($curl) );//如果执行curl过程中出现异常,可打开此开关,以便查看异常内容
curl_close($curl);
return $responseText;
}
这里有几点要注意一下:
- rsaSign()方法中的private_key是使用1024位的密钥,用来给数据加签用的。
- 可以post发送请求也可以发送get请求,官方技术推荐get方式。
- rsa加签后的sign值是需要urlencode一下再进行拼装。
请求发送后,打印一下支付宝的同步返回,如果 is_success为 T ,则报关支付宝成功,但这不代表业务处理成功。实际上,海关返回信息并不是同步返回的,他会间隔10分钟左右。所以继续接入报关查询接口:
if($data['is_success'] == 'T'){
$response = $data['response'];//返回数组中的response数组
$alipay_data = $response['alipay'];//response中的alipay数组
//报关查询sign值加签参数
$datas = array(
'service' => 'alipay.overseas.acquire.customs.query',
'partner' => '2088xxxxxxxxxxxx',
'_input_charset' => 'utf-8',
'out_request_nos' => $alipay_data['out_request_no']
);
//排序
$datas = argSort($datas);
//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
$sign_datas= createLinkstring($datas);
//生成sign参数
$sign = urlencode(rsaSign($sign_datas));
//签名结果与签名方式 加入请求提交参数组中
$datas['sign_type'] = 'RSA';
$datas['sign'] = $sign;
//var_dump($datas);
$datas = createLinkstring($datas);
//报关查询发送get请求的url
$getUrls = $url.$datas;
//接受报关返回的xml,转换成数组
$response = getHttpResponseGET($getUrls);
$xml = simplexml_load_string($response);
$datas = json_decode(json_encode($xml),TRUE);
//找customs_declare中的status,succ是海关申报成功
这一步和报关其实差不多,主要是发送的数据不一样。接受到的结果如图,当status对于succ是,恭喜你,成功了!既然工作已经完成了,那就盘点一下我视为障碍的地方:
- sign值:文档中写着参见9,签名机制,我找了半天没找到这个9在哪里,后来还是问客服解决的。
- 刚开始说道的生成的两对公私钥,文档中没有看到对应的提醒,商户中心也没有对应的提醒,害!新手很难的啊。
- 支付宝的公钥是会变的,每次更改商户的密钥对他都会变化一次。(刚开始我以为他不会变。。。。)
- 如果你打印出来的数据显示不全,打开php.ini,在xdebug的地方加上:
xdebug.var_display_max_children=128 //允许一个数组最多显示多少个元素
xdebug.var_display_max_data=1024 //允许一个字符串变量最多显示多少个字节
xdebug.var_display_max_depth=10 //允许一个数组最多显示多少个维度
在简单说一下我对密钥对的理解:
RSA 是一种非对称的签名算法,即签名密钥(私钥)与验签密钥(公钥)是不一样的, 私钥用于签名,公钥用于验签。
私钥就是锁,公钥就是钥匙,只有拿对应的钥匙(公钥)才能解对应的锁(私钥)。
功夫不负有心人,bug总会被解决的,自己看不懂就多问问多看看,看多了就熟了,问多了就懂了!