ref 迁移支付流程

This commit is contained in:
nws
2023-12-21 23:08:04 +08:00
parent d72f268944
commit e6c7747351
26 changed files with 660 additions and 155 deletions

View File

@@ -18,7 +18,7 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(title = "支付参数")
public class PayParam extends PayCommonParam{
public class PayParam extends PayCommonParam {
@Schema(description = "业务号")
@NotBlank(message = "业务号不可为空")

View File

@@ -27,14 +27,14 @@ public class PayWayParam {
*/
@Schema(description = "支付渠道编码")
@NotBlank(message = "支付渠道编码不可为空")
private String payChannel;
private String channel;
/**
* @see PayWayEnum#getCode()
*/
@Schema(description = "支付方式编码")
@NotBlank(message = "支付方式编码不可为空")
private String payWay;
private String way;
@Schema(description = "支付金额")
@NotNull(message = "支付金额不可为空")

View File

@@ -0,0 +1,56 @@
package cn.bootx.platform.daxpay.openapi.controller.channel;
import cn.bootx.platform.common.core.rest.Res;
import cn.bootx.platform.common.core.rest.ResResult;
import cn.bootx.platform.common.core.rest.dto.LabelValue;
import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipayConfigService;
import cn.bootx.platform.daxpay.dto.channel.alipay.AlipayConfigDto;
import cn.bootx.platform.daxpay.param.channel.alipay.AlipayConfigParam;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* @author xxm
* @since 2021/2/26
*/
@Tag(name = "支付宝配置")
@RestController
@RequestMapping("/alipay")
@AllArgsConstructor
public class AlipayConfigController {
private final AlipayConfigService alipayConfigService;
@Operation(summary = "更新")
@PostMapping("/update")
public ResResult<Void> update(@RequestBody AlipayConfigParam param) {
alipayConfigService.update(param);
return Res.ok();
}
@Operation(summary = "根据Id查询")
@GetMapping("/findById")
public ResResult<AlipayConfigDto> findById() {
return Res.ok(alipayConfigService.getConfig().toDto());
}
@Operation(summary = "支付宝支持支付方式")
@GetMapping("/findPayWayList")
public ResResult<List<LabelValue>> findPayWayList() {
return Res.ok(alipayConfigService.findPayWayList());
}
@SneakyThrows
@Operation(summary = "读取证书文件内容")
@PostMapping("/readPem")
public ResResult<String> readPem(MultipartFile file){
return Res.ok(new String(file.getBytes(), StandardCharsets.UTF_8));
}
}

View File

@@ -1,6 +1,7 @@
package cn.bootx.platform.daxpay.openapi.controller;
import cn.bootx.platform.common.core.annotation.IgnoreAuth;
import cn.bootx.platform.daxpay.core.payment.pay.service.PayService;
import cn.bootx.platform.daxpay.param.pay.*;
import cn.bootx.platform.daxpay.result.DaxResult;
import cn.bootx.platform.daxpay.result.pay.PayResult;
@@ -25,10 +26,11 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/unipay")
@RequiredArgsConstructor
public class UniPayController {
private final PayService payService;
@Operation(summary = "统一下单")
@PostMapping("/pay")
public DaxResult<PayResult> pay(@RequestBody PayParam payParam){
payService.pay(payParam);
return DaxRes.ok();
}
@Operation(summary = "简单下单")

View File

@@ -55,7 +55,7 @@ public class AliPayOrderService {
refundableInfos.removeIf(payTypeInfo -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payTypeInfo.getChannel()));
// 更新支付宝支付类型信息
payChannelInfo.add(new PayOrderChannel().setChannel(PayChannelEnum.ALI.getCode())
.setPayWay(payWayParam.getPayWay())
.setPayWay(payWayParam.getWay())
.setAmount(payWayParam.getAmount())
.setChannelExtra(payWayParam.getChannelExtra()));
// TODO 更新支付关联信息

View File

@@ -53,7 +53,7 @@ public class AliPayService {
throw new PayFailureException("支付宝未配置可用的支付方式");
}
// 发起的支付类型是否在支持的范围内
PayWayEnum payWayEnum = Optional.ofNullable(AliPayWay.findByCode(payWayParam.getPayWay()))
PayWayEnum payWayEnum = Optional.ofNullable(AliPayWay.findByCode(payWayParam.getWay()))
.orElseThrow(() -> new PayFailureException("非法的支付宝支付类型"));
if (!alipayConfig.getPayWays().contains(payWayEnum.getCode())) {
throw new PayFailureException("该支付宝支付方式不可用");
@@ -69,23 +69,23 @@ public class AliPayService {
// 线程存储
AsyncPayInfo asyncPayInfo = Optional.ofNullable(AsyncPayInfoLocal.get()).orElse(new AsyncPayInfo());
// wap支付
if (Objects.equals(payWayParam.getPayWay(), PayWayEnum.WAP.getCode())) {
if (Objects.equals(payWayParam.getWay(), PayWayEnum.WAP.getCode())) {
payBody = this.wapPay(amount, payOrder, alipayConfig, aliPayParam);
}
// 程序支付
else if (Objects.equals(payWayParam.getPayWay(), PayWayEnum.APP.getCode())) {
else if (Objects.equals(payWayParam.getWay(), PayWayEnum.APP.getCode())) {
payBody = this.appPay(amount, payOrder, alipayConfig);
}
// pc支付
else if (Objects.equals(payWayParam.getPayWay(), PayWayEnum.WEB.getCode())) {
else if (Objects.equals(payWayParam.getWay(), PayWayEnum.WEB.getCode())) {
payBody = this.webPay(amount, payOrder, alipayConfig, aliPayParam);
}
// 二维码支付
else if (Objects.equals(payWayParam.getPayWay(), PayWayEnum.QRCODE.getCode())) {
else if (Objects.equals(payWayParam.getWay(), PayWayEnum.QRCODE.getCode())) {
payBody = this.qrCodePay(amount, payOrder, alipayConfig);
}
// 付款码支付
else if (Objects.equals(payWayParam.getPayWay(), PayWayEnum.BARCODE.getCode())) {
else if (Objects.equals(payWayParam.getWay(), PayWayEnum.BARCODE.getCode())) {
String tradeNo = this.barCode(amount, payOrder, aliPayParam, alipayConfig);
asyncPayInfo.setExpiredTime(false).setTradeNo(tradeNo);
}

View File

@@ -2,16 +2,12 @@ package cn.bootx.platform.daxpay.core.channel.alipay.service;
import cn.bootx.platform.common.core.exception.BizException;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.common.core.rest.PageResult;
import cn.bootx.platform.common.core.rest.dto.LabelValue;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.daxpay.code.AliPayCode;
import cn.bootx.platform.daxpay.code.AliPayWay;
import cn.bootx.platform.daxpay.core.channel.alipay.dao.AlipayConfigManager;
import cn.bootx.platform.daxpay.core.channel.alipay.entity.AlipayConfig;
import cn.bootx.platform.daxpay.dto.channel.alipay.AlipayConfigDto;
import cn.bootx.platform.daxpay.param.channel.alipay.AlipayConfigParam;
import cn.bootx.platform.daxpay.param.channel.alipay.AlipayConfigQuery;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.CharsetUtil;
@@ -37,42 +33,20 @@ import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class AlipayConfigService {
/** 默认支付包配置的主键ID */
private final static Long ID = 0L;
private final AlipayConfigManager alipayConfigManager;
/**
* 添加支付宝配置
*/
@Transactional(rollbackFor = Exception.class)
public void add(AlipayConfigParam param) {
AlipayConfig alipayConfig = AlipayConfig.init(param);
alipayConfigManager.save(alipayConfig);
}
/**
* 修改
*/
@Transactional(rollbackFor = Exception.class)
public void update(AlipayConfigParam param) {
AlipayConfig alipayConfig = alipayConfigManager.findById(param.getId()).orElseThrow(DataNotExistException::new);
AlipayConfig alipayConfig = alipayConfigManager.findById(ID).orElseThrow(() -> new DataNotExistException("支付宝配置不存在"));
BeanUtil.copyProperties(param, alipayConfig, CopyOptions.create().ignoreNullValue());
alipayConfigManager.updateById(alipayConfig);
}
/**
* 获取
*/
public AlipayConfigDto findById(Long id) {
return alipayConfigManager.findById(id).map(AlipayConfig::toDto).orElseThrow(DataNotExistException::new);
}
/**
* 分页
*/
public PageResult<AlipayConfigDto> page(PageParam pageParam, AlipayConfigQuery param) {
return null;
}
/**
* 支付宝支持支付方式
*/
@@ -84,18 +58,18 @@ public class AlipayConfigService {
}
/**
* 初始化IJPay服务,通过商户应用AppCode获取支付配置
* 获取支付配置
*/
public void initApiConfigByMchAppCode(String mchAppCode){
AlipayConfig alipayConfig = null;
this.initApiConfig(alipayConfig);
public AlipayConfig getConfig(){
return alipayConfigManager.findById(ID).orElseThrow(() -> new DataNotExistException("支付宝配置不存在"));
}
/**
* 初始化IJPay服务
*/
@SneakyThrows
public void initApiConfig(AlipayConfig alipayConfig) {
public void initConfig(AlipayConfig alipayConfig) {
AliPayApiConfig aliPayApiConfig;
// 公钥

View File

@@ -68,7 +68,7 @@ public class WeChatPayService {
throw new PayFailureException("未配置微信支付方式");
}
PayWayEnum payWayEnum = Optional.ofNullable(WeChatPayWay.findByCode(payWayParam.getPayWay()))
PayWayEnum payWayEnum = Optional.ofNullable(WeChatPayWay.findByCode(payWayParam.getWay()))
.orElseThrow(() -> new PayFailureException("非法的微信支付类型"));
if (!payWays.contains(payWayEnum.getCode())) {
throw new PayFailureException("该微信支付方式不可用");
@@ -84,7 +84,7 @@ public class WeChatPayService {
String totalFee = String.valueOf(amount);
AsyncPayInfo asyncPayInfo = Optional.ofNullable(AsyncPayInfoLocal.get()).orElse(new AsyncPayInfo());
String payBody = null;
PayWayEnum payWayEnum = PayWayEnum.findByCode(payWayParam.getPayWay());
PayWayEnum payWayEnum = PayWayEnum.findByCode(payWayParam.getWay());
// wap支付
if (payWayEnum == PayWayEnum.WAP) {

View File

@@ -51,7 +51,7 @@ public class WeChatPaymentService {
refundableInfos.removeIf(payTypeInfo -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payTypeInfo.getChannel()));
// 添加微信支付类型信息
payTypeInfos.add(new PayOrderChannel().setChannel(PayChannelEnum.WECHAT.getCode())
.setPayWay(payWayParam.getPayWay())
.setPayWay(payWayParam.getWay())
.setAmount(payWayParam.getAmount())
.setChannelExtra(payWayParam.getChannelExtra()));
// TODO 更新支付方式列表

View File

@@ -1,21 +1,22 @@
package cn.bootx.platform.daxpay.core.order.pay.builder;
import cn.bootx.platform.common.spring.util.WebServletUtil;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
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.core.order.pay.entity.PayOrderRefundableInfo;
import cn.bootx.platform.daxpay.core.payment.pay.local.AsyncPayInfo;
import cn.bootx.platform.daxpay.core.payment.pay.local.AsyncPayInfoLocal;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.result.pay.PayResult;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.experimental.UtilityClass;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -31,45 +32,45 @@ public class PaymentBuilder {
* 构建payment记录
*/
public PayOrder buildPayOrder(PayParam payParam) {
PayOrder payOrder = new PayOrder();
HttpServletRequest request = WebServletUtil.getRequest();
String ip = ServletUtil.getClientIP(request);
// 基础信息
payOrder.setBusinessNo(payParam.getBusinessNo())
.setTitle(payParam.getTitle());
// 支付方式
// 可退款信息
List<PayOrderRefundableInfo> refundableInfos = buildRefundableInfo(payParam.getPayWays());
List<PayOrderChannel> payOrderChannels = buildPayChannel(payParam.getPayWays());
// 计算总价
int sumAmount = payOrderChannels.stream()
.map(PayOrderChannel::getAmount)
int sumAmount = payParam.getPayWays().stream()
.map(PayWayParam::getAmount)
.filter(Objects::nonNull)
.reduce(Integer::sum)
.orElse(0);
// 支付渠道信息
payOrder.setRefundableInfos(refundableInfos)
// 是否有异步支付方式
Optional<String> asyncPayMode = payParam.getPayWays().stream()
.map(PayWayParam::getChannel)
.filter(PayChannelEnum.ASYNC_TYPE_CODE::contains)
.findFirst();
// 构建支付订单对象
return new PayOrder()
.setBusinessNo(payParam.getBusinessNo())
.setTitle(payParam.getTitle())
.setRefundableInfos(refundableInfos)
.setStatus(PayStatusEnum.PROGRESS.getCode())
.setAmount(sumAmount)
.setCombinationPayMode(payParam.getPayWays().size()>1)
.setCombinationPayMode(payParam.getPayWays().size() > 1)
.setAsyncPayMode(asyncPayMode.isPresent())
.setAsyncPayChannel(asyncPayMode.orElse(null))
.setRefundableBalance(sumAmount);
return payOrder;
}
/**
* 构建订单关联通道信息
*/
private List<PayOrderChannel> buildPayChannel(List<PayWayParam> payWayParamList) {
if (CollectionUtil.isEmpty(payWayParamList)) {
public List<PayOrderChannel> buildPayChannel(List<PayWayParam> payWayParams) {
if (CollectionUtil.isEmpty(payWayParams)) {
return Collections.emptyList();
}
return payWayParamList.stream()
return payWayParams.stream()
.map(o-> new PayOrderChannel()
.setChannel(o.getPayChannel())
.setPayWay(o.getPayWay())
.setAmount(o.getAmount())
.setChannelExtra(o.getChannelExtra()))
.setChannel(o.getChannel())
.setPayWay(o.getWay())
.setAmount(o.getAmount())
.setChannelExtra(o.getChannelExtra()))
.collect(Collectors.toList());
}
@@ -82,41 +83,50 @@ public class PaymentBuilder {
}
return payWayParamList.stream()
.map(o-> new PayOrderRefundableInfo()
.setChannel(o.getPayChannel())
.setChannel(o.getChannel())
.setAmount(o.getAmount()))
.collect(Collectors.toList());
}
/**
* 根据Payment构建PaymentResult
* @param payment payment
* @return paymentVO
* 根据支付订单构建支付结果
* @param payOrder 支付订单
* @return PayResult 支付结果
*/
public PayResult buildResultByPayment(PayOrder payment) {
// PayResult paymentResult;
// try {
// paymentResult = new PayResult();
// // 异步支付信息
// paymentResult.setAsyncPayChannel(payment.getAsyncPayChannel())
// .setAsyncPayMode(payment.isAsyncPayMode())
// .setStatus(payment.getStatus());
//
//
// // 设置异步支付参数
// List<PayOrderChannel> moneyPayTypeInfos = channelInfos.stream()
// .filter(payTypeInfo -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payTypeInfo.getPayChannel()))
// .collect(Collectors.toList());
// if (!CollUtil.isEmpty(moneyPayTypeInfos)) {
// paymentResult.setAsyncPayInfo(AsyncPayInfoLocal.get());
// }
// }
// finally {
// // 清空线程变量
// AsyncPayInfoLocal.clear();
// }
// return paymentResult;
return null;
public PayResult buildPayResultByPayOrder(PayOrder payOrder) {
PayResult paymentResult;
try {
paymentResult = new PayResult()
.setPaymentId(payOrder.getId())
.setAsyncPayMode(payOrder.isAsyncPayMode())
.setAsyncPayChannel(payOrder.getAsyncPayChannel())
.setStatus(payOrder.getStatus());
// 设置异步支付参数
AsyncPayInfo asyncPayInfo = AsyncPayInfoLocal.get();
if (Objects.nonNull(asyncPayInfo)) {
paymentResult.setPayBody(AsyncPayInfoLocal.get().getPayBody());
}
}
finally {
// 清空线程变量
AsyncPayInfoLocal.clear();
}
return paymentResult;
}
/**
* 根据新的新传入的支付参数和支付订单构建出当前需要支付参数
* 主要是针对其中的异步支付参数进行处理
*
* @param newPayParam 新传入的参数
* @param payOrder 支付订单
* @return PayParam 支付参数
*/
public PayParam buildPayParam(PayParam newPayParam, PayOrder payOrder) {
return null;
}
}

View File

@@ -0,0 +1,52 @@
package cn.bootx.platform.daxpay.core.order.pay.dao;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.common.redis.RedisClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.Set;
/**
* 支付单过期处理
*
* @author xxm
* @since 2022/7/12
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class PayExpiredTimeRepository {
private static final String KEY = "payment:pay:expiredtime";
private final RedisClient redisClient;
/**
* 根据 token 存储对应的 ExpiredTokenKey
*/
public void store(Long paymentId, LocalDateTime expiredTime) {
long time = LocalDateTimeUtil.timestamp(expiredTime);
redisClient.zadd(KEY, String.valueOf(paymentId), time);
}
/**
* 获取所有已过期的ExpiredTokenKey
*/
public Set<String> retrieveExpiredKeys(LocalDateTime expiredTime) {
long time = LocalDateTimeUtil.timestamp(expiredTime);
return redisClient.zrangeByScore(KEY, 0L, time);
}
/**
* 删除指定的ExpiredTokenKey
*/
public void removeKeys(String... keys) {
if (keys != null && keys.length > 0) {
redisClient.zremByMembers(KEY, keys);
}
}
}

View File

@@ -0,0 +1,29 @@
package cn.bootx.platform.daxpay.core.order.pay.dao;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderChannel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 支付订单关联支付时通道信息
* @author xxm
* @since 2023/12/20
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class PayOrderChannelManager extends BaseManager<PayOrderChannelMapper, PayOrderChannel> {
/**
* 根据订单id和支付渠道查询
*/
public Optional<PayOrderChannel> findByOderIdAndChannel(Long paymentId, String channel) {
return lambdaQuery()
.eq(PayOrderChannel::getPaymentId,paymentId)
.eq(PayOrderChannel::getChannel,channel)
.oneOpt();
}
}

View File

@@ -0,0 +1,14 @@
package cn.bootx.platform.daxpay.core.order.pay.dao;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderChannel;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 支付订单关联支付时通道信息
* @author xxm
* @since 2023/12/20
*/
@Mapper
public interface PayOrderChannelMapper extends BaseMapper<PayOrderChannel> {
}

View File

@@ -0,0 +1,18 @@
package cn.bootx.platform.daxpay.core.order.pay.dao;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderExtra;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
/**
* 支付订单扩展信息
* @author xxm
* @since 2023/12/20
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class PayOrderExtraManager extends BaseManager<PayOrderExtraMapper, PayOrderExtra> {
}

View File

@@ -0,0 +1,14 @@
package cn.bootx.platform.daxpay.core.order.pay.dao;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderExtra;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 支付订单扩展信息
* @author xxm
* @since 2023/12/20
*/
@Mapper
public interface PayOrderExtraMapper extends BaseMapper<PayOrderExtra> {
}

View File

@@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 支付订单
* @author xxm
@@ -15,4 +17,7 @@ import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class PayOrderManager extends BaseManager<PayOrderMapper, PayOrder> {
public Optional<PayOrder> findByBusinessNo(String businessNo) {
return findByField(PayOrder::getBusinessNo,businessNo);
}
}

View File

@@ -32,7 +32,7 @@ public class PayOrder extends MpBaseEntity {
/** 关联的业务id */
@DbMySqlIndex(comment = "业务业务号索引",type = MySqlIndexType.UNIQUE)
@DbColumn(comment = "关联的业务id")
@DbColumn(comment = "关联的业务")
private String businessNo;
/** 标题 */

View File

@@ -1,6 +1,6 @@
package cn.bootx.platform.daxpay.core.order.pay.entity;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import cn.bootx.platform.common.mybatisplus.base.MpDelEntity;
import cn.bootx.platform.daxpay.param.channel.AliPayParam;
import cn.bootx.platform.daxpay.param.channel.VoucherPayParam;
import cn.bootx.platform.daxpay.param.channel.WalletPayParam;
@@ -22,7 +22,7 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
@DbTable(comment = "支付订单关联支付时通道信息")
@TableName("pay_order_channel")
public class PayOrderChannel extends MpBaseEntity {
public class PayOrderChannel extends MpDelEntity {
@DbColumn(comment = "支付id")
private Long paymentId;

View File

@@ -5,7 +5,7 @@ import lombok.Data;
import lombok.experimental.Accessors;
/**
* 支付订单可退款信息
* 支付订单可退款信息(不持久化,直接保存为json)
* @author xxm
* @since 2023/12/18
*/

View File

@@ -1,9 +1,30 @@
package cn.bootx.platform.daxpay.core.order.pay.service;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.common.spring.exception.RetryableException;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayExpiredTimeRepository;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderChannelManager;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderExtraManager;
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.core.order.pay.entity.PayOrderRefundableInfo;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* 支付订单服务
* @author xxm
@@ -13,4 +34,87 @@ import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class PayOrderService {
private final PayOrderManager orderManager;
private final PayOrderChannelManager orderChannelManager;
private final PayOrderExtraManager orderExtraManager;
private final PayExpiredTimeRepository expiredTimeRepository;
/**
* 保存
*/
public PayOrder saveOder(PayOrder payment) {
return orderManager.save(payment);
}
/**
* 保存支付通道信息列表
*/
public void saveOrderChannels(List<PayOrderChannel> payOrderChannels){
orderChannelManager.saveAll(payOrderChannels);
}
/**
* 更新支付记录
*/
public PayOrder updateById(PayOrder payment) {
// 超时注册
this.registerExpiredTime(payment);
return orderManager.updateById(payment);
}
/**
* 根据id查询
*/
public Optional<PayOrder> findById(Serializable id) {
return orderManager.findById(id);
}
/**
* 根据BusinessId查询
*/
public Optional<PayOrder> findByBusinessId(String businessNo) {
return orderManager.findByBusinessNo(businessNo);
}
/**
* 退款成功处理, 更新可退款信息 不要进行持久化
*/
public void updateRefundSuccess(PayOrder payment, int amount, PayChannelEnum payChannelEnum) {
// 删除旧有的退款记录, 替换退款完的新的
List<PayOrderRefundableInfo> refundableInfos = payment.getRefundableInfos();
PayOrderRefundableInfo refundableInfo = refundableInfos.stream()
.filter(o -> Objects.equals(o.getChannel(), payChannelEnum.getCode()))
.findFirst()
.orElseThrow(() -> new PayFailureException("退款数据不存在"));
refundableInfos.remove(refundableInfo);
refundableInfo.setAmount(refundableInfo.getAmount() - amount);
refundableInfos.add(refundableInfo);
payment.setRefundableInfos(refundableInfos);
}
/**
* 支付单超时关闭事件注册, 失败重试3次, 间隔一秒
*/
@Async("bigExecutor")
@Retryable(value = RetryableException.class)
public void registerExpiredTime(PayOrder payOrder) {
LocalDateTime expiredTime = payOrder.getExpiredTime();
// 支付中且有超时时间才会注册超时关闭时间
if (Objects.equals(payOrder.getStatus(), PayStatusEnum.PROGRESS.getCode()) && Objects.nonNull(expiredTime)) {
try {
// 将过期时间添加到redis中, 往后延时一分钟
expiredTime = LocalDateTimeUtil.offset(expiredTime, 1, ChronoUnit.MINUTES);
expiredTimeRepository.store(payOrder.getId(), expiredTime);
}
catch (Exception e) {
log.error("注册支付单超时关闭失败");
throw new RetryableException();
}
}
}
}

View File

@@ -7,6 +7,7 @@ import cn.bootx.platform.daxpay.func.AbsPayStrategy;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.experimental.UtilityClass;
import java.util.ArrayList;
import java.util.Collections;
@@ -23,6 +24,7 @@ import static cn.bootx.platform.daxpay.code.PayChannelEnum.ASYNC_TYPE_CODE;
* @author xxm
* @since 2020/12/11
*/
@UtilityClass
public class PayStrategyFactory {
/**
@@ -30,10 +32,9 @@ public class PayStrategyFactory {
* @param payWayParam 支付类型
* @return 支付策略
*/
public static AbsPayStrategy create(PayWayParam payWayParam) {
AbsPayStrategy strategy = null;
PayChannelEnum channelEnum = PayChannelEnum.findByCode(payWayParam.getPayChannel());
public AbsPayStrategy create(PayWayParam payWayParam) {
AbsPayStrategy strategy;
PayChannelEnum channelEnum = PayChannelEnum.findByCode(payWayParam.getChannel());
switch (channelEnum) {
case ALI:
strategy = SpringUtil.getBean(AliPayStrategy.class);
@@ -56,7 +57,6 @@ public class PayStrategyFactory {
default:
throw new PayUnsupportedMethodException();
}
// noinspection ConstantConditions
strategy.setPayWayParam(payWayParam);
return strategy;
}
@@ -64,15 +64,15 @@ public class PayStrategyFactory {
/**
* 根据传入的支付类型批量创建策略, 异步支付在后面
*/
public static List<AbsPayStrategy> createDesc(List<PayWayParam> payWayParamList) {
public static List<AbsPayStrategy> createAsyncLast(List<PayWayParam> payWayParamList) {
return create(payWayParamList, true);
}
/**
* 根据传入的支付类型批量创建策略, 默认异步支付在前面
* 根据传入的支付类型批量创建策略, 异步支付在前面
*/
public static List<AbsPayStrategy> create(List<PayWayParam> payWayParamList) {
return create(payWayParamList, false);
public List<AbsPayStrategy> create(List<PayWayParam> payWayParamList) {
return create(payWayParamList, true);
}
/**
@@ -80,7 +80,7 @@ public class PayStrategyFactory {
* @param payWayParamList 支付类型
* @return 支付策略
*/
private static List<AbsPayStrategy> create(List<PayWayParam> payWayParamList, boolean description) {
private List<AbsPayStrategy> create(List<PayWayParam> payWayParamList, boolean asyncFirst) {
if (CollectionUtil.isEmpty(payWayParamList)) {
return Collections.emptyList();
}
@@ -89,25 +89,24 @@ public class PayStrategyFactory {
// 同步支付
List<PayWayParam> syncPayWayParamList = payWayParamList.stream()
.filter(Objects::nonNull)
.filter(payModeParam -> !ASYNC_TYPE_CODE.contains(payModeParam.getPayChannel()))
.filter(payModeParam -> !ASYNC_TYPE_CODE.contains(payModeParam.getChannel()))
.collect(Collectors.toList());
// 异步支付
List<PayWayParam> asyncPayWayParamList = payWayParamList.stream()
.filter(Objects::nonNull)
.filter(payModeParam -> ASYNC_TYPE_CODE.contains(payModeParam.getPayChannel()))
.filter(payModeParam -> ASYNC_TYPE_CODE.contains(payModeParam.getChannel()))
.collect(Collectors.toList());
List<PayWayParam> sortList = new ArrayList<>(payWayParamList.size());
// 异步在后面
if (description) {
sortList.addAll(syncPayWayParamList);
sortList.addAll(asyncPayWayParamList);
}
else {
if (asyncFirst) {
sortList.addAll(asyncPayWayParamList);
sortList.addAll(syncPayWayParamList);
} else {
sortList.addAll(syncPayWayParamList);
sortList.addAll(asyncPayWayParamList);
}
// 此处有一个根据Type的反转排序

View File

@@ -1,11 +1,40 @@
package cn.bootx.platform.daxpay.core.payment.pay.service;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.common.core.util.ValidationUtil;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.core.order.pay.builder.PaymentBuilder;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderChannelManager;
import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderExtraManager;
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.core.order.pay.entity.PayOrderExtra;
import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.core.payment.pay.factory.PayStrategyFactory;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.func.AbsPayStrategy;
import cn.bootx.platform.daxpay.func.PayStrategyConsumer;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.result.pay.PayResult;
import cn.bootx.platform.daxpay.util.PayWayUtil;
import cn.hutool.core.collection.CollectionUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* 支付流程服务
* 支付流程
*
* @author xxm
* @since 2020/12/9
*/
@@ -13,4 +42,224 @@ import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class PayService {
private final PayOrderService payOrderService;
private final PayOrderExtraManager payOrderExtraManager;
private final PayOrderChannelManager payOrderChannelManager;
/**
* 支付方法(同步/异步/组合支付) 同步支付:都只会在第一次执行中就完成支付,例如钱包、积分都是调用完就进行了扣减,完成了支付记录
* 异步支付:例如支付宝、微信,发起支付后还需要跳转第三方平台进行支付,支付后通常需要进行回调,之后才完成支付记录
* 组合支付:主要是混合了同步支付和异步支付,同时异步支付只能有一个,在支付时先对同步支付进行扣减,然后异步支付回调结束后完成整个支付单
* 组合支付在非第一次支付的时候只对新传入的异步支付PayMode进行处理PayMode的价格使用第一次发起的价格旧的同步支付如果传入后也不做处理
* Payment中PayModeList将会为 旧有的同步支付+新传入的异步支付方式(在具体支付实现中处理)
*/
@Transactional(rollbackFor = Exception.class)
public PayResult pay(PayParam payParam) {
// 检验参数
ValidationUtil.validateParam(payParam);
// 异步支付方式检查
PayWayUtil.validationAsyncPayMode(payParam);
// 获取并校验支付状态
PayOrder payOrder = this.getAndCheckByBusinessId(payParam.getBusinessNo());
// 异步支付且非第一次支付
if (Objects.nonNull(payOrder) && payOrder.isAsyncPayMode()) {
return this.paySyncNotFirst(payParam, payOrder);
}
else {
// 第一次发起支付或同步支付
return this.payFirst(payParam, payOrder);
}
}
/**
* 发起的第一次支付请求(同步/异步)
*/
private PayResult payFirst(PayParam payParam, PayOrder payOrder) {
// 0. 已经发起过支付情况直接返回支付结果
if (Objects.nonNull(payOrder)) {
return PaymentBuilder.buildPayResultByPayOrder(payOrder);
}
// 1. 价格检测
PayWayUtil.validationAmount(payParam.getPayWays());
// 2. 创建支付相关的记录并返回支付订单对象
payOrder = this.createPayOrder(payParam);
// 3. 调用支付方法进行发起支付
this.payFirstMethod(payParam, payOrder);
// 4. 获取支付记录信息
// payOrder = payOrderService.findById(payOrder.getId()).orElseThrow(PayNotExistedException::new);
// 5. 返回支付结果
return PaymentBuilder.buildPayResultByPayOrder(payOrder);
}
/**
* 执行支付方法 (第一次支付)
* @param payParam 支付参数
* @param payOrder 支付订单
*/
private void payFirstMethod(PayParam payParam, PayOrder payOrder) {
// 1.获取支付方式,通过工厂生成对应的策略组
List<AbsPayStrategy> paymentStrategyList = PayStrategyFactory.create(payParam.getPayWays());
if (CollectionUtil.isEmpty(paymentStrategyList)) {
throw new PayUnsupportedMethodException();
}
// 2.初始化支付的参数
for (AbsPayStrategy paymentStrategy : paymentStrategyList) {
paymentStrategy.initPayParam(payOrder, payParam);
}
// 3.支付前准备, 执行支付前处理
this.doHandler(payOrder, paymentStrategyList, AbsPayStrategy::doBeforePayHandler, null);
// 4.支付操作
this.doHandler(payOrder, paymentStrategyList, AbsPayStrategy::doPayHandler, (strategies, payOrderObj) -> {
// 发起支付成功进行的执行方法
strategies.forEach(AbsPayStrategy::doSuccessHandler);
// 所有支付方式都是同步时, 对支付订单进行处理
if (PayWayUtil.isNotSync(payParam.getPayWays())) {
// 修改支付订单状态为成功
payOrderObj.setStatus(PayStatusEnum.SUCCESS.getCode());
payOrderObj.setPayTime(LocalDateTime.now());
}
payOrderService.updateById(payOrderObj);
});
}
/**
* 异步支付执行(非第一次请求), 只执行异步支付策略, 报错不影响继续发起支付
*/
private PayResult paySyncNotFirst(PayParam payParam, PayOrder payOrder) {
// 0. 处理支付完成情况(完成/退款)
List<String> trades = Arrays.asList(PayStatusEnum.SUCCESS.getCode(), PayStatusEnum.CANCEL.getCode(),
PayStatusEnum.PARTIAL_REFUND.getCode(), PayStatusEnum.REFUNDED.getCode());
if (trades.contains(payOrder.getStatus())) {
return PaymentBuilder.buildPayResultByPayOrder(payOrder);
}
// 1.获取 异步支付 通道道,通过工厂生成对应的策略组(只包含异步支付的策略, 同步支付不再进行执行)
PayWayParam payWayParam = this.getAsyncPayParam(payParam, payOrder);
List<AbsPayStrategy> asyncStrategyList = PayStrategyFactory.create(Collections.singletonList(payWayParam));
// 2.初始化支付的参数
for (AbsPayStrategy paymentStrategy : asyncStrategyList) {
paymentStrategy.initPayParam(payOrder, payParam);
}
// 3.支付前准备
this.doHandler(payOrder, asyncStrategyList, AbsPayStrategy::doBeforePayHandler, null);
// 4. 发起支付
this.doHandler(payOrder, asyncStrategyList, AbsPayStrategy::doPayHandler, (strategyList, paymentObj) -> {
// 发起支付成功进行的执行方法
strategyList.forEach(AbsPayStrategy::doSuccessHandler);
payOrderService.updateById(paymentObj);
});
// 5. 获取支付记录信息
// payOrder = payOrderService.findById(payOrder.getId()).orElseThrow(PayNotExistedException::new);
// 6. 组装返回参数
return PaymentBuilder.buildPayResultByPayOrder(payOrder);
}
/**
* 执行策略中不同的handler
* @param payment 主支付对象
* @param strategyList 策略列表
* @param payMethod 执行支付的函数或者支付前的函数
* @param successMethod 执行成功的函数
*/
private void doHandler(PayOrder payment, List<AbsPayStrategy> strategyList, Consumer<AbsPayStrategy> payMethod,
PayStrategyConsumer<List<AbsPayStrategy>, PayOrder> successMethod) {
// 执行策略操作,如支付前/支付时
// 等同strategyList.forEach(payMethod.accept(PaymentStrategy))
strategyList.forEach(payMethod);
// 执行操作成功的处理
Optional.ofNullable(successMethod).ifPresent(fun -> fun.accept(strategyList, payment));
}
/**
* 获取异步支付参数
*/
private PayWayParam getAsyncPayParam(PayParam payParam, PayOrder payOrder) {
// 查询之前的支付方式
String asyncPayChannel = payOrder.getAsyncPayChannel();
PayOrderChannel payOrderChannel = payOrderChannelManager.findByOderIdAndChannel(payOrder.getId(), asyncPayChannel)
.orElseThrow(() -> new PayFailureException("支付方式数据异常"));
// 新的异步支付方式
PayWayParam payWayParam = payParam.getPayWays()
.stream()
.filter(payMode -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payMode.getChannel()))
.findFirst()
.orElseThrow(() -> new PayFailureException("支付方式数据异常"));
// 新传入的金额是否一致
if (!Objects.equals(payOrderChannel.getAmount(), payWayParam.getAmount())){
throw new PayFailureException("传入的支付金额非法!与订单金额不一致");
}
return payWayParam;
}
/**
* 创建支付订单/附加表/支付通道表并保存,返回支付订单
*/
private PayOrder createPayOrder(PayParam payParam) {
// 构建支付订单并保存
PayOrder payOrder = PaymentBuilder.buildPayOrder(payParam);
payOrderService.saveOder(payOrder);
// 构建支付订单扩展表并保存
PayOrderExtra payOrderExtra = new PayOrderExtra()
.setClientIp(payParam.getClientIp())
.setDescription(payParam.getDescription());
payOrderExtra.setId(payOrder.getId());
payOrderExtraManager.save(payOrderExtra);
// 构建支付通道表并保存
List<PayOrderChannel> payOrderChannels = PaymentBuilder.buildPayChannel(payParam.getPayWays())
.stream()
.peek(o -> o.setPaymentId(payOrder.getId()))
.collect(Collectors.toList());
payOrderChannelManager.saveAll(payOrderChannels);
return payOrder;
}
/**
* 校验支付状态,支付成功则返回,支付失败则抛出对应的异常
*/
private PayOrder getAndCheckByBusinessId(String businessId) {
// 根据订单查询支付记录
PayOrder payment = payOrderService.findByBusinessId(businessId).orElse(null);
if (Objects.nonNull(payment)) {
// 支付失败类型状态
List<String> tradesStatus = Arrays.asList(PayStatusEnum.FAIL.getCode(), PayStatusEnum.CANCEL.getCode(),
PayStatusEnum.CLOSE.getCode());
if (tradesStatus.contains(payment.getStatus())) {
throw new PayFailureException("支付失败或已经被撤销");
}
// 退款类型状态
tradesStatus = Arrays.asList(PayStatusEnum.REFUNDED.getCode(), PayStatusEnum.PARTIAL_REFUND.getCode());
if (tradesStatus.contains(payment.getStatus())) {
throw new PayFailureException("支付失败或已经被撤销");
}
// 支付超时状态
if (Objects.nonNull(payment.getExpiredTime())
&& LocalDateTimeUtil.ge(LocalDateTime.now(), payment.getExpiredTime())) {
throw new PayFailureException("支付已超时");
}
return payment;
}
return null;
}
}

View File

@@ -3,7 +3,6 @@ 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.core.channel.alipay.dao.AlipayConfigManager;
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;
@@ -33,12 +32,8 @@ import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROT
@RequiredArgsConstructor
public class AliPayStrategy extends AbsPayStrategy {
private final AlipayConfigManager alipayConfigManager;
private final AliPayOrderService aliPaymentService;
private final AlipaySyncService alipaySyncService;
private final AliPayService aliPayService;
private final AlipayConfigService alipayConfigService;
@@ -80,12 +75,6 @@ public class AliPayStrategy extends AbsPayStrategy {
// 检查并获取支付宝支付配置
this.initAlipayConfig();
aliPayService.validation(this.getPayWayParam(), alipayConfig);
// 如果没有显式传入同步回调地址, 使用默认配置
// if (StrUtil.isBlank(aliPayParam.getReturnUrl())) {
// aliPayParam.setReturnUrl(alipayConfig.getReturnUrl());
// }
this.initAlipayConfig();
}
/**
@@ -156,9 +145,9 @@ public class AliPayStrategy extends AbsPayStrategy {
* 初始化支付宝配置信息
*/
private void initAlipayConfig() {
// 检查并获取支付宝支付配置
this.alipayConfig = null;
alipayConfigService.initApiConfig(this.alipayConfig);
// 获取并初始化支付宝支付配置
this.alipayConfig = alipayConfigService.getConfig();
alipayConfigService.initConfig(this.alipayConfig);
}
}

View File

@@ -45,7 +45,7 @@ public abstract class AbsPayStrategy {
}
/**
* 支付前处理 包含必要的校验以及对Payment对象的创建和保存操作
* 支付前处理 包含必要的校验以及对当前通道支付订单的创建和保存操作
*/
public void doBeforePayHandler() {
}
@@ -56,7 +56,7 @@ public abstract class AbsPayStrategy {
public abstract void doPayHandler();
/**
* 支付成功的处理方式
* 支付调起成功的处理方式
*/
public void doSuccessHandler() {
}

View File

@@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
@@ -14,20 +13,11 @@ import java.util.List;
@Data
@Accessors(chain = true)
@Schema(title = "支付宝配置参数")
public class AlipayConfigParam implements Serializable {
@Schema(description = "主键")
private Long id;
public class AlipayConfigParam {
@Schema(description = "名称")
private String name;
@Schema(description = "商户编码")
private String mchCode;
@Schema(description = "商户应用编码")
private String mchAppCode;
@Schema(description = "支付宝商户appId")
private String appId;

View File

@@ -57,7 +57,7 @@ public class PayWayUtil {
*/
public boolean isNotSync(List<PayWayParam> payWayParams) {
return payWayParams.stream()
.map(PayWayParam::getPayChannel)
.map(PayWayParam::getChannel)
.noneMatch(PayChannelEnum.ASYNC_TYPE_CODE::contains);
}
@@ -67,7 +67,7 @@ public class PayWayUtil {
public PayWayParam getAsyncPayModeParam(PayParam payParam) {
return payParam.getPayWays()
.stream()
.filter(payMode -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payMode.getPayChannel()))
.filter(payMode -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payMode.getChannel()))
.findFirst()
.orElseThrow(() -> new PayFailureException("支付方式数据异常"));
}
@@ -129,7 +129,7 @@ public class PayWayUtil {
public void validationAmount(List<PayWayParam> payModeList) {
for (PayWayParam payWayParam : payModeList) {
// 支付金额小于等于零
if (payWayParam.getAmount() < 1) {
if (payWayParam.getAmount() < 0) {
throw new PayAmountAbnormalException();
}
}
@@ -143,7 +143,7 @@ public class PayWayUtil {
List<PayWayParam> payModeList = payParam.getPayWays();
long asyncPayModeCount = payModeList.stream()
.map(PayWayParam::getPayChannel)
.map(PayWayParam::getChannel)
.map(PayChannelEnum::findByCode)
.filter(PayChannelEnum.ASYNC_TYPE::contains)
.count();