1. 程式人生 > >微信掃碼支付和微信JSAPI支付

微信掃碼支付和微信JSAPI支付

專案中用到了PC端掃碼支付和 微信公眾號的JSAPI支付,在此記錄, 以免小夥伴被網上的‘拿來主義’給誤導。

使用框架THINKPHP5, 類檔案儲存在extend/payment 資料夾內。

包含功能:掃碼支付(採用先生成預支付訂單,然後返回支付二維碼地址,在頁面上使用qrcode.js 生成二維碼 ),JSAPI支付。

<?php
namespace payment;

use \think\Db;
use yunxin\Yunxin;

/**
 * @author     mselect <[email protected]>
 *
 * @DateTime 2018-11-15 14:53:21
 * 微信支付
 */
class Wxwebpay {

	//金鑰
	private $key;
	//商戶號
	private $mch_id;
	//微信公眾號ID
	private $appid;
	//支付方式ID
	private $payment_id;

	public function __construct(){

		$payment = Db::name('payment')->where('code', 'wxwebpay')->find();
		$json = json_decode($payment['json'], true);

		$this->key = $payment['key'];
		$this->mch_id = $payment['mch_id'];
		$this->appid = $json['appid'];
		$this->payment_id = $payment['id'];
	}

	/**
	 * @author     mselect <
[email protected]
> * * @DateTime 2018-11-15 15:14:29 * 統一下單 * * @param <type> $order_id The order identifier */ public function pay($order_id){ $order = Db::name('order')->where('id', $order_id)->find(); //微信統一下單地址 $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; $notify_url = '非同步回撥地址'; $param = [ 'appid' => $this->appid, 'mch_id' => $this->mch_id, //'device_info' => '', 'nonce_str' => $this->create_nonce_str(), 'sign' => '', //'sign_type' => 'MD5' , 'body' => $order['body'], //'detail' => '', //'attach' => '', 'out_trade_no' => $order['order_unique'], //'fee_type' => 'CNY', 'total_fee' => $order['real_total_fee'] * 100, 'spbill_create_ip' => '***', //'time_start' => '', //'time_expire' => '', //'goods_tag' => '', 'notify_url' => $notify_url, 'trade_type' => 'NATIVE', 'product_id' => $order['order_unique'], //'limit_pay' => '', //'openid' => '', //'scene_info' => '', ]; //組合sign 陣列 $signdata = []; $signdata['appid'] = $param['appid']; $signdata['mch_id'] = $param['mch_id']; $signdata['nonce_str'] = $param['nonce_str']; $signdata['body'] = $param['body']; $signdata['out_trade_no'] = $param['out_trade_no']; $signdata['total_fee'] = $param['total_fee']; $signdata['spbill_create_ip'] = $param['spbill_create_ip']; $signdata['notify_url'] = $param['notify_url']; $signdata['trade_type'] = $param['trade_type']; $signdata['product_id'] = $param['product_id']; //生成sign $sign = $this->get_sign($signdata); $param['sign'] = $sign; $xml = $this->array2xml($param); //訪問介面 $return = $this->curl_xml($xml, $url); //返回xml 轉化成陣列 $back = $this->xml2array($return); if($back['return_code'] == 'SUCCESS'){ if($back['result_code'] == 'SUCCESS'){ //用於生成使用者掃描的二維碼連結 $code_url = $back['code_url']; return ['code' =>1 , 'msg' => '成功' , 'data'=>['code_url'=>$code_url] ]; }else { file_put_contents('wxwebpay.log', 'result code:'.$back['err_code'] . ', result msg:'. $back['err_code_des'] . '\r\n', FILE_APPEND); return ['code' =>-1, 'msg' => '錯誤程式碼:'. $back['err_code'] . ',錯誤描述:' . $back['err_code_des']]; } }else { file_put_contents('wxwebpay.log', 'return code:' . $back['return_code'] . ', return msg:' . $back['return_msg'] . '\r\n' , FILE_APPEND ); return ['code' => -1, 'msg' => '錯誤程式碼:' . $back['return_code'] . ', 錯誤描述:' . $back['return_msg']]; } } /** * @author mselect <
[email protected]
> * * @DateTime 2018-10-09 17:28:49 * 產生隨機字串 * * @param integer $length The length */ private function create_nonce_str($length =32){ $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; $str = ""; for ($i = 0; $i < $length; $i++) { $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); } return $str; } /** * @author mselect <
[email protected]
> * * @DateTime 2018-10-09 17:31:02 * 生成簽名 * * @param <type> $arr The arr */ private function get_sign($arr){ foreach ($arr as $k => $v) { $Parameters[$k] = $v; } //簽名步驟一:按字典序排序引數 ksort($Parameters); $String = $this->formatBizQueryParaMap($Parameters, false); //簽名步驟二:在string後加入KEY $String = $String . "&key=" . $this->key; //簽名步驟三:MD5加密 $String = md5($String); //簽名步驟四:所有字元轉為大寫 $result = strtoupper($String); return $result; } ///作用:格式化引數,簽名過程需要使用 private function formatBizQueryParaMap($paraMap, $urlencode) { $buff = ""; ksort($paraMap); foreach ($paraMap as $k => $v) { if ($urlencode) { $v = urlencode($v); } $buff .= $k . "=" . $v . "&"; } $reqPar; if (strlen($buff) > 0) { $reqPar = substr($buff, 0, strlen($buff) - 1); } return $reqPar; } /** * @author mselect <[email protected]> * * @DateTime 2018-10-10 14:59:32 * 陣列轉為XML */ private function array2xml($arr){ $xml = "<xml>"; foreach ($arr as $key => $val) { if (is_array($val)) { $xml .= "<" . $key . ">" . arrayToXml($val) . "</" . $key . ">"; } else { $xml .= "<" . $key . ">" . $val . "</" . $key . ">"; } } $xml .= "</xml>"; return $xml; } /** * @author mselect <[email protected]> * * @DateTime 2018-10-10 15:00:27 * 訪問介面 */ private function curl_xml($xml, $url, $second =30, $use_cert = false){ $ch = curl_init(); //設定超時 curl_setopt($ch, CURLOPT_TIMEOUT, $second); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //嚴格校驗 //設定header curl_setopt($ch, CURLOPT_HEADER, FALSE); //要求結果為字串且輸出到螢幕上 curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); if($use_cert){ //第一種方法,cert 與 key 分別屬於兩個.pem檔案 //預設格式為PEM,可以註釋 curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); curl_setopt($ch,CURLOPT_SSLCERT, EXTEND_PATH.'/payment/cert/cert.pem'); //預設格式為PEM,可以註釋 curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM'); curl_setopt($ch,CURLOPT_SSLKEY, EXTEND_PATH.'/payment/cert/key.pem'); } //post提交方式 curl_setopt($ch, CURLOPT_POST, TRUE); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20); curl_setopt($ch, CURLOPT_TIMEOUT, 40); set_time_limit(0); //執行curl $return = curl_exec($ch); //返回結果 if($return){ curl_close($ch); return $return; }else { $error = curl_errno($ch); curl_close($ch); return '<xml><return_code>FAIL</return_code><return_msg>'.$error.'</return_msg></xml>'; } } /** * @author mselect <[email protected]> * * @DateTime 2018-10-10 16:26:17 * xml轉為array * * @param <type> $xml The xml * * @return <type> ( description_of_the_return_value ) */ public function xml2array($xml){ libxml_disable_entity_loader(true); $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); $val = json_decode(json_encode($xmlstring), true); return $val; } /** * @author mselect <[email protected]> * * @DateTime 2018-11-15 17:31:47 * 支付結果通知 */ public function notify($data){ $signdata = $data; unset($signdata['sign']); $sign = $this->get_sign($signdata); $time = time(); if($data['sign'] == $sign){ //根據支付唯一碼找到訂單 $order = Db::name('order')->where('order_unique', $data['out_trade_no'])->where('status', -1)->find(); if(!empty($order)){ //查詢訂單狀態 $rst = $this->orderquery($order['id']); if($rst['code'] ==1 ){ //微信返回的訂單狀態為成功 //檢查訂單金額 if($order['real_total_fee']* 100 == $data['total_fee']){ //訂單金額正確 Db::startTrans(); try { //修改訂單狀態 $order_data = []; $order_data['status'] =1; $order_data['pay_time'] = $time; $order_data['trade_no'] = $data['transaction_id']; $order_data['payment_id'] = $this->payment_id; $rst = Db::name('order')->where('id', $order['id'])->update($order_data); if($order['type'] == 1){ //賽事 //修改報名使用者表狀態 $ordermh = Db::name('order_match')->where('order_id',$order['id'])->find(); $rst = Db::name('match_member')->where('id', $ordermh['mhmember_id'])->update(['status'=>1]); $match_member = Db::name('match_member')->where('id', $ordermh['mhmember_id'])->find(); $match = Db::name('match')->where('id', $match_member['match_id'])->find(); //match 表報名總人數+1 $rst = Db::name('match')->where('id', $match['id'])->setInc('order_count'); //新增賽事報名記錄 $log_mm = []; $log_mm['match_id'] = $match_member['match_id']; $log_mm['member_id'] = $match_member['member_id']; $log_mm['create_time'] = $time; $log_mm['content'] = '[賽事]報名賽事:' .$match['title'] . ', 支付成功'; $rst = Db::name('log_match_member')->insertGetId($log_mm); //新增訂單日誌 $log_order = []; $log_order['order_id'] = $order['id']; $log_order['content'] = "[賽事]報名賽事:" . $match['title'] . ', 支付成功'; $log_order['create_time'] = $time; $rst = Db::name('log_order')->insertGetId($log_order); //新增平臺資金日誌 $log_money = []; $log_money['type'] = $order['venue_id'] == -1 ? 2 : 1; $log_money['money'] = $order['real_total_fee']; $log_money['content'] = '[賽事] 使用者ID:'.$order['member_id'].' , 報名賽事:' . $match['title'] . ', 賽事ID:' . $match['id'] . ', 支付:' . $order['real_total_fee']; $log_money['create_time'] = $time; $rst = Db::name('log_money')->insertGetId($log_money); if($order['venue_id'] >0 ){ //新增場館資金日誌 $lvm = []; $lvm['type'] = 2; $lvm['venue_id'] = $order['venue_id']; $lvm['money'] = $order['real_total_fee']; $lvm['content'] = '[賽事] 使用者ID:' . $order['member_id'] . ', 報名賽事:' .$match['title'] . ', 賽事ID:' . $match['id'] . ', 支付:' . $order['real_total_fee']; $lvm['create_time'] = $time; $rst = Db::name('log_venue_money')->insertGetId($lvm); } }else if ($order['type'] == 2){ //代金券 //修改使用者代金券表 $ordercp = Db::name('order_coupon')->where('order_id', $order['id'])->find(); //生成唯一碼 $extra = cmf_create_rand_str(9); $rst = Db::name('coupon_member')->where('id', $ordercp['cpnmember_id'])->update(['status' =>1, 'extra'=>$extra ]); $coupon_member = Db::name('coupon_member')->where('id', $ordercp['cpnmember_id'])->find(); $coupon = Db::name('venue_coupon')->where('id', $coupon_member['coupon_id'])->find(); //新增代金券領取記錄 $log_cm = []; $log_cm['coupon_id'] = $coupon_member['coupon_id']; $log_cm['member_id'] = $coupon_member['member_id']; $log_cm['create_time'] = $time; $log_cm['content'] = '[代金券]購買代金券:' . $coupon['title'] . ', 已支付'; $rst = Db::name('log_coupon_member')->insertGetId($log_cm); //新增訂單記錄 $log_order = []; $log_order['order_id'] = $order['id']; $log_order['content'] = '[代金券]購買代金券:' . $coupon['title'] . ', 已支付' ; $log_order['create_time'] = $time; $rst = Db::name('log_order')->insertGetId($log_order); //新增平臺資金日誌 $log_money = []; $log_money['type'] = 3; $log_money['money'] = $order['real_total_fee']; $log_money['content'] = '[代金券]使用者ID:' . $order['member_id'] . ', 購買代金券:' .$coupon['title'] . ', 代金券ID:' .$coupon['id'] . ', 支付:' . $order['real_total_fee']; $log_money['create_time'] = $time; $rst = Db::name('log_money')->insertGetId($log_money); if($order['venue_id'] > 0 ){ //新增場館資金記錄 $log_vm = []; $log_vm['type'] = 1; $log_vm['money'] = $order['real_total_fee']; $log_vm['content'] = '[代金券]使用者ID:' . $order['member_id'] . ', 購買代金券:' .$coupon['title'] . ', 代金券ID:' .$coupon['id'] . ', 支付:' . $order['real_total_fee']; $log_vm['create_time'] = $time; $rst = Db::name('log_venue_money')->insertGetId($log_vm); } } Db::commit(); }catch(\Exception $e){ Db::rollback(); file_put_contents('wxwebnotify.log', '微信web支付通知資料庫操作錯誤:' . $e->getMessage() . '\r\n', FILE_APPEND); exit; } if($order['type'] ==1 ){ //傳送成功簡訊通知 $yunxin = new Yunxin; $arr = []; $arr[] = $match_member['name']; $arr[] = $match['title']; $arr[] = date("Y年m月d日H:i", $match['mhstart']); $arr[] = $match_member['idno']; $yunxin->sendSMSTemplate( 9294589, [$match_member['telephone']], $arr); } echo '<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml>'; exit; } }else { file_put_contents('wxwebpaynotify.txt', '查詢訂單狀態錯誤\r\n', FILE_APPEND); } }else { file_put_contents('wxwebpaynotify.txt', ' 未找到訂單\r\n' , FILE_APPEND ); } }else { file_put_contents('wxwebpaynotify.txt', '簽名驗證失敗 生成簽名為:' . $sign . '\r\n' , FILE_APPEND ); } } /** * @author mselect <[email protected]> * * @DateTime 2018-11-15 17:33:11 * 查詢訂單 */ public function orderquery($order_id){ $order = Db::name('order')->where('id', $order_id)->find(); //查詢訂單鏈接 $url = "https://api.mch.weixin.qq.com/pay/orderquery"; if(!empty($order['trade_no'])){ $param = [ 'appid' => $this->appid, 'mch_id' => $this->mch_id, 'transaction_id' => $order['trade_no'], 'nonce_str' => $this->create_nonce_str(), 'sign' => '', ]; //組合生成簽名資料 $signdata = []; $signdata['appid'] = $param['appid']; $signdata['mch_id'] = $param['mch_id']; $signdata['transaction_id'] = $param['transaction_id']; $signdata['nonce_str'] = $param['nonce_str']; }else { $param = [ 'appid' => $this->appid, 'mch_id' => $this->mch_id, 'out_trade_no' => $order['order_unique'], 'nonce_str' => $this->create_nonce_str(), 'sign' => '', ]; //組合生成簽名資料 $signdata = []; $signdata['appid'] = $param['appid']; $signdata['mch_id'] = $param['mch_id']; $signdata['out_trade_no'] = $param['out_trade_no']; $signdata['nonce_str'] = $param['nonce_str']; } //生成sign $sign = $this->get_sign($signdata); $param['sign'] = $sign; $xml = $this->array2xml($param); //訪問介面 $return = $this->curl_xml($xml, $url); //返回xml 轉化成陣列 $back = $this->xml2array($return); if($back['return_code'] == 'SUCCESS'){ if($back['result_code'] == 'SUCCESS'){ if($back['trade_state'] != 'SUCCESS' || $back['total_fee'] != $order['real_total_fee']*100 ){ return ['code' => -1, 'msg' => '支付失敗' ]; }else { return ['code' => 1 , 'msg' => '支付成功']; } }else { file_put_contents('wxwebpayquery.log', '錯誤程式碼:'.$back['err_code'] . ', 錯誤資訊:' . $back['err_code_des']); return ['code' => -1, 'msg' => '錯誤程式碼:' . $back['err_code'] . ', 錯誤資訊:' .$back['err_code_des'] ]; } }else { file_put_contents('wxwebpayquery.log', '錯誤程式碼:'.$back['return_code'] . ',錯誤資訊:'.$back['return_msg'] . '\r\n', FILE_APPEND); return ['code' => -1, 'msg' => '錯誤程式碼:' . $back['return_code'] . ', 錯誤資訊:' . $back['return_msg'] ]; } } /** * @author mselect <[email protected]> * * @DateTime 2018-11-21 10:19:00 * 微信JSAPI 支付 * * @param <type> $order_id The order identifier */ public function h5pay($order_id){ $order = Db::name('order')->where('id', $order_id)->find(); //微信統一下單地址 $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; $notify_url = '非同步回撥地址'; $member = Db::name('member')->where('id', $order['member_id'])->find(); $param = [ 'appid' => $this->appid, 'mch_id' => $this->mch_id, //'device_info' => '', 'nonce_str' => $this->create_nonce_str(), 'sign' => '', //'sign_type' => 'MD5' , 'body' => $order['body'], //'detail' => '', //'attach' => '', 'out_trade_no' => $order['order_unique'], //'fee_type' => 'CNY', 'total_fee' => $order['real_total_fee'] * 100, 'spbill_create_ip' => '***', //'time_start' => '', //'time_expire' => '', //'goods_tag' => '', 'notify_url' => $notify_url, 'trade_type' => 'JSAPI', //'product_id' => $order['order_unique'], //'limit_pay' => '', 'openid' => $member['openid'], //'scene_info' => '', ]; //組合sign 陣列 $signdata = []; $signdata['appid'] = $param['appid']; $signdata['mch_id'] = $param['mch_id']; $signdata['nonce_str'] = $param['nonce_str']; $signdata['body'] = $param['body']; $signdata['out_trade_no'] = $param['out_trade_no']; $signdata['total_fee'] = $param['total_fee']; $signdata['spbill_create_ip'] = $param['spbill_create_ip']; $signdata['notify_url'] = $param['notify_url']; $signdata['trade_type'] = $param['trade_type']; //$signdata['product_id'] = $param['product_id']; $signdata['openid'] = $param['openid']; //生成sign $sign = $this->get_sign($signdata); $param['sign'] = $sign; $xml = $this->array2xml($param); //訪問介面 $return = $this->curl_xml($xml, $url); //返回xml 轉化成陣列 $back = $this->xml2array($return); if($back['return_code'] == 'SUCCESS'){ if($back['result_code'] == 'SUCCESS'){ //生成JSAPI呼叫引數 $data = []; $param2 = [ 'appId' => $this->appid, //公眾號ID 'timeStamp' => (string)time(), //時間戳 'nonceStr' => $this->create_nonce_str(), //隨機字串 'package' => 'prepay_id=' . $back['prepay_id'], //訂單詳情擴充套件字串 'signType' => 'MD5', //簽名方式 'paySign' => '', //簽名 ]; //組合簽名資料 $signdata = []; $signdata = [ 'appId' => $param2['appId'], 'timeStamp' => $param2['timeStamp'], 'nonceStr' => $param2['nonceStr'], 'package' => $param2['package'], 'signType' => $param2['signType'], ]; $sign = $this->get_sign($signdata); $param2['paySign'] = $sign; return ['code' =>1 , 'msg' => '成功' , 'data'=>$param2 ]; }else { file_put_contents('wxwebpay.log', 'result code:'.$back['err_code'] . ', result msg:'. $back['err_code_des'] . '\r\n', FILE_APPEND); return ['code' =>-1, 'msg' => '錯誤程式碼:'. $back['err_code'] . ',錯誤描述:' . $back['err_code_des']]; } }else { file_put_contents('wxwebpay.log', 'return code:' . $back['return_code'] . ', return msg:' . $back['return_msg'] . '\r\n' , FILE_APPEND ); return ['code' => -1, 'msg' => '錯誤程式碼:' . $back['return_code'] . ', 錯誤描述:' . $back['return_msg']]; } } } ?>