基于WordPress的微信小程序支付功能开发

我在2018年的时候总结过一篇微信小程序支付功能开发与踩坑经验总结,当时因为网上相关文档和资源的缺乏,文章获得了很多关注和转载,并且也有很多人指出了其中的不足。主要不足之处就在于那篇文章把所有的签名字串封装都放到了前端,也就是小程序里,通过JS实现,其中还涉及到了商户key这样的敏感字段,因此是不安全的。不过在3年前微信本身对这块也没有做很严格的限制,比如我把对“https://api.mch.weixin.qq.com/pay/unifiedorder”这个接口的请求放在小程序里,那时候照样是能运行的。

近期,把小程序的基础库改成近期版本后,我发现“https://api.mch.weixin.qq.com/pay/unifiedorder”这个接口的请求已经不能放在小程序里了,即使此域名已经加入到request安全域名下也无效,微信那边会自动把你加入的这个域名过滤掉。这就表示一系列的请求必须放到服务器上完成了。因此我重新整理了一下后端的代码,PHP版本的。并且因为我的微信小程序都是基于WordPress做后端的,索性把自定义的接口这块也缝合过来。

当前最新版调试通过:

打包代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//用于封装各种用得到的字串处理方法
class wx_pay_funcs{
	function postXmlCurl($xml, $url, $useCert = false, $second = 10){
		$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);
		curl_setopt($ch, CURLOPT_HEADER, FALSE);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
		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);
			curl_close($ch);
			return $error;
		}
	}
	//微信支付签名字符串串联
	function toUrlParams($data){
		$buff = "";
		foreach ($data as $k => $v) {
			if($k != "sign" && $v != "" && !is_array($v)){
				$buff .= $k . "=" . $v . "&";
			}
		}
		$buff = trim($buff, "&");
		return $buff;
	}
	//数组转XML
	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;
	}
	//XML转JSON
	function xml_to_json($xmlstring) {
		//XML转数组
		$xmlarray = simplexml_load_string($xmlstring,'SimpleXMLElement',LIBXML_NOCDATA);
		return json_encode($xmlarray,JSON_UNESCAPED_UNICODE);
	}
	//XML转数组
	function xmlToArray($xml){
		if(!$xml){
			// 人工抛出错误
			throw new Exception("xml数据异常!");
		}
		//将XML转为array
		//禁止引用外部xml实体
		libxml_disable_entity_loader(true);
		$this->values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
		return $this->values;
	}
}
 
//以下是调起支付请求的接口URL
//例: https://www.abc.com/wp-json/wx_pay/v1/pay/
add_action( 'rest_api_init', function () {
	register_rest_route( 'wx_pay/v1', '/pay/', array(
		'methods' => 'POST',
		'callback' => 'brain1981_wx_pay',
		'show_in_index' => false, //注意隐藏接口
		'permission_callback' => '__return_true'
	) );
});
function brain1981_wx_pay($request){
	//仅以下4个变量需要自己修改
	$appid="wx..."; //小程序appid
	$appsecret= "avasdfasdf....."; //小程序的secret
	$mch_id="1......"; //商户号id
	$mch_key="abc.........."; //商户号key
 
	$pay_funcs = new wx_pay_funcs;
 
	$total_fee = $request['total_fee']; //支付金额
	$openid = $request['openid']; //用户的Openid
	$order_name = $request['order_name']; //订单名称
	if(empty($total_fee) || empty($openid) || empty($order_name)){
		return "error, loss some parameters.";
	}
 
	$total_fee = $total_fee * 100; //支付金额单位是分
	$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
 
	$data['appid'] = $appid;
	$data['mch_id'] = $mch_id;
	$data['nonce_str'] = md5($mch_id.time());
	$data['openid'] = $openid;
	$data['body'] = $order_name; 
	$data['out_trade_no'] = date("YmdHis").rand(0,1000); //订单号id
	$data['total_fee'] = $total_fee;
	$data['spbill_create_ip'] = '8.8.8.8'; //此参数在小程序中可以随便填个合法的IP地址
	$data['notify_url'] = "https://www.abc.com/wp-json/wx_pay/v1/pay/notify/"; //回调url,用于接收支付完成后,微信返回的数据,其中包含重要信息,后面代码会附上回调处理部分
	$data['trade_type'] = "JSAPI";
 
 
	ksort($data); 
	$sign_str = $pay_funcs->toUrlParams($data);
	$sign_str = $sign_str."&key=".$mch_key;
	$data['sign'] = strtoupper(md5($sign_str));
	$xml = $pay_funcs->arrayToXml($data);
	$r = $pay_funcs->postXmlCurl($xml,$url,true);
	$result = json_decode($pay_funcs->xml_to_json($r));
 
	//如果支付失败,在小程序开发工具中直接输出此行,可查看报错信息
	//return $result;
 
	if($result->return_code == 'SUCCESS'){
		$second_data['appId'] = $appid;
		$second_data['timeStamp'] = time();
		$second_data['nonceStr'] = md5(time().rand().rand().$openid);
		$second_data['package'] = "prepay_id=".$result->prepay_id;
		$second_data['signType'] = "MD5";
 
		ksort($second_data);
		$sign_str = $pay_funcs->toUrlParams($second_data);
		$sign_str = $sign_str."&key=".$mch_key;
		$second_data['paySign'] = strtoupper(md5($sign_str));
		//返回二次签名数据到微信小程序,用于最后的支付请求
		return $second_data;
	}else{
		return "签名错误";
	}
}
 
//以下是完成支付请求的接口URL,用于网站数据处理
//例: https://www.abc.com/wp-json/wx_pay/vi/paid/
add_action( 'rest_api_init', function () {
	register_rest_route( 'wx_pay/v1', '/paid/', array(
		'methods' => 'POST',
		'callback' => 'brain1981_api_wx_pay_paid',
		'show_in_index' => false, //注意隐藏接口
		'permission_callback' => '__return_true'
	) );
});
 
function brain1981_api_wx_pay_paid( $request ){
	if( $request['action']!="paid"){
		return "not a valied request.";
	}
	//此处插入自己的业务代码
	return "success";
}

注1:代码中spbill_create_ip和notify_url这两个参数,在H5支付中用得到,但是在小程序中是可以随便填写的,因为小程序不会直接返回通知页面(需在支付完毕后自己写反馈请求到服务器),也无所谓用户的IP地址(反正都是在微信里)。
注2:如果不能跑通支付,可去掉106行的注释斜杠,在小程序中查看微信官方给出的报错信息以及签名所需字段的实际输出,以便debug。很多权限、小程序配置等低级错误,都能在这里发现。

小程序端的JS代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//发起参数请求,开始支付
  payfee: function(){
    var that = this
    var url = "https://www.abc.com/wp-json/wx_pay/v1/pay/"
    var requestData = {
      openid: openid, //此处请先获得用户openid,方法见官方开发文档
      total_fee: 0.01,
      order_name: "XXX费用-订单"+that.data.project_id //自己业务里的订单名称,自由发挥
    }
 
    wx.request({
      url: url,
      data: requestData,
      method: 'POST',
      success: function (res) {
        if (res.statusCode == 200) {
          var param = {
            "timeStamp": res.data.timeStamp.toString(),
            "package": res.data.package,
            "paySign": res.data.paySign,
            "signType": "MD5",
            "nonceStr": res.data.nonceStr
          }
          console.log("param小程序支付接口参数", param);
          that.processPay(param);
 
        } else {
          //请求失败
          wx.showModal({
            title: '请求失败',
            //content: '',
            showCancel:false
          })
        }
      },
    })
  },
 
  /* 小程序支付 */
  processPay: function (param) {
    var that = this
    wx.requestPayment({
      timeStamp: param.timeStamp,
      nonceStr: param.nonceStr,
      package: param.package,
      signType: param.signType,
      paySign: param.paySign,
      success: function (res) {
        // success
        console.log("wx.requestPayment返回信息",res);
        //提交给服务器信息,添加消息
        that.wxPayAfter()
 
        wx.showModal({
          title: '支付成功',
          content: '您将在“微信支付”官方号中收到支付凭证',
          showCancel: false,
          success: function (res) {
            if (res.confirm) {
              //按下确认
            }
          }
        })
      },
      fail: function (res) {
        console.log("支付失败",res);
      },
      complete: function () {
        //console.log("支付完成(成功或失败都为完成)");
      }
    })
  },//processPay()
 
  //支付完成后,使服务器添加一条完成的消息
  wxPayAfter: function(){
    var that = this
    var url = "https://www.abc.com/wp-json/wx_pay/vi/paid/"
    var requestData = {
      project_id: that.data.project_id,//自己业务里的订单号等,此处请自由发挥
      action:"paid"
    }
    wx.request({
      url: url,
      data: requestData,
      method: 'POST',
      success: function (res) {
        console.log("付款状态更新", res)
      },
    })
  },

自此填坑完毕。如果你不是用WordPress也没关系,其中后端部分稍加修改,去掉两个Rest API的接口相关部分,即可使用。

2022.4.24更新:
微信小程序开发中涉及登录、支付这两块,隔几个月就可能因为微信官方更新接口或权限的原因,使项目出现故障,必须跟进维护。因再次开发小程序用于跑通测试,又调整了本文少量代码,再次亲测可通。

2022.5.2更新:
修改了部分代码,并附上回调处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
add_action( 'rest_api_init', function () {
	register_rest_route( 'wx_pay/v1', '/pay/notify/', array(
		'methods' => 'POST',
		'callback' => 'brain1981_api_wx_pay_notify',
		'show_in_index' => false,
		'permission_callback' => '__return_true'
	) );
});
 
function brain1981_api_wx_pay_notify( $request ){
	$xml = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : file_get_contents("php://input");
	$pay_funcs = new wx_pay_funcs;
 
	//将服务器返回的XML数据转化为数组
	$data = $pay_funcs->xmlToArray($xml);
 
	// 判断签名是否正确  判断支付状态
	if ( ($data['return_code']=='SUCCESS') && ($data['result_code']=='SUCCESS') ) {
		//获取服务器返回的数据
		$order_num = $data['out_trade_no'];         //订单单号
		$openid = $data['openid'];                  //付款人openID
		$total_fee = $data['total_fee'];            //付款金额
		$transaction_id = $data['transaction_id'];  //微信支付流水号
 
		//以下为WordPress业务部分,用于处理WooCommerce订单
		//从订单out_trade_no获取WC的order_id,添加_transaction_id字段,transaction_id是用于退款的重要凭证
		global $wpdb;
		$tbl = $wpdb->prefix.'postmeta';
		$prepare_guery = $wpdb->get_results( "SELECT post_id FROM $tbl where meta_key ='out_trade_no' AND meta_value='".$order_num."'" );
		if (is_array($prepare_guery) && !empty($prepare_guery) && isset($prepare_guery[0])) {
			$order_id = $prepare_guery[0]->post_id;
			$order = wc_get_order( $order_id ); 
			$order->payment_complete($transaction_id);
			$result = 0;
		}
	}else{
	    $result = -1;
	}
	// 返回状态给微信服务器
	if ($result == 0) { // 成功之后不会再回调
		$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
	}else{ // 失败后会继续发送几次回调
		$str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[Failed]]></return_msg></xml>';
	} 
	return $str;
}

本站所有文章均为原创,欢迎转载,请注明文章出处:https://blog.brain1981.com/2354.html。百度和各类采集站皆不可信,搜索请谨慎鉴别。技术类文章一般都有时效性,本人习惯不定期对自己的博文进行修正和更新,因此请访问出处以查看本文的最新版本。

关注我们的微信公众号-JennyStudio 本站记录了近几年的工作中遇到的一些技术问题和解决过程,“作品集”还收录了本人的大部分作品展示。除了本博客外,我们的工作室网站 – JennyStudio,内有更多作品回顾和展示。
您也可以扫描左边的二维码,关注我们的微信公众号,在微信上查看我们的案例。

2 关于 “基于WordPress的微信小程序支付功能开发” 的评论

  1. 大佬,微信小程序我可以给他设置支付立减吗,就是搞活动,别人扫我的小程序的支付码,然后随机会减几块钱,这种。

    回复
    1. Brain 文章作者

      我没做过这个方面的开发,但这肯定不仅仅要做开发的修改,还要开通相应的服务的。我找了下,你可以看看这里https://blog.csdn.net/sunking001/article/details/79824119

      回复

发表评论

您的电子邮箱地址不会被公开。