Java接入微信支付超级详细教程——从入门到精通
一、准备开发所需的账号以及配置信息
解释:想要接入微信支付我们需要两个玩意 ,一个是公众号/小程序/企业微信(开发用的),一个是微信支付商户(收钱用的)
1、前往:https://mp.weixin.qq.com/ (微信公众平台)注册一个应用,类型只能是:公众号/小程序/企业微信,注意:订阅号不支持微信支付接口,注册完成需要完成”微信认证“(微信需要收取300元)
2、前往:https://pay.weixin.qq.com(微信支付商户平台)注册一个商户,支付成功后的钱就会在这个账号里面
上面两个平台注册完成之后就需要把配置信息拿到了:
图1
1、APPID:应用id也就是 公众号/小程序的ID ,查看 ”图1“
2、Api_key: 应用密钥,查看 ”图1“
3、mch_Id:商户ID (收钱的商家ID),查看 ”图2“
二、准备环境
项目采用SpringBoot
微信支付有两种版本:V3和V2,本文的接入版本为V2
1、导入jar包
1.1微信支付jar包
<!-- 微信支付 SDK -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
微信支付有两种版本:V3和V2,本文的接入版本为V2
1.2导入hutool工具类jar包
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.2</version>
</dependency>
2、设置开发参数
在application.yml,设置号开发参数
pay:
appid: wxea266524343a9 #微信公众号appid
api_key: gwxkjfewfabcrxgrawgs #公众号设置的api密钥
mch_id: 1603131282 #微信商户平台 商户id
3、参数注入到payProperties类中
![image-20220916153709516](微信支付.assets/image-20220916153709516.png
package com.maomao.pay.common;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* 微信支付配置
* @author shuqingyou 2022/9/16
*/
@Data
@Component
@Configuration
@ConfigurationProperties(prefix = "pay")
public class payProperties {
//微信公众号appid
private String appid;
//公众号设置的api密钥
private String api_key;
//#微信商户平台 商户id
private String mch_id;
}
4、微信支付接口Url地址列表
package com.maomao.pay.common.utils;
/**
* 微信支付接口Url列表
* @author shuqingyou 2022/9/16
*/
public class WeChatPayUrl {
//统一下单预下单接口url
public static final String Uifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//订单状态查询接口URL
public static final String Orderquery = "https://api.mch.weixin.qq.com/pay/orderquery";
//订单申请退款
public static final String Refund = "https://api.mch.weixin.qq.com/secapi/pay/refund";
//付款码 支付
public static final String MicroPay = "https://api.mch.weixin.qq.com/pay/micropay";
//微信网页授权 获取“code”请求地址
public static final String GainCodeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize";
//微信网页授权 获取“code” 回调地址
public static final String GainCodeRedirect_uri = "http://i5jmxe.natappfree.cc/boss/WeChatPayMobile/SkipPage.html";
}
5、自定义微信支付工具类
package com.maomao.pay.common.utils;
import org.springframework.util.StringUtils;
import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URL;
/**
* 自定义微信支付工具类
*/
public class WxChatPayCommonUtil {
/**
* 发送 http 请求
* @param requestUrl 请求路径
* @param requestMethod 请求方式(GET/POST/PUT/DELETE/...)
* @param outputStr 请求参数体
* @return 结果信息
*/
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) 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 (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取ip
* @param request 请求
* @return ip 地址
*/
public static String getIp(HttpServletRequest request) {
if (request == null) {
return "";
}
String ip = request.getHeader("X-Requested-For");
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 从流中读取微信返回的xml数据
* @param httpServletRequest
* @return
* @throws IOException
*/
public static String readXmlFromStream(HttpServletRequest httpServletRequest) throws IOException {
InputStream inputStream = httpServletRequest.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
final StringBuffer sb = new StringBuffer();
String line = null;
try {
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
} finally {
bufferedReader.close();
inputStream.close();
}
return sb.toString();
}
/**
* 设置返回给微信服务器的xml信息
* @param returnCode
* @param returnMsg
* @return
*/
public static String setReturnXml(String returnCode, String returnMsg) {
return "<xml><return_code><![CDATA[" + returnCode + "]]></return_code><return_msg><![CDATA[" + returnMsg + "]]></return_msg></xml>";
}
}
三、预下单
本项目返回参数为了方便我都是用的Map(实际开发中禁用),我这是为了方便不想写实体类,嘿嘿…
要完成支付那肯定我们需要先下一笔订单把,没订单客户咋付钱,所以先“预下单”
1、微信支付预下单实体类
官方文档:https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=9_1
package com.maomao.pay.model;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 微信支付预下单实体类
* @author shuqingyou 2022/9/16
*/
@Data
@Accessors(chain = true)
public class WeChatPay {
//返回状态码 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
public String return_code;
//返回信息 当return_code为FAIL时返回信息为错误原因 ,例如 签名失败 参数格式校验错误
private String return_msg;
//公众账号ID 调用接口提交的公众账号ID
private String appid;
//商户号 调用接口提交的商户号
private String mch_id;
//api密钥 详见:https://pay.weixin.qq.com/index.php/extend/employee
private String api_key;
//设备号 自定义参数,可以为请求支付的终端设备号等
private String device_info;
//随机字符串 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 微信返回的随机字符串
private String nonce_str;
//签名 微信返回的签名值,详见签名算法:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
private String sign;
//业务结果 SUCCESS SUCCESS/FAIL
private String result_code;
//错误代码 当result_code为FAIL时返回错误代码,详细参见下文错误列表
private String err_code;
//错误代码描述 当result_code为FAIL时返回错误描述,详细参见下文错误列表
private String err_code_des;
//交易类型 JSAPI JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付 说明详见;https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private String trade_type;
//预支付交易会话标识 微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
private String prepay_id;
//二维码链接 weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00 trade_type=NATIVE时有返回,此url用于生成支付二维码,然后提供给用户进行扫码支付。注意:code_url的值并非固定,使用时按照URL格式转成二维码即可
private String code_url;
//商品描述 商品简单描述,该字段请按照规范传递,具体请见 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private String body;
//商家订单号 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private String out_trade_no;
//标价金额 订单总金额,单位为分,详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private String total_fee;
//终端IP 支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
private String spbill_create_ip;
//通知地址 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
private String notify_url;
//子商户号 sub_mch_id 非必填(商户不需要传入,服务商模式才需要传入) 微信支付分配的子商户号
private String sub_mch_id;
//附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
private String attach;
//商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
private String out_refund_no;
//退款总金额,单位为分,只能为整数,可部分退款。详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=4_2
private String refund_fee;
//退款原因 若商户传入,会在下发给用户的退款消息中体现退款原因 注意:若订单退款金额≤1元,且属于部分退款,则不会在退款消息中体现退款原因
private String refund_desc;
//交易结束时间 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟
private String time_expire;
//用户标识 trade_type=JSAPI,此参数必传,用户在主商户appid下的唯一标识。openid和sub_openid可以选传其中之一,如果选择传sub_openid,则必须传sub_appid。下单前需要调用【网页授权获取用户信息: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 】接口获取到用户的Openid。
private String openid;
}
2、预下单接口封装服务类
解释:
微信支付的交易类型有好几种方式:官方文档(https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2)
JsApi支付(在微信客户端里面调整的支付,场景示例:用户在微信app里面触发的支付就叫 “JsApi”)
Native支付(二维码扫一扫支付,场景示例:电脑网址点击付款生成二维码图片,用户使用”微信扫一扫“功能进 行付款这个就叫 “Native”)
APP支付(手机app跳转到APP支付,场景示例:客户在开发者的app上面点击付款,跳转到微信app进行支 付,支付成功再跳回开发者的app)
MWEB支付(H5支付,场景示例:手机浏览器中的网站,跳转到微信APP进行付款,付款成功,跳回浏览器)
package com.maomao.pay.web.service;
import cn.hutool.core.util.ObjectUtil;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import com.maomao.pay.common.utils.WeChatPayUrl;
import com.maomao.pay.common.utils.WxChatPayCommonUtil;
import com.maomao.pay.model.WeChatPay;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* 微信支付接口封装服务
* @author shuqingyou 2022/9/16
*/
public class WeChatPayService {
private static final DecimalFormat df = new DecimalFormat("#");
/**
* 微信支付统一预下单接口 请查看接口规则 https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=9_1
* shuqingyou 2022/9/16
* @param weChatPay 参数值appid 商户id等等
* @return Map<String, Object> NATIVE支付则返回二维码扫描地址
* @throws Exception
*/
public static Map<String, Object> Unifiedorder(WeChatPay weChatPay) throws Exception {
Map<String, Object> ResultMap = new HashMap<String, Object>();
//todo 创建请求参数
SortedMap<String, String> req = new TreeMap<String, String>();
req.put("appid", weChatPay.getAppid()); //公众号
req.put("mch_id", weChatPay.getMch_id()); // 商户号
req.put("nonce_str", WXPayUtil.generateNonceStr()); // 32位随机字符串
req.put("body", weChatPay.getBody()); // 商品描述
req.put("out_trade_no", weChatPay.getOut_trade_no()); // 商户订单号
req.put("total_fee", df.format(Double.parseDouble(weChatPay.getTotal_fee()) * 100)); // 标价金额(分)
req.put("spbill_create_ip", weChatPay.getSpbill_create_ip()); // 终端IP
req.put("notify_url", weChatPay.getNotify_url()); // 回调地址
req.put("trade_type", weChatPay.getTrade_type()); // 交易类型
req.put("attach", weChatPay.getAttach()); // 签名
if (ObjectUtil.isNotEmpty(weChatPay.getSub_mch_id())) {
//todo 服务商模式
req.put("sub_mch_id", weChatPay.getSub_mch_id());//子商户号 微信支付 分配的子商户号
}
if (ObjectUtil.isNotEmpty(weChatPay.getTime_expire())) {
//todo 设置订单结束时间
//交易结束时间 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。
req.put("time_expire", weChatPay.getTime_expire());
}
if (ObjectUtil.isNotEmpty(weChatPay.getOpenid())) {
//todo JSAPI支付
req.put("openid", weChatPay.getOpenid());//用户标识 trade_type=JSAPI,此参数必传,用户在主商户appid下的唯一标识。openid和sub_openid可以选传其中之一,如果选择传sub_openid,则必须传sub_appid。下单前需要调用【网页授权获取用户信息: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 】接口获取到用户的Openid。
}
req.put("sign", WXPayUtil.generateSignature(req, weChatPay.getApi_key(), WXPayConstants.SignType.MD5)); // 签名
//todo 生成要发送的 xml
String xmlBody = WXPayUtil.generateSignedXml(req, weChatPay.getApi_key());
System.err.println(String.format("微信支付预下单请求 xml 格式:n%s", xmlBody));
//todo 发送 POST 请求 统一下单 API 并携带 xmlBody 内容,然后获得返回接口结果
String result = WxChatPayCommonUtil.httpsRequest(WeChatPayUrl.Uifiedorder, "POST", xmlBody);
System.err.println(String.format("%s", result));
//todo 将返回结果从 xml 格式转换为 map 格式
Map<String, String> WxResultMap = WXPayUtil.xmlToMap(result);
//todo 判断通信状态 此字段是通信标识,非交易标识
if (ObjectUtil.isNotEmpty(WxResultMap.get("return_code")) && WxResultMap.get("return_code").equals("SUCCESS")) {
//todo 业务结果
if (WxResultMap.get("result_code").equals("SUCCESS")) {
//todo 预下单成功
ResultMap.put("code", 0);
ResultMap.put("msg", "预下单成功");
//微信订单号
ResultMap.put("out_trade_no", weChatPay.getOut_trade_no());
switch (WxResultMap.get("trade_type")) {
case "NATIVE":
//二维码地址
ResultMap.put("QrCode", WxResultMap.get("code_url"));
break;
case "MWEB":
//二维码地址
ResultMap.put("mweb_url", WxResultMap.get("mweb_url"));
break;
case "JSAPI":
//预支付交易会话标识 微信生成的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时
ResultMap.put("prepay_id", WxResultMap.get("prepay_id"));
break;
}
} else {
//todo 下单失败
ResultMap.put("code", 2);
ResultMap.put("msg", WxResultMap.get("err_code_des"));
}
} else {
//todo 通信异常
ResultMap.put("code", 2);
ResultMap.put("msg", WxResultMap.get("return_msg"));//当return_code为FAIL时返回信息为错误原因 ,例如 签名失败 参数格式校验错误
}
return ResultMap;
}
}
这样接口层就写好了,接下来可以使用 进行调用了
3、测试调用“预下单”
本次交易类型为:NATIVE-二维码支付
参数疑惑解释:
Notify_url:此参数为回调通知地址(公网必须可以访问),当这笔订单用户支付成功之后,”微信“会异步请求你这个地址告诉你 某个订单支付成功了。这个后面会讲到怎么写这个接口,这里只是简单解释一下
Sub_mch_id:子商户的商户号(真正收钱的商家),这个参数只在”服务商模式“下需要传输,不懂什么是”服务商模式“看下方文章
第一篇:https://kf.qq.com/touch/wxappfaq/1707193mAZRN170719ZfAZRV.html?platform=14
第二篇:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/terms_definition/chapter1_1.shtml#part-2
如果你不是“服务商模式”,这个字段注释掉就行了
package com.maomao.pay;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.github.wxpay.sdk.WXPayUtil;
import com.maomao.pay.common.payProperties;
import com.maomao.pay.model.WeChatPay;
import com.maomao.pay.web.service.WeChatPayService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* 测试类
* @author shuqingyou 2022/9/17
*/
@SpringBootTest
@Slf4j
public class weChatPayTest {
@Resource
private payProperties payProperties;
/**
* 微信支付 统一预下单接口
* 测试接口
*/
@Test
public void createUnifiedorderTest() {
Map<String, Object> ResultMap = new HashMap<String, Object>();
try {
WeChatPay weChatPay = new WeChatPay();
weChatPay.setAppid(payProperties.getAppid())//公众号appid
.setMch_id(payProperties.getMch_id()) // 商户号
.setApi_key(payProperties.getApi_key())//api密钥
.setNonce_str(WXPayUtil.generateNonceStr())// 32位随机字符串
.setBody("小米MIX3 12+568国行陶瓷黑")// 商品描述
.setTotal_fee("0.01") //标价金额
.setOut_trade_no(IdUtil.simpleUUID())// 商户订单号 唯一
.setSpbill_create_ip("10.1.1.10")// 终端IP
.setNotify_url("https://www.baidu.com")//异步回调地址
.setTrade_type("NATIVE") // 交易类型 JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付
//.setSub_mch_id("1609469848")//子商户号 微信支付 分配的子商户号(服务商模式的时候填入)
.setAttach("附加数据NO.1");//附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
ResultMap = WeChatPayService.Unifiedorder(weChatPay);
log.info("返回结果:{}",JSONUtil.toJsonStr(ResultMap));
} catch (Exception e) {
ResultMap.put("code", 2);
ResultMap.put("msg", "系统异常错误代码:" + e.getMessage());
e.printStackTrace();
}
}
}
3.1返回结果
{
"msg":"预下单成功",
"code":0,
"QrCode":"weixin://wxpay/bizpayurl?pr=e7lEkHezz",//二维码地址
"out_trade_no":"a1fa1f29a4e6402296eae4c8323c6120"//商户平台订单号(非微信平台订单号)
}
四、查询订单交易状态
官方文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_2
上面我们已经完成了“下单”操作,那现在我们要来完成“查询订单交易状态”的操作,在我们下单成功之后我们肯定要查询这个订单交易成功了没有对吧,要不然客户也不知道这个订单是支付成功了还是失败了。
1、将下方代码放入 com.maomao.pay.web.service.WeChatPayService中
/**
* 查询微信支付订单状态
* shuqingyou 2022/9/16
* @param weChatPay 参数值appid 商户id等等
* @return 支付状态
*/
public static Map<String, Object> QueryPayStatus(WeChatPay weChatPay) throws Exception {
Map<String, Object> ResultMap = new HashMap<String, Object>();
//todo 创建请求参数
SortedMap<String, String> req = new TreeMap<String, String>();
req.put("appid", weChatPay.getAppid()); // 公众号ID
req.put("mch_id", weChatPay.getMch_id()); // 商户号
req.put("out_trade_no", weChatPay.getOut_trade_no());//商户订单号
req.put("nonce_str", weChatPay.getNonce_str());// 随机字符串
if (ObjectUtil.isNotEmpty(weChatPay.getSub_mch_id())) {
req.put("sub_mch_id", weChatPay.getSub_mch_id());//子商户号 微信支付 分配的子商户号(服务商模式的时候填入)
}
req.put("sign", WXPayUtil.generateSignature(req, weChatPay.getApi_key(), WXPayConstants.SignType.MD5));
//todo 生成要发送的 xml
String xmlBody = WXPayUtil.generateSignedXml(req, weChatPay.getApi_key());
System.err.println(String.format("查询订单支付状态 xml 格式:n%s", xmlBody));
//todo 调用查询订单支付状态 API
String result = WxChatPayCommonUtil.httpsRequest(WeChatPayUrl.Orderquery, "POST", xmlBody);
//todo 返回解析后的 map 数据
Map<String, String> WxResultMap = WXPayUtil.xmlToMap(result);
//todo 判断通信状态 此字段是通信标识,非交易标识
if (ObjectUtil.isNotEmpty(WxResultMap.get("return_code")) && WxResultMap.get("return_code").equals("SUCCESS")) {
//todo 业务结果
if (WxResultMap.get("result_code").equals("SUCCESS")) {
//todo 状态查询成功
ResultMap.put("code", 0);
ResultMap.put("daata", WxResultMap);
switch (WxResultMap.get("trade_state")) {
case "SUCCESS":
ResultMap.put("OrderCode", WxResultMap.get("trade_state"));//订单交易状态code
ResultMap.put("msg", "支付成功");
ResultMap.put("out_trade_no", WxResultMap.get("out_trade_no"));//商户订单号
ResultMap.put("time_end", WxResultMap.get("time_end"));//支付完成时间
ResultMap.put("attach", WxResultMap.get("attach"));//下单时候传过去的附加数据
break;
case "REFUND":
ResultMap.put("msg", "转入退款");
break;
case "NOTPAY":
ResultMap.put("msg", "未支付");
break;
case "CLOSED":
ResultMap.put("msg", "已关闭");
break;
case "REVOKED":
ResultMap.put("msg", "已撤销(刷卡支付)");
break;
case "USERPAYING":
ResultMap.put("msg", "用户支付中");
break;
case "PAYERROR":
ResultMap.put("msg", "支付失败(其他原因,如银行返回失败)");
break;
case "ACCEPT":
ResultMap.put("msg", "已接收,等待扣款");
break;
}
} else {
//todo 下单失败
ResultMap.put("code", 2);
ResultMap.put("msg", WxResultMap.get("err_code_des"));
}
} else {
//todo 通信异常
ResultMap.put("code", 2);
ResultMap.put("msg", WxResultMap.get("return_msg"));//当return_code为FAIL时返回信息为错误原因 ,例如 签名失败 参数格式校验错误
}
System.out.println(String.format("%s", WxResultMap));
return ResultMap;
}
2、调用“查询交易状态”接口
将下方代码放入com.maomao.pay.weChatPayTest,运行
/**
* 查询微信订单交易状态
*/
@Test
public void QueryPayStatus() {
Map<String, Object> ResultMap = new HashMap<String, Object>();
try {
//todo // 商户订单号 唯一
String out_trade_no = "a1fa1f29a4e6402296eae4c8323c6120";
WeChatPay weChatPay = new WeChatPay();
weChatPay.setAppid(payProperties.getAppid())//公众号appid
.setMch_id(payProperties.getMch_id()) // 商户号
.setApi_key(payProperties.getApi_key())//api密钥
.setNonce_str(WXPayUtil.generateNonceStr())// 32位随机字符串
.setOut_trade_no(out_trade_no);// 商户订单号 唯一
//.setSub_mch_id("1603126310");//子商户号 微信支付 分配的子商户号(服务商模式的时候填入)
/**查询订单交易状态**/
ResultMap=WeChatPayService.QueryPayStatus(weChatPay);
log.info("返回结果:{}",JSONUtil.toJsonStr(ResultMap));
} catch (Exception e) {
e.printStackTrace();
}
}
2.1返回结果
{
"msg":"未支付",
"code":0,
"daata":{
"nonce_str":"w1EBrnqCnoZl0aWM",
"trade_state":"NOTPAY",
"sign":"3BC73DB4205FBA7F835FD534C921BC2F",
"return_msg":"OK",
"mch_id":"1603076382",
"sub_mch_id":"1603126310",
"sub_appid":"wx609af6beda27e69d",
"device_info":"",
"out_trade_no":"a1fa1f29a4e6402296eae4c8323c6120",
"appid":"wxea266a95de9e87a9",
"total_fee":"1",
"trade_state_desc":"订单未支付",
"result_code":"SUCCESS",
"return_code":"SUCCESS"
}
}
五、前端整合调用支付
效果图:
1、接口代码
package com.maomao.pay.web.controller;
import cn.hutool.core.util.IdUtil;
import com.github.wxpay.sdk.WXPayUtil;
import com.maomao.pay.common.payProperties;
import com.maomao.pay.common.utils.WxChatPayCommonUtil;
import com.maomao.pay.model.WeChatPay;
import com.maomao.pay.web.service.WeChatPayService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* 微信支付控制层
* @author shuqingyou 2022/9/17
*/
@RestController
@RequestMapping(value = "/WeiXinPayController")
@Slf4j
public class WeiXinPayController {
@Resource
private payProperties payProperties;
/**
* 微信支付 统一预下单接口
* @author shuqingyou 2022/9/17
*/
@RequestMapping(value = "/createNative")
public Map<String, Object> createNative(HttpServletRequest request) {
Map<String, Object> ResultMap = new HashMap<String, Object>();
try {
WeChatPay weChatPay = new WeChatPay();
weChatPay.setAppid(payProperties.getAppid())//公众号appid
.setMch_id(payProperties.getMch_id()) // 商户号
.setApi_key(payProperties.getApi_key())//api密钥
.setNonce_str(WXPayUtil.generateNonceStr())// 32位随机字符串
.setBody("小米MIX3 12+568国行陶瓷黑")// 商品描述
.setTotal_fee("0.01") //标价金额
.setOut_trade_no(IdUtil.simpleUUID())// 商户订单号 唯一
.setSpbill_create_ip(WxChatPayCommonUtil.getIp(request))// 终端IP
.setNotify_url("https://www.baidu.com")//异步回调地址
.setTrade_type("NATIVE") // 交易类型 JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付
.setSub_mch_id("1609469848")//子商户号 微信支付 分配的子商户号(服务商模式的时候填入)
.setAttach("shuqingyou");//附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
return WeChatPayService.Unifiedorder(weChatPay);
} catch (Exception e) {
ResultMap.put("code", 2);
ResultMap.put("msg", "系统异常错误代码:" + e.getMessage());
e.printStackTrace();
}
return ResultMap;
}
/**
* 查询微信订单交易状态
* @author shuqingyou 2022/9/17
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/QueryPayStatus")
@ResponseBody
public Map<String, Object> QueryPayStatus(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> ResultMap = new HashMap<String, Object>();
try {
//todo // 商户订单号 唯一
String out_trade_no = request.getParameter("out_trade_no");
WeChatPay weChatPay = new WeChatPay();
weChatPay.setAppid(payProperties.getAppid())//公众号appid
.setMch_id(payProperties.getMch_id()) // 商户号
.setApi_key(payProperties.getApi_key())//api密钥
.setNonce_str(WXPayUtil.generateNonceStr())// 32位随机字符串
.setOut_trade_no(out_trade_no)// 商户订单号 唯一
.setSub_mch_id("1609469848");//子商户号 微信支付 分配的子商户号(服务商模式的时候填入)
/**查询订单交易状态**/
return WeChatPayService.QueryPayStatus(weChatPay);
} catch (Exception e) {
ResultMap.put("code", 2);
ResultMap.put("msg", "系统异常错误代码:" + e.getMessage());
e.printStackTrace();
}
return ResultMap;
}
}
2、前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>二维码付款</title>
<script type="text/javascript" src="js/jquery-1.11.2.min.js"></script>
<script type="text/javascript" src="js/jquery.qrcode.min.js"></script>
</head>
<body>
<a href="javascript:void(0);" onclick="WeChatPay()">点击生成付款二维码(微信)</a>
<div id="Orcode_div" style="height: 165px;width: 165px"></div>
</body>
<script type="application/javascript">
var basePath="http://127.0.0.1:1688"
//商户订单号
var outTradeNo;
//定时任务
var WeChatPayTrade;
//记录是否通知页面“用户已扫码”
var WeChatPayFindNumber = true
//微信商家唯一订单编号
var WeChatPayOut_trade_no;
/**
* 微信支付分割线
* sqy 2022/9/16
*/
//支付宝预下单
function WeChatPay() {
$.ajax({
url: basePath + '/WeiXinPayController/createNative',
method: 'post',
async: false, //是否异步请求 默认truefalse为同步请求 ,true为异步请求)
dataType: 'JSON',
success: function (res) {
if (res.code == 0) {
//商户订单编号
WeChatPayOut_trade_no = res.out_trade_no;
//创建订单二维码
createQrcode(res.QrCode);
} else {
alert(res.msg)
}
}
})
}
/**
* 查询微信交易状态
*/
function findWeChatPay_trade() {
WeChatPayTrade = setInterval(function () {
console.log("每3秒执行一次");
$.ajax({
url: basePath + '/WeiXinPayController/QueryPayStatus',
method: 'post',
async: false, //是否异步请求 默认truefalse为同步请求 ,true为异步请求)
data: {
"out_trade_no": WeChatPayOut_trade_no
},
dataType: 'JSON',
success: function (res) {
if (res.code == 0 && res.OrderCode == "USERPAYING") {
//订单已经创建但未支付(用户扫码后但是未支付)
if (WeChatPayFindNumber) {
console.log("用户已扫码但是未支付");
WeChatPayFindNumber = false;
}
} else if (res.code == 0 && res.OrderCode == "SUCCESS") {
//阻止定时
window.clearInterval(WeChatPayTrade);
alert("订单已支付,感谢支持。。。");
}
}
})
}, 3000);
}
var qRCode;
//生成付款二维码
function createQrcode(url) {
if (qRCode != undefined && qRCode != '') {
//清空之前的二维码
$("#Orcode_div canvas").remove()
$("#yes_qrcode").hide();
}
//生成二维码放入”Orcode_div“ div
qRCode = $('#Orcode_div').qrcode({
width: 168, //宽度
height: 168, //高度
text: url
});
if (url.indexOf("weixin") > -1) {
findWeChatPay_trade();
}
}
</script>
</html>
完成上面的代码,简单的一个支付实现就完成了。
六、异步回调通知
官方文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8
上诉代码,我们已经弄完了前端和后端的交互流程,前端页面也告诉了用户这笔订单的状态。但是。。。。。但是… ,我们后端不知道啊,我这么说你们肯定有疑问了,-那我们刚刚不是有一个“查询交易状态”的接口不是已经知道用户支付成功了吗?,为什么还要弄一个异步回调接口呢。你要是这么想的话,那你就肤浅了。。。。
1、什么是异步回调接口???
答:在一笔订单支付成功之后微信会告诉你的服务器这笔订单支付成功了,然后你就需要根据你的项目业务逻辑进行处理,该发货的发货,该退款的退款(比如用户重复支付),所以你需要写一个接口放到你们项目中,让微信来调用你的接口就行了。
2、为什么我们要弄一个异步回调接口???
答:前面我们有一个“查询交易状态”的接口,但是你有没有发现这个接口是要你的服务器去主动调用微信的接口的,而且最开始触发这个调用的是前端触发的,这样假如用户再下单了之后把这个页面给关掉了呢,那是不是“查询交易状态”的这个接口是不是就不会触发了,不会触发是不是就导致了服务器不知道这个订单有没有支付成功了(用户钱就白给了。。。)。那有些人可能还想到了,每次我订单下单完成之后,我服务器定时去调用微信接口不就行了吗?这也是个法子但是这样服务器就肯定吃不消了啊,一个用户就要弄一个定时用户,那要是用户多了怎么办呢,所以说这个方法行不通服务器会受不了滴。
注意:回调的接口地址必须是公网可以进行访问的,如果开发中您的项目公网没有办法访问的话,微信是无法调用的。所以我们需要弄一个内网穿透,各位可以参考下面的几个网站:
1、花生壳:https://hsk.oray.com/(免费)
2、natapp:https://natapp.cn(免费),但是每次启动域名会变
1、回调接口服务层
解析微信返回的数据、验证签名
将下方代码放入:com.maomao.pay.web.service.WeChatPayService
/**
* 微信支付异步回调通知接口
* shuqingyou 2022/9/16
* @param request 请求对象
* @param api_key api密钥
* @return
* @throws Exception
*/
public static Map<String, Object> WeChatPayCallback(HttpServletRequest request, String api_key) throws Exception {
Map<String, Object> ResultMap = new HashMap<String, Object>();
//todo 解析到微信返回过来的xml数据
String WxPayxmlData = WxChatPayCommonUtil.readXmlFromStream(request);
//todo xml转Map
Map<String, String> WxResultMap = WXPayUtil.xmlToMap(WxPayxmlData);
//todo 验证签名
boolean SignStatus = WXPayUtil.isSignatureValid(WxResultMap, api_key);
if (SignStatus) {
//验证成功
//要返回给微信的xml数据
String returnWeChat = WxChatPayCommonUtil.setReturnXml("SUCCESS", "OK");
ResultMap.put("Verify", "YES");
ResultMap.put("returnWeChat", returnWeChat);
ResultMap.put("data", WxResultMap);
} else {
//验证失败(表示可能接口被他人调用 需要留意)
ResultMap.put("Verify", "NO");
ResultMap.put("msg", "验签失败。");
}
return ResultMap;
}
2、回调接口控制层
将下方代码放入:com.maomao.pay.web.controller.WeiXinPayController
注意:在微信回调完成之后,你需要将参数“returnWeChat”里面的数据返回给微信以告诉微信,你的服务器收到了它的回调,要不然微信会重复调用您的这个接口(24小时内),除非你回一个正确的数据
returnWeChat实例值:
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
3、预下单接口设置回调地址
在“预下单”接口控制层传入,回调地址(公网可以访问),也可以放入 yml文件中通过payProperties类进行注入(我这里就不演示了)
4、演示效果
走到这里就代表回调处理成功了,这样子你的真个流程就走完了。
在这里你需要做”验签“保证系统的安全性,防止不法分子仿造数据调用你的接口。记住处理你的项目业务逻辑的代码只能再这里进行,不要去“查询交易状态”接口中进行,要保证系统的安全性以及一致性,要不然用户支付成功了,你最后没给别人发货,那谁还敢用你的平台,其次当如果一个回调过来了,你要先查一下这个订单有没有支付过,如果是重复支付那你就要给用户进行退款处理。(退款的代码我就不放在这里演示了),我会放在项目源码里面。