微信小程序支付流程
1 用户下单,将订单插入订单表(不贴代码,因程序而异)
2 将订单编辑整合预下单发给微信 前端唤起支付
2.1 整合下单内容 并与下单给微信
public function order_content($post){
$url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //微信接口
$arr = [ //下单内容
'appid' => config('appid'), //小程序的appid
'mch_id' => config('mch_id'), //商户号
'nonce_str' => $post['nonce_str'], //随机数(没强制要求)
'body' => $post['goods_name'], //商品名称
'out_trade_no' => $post['out_trade_no'], //商家单号(这个要记录,一般我是用订单号作为这个商家单号,退款时就是用这个商家单号退款)
'total_fee' => $post['price'], //商品价格(这个时用户支付的金额,注:用户支付1块钱 你要传100)
'spbill_create_ip' => request()->ip(), //客户端IP
'notify_url' => config('callback') . "/api_b/v1/collage/notfy", //支付成功后微信调用你的接口的url
'trade_type' => 'JSAPI', //这个不变
'openid' => $post['openid'] //支付用户对应appid的openid
];
ksort($arr);
//给arr数组追加签名
$arr['sign'] = getSign($arr, config('key')); //config('key')是商户号的密钥
//数组转xml
$xml = ArrayToXml($arr); //微信只接受xml文件
$data = postCurl($xml, $url); //通过curl发给微信,post请求
$arr = XmlToArray($data); //将微信返回的xml数据转化成数组
return $arr;
}
// 生成签名sign
function getSign($arr,$key){
$string = "";
ksort($arr);
foreach ($arr as $k => $v){
if(!empty($v)){
if(!empty($string)){
$string .= "&".$k."=".$v;
} else {
$string = $k."=".$v;
}
} else {
unset($arr[$k]);
}
}
$stringSignTemp = $string."&key=".$key;
// 大写转小写
$sign = strtoupper(md5($stringSignTemp));
return $sign;
}
// 数组转xml
function ArrayToXml($arr){
$xml = '<xml>';
foreach ($arr as $key => $val) {
if (is_numeric($val)){
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
}else{
$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
}
}
$xml .= '</xml>';
return $xml;
}
// post请求 需要证书就传前三个参数,不需要的传前两个参数
function postCurl($xml, $url, $useCert = false, $second = 30){
$ch = curl_init();
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
// //如果有配置代理这里就设置代理
// if(WxPayConfig::CURL_PROXY_HOST != "0.0.0.0" && WxPayConfig::CURL_PROXY_PORT != 0){
// curl_setopt($ch,CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
// curl_setopt($ch,CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
// }
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);//严格校验
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
if($useCert == true){
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLCERT, 'cert/apiclient_cert.pem');
curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLKEY, 'cert/apiclient_key.pem');
}
//post提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//运行curl
$data = curl_exec($ch);
//返回结果
if($data){
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
curl_close($ch);
exit("curl出错,错误码:$error");
}
}
// xml转数组
function XmlToArray($xml){
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)),true);
return $arr;
}
2.2 预下单判断微信返回的结果
//预支付下单成功
if ($arr['return_code'] == 'SUCCESS') {
//这里我是保存预下单的prepay_id 用户发送模板消息
$msg = $order->where('id',$order_id)->update(['prepay_id'=>$arr['prepay_id']]);
//统一下单,编辑小程序前端调起支付的参数
$order = $servercontroller->place_order($arr, $order_id);
Db::commit();
return $order;
}else{
return $arr;
}
//统一下单
public function place_order($arr,$order_id=''){
$order = [];
$order['appId'] = config('appid');
$order['timeStamp'] = (string)time(); //时间
$order['nonceStr'] = $arr['nonce_str']; //这个是微信返回给你的订单号,这个也可以用于退款
$order['package'] = "prepay_id=" . $arr['prepay_id']; //这个是预支付ID
$order['signType'] = "MD5";
$order['paySign'] = getSign($order, config('key')); //签名同上面
$order['order_id'] = (string)$order_id; //这个是根据需求追加的 可以不用加上去
return $order;
}
2.3 小程序调起支付 下面的参数都是后台返回的$order里面的数据;
wx.requestPayment({
'timeStamp': payment.timeStamp,
'nonceStr': payment.nonceStr,
'package': payment.package,
'signType': 'MD5',
'paySign': payment.paySign,
'success': function (res) {
$.prompt('支付成功')
setTimeout(() => {
wx.switchTab({
url: '/pages/profile/profile',
})
}, 1500)
},
'fail': function (res) {
$.prompt('如何遇到无法支付,请联系客服')
}
})
3 支付成功的回调
public function notfy(){
libxml_disable_entity_loader(true);
$postStr = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : file_get_contents("php://input");//将用户端放松的数据保存到变量postStr中,由于微信端发送的都是xml,使用postStr无法解析,故使用$GLOBALS["HTTP_RAW_POST_DATA"]获取
if(!empty($postStr)){
$postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);//将postStr变量进行解析并赋予变量postObj。simplexml_load_string()函数是php中一个解析XML的函数,SimpleXMLElement为新对象的类,LIBXML_NOCDATA表示将CDATA设置为文本节点,CDATA标签中的文本XML不进行解析
$result_code = $postObj->result_code;
$return_code = $postObj->return_code;
if($result_code == 'SUCCESS' && $return_code == 'SUCCESS'){
$pay_amount = trim((string)$postObj->total_fee); //支付金额
$order_sn = trim((string)$postObj->out_trade_no); //商户单号/数据库的订单编号
$pay_sn = trim((string)$postObj->transaction_id); //微信支付订单号/数据库的支付编号
$pay_time = trim((string)$postObj->time_end); //支付时间
$this->pay_success_callback($order_sn,$pay_amount,$pay_sn,$pay_time); //这里是根据需求编写自己的支付成功后的业务逻辑 注:你没有告诉微信他调用你这个回调地址成功,微信会一直调用你这个接口,所有你要作判断
//这个是告诉微信已经调用我们的接口成功 我试了没有效果,我是在pay_success_callback里面判断
return '<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml> ';
}
}
//将结果写进文件,便于查错