2023微信支付对接全流程

简单说一下微信支付的几种类型的应用场景以及前提条件
官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml

前提条件:
1.需要一个载体公众号或者小程序,公众号要是服务号不是订阅号。
2.小程序和公众号支付都要认证,要300rmb。
3.需要一个商户号,绑定公众号或者小程序。

1.jsapi支付:
就是在微信平台内,微信内置浏览器或者小程序唤起微信支付

对接流程:
在自己的后台请求微信接口下预订单,然后将下单参数各种加密传到前端,用微信浏览器内置对象方法唤起支付WeixinJSBridge.invoke,小程序用
wx.requestPayment唤起支付,用户支付成功后将回调给你在下单参数中的回调地址,你再根据订单号自己处理结果。注意,两个方法的参数名相同,但是小程序支付唤起参数requestPayment下单接口参数中的openid和appid必须用小程序的,微信浏览器的同理。

2.h5支付:
普通浏览器,或者app的webview里面访问下单接口返回的h5_url,可通过访问该url来拉起微信客户端,完成支付。

对接流程:
在自己的后台请求微信接口下预订单,然后接口返回h5_url,前端浏览器访问这个url即可唤起客户端。

3Native下单
生成二维码,用户扫码支付,这个比较适用于PC网站等非移动端。

对接流程:
在自己的后台请求微信接口下预订单,然后接口返回code_url,将这个code_url转换成二维码即可。

以上这几个几乎满足所有需求情况了,上点代码,研究全流程细节和坑点

开发示例
这里jspapi为例
1.申请个公众号 https://mp.weixin.qq.com,必须是服务号不然无法开通支付。
2.在后台通过微信认证,要不然商户号没办法绑定你的公众号

3.申请个商户号 https://pay.weixin.qq.com/
4.商户号绑定公众号,在商户号后台

5.这里开始看后台代码,接口参数缺的我们再找,首先微信支付的加密解密签名太复杂,而且也不是我们关注的点,已经有大佬写好了,IJPay,不仅微信,还封装了、支付宝支付、银联支付常用的支付方式以及各种常用的接口,源码及示例工程gitee :https://gitee.com/luozhizkang/IJPay
也可以引个maven,这里只引微信支付需要的

	<dependency>
        <groupId>com.github.javen205</groupId>
        <artifactId>IJPay-WxPay</artifactId>
        <version>2.9.4</version>
    </dependency>

1
2
3
4
5
6.参数准备类

@Component
@Data
public class WxPayV3Bean {
@Value(“ w x P a y . a p p I d " ) p r i v a t e S t r i n g a p p I d ; / / 小程序 a p p i d @ V a l u e ( " {wxPay.appId}") private String appId ;//小程序appid @Value(" wxPay.appId")privateStringappId;//小程序appid@Value("{wxPay.keyPath}”)
private String keyPath;
@Value(“ w x P a y . c e r t P a t h " ) p r i v a t e S t r i n g c e r t P a t h ; @ V a l u e ( " {wxPay.certPath}") private String certPath ; @Value(" wxPay.certPath")privateStringcertPath;@Value("{wxPay.certP12Path}”)
private String certP12Path ;
@Value(“ w x P a y . p l a t f o r m C e r t P a t h " ) p r i v a t e S t r i n g p l a t f o r m C e r t P a t h ; @ V a l u e ( " {wxPay.platformCertPath}") private String platformCertPath ; @Value(" wxPay.platformCertPath")privateStringplatformCertPath;@Value("{wxPay.mchId}”)
private String mchId;//商户号mchId
@Value(“ w x P a y . a p i K e y " ) p r i v a t e S t r i n g a p i K e y ; @ V a l u e ( " {wxPay.apiKey}") private String apiKey ; @Value(" wxPay.apiKey")privateStringapiKey;@Value("{wxPay.apiKey3}”)
private String apiKey3 ;
@Value(“${wxPay.domain}”)
private String domain;//支付成功回调地址
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
剩下这几个参数怎么获取呢?

在商户平台中按照指引“申请api证书”,会得到几个文件

keyPath就是 apiclient_key.pem的绝对路径,例如:D://xxx/apiclient_key.pem
certPath就是 apiclient_cert.pem的绝对路径
certP12Path就是 apiclient_cert.p12的绝对路径
platformCertPath 等会要根据上面的秘钥和证书生成一个文件名字叫platformCertPath.pem,你只需要将绝对路径写好在这就行了,后面程序启动再通过接口下载。例如:D://xxx/platformCertPath.pem
apiKey 可以不用填。
domain就是支付成功的回调地址,这个回调地址的域名要在商户平台配置。

建个controller

@RestController
@RequestMapping(“/api/v3”)
public class WxPayV3Controller {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final static int OK = 200;

@Resource
WxPayV3Bean wxPayV3Bean;

String serialNo;
String platSerialNo;

@RequestMapping("/getSerialNumber")
@ResponseBody
public String serialNumber() {
	return getSerialNumber();
}

@RequestMapping("/getPlatSerialNumber")
@ResponseBody
public String platSerialNumber() {
	return getPlatSerialNumber();
}

private String getSerialNumber() {
	if (StrUtil.isEmpty(serialNo)) {
		// 获取证书序列号
		X509Certificate certificate = PayKit.getCertificate(wxPayV3Bean.getCertPath());
		if (null != certificate) {
			serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
			// 提前两天检查证书是否有效
			boolean isValid = PayKit.checkCertificateIsValid(certificate, wxPayV3Bean.getMchId(), -2);
			log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
		}

// System.out.println(“输出证书信息:\n” + certificate.toString());
// // 输出关键信息,截取部分并进行标记
// System.out.println(“证书序列号:” + certificate.getSerialNumber().toString(16));
// System.out.println(“版本号:” + certificate.getVersion());
// System.out.println(“签发者:” + certificate.getIssuerDN());
// System.out.println(“有效起始日期:” + certificate.getNotBefore());
// System.out.println(“有效终止日期:” + certificate.getNotAfter());
// System.out.println(“主体名:” + certificate.getSubjectDN());
// System.out.println(“签名算法:” + certificate.getSigAlgName());
// System.out.println(“签名:” + certificate.getSignature().toString());
}
System.out.println(“serialNo:” + serialNo);
return serialNo;
}

private String getPlatSerialNumber() {
	if (StrUtil.isEmpty(platSerialNo)) {
		// 获取平台证书序列号
		X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(wxPayV3Bean.getPlatformCertPath()));
		platSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
	}
	System.out.println("platSerialNo:" + platSerialNo);
	return platSerialNo;
}

private String savePlatformCert(String associatedData, String nonce, String cipherText, String certPath) {
	try {
		AesUtil aesUtil = new AesUtil(wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
		// 平台证书密文解密
		// encrypt_certificate 中的  associated_data nonce  ciphertext
		String publicKey = aesUtil.decryptToString(
			associatedData.getBytes(StandardCharsets.UTF_8),
			nonce.getBytes(StandardCharsets.UTF_8),
			cipherText
		);
		// 保存证书
		FileWriter writer = new FileWriter(certPath);
		writer.write(publicKey);
		// 获取平台证书序列号
		X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
		return certificate.getSerialNumber().toString(16).toUpperCase();
	} catch (Exception e) {
		e.printStackTrace();
		return e.getMessage();
	}
}

@RequestMapping("/get")
@ResponseBody
public String v3Get() {
	// 获取平台证书列表
	try {
		IJPayHttpResponse response = WxPayApi.v3(
			RequestMethodEnum.GET,
			WxDomainEnum.CHINA.toString(),
			OtherApiEnum.GET_CERTIFICATES.toString(),
			wxPayV3Bean.getMchId(),
			getSerialNumber(),
			null,
			wxPayV3Bean.getKeyPath(),
			""
		);

		String timestamp = response.getHeader("Wechatpay-Timestamp");
		String nonceStr = response.getHeader("Wechatpay-Nonce");
		String serialNumber = response.getHeader("Wechatpay-Serial");
		String signature = response.getHeader("Wechatpay-Signature");

		String body = response.getBody();
		int status = response.getStatus();

		log.info("serialNumber: {}", serialNumber);
		log.info("status: {}", status);
		log.info("body: {}", body);
		int isOk = 200;
		if (status == isOk) {
			JSONObject jsonObject = JSONUtil.parseObj(body);
			JSONArray dataArray = jsonObject.getJSONArray("data");
			// 默认认为只有一个平台证书
			JSONObject encryptObject = dataArray.getJSONObject(0);
			JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
			String associatedData = encryptCertificate.getStr("associated_data");
			String cipherText = encryptCertificate.getStr("ciphertext");
			String nonce = encryptCertificate.getStr("nonce");
			String serialNo = encryptObject.getStr("serial_no");
			final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, wxPayV3Bean.getPlatformCertPath());
			log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
		}
		// 根据证书序列号查询对应的证书来验证签名结果
		boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Bean.getPlatformCertPath());
		System.out.println("verifySignature:" + verifySignature);
		return body;
	} catch (Exception e) {
		e.printStackTrace();
		return null;
	}
}

@RequestMapping("/nativePay")
@ResponseBody
public String nativePay() {
	try {
		String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
		UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
			.setAppid(wxPayV3Bean.getAppId())
			.setMchid(wxPayV3Bean.getMchId())
			.setDescription("IJPay 让支付触手可及")
			.setOut_trade_no(PayKit.generateStr())
			.setTime_expire(timeExpire)
			.setAttach("微信系开发脚手架 https://gitee.com/javen205/TNWX")
			.setNotify_url(wxPayV3Bean.getDomain().concat("/v3/payNotify"))
			.setAmount(new Amount().setTotal(1));

		log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
		IJPayHttpResponse response = WxPayApi.v3(
			RequestMethodEnum.POST,
			WxDomainEnum.CHINA.toString(),
			BasePayApiEnum.NATIVE_PAY.toString(),
			wxPayV3Bean.getMchId(),
			getSerialNumber(),
			null,
			wxPayV3Bean.getKeyPath(),
			JSONUtil.toJsonStr(unifiedOrderModel)
		);
		log.info("统一下单响应 {}", response);
		// 根据证书序列号查询对应的证书来验证签名结果
		boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Bean.getPlatformCertPath());
		log.info("verifySignature: {}", verifySignature);
		return response.getBody();
	} catch (Exception e) {
		e.printStackTrace();
		return e.getMessage();
	}
}

@RequestMapping("/jsApiPay")
@ResponseBody
public String jsApiPay(@RequestParam(value = "openId", required = false, defaultValue = "o-_-itxuXeGW3O1cxJ7FXNmq8Wf8") String openId) {
	try {
		String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
		UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
			.setAppid(wxPayV3Bean.getAppId())
			.setMchid(wxPayV3Bean.getMchId())
			.setDescription("IJPay 让支付触手可及")
			.setOut_trade_no(PayKit.generateStr())
			.setTime_expire(timeExpire)
			.setAttach("微信系开发脚手架 https://gitee.com/javen205/TNWX")
			.setNotify_url(wxPayV3Bean.getDomain().concat("/v3/payNotify"))
			.setAmount(new Amount().setTotal(1))
			.setPayer(new Payer().setOpenid(openId));

		log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
		IJPayHttpResponse response = WxPayApi.v3(
			RequestMethodEnum.POST,
			WxDomainEnum.CHINA.toString(),
			BasePayApiEnum.JS_API_PAY.toString(),
			wxPayV3Bean.getMchId(),
			getSerialNumber(),
			null,
			wxPayV3Bean.getKeyPath(),
			JSONUtil.toJsonStr(unifiedOrderModel)
		);
		log.info("统一下单响应 {}", response);
		// 根据证书序列号查询对应的证书来验证签名结果
		boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Bean.getPlatformCertPath());
		log.info("verifySignature: {}", verifySignature);
		if (response.getStatus() == OK && verifySignature) {
			String body = response.getBody();
			JSONObject jsonObject = JSONUtil.parseObj(body);
			String prepayId = jsonObject.getStr("prepay_id");
			//这些参数传到前端去,是小程序或者微信浏览器唤起支付的参数
			Map<String, String> map = WxPayKit.jsApiCreateSign(wxPayV3Bean.getAppId(), prepayId, wxPayV3Bean.getKeyPath());
			log.info("唤起支付参数:{}", map);
			return JSONUtil.toJsonStr(map);
		}
		return JSONUtil.toJsonStr(response);
	} catch (Exception e) {
		e.printStackTrace();
		return e.getMessage();
	}
}

//支付成功回调
@RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
@ResponseBody
public void payNotify(HttpServletRequest request, HttpServletResponse response) {
	Map<String, String> map = new HashMap<>(12);
	try {
		String timestamp = request.getHeader("Wechatpay-Timestamp");
		String nonce = request.getHeader("Wechatpay-Nonce");
		String serialNo = request.getHeader("Wechatpay-Serial");
		String signature = request.getHeader("Wechatpay-Signature");

		log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
		String result = HttpKit.readData(request);
		log.info("支付通知密文 {}", result);

		// 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
		String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
			wxPayV3Bean.getApiKey3(), wxPayV3Bean.getPlatformCertPath());

		log.info("支付通知明文 {}", plainText);

		if (StrUtil.isNotEmpty(plainText)) {
			//完成订单充值
			com.alibaba.fastjson.JSONObject plain = com.alibaba.fastjson.JSONObject.parseObject(plainText);
			orderService.updateOrderStatus(plain.getString("out_trade_no"));

			response.setStatus(200);
			map.put("code", "SUCCESS");
			map.put("message", "SUCCESS");
		} else {
			response.setStatus(500);
			map.put("code", "ERROR");
			map.put("message", "签名错误");
		}
		response.setHeader("Content-type", ContentType.JSON.toString());
		response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
		response.flushBuffer();
	} catch (Exception e) {
		e.printStackTrace();
	}
}

}

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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
上面的代码也是从IJPay的示例工程中copy来的,如果运行有什么问题直接去gitee去复制源码,我上面有贴gitee地址
启动成功后,调用 /get 接口下载 platformCertPath.pem
调用 /jsApiPay 接口生成微信预支付订单,并将参数传到前端,唤起支付,用户支付成功后会回调到 /payNotify 接口,但是回调地址的域名必须在商户后台配置过,在产品中心->开发配置 里面设置业务域名,如果是本地调试,可以用花生壳或者natapp伪装一个域名,建议用natapp。
前端唤起:
微信浏览器

//order是调用/jsApiPay接口返回的参数
WeixinJSBridge.invoke(‘getBrandWCPayRequest’, order,
function(res) {
if (res.err_msg == “get_brand_wcpay_request:ok”) {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。

					} else {
						
					}
				});

1
2
3
4
5
6
7
8
9
10
11
小程序

//order是调用/jsApiPay接口返回的参数
wx.requestPayment({
‘timeStamp’:order.timeStamp,
‘nonceStr’: order.nonceStr,
‘package’: order.package,
‘signType’: order.signType,
‘paySign’: order.paySign,
‘success’:function(res){

        },
        'fail':function(res){
         
        },
      })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
这里一定要记得 /jsApiPay接口的 openid 如果是公众号的方式获取的,wxPayV3Bean.getAppId 也要是公众号的appid,小程序同理。
openid是微信的 相对于载体的用户唯一标识,公众号用户openid获取方式文档:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
用户openid获取简单流程就是
1.引导用户访问
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
scope 为snsapi_base时只能获取appid ,scope为snsapi_userinfo 可以获取用户的具体信息,可以来做微信登录(其他参数自己文档里看)
用授权成功后候回调到 REDIRECT_URI 并带着参数code
2.拿这个code到后台
访问接口
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
获得用户的 appid

以上就是本人对接微信支付的过程记录,如果碰到什么问题欢迎大家留言讨论
————————————————
版权声明:本文为CSDN博主「无法停止思考」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45029496/article/details/128470112

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值