微信掃碼支付和微信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']];
		}
	}

}

?>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章