引入微信支付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());
}
}
}

SpringBoot+微信支付-JSAPI{微信支付回调}的更多相关文章

  1. 微信 之jsapi实现支付

    一.微信公众号号后台支付配置 附微信支付参考文档:https://pay.weixin.qq.com/wiki/doc/api/index.html 二.微信支付类封装 该类可以实现付款码支付.JSA ...

  2. 微信支付JsAPI

    https://pay.weixin.qq.com/wiki/doc/api/download/WxpayAPI_php_v3.zip 下载获取微信支付demo压缩包 打开压缩包,并将其中 Wxpay ...

  3. 新版本 JSAPI微信支付V3 C# DEMO

    小弟在公众号后台无意中点了更新(微信支付接口升级)PS:想都没有想,心里还乐滋滋的免费的干嘛不升级...后果来了.面临着支付不能用了,代码需要重新更新. /** * JS_API支付demo * == ...

  4. PHP微信公众号JSAPI网页支付(下)

    上一篇PHP微信公众号JSAPI网页支付(上)中讲到了公众号平台的相关设置以及支付的大致流程. 这一篇重点讲支付后,异步接受回调通知,以及处理后同步通知微信服务器. 首先梳理下整个jsapi支付的流程 ...

  5. PHP微信公众号JSAPI网页支付(上)

    一.使用场景以及说明 使用场景:商户已有H5商城网站,用户通过消息或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程. 说明:1.用户打开图文消息或者扫描二维码,在微信内置浏览器打开网 ...

  6. jsapi微信支付

    JSAPI微信支付 引用js <script type="text/javascript" src="http://res.wx.qq.com/open/js/jw ...

  7. 微信JSAPI 公众号支付 H5支付以及APP支付 WEBAPI接口开发测试

    统一下单入口 调用该方法入口: public void WxPayAPI() { //string PayPrice ="99.9"; ////订单号 //string Payor ...

  8. JAVA开发微信支付-公众号支付/微信浏览器支付(JSAPI)

    写这篇文章的目的有2个,一是自己的项目刚开发完微信支付功能,趁热回个炉温习一下,二也是帮助像我这样对微信支付不熟悉,反复看了多天文档还是一知半解,原理都没摸清,更不要说实现了.本以为网上的微信开发教程 ...

  9. 微信支付配置参数:支付授权目录、回调支付URL

    一.开通微信支付的首要条件是:认证服务号或政府媒体类认证订阅号(一般认证订阅号无法申请微信支付) 二.微信支付分为老版支付和新版支付,除了较早期申请的用户为老版支付,现均为新版微信支付. 三.公众平台 ...

  10. 微信支付JSAPI支付

    1.介绍 JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付.应用场景有:    ◆ 用户在微信公众账号内进入商家公众号,打开某 ...

随机推荐

  1. 状压 DP 做题记录

    1.普通状态压缩 DP oi-wiki I.P1896 [SCOI2005] 互不侵犯 \(f_{i,j,st}\) 表示前 \(i\) 行中放置了 \(j\) 个国王,当前行状态为 \(st\) 的 ...

  2. 手把手带你使用Karpenter减少K8s集群资源浪费

    Kubernetes 集群的主要成本因素之一是数据平面上的计算层.将 Kubernetes 集群运行在 Amazon EC2 Spot 实例上是一种显著降低计算成本的有效方式.使用 Spot 实例可以 ...

  3. linxux学习01

    Linux第一天 1.为什么要学习linux? 因为大数据中绝大部分核心组件都是基于linux操作系统运行的,企业中基本上都是linux系统. 2.怎么去学linux?(什么是大数据) 大数据技术组件 ...

  4. python实现excel数据处理

    python xlrd读取excel(表格)详解 安装: pip install xlrd 官网地址: https://xlrd.readthedocs.io/ 介绍: 为开发人员提供一个库,用于从M ...

  5. law Intermediate walkthrough pg

    靶场很简单分数只有10分跟平常做的20分的中级靶场比确实简单 我拿来放松的 算下来30分钟解决战斗 nmap 扫到80端口web界面 是个框架 搜exp https://www.exploit-db. ...

  6. 从存钱罐到子数组:一个关于累加和的精妙问题|LeetCode 560 和为K的子数组

    LeetCode 560 和为K的子数组 点此看全部题解 LeetCode必刷100题:一份来自面试官的算法地图(题解持续更新中) 生活中的算法 你有没有这样的经历:每天往存钱罐里存一些零钱,某一天突 ...

  7. Kali 安装谷歌拼音

    Kali 安装谷歌拼音 1. 安装 Google 输入法 sudo apt install fcitx-googlepinyin 2. 重新启动系统 reboot 3. 打开开始菜单,搜索fcitx配 ...

  8. Kali Linux 安装教程

    Kali Linux 安装教程 下载镜像文件 Kali官网下载 访问Kali官网(https://www.kali.org/ ),根据下图所示进行下载 清华大学开源软件镜像站下载 访问清华大学开源软件 ...

  9. 手把手教你喂养 DeepSeek 本地模型

    上篇文章<手把手教你部署 DeepSeek 本地模型>首发是在公众号,但截止目前只有500多人阅读量,而在自己博客园BLOG同步更新的文章热度很高,目前已达到50000+的阅读量,流量是公 ...

  10. 【忍者算法】从生活场景理解链表反转:最重要的基础算法|LeetCode第206题 反转链表

    从生活场景理解链表反转:最重要的基础算法 为什么这道题如此重要 反转链表看似简单,却是链表操作的基石.就像建房子要先打好地基,做复杂的链表操作前必须深刻理解反转原理.无数高频面试题都建立在这个基础之上 ...