【功能优化】支付:支付应用,增加 appKey 标识,用于不同接入方的标识

This commit is contained in:
YunaiV
2024-08-18 15:30:35 +08:00
parent 6eb40aa544
commit 1dadfb8fba
18 changed files with 122 additions and 137 deletions

View File

@ -14,6 +14,10 @@ import jakarta.validation.constraints.*;
@Data
public class PayAppBaseVO {
@Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
@NotEmpty(message = "应用标识不能为空")
private String appKey;
@Schema(description = "应用名", requiredMode = Schema.RequiredMode.REQUIRED, example = "小豆")
@NotNull(message = "应用名不能为空")
private String name;

View File

@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.pay.controller.admin.app.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 支付应用信息创建 Request VO")
@Data
@ -9,8 +11,4 @@ import lombok.*;
@ToString(callSuper = true)
public class PayAppCreateReqVO extends PayAppBaseVO {
@Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
@NotNull(message = "应用标识不能为空")
private String appKey;
}

View File

@ -17,9 +17,6 @@ public class PayAppPageItemRespVO extends PayAppBaseVO {
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
private String appKey;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;

View File

@ -20,7 +20,7 @@ public class PayAppPageReqVO extends PageParam {
@Schema(description = "应用名", example = "小豆")
private String name;
@Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
@Schema(description = "应用标识", example = "yudao")
private String appKey;
@Schema(description = "开启状态", example = "0")

View File

@ -14,8 +14,4 @@ public class PayAppUpdateReqVO extends PayAppBaseVO {
@NotNull(message = "应用编号不能为空")
private Long id;
@Schema(description = "应用标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
@NotNull(message = "应用标识不能为空")
private String appKey;
}

View File

@ -15,6 +15,8 @@ public class PayProperties {
private static final String ORDER_NO_PREFIX = "P";
private static final String REFUND_NO_PREFIX = "R";
private static final String WALLET_PAY_APP_KEY_DEFAULT = "wallet";
/**
* 支付回调地址
*
@ -49,4 +51,10 @@ public class PayProperties {
@NotEmpty(message = "退款订单 no 的前缀不能为空")
private String refundNoPrefix = REFUND_NO_PREFIX;
/**
* 钱包支付应用 AppKey
*/
@NotEmpty(message = "钱包支付应用 AppKey 不能为空")
private String walletPayAppKey = WALLET_PAY_APP_KEY_DEFAULT;
}

View File

@ -1,9 +1,7 @@
package cn.iocoder.yudao.module.pay.service.app;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO;
@ -20,7 +18,6 @@ import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
@ -46,8 +43,9 @@ public class PayAppServiceImpl implements PayAppService {
@Override
public Long createApp(PayAppCreateReqVO createReqVO) {
// 验证appKey是否重复
validateAppKeyDuplicate(null, createReqVO.getAppKey());
// 验证 appKey 是否重复
validateEmailUnique(null, createReqVO.getAppKey());
// 插入
PayAppDO app = PayAppConvert.INSTANCE.convert(createReqVO);
appMapper.insert(app);
@ -59,13 +57,28 @@ public class PayAppServiceImpl implements PayAppService {
public void updateApp(PayAppUpdateReqVO updateReqVO) {
// 校验存在
validateAppExists(updateReqVO.getId());
// 验证appKey是否重复
validateAppKeyDuplicate(updateReqVO.getId(), updateReqVO.getAppKey());
// 验证 appKey 是否重复
validateEmailUnique(updateReqVO.getId(), updateReqVO.getAppKey());
// 更新
PayAppDO updateObj = PayAppConvert.INSTANCE.convert(updateReqVO);
appMapper.updateById(updateObj);
}
void validateEmailUnique(Long id, String appKey) {
PayAppDO app = appMapper.selectByAppKey(appKey);
if (app == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 appKey 的应用
if (id == null) {
throw exception(APP_KEY_EXISTS);
}
if (!app.getId().equals(id)) {
throw exception(APP_KEY_EXISTS);
}
}
@Override
public void updateAppStatus(Long id, Integer status) {
// 校验商户存在
@ -119,63 +132,31 @@ public class PayAppServiceImpl implements PayAppService {
@Override
public PayAppDO validPayApp(Long appId) {
PayAppDO app = appMapper.selectById(appId);
// 校验支付应用数据是否存在以及可用
return validatePayAppDO(app);
return validatePayApp(app);
}
@Override
public PayAppDO validPayApp(String appKey) {
PayAppDO app = appMapper.selectByAppKey(appKey);
// 校验支付应用数据是否存在以及可用
return validatePayAppDO(app);
return validatePayApp(app);
}
/**
* 校验支付应用实体的有效性
* 主要包括存在性检查和禁用状态检查
* 校验支付应用实体的有效性:存在 + 开启
*
* @param app 待校验的支付应用实体
* @return 校验通过的支付应用实体
* @throws IllegalArgumentException 如果支付应用实体不存在或已被禁用
*/
private PayAppDO validatePayAppDO(PayAppDO app) {
private PayAppDO validatePayApp(PayAppDO app) {
// 校验是否存在
if (app == null) {
throw exception(ErrorCodeConstants.APP_NOT_FOUND);
}
// 校验是否禁用
if (CommonStatusEnum.DISABLE.getStatus().equals(app.getStatus())) {
if (CommonStatusEnum.isDisable(app.getStatus())) {
throw exception(ErrorCodeConstants.APP_IS_DISABLE);
}
return app;
}
/**
* 校验应用密钥是否重复
* 在新增或更新支付应用时确保应用密钥appKey的唯一性
* 如果是在新增情况下检查数据库中是否已存在相同的appKey
* 如果是在更新情况下检查数据库中是否存在除当前应用外的其他应用使用了相同的appKey
*
* @param payAppId 支付应用的ID更新时使用新增时可能为null
* @param payAppKey 支付应用的密钥,用于校验是否重复
* @throws RuntimeException 如果发现appKey重复抛出运行时异常
*/
private void validateAppKeyDuplicate(Long payAppId, String payAppKey) {
// 新增时校验appKey是否重复
if (Objects.isNull(payAppId) && StrUtil.isNotBlank(payAppKey)) {
if (appMapper.selectCount(PayAppDO::getAppKey, payAppKey) > 0) {
throw exception(APP_KEY_EXISTS);
}
// 更新时校验appKey是否重复
} else if (Objects.nonNull(payAppId) && StrUtil.isNotBlank(payAppKey)) {
LambdaQueryWrapperX<PayAppDO> queryWrapper = new LambdaQueryWrapperX<>();
queryWrapper.eq(PayAppDO::getAppKey, payAppKey)
.ne(PayAppDO::getId, payAppId);
if (appMapper.selectCount(queryWrapper) > 0) {
throw exception(APP_KEY_EXISTS);
}
}
}
}

View File

@ -43,7 +43,7 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
public class PayDemoOrderServiceImpl implements PayDemoOrderService {
/**
* 接入的实力应用编号
* 接入的支付应用标识
*
* 从 [支付管理 -> 应用信息] 里添加
*/

View File

@ -199,8 +199,8 @@ public class PayRefundServiceImpl implements PayRefundService {
* @param channel 支付渠道
* @param notify 通知
*/
@Transactional(rollbackFor = Exception.class)
// 注意,如果是方法内调用该方法,需要通过 getSelf().notifyRefund(channel, notify) 调用,否则事务不生效
@Transactional(rollbackFor = Exception.class)
public void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) {
// 情况一:退款成功
if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) {

View File

@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargeMapper;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
@ -51,11 +52,6 @@ import static cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum.*;
@Slf4j
public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
/**
* TODO 芋艿:放到 payconfig
*/
private static final String WALLET_PAY_APP_KEY = "wallet";
private static final String WALLET_RECHARGE_ORDER_SUBJECT = "钱包余额充值";
@Resource
@ -68,9 +64,13 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
private PayRefundService payRefundService;
@Resource
private PayWalletRechargePackageService payWalletRechargePackageService;
@Resource
public SocialClientApi socialClientApi;
@Resource
private PayProperties payProperties;
@Override
@Transactional(rollbackFor = Exception.class)
public PayWalletRechargeDO createWalletRecharge(Long userId, Integer userType, String userIp,
@ -92,7 +92,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
// 2.1 创建支付单
Long payOrderId = payOrderService.createOrder(new PayOrderCreateReqDTO()
.setAppKey(WALLET_PAY_APP_KEY).setUserIp(userIp)
.setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp)
.setMerchantOrderId(recharge.getId().toString()) // 业务的订单编号
.setSubject(WALLET_RECHARGE_ORDER_SUBJECT).setBody("")
.setPrice(recharge.getPayPrice())
@ -174,7 +174,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
String walletRechargeId = String.valueOf(id);
String refundId = walletRechargeId + "-refund";
Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO()
.setAppKey(WALLET_PAY_APP_KEY).setUserIp(userIp)
.setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp)
.setMerchantOrderId(walletRechargeId)
.setMerchantRefundId(refundId)
.setReason("想退钱").setPrice(walletRecharge.getPayPrice()));

View File

@ -98,9 +98,12 @@ public class AlipayPayClientConfig implements PayClientConfig {
private String rootCertContent;
/**
* 接口内容加密方式,如果为空,将使用无加密方式
* 如果要加密,目前支付宝只有 AES 一种加密方式
* <a href="https://opendocs.alipay.com/common/02mse3">支付宝开放平台</a>
* 接口内容加密方式
*
* 1. 如果为空,将使用无加密方式
* 2. 如果要加密,目前支付宝只有 AES 一种加密方式
*
* @see <a href="https://opendocs.alipay.com/common/02mse3">支付宝开放平台</a>
* @see AlipayPayClientConfig#ENC_TYPE_AES
*/
private String encryptType;