微信小程序、ThinkPHP5 集成微信支付過程
發(fā)布時間:2024-12-09 閱讀量:260
在微信小程序項目中,需要接入微信支付,用戶在小程序中點擊【付款】,喚起微信支付的彈窗,輸入密碼進行支付,雖然支付流程很簡單,但真正接入的時候難免會遇到一些問題。
項目環(huán)境
客戶端:微信小程序
服務(wù)器:CentOS 7
、PHP 7.0
、Nginx
、MySQL 5.6
后臺框架:ThinkPHP 5
準備工作
- 申請微信小程序賬號,并完成微信支付的事情工作。這些工作按照微信的要求一步步進行即可。
- 配置項目環(huán)境,需要注意的是接口的請求地址需要是
https
,可以到阿里云申請一年免費的SSL
證書,按照Nginx
的配置完成。 - 在微信小程序后臺管理上完成微信支付的申請之后,就可以使用微信郵件給你的賬號密碼登陸商戶平臺
https://pay.weixin.qq.com
。 - 在商戶平臺的【賬戶中心】->【API安全】中下載證書,以及【設(shè)置API密鑰】,這兩部分在代碼配置中需要。
- 下載
SDK
。地址。將lib
目錄下的文件拷貝到項目的extend
下的wxpay
目錄中。 - 配置
extend/wxpay/WxPay.Config.php
,將其中的參數(shù)和上面下載的證書路徑配置好。
微信支付參考文檔:開發(fā)文檔
微信小程序文檔:開發(fā)文檔
具體開發(fā)
創(chuàng)建一個訂單
這一步很簡單,就是常規(guī)的根據(jù)用戶購買信息創(chuàng)建訂單,將數(shù)據(jù)保存到數(shù)據(jù)庫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| * 創(chuàng)建訂單 * * @return void */ public function create() { $order = new OrderModel(); $order['title'] = $title.'-'.date('Ymd'); $order['out_trade_no'] = WxPayConfig::MCHID.time(); $order['amount'] = $amount; $order['status'] = 0; if ($order->save()) { return json_encode(['error' => 0, 'msg' => '創(chuàng)建成功']); } return json_encode(['error' => 1026, 'msg' => '創(chuàng)建失敗']); }
|
喚起支付窗口
在小程序中發(fā)起微信支付請求很簡單,需要注意的是,在調(diào)用wx.requestPayment({})
前需要獲取簽名數(shù)據(jù),如以下的代碼所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| app.requestData('Wxpay/sign', { id: id, openid: openid }, 'GET', function (res) { if (res.error) { app.showToast(res.msg, 'success', 2000) } else { wx.requestPayment({ timeStamp: res.result.timeStamp, nonceStr: res.result.nonceStr, package: res.result.package, signType: res.result.signType, paySign: res.result.paySign, 'success': function (res) { }, 'fail': function (res) { }, 'complete': function (res) { } }) } }, function (res) { app.showToast(res.errMsg, 'success', 2000) })
|
獲取支付簽名參數(shù)
在上一步的請求之前,需要獲取簽名參數(shù),需要注意的是獲取簽名參數(shù)是需要當前微信用戶的openid
的(id
是訂單的id
),先忽略openid
如何獲取,看簽名方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| <?php namespace appapicontroller; use thinkLoader; use appapicontrollerBase; use appcommonmodelOrder as OrderModel; Loader::import('wxpay/WxPay', EXTEND_PATH, '.Api.php');
class Wxpay extends Base { public $data = null; private $curl_timeout = 3000;
* 獲取支付參數(shù) * * @return void */ public function sign() { $id = input('?get.id') ? trim(input('get.id')) : 0; $openid = input('?get.openid') ? trim(input('get.openid')) : 0; if (!$openid) { return json_encode(['error' => 4001, 'msg' => '用戶參數(shù)錯誤']); } $order = OrderModel::get($id); if (!$order) { return json_encode(['error' => 4002, 'msg' => '訂單不存在']); } $input = new WxPayUnifiedOrder(); $input->SetBody($order->title); $input->SetAttach($order->id); $input->SetOut_trade_no($order->out_trade_no); $input->SetTotal_fee(floatval($order->amount) * 100); $input->SetTime_start(date("YmdHis")); $input->SetTime_expire(date("YmdHis", time() + 600)); $input->SetGoods_tag("wx-app"); $input->SetNotify_url(config('wxpay.pay_notify_url')); $input->SetTrade_type("JSAPI"); $input->SetOpenid($openid); $order = WxPayApi::unifiedOrder($input);
$jsApiParameters = $this->GetJsApiParameters($order); if ($jsApiParameters) { return json_encode(['error' => 0, 'msg' => '支付參數(shù)', 'result' => json_decode($jsApiParameters)]); } return json_encode(['error' => 4001, 'msg' => '參數(shù)錯誤']); } }
* * 獲取jsapi支付的參數(shù) * @param array $UnifiedOrderResult 統(tǒng)一支付接口返回的數(shù)據(jù) * @throws WxPayException * * @return json數(shù)據(jù),可直接填入js函數(shù)作為參數(shù) */ private function GetJsApiParameters($UnifiedOrderResult) { if (!array_key_exists("appid", $UnifiedOrderResult) || !array_key_exists("prepay_id", $UnifiedOrderResult) || $UnifiedOrderResult['prepay_id'] == "") { return null; } $jsapi = new WxPayJsApiPay(); $jsapi->SetAppid($UnifiedOrderResult["appid"]); $timeStamp = time(); $jsapi->SetTimeStamp("$timeStamp"); $jsapi->SetNonceStr(WxPayApi::getNonceStr()); $jsapi->SetPackage("prepay_id=" . $UnifiedOrderResult['prepay_id']); $jsapi->SetSignType("MD5"); $jsapi->SetPaySign($jsapi->MakeSign()); $parameters = json_encode($jsapi->GetValues()); return $parameters; }
|
以上就是完整的獲取支付簽名參數(shù)的代碼,需要注意的notify_url
,這個微信支付成功的回調(diào)地址,保證瀏覽器可以正常訪問即可。
支付回調(diào)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| * 微信支付回調(diào) * * @return void */ public function callback() { $xml = file_get_contents("php://input"); $data = WxPayResults::Init($xml); if (!array_key_exists("transaction_id", $data)) { return $this->toXML(['return_code' => 'FAIL', 'return_msg' => '參數(shù)錯誤']); } if (!$data["out_trade_no"] || !$this->Queryorder($data["transaction_id"])) { return $this->toXML(['return_code' => 'FAIL', 'return_msg' => '訂單真實性存疑']); } $order = OrderModel::where(['out_trade_no' => $data["out_trade_no"]])->find(); if (!$order) { return $this->toXML(['return_code' => 'FAIL', 'return_msg' => '訂單不存在']); }
$order->status = 1; if ($order->save()) { return $this->toXML(['return_code' => 'SUCCESS', 'return_msg' => 'OK']); } return $this->toXML(['return_code' => 'FAIL', 'return_msg' => '回調(diào)失敗']); }
* 查詢訂單 * * @param [type] $transaction_id * @return void */ private function Queryorder($transaction_id) { $input = new WxPayOrderQuery(); $input->SetTransaction_id($transaction_id); $result = WxPayApi::orderQuery($input); if(array_key_exists("return_code", $result) && array_key_exists("result_code", $result) && $result["return_code"] == "SUCCESS" && $result["result_code"] == "SUCCESS") { return true; } return false; }
* array轉(zhuǎn)xml * * @param [type] $value * @return void */ public function toXML($values) { if(!is_array($values) || count($values) <= 0) { return "<xml></xml>"; }
$xml = "<xml>"; foreach ($values as $key => $val) { if (is_numeric($val)) { $xml.="<".$key.">".$val."</".$key.">"; }else{ $xml.="<".$key."><![CDATA[".$val."]]></".$key.">"; } } $xml.="</xml>"; return $xml; }
|
這里主要就是在回調(diào)方法中將訂單的狀態(tài)變換一下。
獲取用戶 openid
回到上面提到的簽名方法,在那個方法中需要提供用戶的openid
,獲取的流程在小程序的開發(fā)文檔中有,其次服務(wù)器端的實現(xiàn)在下載的sdk
的sample
中也能夠找到,如以下所示:
小程序端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| wx.login({ success: function (res) { if (res.code) { if (!wx.getStorageSync('openid')) { that.requestData('Wxpay/openid', { code: res.code }, 'GET', function (res) { if (res.error) { that.showToast(res.msg, 'success', 2000) } else { wx.setStorageSync('openid', res.result) } }) } } else { that.showToast("用戶登錄失敗", 'success', 1000) } } })
|
服務(wù)器端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| * 獲取用戶的 openid * * @return thinkResponse */ public function openid() { $code = input('?get.code') ? trim(input('get.code')) : ''; if (!$code) { return json_encode(['error' => 2001, 'msg' => '獲取用戶登錄狀態(tài)失敗']); } $openid = $this->getOpenidFromMp($code); return json_encode(['error' => 0, 'msg' => '獲取用戶登錄狀態(tài)成功', 'result' => $openid]); }
* * 通過code從工作平臺獲取openid機器access_token * @param string $code 微信跳轉(zhuǎn)回來帶上的code * * @return openid */ private function getOpenidFromMp($code) { $url = $this->__CreateOauthUrlForOpenid($code); $ch = curl_init(); curl_setopt($ch, CURLOPT_TIMEOUT, $this->curl_timeout); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,FALSE); curl_setopt($ch, CURLOPT_HEADER, FALSE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 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); } $res = curl_exec($ch); curl_close($ch); $data = json_decode($res, true); $this->data = $data; $openid = $data['openid']; return $openid; }
* * 構(gòu)造獲取open和access_toke的url地址 * @param string $code,微信跳轉(zhuǎn)帶回的code * * @return 請求的url */ private function __CreateOauthUrlForOpenid($code) { $urlObj["appid"] = WxPayConfig::APPID; $urlObj["secret"] = WxPayConfig::APPSECRET; $urlObj["code"] = $code; $urlObj["grant_type"] = "authorization_code"; $bizString = $this->ToUrlParams($urlObj); return "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString; }
* * 拼接簽名字符串 * @param array $urlObj * * @return 返回已經(jīng)拼接好的字符串 */ private function ToUrlParams($urlObj) { $buff = ""; foreach ($urlObj as $k => $v) { if ($k != "sign") { $buff .= $k . "=" . $v . "&"; } } $buff = trim($buff, "&"); return $buff; }
|
退款回調(diào)
以上步驟之后微信支付就已經(jīng)完成了,如果用戶付款了后悔,找到平臺客戶,然后客服在商戶平臺進行退款,這時候微信會有退款結(jié)果通知,在商戶平臺的【交易中心】->【退款配置】中設(shè)置通知url
。
然后在服務(wù)器端完成通知方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| * 退款通知回調(diào) * * @return void */ public function refund() { $xml = file_get_contents("php://input"); $obj = $this->fromXml($xml);
if($obj['return_code'] != 'SUCCESS'){ return $this->toXML(['return_code' => 'FAIL', 'return_msg' => '回調(diào)失敗']); }
$string = $obj['req_info']; $data = base64_decode($string); $md5_key = md5(config('wxpay.key')); $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB), MCRYPT_RAND); $res = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $md5_key, $data, MCRYPT_MODE_ECB, $iv); $res_arr = explode('</root>', $res); $req_info = $this->fromXml($res_arr[0].'</root>'); $order = OrderModel::where(['out_trade_no' => $req_info["out_trade_no"]])->find(); if (!$order) { return $this->toXML(['return_code' => 'FAIL', 'return_msg' => '訂單不存在']); }
$order->status = 2; if ($order->save()) { return $this->toXML(['return_code' => 'SUCCESS', 'return_msg' => 'OK']); } return $this->toXML(['return_code' => 'SUCCESS', 'return_msg' => 'OK']); }
* 將xml轉(zhuǎn)為array * * @param [type] $xml * @return void */ public function fromXml($xml) { if(!$xml){ return []; } libxml_disable_entity_loader(true); $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $values; }
|
在通知中主要是對訂單的狀態(tài)變換了一下,其次解析返回的加密數(shù)據(jù),可進一步存儲退款信息。