记一次小程序支付开发--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>这样一条信息,告知微信服务器已收到通知并处理成功。这样微信服务器便不会继续请求,否则会按一定时间规则进行请求。

posted @ 2020-09-04 12:04  热爱交流的程序员  阅读(463)  评论(0)    收藏  举报