项目中用到了PC端扫码支付和 微信公众号的JSAPI支付,在此记录, 以免小伙伴被网上的‘拿来主义’给误导。
使用框架THINKPHP5, 类文件保存在extend/payment 文件夹内。
包含功能:扫码支付(采用先生成预支付订单,然后返回支付二维码地址,在页面上使用qrcode.js 生成二维码 ),JSAPI支付。
欢迎访问: http://www.kevink.club 查看更多内容
*
* @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 $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 $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 = "";
foreach ($arr as $key => $val) {
if (is_array($val)) {
$xml .= "<" . $key . ">" . arrayToXml($val) . "" . $key . ">";
} else {
$xml .= "<" . $key . ">" . $val . "" . $key . ">";
}
}
$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 'FAIL '.$error.' ';
}
}
/**
* @author mselect <[email protected]>
*
* @DateTime 2018-10-10 16:26:17
* xml转为array
*
* @param $xml The xml
*
* @return ( 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 '
';
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 $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']];
}
}
}
?>