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());
}
}
}
SpringBoot+微信支付-JSAPI{微信支付回调}的更多相关文章
- 微信 之jsapi实现支付
一.微信公众号号后台支付配置 附微信支付参考文档:https://pay.weixin.qq.com/wiki/doc/api/index.html 二.微信支付类封装 该类可以实现付款码支付.JSA ...
- 微信支付JsAPI
https://pay.weixin.qq.com/wiki/doc/api/download/WxpayAPI_php_v3.zip 下载获取微信支付demo压缩包 打开压缩包,并将其中 Wxpay ...
- 新版本 JSAPI微信支付V3 C# DEMO
小弟在公众号后台无意中点了更新(微信支付接口升级)PS:想都没有想,心里还乐滋滋的免费的干嘛不升级...后果来了.面临着支付不能用了,代码需要重新更新. /** * JS_API支付demo * == ...
- PHP微信公众号JSAPI网页支付(下)
上一篇PHP微信公众号JSAPI网页支付(上)中讲到了公众号平台的相关设置以及支付的大致流程. 这一篇重点讲支付后,异步接受回调通知,以及处理后同步通知微信服务器. 首先梳理下整个jsapi支付的流程 ...
- PHP微信公众号JSAPI网页支付(上)
一.使用场景以及说明 使用场景:商户已有H5商城网站,用户通过消息或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程. 说明:1.用户打开图文消息或者扫描二维码,在微信内置浏览器打开网 ...
- jsapi微信支付
JSAPI微信支付 引用js <script type="text/javascript" src="http://res.wx.qq.com/open/js/jw ...
- 微信JSAPI 公众号支付 H5支付以及APP支付 WEBAPI接口开发测试
统一下单入口 调用该方法入口: public void WxPayAPI() { //string PayPrice ="99.9"; ////订单号 //string Payor ...
- JAVA开发微信支付-公众号支付/微信浏览器支付(JSAPI)
写这篇文章的目的有2个,一是自己的项目刚开发完微信支付功能,趁热回个炉温习一下,二也是帮助像我这样对微信支付不熟悉,反复看了多天文档还是一知半解,原理都没摸清,更不要说实现了.本以为网上的微信开发教程 ...
- 微信支付配置参数:支付授权目录、回调支付URL
一.开通微信支付的首要条件是:认证服务号或政府媒体类认证订阅号(一般认证订阅号无法申请微信支付) 二.微信支付分为老版支付和新版支付,除了较早期申请的用户为老版支付,现均为新版微信支付. 三.公众平台 ...
- 微信支付JSAPI支付
1.介绍 JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付.应用场景有: ◆ 用户在微信公众账号内进入商家公众号,打开某 ...
随机推荐
- uniapp同城社区交友 仿小红书 APP小程序源码 含后台管理和网页端
注意(预防被骗) 本程序仅在 破晓店铺(https://shop.abyssdawn.com/).破晓一代网络科技淘宝店 出售其余地方均为骗子. 关于本程序 本程序适用于各种同城社区交友类产品,例如同 ...
- 理解IM消息“可靠性”和“一致性”问题,以及解决方案探讨
本文作者"商文默",本次有修订和改动. 1.写在前面 即时通讯网整理的大量IM技术文章中(见本文末"参考资料"一节),有关消息可靠性和一致性问题的文章占了很大比 ...
- 思维导图学《Java性能权威指南》
目录 性能测试 Java 性能调优工具箱 JIT 编译器 垃圾收集 原生内存 线程与同步的性能 Java API 技巧 GitHub LeetCode 项目 目录 YANO SPACE 2021 计划 ...
- Github + Jekyll 搭建项目wiki
网站托管 创建新仓库 创建以自己名字为前缀, .github.io为后缀的仓库 在仓库的Settings中的Pages里设置Build and deployment为Github Actio ...
- AngleSharp 自带的HttpRequest参数设置
AngleSharp自带一个获取网址源码的api,可以方便的从web取得html var config = Configuration.Default.WithDefaultLoader(); var ...
- mina保持android端\服务端的长连接-copy
一.mina简介 Apache Mina是一个能够帮助用户开发高性能和高伸缩性网络应用程序的框架.与Netty出自同一人之手,都是一个介于应用程序与网络之间的NIO框架,通过Java nio技术基于T ...
- Kotlin:【异常处理】自定义异常、先决条件函数
- Node.js 中 mysql 事务的写法
最近做一个公司内部的信息化平台,本着短平快,选择了 Nodejs + Express + Vue + mysql/mongodb 的技术路线. 该写法主要利用了递归,下面把事务的写法记录一下,做了简单 ...
- Ping测试记录脚本
@echo off echo PingTest del PingTest_result.txt timeout /t 10 echo=> PingTest_result.txt :TEST ec ...
- ReentrantLock实现机制
掌握Reentrantlock 具体结构 下文Reentrantlock简称RL,阅读之前强烈建议读一下AQS源码解析: https://www.cnblogs.com/seamount3/p/186 ...