ref 支付同步逻辑, 支付单状态不一致修复逻辑, 单商户单独拆包

This commit is contained in:
nws
2023-12-27 22:14:01 +08:00
parent 21bf0e40a3
commit af34eb9af2
99 changed files with 820 additions and 264 deletions

View File

@@ -1,32 +0,0 @@
package cn.bootx.platform.daxpay.code;
/**
* 支付网关同步状态
*
* @author xxm
* @since 2021/4/21
*/
public interface PaySyncStatus {
/** 不需要同步 */
String NOT_SYNC = "not_sync";
/** 远程支付成功 */
String TRADE_SUCCESS = "trade_success";
/** 交易创建,等待买家付款 */
String WAIT_BUYER_PAY = "wait_buyer_pay";
/** 已关闭 */
String TRADE_CLOSED = "trade_closed";
/** 已退款 */
String TRADE_REFUND = "trade_refund";
/** 查询不到订单 */
String NOT_FOUND = "not_found";
/** 查询失败 */
String FAIL = "fail";
}

View File

@@ -1,5 +1,6 @@
package cn.bootx.platform.daxpay.common.context;
import cn.bootx.platform.daxpay.code.PayWayEnum;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -14,12 +15,21 @@ import java.time.LocalDateTime;
@Accessors(chain = true)
public class AsyncPayLocal {
/** 异步支付方式 */
private PayWayEnum payWay;
/**
* 第三方支付平台订单号
* 1. 如付款码支付直接成功时会出现
* 2. 回调或者支付同步时也会有这个值
*/
private String tradeNo;
/** 支付参数体(通常用于发起支付的参数) */
private String payBody;
/** 第三方支付平台订单号(如付款码支付直接成功时会出现) */
private String tradeNo;
/** 订单失效时间, 优先用这个 */
private LocalDateTime expiredTime;

View File

@@ -21,8 +21,8 @@ public class PaymentContext {
/** 异步支付相关信息 */
private final AsyncPayLocal asyncPayInfo = new AsyncPayLocal();
/** 退款相关信息 */
private final AsyncRefundLocal refundInfo = new AsyncRefundLocal();
/** 异步退款相关信息 */
private final AsyncRefundLocal asyncRefundInfo = new AsyncRefundLocal();
/** 消息通知相关信息 */
private final NoticeLocal noticeInfo = new NoticeLocal();

View File

@@ -24,6 +24,9 @@ public class AliPayOrder extends BasePayOrder implements EntityBaseFunction<AliP
/** 支付宝交易号 */
private String tradeNo;
/** 支付方式 */
private String payWay;
@Override
public AliPaymentDto toDto() {
AliPaymentDto dto = new AliPaymentDto();

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.core.channel.alipay.service;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.common.context.AsyncPayLocal;
import cn.bootx.platform.daxpay.common.entity.OrderRefundableInfo;
import cn.bootx.platform.daxpay.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.core.channel.alipay.dao.AliPayOrderManager;
import cn.bootx.platform.daxpay.core.channel.alipay.entity.AliPayOrder;
@@ -10,8 +10,6 @@ import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderChannelManager;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderChannel;
import cn.bootx.platform.daxpay.common.entity.OrderRefundableInfo;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -46,8 +44,7 @@ public class AliPayOrderService {
* 支付调起成功 更新payment中异步支付类型信息, 如果支付完成, 创建支付宝支付单
*/
public void updatePaySuccess(PayOrder payOrder, PayWayParam payWayParam) {
AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo();
payOrder.setAsyncPayMode(true)
payOrder.setAsyncPay(true)
.setAsyncPayChannel(PayChannelEnum.ALI.getCode());
// 更新支付宝异步支付类型信息
@@ -80,29 +77,31 @@ public class AliPayOrderService {
payOrder.setRefundableInfos(refundableInfos);
// 如果支付完成(付款码情况) 调用 updateSyncSuccess 创建支付宝支付记录
if (Objects.equals(payOrder.getStatus(), PayStatusEnum.SUCCESS.getCode())) {
this.createAliPayment(payOrder, payWayParam, asyncPayInfo.getTradeNo());
this.updateAsyncSuccess(payOrder, payWayParam.getAmount());
}
}
/**
* 更新异步支付记录成功状态, 并创建支付宝支付记录
*/
public void updateAsyncSuccess(Long id, PayWayParam payWayParam, String tradeNo) {
// 更新支付记录
PayOrder payOrder = payOrderManager.findById(id).orElseThrow(() -> new PayFailureException("支付记录不存在"));
this.createAliPayment(payOrder,payWayParam,tradeNo);
public void updateAsyncSuccess(PayOrder payOrder, Integer amount) {
// 创建支付宝支付订单
this.createAliPayOrder(payOrder,amount);
}
/**
* 创建支付宝支付记录(支付调起成功后才会创建)
* 创建支付宝支付订单(支付成功后才会创建)
*/
private void createAliPayment(PayOrder payOrder, PayWayParam payWayParam, String tradeNo) {
// 创建支付宝支付记录
private void createAliPayOrder(PayOrder payOrder, Integer amount) {
String tradeNo = PaymentContextLocal.get()
.getAsyncPayInfo()
.getTradeNo();
AliPayOrder aliPayOrder = new AliPayOrder();
aliPayOrder.setTradeNo(tradeNo)
.setPaymentId(payOrder.getId())
.setAmount(payWayParam.getAmount())
.setRefundableBalance(payWayParam.getAmount())
.setAmount(amount)
.setRefundableBalance(amount)
.setBusinessNo(payOrder.getBusinessNo())
.setStatus(PayStatusEnum.SUCCESS.getCode())
.setPayTime(LocalDateTime.now());

View File

@@ -37,7 +37,7 @@ public class AliPayRefundService {
refundModel.setRefundAmount(refundAmount);
// 设置退款信息
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getRefundInfo();
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getAsyncRefundInfo();
refundInfo.setRefundNo(IdUtil.getSnowflakeNextIdStr());
refundModel.setOutRequestNo(refundInfo.getRefundNo());
try {

View File

@@ -1,8 +1,8 @@
package cn.bootx.platform.daxpay.core.channel.alipay.service;
import cn.bootx.platform.daxpay.code.AliPayCode;
import cn.bootx.platform.daxpay.code.PaySyncStatus;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import cn.hutool.json.JSONUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeQueryModel;
@@ -29,8 +29,8 @@ public class AlipaySyncService {
/**
* 与支付宝网关同步状态 1 远程支付成功 2 交易创建,等待买家付款 3 超时关闭 4 查询不到 5 查询失败
*/
public PaySyncResult syncPayStatus(Long paymentId) {
PaySyncResult paySyncResult = new PaySyncResult().setPaySyncStatus(PaySyncStatus.FAIL);
public SyncResult syncPayStatus(Long paymentId) {
SyncResult syncResult = new SyncResult().setSyncStatus(PaySyncStatusEnum.FAIL.getCode());
// 查询
try {
@@ -39,35 +39,35 @@ public class AlipaySyncService {
// 查询退款参数
AlipayTradeQueryResponse response = AliPayApi.tradeQueryToResponse(queryModel);
String tradeStatus = response.getTradeStatus();
paySyncResult.setJson(JSONUtil.toJsonStr(response));
syncResult.setJson(JSONUtil.toJsonStr(response));
// 支付完成
if (Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_SUCCESS)
|| Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_FINISHED)) {
HashMap<String, String> map = new HashMap<>(1);
map.put(AliPayCode.TRADE_NO, response.getTradeNo());
return paySyncResult.setPaySyncStatus(PaySyncStatus.TRADE_SUCCESS).setMap(map);
return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_SUCCESS.getCode()).setMap(map);
}
// 待支付
if (Objects.equals(tradeStatus, AliPayCode.PAYMENT_WAIT_BUYER_PAY)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.WAIT_BUYER_PAY);
return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_WAIT.getCode());
}
// 已关闭
if (Objects.equals(tradeStatus, AliPayCode.PAYMENT_TRADE_CLOSED)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.TRADE_CLOSED);
return syncResult.setSyncStatus(PaySyncStatusEnum.CLOSED.getCode());
}
// 未找到
if (Objects.equals(response.getSubCode(), AliPayCode.ACQ_TRADE_NOT_EXIST)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.NOT_FOUND);
return syncResult.setSyncStatus(PaySyncStatusEnum.NOT_FOUND.getCode());
}
// 退款 支付宝查不到
}
catch (AlipayApiException e) {
log.error("查询订单失败:", e);
paySyncResult.setMsg(e.getErrMsg());
syncResult.setMsg(e.getErrMsg());
}
return paySyncResult;
return syncResult;
}
}

View File

@@ -62,7 +62,7 @@ public class WeChatPayCloseService {
errorMsg = result.get(WeChatPayCode.RETURN_MSG);
}
log.error("订单关闭失败 {}", errorMsg);
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getRefundInfo();
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getAsyncRefundInfo();
refundInfo.setErrorMsg(errorMsg);
refundInfo.setErrorCode(Optional.ofNullable(resultCode).orElse(returnCode));
throw new PayFailureException(errorMsg);

View File

@@ -42,7 +42,7 @@ public class WeChatPayOrderService {
*/
public void updatePaySuccess(PayOrder payOrder, PayWayParam payWayParam) {
AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo();;
payOrder.setAsyncPayMode(true).setAsyncPayChannel(PayChannelEnum.WECHAT.getCode());
payOrder.setAsyncPay(true).setAsyncPayChannel(PayChannelEnum.WECHAT.getCode());
List<PayOrderChannel> payTypeInfos = new ArrayList<>();
List<OrderRefundableInfo> refundableInfos = new ArrayList<>();

View File

@@ -12,7 +12,7 @@ import cn.bootx.platform.daxpay.common.context.AsyncPayLocal;
import cn.bootx.platform.daxpay.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult;
import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import cn.bootx.platform.daxpay.core.payment.sync.service.PaySyncService;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.channel.wechat.WeChatPayParam;
@@ -40,7 +40,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import static cn.bootx.platform.daxpay.code.PaySyncStatus.WAIT_BUYER_PAY;
import static cn.bootx.platform.daxpay.code.PaySyncStatusEnum.PAY_WAIT;
import static com.ijpay.wxpay.model.UnifiedOrderModel.UnifiedOrderModelBuilder;
import static com.ijpay.wxpay.model.UnifiedOrderModel.builder;
@@ -272,14 +272,14 @@ public class WeChatPayService {
*/
@Async("bigExecutor")
@Retryable(value = RetryableException.class, maxAttempts = 10, backoff = @Backoff(value = 5000L))
public void rotationSync(PayOrder payment, WeChatPayConfig weChatPayConfig) {
PaySyncResult paySyncResult = weChatPaySyncService.syncPayStatus(payment.getId(), weChatPayConfig);
public void rotationSync(PayOrder payOrder, WeChatPayConfig weChatPayConfig) {
SyncResult syncResult = weChatPaySyncService.syncPayStatus(payOrder.getId(), weChatPayConfig);
// 不为支付中状态后, 调用系统同步更新状态, 支付状态则继续重试
if (Objects.equals(WAIT_BUYER_PAY, paySyncResult.getPaySyncStatus())) {
if (Objects.equals(PAY_WAIT.getCode(), syncResult.getSyncStatus())) {
throw new RetryableException();
}
else {
paySyncService.sync(payment);
paySyncService.syncPayOrder(payOrder);
}
}

View File

@@ -1,9 +1,9 @@
package cn.bootx.platform.daxpay.core.channel.wechat.service;
import cn.bootx.platform.daxpay.code.PaySyncStatus;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.code.WeChatPayCode;
import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult;
import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import cn.hutool.json.JSONUtil;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.WxPayKit;
@@ -30,8 +30,8 @@ public class WeChatPaySyncService {
/**
* 同步查询
*/
public PaySyncResult syncPayStatus(Long paymentId, WeChatPayConfig weChatPayConfig) {
PaySyncResult paySyncResult = new PaySyncResult().setPaySyncStatus(PaySyncStatus.FAIL);
public SyncResult syncPayStatus(Long paymentId, WeChatPayConfig weChatPayConfig) {
SyncResult syncResult = new SyncResult().setSyncStatus(PaySyncStatusEnum.FAIL.getCode());
Map<String, String> params = UnifiedOrderModel.builder()
.appid(weChatPayConfig.getWxAppId())
.mch_id(weChatPayConfig.getWxMchId())
@@ -42,47 +42,47 @@ public class WeChatPaySyncService {
try {
String xmlResult = WxPayApi.orderQuery(params);
Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
paySyncResult.setJson(JSONUtil.toJsonStr(result));
syncResult.setJson(JSONUtil.toJsonStr(result));
// 查询失败
if (!WxPayKit.codeIsOk(result.get(WeChatPayCode.RETURN_CODE))) {
log.warn("查询微信订单失败:{}", result);
return paySyncResult;
return syncResult;
}
// 未查到订单
if (!WxPayKit.codeIsOk(result.get(WeChatPayCode.RESULT_CODE))) {
log.warn("疑似未查询到订单:{}", result);
return paySyncResult.setPaySyncStatus(PaySyncStatus.NOT_FOUND);
return syncResult.setSyncStatus(PaySyncStatusEnum.NOT_FOUND.getCode());
}
String tradeStatus = result.get(WeChatPayCode.TRADE_STATE);
// 支付完成
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_SUCCESS)
|| Objects.equals(tradeStatus, WeChatPayCode.TRADE_ACCEPT)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.TRADE_SUCCESS).setMap(result);
return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_SUCCESS.getCode()).setMap(result);
}
// 待支付
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_NOTPAY)
|| Objects.equals(tradeStatus, WeChatPayCode.TRADE_USERPAYING)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.WAIT_BUYER_PAY);
return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_WAIT.getCode());
}
// 已退款/退款中
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_REFUND)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.TRADE_REFUND);
return syncResult.setSyncStatus(PaySyncStatusEnum.REFUND.getCode());
}
// 已关闭
if (Objects.equals(tradeStatus, WeChatPayCode.TRADE_CLOSED)
|| Objects.equals(tradeStatus, WeChatPayCode.TRADE_REVOKED)
|| Objects.equals(tradeStatus, WeChatPayCode.TRADE_PAYERROR)) {
return paySyncResult.setPaySyncStatus(PaySyncStatus.TRADE_CLOSED);
return syncResult.setSyncStatus(PaySyncStatusEnum.CLOSED.getCode());
}
}
catch (RuntimeException e) {
log.error("查询订单失败:", e);
paySyncResult.setMsg(e.getMessage());
syncResult.setMsg(e.getMessage());
}
return paySyncResult;
return syncResult;
}
}

View File

@@ -47,7 +47,7 @@ public class WechatRefundService {
String refundFee = String.valueOf(amount);
String totalFee = refundableInfo.getAmount().toString();
// 设置退款信息
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getRefundInfo();
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getAsyncRefundInfo();
refundInfo.setRefundNo(IdUtil.getSnowflakeNextIdStr());
Map<String, String> params = RefundModel.builder()
.appid(weChatPayConfig.getWxAppId())
@@ -86,7 +86,7 @@ public class WechatRefundService {
errorMsg = result.get(WeChatPayCode.RETURN_MSG);
}
log.error("订单退款失败 {}", errorMsg);
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getRefundInfo();
AsyncRefundLocal refundInfo = PaymentContextLocal.get().getAsyncRefundInfo();
refundInfo.setErrorMsg(errorMsg);
refundInfo.setErrorCode(Optional.ofNullable(resultCode).orElse(returnCode));
throw new PayFailureException(errorMsg);

View File

@@ -56,8 +56,8 @@ public class PaymentBuilder {
.setRefundableInfos(refundableInfos)
.setStatus(PayStatusEnum.PROGRESS.getCode())
.setAmount(sumAmount)
.setCombinationPayMode(payParam.getPayWays().size() > 1)
.setAsyncPayMode(asyncPayMode.isPresent())
.setCombinationPay(payParam.getPayWays().size() > 1)
.setAsyncPay(asyncPayMode.isPresent())
.setAsyncPayChannel(asyncPayMode.orElse(null))
.setRefundableBalance(sumAmount);
}
@@ -123,7 +123,7 @@ public class PaymentBuilder {
PayResult paymentResult;
paymentResult = new PayResult();
paymentResult.setPaymentId(payOrder.getId());
paymentResult.setAsyncPayMode(payOrder.isAsyncPayMode());
paymentResult.setAsyncPayMode(payOrder.isAsyncPay());
paymentResult.setAsyncPayChannel(payOrder.getAsyncPayChannel());
paymentResult.setStatus(payOrder.getStatus());

View File

@@ -42,11 +42,11 @@ public class PayOrder extends MpBaseEntity {
/** 是否是异步支付 */
@DbColumn(comment = "是否是异步支付")
private boolean asyncPayMode;
private boolean asyncPay;
/** 是否是组合支付 */
@DbColumn(comment = "是否是组合支付")
private boolean combinationPayMode;
private boolean combinationPay;
/**
* 异步支付渠道

View File

@@ -49,6 +49,9 @@ public class PayRefundOrder extends MpBaseEntity implements EntityBaseFunction<P
/** 退款终端ip */
private String clientIp;
/** 退款原因 */
private String reason;
/** 退款时间 */
private LocalDateTime refundTime;

View File

@@ -0,0 +1,19 @@
package cn.bootx.platform.daxpay.core.order.sync.convert;
import cn.bootx.platform.daxpay.core.order.sync.entity.PaySyncOrder;
import cn.bootx.platform.daxpay.dto.order.sync.PaySyncOrderDto;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 支付同步记录同步
* @author xxm
* @since 2023/7/14
*/
@Mapper
public interface PaySyncOrderConvert {
PaySyncOrderConvert CONVERT = Mappers.getMapper(PaySyncOrderConvert.class);
PaySyncOrderDto convert(PaySyncOrder in);
}

View File

@@ -0,0 +1,35 @@
package cn.bootx.platform.daxpay.core.order.sync.dao;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.common.mybatisplus.base.MpIdEntity;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.daxpay.core.order.sync.entity.PaySyncOrder;
import cn.bootx.platform.daxpay.dto.order.sync.PaySyncOrderDto;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Objects;
/**
*
* @author xxm
* @since 2023/7/14
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class PaySyncOrderManager extends BaseManager<PaySyncOrderMapper, PaySyncOrder> {
public Page<PaySyncOrder> page(PageParam pageParam, PaySyncOrderDto param) {
Page<PaySyncOrder> mpPage = MpUtil.getMpPage(pageParam, PaySyncOrder.class);
return lambdaQuery().orderByDesc(MpIdEntity::getId)
.like(Objects.nonNull(param.getPaymentId()), PaySyncOrder::getPaymentId, param.getPaymentId())
.eq(Objects.nonNull(param.getChannel()), PaySyncOrder::getChannel, param.getChannel())
.eq(Objects.nonNull(param.getStatus()), PaySyncOrder::getStatus, param.getStatus())
.page(mpPage);
}
}

View File

@@ -0,0 +1,14 @@
package cn.bootx.platform.daxpay.core.order.sync.dao;
import cn.bootx.platform.daxpay.core.order.sync.entity.PaySyncOrder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 支付同步记录
* @author xxm
* @since 2023/7/14
*/
@Mapper
public interface PaySyncOrderMapper extends BaseMapper<PaySyncOrder> {
}

View File

@@ -0,0 +1,75 @@
package cn.bootx.platform.daxpay.core.order.sync.entity;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.core.order.sync.convert.PaySyncOrderConvert;
import cn.bootx.platform.daxpay.dto.order.sync.PaySyncOrderDto;
import cn.bootx.table.modify.annotation.DbComment;
import cn.bootx.table.modify.annotation.DbTable;
import cn.bootx.table.modify.mysql.annotation.DbMySqlFieldType;
import cn.bootx.table.modify.mysql.constants.MySqlFieldTypeEnum;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 支付同步订单
* @author xxm
* @since 2023/7/14
*/
@EqualsAndHashCode(callSuper = true)
@Data
@DbTable(comment = "支付同步订单")
@Accessors(chain = true)
@TableName("pay_sync_order")
public class PaySyncOrder extends MpCreateEntity implements EntityBaseFunction<PaySyncOrderDto> {
/** 支付记录id */
@DbComment("支付记录id")
private Long paymentId;
/**
* 支付渠道
* @see PayChannelEnum#getCode()
*/
@DbComment("支付渠道")
private String channel;
/** 通知消息 */
@DbMySqlFieldType(MySqlFieldTypeEnum.LONGTEXT)
@DbComment("通知消息")
private String syncInfo;
/**
* 同步状态
* @see PaySyncStatusEnum
*/
@DbComment("同步状态")
private String status;
/**
* 支付单如果状态不一致, 是否修复成功
*/
private boolean repairOrder;
@DbComment("错误消息")
private String msg;
/** 同步时间 */
@DbComment("同步时间")
private LocalDateTime syncTime;
/**
* 转换
*/
@Override
public PaySyncOrderDto toDto() {
return PaySyncOrderConvert.CONVERT.convert(this);
}
}

View File

@@ -0,0 +1,61 @@
package cn.bootx.platform.daxpay.core.order.sync.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.common.core.rest.PageResult;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.order.sync.dao.PaySyncOrderManager;
import cn.bootx.platform.daxpay.core.order.sync.entity.PaySyncOrder;
import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import cn.bootx.platform.daxpay.dto.order.sync.PaySyncOrderDto;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
/**
* 支付同步记录
* @author xxm
* @since 2023/7/14
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PaySyncOrderService {
private final PaySyncOrderManager orderManager;
/**
* 记录同步记录
*/
@Transactional(propagation= Propagation.REQUIRES_NEW)
public void saveRecord(SyncResult paySyncResult, PayOrder payment){
PaySyncOrder paySyncOrder = new PaySyncOrder()
.setPaymentId(payment.getId())
.setChannel(payment.getAsyncPayChannel())
.setSyncInfo(paySyncResult.getJson())
.setStatus(paySyncResult.getSyncStatus())
.setMsg(paySyncResult.getMsg())
.setSyncTime(LocalDateTime.now());
orderManager.save(paySyncOrder);
}
/**
* 分页查询
*/
public PageResult<PaySyncOrderDto> page(PageParam pageParam, PaySyncOrderDto param) {
Page<PaySyncOrder> page = orderManager.page(pageParam, param);
return MpUtil.convert2DtoPageResult(page);
}
/**
* 根据id查询
*/
public PaySyncOrderDto findById(Long id) {
return orderManager.findById(id).map(PaySyncOrder::toDto).orElseThrow(DataNotExistException::new);
}
}

View File

@@ -75,7 +75,7 @@ public class PayService {
payAssistService.initPayContext(payOrder, payParam);
// 异步支付且非第一次支付
if (Objects.nonNull(payOrder) && payOrder.isAsyncPayMode()) {
if (Objects.nonNull(payOrder) && payOrder.isAsyncPay()) {
return this.paySyncNotFirst(payParam, payOrder);
} else {
// 第一次发起支付或同步支付

View File

@@ -3,6 +3,7 @@ package cn.bootx.platform.daxpay.core.payment.pay.strategy;
import cn.bootx.platform.daxpay.code.AliPayCode;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.common.exception.ExceptionInfo;
import cn.bootx.platform.daxpay.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.core.channel.alipay.entity.AlipayConfig;
import cn.bootx.platform.daxpay.core.channel.alipay.service.*;
import cn.bootx.platform.daxpay.exception.pay.PayAmountAbnormalException;
@@ -106,8 +107,8 @@ public class AliPayStrategy extends AbsPayStrategy {
*/
@Override
public void doAsyncSuccessHandler(Map<String, String> map) {
String tradeNo = map.get(AliPayCode.TRADE_NO);
aliPaymentService.updateAsyncSuccess(this.getOrder().getId(), this.getPayWayParam(), tradeNo);
PaymentContextLocal.get().getAsyncPayInfo().setTradeNo( map.get(AliPayCode.TRADE_NO));
aliPaymentService.updateAsyncSuccess(this.getOrder(), this.getPayWayParam().getAmount());
}
/**

View File

@@ -11,8 +11,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import java.util.List;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/**
@@ -53,7 +51,7 @@ public class VoucherPayStrategy extends AbsPayStrategy {
@Override
public void doPayHandler() {
VoucherRecord voucherRecord;
if (this.getOrder().isAsyncPayMode()){
if (this.getOrder().isAsyncPay()){
voucherRecord = voucherPayService.freezeBalance(this.getPayWayParam().getAmount(), this.getOrder(), this.vouchers);
} else {
voucherRecord = voucherPayService.pay(this.getPayWayParam().getAmount(), this.getOrder(), this.vouchers);
@@ -66,7 +64,7 @@ public class VoucherPayStrategy extends AbsPayStrategy {
*/
@Override
public void doSuccessHandler() {
if (this.getOrder().isAsyncPayMode()){
if (this.getOrder().isAsyncPay()){
voucherPayService.paySuccess(this.getOrder().getId());
}
voucherPaymentService.updateSuccess(this.getOrder().getId());

View File

@@ -82,7 +82,7 @@ public class WalletPayStrategy extends AbsPayStrategy {
@Override
public void doPayHandler() {
// 异步支付方式时使用冻结方式
if (this.getOrder().isAsyncPayMode()){
if (this.getOrder().isAsyncPay()){
walletPayService.freezeBalance(getPayWayParam().getAmount(), this.getOrder(), this.wallet);
} else {
walletPayService.pay(getPayWayParam().getAmount(), this.getOrder(), this.wallet);
@@ -95,7 +95,7 @@ public class WalletPayStrategy extends AbsPayStrategy {
*/
@Override
public void doSuccessHandler() {
if (this.getOrder().isAsyncPayMode()){
if (this.getOrder().isAsyncPay()){
walletPayService.paySuccess(this.getOrder().getId());
}
walletPaymentService.updateSuccess(this.getOrder().getId());

View File

@@ -1,7 +1,7 @@
package cn.bootx.platform.daxpay.core.payment.refund.factory;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.core.payment.refund.strategy.*;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.param.pay.RefundChannelParam;

View File

@@ -1,17 +0,0 @@
package cn.bootx.platform.daxpay.core.payment.refund.func;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import java.util.List;
/**
* 支付退款策略接口
* @author xxm
* @since 2023/7/5
*/
@FunctionalInterface
public interface PayRefundStrategyConsumer<T extends List<AbsPayRefundStrategy>, S extends PayOrder> {
void accept(T t, S s);
}

View File

@@ -2,19 +2,27 @@ package cn.bootx.platform.daxpay.core.payment.refund.service;
import cn.bootx.platform.common.core.exception.ValidationFailedException;
import cn.bootx.platform.common.core.util.CollUtil;
import cn.bootx.platform.daxpay.code.PayRefundStatusEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.common.context.AsyncRefundLocal;
import cn.bootx.platform.daxpay.common.context.NoticeLocal;
import cn.bootx.platform.daxpay.common.context.PlatformLocal;
import cn.bootx.platform.daxpay.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.order.refund.dao.PayRefundOrderManager;
import cn.bootx.platform.daxpay.core.order.refund.entity.PayRefundOrder;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.RefundChannelParam;
import cn.bootx.platform.daxpay.param.pay.RefundParam;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -30,14 +38,14 @@ import java.util.Objects;
public class PayRefundAssistService {
private final PayOrderManager payOrderManager;
private final PayRefundOrderManager payRefundOrderManager;
/**
* 初始化上下文
*/
public void initRefundContext(RefundParam param, PayOrder payOrder){
public void initRefundContext(RefundParam param){
// 初始化通知相关上下文
this.initNotice(param);
// 初始化请求相关上下文
this.initRequest(param);
}
/**
@@ -55,20 +63,16 @@ public class PayRefundAssistService {
}
}
public void initRequest(RefundParam param){
}
/**
* 根据退款参数获取支付订单, 并进行检查
*/
public PayOrder getPayOrderAndCheckByRefundParam(RefundParam param, boolean simple){
if (!param.isRefundAll()){
if (CollUtil.isEmpty(param.getRefundChannels())){
if (!param.isRefundAll()) {
if (CollUtil.isEmpty(param.getRefundChannels())) {
throw new ValidationFailedException("退款通道参数不能为空");
}
if (Objects.isNull(param.getRefundNo())){
if (Objects.isNull(param.getRefundNo())) {
throw new ValidationFailedException("部分退款时退款单号必传");
}
}
@@ -84,7 +88,7 @@ public class PayRefundAssistService {
}
// 简单退款校验
if (payOrder.isCombinationPayMode() != simple){
if (payOrder.isCombinationPay() != simple){
throw new PayFailureException("组合支付不可以使用简单退款方式");
}
// 状态判断, 支付中/失败/取消等不能进行退款
@@ -99,4 +103,31 @@ public class PayRefundAssistService {
}
return payOrder;
}
/**
* 保存退款记录 成不成功都记录
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveRefundOrder(RefundParam refundParam, PayOrder payOrder){
AsyncRefundLocal asyncRefundInfo = PaymentContextLocal.get().getAsyncRefundInfo();
// 退款金额
Integer amount = refundParam.getRefundChannels()
.stream()
.map(RefundChannelParam::getAmount)
.reduce(0, Integer::sum);
PayRefundOrder refundOrder = new PayRefundOrder()
.setRefundRequestNo(asyncRefundInfo.getRefundNo())
.setAmount(amount)
.setClientIp(refundParam.getClientIp())
.setPaymentId(payOrder.getId())
.setBusinessNo(payOrder.getBusinessNo())
.setRefundTime(LocalDateTime.now())
.setTitle(payOrder.getTitle())
.setErrorMsg(asyncRefundInfo.getErrorMsg())
.setErrorCode(asyncRefundInfo.getErrorCode())
.setStatus(Objects.isNull(asyncRefundInfo.getErrorCode()) ? PayRefundStatusEnum.SUCCESS.getCode()
: PayRefundStatusEnum.FAIL.getCode());
payRefundOrderManager.save(refundOrder);
}
}

View File

@@ -5,8 +5,7 @@ import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.payment.refund.factory.PayRefundStrategyFactory;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.core.payment.refund.func.PayRefundStrategyConsumer;
import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.param.pay.RefundChannelParam;
import cn.bootx.platform.daxpay.param.pay.RefundParam;
@@ -21,8 +20,6 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -73,7 +70,7 @@ public class PayRefundService {
ValidationUtil.validateParam(param);
PayOrder payOrder = payRefundAssistService.getPayOrderAndCheckByRefundParam(param, simple);
// 退款上下文初始化
payRefundAssistService.initRefundContext(param,payOrder);
payRefundAssistService.initRefundContext(param);
// 是否全部退款
if (param.isRefundAll()){
// 全部退款根据支付订单的退款信息构造退款参数
@@ -90,67 +87,42 @@ public class PayRefundService {
/**
* 分支付通道进行退款
*/
public RefundResult refundByChannel(RefundParam param,PayOrder order){
List<RefundChannelParam> refundChannels = param.getRefundChannels();
public RefundResult refundByChannel(RefundParam refundParam, PayOrder payOrder){
List<RefundChannelParam> refundChannels = refundParam.getRefundChannels();
// 1.获取退款参数方式,通过工厂生成对应的策略组
List<AbsPayRefundStrategy> payRefundStrategies = PayRefundStrategyFactory.create(refundChannels);
if (CollectionUtil.isEmpty(payRefundStrategies)) {
throw new PayUnsupportedMethodException();
}
// 2.初始化支付的参数
for (AbsPayRefundStrategy refundStrategy : payRefundStrategies) {
refundStrategy.initPayParam(order, param);
}
// 2.初始化退款策略的参数
payRefundStrategies.forEach(refundStrategy -> refundStrategy.initPayParam(payOrder, refundParam));
// 3.支付前准备
this.doHandler(refundChannels, order, payRefundStrategies, AbsPayRefundStrategy::doBeforeRefundHandler, null);
// 4.执行退款
this.doHandler(refundChannels,order, payRefundStrategies, AbsPayRefundStrategy::doRefundHandler, (strategyList, payOrder) -> {
this.paymentHandler(payOrder, refundChannels);
});
return new RefundResult();
}
/**
* 处理方法
* @param channelParams 退款方式参数
* @param payOrder 支付记录
* @param strategyList 退款策略
* @param refundStrategy 执行方法
* @param successCallback 成功操作
*/
private void doHandler( List<RefundChannelParam> channelParams,
PayOrder payOrder,
List<AbsPayRefundStrategy> strategyList,
Consumer<AbsPayRefundStrategy> refundStrategy,
PayRefundStrategyConsumer<List<AbsPayRefundStrategy>, PayOrder> successCallback) {
try {
// 执行策略操作,如退款前/退款时
// 等同strategyList.forEach(payMethod.accept(PaymentStrategy))
strategyList.forEach(refundStrategy);
// 3.退款前准备
payRefundStrategies.forEach(AbsPayRefundStrategy::doBeforeRefundHandler);
// 执行操作成功的处
Optional.ofNullable(successCallback).ifPresent(fun -> fun.accept(strategyList, payOrder));
// 4.执行退款策略
payRefundStrategies.forEach(AbsPayRefundStrategy::doRefundHandler);
}
catch (Exception e) {
// 记录退款失败的记录
Integer i = channelParams.stream()
.map(RefundChannelParam::getAmount)
.reduce(0,Integer::sum);
// TODO 保存
// SpringUtil.getBean(this.getClass()).saveRefund(payOrder, amount, channelParams);
payRefundAssistService.saveRefundOrder(refundParam,payOrder);
throw e;
}
// 5.支付成功后处理
this.paymentHandler(refundParam, payOrder);
// 返回结果
return new RefundResult();
}
/**
* 支付订单处理
*/
private void paymentHandler(PayOrder payOrder, List<RefundChannelParam> refundModeParams) {
Integer amount = refundModeParams.stream()
private void paymentHandler(RefundParam refundParam,PayOrder payOrder) {
Integer amount = refundParam.getRefundChannels().stream()
.map(RefundChannelParam::getAmount)
.reduce(0, Integer::sum);
// 剩余可退款余额
@@ -165,7 +137,6 @@ public class PayRefundService {
}
payOrder.setRefundableBalance(refundableBalance);
payOrderManager.updateById(payOrder);
// TODO 记录退款成功的记录
// SpringUtil.getBean(this.getClass()).saveRefund(payOrder, amount, refundModeParams);
payRefundAssistService.saveRefundOrder(refundParam,payOrder);
}
}

View File

@@ -6,7 +6,7 @@ import cn.bootx.platform.daxpay.core.channel.alipay.service.AliPayOrderService;
import cn.bootx.platform.daxpay.core.channel.alipay.service.AliPayRefundService;
import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipayConfigService;
import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

View File

@@ -3,7 +3,7 @@ package cn.bootx.platform.daxpay.core.payment.refund.strategy;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.channel.cash.service.CashService;
import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

View File

@@ -1,7 +1,7 @@
package cn.bootx.platform.daxpay.core.payment.refund.strategy;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

View File

@@ -4,7 +4,7 @@ import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.channel.voucher.service.VoucherPayService;
import cn.bootx.platform.daxpay.core.channel.voucher.service.VoucherPaymentService;
import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

View File

@@ -4,7 +4,7 @@ import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.channel.wallet.service.WalletPayService;
import cn.bootx.platform.daxpay.core.channel.wallet.service.WalletPaymentService;
import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

View File

@@ -6,7 +6,7 @@ import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPayConfigServi
import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPayOrderService;
import cn.bootx.platform.daxpay.core.channel.wechat.service.WechatRefundService;
import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.core.payment.refund.func.AbsPayRefundStrategy;
import cn.bootx.platform.daxpay.func.AbsPayRefundStrategy;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

View File

@@ -0,0 +1,20 @@
package cn.bootx.platform.daxpay.core.payment.repair.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 支付订单修复参数
* @author xxm
* @since 2023/12/27
*/
@Data
@Accessors(chain = true)
public class PayOrderRepairParam {
@Schema(description = "支付ID")
private Long paymentId;
}

View File

@@ -1,16 +1,21 @@
package cn.bootx.platform.daxpay.core.payment.sync.service;
package cn.bootx.platform.daxpay.core.payment.repair.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 退款状态同步服务
* 支付修复服务
* @author xxm
* @since 2023/12/18
* @since 2023/12/27
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class RefundSyncService {
public class PayRepairService {
public void repair(){
}
}

View File

@@ -0,0 +1,52 @@
package cn.bootx.platform.daxpay.core.payment.repair.strategy;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.channel.alipay.service.AliPayOrderService;
import cn.bootx.platform.daxpay.func.AbsPayRepairStrategy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/**
* 支付宝支付单修复策略
* @author xxm
* @since 2023/12/27
*/
@Slf4j
@Scope(SCOPE_PROTOTYPE)
@Service
@RequiredArgsConstructor
public class AliPayRepairStrategy extends AbsPayRepairStrategy {
private final AliPayOrderService orderService;
@Override
public PayChannelEnum getType() {
return PayChannelEnum.ALI;
}
/**
* 支付成功处理
*/
@Override
public void successHandler() {
orderService.updateAsyncSuccess(this.getOrder(), 0);
}
/**
* 取消支付
*/
@Override
public void closeHandler() {
orderService.updateClose(this.getOrder().getId());
}
/**
* 退款
*/
@Override
public void refundHandler() {
orderService.updatePayRefund(this.getOrder().getId(), 0);
}
}

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.core.payment.sync.factory;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.payment.sync.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.strategy.AliPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.strategy.WeChatPaySyncStrategy;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;

View File

@@ -1,28 +1,28 @@
package cn.bootx.platform.daxpay.core.payment.sync.result;
import cn.bootx.platform.daxpay.code.PaySyncStatus;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Map;
import static cn.bootx.platform.daxpay.code.PaySyncStatus.NOT_SYNC;
import static cn.bootx.platform.daxpay.code.PaySyncStatusEnum.NOT_SYNC;
/**
* 支付网关通知状态对象
* 支付网关同步状态记录对象
*
* @author xxm
* @since 2021/4/21
*/
@Data
@Accessors(chain = true)
public class PaySyncResult {
public class SyncResult {
/**
* 支付网关同步状态
* @see PaySyncStatus#NOT_SYNC
* @see PaySyncStatusEnum#NOT_SYNC
*/
private String paySyncStatus = NOT_SYNC;
private String syncStatus = NOT_SYNC.getCode();
/** 网关返回参数(会被用到的参数) */
private Map<String, String> map;

View File

@@ -1,10 +1,26 @@
package cn.bootx.platform.daxpay.core.payment.sync.service;
import cn.bootx.platform.common.core.exception.BizException;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.order.sync.service.PaySyncOrderService;
import cn.bootx.platform.daxpay.core.payment.sync.factory.PaySyncStrategyFactory;
import cn.bootx.platform.daxpay.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.func.AbsPayStrategy;
import cn.bootx.platform.daxpay.param.pay.PaySyncParam;
import cn.bootx.platform.daxpay.result.pay.PaySyncResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
/**
* 支付同步服务
* @author xxm
@@ -14,8 +30,146 @@ import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class PaySyncService {
private final PayOrderManager payOrderManager;
public void sync(PayOrder payment) {
private final PaySyncOrderService syncOrderService;
/**
* 支付同步
*/
public PaySyncResult sync(PaySyncParam param) {
PayOrder payOrder = null;
if (Objects.nonNull(param.getPaymentId())){
payOrder = payOrderManager.findById(param.getPaymentId())
.orElseThrow(() -> new PayFailureException("未查询到支付订单"));
}
if (Objects.isNull(payOrder)){
payOrder = payOrderManager.findByBusinessNo(param.getBusinessNo())
.orElseThrow(() -> new PayFailureException("未查询到支付订单"));
}
// 如果不是异步支付, 直接返回不需要同步的结果
if (!payOrder.isAsyncPay()){
return new PaySyncResult().setSuccess(true).setStatus(PaySyncStatusEnum.NOT_SYNC.getCode());
}
// 执行逻辑
return this.syncPayOrder(payOrder);
}
/**
* 同步支付状态 传入 payment 对象
*/
public PaySyncResult syncPayOrder(PayOrder order) {
// 获取同步策略类
AbsPaySyncStrategy syncPayStrategy = PaySyncStrategyFactory.create(order.getAsyncPayChannel());
syncPayStrategy.initPayParam(order);
// 同步支付状态
SyncResult syncResult = syncPayStrategy.doSyncPayStatusHandler();
// 根据同步记录对支付单进行处理处理
this.resultHandler(syncResult,order);
// 记录同步的结果
syncOrderService.saveRecord(syncResult,order);
return null;
}
/**
* 根据同步的结果对支付单进行处理
*/
public void resultHandler(SyncResult syncResult, PayOrder payment){
PaySyncStatusEnum syncStatusEnum = PaySyncStatusEnum.getByCode(syncResult.getSyncStatus());
// 对同步结果处理
switch (syncStatusEnum) {
// 支付成功 支付宝退款时也是支付成功状态, 除非支付完成
case PaySyncStatusEnum.PAY_SUCCESS: {
this.paymentSuccess(payment, syncPayStrategy, syncResult);
break;
}
// 待付款/ 支付中
case PaySyncStatusEnum.PAY_WAIT: {
log.info("依然是付款状态");
break;
}
// 订单已经关闭超时关闭 和 网关没找到记录, 支付宝退款完成也是这个状态
case PaySyncStatusEnum.CLOSED:
case PaySyncStatusEnum.NOT_FOUND: {
// 判断下是否超时, 同时payment 变更为取消支付
this.paymentCancel(payment, paymentStrategyList);
break;
}
// 交易退款 支付宝没这个状态
case PaySyncStatusEnum.REFUND: {
this.paymentRefund(payment, syncPayStrategy, syncResult);
break;
}
// 调用出错
case PaySyncStatusEnum.FAIL: {
// 不进行处理
log.warn("支付状态同步接口调用出错");
break;
}
case PaySyncStatusEnum.NOT_SYNC:
default: {
throw new BizException("代码有问题");
}
}
}
/**
* payment 变更为已支付
*/
private void paymentSuccess(PayOrder payment, AbsPayStrategy syncPayStrategy, SyncResult syncResult) {
// 已支付不在重复处理
if (Objects.equals(payment.getStatus(), PayStatusEnum.SUCCESS.getCode())) {
return;
}
// 退款的不处理
if (Objects.equals(payment.getStatus(), PayStatusEnum.PARTIAL_REFUND.getCode())
|| Objects.equals(payment.getStatus(), PayStatusEnum.REFUNDED.getCode())) {
return;
}
// 修改payment支付状态为成功
syncPayStrategy.doAsyncSuccessHandler(syncResult.getMap());
payment.setStatus(PayStatusEnum.SUCCESS.getCode());
payment.setPayTime(LocalDateTime.now());
paymentService.updateById(payment);
// 发送成功事件
eventSender.sendPayComplete(PayEventBuilder.buildPayComplete(payment));
}
/**
* payment 变更为取消支付
*/
private void paymentCancel(Payment payment, List<AbsPayStrategy> absPayStrategies) {
try {
// 已关闭的不再进行关闭
if (Objects.equals(payment.getPayStatus(), TRADE_CANCEL)) {
return;
}
// 修改payment支付状态为取消, 退款状态则不进行更新
if (Objects.equals(payment.getPayStatus(), TRADE_REFUNDED)
|| Objects.equals(payment.getPayStatus(), TRADE_REFUNDING)) {
return;
}
payment.setPayStatus(TRADE_CANCEL);
// 执行策略的关闭方法
absPayStrategies.forEach(AbsPayStrategy::doCloseHandler);
paymentService.updateById(payment);
// 发送事件
eventSender.sendPayCancel(PayEventBuilder.buildPayCancel(payment));
}
catch (Exception e) {
log.warn("支付状态同步后关闭支付单报错了", e);
throw new PayFailureException("支付状态同步后关闭支付单报错了");
}
}
/**
* payment 退款处理 TODO 需要考虑退款详情的合并处理
*/
private void paymentRefund(Payment payment, AbsPayStrategy syncPayStrategy, PaySyncResult paySyncResult) {
}
}

View File

@@ -4,8 +4,8 @@ package cn.bootx.platform.daxpay.core.payment.sync.strategy;
import cn.bootx.platform.daxpay.core.channel.alipay.entity.AlipayConfig;
import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipayConfigService;
import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipaySyncService;
import cn.bootx.platform.daxpay.core.payment.sync.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult;
import cn.bootx.platform.daxpay.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -30,7 +30,7 @@ public class AliPaySyncStrategy extends AbsPaySyncStrategy {
* 异步支付单与支付网关进行状态比对
*/
@Override
public PaySyncResult doSyncPayStatusHandler() {
public SyncResult doSyncPayStatusHandler() {
this.initAlipayConfig();
return alipaySyncService.syncPayStatus(this.getOrder().getId());
}

View File

@@ -3,8 +3,8 @@ package cn.bootx.platform.daxpay.core.payment.sync.strategy;
import cn.bootx.platform.daxpay.core.channel.wechat.dao.WeChatPayConfigManager;
import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.core.channel.wechat.service.WeChatPaySyncService;
import cn.bootx.platform.daxpay.core.payment.sync.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult;
import cn.bootx.platform.daxpay.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -31,7 +31,7 @@ public class WeChatPaySyncStrategy extends AbsPaySyncStrategy {
* 异步支付单与支付网关进行状态比对
*/
@Override
public PaySyncResult doSyncPayStatusHandler() {
public SyncResult doSyncPayStatusHandler() {
// 检查并获取微信支付配置
this.initWeChatPayConfig();
return weChatPaySyncService.syncPayStatus(this.getOrder().getId(), this.weChatPayConfig);

View File

@@ -0,0 +1,52 @@
package cn.bootx.platform.daxpay.dto.order.sync;
import cn.bootx.platform.common.core.rest.dto.BaseDto;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 支付同步记录
* @author xxm
* @since 2023/7/14
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "支付同步订单")
public class PaySyncOrderDto extends BaseDto {
/** 支付记录id */
@Schema(description = "支付记录id")
private Long paymentId;
/**
* 支付渠道
* @see PayChannelEnum#getCode()
*/
@Schema(description = "支付渠道")
private String channel;
/** 通知消息 */
@Schema(description = "通知消息")
private String syncInfo;
/**
* 同步状态
* @see PaySyncStatusEnum
*/
@Schema(description = "同步状态")
private String status;
@Schema(description = "错误消息")
private String msg;
/** 同步时间 */
@Schema(description = "同步时间")
private LocalDateTime syncTime;
}

View File

@@ -22,7 +22,7 @@ import java.util.Map;
*/
@Slf4j
@RequiredArgsConstructor
public abstract class AbsPayCallbackStrategy {
public abstract class AbsPayCallbackStrategy implements PayStrategy {
protected static final ThreadLocal<Map<String, String>> PARAMS = new TransmittableThreadLocal<>();

View File

@@ -1,4 +1,4 @@
package cn.bootx.platform.daxpay.core.payment.refund.func;
package cn.bootx.platform.daxpay.func;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.common.exception.ExceptionInfo;
@@ -16,7 +16,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public abstract class AbsPayRefundStrategy {
public abstract class AbsPayRefundStrategy implements PayStrategy{
/** 支付对象 */
private PayOrder order = null;

View File

@@ -0,0 +1,55 @@
package cn.bootx.platform.daxpay.func;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.payment.repair.param.PayOrderRepairParam;
import lombok.Getter;
import lombok.Setter;
/**
* 支付订单修复策略
* @author xxm
* @since 2023/12/27
*/
@Getter
@Setter
public abstract class AbsPayRepairStrategy {
/** 支付对象 */
private PayOrder order = null;
/** 支付对象 */
private PayOrderRepairParam repairParam = null;
/**
* 初始化修复参数
*/
public void initRepairParam(PayOrder order,PayOrderRepairParam repairParam){
this.order = order;
this.repairParam = repairParam;
}
/**
* 策略标识
* @see PayChannelEnum
*/
public abstract PayChannelEnum getType();
/**
* 支付成功处理
*/
public abstract void successHandler();
/**
* 取消支付
*/
public abstract void closeHandler();
/**
* 退款
*/
public abstract void refundHandler();
}

View File

@@ -18,7 +18,7 @@ import java.util.Map;
*/
@Getter
@Setter
public abstract class AbsPayStrategy {
public abstract class AbsPayStrategy implements PayStrategy{
/** 支付对象 */
@@ -31,7 +31,7 @@ public abstract class AbsPayStrategy {
private PayWayParam payWayParam = null;
/**
* 策略标
* 策略标
* @see PayChannelEnum
*/
public abstract PayChannelEnum getType();
@@ -71,12 +71,14 @@ public abstract class AbsPayStrategy {
/**
* 异步支付成功的处理方式
*/
@Deprecated
public void doAsyncSuccessHandler(Map<String, String> map) {
}
/**
* 异步支付失败的处理方式, 默认使用支付失败的处理方式 同步支付方式调用时同 this#doErrorHandler
*/
@Deprecated
public void doAsyncErrorHandler(ExceptionInfo exceptionInfo) {
this.doErrorHandler(exceptionInfo);
}

View File

@@ -1,7 +1,8 @@
package cn.bootx.platform.daxpay.core.payment.sync.func;
package cn.bootx.platform.daxpay.func;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.payment.sync.result.PaySyncResult;
import cn.bootx.platform.daxpay.core.payment.sync.result.SyncResult;
import lombok.Getter;
import lombok.Setter;
@@ -12,7 +13,7 @@ import lombok.Setter;
*/
@Getter
@Setter
public abstract class AbsPaySyncStrategy {
public abstract class AbsPaySyncStrategy implements PayStrategy{
/** 支付对象 */
private PayOrder order = null;
@@ -27,8 +28,8 @@ public abstract class AbsPaySyncStrategy {
/**
* 异步支付单与支付网关进行状态比对
* @see cn.bootx.platform.daxpay.code.PaySyncStatus
* @see PaySyncStatusEnum
*/
public abstract PaySyncResult doSyncPayStatusHandler();
public abstract SyncResult doSyncPayStatusHandler();
}

View File

@@ -0,0 +1,9 @@
package cn.bootx.platform.daxpay.func;
/**
* 支付相关策略标识接口
* @author xxm
* @since 2023/12/27
*/
public interface PayStrategy {
}

View File

@@ -5,14 +5,19 @@ import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import java.util.List;
/**
* 支付策略接口
* 支付策略消费者接口
*
* @author xxm
* @since 2020/12/9
*/
@FunctionalInterface
public interface PayStrategyConsumer<T extends List<AbsPayStrategy>, S extends PayOrder> {
public interface PayStrategyConsumer<T extends List<? extends PayStrategy>, S extends PayOrder> {
/**
* 消费者接口
* @param t 策略结论
* @param s
*/
void accept(T t, S s);
}