微信小程序JSAPI下单 APIv3 PHP

JSAPI下单

商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后再按Native、JSAPI、APP等不同场景生成交易串调起支付。

文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml

接口说明
适用对象: 直连商户

请求URL:https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

请求方式:POST

public function jsApiPlaceOrder()
{
    $params = \request()->all();//请求参数
    /**
    * 应用ID          appid
    * 直连商户号       mchid
    * 商品描述         description
    * 商户订单号       out_trade_no
    * 交易结束时间     time_expire
    * 附加数据         attach
    * 通知地址         notify_url
    * 总金额           total
    * 货币类型         currency  CNY:人民币,境内商户号仅支持人民币。
    * 用户标识         openid
    */
    $data_arr['mchid'] = '直连商户号';
    $data_arr['out_trade_no'] = '商户订单号';
    $data_arr['appid'] = '应用ID';
    $data_arr['description'] = '商品描述';
    $data_arr['notify_url'] = '通知地址';
    $data_arr['attach'] = '附加数据';
    $data_arr['amount'] = [
        'total' => '总金额',
        'currency' => '货币类型',
    ];
    $data_arr['payer'] = [
        // 通过前端传入的jscode换取openid,$get_config 包含了appid,密钥等信息的数组
        'openid' => $this->GetOpenidFromMp($params['code'], $get_config) // 用户标识
    ];
    
    $url_parts = parse_url('https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi');
    $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
    $timestamp = time();
    $nonce = $this->getNonceStr();
    $body = json_encode($data_arr);
    $schema = 'WECHATPAY2-SHA256-RSA2048';
    $sign = $this->getWxRSA256Sign([
        'POST'
        , $canonical_url
        , $timestamp
        , $nonce
        , $body
    ], '商户密钥');

    $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
        '直连商户号', $nonce, $timestamp, '69B99084AF6C21B0D57C152BFE804851327CAAF3', $sign);

    $re = $this->httpRequest(
        'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi'
        , 'POST'
        , $body
        , [
            'Authorization:' . $schema . ' ' . $token
            , 'Accept: application/json'
            , 'Content-Type: application/json'
            , 'User-Agent: '.\request()->header('user-agent', '')
        ]
    );

    $re = json_decode($re,true);
    if(!empty($re) && isset($re['prepay_id'])) {

        $args['timeStamp'] = (string) time();
        $args['nonceStr'] = $this->getNonceStr();
        $args['package'] = 'prepay_id='.$re['prepay_id'];
        $args['signType'] = 'RSA';
        $args['paySign'] = $this->getWxRSA256Sign([
            '应用ID'
            , $args['timeStamp']
            , $args['nonceStr']
            , $args['package']
        ], '商户密钥');

        if($args['paySign']) {
            // 将生成的参数返回给前端,给它调用wx.requestPayment(OBJECT)发起微信支付
            return ['code' => 0, 'msg' => 'success', 'data' => $args];
        } else {
            Log::channel(\request('game'))->info(__CLASS__.';'.__FUNCTION__.";小程序调起支付的签名计算异常", [$args, $get_config]);
            return ['code' => 1, 'msg' => '支付签名计算异常'];
        }

    } else {
        Log::channel(\request('game'))->info(__CLASS__.';'.__FUNCTION__.";prepay_id 结果异常", [$params, $re]);
        return ['code' => 1, 'msg' => '调起支付异常'];
    }
}

/**
 * 小程序调起支付的签名计算
 * @param array $sign_arr
 * @param string $apiclient_key
 * @return string
 */
private function getWxRSA256Sign(array $sign_arr, string $apiclient_key) :string
{

    $message = '';
    foreach ($sign_arr as $value) $message .= $value."\n";

    $pri_key = "-----BEGIN RSA PRIVATE KEY-----\n" .
        wordwrap($apiclient_key, 64, "\n", true) .
        "\n-----END RSA PRIVATE KEY-----";
    $pi_key =openssl_pkey_get_private($pri_key);
    openssl_sign($message, $raw_sign, $pi_key, 'sha256WithRSAEncryption');

    return base64_encode($raw_sign);
}

private function httpRequest($url,$method,$param,$headers,$post_file=false)
{
    $oCurl = curl_init();
    if(stripos($url,"https://")!==FALSE){
        curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
    }

    if (PHP_VERSION_ID >= 50500 && class_exists('\CURLFile')) {
        $is_curlFile = true;
    } else {
        $is_curlFile = false;
        if (defined('CURLOPT_SAFE_UPLOAD')) {
            curl_setopt($oCurl, CURLOPT_SAFE_UPLOAD, false);
        }
    }

    if (is_string($param)) {
        $strPOST = $param;
    } elseif($post_file) {
        if($is_curlFile) {
            foreach ($param as $key => $val) {
                if (substr($val, 0, 1) == '@') {
                    $param[$key] = new \CURLFile(realpath(substr($val,1)));
                }
            }
        }
        $strPOST = $param;
    } else {
        $aPOST = array();
        foreach($param as $key=>$val){
            $aPOST[] = $key."=".urlencode($val);
        }
        $strPOST =  join("&", $aPOST);
    }
    curl_setopt($oCurl, CURLOPT_CUSTOMREQUEST, $method);
    curl_setopt($oCurl, CURLOPT_URL, $url);
    curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );
    curl_setopt($oCurl, CURLOPT_POST,true);
    curl_setopt($oCurl, CURLOPT_POSTFIELDS,$strPOST);
    curl_setopt($oCurl, CURLOPT_HTTPHEADER, $headers);
    $sContent = curl_exec($oCurl);
    $aStatus = curl_getinfo($oCurl);

    $errno = curl_errno($oCurl);
    if($errno) {
        Log::error("curl异常:", [
            'url' => $url,
            'params' => $param,
            'errno' => $errno,
        ]);
    }

    curl_close($oCurl);
    if(intval($aStatus["http_code"])==200){
        return $sContent;
    }else{
        Log::error("curl 不为200:", [
            'url' => $url,
            'params' => $param,
            'errno' => $errno,
            'rtn' => $sContent,
        ]);
        return false;
    }
}

/**
 * 随机字符串,不长于32位。
 * @param int $length
 * @return string
 */
private function getNonceStr(int $length = 32) :string
{
    $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
    $str = '';
    for ($i = 0; $i<$length; $i++) {
        $str .=substr($chars, mt_rand(0, strlen($chars) - 1), 1);
    }
    return $str;
}

getOpenidFromMp() 方法

private function getOpenidFromMp($code, $get_config)
    {
        $url = $this->__CreateOauthUrlForOpenid($code, $get_config);

        //初始化curl
        $ch = curl_init();
        $curlVersion = curl_version();
        $ua = "WXPaySDK/3.0.9 (".PHP_OS.") PHP/".PHP_VERSION." CURL/".$curlVersion['version']." "
            .'1481215812';

        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,FALSE);
        curl_setopt($ch, CURLOPT_USERAGENT, $ua);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

        $proxyHost = "0.0.0.0";
        $proxyPort = 0;
        if($proxyHost != "0.0.0.0" && $proxyPort != 0){
            curl_setopt($ch,CURLOPT_PROXY, $proxyHost);
            curl_setopt($ch,CURLOPT_PROXYPORT, $proxyPort);
        }
        //运行curl,结果以jason形式返回
        $res = curl_exec($ch);

        if (curl_errno($ch)) {
            Log::error("curl异常 MiniAppAutoReply GetOpenidFromMp:", [
                'url' => $url,
                'params' => $code,
                'errno' => curl_errno($ch),
            ]);
        }

        curl_close($ch);
        //取出openid
        $data = json_decode($res,true);

        if (isset($data['openid'])) {
            $openid = $data['openid'];
        } else {
            Log::info(__CLASS__.';'.__FUNCTION__.";请求获取openid 出错,结果为:",[ $data ]);
            $openid = '';
        }
        return $openid;
    }

构造获取open和access_toke的url地址 方法__CreateOauthUrlForOpenid()

    /**
     * 构造获取open和access_toke的url地址
     * @param $code
     * @param array $get_conf
     * @return string 请求的url
     */
    private function __CreateOauthUrlForOpenid($code, $get_conf)
    {
        $urlObj["appid"] = $get_conf['zbAppId'];//应用ID
        $urlObj["secret"] = $get_conf['zbAppSecret'];//商户密钥
        $urlObj["js_code"] = $code;
        $urlObj["grant_type"] = "authorization_code";
        $bizString = $this->ToUrlParams($urlObj);
        //return "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString;
        return "https://api.weixin.qq.com/sns/jscode2session?".$bizString;
    }

拼接签名字符串方法 ToUrlParams()

    /**
     * 拼接签名字符串
     * @param array $urlObj
     * @return string 返回已经拼接好的字符串
     */
    private function ToUrlParams($urlObj)
    {
        $buff = "";
        foreach ($urlObj as $k => $v)
        {
            if($k != "sign"){
                $buff .= $k . "=" . $v . "&";
            }
        }

        $buff = trim($buff, "&");
        return $buff;
    }

调用wx.requestPayment(OBJECT)发起微信支付成功后,支付回调处理

public function payCallBack()
{
    try {
        $params = \request()->all();//请求参数
        /**
         * 微信小程序回调文档 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_5.shtml
         * 微信小程序APIv3支付回调解密  https://blog.csdn.net/qq_16746785/article/details/120260761
         *
         * 支付成功结果通知
         * {
            "id": "EV-2018022511223320873",
            "create_time": "2015-05-20T13:29:35+08:00",
            "resource_type": "encrypt-resource",
            "event_type": "TRANSACTION.SUCCESS",
            "summary": "支付成功",
            "resource": {
                "original_type": "transaction",
                "algorithm": "AEAD_AES_256_GCM",
                "ciphertext": "",
                "associated_data": "",
                "nonce": ""
            }
        }
         */
        if (isset($params['summary']) && $params['summary'] == '支付成功') {
            $nonce_str = $params['resource']['nonce'];
            $associated_data = $params['resource']['associated_data'];
            $cipher_text = base64_decode($params['resource']['ciphertext']);
            if (strlen($cipher_text) <= 12) {
                Log::warning(__CLASS__ .';' . __FUNCTION__ . ";微信小程序APIv3支付回调解密失败 cipher_text <= 12");
            }

            $get_config = $this->config();
            $key = $get_config['zbApiV3Key'];//商户平台设置的api v3 密钥
            // sodium_crypto_aead_aes256gcm_decrypt ( string $ciphertext , string $ad , string $nonce , string $key ) 这个函数。
            //这个函数调用的时候会报错,那是因为使用这个函数需要开启 libsodium 扩展才能使用。需要先自行安装下这个扩展
            $result = json_decode( sodium_crypto_aead_aes256gcm_decrypt($cipher_text, $associated_data, $nonce_str, $key), true );
//                $dd = [
//                    'appid' => $result['appid'],//应用ID
//                    'mchid' => $result['mchid'],//商户号
//                    'out_trade_no' => $result['out_trade_no'],//商户订单号
//                    'transaction_id' => $result['transaction_id'],//微信支付订单号
//                    /**
//                     * 交易类型,枚举值:
//                        JSAPI:公众号支付
//                        NATIVE:扫码支付
//                        APP:APP支付
//                        MICROPAY:付款码支付
//                        MWEB:H5支付
//                        FACEPAY:刷脸支付
//                        示例值:MICROPAY
//                     */
//                    'trade_type' => $result['trade_type'],
//                    'openid' => $result['payer']['openid'],//payer 支付者信息/openid 用户标识
//                    'payer_total' => $result['amount']['payer_total'],//amount 订单金额信息/payer_total 用户支付金额,单位为分
//                    /**
//                     * 交易状态,枚举值:
//                        SUCCESS:支付成功
//                        REFUND:转入退款
//                        NOTPAY:未支付
//                        CLOSED:已关闭
//                        REVOKED:已撤销(付款码支付)
//                        USERPAYING:用户支付中(付款码支付)
//                        PAYERROR:支付失败(其他原因,如银行返回失败)
//                     */
//                    'trade_state' => $result['trade_state'],//交易状态
//                ];
            //Log::debug(__CLASS__ .';' . __FUNCTION__ . ";微信小程序APIv3支付回调解密 resource对象 结果", [$result]);
        }

        if($result['trade_state'] == 'SUCCESS') {
            // TODO 操作发货,更新订单信息等等。。
        } else {
            Log::warning(__CLASS__ .';' . __FUNCTION__ . ";支付通知API 回调结果不是成功的 trade_state:".$result['trade_state']);
        }

    } catch (Exception $e) {
        Log::warning(__CLASS__ .';' . __FUNCTION__ . ";发货异常:" . $e->getMessage() . ";所在行数:" . $e->getLine());
    }

    return ['code' => 'SUCCESS', 'msg' => '成功'];
}

你可能感兴趣的:(微信小程序JSAPI下单 APIv3 PHP)