微信小程式支付全問題解決

李益達發表於2018-08-02

這幾天在做小程式的支付,沒有用官方的SDK,這裡就純用官方的文件搞一發。

* 注作者使用的PHP,不過支付流程都是這樣

開發前必讀

主要流程

  • 小程式前端傳送求參請求
  • 接受請求封裝 “統一下單” 獲取package
  • 小程式接受 “統一下單” 獲取的package值帶入wx.requestPayment發起支付請求

準備工具

  • 申請小程式微信支付
  • 拿到小程式微信支付的商戶號及設定祕鑰

注意:小程式就只需要這兩步,如果是web的話還需要設定支付目錄授權域名,文件裡面也有寫的:https://pay.weixin.qq.com/wik…


統一下單

官方文件:https://pay.weixin.qq.com/wik…

/**
 * 統一訂單
 */
public function unifiedorder(){
    // 以下配置是必填項,如有其它需求請自行配置
    $config = array(
        `appid`         =>    `xxxxxxx`,//這裡是小程式appid
        `mch_id`        =>    `xxxxxxx`,//商戶ID
        `nonce_str`     =>    $this->getNonceStr(),//隨機字串
        `body`          =>    `這裡是測試 - 測試`,//請按照文件要求填寫合格名稱
        `out_trade_no`  =>    time().$this->getNonceStr(2),//流水單號
        `total_fee`     =>    `20`,//金額,分為單位,這裡是0.2元
        `spbill_create_ip` => `123.123.123.123`,//當前IP
        `notify_url`    =>    `http://xxxx.com`,//請恕我愚昧,我沒搞懂他有什麼用
        `trade_type`    =>    `JSAPI`,//必須填寫JSAPI
        `openid`        =>    `xxxxxxxx`//當前使用者的openid,在trade_type=JSAPI的時候,此項就變成必填項了
    );
    $config[`sign`] = $this->getSignPay($config);
    $xmlData = $this->ToXml($config);//轉成xml資料
    $postData = $this->http_post($xmlData);
    $arrayData = $this->FromXml($postData);
    if($arrayData[`return_code`] == `SUCCESS` || $arrayData[`result_code`] == `SUCCESS`){
        return $arrayData[`prepay_id`];//重點來了:走了這麼多路,就為了這個值。到這一步就證明成功一多半了。
    }else{
        return $arrayData;//返回錯誤
    }
}

/**
 * 獲取簽名
 */
public function getSignPay($config){
    $key = `xxxxxxx`;//商戶祕鑰,就是自己生成的32位密碼
    $strA = `appid=`.$config[`appid`].`&body=`.$config[`body`].`&mch_id=`.$config[`mch_id`].`&nonce_str=`.$config[`nonce_str`].`&notify_url=`.$config[`notify_url`].`&spbill_create_ip`.$config[`spbill_create_ip`].`&total_fee=`.$config[`total_fee`].`&trade_type=`.$config[`trade_type`];//ASCII 字典序
    $strB = $strA.`&key=`.$key;
    $sign = strtoupper(md5($strB));//大寫MD5
    return $sign;
}

/**
 * 隨機字串 32位
 */
public function getNonceStr($length = 32){
    $chars = "abcdefghijklmnopqrstuvwxyz0123456789";  
    $str ="";
    for ( $i = 0; $i < $length; $i++ )  {  
        $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);  
    } 
    return $str;
}

/**
 * array轉XML
 */
public function ToXml($data){
    if(!is_array($data) || count($data) <= 0){
        throw new WxPayException("陣列資料異常!");
    }
    $xml = "<xml>";
    foreach ($data as $key=>$val){
        $xml.="<".$key.">".$val."</".$key.">";
    }
    $xml.="</xml>";
    return $xml; 
}

/**
 * xml轉array
 */
public function FromXml($xml){    
    if(!$xml){
        throw new WxPayException("xml資料異常!");
    }
    libxml_disable_entity_loader(true);
    $this->values = json_decode(json_encode(simplexml_load_string($xml, `SimpleXMLElement`, LIBXML_NOCDATA)), true);
    return $this->values;
}


/**
 * post 請求
 */
public function http_post($url,$param,$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) == `@`) {

                }
            }
        }
        $strPOST = $param;
    } else {
        $aPOST = array();
        foreach($param as $key=>$val){
            $aPOST[] = $key."=".urlencode($val);
        }
        $strPOST =  join("&", $aPOST);
    }
    curl_setopt($oCurl, CURLOPT_URL, $url);
    curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );
    curl_setopt($oCurl, CURLOPT_POST,true);
    curl_setopt($oCurl, CURLOPT_POSTFIELDS,$strPOST);
    $sContent = curl_exec($oCurl);
    $aStatus = curl_getinfo($oCurl);
    curl_close($oCurl);
    if(intval($aStatus["http_code"])==200){
        return $sContent;
    }else{
        return false;
    }
}

好了現在已經獲取到了 prepay_id 的值,我們的統一下單就算完成了,其實我更樂意叫他資料封裝


小程式微信支付

官方文件:https://developers.weixin.qq….

先來一個插曲,首先我們小程式的前端需要去觸發pay,實現的功能肯定是要點選小程式的一個觸發,然後才能支付對吧,

pay:function(e){
    //這裡面使用post去請求。然後通過我接下來要寫的API支付程式碼獲取小程式支付引數
    success:function(res){
         wx.requestPayment({
             `timeStamp`:toString(res.timeStamp),//這裡轉字串,這裡被坑過,不轉的話可能會出現total_fee為空
             `nonceStr`:toString(res.nonceStr),
             `package`:toString(res.package),
             `signType`:`MD5`,
             `paySign`:toString(res.paySign),
             success:function(res){
                 console.log(res);//這裡可以跳轉到帶參地址
             },
             fail:function(res){
                 console.info(`支付失敗`,res);
             },
             complete:function(){
                 console.info(`支付觸發回撥`,res);
             }
         })  
    }
       
}

api支付

也就是上面小程式程式碼的後端請求地址

/**
 * api組裝資料
 */
public function payApiBlack(){
    $appid = `xxxxxx`;//小程式appid,上面有重複,不過這樣比較直觀
    $timeStamp = time();
    $nonceStr = $this->getNonceStr();//這是呼叫統一下單裡面的方法,為了直觀,我把這些程式碼都寫在了一個類裡
    $package = `prepay_id=`.$this->unifiedorder();
    $signType = `MD5`;
    $key = `xxxxxx`;//這裡是商戶祕鑰,32位,同上面也有
    $strA = `appId=`.$appid.`&nonceStr=`.$nonceStr.`package=`.$package.`&= signType=`.$signType.`&timeStamp=`.$timeStamp.`&key=`.$key;
    $paySign = strtoupper(md5($strA));
    $data = array(
        `appid`=>$appid,
        `timeStamp`=>$timeStamp,
        `nonceStr`=>$nonceStr,
        `package`=>$package,
        `signType`=>$signType
    );
    return $data;//返回給小程式
}

以上就是全部程式碼,還有小程式的支付回撥沒有什麼資訊,所以,我的思路判斷success後進行跳轉帶參

//此程式碼為wx.requestPayment success,部分程式碼省略
//res 回撥引數包括使用者uid及其他重要傳遞
success:function(res){
    wx.redirect({
        url:`pages/pay/done?uid=`+res.uid
    })
}

當然那個統一下單的 notify_url 好像與回撥有關,至於怎麼用,試了幾次回撥的CURD都沒反應,所以有空再研究啦。

以上程式碼僅作為支付流程解釋,所以真正要用到專案上,還是去套官方的SDK吧,畢竟涉及到錢嘛

相關文章