SpringBoot+微信支付-JSAPI{微信支付回调}
引入微信支付SDK
Maven: com.github.wechatpay-apiv3:wechatpay-java-core:0.2.12
Maven: com.github.wechatpay-apiv3:wechatpay-java:0.2.12
响应微信回调的封装
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class WxNotifyVo {
private String code;
private String message;
}
微信回调接口必须是:HTTPS的接口
@RestController
@RequestMapping("/wxpay/notify")
public class WxPayNotifyController {
private static Logger logger = LoggerFactory.getLogger(WxPayNotifyController.class);
@Autowired
private WxPayCallBackNotifyService wxPayCallBackNotifyService;
/**
* 支付结果:回调接口通知
*
* @return
*/
@RequestMapping("/pay/{merchantNo}")
public WxNotifyVo pay(@PathVariable("merchantNo") String merchantNo, HttpServletRequest request) throws Exception {
logger.info("微信支付回调通知------->");
return wxPayCallBackNotifyService.dealPayResultV3(request,merchantNo);
}
/**
* 退款结果通知
*
* @return
*/
@RequestMapping("/refund/{merchantNo}")
public WxNotifyVo refund(@PathVariable("merchantNo") String merchantNo,HttpServletRequest request) throws Exception {
logger.info("微信退款回调通知------->");
return wxPayCallBackNotifyService.dealRefundResultV3(request,merchantNo);
}
}
微信回调实现
package xxxx.xxxx.cashier.payChannel.callback.handler;
import cn.hutool.core.date.DateUtil;
import xxxx.common.domain.model.exception.BusinessException;
import xxxx.xxxx.cashier.common.LocalTimeUtils;
import xxxx.xxxx.cashier.common.runable.NotifyThreadRunnable;
import xxxx.xxxx.cashier.domain.dto.ChannelRCRate;
import xxxx.xxxx.cashier.domain.model.CashierPayment;
import xxxx.xxxx.cashier.domain.model.PayTypeEnum;
import xxxx.xxxx.cashier.notifyRecord.application.NotifyRecordService;
import xxxx.xxxx.cashier.payChannel.callback.vo.WxNotifyVo;
import xxxx.xxxx.cashier.payChannel.handler.OperationWxHandler;
import xxxx.xxxx.cashier.payment.application.MerchantRefundHandlerService;
import xxxx.xxxx.cashier.payment.application.ModifyAccountService;
import xxxx.xxxx.cashier.payment.application.PaymentService;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import static com.wechat.pay.java.core.http.Constant.*;
/**
* packageName xxxx.xxxx.cashier.payChannel.callback.handler
*
* @author GuoTong
* @version JDK 8
* @className WxPayCallBackNotifyService
* @date 2024/3/22
* @description 微信支付通知回调业务处理
*/
@Component
@SuppressWarnings("all")
public class WxPayCallBackNotifyService {
private static Logger log = LoggerFactory.getLogger(WxPayCallBackNotifyService.class);
@Autowired
private OperationWxHandler operationWxHandler;
@Autowired
private TaskExecutor taskExecutor;
@Autowired
private NotifyRecordService notifyRecordService;
@Autowired
private PaymentService paymentService;
@Autowired
private ModifyAccountService modifyAccountService;
@Autowired
private MerchantRefundHandlerService merchantRefundHandlerService;
/**
* 对账之后订单的费率计算结果----入账{调用方若批量请加事务处理}
* 调用支付渠道结算流程
*
* @return
*/
@Transactional
public boolean orderRateSupplement(String paymentNo, ChannelRCRate channelRCRate, Date time) {
log.info("入账-----orderRateSupplement-----paymentNo:{}", paymentNo);
if (StringUtils.isBlank(paymentNo)) {
log.error("paymentNo is null|订单号不能为空");
return false;
}
CashierPayment cashierPayment = paymentService.queryByPamentNo(paymentNo);
if (cashierPayment == null) {
log.error("paymentNo not exist|订单不存在");
return false;
}
// 设置渠道手续费
if (channelRCRate.getChannelROrMBalance()) {
// 渠道对账入账
cashierPayment.setChannelServiceFee(channelRCRate.getChannelServiceFee());
cashierPayment.setCheckStatus(channelRCRate.getCheckStatus());
cashierPayment.setCheckTime(channelRCRate.getCheckTime());
cashierPayment.setCheckAmount(channelRCRate.getCheckAmount());
if (channelRCRate.getCheckStatus() == 1) {
// 渠道结算
cashierPayment.setSettleStatus(1);
cashierPayment.setSettleDate(new Date());
// 订单金额 - 渠道手续费 = 结算金额
long channelSettleAmount = cashierPayment.getAmount() - channelRCRate.getChannelServiceFee().longValue();
cashierPayment.setSettleAmount(channelSettleAmount);
}
} else {
cashierPayment.setSettleAmount(channelRCRate.getSettleAmount());
cashierPayment.setSettleDate(channelRCRate.getSettleDate());
cashierPayment.setSettleStatus(channelRCRate.getSettleStatus());
}
// 获取交易的退款总金额(原始交易记录流水号)
String channelRefundFlowNo = cashierPayment.getChannelFlowNo();
Date beginOfDay = DateUtil.beginOfDay(time);
String beginDay = DateUtil.format(beginOfDay, LocalTimeUtils.WX_SUCCESS_TIME_FORMATTER);
Date endOfDay = DateUtil.endOfDay(time);
String endDay = DateUtil.format(endOfDay, LocalTimeUtils.WX_SUCCESS_TIME_FORMATTER);
List<CashierPayment> cashierPaymentRefunds = paymentService.queryForThatOrderRefundInfo(channelRefundFlowNo, beginDay, endDay);
BigDecimal refundAmountTotalFee = BigDecimal.ZERO;
cashierPayment.setRefundFeeAmountCount(channelRCRate.getChannelServiceFee() == null ? BigDecimal.ZERO : channelRCRate.getChannelServiceFee());
if (CollectionUtils.isNotEmpty(cashierPaymentRefunds)) {
Long refundAmountTotal = new Long(0);
// 结算金额--原订单(不包含退款)
BigDecimal checkAmount = channelRCRate.getCheckAmount();
for (CashierPayment refund : cashierPaymentRefunds) {
Long refundAmount = refund.getRefundAmount();
BigDecimal channelServiceFee = refund.getChannelServiceFee();
if (channelServiceFee == null) {
channelServiceFee = BigDecimal.ZERO;
}
if (refundAmount == null) {
refundAmount = new Long(0);
}
checkAmount = checkAmount.subtract(new BigDecimal(refundAmount));
refundAmountTotalFee = refundAmountTotalFee.add(channelServiceFee);
refundAmountTotal += refundAmount;
}
if (checkAmount.compareTo(BigDecimal.ZERO) <= 0) {
cashierPayment.setCheckAmount(BigDecimal.ZERO);
}
cashierPayment.setRefundFeeAmountCount(cashierPayment.getRefundFeeAmountCount().multiply(refundAmountTotalFee));
cashierPayment.setRefundAmount(refundAmountTotal);
}
// 设置商户未结算
cashierPayment.setMerchantRefundStatus(0);
// 订单流痕
paymentService.update(cashierPayment);
// 执行支付渠道结算流程---动账通知
modifyAccountService.channelSettleHandler(cashierPayment);
return true;
}
/**
* 微信支付的回调通知
* 首先,你需要在你的服务器上创建一个公开的 HTTP 端点,接受来自微信支付的回调通知。 当接收到回调通知,使用 notification 中的 NotificationParser 解析回调通知。
* <p>
* 具体步骤如下:
* <p>
* 使用回调通知请求的数据,构建 RequestParam。
* HTTP 请求体 body。切记使用原始报文,不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。
* HTTP 头 Wechatpay-Signature。应答的微信支付签名。
* HTTP 头 Wechatpay-Serial。微信支付平台证书的序列号,验签必须使用序列号对应的微信支付平台证书。
* HTTP 头 Wechatpay-Nonce。签名中的随机数。
* HTTP 头 Wechatpay-Timestamp。签名中的时间戳。
* HTTP 头 Wechatpay-Signature-Type。签名类型。
* 初始化 RSAAutoCertificateConfig。微信支付平台证书由 SDK 的自动更新平台能力提供,也可以使用本地证书。
* 初始化 NotificationParser。
* 调用 NotificationParser.parse() 验签、解密并将 JSON 转换成具体的通知回调对象。如果验签失败,SDK 会抛出 ValidationException。
* 接下来可以执行你的业务逻辑了。如果执行成功,你应返回 200 OK 的状态码。如果执行失败,你应返回 4xx 或者 5xx的状态码,例如数据库操作失败建议返回 500 Internal Server Error。
* | 多商户支持
* @param request HttpServletRequest
* @return WxNotifyVo
*/
public WxNotifyVo dealPayResultV3(HttpServletRequest request,String merchantNo) {
try (ServletInputStream inputStream = request.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
// 读取请求体
StringBuilder sb = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
String notify = sb.toString();
log.info("微信支付通知: {}", notify);
RequestParam requestParam = buildByHeaderRequestParam(request, notify);
// 初始化 RSAAutoCertificateConfig。微信支付平台证书由 SDK 的自动更新平台能力提供,也可以使用本地证书。
NotificationConfig config = operationWxHandler.getCallBackConfig(merchantNo,PayTypeEnum.WECHAT);
// 验证签名并解析请求体
NotificationParser notificationParser = new NotificationParser(config);
// 调用 NotificationParser.parse() 验签、解密并将 JSON 转换成具体的通知回调对象。如果验签失败,SDK 会抛出 ValidationException。
log.info("支付通知回调:验签、解密并转换成 Transaction对象:-------");
Transaction transaction = null;
try {
/**
* 常用的通知回调调对象类型有:
* 支付 Transaction
* 退款 RefundNotification
* 若 SDK 暂不支持的类型,请使用 Map.class,嵌套的 Json 对象将被转换成 LinkedTreeMap
*/
transaction = notificationParser.parse(requestParam, Transaction.class);
} catch (Exception e) {
throw new BusinessException("支付通知回调,验签、解密失败--->" + e.getMessage());
}
log.info("Transaction:===>>>>支付CallBack状态" + transaction.getTradeState());
log.info("Transaction:===>>>>" + transaction);
// 开启线程异步通知业务侧
taskExecutor.execute(new NotifyThreadRunnable(transaction, paymentService, notifyRecordService));
// 接收消息成功
return new WxNotifyVo().setCode("SUCCESS");
} catch (Exception e) {
log.error("解析付款通知出错:{}", e.getMessage(), e);
return new WxNotifyVo().setCode("FAIL").setMessage(e.getMessage());
}
}
/**
* 使用回调通知请求的数据,构建 RequestParam。
*
* @param request HttpServletRequest
* @param notify notify
* @return notify
*/
public RequestParam buildByHeaderRequestParam(HttpServletRequest request, String notify) {
log.info("-------------------WxPay---------GetHeader--------------------BEGIN");
// HTTP 头 Wechatpay-Timestamp。签名中的时间戳。
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
// HTTP 头 Wechatpay-Nonce。签名中的随机数。
String nonce = request.getHeader(WECHAT_PAY_NONCE);
// HTTP 头 Wechatpay-Signature-Type。签名类型。
String signType = request.getHeader("Wechatpay-Signature-Type");
//HTTP 头 Wechatpay-Serial。微信支付平台证书的序列号,验签必须使用序列号对应的微信支付平台证书。
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
// HTTP 头 Wechatpay-Signature。应答的微信支付签名。
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
log.info("-------------------WxPay---------GetHeader--------------------ENDING");
// 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.signType(signType)
.body(notify)
.build();
return requestParam;
}
/**
* 处理退款结果| 多商户支持
*
* @param request HttpServletRequest
* @return WxNotifyVo
*/
public WxNotifyVo dealRefundResultV3(HttpServletRequest request,String merchantNo) {
try (ServletInputStream inputStream = request.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
// 读取请求体
StringBuilder sb = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
String notify = sb.toString();
log.info("微信退款通知: {}", notify);
RequestParam requestParam = buildByHeaderRequestParam(request, notify);
// 初始化 RSAAutoCertificateConfig。微信支付平台证书由 SDK 的自动更新平台能力提供,也可以使用本地证书。
NotificationConfig config = operationWxHandler.getCallBackConfig(merchantNo,PayTypeEnum.WECHAT);
// 验证签名并解析请求体
NotificationParser notificationParser = new NotificationParser(config);
// 调用 NotificationParser.parse() 验签、解密并将 JSON 转换成具体的通知回调对象。如果验签失败,SDK 会抛出 ValidationException。
log.info("退款通知回调:验签、解密并转换成 RefundNotification对象:-------");
RefundNotification refundNotification = null;
try {
/**
* 常用的通知回调调对象类型有:
* 支付 Transaction
* 退款 RefundNotification
* 若 SDK 暂不支持的类型,请使用 Map.class,嵌套的 Json 对象将被转换成 LinkedTreeMap
*/
refundNotification = notificationParser.parse(requestParam, RefundNotification.class);
} catch (Exception e) {
throw new BusinessException("退款通知回调,验签、解密失败(可能是微信官方的探测流量故意生成的错误签名)--->" + e.getMessage());
}
log.info("Transaction:===>>>>退款CallBack状态" + refundNotification.getRefundStatus());
log.info("Transaction:===>>>>" + refundNotification);
// 开启线程异步通知业务侧
taskExecutor.execute(new NotifyThreadRunnable(refundNotification, paymentService, notifyRecordService));
log.info("退款通知回调:验签、解密并转换成 RefundNotification对象:-------end");
// 解析消息成功
return new WxNotifyVo().setCode("SUCCESS");
} catch (Exception e) {
log.error("解析退款通知出错:{}", e.getMessage(), e);
return new WxNotifyVo().setCode("FAIL").setMessage(e.getMessage());
}
}
}
作者:隔壁老郭
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
Java入门到入坟
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!