微信掃碼支付和微信JSAPI支付
阿新 • • 發佈:2018-11-30
專案中用到了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']];
}
}
}
?>