> 技术文档 > 【微信支付】【java】Springboot对接开发微信支付_wechatpay-java

【微信支付】【java】Springboot对接开发微信支付_wechatpay-java

本文章是介绍java对接(微信小程序)微信支付,包括微信预下单、支付、退款等等。 

目录

 一、微信配置申请

1、微信支付配置申请

二、开发环境

1、开发环境

2、maven依赖

3、application.yml文件配置

三、代码开发

1、配置类

2、初始化商户配置

3、JSAPI微信预下单

3.1、先建个WxPayService服务类

3.1、R实体类

3.2、CreateOrderReq类

4、微信支付回调通知 

5、根据商户订单号查询订单(out_trade_no)

5.1  QueryOrderReq类

6、根据支付订单号查询订单 (transaction_id)

7、微信申请退款

8、退款回调通知 

四、mysql表结构

五、controller类

六、很多人问我要退款接口,来今日补充下! 

1.退款controller

2.退款service

七、退款回调通知

八、错误汇总


 一、微信配置申请

1、微信支付配置申请

详细操作流程参考官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml#part-1

配置完成需要以下信息:

  • APPID
  • 商户号(mchid)
  • 商户API私钥(apiclient_key.pem)
  • 商户证书序列号
  • 商户APIv3密钥

二、开发环境

1、开发环境

开发语言:java ,编译工具:idea ,框架:springboot ,仓库:maven

2、maven依赖

 com.github.wechatpay-apiv3 wechatpay-java 0.2.10

3、application.yml文件配置

#微信支付配置wx: pay: #应用id(小程序id) appId: wx6b5xxxxxxxxxxxx #商户号 merchantId: 1xxxxxxxxx #商户API私钥 privateKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx #商户证书序列号 merchantSerialNumber: 315DDXXXXXXXXXXXXXXXXXXXXXXXXXXX #商户APIv3密钥 apiV3Key: XXXXXXXXXXXXXXXXXXXXXXXXXX #支付通知地址 payNotifyUrl: https://xxx.xxxx.xxx.xxx/xx/xxxx/xxxx/openapi/wx/payNotify #退款通知地址 refundNotifyUrl: https://xxx.xxx.xxx.xxx/xxxx/xxxx/xxxx/openapi/wx/refundNotify

三、代码开发

1、配置类

import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;/** * @author caozhen * @ClassName WxPayConfig * @description: 微信支付配置类 * @date 2024年01月03日 * @version: 1.0 */@Data@Component@ConfigurationProperties(prefix = \"wx.pay\")public class WxPayConfig { //APPID private String appId; //mchid private String merchantId; //商户API私钥 private String privateKey; //商户证书序列号 private String merchantSerialNumber; //商户APIv3密钥 private String apiV3Key; //支付通知地址 private String payNotifyUrl; //退款通知地址 private String refundNotifyUrl;}

2、初始化商户配置

import com.wechat.pay.java.core.RSAAutoCertificateConfig;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;/** * @author caozhen * @ClassName WxPayAutoCertificateConfig * @description: 微信支付证书自动更新配置 * @date 2024年01月03日 * @version: 1.0 */@Configurationpublic class WxPayAutoCertificateConfig { @Resource private WxPayConfig wxPayConfig; /** * 初始化商户配置 * @return */ @Bean public RSAAutoCertificateConfig rsaAutoCertificateConfig() { RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder() .merchantId(wxPayConfig.getMerchantId()) .privateKey(wxPayConfig.getPrivateKey()) .merchantSerialNumber(wxPayConfig.getMerchantSerialNumber()) .apiV3Key(wxPayConfig.getApiV3Key()) .build(); return config; }}

3、JSAPI微信预下单

3.1、先建个WxPayService服务类

import com.alibaba.fastjson.JSONObject;import com.baomidou.mybatisplus.mapper.EntityWrapper;import com.baomidou.mybatisplus.mapper.Wrapper;import com.hvit.user.exception.DataAccessException;import com.hvit.user.util.DateUtils;import com.hvit.user.util.R;import com.hvit.user.yst.entity.WxOrderEntity;import com.hvit.user.yst.entity.WxPayLogEntity;import com.hvit.user.yst.request.CreateOrderReq;import com.hvit.user.yst.request.QueryOrderReq;import com.hvit.user.yst.request.WxNotifyReq;import com.hvit.user.yst.service.WKShoppingMallService;import com.hvit.user.yst.service.data.WxOrderDataService;import com.hvit.user.yst.service.data.WxPayLogDataService;import com.wechat.pay.java.core.RSAAutoCertificateConfig;import com.wechat.pay.java.core.exception.HttpException;import com.wechat.pay.java.core.exception.MalformedMessageException;import com.wechat.pay.java.core.exception.ServiceException;import com.wechat.pay.java.core.notification.NotificationParser;import com.wechat.pay.java.core.notification.RequestParam;import com.wechat.pay.java.service.payments.jsapi.*;import com.wechat.pay.java.service.payments.jsapi.model.*;import com.wechat.pay.java.service.payments.jsapi.model.Amount;import com.wechat.pay.java.service.payments.model.Transaction;import com.wechat.pay.java.service.refund.RefundService;import com.wechat.pay.java.service.refund.model.*;import io.swagger.annotations.ApiModelProperty;import io.swagger.models.auth.In;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import javax.servlet.ServletInputStream;import javax.servlet.http.HttpServletRequest;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.math.BigDecimal;import java.util.Date;import java.util.HashMap;import java.util.LinkedHashMap;import java.util.Map;/** * @author caozhen * @ClassName WxPayService * @description: 微信支付 * @date 2024年01月03日 * @version: 1.0 */@Slf4j@Servicepublic class WxPayService { @Resource private WxPayConfig wxPayConfig; @Autowired private RSAAutoCertificateConfig rsaAutoCertificateConfig; @Autowired private WxOrderDataService wxOrderDataService; @Autowired private WxPayLogDataService wxPayLogDataService; /*** * 预支付订单 * @param req * @return */ public R createOrder(CreateOrderReq req) throws Exception { if (req == null) { return R.error(\"创建订单失败,缺少参数!\"); } //先解密 String orderNo = req.getOutTradeNo(); Integer totalFee = req.getTotal(); //创建初始化订单 //todo,创建订单这边你们自己来(后面我会放出表结构) //请求微信支付相关配置 JsapiServiceExtension service = new JsapiServiceExtension.Builder() .config(rsaAutoCertificateConfig) .signType(\"RSA\") // 不填默认为RSA .build(); PrepayWithRequestPaymentResponse response = new PrepayWithRequestPaymentResponse(); try { PrepayRequest request = new PrepayRequest(); request.setAppid(wxPayConfig.getAppId()); request.setMchid(wxPayConfig.getMerchantId()); request.setDescription(description); request.setOutTradeNo(orderNo); request.setNotifyUrl(wxPayConfig.getPayNotifyUrl()); Amount amount = new Amount(); //amount.setTotal(totalFee.multiply(new BigDecimal(\"100\")).intValue()); amount.setTotal(totalFee); request.setAmount(amount); Payer payer = new Payer(); payer.setOpenid(req.getWxOpenId()); request.setPayer(payer); log.info(\"请求预支付下单,请求参数:{}\", JSONObject.toJSONString(request)); // 调用预下单接口 response = service.prepayWithRequestPayment(request); log.info(\"订单【{}】发起预支付成功,返回信息:{}\", orderNo, response); } catch (HttpException e) { // 发送HTTP请求失败 log.error(\"微信下单发送HTTP请求失败,错误信息:{}\", e.getHttpRequest()); return R.error(\"下单失败\"); } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500 log.error(\"微信下单服务状态错误,错误信息:{}\", e.getErrorMessage()); return R.error(\"下单失败\"); } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败 log.error(\"服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}\", e.getMessage()); return R.error(\"下单失败\"); } return R.ok().put(\"data\", response); }}

3.1、R实体类

import java.util.HashMap;import java.util.Map;/** * * @author 曹震 * @date 2024-1-03 */public class R extends HashMap { private static final long serialVersionUID = 1L; public R() { put(\"code\", 0); } public R(Integer code) { put(\"code\", code); put(\"data\", new HashMap()); } public R(Integer code, String msg) { put(\"code\", code); put(\"msg\", msg); put(\"data\", new HashMap()); } public static R error() { return error(500, \"未知异常,请联系管理员\"); } public static R errorDebug(String message) { return error(500, \"未知异常 \" + message + \",请联系管理员\"); } public static R error(String msg) { return error(500, msg); } public static R error(int code, String msg) { R r = new R(); r.put(\"code\", code); r.put(\"msg\", msg); return r; } public R errorInfo(String msg) { this.put(\"errorMsg\", msg); return this; } public static R ok(String msg) { R r = new R(); r.put(\"msg\", msg); r.put(\"data\", new HashMap()); return r; } public static R ok(Map map) { R r = new R(); r.putAll(map); r.put(\"data\", new HashMap()); return r; } public static R ok() { return new R().put(\"msg\", \"success\").put(\"data\", new HashMap()); } public static R ok(Integer size) { return new R().put(\"data\", new HashMap((int)Math.round(size / 0.75))); } @Override public R put(String key, Object value) { super.put(key, value); return this; } /** * 添加返回结果数据 * * @param key * @param value * @return */ public R putData(String key, Object value) { Map map = (HashMap)this.get(\"data\"); map.put(key, value); return this; }}

3.2、CreateOrderReq类

import io.swagger.annotations.ApiModelProperty;import lombok.Data;/** * @author caozhen * @ClassName CreateOrderReq * @description: TODO * @date 2024年01月03日 * @version: 1.0 */@Datapublic class CreateOrderReq { @ApiModelProperty(name = \"description\", value = \"商品描述\") private String description; @ApiModelProperty(name = \"wxOpenId\", value = \"用户小程序openid\") private String wxOpenId; @ApiModelProperty(name = \"outTradeNo\", value = \"商户订单号\") private String outTradeNo; @ApiModelProperty(name = \"totalFee\", value = \"支付金额,单位:分\") private Long totalFee;

4、微信支付回调通知 

/*** * 微信支付回调通知 * @param request * @return * @throws IOException */ @Transactional public synchronized String payNotify(HttpServletRequest request) throws Exception { log.info(\"------收到支付通知------\"); // 请求头Wechatpay-Signature String signature = request.getHeader(\"Wechatpay-Signature\"); // 请求头Wechatpay-nonce String nonce = request.getHeader(\"Wechatpay-Nonce\"); // 请求头Wechatpay-Timestamp String timestamp = request.getHeader(\"Wechatpay-Timestamp\"); // 微信支付证书序列号 String serial = request.getHeader(\"Wechatpay-Serial\"); // 签名方式 String signType = request.getHeader(\"Wechatpay-Signature-Type\"); // 构造 RequestParam RequestParam requestParam = new RequestParam.Builder() .serialNumber(serial) .nonce(nonce) .signature(signature) .timestamp(timestamp) .signType(signType) .body(HttpServletUtils.getRequestBody(request)) .build(); // 初始化 NotificationParser NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig); // 以支付通知回调为例,验签、解密并转换成 Transaction log.info(\"验签参数:{}\", requestParam); Transaction transaction = parser.parse(requestParam, Transaction.class); log.info(\"验签成功!-支付回调结果:{}\", transaction.toString()); Map returnMap = new HashMap(2); returnMap.put(\"code\", \"FAIL\"); returnMap.put(\"message\", \"失败\"); //修改订单前,建议主动请求微信查询订单是否支付成功,防止恶意post Wrapper wrapper = new EntityWrapper(); wrapper.eq(\"out_trade_no\", transaction.getOutTradeNo()); //wrapper.eq(\"transaction_id\", transaction.getTransactionId()); WxOrderEntity wxOrderEntity = wxOrderDataService.selectOne(wrapper); if (wxOrderEntity != null) { if (wxOrderEntity.getPayStatus() == 1) { //此时已经是支付成功,不在处理订单信息 returnMap.put(\"code\", \"SUCCESS\"); returnMap.put(\"message\", \"成功\"); return JSONObject.toJSONString(returnMap); } } if (Transaction.TradeStateEnum.SUCCESS != transaction.getTradeState()) { log.info(\"内部订单号【{}】,微信支付订单号【{}】支付未成功\", transaction.getOutTradeNo(), transaction.getTransactionId()); if (wxOrderEntity != null) { wxOrderEntity.setUpdateTime(new Date()); wxOrderEntity.setPayStatus(2); wxOrderEntity.setPayNonce(nonce); wxOrderEntity.setTransactionId(transaction.getTransactionId()); //修改订单信息 wxOrderDataService.updateById(wxOrderEntity); } return JSONObject.toJSONString(returnMap); } if (wxOrderEntity != null) { wxOrderEntity.setUpdateTime(new Date()); wxOrderEntity.setPayTime(DateUtils.stringToDateTime(transaction.getSuccessTime())); wxOrderEntity.setPayDate(DateUtils.stringToDateTime(transaction.getSuccessTime())); wxOrderEntity.setPayStatus(1); wxOrderEntity.setPayNonce(nonce); wxOrderEntity.setTransactionId(transaction.getTransactionId()); //修改订单信息 wxOrderDataService.updateById(wxOrderEntity); //同时处理支付记录 Wrapper payWrapper = new EntityWrapper(); wrapper.eq(\"out_trade_no\", transaction.getOutTradeNo()); wrapper.eq(\"pay_status\", 1);//支付 WxPayLogEntity wxPayLogEntity = wxPayLogDataService.selectOne(payWrapper); if (wxPayLogEntity == null) { wxPayLogEntity = new WxPayLogEntity(); wxPayLogEntity.setCreateTime(new Date()); wxPayLogEntity.setOutTradeNo(wxOrderEntity.getOutTradeNo()); wxPayLogEntity.setPayStatus(1); wxPayLogEntity.setTotalFee(wxOrderEntity.getTotalFee()); wxPayLogEntity.setTransactionId(wxOrderEntity.getTransactionId()); wxPayLogEntity.setWxOpenId(wxOrderEntity.getWxOpenId()); wxPayLogDataService.insert(wxPayLogEntity); } } returnMap.put(\"code\", \"SUCCESS\"); returnMap.put(\"message\", \"成功\"); return JSONObject.toJSONString(returnMap); }

5、根据商户订单号查询订单(out_trade_no)

/*** * 根据商户订单号查询订单 outTradeNo * @param req * @return */ @Transactional public R queryOrderByOrderNo(QueryOrderReq req) { QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest(); queryRequest.setMchid(wxPayConfig.getMerchantId()); queryRequest.setOutTradeNo(req.getOrderNo()); try { JsapiServiceExtension service =  new JsapiServiceExtension.Builder() .config(rsaAutoCertificateConfig) .signType(\"RSA\") // 不填默认为RSA .build(); Transaction result = service.queryOrderByOutTradeNo(queryRequest); LinkedHashMap retmap = new LinkedHashMap(); //支付成功 if (Transaction.TradeStateEnum.SUCCESS == result.getTradeState()) { log.info(\"内部订单号【{}】,微信支付订单号【{}】支付成功\", result.getOutTradeNo(), result.getTransactionId()); retmap.put(\"out_trade_no\", result.getOutTradeNo()); retmap.put(\"transaction_id\", result.getTransactionId()); retmap.put(\"success\", true); retmap.put(\"msg\", \"支付成功!\"); retmap.put(\"success_time\", DateUtils.getDateTimeString(DateUtils.stringToDateTime(result.getSuccessTime()))); //主动查询 Wrapper wrapper = new EntityWrapper(); wrapper.eq(\"out_trade_no\", req.getOrderNo()); WxOrderEntity wxOrderEntity = wxOrderDataService.selectOne(wrapper); if (wxOrderEntity != null) {  if (wxOrderEntity.getPayStatus() != 1) { wxOrderEntity.setPayStatus(1); wxOrderEntity.setTransactionId(result.getTransactionId()); wxOrderEntity.setPayDate(DateUtils.stringToDateTime(result.getSuccessTime())); wxOrderEntity.setPayTime(DateUtils.stringToDateTime(result.getSuccessTime())); wxOrderEntity.setUpdateTime(new Date()); wxOrderDataService.updateById(wxOrderEntity); //同时处理支付记录 Wrapper payWrapper = new EntityWrapper(); wrapper.eq(\"out_trade_no\", req.getOrderNo()); WxPayLogEntity wxPayLogEntity = wxPayLogDataService.selectOne(payWrapper); if (wxPayLogEntity == null) { wxPayLogEntity = new WxPayLogEntity(); wxPayLogEntity.setCreateTime(new Date()); wxPayLogEntity.setOutTradeNo(wxOrderEntity.getOutTradeNo()); wxPayLogEntity.setPayStatus(1); wxPayLogEntity.setTotalFee(wxOrderEntity.getTotalFee()); wxPayLogEntity.setTransactionId(result.getTransactionId()); wxPayLogEntity.setWxOpenId(wxOrderEntity.getWxOpenId()); wxPayLogDataService.insert(wxPayLogEntity); }  } } } else { log.info(\"内部订单号【{}】,微信支付订单号【{}】支付未成功\", result.getOutTradeNo(), result.getTransactionId()); retmap.put(\"out_trade_no\", result.getOutTradeNo()); retmap.put(\"transaction_id\", result.getTransactionId()); retmap.put(\"success\", false); retmap.put(\"msg\", \"支付失败!\"); retmap.put(\"success_time\", null); } return R.ok().put(\"data\", retmap); } catch (ServiceException e) { log.error(\"订单查询失败,返回码:{},返回信息:{}\", e.getErrorCode(), e.getErrorMessage()); return R.error(\"订单查询失败!\"); } }

5.1  QueryOrderReq类

mport io.swagger.annotations.ApiModelProperty;import lombok.Data;/** * @author caozhen * @ClassName QueryOrderReq * @description: 支付查询 * @date 2024年01月04日 * @version: 1.0 */@Datapublic class QueryOrderReq { @ApiModelProperty(name = \"paymentNo\", value = \"微信支付订单号,同:transaction_id\") private String paymentNo; @ApiModelProperty(name = \"orderNo\", value = \"商户订单号,同:out_trade_no\") private String orderNo;}

6、根据支付订单号查询订单 (transaction_id)

/*** * 根据支付订单号查询订单 paymentNo * @param req * @return */ @Transactional public R queryOrderByPaymentNo(QueryOrderReq req) { QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest(); queryRequest.setMchid(wxPayConfig.getMerchantId()); queryRequest.setTransactionId(req.getPaymentNo()); try { JsapiServiceExtension service =  new JsapiServiceExtension.Builder() .config(rsaAutoCertificateConfig) .signType(\"RSA\") // 不填默认为RSA .build(); Transaction result = service.queryOrderById(queryRequest); LinkedHashMap map = new LinkedHashMap(); //支付成功 if (Transaction.TradeStateEnum.SUCCESS == result.getTradeState()) { log.info(\"内部订单号【{}】,微信支付订单号【{}】支付成功\", result.getOutTradeNo(), result.getTransactionId()); map.put(\"out_trade_no\", result.getOutTradeNo()); map.put(\"transaction_id\", result.getTransactionId()); map.put(\"success\", true); map.put(\"msg\", \"支付成功!\"); map.put(\"success_time\", DateUtils.getDateTimeString(DateUtils.stringToDateTime(result.getSuccessTime()))); //主动查询 Wrapper wrapper = new EntityWrapper(); wrapper.eq(\"transaction_id\", req.getPaymentNo()); WxOrderEntity wxOrderEntity = wxOrderDataService.selectOne(wrapper); if (wxOrderEntity != null) {  if (wxOrderEntity.getPayStatus() != 1) { wxOrderEntity.setPayStatus(1); wxOrderEntity.setPayDate(DateUtils.stringToDateTime(result.getSuccessTime())); wxOrderEntity.setPayTime(DateUtils.stringToDateTime(result.getSuccessTime())); wxOrderEntity.setUpdateTime(new Date()); wxOrderDataService.updateById(wxOrderEntity); //同时处理支付记录 Wrapper payWrapper = new EntityWrapper(); wrapper.eq(\"transaction_id\", req.getPaymentNo()); WxPayLogEntity wxPayLogEntity = wxPayLogDataService.selectOne(payWrapper); if (wxPayLogEntity == null) { wxPayLogEntity = new WxPayLogEntity(); wxPayLogEntity.setCreateTime(new Date()); wxPayLogEntity.setOutTradeNo(wxOrderEntity.getOutTradeNo()); wxPayLogEntity.setPayStatus(1); wxPayLogEntity.setTotalFee(wxOrderEntity.getTotalFee()); wxPayLogEntity.setTransactionId(result.getTransactionId()); wxPayLogEntity.setWxOpenId(wxOrderEntity.getWxOpenId()); wxPayLogDataService.insert(wxPayLogEntity); }  } } } else { log.info(\"内部订单号【{}】,微信支付订单号【{}】支付未成功\", result.getOutTradeNo(), result.getTransactionId()); map.put(\"out_trade_no\", result.getOutTradeNo()); map.put(\"transaction_id\", result.getTransactionId()); map.put(\"success\", false); map.put(\"msg\", \"支付失败!\"); map.put(\"success_time\", null); } return R.ok().put(\"data\", map); } catch (ServiceException e) { log.error(\"订单查询失败,返回码:{},返回信息:{}\", e.getErrorCode(), e.getErrorMessage()); return R.error(\"订单查询失败!\"); } }

7、微信申请退款

/*** * 微信申请退款 * @param outTradeNo 商户订单号 * @param totalAmount * @return */ public R createRefund(String outTradeNo, Long totalAmount) { //返回参数 LinkedHashMap map = new LinkedHashMap(); map.put(\"out_trade_no\", outTradeNo); map.put(\"success\", false); map.put(\"msg\", \"正在申请退款中!\"); String outRefundNo = \"REFUND_\" + outTradeNo; map.put(\"out_refund_no\", outRefundNo); //申请退款订单,需要变更订单记录 Wrapper wrapper = new EntityWrapper(); wrapper.eq(\"out_trade_no\", outTradeNo); WxOrderEntity wxOrderEntity = wxOrderDataService.selectOne(wrapper); if (wxOrderEntity == null) { return R.error(\"订单不存在,申请退款不存在!\"); } wxOrderEntity.setPayStatus(4);//退款中 wxOrderEntity.setUpdateTime(new Date()); wxOrderDataService.updateById(wxOrderEntity); try { // 构建退款service RefundService service = new RefundService.Builder()  .config(rsaAutoCertificateConfig)  .build(); CreateRequest request = new CreateRequest(); // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义 request.setOutTradeNo(outTradeNo); request.setOutRefundNo(outRefundNo); AmountReq amount = new AmountReq(); amount.setTotal(totalAmount); amount.setRefund(totalAmount); amount.setCurrency(\"CNY\"); request.setAmount(amount); request.setNotifyUrl(wxPayConfig.getRefundNotifyUrl()); //接收退款返回参数 Refund refund = service.create(request); log.info(\"退款返回信息:{}\", refund); if (refund.getStatus().equals(Status.SUCCESS)) { map.put(\"success\", true); map.put(\"msg\", \"退款成功!\"); //说明退款成功,开始接下来的业务操作 //主动查询 Wrapper againWrapper = new EntityWrapper(); againWrapper.eq(\"out_trade_no\", outTradeNo); WxOrderEntity orderEntity = wxOrderDataService.selectOne(againWrapper); if (orderEntity != null) {  orderEntity.setPayStatus(3);//退款成功  orderEntity.setUpdateTime(new Date());  wxOrderDataService.updateById(orderEntity);  //同时处理退款记录  Wrapper payWrapper = new EntityWrapper();  payWrapper.eq(\"out_trade_no\", outTradeNo);  payWrapper.eq(\"pay_status\", 2);//退款  WxPayLogEntity wxPayLogEntity = wxPayLogDataService.selectOne(payWrapper);  if (wxPayLogEntity == null) { wxPayLogEntity = new WxPayLogEntity(); wxPayLogEntity.setCreateTime(new Date()); wxPayLogEntity.setOutTradeNo(outTradeNo); wxPayLogEntity.setPayStatus(2); wxPayLogEntity.setTotalFee(totalAmount.intValue()); wxPayLogEntity.setTransactionId(wxOrderEntity.getTransactionId()); wxPayLogEntity.setOutRefundNo(outRefundNo); wxPayLogEntity.setWxOpenId(wxOrderEntity.getWxOpenId()); wxPayLogDataService.insert(wxPayLogEntity);  } } } } catch (ServiceException e) { log.error(\"退款失败!,错误信息:{}\", e.getMessage()); return R.error(\"退款失败!\"); } catch (Exception e) { log.error(\"服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}\", e.getMessage()); return R.error(\"退款失败!\"); } return R.ok().put(\"data\", map); }

8、退款回调通知 

待续......有需要的再联系我!

四、mysql表结构

SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ Table structure for t_wx_order-- ----------------------------DROP TABLE IF EXISTS `t_wx_order`;CREATE TABLE `t_wx_order` ( `uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, `trade_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT \'商品名称\', `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT \'订单描述\', `out_trade_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT \'(商户)订单流水号\', `transaction_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT \'微信订单号\', `total_fee` int(10) NULL DEFAULT NULL COMMENT \'订单金额(单位:分)\', `pay_nonce` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT \'支付成功后的随机32位字符串\', `pay_time` datetime NULL DEFAULT NULL COMMENT \'支付时间\', `pay_date` date NULL DEFAULT NULL COMMENT \'支付日期\', `pay_status` int(3) NULL DEFAULT 0 COMMENT \'0:待支付,1:支付成功,2:支付失败,3:退款成功,4:正在退款中,5:未知\', `wx_open_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT \'微信小程序openid\', `status` int(2) NULL DEFAULT 0 COMMENT \'0:未删除,1:已删除\', `create_time` datetime NULL DEFAULT NULL COMMENT \'创建订单时间\', `update_time` datetime NULL DEFAULT NULL COMMENT \'修改订单时间\', PRIMARY KEY (`uuid`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = \'微信商品订单表\' ROW_FORMAT = Dynamic;-- ------------------------------ Table structure for t_wx_pay_log-- ----------------------------DROP TABLE IF EXISTS `t_wx_pay_log`;CREATE TABLE `t_wx_pay_log` ( `uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL, `wx_open_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT \'微信用户openid\', `out_trade_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT \'(商户)订单流水号\', `out_refund_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT \'(商户)退款流水号\', `transaction_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT \'微信订单号\', `total_fee` int(10) NULL DEFAULT NULL COMMENT \'支付金额\', `pay_status` int(2) NULL DEFAULT NULL COMMENT \'1:支付,2:退款\', `create_time` datetime NULL DEFAULT NULL COMMENT \'创建时间\', PRIMARY KEY (`uuid`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = \'微信用户支付记录表\' ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;

五、controller类

退款回调通知,和controller就不写了,如果需要,联系我即可!

哎呀呀呀,缺少了一个类,代码如下:

import javax.servlet.ServletInputStream;import javax.servlet.http.HttpServletRequest;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;/** * @author caozhen * @ClassName HttpServletUtils * @description: TODO * @date 2024年01月04日 * @version: 1.0 */public class HttpServletUtils { /** * 获取请求体 * * @param request * @return * @throws IOException */ public static String getRequestBody(HttpServletRequest request) throws IOException { ServletInputStream stream = null; BufferedReader reader = null; StringBuffer sb = new StringBuffer(); try { stream = request.getInputStream(); // 获取响应 reader = new BufferedReader(new InputStreamReader(stream)); String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { throw new IOException(\"读取返回支付接口数据流出现异常!\"); } finally { reader.close(); } return sb.toString(); }}

如果你在微信支付回调通知中出现 “签名错误”,并且你是windows服务器,请在HttpServletUtils 类中,将reader = new BufferedReader(new InputStreamReader(stream));   替换成:reader = new BufferedReader(new InputStreamReader(stream,\"UTF-8\"));

替换完整代码:

import javax.servlet.ServletInputStream;import javax.servlet.http.HttpServletRequest;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;/** * @author caozhen * @ClassName HttpServletUtils * @description: TODO * @date 2024年01月04日 * @version: 1.0 */public class HttpServletUtils { /** * 获取请求体 * * @param request * @return * @throws IOException */ public static String getRequestBody(HttpServletRequest request) throws IOException { ServletInputStream stream = null; BufferedReader reader = null; StringBuffer sb = new StringBuffer(); try { stream = request.getInputStream(); // 获取响应 reader = new BufferedReader(new InputStreamReader(stream,\"UTF-8\")); String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { throw new IOException(\"读取返回支付接口数据流出现异常!\"); } finally { reader.close(); } return sb.toString(); }}

六、很多人问我要退款接口,来今日补充下! 

1.退款controller

 @RequestMapping(value = \"/xcx/createRefund\", method = RequestMethod.POST) @ApiOperation(value = \"微信支付-微信申请退款\") public ResponseEntity createRefund(String outTradeNo, Long totalAmount, String outRefundNo) { return ResponseEntity.ok(wxPayService.createRefund(outTradeNo, totalAmount, outRefundNo)); }

2.退款service

/*** * 微信申请退款 * @param outTradeNo 商户订单号 * @param totalAmount 退款金额,单位分 * @return */ public R createRefund(String outTradeNo, Long totalAmount, String outRefundNo) { log.info(\"申请退款参数,outTradeNo:{},totalAmount:{},outRefundNo:{}\", outTradeNo, totalAmount, outRefundNo); //返回参数 LinkedHashMap map = new LinkedHashMap(); map.put(\"out_trade_no\", outTradeNo); map.put(\"success\", false); map.put(\"msg\", \"退款失败!\"); if (StringUtils.isEmpty(outRefundNo)) { outRefundNo = \"REFUND_\" + outTradeNo; } map.put(\"out_refund_no\", outRefundNo); //申请退款订单,需要变更订单记录 Wrapper wrapper = new EntityWrapper(); wrapper.eq(\"out_trade_no\", outTradeNo); WxOrderEntity wxOrderEntity = wxOrderDataService.selectOne(wrapper); if (wxOrderEntity == null) { return R.error(\"订单不存在,申请退款不存在!\"); } wxOrderEntity.setPayStatus(4);//退款中 wxOrderEntity.setUpdateTime(new Date()); wxOrderDataService.updateById(wxOrderEntity); try { // 构建退款service RefundService service = new RefundService.Builder()  .config(rsaAutoCertificateConfig)  .build(); CreateRequest request = new CreateRequest(); // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义 request.setOutTradeNo(outTradeNo); request.setOutRefundNo(outRefundNo); AmountReq amount = new AmountReq(); amount.setTotal(wxOrderEntity.getTotalFee().longValue()); amount.setRefund(totalAmount); amount.setCurrency(\"CNY\"); request.setAmount(amount); request.setNotifyUrl(wxPayConfig.getRefundNotifyUrl()); //接收退款返回参数 Refund refund = service.create(request); log.info(\"退款返回信息:{}\", refund); //SUCCESS:退款成功,PROCESSING:正在处理中 if (refund.getStatus().equals(Status.SUCCESS)) { map.put(\"success\", true); map.put(\"msg\", \"退款成功!\"); map.put(\"transaction_id\", refund.getTransactionId()); //说明退款成功,开始接下来的业务操作 //主动查询 Wrapper againWrapper = new EntityWrapper(); againWrapper.eq(\"out_trade_no\", outTradeNo); WxOrderEntity orderEntity = wxOrderDataService.selectOne(againWrapper); if (orderEntity != null) {  orderEntity.setPayStatus(3);//退款成功  orderEntity.setUpdateTime(new Date());  wxOrderDataService.updateById(orderEntity);  //同时处理退款记录  Wrapper payWrapper = new EntityWrapper();  payWrapper.eq(\"out_trade_no\", outTradeNo);  payWrapper.eq(\"pay_status\", 2);//退款  WxPayLogEntity wxPayLogEntity = wxPayLogDataService.selectOne(payWrapper);  if (wxPayLogEntity == null) { wxPayLogEntity = new WxPayLogEntity(); wxPayLogEntity.setCreateTime(new Date()); wxPayLogEntity.setOutTradeNo(outTradeNo); wxPayLogEntity.setPayStatus(2); wxPayLogEntity.setTotalFee(totalAmount.intValue()); wxPayLogEntity.setTransactionId(refund.getTransactionId()); wxPayLogEntity.setOutRefundNo(outRefundNo); wxPayLogEntity.setWxOpenId(wxOrderEntity.getWxOpenId()); wxPayLogDataService.insert(wxPayLogEntity);  } } } else if (refund.getStatus().equals(Status.ABNORMAL)) { map.put(\"success\", false); map.put(\"msg\", \"退款失败,账户异常!\"); map.put(\"transaction_id\", refund.getTransactionId()); } else if (refund.getStatus().equals(Status.PROCESSING)) { map.put(\"success\", true); map.put(\"msg\", \"正在退款中!\"); map.put(\"transaction_id\", refund.getTransactionId()); } else if (refund.getStatus().equals(Status.CLOSED)) { map.put(\"success\", false); map.put(\"msg\", \"用户余额不足或者订单超过退款期限!\"); map.put(\"transaction_id\", refund.getTransactionId()); } } catch (ServiceException e) { log.error(\"退款失败!,错误信息:{}\", e.getErrorMessage()); return R.error(\"退款失败!错误信息:\" + e.getErrorMessage()); } catch (Exception e) { log.error(\"服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}\", e.getMessage()); return R.error(\"退款失败!错误信息:\" + e.getMessage()); } log.info(\"微信退款响应参数:{}\", map); return R.ok().put(\"data\", map); }

其中outRefundNo自定义退款号。

七、退款回调通知

 /*** * 微信退款回调 * @param request * @throws Exception */ public void refundNotify(HttpServletRequest request) throws Exception { try { log.info(\"------收到退款通知------\"); // 请求头Wechatpay-Signature String signature = request.getHeader(\"Wechatpay-Signature\"); // 请求头Wechatpay-nonce String nonce = request.getHeader(\"Wechatpay-Nonce\"); // 请求头Wechatpay-Timestamp String timestamp = request.getHeader(\"Wechatpay-Timestamp\"); // 微信支付证书序列号 String serial = request.getHeader(\"Wechatpay-Serial\"); // 签名方式 String signType = request.getHeader(\"Wechatpay-Signature-Type\"); // 构造 RequestParam RequestParam requestParam = new RequestParam.Builder()  .serialNumber(serial)  .nonce(nonce)  .signature(signature)  .timestamp(timestamp)  .signType(signType)  .body(HttpServletUtils.getRequestBody(request))  .build(); // 初始化 NotificationParser NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig); // 以支付通知回调为例,验签、解密并转换成 Transaction log.info(\"验签参数:{}\", requestParam); RefundNotification parse = parser.parse(requestParam, RefundNotification.class); log.info(\"验签成功!-退款回调结果:{}\", parse.toString()); //parse.getRefundStatus().equals(\"SUCCESS\");说明退款成功 String refundStatus = parse.getRefundStatus().toString(); log.info(\"getRefundStatus状态:{}\", refundStatus); if (refundStatus.equals(\"SUCCESS\")) { log.info(\"成功进入退款回调,状态:{}\", parse.getRefundStatus()); //你的业务代码 Wrapper wrapper = new EntityWrapper(); wrapper.eq(\"out_trade_no\", parse.getOutTradeNo()); WxOrderEntity wxOrderEntity = wxOrderDataService.selectOne(wrapper); log.info(\"订单数据:{}\", wxOrderEntity); if (wxOrderEntity != null) {  wxOrderEntity.setUpdateTime(new Date());  wxOrderEntity.setPayStatus(3);//退款成功  wxOrderEntity.setPayNonce(nonce);  wxOrderDataService.updateById(wxOrderEntity);  //同时处理退款记录  Wrapper payWrapper = new EntityWrapper();  payWrapper.eq(\"out_trade_no\", parse.getOutTradeNo());  payWrapper.eq(\"pay_status\", 2);//退款  payWrapper.eq(\"out_refund_no\", parse.getOutRefundNo());//退款  WxPayLogEntity wxPayLogEntity = wxPayLogDataService.selectOne(payWrapper);  if (wxPayLogEntity == null) { wxPayLogEntity = new WxPayLogEntity(); wxPayLogEntity.setCreateTime(new Date()); wxPayLogEntity.setOutTradeNo(parse.getOutTradeNo()); wxPayLogEntity.setPayStatus(2); wxPayLogEntity.setTotalFee(parse.getAmount().getRefund().intValue()); wxPayLogEntity.setTransactionId(parse.getTransactionId()); wxPayLogEntity.setOutRefundNo(parse.getOutRefundNo()); wxPayLogEntity.setWxOpenId(wxOrderEntity.getWxOpenId()); wxPayLogDataService.insert(wxPayLogEntity);  } } } } catch (Exception e) { log.info(\"退款回调失败!错误信息:{}\", e.getMessage()); } }
 @RequestMapping(value = \"/wx/refundNotify\", method = RequestMethod.POST) @ApiOperation(value = \"微信支付-微信退款回调通知\") public void refundNotify(HttpServletRequest request) throws Exception { wxPayService.refundNotify(request); }

八、错误汇总

1.目前微信支付回调或者退款回调中,出现返回这样的错误:Last unit does not have enough valid bits。

解决方案:如何验证签名 - 通用规则 | 微信支付商户文档中心

 2.下单接口 返回 系统繁忙,请稍后重试 

解决方案:请检查下传的参数,是否正确,仔细对照下。

具体链接:基础支付-报错“系统繁忙,请稍后重试” | 微信开放社区 

注意: 新商户号不再签发平台证书这个问题,也会导致微信支付下单出现系统繁忙,请稍后重试 ,具体查看官方解决方案:

关于新商户调用接口验签方式变更通知 | 微信开放社区 

有其他问题,我会持续更新!

一键三连,不会迷路!