记一次小程序支付开发--thinkphp5.1
上篇记录了小程序授权登录的接口,这次就直接收尾记录一下小程序支付,依旧先上文档 小程序支付文档
小程序支付是包含在JSAPI支付的类型中的,所以有过JSAPI支付开发经验的,不值一提。
开发之前,小程序开通微信支付,并绑定关联商户号,这其中的审核过程就不说了,比较多,但跟着步骤走一般都没问题。
直接记录开发过程:
前端请求支付接口,后台需要获取用户的openid,由于我已经在前面把openid获取并存到数据库,因此这里直接读取数据库的openid。若想获取openid请移步上一篇文章《小程序授权登录》
public function pay_order() { $id = intval(input(\'id\')); $user = get_user(input(\'openid\')); if (empty($user)) { return json([\'status\' => 201, \'msg\' => \'请先登录\']); }
//查询自己数据库的订单 $order = Db::name(\'order\')->where(\'id\', $id)->where(\'status\', 1)->where(\'user_id\', $user[\'id\'])->find(); if (empty($order)) { return json([\'status\' => 201, \'msg\' => \'订单错误\']); } $uniurl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //微信统一下单API $params = [ \'appid\' => config(\'site.app_id\'), //小程序app_id \'mch_id\' => config(\'site.mch_id\'), //微信支付商户号 \'nonce_str\' => createNoncestr(), //随机字符串 \'sign_type\' => \'MD5\', //签名方式MD5 \'body\' => \'夜夜夜夜\', \'out_trade_no\' => $order[\'order_num\'], //订单编号 // \'total_fee\' => $order[\'money\'] * 100, //价格,单位 :分 \'total_fee\' => 1, \'spbill_create_ip\' => \'X.X.X.X\', //服务器地址 \'notify_url\' => \'http://xxx.com/pay_order\', //微信支付结果回调通知地址 \'trade_type\' => \'JSAPI\', \'openid\' => $user[\'openid\'], //用户openid ]; $params[\'sign\'] = getSign($params); $xml = arrayToXml($params); $res = curl_post($uniurl,$xml); $data = xmlToArr($res); $return_params = [ \'appId\' => config(\'site.app_id\'), \'timeStamp\' => strval(time()), \'nonceStr\' => createNoncestr(), \'package\' => \'prepay_id=\'.$data[\'prepay_id\'], \'signType\' => \'MD5\' ]; $return_params[\'paySign\'] = getSign($return_params); unset($return_params[\'appId\']); $return_params[\'total_fee\'] = 0.01; return json_encode($return_params); }
我这里呢加密方式使用了默认的MD5加密,也可以使用HMAC-SHA256方式。
下面是一些用到的函数
生成随机字符串nonce_str函数:
function createNoncestr($length = 32) { $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; $str = ""; for ($i = 0; $i < $length; $i++) { $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); } return $str; }
对请求参数进行签名函数,签名是重中之重,可以在微信官方提供的签名校验校验一下,加密是否成功。
function getSign($param) { if (isset($param[\'sign\'])) { unset($param[\'sign\']); } ksort($param); $str = urldecode(http_build_query($param)); $str .= \'&key=\' . config(\'site.mch_key\'); return strtoupper(md5($str)); }
下面是数组转XML,XML转数组,因为请求参数必须为XML格式
/** * 数组转XML */ function arrayToXml($arr) { if (!is_array($arr) || count($arr) == 0) return \'\'; $xml = "<xml>"; foreach ($arr as $key => $val) { if (is_numeric($val)) { $xml .= "<" . $key . ">" . $val . "</" . $key . ">"; } else { $xml .= "<" . $key . ">" . $val . "</" . $key . ">"; } } $xml .= "</xml>"; return $xml; } /** * XML转为数组 * @param $xml * @return mixed|string * @create_time: 2020-09-01 15:06:49 */ function xmlToArr($xml) { if ($xml == \'\') return \'\'; libxml_disable_entity_loader(true); $arr = json_decode(json_encode(simplexml_load_string($xml, \'SimpleXMLElement\', LIBXML_NOCDATA)), true); return $arr; }
curl_post,请求接口函数
// post方法 function curl_post($url, $postData) { $ch = curl_init(); $header = [\'Accept-Charset: utf-8\']; curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($ch, CURLOPT_HTTPHEADER, $header); curl_setopt($ch, CURLOPT_USERAGENT, \'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)\'); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_AUTOREFERER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $tmpInfo = curl_exec($ch); if (curl_errno($ch)) { return false; } else { curl_close($ch); return $tmpInfo; } }
之后请求统一下单接口,会得到预支付交易标识prepay_id,需要再次对调起支付API的参数进行签名。如上变量return_params所示。之后就可以返回给前端,前端唤起微信支付。
支付成功的回调也是很重要的:如下,需要对返回的数据进行延签,并比对金额是否正确
//订单支付成功处理逻辑 public function pay_order() { $arr = file_get_contents(\'php://input\'); $data = xmlToArr($arr); $sign = getSign($data); if ($sign == $data[\'sign\']) { if ($data[\'return_code\'] == \'SUCCESS\' || $data[\'result_code\'] == \'SUCCESS\') { $orderInfo = Db::name(\'order\')->where(\'order_num\', $data[\'out_trade_no\'])->find(); $user = Db::name(\'user\')->where(\'id\', $orderInfo[\'user_id\'])->find(); if (!empty($orderInfo) && !empty($user)) { //判断订单是否已经处理 if ($orderInfo[\'status\'] == 2) { return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; } //判断返回金额和订单金额是否一致 if ($orderInfo[\'money\']*100 == $data[\'total_fee\']) { /**此处写自己的成功之后的逻辑 * XXXX */ //记录日志 Log::record($user[\'phone\'].\'支付了一笔订单,订单编号:\'.$orderInfo[\'order_num\'],\'wxpay\'); return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; } } } } else { return \'Access Deny\'; } }
这里处理成功之后需要给微信返回
<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>这样一条信息,告知微信服务器已收到通知并处理成功。这样微信服务器便不会继续请求,否则会按一定时间规则进行请求。