feat 客户系统消息通知联调, 一些小问题修改

This commit is contained in:
xxm1995
2024-02-26 15:08:32 +08:00
parent fabea44a7b
commit d5e7dc1911
14 changed files with 101 additions and 43 deletions

View File

@@ -1,6 +1,7 @@
# CHANGELOG
## [v2.0.1]
- 增加支付、退款时客户通知功能,支持多次重发
- 开源文档增加支付通知和退款通知文档
- 增加客户通知任务记录功能
- 支持钱包支付、流水记录、各类操作等功能
- 支持储值卡支付、流水记录、各类操作等功能
@@ -10,7 +11,11 @@
- 变更:废弃调用接口时的`version`字段调用时不再进行传输SDK中同步进行删除
- 优化:订单支持关闭时间记录
- 优化:增加退款订单扩展记录
- fix: 同步支付通道订单不能正确生成
- 优化SDK增加简单退款、多通道退款等多中测试样例
- fix同步支付通道订单不能正确生成
- fix修复聚合条码支付时付款码未传输问题
- fix修复微信退款同步时, 错误信息未保存问题
- fix修复手动发起退款时上下文未进行初始化的问题
## [v2.0.0]
- 支持支付宝支付扫码支付、付款码支付、PC支付、H5支付

View File

@@ -5,34 +5,41 @@
- [x] 储值卡支付, 储值卡流水
- [x] 支付回调通知和退款回调通知功能
- [x] 回调任务发起
- [ ] 支付关闭发送回调
- [ ] 退款关闭发送回调
- [x] 回调调用记录
- [x] SDK接收对象编写
- [x] Demo模块接收回调演示
- [x] 通道配置启用停用配置生效
- [x] 支付对账的序列化生成器有问题待platform更新
2.0.2:
- [ ] 对账功能补全账单比对功能
- [ ] 数据库表进行规则, 字段设置长度, 增加索引
- [ ] 退款操作支持重试
2.0.x 版本内容
- [ ] 云闪付接入
- [ ] 退款操作支持重试
- [ ] 增加各类日志记录,例如钱包的各项操作
- [ ] 支付流程也改为先落库后支付情况, 避免极端情况导致掉单
- [ ] 支付流程涉及异步支付时, 更换支付方式需要控制预防客户重复付款
**任务池**
- [x] 支付SDK编写
- [x] 接入支付网关的演示项目
- [x] 增加聚合支付功能支持
- [x] 增加手机用户收银台功能
**任务池**
- [x] 支付SDK编写
- [x] 接入支付网关的演示项目
- [ ] 订单超时支持数据表级别触发
- [ ] 支付宝关闭支付时支持撤销方式,
- [ ] 支持转账操作, 通过支付通道专有参数进行实现, 转账时只能单个通道进行操作
- [ ] 支付成功回调后, 如果订单已超时, 则进入待退款订单中,提示进行退款,或者自动退款
- [ ] 增加回调机制(通知客户端)
- [ ] 增加消息通知机制(通知客户端)
- [ ] 新增支付单预警功能, 处理支付单与网关状态不一致且无法自动修复的情况
- [ ] 微信消息通知相关配置
- [ ] 钉钉消息通知配置
- [ ] 支付宝接口升级为V3接口
- [ ] 微信支付接口更新为V3接口
- [ ] 增加验签调试等功能的页面
- [ ] 请求IP参数增加正则校验
- [ ] 增加账户金额表, 记录每天每个通道的账户金额, 并且提供查询接口
- [ ] 订单超时支持数据表级别触发
- [ ] 支付宝关闭支付时支持撤销方式,
- [ ] 支持转账操作, 通过支付通道专有参数进行实现, 转账时只能单个通道进行操作
- [ ] 支付成功回调后, 如果订单超时, 则进入待退款订单中,提示进行退款,或者自动退款
- [x] 增加回调机制(通知客户端)
- [ ] 增加消息通知机制(通知客户端)
- [ ] 新增支付单预警功能, 处理支付单与网关状态不一致且无法自动修复的情况
- [ ] 微信消息通知相关配置
- [ ] 钉钉消息通知配置
- [ ] 支付宝接口升级为V3接口
- [ ] 微信支付接口更新为V3接口
- [ ] 增加验签调试等功能的页面
- [ ] 请求IP参数增加正则校验
- [ ] 增加账户金额表, 记录每天每个通道的账户金额, 并且提供查询接口

View File

@@ -17,6 +17,7 @@ import cn.bootx.platform.daxpay.sdk.model.pay.PayOrderModel;
import cn.bootx.platform.daxpay.sdk.net.DaxPayKit;
import cn.bootx.platform.daxpay.sdk.param.assist.WxAccessTokenParam;
import cn.bootx.platform.daxpay.sdk.param.assist.WxAuthUrlParam;
import cn.bootx.platform.daxpay.sdk.param.channel.BarCodePayParam;
import cn.bootx.platform.daxpay.sdk.param.channel.WeChatPayParam;
import cn.bootx.platform.daxpay.sdk.param.pay.SimplePayParam;
import cn.bootx.platform.daxpay.sdk.response.DaxPayResult;
@@ -217,6 +218,9 @@ public class AggregateService {
simplePayParam.setAmount(amount);
simplePayParam.setChannel(payChannel.getCode());
simplePayParam.setPayWay(PayWayEnum.BARCODE.getCode());
BarCodePayParam barCodePayParam = new BarCodePayParam();
barCodePayParam.setAuthCode(param.getAuthCode());
simplePayParam.setChannelParam(barCodePayParam);
String ip = Optional.ofNullable(WebServletUtil.getRequest())
.map(ServletUtil::getClientIP)

View File

@@ -61,6 +61,7 @@ public class SimplePayOrderTest {
param.setChannel(PayChannelEnum.CASH.getCode());
param.setPayWay(PayWayEnum.NORMAL.getCode());
param.setClientIp("127.0.0.1");
param.setAttach("Attach");
param.setNotifyUrl("http://127.0.0.1:9000/demo/callback/pay");
param.setNotifyUrl("http://127.0.0.1:9000/demo/callback/payObject");
DaxPayResult<PayOrderModel> execute = DaxPayKit.execute(param);

View File

@@ -18,7 +18,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
*
* 微信支付记录
* @author xxm
* @since 2024/2/19
*/

View File

@@ -1,8 +1,8 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.service;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.daxpay.code.RefundSyncStatusEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.code.RefundSyncStatusEnum;
import cn.bootx.platform.daxpay.service.code.WeChatPayCode;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
@@ -10,6 +10,7 @@ import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundOrder;
import cn.bootx.platform.daxpay.service.core.payment.sync.result.PayGatewaySyncResult;
import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.WxPayKit;
@@ -132,12 +133,31 @@ public class WeChatPaySyncService {
if (Objects.equals(tradeStatus, WeChatPayCode.REFUND_PROCESSING)) {
return syncResult.setSyncStatus(RefundSyncStatusEnum.PROGRESS);
}
return syncResult.setSyncStatus(RefundSyncStatusEnum.FAIL);
String errorMsg = this.getErrorMsg(result);
return syncResult.setSyncStatus(RefundSyncStatusEnum.FAIL).setErrorMsg(errorMsg);
} catch (Exception e) {
log.error("查询退款订单失败:", e);
syncResult.setSyncStatus(RefundSyncStatusEnum.PROGRESS).setErrorMsg(e.getMessage());
}
return syncResult;
}
/**
* 错误处理
*/
/**
* 验证错误信息
*/
private String getErrorMsg(Map<String, String> result) {
String returnCode = result.get(WeChatPayCode.RETURN_CODE);
String resultCode = result.get(WeChatPayCode.RESULT_CODE);
if (!WxPayKit.codeIsOk(returnCode) || !WxPayKit.codeIsOk(resultCode)) {
String errorMsg = result.get(WeChatPayCode.ERR_CODE_DES);
if (StrUtil.isBlank(errorMsg)) {
errorMsg = result.get(WeChatPayCode.RETURN_MSG);
}
return errorMsg;
}
return null;
}
}

View File

@@ -5,6 +5,7 @@ import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.result.pay.PayResult;
import cn.bootx.platform.daxpay.service.common.context.ApiInfoLocal;
import cn.bootx.platform.daxpay.service.common.context.PayLocal;
import cn.bootx.platform.daxpay.service.common.context.NoticeLocal;
import cn.bootx.platform.daxpay.service.common.context.PlatformLocal;
@@ -71,9 +72,11 @@ public class PayBuilder {
public PayOrderExtra buildPayOrderExtra(PayParam payParam, Long paymentId) {
PlatformLocal platform = PaymentContextLocal.get().getPlatformInfo();
NoticeLocal noticeInfo = PaymentContextLocal.get().getNoticeInfo();
ApiInfoLocal apiInfo = PaymentContextLocal.get().getApiInfo();
PayOrderExtra payOrderExtra = new PayOrderExtra()
.setClientIp(payParam.getClientIp())
.setDescription(payParam.getDescription())
.setNoticeSign(apiInfo.isNoticeSign())
.setNotifyUrl(noticeInfo.getNotifyUrl())
.setReturnUrl(noticeInfo.getReturnUrl())
.setReqSign(payParam.getSign())

View File

@@ -50,10 +50,10 @@ public class PayChannelOrderService {
}
/**
* 切换支付订单关联的异步支付通道, 同时会设置是否支付完成状态
* 切换支付订单关联的异步支付通道, 同时会设置是否支付完成状态, 并返回通道订单
*/
@Transactional(rollbackFor = Exception.class)
public void switchAsyncPayChannel(PayOrder payOrder, PayChannelParam payChannelParam){
public PayChannelOrder switchAsyncPayChannel(PayOrder payOrder, PayChannelParam payChannelParam){
PayLocal payInfo = PaymentContextLocal.get().getPayInfo();
// 是否支付完成
PayStatusEnum payStatus = payInfo.isPayComplete() ? PayStatusEnum.SUCCESS : PayStatusEnum.PROGRESS;
@@ -81,15 +81,18 @@ public class PayChannelOrderService {
channelOrderManager.deleteByPaymentIdAndAsync(payOrder.getId());
channelOrderManager.save(payChannelOrder);
payInfo.getPayChannelOrders().add(payChannelOrder);
return payChannelOrder;
} else {
// 更新支付通道信息
payOrderChannelOpt.get()
PayChannelOrder payChannelOrder = payOrderChannelOpt.get();
payChannelOrder
.setPayWay(payChannelParam.getWay())
.setPayTime(LocalDateTime.now())
.setChannelExtra(channelParamStr)
.setStatus(payStatus.getCode());
channelOrderManager.updateById(payOrderChannelOpt.get());
channelOrderManager.updateById(payChannelOrder);
payInfo.getPayChannelOrders().add(payOrderChannelOpt.get());
return payChannelOrder;
}
}

View File

@@ -13,6 +13,7 @@ import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderQueryService;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.service.core.payment.close.factory.PayCloseStrategyFactory;
import cn.bootx.platform.daxpay.service.core.payment.notice.service.ClientNoticeService;
import cn.bootx.platform.daxpay.service.core.record.close.entity.PayCloseRecord;
import cn.bootx.platform.daxpay.service.core.record.close.service.PayCloseRecordService;
import cn.bootx.platform.daxpay.service.func.AbsPayCloseStrategy;
@@ -44,6 +45,7 @@ public class PayCloseService {
private final PayOrderService payOrderService;
private final PayOrderQueryService payOrderQueryService;
private final PayCloseRecordService payCloseRecordService;
private final ClientNoticeService clientsService;;
private final LockTemplate lockTemplate;
@@ -76,9 +78,10 @@ public class PayCloseService {
* 关闭支付记录
*/
private void close(PayOrder payOrder) {
List<PayChannelOrder> payChannelOrders;
try {
// 状态检查, 只有支付中可以进行取消支付
if (!Objects.equals(payOrder.getStatus(), PayStatusEnum.PROGRESS.getCode())){
if (!Objects.equals(payOrder.getStatus(), PayStatusEnum.PROGRESS.getCode())) {
throw new PayFailureException("订单不是支付中, 无法进行关闭订单");
}
@@ -88,7 +91,8 @@ public class PayCloseService {
.collect(Collectors.toMap(PayChannelOrder::getChannel, Function.identity(), CollectorsFunction::retainLatest));
// 1.获取支付方式, 通过工厂生成对应的策略组
List<String> channels = orderChannelMap.values().stream()
List<String> channels = orderChannelMap.values()
.stream()
.map(PayChannelOrder::getChannel)
.collect(Collectors.toList());
List<AbsPayCloseStrategy> payCloseStrategies = PayCloseStrategyFactory.createAsyncLast(channels);
@@ -98,7 +102,8 @@ public class PayCloseService {
// 2.初始化关闭支付的参数
for (AbsPayCloseStrategy strategy : payCloseStrategies) {
strategy.initCloseParam(payOrder, orderChannelMap.get(strategy.getChannel().getCode()));
strategy.initCloseParam(payOrder, orderChannelMap.get(strategy.getChannel()
.getCode()));
}
// 3.关闭前准备
@@ -111,29 +116,30 @@ public class PayCloseService {
payCloseStrategies.forEach(AbsPayCloseStrategy::doSuccessHandler);
// 6.更新支付通道订单的状态
List<PayChannelOrder> payChannelOrders = payCloseStrategies.stream()
payChannelOrders = payCloseStrategies.stream()
.map(AbsPayCloseStrategy::getChannelOrder)
.collect(Collectors.toList());
payChannelOrderManager.updateAllById(payChannelOrders);
}
catch (PayFailureException e) {
} catch (PayFailureException e) {
// 记录关闭失败的记录
this.saveRecord(payOrder,false,e.getMessage());
this.saveRecord(payOrder, false, e.getMessage());
throw e;
}
// 关闭成功后处理
this.successHandler(payOrder);
this.successHandler(payOrder, payChannelOrders);
}
/**
* 成功后处理方法
*/
private void successHandler(PayOrder payOrder){
private void successHandler(PayOrder payOrder, List<PayChannelOrder> payChannelOrders){
// 取消订单
payOrder.setStatus(PayStatusEnum.CLOSE.getCode())
.setCloseTime(LocalDateTime.now());
payOrderService.updateById(payOrder);
// 发送通知
clientsService.registerPayNotice(payOrder,null, payChannelOrders);
this.saveRecord(payOrder,true,null);
}

View File

@@ -11,6 +11,7 @@ import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayConfig;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayConfigService;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayRecordService;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayService;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayChannelOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayChannelOrderService;
import cn.bootx.platform.daxpay.service.func.AbsPayStrategy;
import cn.hutool.core.bean.BeanUtil;
@@ -26,7 +27,7 @@ import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROT
/**
* 支付宝支付
*
* 注意: channelOrder对象需要单独处理, 直接获取会空指针
* @author xxm
* @since 2021/2/27
*/
@@ -102,10 +103,10 @@ public class AliPayStrategy extends AbsPayStrategy {
@Override
public void doSuccessHandler() {
PayLocal asyncPayInfo = PaymentContextLocal.get().getPayInfo();
channelOrderService.switchAsyncPayChannel(this.getOrder(), this.getPayChannelParam());
PayChannelOrder payChannelOrder = channelOrderService.switchAsyncPayChannel(this.getOrder(), this.getPayChannelParam());
// 支付完成, 保存记录
if (asyncPayInfo.isPayComplete()) {
aliRecordService.pay(this.getOrder(), this.getChannelOrder());
aliRecordService.pay(this.getOrder(), payChannelOrder);
}
}

View File

@@ -53,5 +53,4 @@ public class CashPayStrategy extends AbsPayStrategy {
public void doPayHandler() {
cashRecordService.pay(this.getChannelOrder(),this.getOrder().getTitle());
}
}

View File

@@ -9,6 +9,7 @@ import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConf
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayConfigService;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayRecordService;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayService;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayChannelOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayChannelOrderService;
import cn.bootx.platform.daxpay.service.func.AbsPayStrategy;
import cn.bootx.platform.daxpay.service.param.channel.wechat.WeChatPayParam;
@@ -24,7 +25,7 @@ import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROT
/**
* 微信支付
*
* 注意: channelOrder对象需要单独处理, 直接获取会空指针
* @author xxm
* @since 2021/4/5
*/
@@ -90,12 +91,12 @@ public class WeChatPayStrategy extends AbsPayStrategy {
*/
@Override
public void doSuccessHandler() {
channelOrderService.switchAsyncPayChannel(this.getOrder(), this.getPayChannelParam());
PayChannelOrder payChannelOrder = channelOrderService.switchAsyncPayChannel(this.getOrder(), this.getPayChannelParam());
this.getOrder().setAsyncChannel(this.getChannel().getCode());
PayLocal asyncPayInfo = PaymentContextLocal.get().getPayInfo();
// 是否支付完成, 保存流水记录
if (asyncPayInfo.isPayComplete()){
weChatPayRecordService.pay(this.getOrder(), this.getChannelOrder());
weChatPayRecordService.pay(this.getOrder(), payChannelOrder);
}
}

View File

@@ -18,6 +18,10 @@ import lombok.experimental.Accessors;
@Schema(title = "现金记录")
public class CashRecordDto extends BaseDto {
/** 标题 */
@Schema(description = "标题")
private String title;
/**
* 业务类型
* @see VoucherRecordTypeEnum

View File

@@ -22,6 +22,10 @@ public class VoucherRecordDto extends BaseDto {
@Schema(description = "储值卡id")
private Long voucherId;
/** 标题 */
@Schema(description = "标题")
private String title;
/**
* 业务类型
* @see VoucherRecordTypeEnum