微信支付退款
- 微信支付前的准备
- 后台开发
- vue前端
微信支付前的准备
微信支付退款功能需要证书,需要在微信支付平台的账号管理–>api安全中申请证书。
后台开发
话不多说,直接上代码吧,如果想细了解的请看官方文档
微信退款
1.实体类
复制代码
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@ApiModel("微信退款实体类") @Data public class RefundDomain { /* * orderId 商户订单号 * */ @JsonProperty(value = "orderId") @ApiModelProperty(value = "商户订单号",required=true) private String orderId; /* * totalFee 订单金额/退款金额 也可以分为两个字段,退款金额不能大于订单金额 * */ @JsonProperty(value = "money") @ApiModelProperty(value = "订单金额/退款金额",required=true) private Double money; /* * refundAccount 退款资金来源(默认传 "REFUND_SOURCE_UNSETTLED_FUNDS") * */ @JsonProperty(value = "refundAccount") @ApiModelProperty(value = "退款资金来源",required=false) private String refundAccount = "REFUND_SOURCE_UNSETTLED_FUNDS"; }
- 控制层
复制代码
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/** * 申请退款 */ @ApiOperation(value="微信支付退款", notes="微信支付退款功能,前端传用户支付的金额以及当前用户退款的订单号") @PostMapping("/refund") public Map<String, String> refund(@RequestBody RefundDomain refundDomain) { System.out.println("money-------->:"+refundDomain.getMoney()); System.out.println("OrderId-------->:"+refundDomain.getOrderId()); System.out.println("getRefundAccount-------->:"+refundDomain.getRefundAccount()); // 生成唯一退款订单号 Integer out_trade_no = (int) (System.currentTimeMillis() / 1000 + 970516); // 获取退款的金额 微信支付中的金额必须为正整数,所以需要把小数去掉。 Double money = refundDomain.getMoney(); Integer total_fee = money.intValue(); SortedMap<String, Object> params = new TreeMap<String, Object>(); params.put("appid", Parm.APPID); // 微信分配的小程序ID params.put("mch_id", Parm.MCH_ID); // 微信支付分配的商务号 params.put("nonce_str", randomString); // 随机字符串,不长于32位。 params.put("out_trade_no", refundDomain.getOrderId()); //商户订单号和微信订单号二选一(我这里选的是商户订单号) params.put("out_refund_no", out_trade_no); // 商户退款单号 params.put("total_fee", total_fee); // 订单金额 params.put("refund_fee", total_fee); // 退款金额 params.put("refund_account", refundDomain.getRefundAccount()); // 退款资金来源 params.put("sign_type", "MD5"); // 签名类型 String preStr = PayCommonUtil.createLinkString(params); //签名算法 String sign = (PayCommonUtil.sign(preStr, Parm.KEY, "utf-8")).toUpperCase(); System.out.println("sign-------->:"+sign); params.put("sign", sign); Map<String, String> map = new HashMap<>(); try { System.out.println("params-------->:"+params); String xml = PayCommonUtil.getRequestXml(params); System.out.println("xml-------->:"+xml); String xmlStr = PayCommonUtil.doRefund("https://api.mch.weixin.qq.com/secapi/pay/refund", xml); System.out.println("xmlStr-------->:"+xmlStr); map = PayCommonUtil.doXMLParse(xmlStr); } catch (Exception e) { } return map; }
3.微信常量类
复制代码
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
125package com.studies.wx.util; import org.springframework.stereotype.Component; /** * @Description: 微信常量类 */ @Component public class Parm { /** * 微信接入token,自定义但是一定要与公众平台上的保持一致 */ public static final String TOKEN = "微信公众号平台中设置的,小程序不需要"; public static final String APPID = "微信公众号或者小程序平台中开发者模式里面的"; public static final String APPSECRET = "微信公众号或者小程序平台中开发者模式里面的"; public static final String REDIRECT_URI = "http://localhost:8090/PcText(随意写的)"; public static byte [] certData; /* * 商务号api秘银 * */ public static String KEY = "微信支付平台中的秘银"; /* * 微信商务号 * */ public static String MCH_ID = "微信支付平台中给的商务号"; /** * EncodingAESKey 公众平台上面自己填写的43位EncodingAESKey(服务号的) */ public static final String EncodingAESKey = "自己填写的"; /** * 获取access_token的接口地址(GET) 限200(次/天) */ public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; /** * 获取JS_SDK_TICKET */ public static final String JS_SDK_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"; /** * 获取网页授权的access_token的接口地址 */ public static final String OAUTH2_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code"; /** * 自定义菜单删除接口 */ public static final String MENU_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN"; /** * 自定义菜单的创建接口 */ public static final String MENU_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN"; /** * 自定义菜单的查询接口 */ public static final String MENU_GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN"; /** * 客服接口-发消息接口 */ public static final String CUSTOM_SERVICE_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN"; /** * 发送模板消息接口 */ public static final String SEND_TEMPLATE_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN"; /** * 创建标签接口 * * @Method :POST */ public static final String USE_TAG_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/tags/create?access_token=ACCESS_TOKEN"; /** * 获取用户身上的标签 * * @Method:POST */ public static final String GET_INUSER_TAG_URL = "https://api.weixin.qq.com/cgi-bin/tags/getidlist?access_token=ACCESS_TOKEN"; /** * 批量为用户取消标签 * * @Method:POST */ public static final String UNTAGGING_USER_BATCH_URL = "https://api.weixin.qq.com/cgi-bin/tags/members/batchuntagging?access_token=ACCESS_TOKEN"; /** * 创建个性化菜单 * * @Method :POST */ public static final String CREATE_PERSONALIZED_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/addconditional?access_token=ACCESS_TOKEN"; /** * 删除个性化菜单 * * @Method: */ public static final String DELETE_PERSONAL_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/delconditional?access_token=ACCESS_TOKEN"; /** * 给用户打标签的姐接口 */ public static final String CREATE_USERTAG_URL = "https://api.weixin.qq.com/cgi-bin/tags/members/batchtagging?access_token=ACCESS_TOKEN"; /** * 网页授权获取用户详细信息的的接口 */ public static final String GET_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN"; /** * openid获取用户的基本信息的接口 */ public static final String OPENID_USERINFO_URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN"; }
4.工具类
复制代码
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329public class PayCommonUtil { //微信参数配置 public static String API_KEY = Parm.KEY; //随机字符串生成 public static String getRandomString(int length) { //length表示生成字符串的长度 String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } //请求xml组装 public static String getRequestXml(SortedMap<String, Object> parameters) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set es = parameters.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String key = (String) entry.getKey(); Object value = entry.getValue(); if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key) || "sign".equalsIgnoreCase(key)) { sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">"); } else { sb.append("<" + key + ">" + value + "</" + key + ">"); } } sb.append("</xml>"); return sb.toString(); } //xml解析 public static Map doXMLParse(String strxml) throws JDOMException, IOException { strxml = strxml.replaceFirst("encoding=".*"", "encoding="UTF-8""); if (null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if (children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } //生成签名 public static String createSign(String characterEncoding, SortedMap<String, Object> parameters) { StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + API_KEY); System.out.println(sb.toString()); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } /** * 签名字符串 * * @param text 需要签名的字符串 * @param key 密钥 * @param input_charset 编码格式 * @return 签名结果 */ public static String sign(String text, String key, String input_charset) { text = text + "&key=" + key; return DigestUtils.md5Hex(getContentBytes(text, input_charset)); } private static byte[] getContentBytes(String content, String charset) { if (charset == null || "".equals(charset)) { return content.getBytes(); } try { return content.getBytes(charset); } catch (UnsupportedEncodingException e) { throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); } } /** * 验证回调签名 * * @return */ public static boolean isTenpaySign(Map<String, String> map) { String characterEncoding = "utf-8"; String charset = "utf-8"; String signFromAPIResponse = map.get("sign"); if (signFromAPIResponse == null || signFromAPIResponse.equals("")) { System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!"); return false; } System.out.println("服务器回包里面的签名是:" + signFromAPIResponse); //过滤空 设置 TreeMap SortedMap<String, String> packageParams = new TreeMap(); for (String parameter : map.keySet()) { String parameterValue = map.get(parameter); String v = ""; if (null != parameterValue) { v = parameterValue.trim(); } packageParams.put(parameter, v); } StringBuffer sb = new StringBuffer(); Set es = packageParams.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); if (!"sign".equals(k) && null != v && !"".equals(v)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + API_KEY); //将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较 //算出签名 String resultSign = ""; String tobesign = sb.toString(); if (null == charset || "".equals(charset)) { resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase(); } else { try { resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase(); } catch (Exception e) { resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase(); } } String tenpaySign = ((String) packageParams.get("sign")).toUpperCase(); return tenpaySign.equals(resultSign); } //请求方法 public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) { try { URL url = new URL(requestUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // 设置请求方式(GET/POST) conn.setRequestMethod(requestMethod); conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); // 当outputStr不为null时向输出流写数据 if (null != outputStr) { OutputStream outputStream = conn.getOutputStream(); // 注意编码格式 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 从输入流读取返回内容 InputStream inputStream = conn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuffer buffer = new StringBuffer(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } // 释放资源 bufferedReader.close(); inputStreamReader.close(); inputStream.close(); inputStream = null; conn.disconnect(); return buffer.toString(); } catch (ConnectException ce) { System.out.println("连接超时:{}" + ce); } catch (Exception e) { System.out.println("https请求异常:{}" + e); } return null; } /** * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串 * * @param params 需要排序并参与字符拼接的参数组 * @return 拼接后字符串 */ public static String createLinkString(SortedMap<String, Object> params) { List<String> keys = new ArrayList<>(params.keySet()); Collections.sort(keys); String preStr = ""; for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = params.get(key).toString(); if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符 preStr = preStr + key + "=" + value; } else { preStr = preStr + key + "=" + value + "&"; } } return preStr; } public static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if (!children.isEmpty()) { Iterator it = children.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if (!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } /** * 调用微信退款接口 */ public static String doRefund(String url, String data) throws Exception { KeyStore keyStore = KeyStore.getInstance("PKCS12"); //证书格式 try { InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("apiclient_cert.p12"); System.out.println("certStream----->:"+certStream); Parm.certData = IOUtils.toByteArray(certStream); certStream.close(); } catch (Exception e) { e.printStackTrace(); } ByteArrayInputStream is = new ByteArrayInputStream(Parm.certData); try { keyStore.load(is, Parm.MCH_ID.toCharArray()); } finally { is.close(); } // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial( keyStore, Parm.MCH_ID.toCharArray()) .build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER ); CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); try { HttpPost httpost = new HttpPost(url); // 设置响应头信息 httpost.addHeader("Connection", "keep-alive"); httpost.addHeader("Accept", "*/*"); httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); httpost.addHeader("Host", "api.mch.weixin.qq.com"); httpost.addHeader("X-Requested-With", "XMLHttpRequest"); httpost.addHeader("Cache-Control", "max-age=0"); httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) "); httpost.setEntity(new StringEntity(data, "UTF-8")); CloseableHttpResponse response = httpclient.execute(httpost); try { HttpEntity entity = response.getEntity(); String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8"); EntityUtils.consume(entity); return jsonStr; } finally { response.close(); } } finally { httpclient.close(); } } // 获取设备ip public static String getRemoteHost(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip; } }
vue前端
复制代码
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// 微信退款 let param = { orderId:this.data.out_trade_no, // 支付的商户订单号 money:this.data.total_fee, // 用户支付的金额 }; console.log(param); // 前端我使用了axios接口统一管理模式,refundController后台接口 refundController(param).then(ref =>{ console.log(ref); // 如果退款成功 result_code 返回success,如果退款失败 result_code 返回fall if(ref.data.result_code === "SUCCESS"){ uni.showToast({ icon: 'none', title: "退款成功", duration: 2000 }) }else{ uni.showToast({ icon: 'none', title: ref.data.err_code_des, // 退款失败,err_code_des才会返回,退款成功则不会返回这个字段 duration: 2000 }) } })
微信支付退款就到此结束了。
最后
以上就是奋斗金针菇最近收集整理的关于微信支付退款功能的全部内容,更多相关微信支付退款功能内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复