SpringBoot对接小程序微信支付的实现
作者:koko、
前言
项目采用SpringBoot
微信支付有两个版本:V3 和 V2,本文的接入版本为V2
API V2 和 API V3 的区别
1、接口请求参数不同
2、API V2 调用流程
在微信v2接口中,只有涉及资金流出或获取重要信息才会使用证书,比如退款、企业付款和下载资金账单等。
3、API V3 调用流程
(1)证书序列号
每个证书都有一个由CA颁发的唯一编号,即证书序列号。如果读取到的序列号是10进制整形需要转换为大写的16进制。
(2)平台证书
微信支付平台证书是指由微信支付负责申请的,包含微信支付平台标识、公钥信息的证书。
a、不同的商户,对应的微信支付平台证书是不一样的。
b、微信支付APIv3使用微信支付的平台私钥进行应答签名,商户使用平台证书中的公钥进行验签。
c、微信平台证书会周期性更换,商户应实现定期更新平台证书的逻辑实现平台证书平滑切换,即:定期调用该接口,间隔时间小于12 小时,参考获取平台证书接口。目前已知的微信更换平台证书的场景:
- 证书到期后,必须更换。(目前是五年)
- 证书到期前,例行更换。(每年一次)
d、旧证书过期前10天生成新证书,旧证书过期前5天至过期当天,新证书开始逐步放量用于应答和回调的签名。为了保证更换过程中不影响API的使用,请求和应答的HTTP头部中包括证书序列号,以声明签名或者加密所用的密钥对和证书。
一、准备工作
- 微信小程序账号:要认证、获取appid、生成secret、开通支付、关联商户号
- 微信商户平台账号:要认证、获取商户号mch_id、获取商户API密钥mch_key、APPID授权、配置支付接口
2.1、企业微信小程序开通
2.1.1、获取开发者ID
- 获取appid:小程序的身份证明
- 获取secret:小程序的唯一凭证密钥
2.1.2、开通支付功能
2.1.3、关联商户号
2.2、企业商户号的开通
2.2.1、获取商户号mch_id
2.2.2、获取商户API密钥mch_key
二、整体流程
商户系统与微信支付系统主要交互:
1、JSAPI支付、APP支付、H5支付、Native支付、付款码支付、小程序支付【微信支付API列表】
2、小程序内调用登录接口,获取到用户的openid【小程序登录API】
3、商户server调用支付统一下单【统一下单API】
4、小程序支付成功回调notity_url(业务逻辑处理)【支付结果通知】
5、商户server查询支付结果,如未收到支付通知的情况,商户后台系统可调用【查询订单API】
三、后端项目搭建
3.1、统一下单
1、导入相关依赖 pom.yml
<!-- 微信支付 SDK --> <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency>
2、文件配置微信公众号的基础信息 application.yml
# 微信支付配置 notifyUrl:微信支付异步回调地址 pay: appId: #应用id apiKey: #商户私钥key mchId: #商户id appSecret: #小程序密钥 notifyUrl: #支付回调地址
3、设置配置文件 WxPayConfig.java
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; /** * 微信支付配置 * @author lf * @date 2023/8/30 */ @Data @Component @Configuration @ConfigurationProperties(prefix = "pay") public class WxPayConfig { /** * 微信公众号appid */ private String appId; /** * 公众号设置的API v2密钥 */ private String apiKey; /** * 微信商户平台 商户id */ private String mchId; /** *小程序密钥 */ private String appSecret; /** * 小程序支付异步回调地址 */ private String notifyUrl; }
4、微信支付预下单实体类 WxChatPay.java
import lombok.Data; import lombok.experimental.Accessors; import java.math.BigDecimal; /** * 微信支付预下单实体类 */ @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; /** * 签名类型 */ private String sign_type; /** * 业务结果 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; /** * 时间戳 */ private String time_stamp; /** * 会员类型 */ private String memberShipType; }
5、微信支付API地址 WeChatPayUrlContants.java
/** * 微信支付API地址 * @author lf * @date 2023/8/30 */ public class WeChatPayUrlConstants { /** * 统一下单预下单接口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"; }
6、预下单成功之后返回结果 OrderReturnInfo.java
import lombok.Data; @Data public class OrderReturnInfo { private String return_code; private String return_msg; private String result_code; private String appid; private String mch_id; private String nonce_str; private String sign; private String prepay_id; private String trade_type; }
7、查询订单返回的实体类 QueryReturnInfo.java
import lombok.Data; /** * 查询订单返回实体类 * @author lf * @date 2023/9/1 */ @Data public class QueryReturnInfo { private String return_code; private String return_msg; private String result_code; private String err_code; private String err_code_des; private String appid; private String mch_id; private String nonce_str; private String sign; private String prepay_id; private String trade_type; private String device_info; private String openid; private String is_subscribe; private String trade_state; private String bank_type; private int total_fee; private int settlement_total_fee; private String fee_type; private int cash_fee; private String cash_fee_type; private int coupon_fee; private int coupon_count; private String coupon_type_$n; private String coupon_id_$n; private String transaction_id; private String out_trade_no; private String time_end; private String trade_state_desc; }
8、签名实体类 SignInfo.java
/** * 签名实体类 * @author lf * @date 2023/9/1 */ @Data public class SignInfo { private String appId;//小程序ID private String timeStamp;//时间戳 private String nonceStr;//随机串 @XStreamAlias("package") private String repay_id; private String signType;//签名方式 public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getTimeStamp() { return timeStamp; } public void setTimeStamp(String timeStamp) { this.timeStamp = timeStamp; } public String getNonceStr() { return nonceStr; } public void setNonceStr(String nonceStr) { this.nonceStr = nonceStr; } public String getRepay_id() { return repay_id; } public void setRepay_id(String repay_id) { this.repay_id = repay_id; } public String getSignType() { return signType; } public void setSignType(String signType) { this.signType = signType; } }
9、Http工具类 HttpRequest.java
/** * Http工具类 * @author lf * @date 2023/9/1 */ public class HttpRequest { //连接超时时间,默认10秒 private static final int socketTimeout = 10000; //传输超时时间,默认30秒 private static final int connectTimeout = 30000; /** * post请求 * * @throws IOException * @throws ClientProtocolException * @throws NoSuchAlgorithmException * @throws KeyStoreException * @throws KeyManagementException * @throws UnrecoverableKeyException */ public static String sendPost(String url, Object xmlObj) throws ClientProtocolException, IOException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException { HttpPost httpPost = new HttpPost(url); //解决XStream对出现双下划线的bug XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_"))); xStreamForRequestPostData.alias("xml", xmlObj.getClass()); //将要提交给API的数据对象转换成XML格式数据Post给API String postDataXML = xStreamForRequestPostData.toXML(xmlObj); //得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别 StringEntity postEntity = new StringEntity(postDataXML, "UTF-8"); httpPost.addHeader("Content-Type", "text/xml"); httpPost.setEntity(postEntity); //设置请求器的配置 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build(); httpPost.setConfig(requestConfig); HttpClient httpClient = HttpClients.createDefault(); HttpResponse response = httpClient.execute(httpPost); HttpEntity entity = response.getEntity(); String result = EntityUtils.toString(entity, "UTF-8"); return result; } /** * 自定义证书管理器,信任所有证书 * * @author pc */ public static class MyX509TrustManager implements X509TrustManager { @Override public void checkClientTrusted( java.security.cert.X509Certificate[] arg0, String arg1) throws CertificateException { } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] arg0, String arg1) throws CertificateException { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } } }
10、微信签名 SignUtils.java
/** * 微信签名 * @author lf * @date 2023/9/1 */ public class SignUtils { /** * 签名算法 * * @param o 要参与签名的数据对象 * @return 签名 * @throws IllegalAccessException */ public static String getSign(Object o) throws IllegalAccessException { ArrayList<String> list = new ArrayList<String>(); Class cls = o.getClass(); Field[] fields = cls.getDeclaredFields(); for (Field f : fields) { f.setAccessible(true); if (f.get(o) != null && f.get(o) != "") { String name = f.getName(); XStreamAlias anno = f.getAnnotation(XStreamAlias.class); if (anno != null) { name = anno.value(); } list.add(name + "=" + f.get(o) + "&"); } } int size = list.size(); String[] arrayToSort = list.toArray(new String[size]); Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); StringBuilder sb = new StringBuilder(); for (int i = 0; i < size; i++) { sb.append(arrayToSort[i]); } String result = sb.toString(); result += "key=" + Configure.getKey(); System.out.println("签名数据:" + result); result = MD5.MD5Encode(result).toUpperCase(); return result; } public static String getSign(Map<String, Object> map) { ArrayList<String> list = new ArrayList<String>(); for (Map.Entry<String, Object> entry : map.entrySet()) { if (entry.getValue() != "") { list.add(entry.getKey() + "=" + entry.getValue() + "&"); } } int size = list.size(); String[] arrayToSort = list.toArray(new String[size]); Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); StringBuilder sb = new StringBuilder(); for (int i = 0; i < size; i++) { sb.append(arrayToSort[i]); } String result = sb.toString(); result += "key=" + Configure.getKey(); //Util.log("Sign Before MD5:" + result); result = MD5.MD5Encode(result).toUpperCase(); //Util.log("Sign Result:" + result); return result; } }
11、MD5加密工具类 MD5.java
import java.security.MessageDigest; /** * MD5加密工具类 * @author lf * @date 2023/9/1 */ public class MD5 { private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; /** * 转换字节数组为16进制字串 * * @param b 字节数组 * @return 16进制字串 */ public static String byteArrayToHexString(byte[] b) { StringBuilder resultSb = new StringBuilder(); for (byte aB : b) { resultSb.append(byteToHexString(aB)); } return resultSb.toString(); } /** * 转换byte到16进制 * * @param b 要转换的byte * @return 16进制格式 */ private static String byteToHexString(byte b) { int n = b; if (n < 0) { n = 256 + n; } int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } /** * MD5编码 * * @param origin 原始字符串 * @return 经过MD5加密之后的结果 */ public static String MD5Encode(String origin) { String resultString = null; try { resultString = origin; MessageDigest md = MessageDigest.getInstance("MD5"); resultString = byteArrayToHexString(md.digest(resultString.getBytes())); } catch (Exception e) { e.printStackTrace(); } return resultString; } }
12、微信支付配置类 Configure.java
/** * 微信支付配置类 * @author lf * @date 2023/9/1 */ public class Configure { /** * 商户支付秘钥 */ private static String key = ""; public static String getKey() { return key; } public static void setKey(String key) { Configure.key = key; } }
13、Controller层,PayparameterVO可以不需要,我这里是因为业务需要,处理业务逻辑
/** * 小程序支付下单接口 * @return 返回结果 */ @ApiOperation("小程序支付功能") @PostMapping("/pay") public AjaxResult wxPay(@RequestBody PayParameterVO payParameterVO){ Map payHistory = wxPayInfoService.insertPayRecord(payParameterVO); return success("success",payHistory); } /** * 查询订单 */ @ApiOperation("订单查询") @PostMapping("/wx/query") public AjaxResult orderQuery(@RequestParam("out_trade_no") String out_trade_no) { Map query = wxPayInfoService.orderQuery(out_trade_no); return success("success", query); }
14、业务接口层 WxPayInfoService.java
import com.ruoyi.ai.doamin.PayParameterVO; import java.util.Map; /** * 微信小程序支付-业务接口层 * @author lf * @date 2023/8/31 */ public interface WxPayInfoService { /** * 插入订单记录 */ Map insertPayRecord(PayParameterVO payParameterVO); /** * 查询订单 * @param out_trade_no 订单号 * @return 返回结果 */ Map orderQuery(String out_trade_no); }
15、业务接口实现层 WxPayInfoServiceImpl.java
import cn.hutool.core.util.ObjectUtil; import com.github.wxpay.sdk.WXPayConstants; import com.github.wxpay.sdk.WXPayUtil; import com.ruoyi.ai.doamin.PayParameterVO; import com.ruoyi.ai.service.WxPayInfoService; import com.ruoyi.common.config.WxPayConfig; import com.ruoyi.common.constant.WeChatPayUrlConstants; import com.ruoyi.common.core.domain.pay.WeChatPay; import com.ruoyi.common.utils.pay.HttpRequest; import com.ruoyi.common.utils.pay.SignUtils; import com.ruoyi.common.utils.pay.entity.OrderReturnInfo; import com.ruoyi.common.utils.pay.entity.QueryReturnInfo; import com.ruoyi.common.utils.pay.entity.SignInfo; import com.thoughtworks.xstream.XStream; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.*; /** * 微信小程序支付-业务接口实现层 * @author lf * @date 2023/8/31 */ @Service @Slf4j public class WxPayInfoServiceImpl implements WxPayInfoService { @Resource private WxPayConfig payProperties; private static final DecimalFormat df = new DecimalFormat("#"); /** * 插入订单记录 * @param payParameterVO 用户ID 会员套餐ID * @return 返回结果 */ @Override @Transactional public Map insertPayRecord(PayParameterVO payParameterVO) { //接收返回的参数 Map<String, Object> map = new HashMap<>(); String title = "koko测试点数"; //金额 * 100 以分为单位 BigDecimal fee = BigDecimal.valueOf(1); BigDecimal RMB = new BigDecimal(100); BigDecimal totalFee = fee.multiply(RMB); try { WeChatPay weChatPay = new WeChatPay(); weChatPay.setAppid(payProperties.getAppId()); weChatPay.setMch_id(payProperties.getMchId()); weChatPay.setNonce_str(getRandomStringByLength(32)); weChatPay.setBody(title); weChatPay.setOut_trade_no(getRandomStringByLength(32)); weChatPay.setTotal_fee( df.format(Double.parseDouble(String.valueOf(totalFee)))); weChatPay.setSpbill_create_ip("127.0.0.1"); weChatPay.setNotify_url(payProperties.getNotifyUrl()); weChatPay.setTrade_type("JSAPI"); //这里直接使用当前用户的openid weChatPay.setOpenid("oOKq*******xj8o"); weChatPay.setSign_type("MD5"); //生成签名 String sign = SignUtils.getSign(weChatPay); weChatPay.setSign(sign); String result = HttpRequest.sendPost(WeChatPayUrlConstants.Uifiedorder, weChatPay); System.out.println(result); //将返回结果从xml格式转换为map格式 Map<String, String> wxResultMap = WXPayUtil.xmlToMap(result); if (ObjectUtil.isNotEmpty(wxResultMap.get("return_code")) && wxResultMap.get("return_code").equals("SUCCESS")){ if (wxResultMap.get("result_code").equals("FAIL")){ map.put("msg", "统一下单失败"); map.put("status",500); map.put("data", wxResultMap.get("err_code_des")); return map; } } XStream xStream = new XStream(); xStream.alias("xml", OrderReturnInfo.class); OrderReturnInfo returnInfo = (OrderReturnInfo) xStream.fromXML(result); // 二次签名 if ("SUCCESS".equals(returnInfo.getReturn_code()) && returnInfo.getReturn_code().equals(returnInfo.getResult_code())) { SignInfo signInfo = new SignInfo(); signInfo.setAppId(payProperties.getAppId()); long time = System.currentTimeMillis() / 1000; signInfo.setTimeStamp(String.valueOf(time)); signInfo.setNonceStr(WXPayUtil.generateNonceStr()); signInfo.setRepay_id("prepay_id=" + returnInfo.getPrepay_id()); signInfo.setSignType("MD5"); //生成签名 String sign1 = SignUtils.getSign(signInfo); Map<String, String> payInfo = new HashMap<>(); payInfo.put("timeStamp", signInfo.getTimeStamp()); payInfo.put("nonceStr", signInfo.getNonceStr()); payInfo.put("package", signInfo.getRepay_id()); payInfo.put("signType", signInfo.getSignType()); payInfo.put("paySign", sign1); map.put("status", 200); map.put("msg", "统一下单成功!"); map.put("data", payInfo); //预下单成功,处理业务逻辑 //****************************// // 业务逻辑结束 回传给小程序端唤起支付 return map; } map.put("status", 500); map.put("msg", "统一下单失败!"); map.put("data", null); return map; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 查询订单 * @param out_trade_no 订单号 * @return 返回结果 */ @Override public Map orderQuery(String out_trade_no){ Map<String, Object> map = new HashMap<>(); try { WeChatPay weChatPay = new WeChatPay(); weChatPay.setAppid(payProperties.getAppId()); weChatPay.setMch_id(payProperties.getMchId()); weChatPay.setNonce_str(WXPayUtil.generateNonceStr()); weChatPay.setOut_trade_no(out_trade_no); //order.setSign_type("MD5"); //生成签名 String sign = SignUtils.getSign(weChatPay); weChatPay.setSign(sign); String result = HttpRequest.sendPost(WXPayConstants.ORDERQUERY_URL, weChatPay); System.out.println(result); XStream xStream = new XStream(); xStream.alias("xml", QueryReturnInfo.class); QueryReturnInfo returnInfo = (QueryReturnInfo) xStream.fromXML(result); map.put("status", 500); map.put("msg", "统一下单失败!"); map.put("data", returnInfo); return map; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 获取一定长度的随机字符串 * * @param length 指定字符串长度 * @return 一定长度的字符串 */ public static String getRandomStringByLength(int length) { String base = "abcdefghijklmnopqrstuvwxyz0123456789"; 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(); } }
3.2、支付支付回调
/** * 微信小程序支付成功回调 * @param request 请求 * @param response 响应 * @return 返回结果 * @throws Exception 异常处理 */ @RequestMapping("/weixin/callback") public String callBack(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("接口已被调用"); ServletInputStream inputStream = request.getInputStream(); String notifyXml = StreamUtils.inputStream2String(inputStream, "utf-8"); System.out.println(notifyXml); // 解析返回结果 Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyXml); // 判断支付是否成功 if ("SUCCESS".equals(notifyMap.get("result_code"))) { //支付成功时候,处理业务逻辑 System.out.println("支付成功"); System.out.println("<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "); /** * 注意 * 因为微信回调会有八次之多,所以当第一次回调成功了,那么我们就不再执行逻辑了 * return返回的结果一定是这种格式,当result_code返回的结果是SUCCESS时,则不进行调用了 * 如果不返回下面的格式,业务逻辑会出现回调多次的情况,我就遇到过这种情况。 */ return "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } // 创建响应对象:微信接收到校验失败的结果后,会反复的调用当前回调函数 Map<String, String> returnMap = new HashMap<>(); returnMap.put("return_code", "FAIL"); returnMap.put("return_msg", ""); String returnXml = WXPayUtil.mapToXml(returnMap); response.setContentType("text/xml"); System.out.println("校验失败"); return returnXml; }
注:如果能正常走预支付的接口,而没有处理回调接口的业务逻辑。
1、返回的xml格式是否正确,V2会比对xml返回的数据格式是否一致
2、回调的接口是否允许外网访问,并且接口不能携带任何参数,如果框架存在token验证时,则需要关闭支付回调的token验证。
3、如果以上都没问题,却还是没走回调,可以看看这篇文档【接收不到回调排查指引】
3.3、问题排查
1、在与前端联调时,如果出现如下问题,看看预下单的packeage参数是否正确,必须严格按照如下格式:
package: "prepay_id=wx*********0000";
2、在与前端联调时,如果出现如下问题,看看签名的工具类是否能够验证通过【签名校验】
3.4、统一下单和订单查询
1、统一下单
2、订单查询-支付成功
3、订单查询-支付失败
4、预下单成功并且支付成功
到此这篇关于SpringBoot对接小程序微信支付的实现的文章就介绍到这了,更多相关SpringBoot 小程序微信支付内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!