微信支付和微信退款大致相同,只不过微信支付有一个回调的过程,而退款只需要看返回的状态就可以了。
一、微信小程序支付
主要的步骤:
- 传递相关参数,后台获取预支付id:prepay_id和签名
- 小程序端使用wx.requestPayment来进行支付操作
- 根据后台回调地址中接受的数据进行判断是否支付成功
- 消息订阅,推送支付成功消息
pay: function () {
//尚未开通支付
// wx.showModal({
// title: '提示',
// content: '尚未开通支付',
// success: function (res) {
// wx.switchTab({
// url:'../../index'
// })
// }
// })
// return false
var _this = this
var PHPSESSID = wx.getStorageSync('PHPSESSID')
var orderIds = this.data.orderid
var amount = this.data.money
if (!orderIds || !amount){
app.openPage('member/member?type=1')
}
if (PHPSESSID){
app.reqPost("wxpay", "pay", {
orderIds: orderIds,
order_amount: amount,
appID: config.AppID,
appSecret: config.AppSecret,
PHPSESSID: PHPSESSID
}, function (res) {
console.log('支付完成:')
console.log(res)
var prepay_id = res.data.data.prepay_id
var uid = res.data.data.uid
var pay_time = res.data.data.pay_time
var out_trade_no = res.data.data.out_trade_no //商户订单号,退款需要用到
wx.requestPayment({
'timeStamp': res.data.data.response.timeStamp,
'nonceStr': res.data.data.response.nonceStr,
'package': res.data.data.response.package,
'signType': res.data.data.response.signType,
'paySign': res.data.data.response.paySign,
'success': function (res) {
console.log('下面是支付成功后的打印')
console.log(res)
console.log(prepay_id)
_this.setData({
prepay_id: prepay_id
})
if (prepay_id){
//更新订单状态
app.reqPost("buy", "payresult1", {
out_trade_no: out_trade_no
},function(res){
//_this.submitForm(orderIds, prepay_id)
console.log('更新订单状态后:')
console.log(res)
if(res.data.code == 1000){
wx.showModal({
title: '提示',
content: '支付成功',
success: function (res) {
//消息订阅
var touser = uid
var tmplId = 'xxxx'
var page = 'pages/index/index'
var content = _this.data.orderid
console.log('打印消息订阅参数:')
console.log(touser)
console.log(tmplId)
console.log(page)
console.log(content)
app.toggleSubscribe(touser,tmplId,page,content)
app.openPage('member/mine_order?type=2')
}
})
}else{
wx.showModal({
title: '提示',
content: '支付失败',
success: function (res) {
app.openPage('member/mine_order?type=1')
}
})
}
})
}
},
'fail': function (res) {
wx.showModal({
title: '提示',
content: '支付失败',
success: function (res) {
app.openPage('member/mine_order?type=0')
}
})
}
})
})
}
},
后端获取签名和预支付id
public function pay(){
$app_id=empty($_REQUEST['appID'])?"wx5cf75606efxxx":$_REQUEST['appID'];
$mch_id='xxx'; //商户号
$app_key='xxxx';
$sessionid=$_REQUEST['PHPSESSID'];
$userinfo = Db::name('users')->where('sessionid',$sessionid)->find();
$uid = $userinfo['id'];
$openid = $userinfo['openid'];
// order_info
$orderIds=$_REQUEST['orderIds'];
if(empty($orderIds)){
return json("网络繁忙");
}
$out_trade_no=$this->getRandChar(5)."_".$orderIds; //商户订单号,退款需要用到
//存储到数据库
Db::name('order')->where('id',$orderIds)->update(['out_trade_no'=>$out_trade_no]);
$order_amount=(float)$_REQUEST['order_amount'];
// get prepay id
$prepay_id = $this->generatePrepayId($app_id, $mch_id, $app_key,$out_trade_no,$order_amount,$openid);
$response = array(
'appId' => $app_id,
'timeStamp' => ''.time(),
'nonceStr' => $this->generateNonce(),
'package' => 'prepay_id='.$prepay_id,
'signType' => 'MD5'
);
$response['paySign'] = $this->calculateSign($response, $app_key);
return json(['code'=>'1000','msg'=>'success','data'=>['response'=>$response,'prepay_id'=>$prepay_id,'uid'=>$uid,'pay_time'=>date('Y-m-d H:i:s',time()),'out_trade_no'=>$out_trade_no]]);
}
public function getRandChar($length){
$str = "";
$strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
$max = strlen($strPol)-1;
for($i=0;$i<$length;$i++){
$str.=$strPol[rand(0,$max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
}
return $str;
}
public function generatePrepayId($appId, $mchId, $appKey, $out_trade_no,$order_amount,$openid){
// add sign
$params = array(
'appid' => $appId,
'mch_id' => $mchId,
'openid' => $openid,
'nonce_str' => $this->generateNonce(),
'body' => 'xx小程序商品',
'detail' => "微信支付",
'out_trade_no' => $out_trade_no,
'total_fee' => $order_amount*100,
'spbill_create_ip' => $this->getIP(),
'notify_url' => 'https://xxx/api/Wxpaynotify/index',
'trade_type' => 'JSAPI'
);
$params['sign'] = $this->calculateSign($params, $appKey);
// create xml
$xml = $this->getXMLFromArray($params);
// send request
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => "https://api.mch.weixin.qq.com/pay/unifiedorder",
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_POSTFIELDS => $xml,
));
$result = curl_exec($ch);
curl_close($ch);
// get the prepay id from response
$xml = simplexml_load_string($result);
return (string)$xml->prepay_id;
}
/**
* Generate a nonce string
*
* @link https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=4_3
*/
public function generateNonce(){
return md5(uniqid('', true));
}
/**
* Get a sign string from array using app key
*
* @link https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=4_3
*/
public function calculateSign($arr, $key){
ksort($arr);
$buff = "";
foreach ($arr as $k => $v) {
if ($k != "sign" && $k != "key" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return strtoupper(md5($buff . "&key=" . $key));
}
/**
* Get xml from array
*/
public function getXMLFromArray($arr){
$xml = "<xml>";
foreach ($arr as $key => $val) {
if (is_numeric($val)) {
$xml .= sprintf("<%s>%s</%s>", $key, $val, $key);
} else {
$xml .= sprintf("<%s><![CDATA[%s]]></%s>", $key, $val, $key);
}
}
$xml .= "</xml>";
return $xml;
}
public function getIP() {
if (getenv('HTTP_CLIENT_IP')) {
$ip = getenv('HTTP_CLIENT_IP');
}
elseif (getenv('HTTP_X_FORWARDED_FOR')) {
$ip = getenv('HTTP_X_FORWARDED_FOR');
}
elseif (getenv('HTTP_X_FORWARDED')) {
$ip = getenv('HTTP_X_FORWARDED');
}
elseif (getenv('HTTP_FORWARDED_FOR')) {
$ip = getenv('HTTP_FORWARDED_FOR');
}
elseif (getenv('HTTP_FORWARDED')) {
$ip = getenv('HTTP_FORWARDED');
}
else {
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}
回调
public function index(){
$post = file_get_contents("php://input");
if ($post == null) {
$post = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';
}
if (empty($post) || $post == null || $post == '') {
//阻止微信接口反复回调接口 文档地址 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=7,下面这句非常重要!!!
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
echo $str;
exit('Notify 非法回调');
}
/*****************微信回调返回数据样例*******************
$post = '<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
<sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id>
<trade_type><![CDATA[APP]]></trade_type>
</xml>';
*************************微信回调返回*****************/
libxml_disable_entity_loader(true); //禁止引用外部xml实体
$xml = simplexml_load_string($post, 'SimpleXMLElement', LIBXML_NOCDATA);//XML转数组
$post_data = (array)$xml;
//打印
$this->print_back_pay_result(['msg'=>'打印开始:回调的参数']);
$this->print_back_pay_result($post_data);
$this->print_back_pay_result(['msg'=>'打印结束']);
/** 解析出来的数组
*Array
* {
"appid": "wx5xxxxxx20c9b",
"bank_type": "OTHERS",
"cash_fee": "1",
"fee_type": "CNY",
"is_subscribe": "N",
"mch_id": "1587530151",
"nonce_str": "4f28da5dcf4d91e2c27ec8685f53978d",
"openid": "oZUGc5eMQWLLJ4nYIHV9AEGn4llk",
"out_trade_no": "iL3tS_97",
"result_code": "SUCCESS",
"return_code": "SUCCESS",
"sign": "16B463A5CE8A467F98E0DAFD80510618",
"time_end": "20201012092037",
"total_fee": "1",
"trade_type": "JSAPI",
"transaction_id": "420000072xxx1xxx25776254821"
}
**/
//订单号
$out_trade_no = isset($post_data['out_trade_no']) && !empty($post_data['out_trade_no']) ? $post_data['out_trade_no'] : 0;
//$this->print_back_pay_result($out_trade_no);
//查询订单信息
$order_info = Db::name('order')->where('out_trade_no',$out_trade_no)->find();
$productid = $order_info['class_id'];
$productinfo = Db::name('product')->where('id',$productid)->find();
$this->print_back_pay_result(['msg'=>'打印开始:获取订单信息']);
$this->print_back_pay_result($order_info);
$this->print_back_pay_result(['msg'=>'打印结束']);
if(count($order_info) > 0){
//查询平台信息
//$platform_info = DB::fetch_first("SELECT * FROM pingtaiInfo WHERE `open_pid`= {$order_info['part1']}");
//平台支付key
$wxpay_key = 'xxx';
//接收到的签名
$post_sign = $post_data['sign'];
unset($post_data['sign']);
//重新生成签名
$newSign = $this->MakeSign($post_data,$wxpay_key);
$this->print_back_pay_result(['msg'=>'打印开始:生成签名']);
$this->print_back_pay_result(['new'=>$newSign,'old'=>$post_sign]);
$this->print_back_pay_result(['msg'=>'打印结束']);
//签名统一,则更新数据库
if($post_sign == $newSign){
$updateData = array();
$updateData['pay_time'] = time(); //支付时间
$updateData['status'] = 2; //订单状态-支付完成
//购买的课程数量加一
$updatenum = $productinfo['num'] + 1;
Db::name('product')->where('id',$order_info['class_id'])->update(['num'=>$updatenum]);
//更新order数据库
Db::name('order')->where('out_trade_no',$out_trade_no)->update($updateData);
$this->print_back_pay_result(['msg'=>'打印开始:签名成功']);
$this->print_back_pay_result($updateData);
$this->print_back_pay_result(['msg'=>'打印结束']);
}
}
//阻止微信接口反复回调接口 文档地址 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=7,下面这句非常重要!!!
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
echo $str;
}
public function MakeSign($params,$key){
//签名步骤一:按字典序排序数组参数
ksort($params);
$string = $this->ToUrlParams($params); //参数进行拼接key=value&k=v
//签名步骤二:在string后加入KEY
$string = $string . "&key=".$key;
//签名步骤三:MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
public function ToUrlParams( $params ){
$string = '';
if( !empty($params) ){
$array = array();
foreach( $params as $key => $value ){
$array[] = $key.'='.$value;
}
$string = implode("&",$array);
}
return $string;
}
//写日志-返回码-交易结果
public function print_back_pay_result($data){
//var_dump(self::payback('PAY'));die;
//$data = json_decode($data,true);
//$data = ['date'=>time(),'name'=>'yk','age'=>'27'];
$params = json_encode($data,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
$time = date('Y-m-d h:i:s',time());
$address = self::payback('PAY');
$file_size = filesize($address);
if($file_size > 2097152){
file_put_contents($address,'');
$data = $time.$params;
}
$data = $time.$params;
file_put_contents($address,$data."\r\n",FILE_APPEND);
}
static public function payback($option){
switch ($option){
case 'ROOT_PATH' : return dirname(realpath($_SERVER['SCRIPT_FILENAME'])) . DIRECTORY_SEPARATOR;break;
case 'EXTEND_PATH' : return ROOT_PATH.'extend' . DIRECTORY_SEPARATOR;break;
case 'PAY' : return EXTEND_PATH.'payback/payback.txt';break;
}
return $option;
}
二、退款
具体步骤:
- 小程序端申请退款
- 后台进行退款
小程序申请退款
这里可以有多种写法,主要就是给这个订单的状态改变,然后后台可以接受到退款的申请就行了。
后台
1、证书在微信商户号平台获取,必须是在申请商户号的电脑上下载,否则更改证书,就可以在自己电脑这边下载
2、证书的路径需要写绝对路径,否则会报:58的错误
3、需要传递退款者的openid,这样才能知道退给谁
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2020/10/7 0007
* Time: 15:56
*/
namespace app\admin\controller;
use think\Db;
class WxRefund extends Wxpay
{
protected $SSLCERT_PATH = '/www/wwwroot/text.jcy.com/public/cert/apiclient_cert.pem';//证书路径
protected $SSLKEY_PATH = '/www/wwwroot/text.jcy.com/public/cert/apiclient_key.pem';//证书路径
protected $MCHID = 'xxx';//商户号
protected $APPID = 'xxx';
protected $APPKEY='xxx';
public function refund($orderid){
//对外暴露的退款接口
$result = $this->wxrefundapi($orderid);
return $result;
}
private function wxrefundapi($orderid){
$orderinfo = Db::name('order')->where('id',$orderid)->find();
//通过微信api进行退款流程
$params = array(
'appid'=> $this->APPID,
'mch_id'=> $this->MCHID,
'nonce_str'=> $this->generateNonce(),
'out_refund_no'=> $this->getRandChar(5)."_".$orderid,
'out_trade_no'=> $orderinfo['out_trade_no'],
'total_fee'=> (float)$orderinfo['money'] * 100,
'refund_fee'=> (float)$orderinfo['money'] * 100,
'op_user_id' => $this->MCHID,
);
$params['sign'] = $this->calculateSign($params, $this->APPKEY);
$xmldata = $this->getXMLFromArray($params);
$xmlresult = $this->postXmlSSLCurl($xmldata,'https://api.mch.weixin.qq.com/secapi/pay/refund');
$result = $this->xmlToArray($xmlresult);
return $result;
}
//数组转字符串方法
protected function arrayToXml($arr){
$xml = "<xml>";
foreach ($arr as $key=>$val)
{
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
protected function xmlToArray($xml){
$array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $array_data;
}
//需要使用证书的请求
function postXmlSSLCurl($xml,$url,$second=30)
{
$ch = curl_init();
//超时时间
curl_setopt($ch,CURLOPT_TIMEOUT,$second);
//这里设置代理,如果有的话
//curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
//curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
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);
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
//默认格式为PEM,可以注释
curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLCERT, $this->SSLCERT_PATH);
//默认格式为PEM,可以注释
curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLKEY, $this->SSLKEY_PATH);
//post提交方式
curl_setopt($ch,CURLOPT_POST, true);
curl_setopt($ch,CURLOPT_POSTFIELDS,$xml);
$data = curl_exec($ch);
//返回结果
if($data){
curl_close($ch);
return $data;
}
else {
$error = curl_errno($ch);
echo "curl出错,错误码:$error"."<br>";
curl_close($ch);
return false;
}
}
}
网友评论