微信支付类
<?php
namespace extend;
class WechatService
{
private $appid = '';
private $mch_id = '';
private $key = '';
private $secret = '';
private $notify_url = '';
private $sslCert_path = '/data/apiclient_cert.pem';
private $sslKey_path = '/data/apiclient_key.pem';
static $access_token_cache = './access_token_cache.txt';
public function __construct()
{
}
/**
* 微信支付统一下单
* @param string $out_trade_no 商户订单号
* @param array $params [out_trade_no,body,price]必填 [openid,notify_url]选填,例如
* $params['out_trade_no'] = createOrderSn();//商户交易号
* $params['body']='订单支付xxxxxx';//商品描述
* $params['price']=0.01;//价格
* $params['openid']='oycwZ4zZYZEaOj1F4HVf08dJQs68';//openid
* @param string $trade_type JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付
*/
public function wechat_pay($params, $trade_type = 'JSAPI')
{
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$now = time();
if(isset($params['notify_url'])){
$this->notify_url=$params['notify_url'];
}
$data = [
'appid' => $this->appid,
'mch_id' => $this->mch_id,
'nonce_str' => $this->getNonceStr(),
'body' => $params['body'],
'out_trade_no' => $params['out_trade_no'],
'total_fee' => $params['price']*100,
'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
'notify_url' => $this->notify_url,
'sign_type' => 'MD5',
'trade_type' => $trade_type,
];
if ($trade_type == 'JSAPI') {
$data['openid'] = $params['openid'];
}elseif ($trade_type == 'MWEB') {
$data['scene_info'] = '{"h5_info": {"type":"Wap","wap_url": "'.$_SERVER['HTTP_HOST'].'","wap_name": "'.$params['body'].'"}}';
}
$this->sign($data);
$xml = $this->ToXml($data);
if (!$xml) {
return false;
}
$result = $this->postXmlCurl($xml, $url);
if (!$result) {
return false;
}
$return = $this->FromXml($result);
if (!$return) {
return false;
}
if ($trade_type == 'JSAPI') {
$res = [
'appId' => $return['appid'],
'timeStamp' => strval($now),
'nonceStr' => $return['nonce_str'],
'package' => "prepay_id=".$return['prepay_id'],
'signType' => 'MD5',
];
$this->sign($res);
$res['paySign']=$res['sign'];
unset($res['sign']);
return array_merge($res,[
'result_code' => $return['result_code'],
'return_code' => $return['return_code'],
]);
}
return $return;
}
/**
* 签名
* @param array $data 需要签名的数据
*/
private function sign(&$data)
{
ksort($data);
$text = '';
foreach ($data as $key => $val) {
$text == '' ? $text .= $key.'='.$val : $text .= '&'.$key.'='.$val;
}
$data['sign'] = md5($text.'&key='.$this->key);
}
/**
* 输出随机字符串
* @param int $length 字符串长度
*/
private function getNonceStr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str ="";
for ( $i = 0; $i < $length; $i++ ) {
$str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
}
return $str;
}
/**
* 输出xml字符
* @param array $data 需要post的xml数据
*/
private function ToXml($data)
{
if(!is_array($data)
|| count($data) <= 0)
{
return false;
}
$xml = "<xml>";
foreach ($data as $key=>$val)
{
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
/**
* 将xml转为array
* @param string $xml
*/
private function FromXml($xml)
{
if(!$xml){
return false;
}
//将XML转为array
//禁止引用外部xml实体
$disableLibxmlEntityLoader = libxml_disable_entity_loader(true); //添加这句 2018.07.05
$return = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
libxml_disable_entity_loader($disableLibxmlEntityLoader); //添加这句 2018.07.05
return $return;
}
/**
* 以post方式提交xml到对应的接口url
*
* @param string $xml 需要post的xml数据
* @param string $url url
* @param bool $useCert 是否需要证书,默认不需要
* @param int $second url执行超时时间,默认30s
*/
private function postXmlCurl($xml, $url, $method = 'POST', $useCert = false, $second = 30)
{
$ch = curl_init();
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
curl_setopt($ch,CURLOPT_URL, $url);
// curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
// curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
if(stripos($url,"https://")!==FALSE){
//curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
}else{
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
}
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
if($useCert == true){
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLCERT, $this->sslCert_path);
curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLKEY, $this->sslKey_path);
}
//post提交方式
if ($method == 'POST') {
curl_setopt($ch, CURLOPT_POST, TRUE);
}
if (is_array($xml)) {
$xml = json_encode($xml);
}
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//运行curl
$data = curl_exec($ch);
//返回结果
if($data){
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
curl_close($ch);
return false;
}
}
public function orderquery($out_trade_no)
{
// 查询订单状态
$url = 'https://api.mch.weixin.qq.com/pay/orderquery';
$data = [
'appid' => $this->appid,
'mch_id' => $this->mch_id,
'out_trade_no' => $out_trade_no,
'nonce_str' => $this->getNonceStr(),
];
$this->sign($data);
$xml = $this->ToXml($data);
if (!$xml) {
return false;
}
$result = $this->postXmlCurl($xml, $url);
if (!$result) {
return false;
}
$return = $this->FromXml($result);
if (!$return) {
return false;
}
return $return;
}
public function getAccessToken($code) {
if (empty($code)) {
return false;
}
$url = 'https://api.weixin.qq.com/sns/oauth2/access_token';
$data = [
'grant_type' => 'authorization_code',
'appid' => $this->appid,
'secret' => $this->secret,
'code' => $code,
];
$data = json_decode($this->postXmlCurl($data,$url,"GET"),true);
if (!empty($data['access_token']) && !empty($data['openid'])) {
return $data;
} else {
return false;
}
}
public function getUserInfo($access_token,$openid) {
$url = 'https://api.weixin.qq.com/sns/userinfo';
$data = [
'access_token' => $access_token,
'openid' => $openid,
'lang' => 'zh_CN',
];
$data = json_decode($this->postXmlCurl($data,$url,"GET"),true);
if (!empty($data['openid'])) {
return $data;
} else {
return false;
}
}
/**
* 退款
*/
public function refund($out_trade_no,$out_refund_no,$total_fee,$refund_fee) {
$url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
$data = [
'appid' => $this->appid,
'mch_id' => $this->mch_id,
'nonce_str' => $this->getNonceStr(),
'out_trade_no' => $out_trade_no,
'out_refund_no' => $out_refund_no,
'total_fee' => $total_fee,
'refund_fee' => $refund_fee,
];
$this->sign($data);
$xml = $this->ToXml($data);
if (!$xml) {
return false;
}
$result = $this->postXmlCurl($xml, $url, 'POST', true);
if (!$result) {
return false;
}
$return = $this->FromXml($result);
if (!$return) {
return false;
}
return $return;
}
/**
* 服务器端获取AccessToken
*/
public function getAccessTokenServic() {
$data = json_decode(file_get_contents(self::$access_token_cache),true);
if ($data['expire_time']+$data['expires_in'] > time()) {
return $data['access_token'];
}else{
$url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$this->appid.'&secret='.$this->secret.'';
$res = json_decode(file_get_contents($url), true);
if (empty($res['access_token'])) {
return false;
}
$res['expire_time']=time();
file_put_contents(self::$access_token_cache,json_encode($res));
return $res['access_token'];
}
}
// 发送订阅消息
public function sendMessage(){
// 小程序前端需要wx.requestSubscribeMessage,弹框用户授权订阅
$access_token=$this->getAccessTokenServic();
if(!$access_token){
return false;
}
//发送模板消息url
$urls = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=' . $access_token;
//发送内容
$data=[];
$data['touser']='xxx';
$data['template_id']='xxxx';
// $data['page']='';//跳转页面不填则模板无跳转。
// $data['lang']='zh_CN';
$data['data']=[
"phrase1" => [
'value' => '已完成'
],
"time2" => [
'value' => '2023-11-12'
],
"thing3" => [
'value' => '李理2'
],
];
$data['miniprogram_state'] = 'trial';//跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
$result = $this->postXmlCurl($data,$urls);
if ($result)
{
$json = json_decode($result,true);
if (!$json || !empty($json['errcode'])) {
return $json;
}
return true;
}
return false;
}
}
订阅消息发送
$wechatService = new WechatService();
$res = $wechatService->sendMessage();
dd($res);
微信支付
$wechatService = new WechatService();
$data['out_trade_no'] = createOrderSn();//商户交易号
$data['body']='订单支付'.$equipmentInfo['order_sn'];//商品描述
$data['price']=$equipmentInfo['total_price'];//价格
$open_id = model('thirdLogin')->findValue(['user_id' => $this->userId], 'openid');
if(!$open_id){
$this->error('请先授权获取openid');
}
$data['openid']=$open_id;
$data['notify_url']=domain().'/common/public/wechatNotify';//回调
$res = $wechatService->wechat_pay($data);
if ($res['result_code'] == "SUCCESS" && $res['return_code'] == "SUCCESS") {
//更新订单商户交易号
$equipmentModel->updateData(['out_trade_no'=>$data['out_trade_no']],['id'=>$equipmentInfo['id']]);
$this->success($res);
}else{
$this->error('获取支付信息失败');
}
微信回调方法
public function wechatNotify() {
$returnFAIL='<xml><return_code><![CDATA[FAIL]]></return_code></xml>';
$returnSUCCESS='<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>';
$xml = file_get_contents('php://input');
if(!$xml){
echo $returnFAIL;exit;
}
libxml_disable_entity_loader(true);
$msg = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
$wechatService = new WechatService();
$result = $wechatService->orderquery($msg['out_trade_no']);//查询订单
// $result['return_code']='SUCCESS'; //测试数据
// $result['result_code']='SUCCESS';
// $result['trade_state']='SUCCESS';
// $result['total_fee']='7400';
// $result['out_trade_no']='2304058623';
// $result['transaction_id']='1004400740201409030005092168';
// error_log(print_r($result, 1), 3, TMP_PATH . 'log/payWx_' . date('Ymd', time()) . '.log');
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS' && $result['trade_state'] == 'SUCCESS') {
// 更改订单状态
$update=$this->paySuccess($result);
if($update){
echo $returnSUCCESS;exit;
}else{
echo $returnFAIL;exit;
}
} else {
echo $returnFAIL;exit;
}
}
private function paySuccess($params) {
$equipmentModel = model('equipment');
$equipmentInfo = $equipmentModel->findOne(['out_trade_no'=>$params['out_trade_no']],['id,is_pay,total_price']);
if (!$equipmentInfo) {
return false;
}
if ($equipmentInfo['is_pay'] == 1) {
return true;
}
// 金额是否相同
if($params['total_fee']!=$equipmentInfo['total_price']*100){
return false;
}
$now = time();
try {
Db::pdo()->beginTransaction();
$ordersModel = model('equipment');
$updateData['status']=11;//已支付状态
$updateData['pay_id']=1;//在线支付
$updateData['is_pay']=1;//已支付
$updateData['pay_time']=$now;//支付时间
$updateData['transaction_id']=$params['transaction_id'];//第三方平台交易流水号
$orderRes = $ordersModel->updateData($updateData, ['out_trade_no'=>$params['out_trade_no']]);
if($orderRes){
Db::pdo()->commit();
return true;
}else{
Db::pdo()->rollBack();
return false;
}
} catch (\Exception $e) {
return false;
}
}
前端代码
<!DOCTYPE html>
<html>
<head>
<include file="public@head" />
</head>
<php>
$head_no_search = true;
$is_weixin = strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger') !== false ? true : false;
</php>
<body>
<include file="public@nav" />
<main>
<div class="pay_msg_box">
<div class="pay_msg_item1">
<div class="icon gou"></div>
<h4>订单提交成功</h4>
<p class="date">您的订单在<span class="m_color1">24小时</span>内未支付将被取消,
请尽快完成支付!
</p>
<div class="box">
<div class="item">
订单号:
<span class="num">{$order.order_number}</span>
</div>
<div class="item">
订单总额:
<span class="price">¥ {$order.pay_price*1}</span>
</div>
</div>
</div>
<div class="zf_title">选择支付方式</div>
<div class="zf_list">
<div class="zf_item" <if condition="$is_weixin">style="display:none;"</if>>
<div class="name zfb_pay">支付宝支付</div>
<label class="zf_radio">
<input type="radio" name="pay">
<span></span>
</label>
</div>
<div class="zf_item">
<div class="name wx_pay">微信支付</div>
<label class="zf_radio">
<input type="radio" name="pay">
<span></span>
</label>
</div>
<div class="zf_item">
<div class="name yhk_pay">对公付款支付</div>
<label class="zf_radio">
<input type="radio" name="pay">
<span></span>
</label>
</div>
</div>
<a class="pay_btn btn_icon active" href="javascript:;" onclick="submitPay()">立即支付</a>
<!-- 公司信息 -->
<div class="order_gs_msg" style="display: none;">
<p>单位名称:{$site_info.corporate_payment_company}</p>
<p>开户行:{$site_info.corporate_payment_bank}</p>
<p>账号:{$site_info.corporate_payment_account}</p>
</div>
<div class="pay_msg_item2">
<div class="title">温馨提示</div>
<div class="box">
<p>{:htmlspecialchars_decode($site_info.site_pay_tip)}</p>
</div>
</div>
</div>
</main>
<div class="add_cover" style="display: none;">
</div>
<div class="foot_zw"></div>
<include file="public@footer" />
</body>
</html>
<include file="public@script" />
<script>
$(function () {
$('.zf_radio').click(function(){
var index = $(this).parents('.zf_item').index()
var nub = $('.zf_item').length
if(index == nub - 1){
$('.btn_icon').hide()
$('.order_gs_msg').show()
}else{
$('.btn_icon').show()
$('.order_gs_msg').hide()
}
})
})
Wind.use('noty3');
var paycon_loop = null;
var ajax_post = true;
var pay_post = true;
function submitPay() {
var pay_type = 0;
$('.zf_list input').each(function(k,v) {
if ($(v)[0].checked) {
pay_type = k+1
}
})
if (pay_type == 0) {
new Noty({
text: '请选择支付方式',
type: 'error',
layout: 'center',
modal: true,
animation: {
open: 'animated bounceInDown', // Animate.css class names
close: 'animated bounceOutUp', // Animate.css class names
},
timeout: 1.5,
}).show();
return;
}
if (pay_post) {
pay_post = false
} else {
return;
}
var wap = 1;
if (pay_type == 2 && is_weixin) {
wap = 2;
}
$.ajax({
url: "{:url('goods/details/payPost')}",
method: "post",
data: {
id: "{$order.id}",
type: pay_type,
wap: wap,
},
success: function(res) {
pay_post = true
if (res.code == 1) {
if (pay_type == 1) {
$('.add_cover').html(res.data.html);
// document.forms['alipaysubmit'].submit();
document.forms['alipaysubmit'].submit();
if (paycon_loop) {
clearInterval(paycon_loop)
}
paycon_loop = setInterval(function(){
synConfirm(res.data.log_id)
}, 2000);
} else if (pay_type == 2) {
// $('.add_cover').html('<div style="padding-top: 25%;text-align: center;"></div><h1 style="text-align: center;font-size: .5rem;font-weight: 700;">请扫码支付</h1>')
// $('.add_cover').find('div').qrcode({
// render: "canvas",
// width: 250,
// height: 250,
// text: res.data.code_url,
// })
// $('.add_cover').show();
// $('.wx_pay_mask').find('.img_box').html('').qrcode({
// render: "canvas",
// width: 210,
// height: 210,
// text: res.data.code_url,
// })
// $('.wx_pay_mask').show();
if (wap == 1 && res.data.mweb_url) {
var url = res.data.mweb_url+'&redirect_url='+encodeURIComponent(window.location.href)
window.location.href = url
// window.open(res.data.mweb_url)
if (paycon_loop) {
clearInterval(paycon_loop)
}
paycon_loop = setInterval(function(){
synConfirm(res.data.log_id)
}, 2000);
} else if (wap == 2 && res.data) {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId":res.data.appId,
"timeStamp":res.data.timeStamp,
"nonceStr":res.data.nonceStr,
"package":res.data.package,
"signType":res.data.signType,
"paySign":res.data.sign,
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ){
synConfirm(res.data.log_id)
}
});
if (paycon_loop) {
clearInterval(paycon_loop)
}
paycon_loop = setInterval(function(){
synConfirm(res.data.log_id)
}, 2000);
} else {
new Noty({
text: '请求失败,请稍后重新尝试',
type: 'error',
layout: 'center',
modal: true,
animation: {
open: 'animated bounceInDown', // Animate.css class names
close: 'animated bounceOutUp', // Animate.css class names
},
timeout: 1.5
}).show();
return;
}
} else {
new Noty({
text: res.msg,
type: 'success',
layout: 'center',
modal: true,
animation: {
open: 'animated bounceInDown', // Animate.css class names
close: 'animated bounceOutUp', // Animate.css class names
},
timeout: 1.5,
callbacks: {
afterClose: function () {
window.location.href = "{:url('user/user/orderList')}"
}
}
}).show();
return;
}
} else {
new Noty({
text: res.msg,
type: 'error',
layout: 'center',
modal: true,
animation: {
open: 'animated bounceInDown', // Animate.css class names
close: 'animated bounceOutUp', // Animate.css class names
},
timeout: 1.5,
}).show();
return;
}
},
error: function() {
pay_post = true
new Noty({
text: '服务器错误,请联系管理员',
type: 'error',
layout: 'center',
modal: true,
animation: {
open: 'animated bounceInDown', // Animate.css class names
close: 'animated bounceOutUp', // Animate.css class names
},
timeout: 1.5,
}).show();
return;
}
})
}
function synConfirm(log_id) {
if (ajax_post) {
ajax_post = false;
$.ajax({
url:"{:Url('goods/details/ajaxCheckPay')}",
method: "post",
data:{
log_id:log_id,
},
success:function(res){
ajax_post = true
if (res.code == 1) {
clearInterval(paycon_loop)
location.href = "{:url('goods/details/paySuccess',['id'=>$order.id])}"
} else {
}
}
})
}
}
</script>