按照微信的文档来看确实流程是什么样的,但某些数据却神一般的缺少说明,硬生生调了一天才知道完整的使用数据。
官方的流程图是这样子的
小程序提交订单后就需要后台请求两次API,一次为获取openid(某文档说是在小程序内获取不安全,所以丢给后台来获取),后面一次为获取prepay_id。最后那次“推送支付结果”是用到回调地址"
官方文档里有说明,凭js_code获取openid,appid和secret(小程序密钥)这个在微信公众平台的小程序的管理中设置。
$data=file_get_contents('https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code');
$openId=json_decode($data)->openid;
设置xml的提交数据
$appAttr = array();
$appAttr['appid'] = '小程序ID';
$appAttr['attach'] = 'msg';
$appAttr['body'] = 'order';
$appAttr['detail'] = '{"goods_detail":[{"goods_id":"iphone6s_32G","wxpay_goods_id":"1002","goods_name":"iPhone6s 32G","quantity":1,"price":608800,"goods_category":"123789","body":"苹果手机"}]}';
$appAttr['mch_id'] = '商户ID';
$appAttr['nonce_str'] = md5(time());
$appAttr['notify_url'] = 'http://xxxx.com';
$appAttr['openid'] = $openId;
$appAttr['out_trade_no'] = '商户订单号';
$appAttr['spbill_create_ip'] = '127.0.0.1';
$appAttr['total_fee'] = 1;//单位分
$appAttr['trade_type'] = 'JSAPI';
//推荐上面有数据按官方的“ASCII字典”排序的设键放置数据
$signStr = '';
$appAttrKeys = array_keys($appAttr);
foreach ($appAttrKeys as $appAt) {
$signStr .= $appAt . '=' . $appAttr[$appAt].'&';
}
//key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
$signStr=$signStr.'key=xxxxxxx';//key不参与ASCII字典排序,放置在最后
$appAttr['sign'] = strtoupper(md5($signStr));
$xmlDom = new DOMDocument();
$xmlDomRoot = $xmlDom->createElement('xml');
$xmlDom->appendChild($xmlDomRoot);
$childDomAttr = ['appid', 'attach','body', 'mch_id', 'detail', 'notify_url','nonce_str', 'openid', 'out_trade_no', 'spbill_create_ip', 'total_fee', 'trade_type', 'sign'];
foreach ($childDomAttr as $child) {
$nowChild = $xmlDom->createElement($child);
/*[!CDATA[]]特殊处理*/
if($child=='detail'){
$childText = $xmlDom->createCDATASection('');
}else{
$childText = $xmlDom->createTextNode($appAttr[$child]);
}
$nowChild->appendChild($childText);
$xmlDomRoot->appendChild($nowChild);
}
$xmlStr=$xmlDom->saveHTML();
如果直接用saveXML(),生成xml的字符串就会包含开头的的单标签。我看到的微信的文档的示例数据xml标签是包含提交的数据标签的,所以简单点用了saveHTML(),导出来的数据就是理想的结构。
xml里的数据存在中文会进行html编码,所以存在中文的不要savaHTML(),而用如下的savaXML()。这个不会带,操作参考PHP官方的DOMdocument的savaXML()操作
$xmlStr=$xmlDom->saveXML($xmlDom->documentElement);
$wechatPayCurl = curl_init();
curl_setopt($wechatPayCurl, CURLOPT_URL, 'https://api.mch.weixin.qq.com/pay/unifiedorder');
curl_setopt($wechatPayCurl, CURLOPT_POST, 1);
curl_setopt($wechatPayCurl, CURLOPT_HTTPHEADER, ["Content-type: text/xml"]);
curl_setopt($wechatPayCurl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($wechatPayCurl, CURLOPT_POSTFIELDS, $xmlStr);
curl_setopt($wechatPayCurl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($wechatPayCurl, CURLOPT_SSL_VERIFYHOST, false);
$payData=curl_exec($wechatPayCurl);
curl_close($wechatPayCurl);
使用curl进行提交,模拟POST,把xml数据按文件二进制形式提交
保证了前面sign值正确,微信的后台服务就会返回类似这样的数据。如果说签名错误,去官方的签名校验工具,用XML的数据校验出md5之前的sign文本数据,文本对比一下就知道那里有错了。
再用DOMdocument的loadXML($payData)的方法解析得到结果,实际这一步的接口就是为拿到"prepay_id"这个值。
$resultData=array();
$resultData['appId']=appId;
$resultData['nonceStr']=$loadXml->getElementsByTagName('nonce_str')->item(0)->nodeValue;
$resultData['package']='prepay_id='.$loadXml->getElementsByTagName('prepay_id')->item(0)->nodeValue;
$resultData['signType']='MD5';
$resultData['timeStamp']=(string)time();
$resultKeys=array_keys($resultData);
$signStr = '';
foreach ($resultKeys as $rk) {
$signStr .= $rk . '=' . $resultData[$rk].'&';
}
$signStr.='key='.key;
$resultData['paySign']=strtoupper(md5($signStr));
这是给小程序那边的"package"用的,注意"package"的值为package="prepay_id=wx201411101639507cbf6ffd8b0779950874",不然小程序那边调起支付出现“JSAPI缺少total_fee参数”。所以直接在这边拼接好。
流程图里返回“五个参数和sign”给小程序的五个参数是指"appId","nonceStr","package","sigType","timeStamp"和使用ASII字典排序这五个参数再md5得到的"paySign"。而小程序对应的除appId不填,requestPayment()里其他参数都填进去。
wx.requestPayment(
{
'timeStamp': res.timeStamp,
'nonceStr': res.nonceStr,
'package': res.package,
'signType': res.signType,
'paySign': res.paySign,
'success':function(res){
//支付成功
},
'fail':function(res){},
'complete':function(res){}
})