feat 聚合支付处理, 增加h5嵌入式项目支持

This commit is contained in:
bootx
2024-02-10 22:50:24 +08:00
parent fcc73d680e
commit 7d65add2ca
68 changed files with 1246 additions and 305 deletions

View File

@@ -98,17 +98,27 @@
<artifactId>weixin-java-pay</artifactId>
<version>${wxjava.version}</version>
</dependency>
<!-- 微信工具包 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${wxjava.version}</version>
</dependency>
<!-- 数据权限 -->
<dependency>
<groupId>cn.bootx.platform</groupId>
<artifactId>common-starter-data-perm</artifactId>
</dependency>
<!-- 测试库 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- 分布式锁 -->
<dependency>
<groupId>com.baomidou</groupId>

View File

@@ -0,0 +1,23 @@
package cn.bootx.platform.daxpay.service.code;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 支付网关接口支持回调类型的枚举
* @author xxm
* @since 2024/2/10
*/
@Getter
@AllArgsConstructor
public enum PayApiCallBackTypeEnum {
/** 支持所有回调类型 */
ALL("all"),
/** 支持异步回调通知 */
ASYNC_NOTICE("async_notice"),
/** 不支持退掉通知 */
NONE("none");
private final String code;
}

View File

@@ -32,16 +32,6 @@ import java.time.LocalDateTime;
@TableName("pay_voucher")
public class Voucher extends MpBaseEntity implements EntityBaseFunction<VoucherDto> {
/** 商户编码 */
@TableField(updateStrategy = FieldStrategy.NEVER)
@DbColumn(comment = "商户编码")
private String mchCode;
/** 商户应用编码 */
@TableField(updateStrategy = FieldStrategy.NEVER)
@DbColumn(comment = "商户应用编码")
private String mchAppCode;
/** 卡号 */
@DbColumn(comment = "卡号")
@DbMySqlIndex(comment = "卡号索引")

View File

@@ -1,22 +1,13 @@
package cn.bootx.platform.daxpay.service.core.channel.voucher.service;
import cn.bootx.platform.common.core.exception.BizException;
import cn.bootx.platform.daxpay.service.code.VoucherCode;
import cn.bootx.platform.daxpay.service.core.channel.voucher.dao.VoucherLogManager;
import cn.bootx.platform.daxpay.service.core.channel.voucher.dao.VoucherManager;
import cn.bootx.platform.daxpay.service.core.channel.voucher.entity.Voucher;
import cn.bootx.platform.daxpay.service.param.channel.voucher.VoucherImportParam;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 储值卡
@@ -34,48 +25,6 @@ public class VoucherService {
private final VoucherLogManager voucherLogManager;
/**
* 批量导入
* @param skip 是否跳过已经导入的储值卡,false时将会异常
*/
@Transactional(rollbackFor = Exception.class)
public void importBatch(Boolean skip, String mchCode, String mchAppCode,List<VoucherImportParam> voucherImports) {
List<String> cardNoList = voucherImports.stream()
.map(VoucherImportParam::getCardNo)
.distinct()
.collect(Collectors.toList());
// 卡号不能重复
if (voucherImports.size()!=cardNoList.size()){
throw new BizException("卡号不能重复");
}
// 查询库中是否已经有对应的储值卡号
List<Voucher> vouchersByDB = voucherManager.findByCardNoList(cardNoList);
// 不跳过已经导入的储值卡且存在数据, 抛出异常
if (Objects.equals(skip,true)&& CollUtil.isNotEmpty(vouchersByDB)){
log.warn("数据库中已经存在的卡号:{}",vouchersByDB.stream().map(Voucher::getCardNo).collect(Collectors.toList()));
throw new BizException("要导入的卡号在数据中已经存在");
}
// 导入对应储值卡
List<String> cardNoListByDb = vouchersByDB.stream()
.map(Voucher::getCardNo)
.distinct()
.collect(Collectors.toList());
long batchNo = IdUtil.getSnowflakeNextId();
List<Voucher> vouchers = voucherImports.stream()
.filter(o -> !cardNoListByDb.contains(o.getCardNo()))
.map(o -> {
Voucher voucher = new Voucher();
BeanUtil.copyProperties(o, voucher);
return voucher.setMchCode(mchCode)
.setMchAppCode(mchAppCode)
.setBatchNo(batchNo);
})
.collect(Collectors.toList());
voucherManager.saveAll(vouchers);
// TODO 记录日志
}
/**
* 启用
*/
@@ -104,5 +53,4 @@ public class VoucherService {
voucherManager.changeStatusBatch(ids, VoucherCode.STATUS_FORBIDDEN);
}
}

View File

@@ -6,8 +6,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 钱包配置
* @author xxm
@@ -17,8 +15,4 @@ import java.util.Optional;
@Repository
@RequiredArgsConstructor
public class WalletConfigManager extends BaseManager<WalletConfigMapper, WalletConfig> {
public Optional<WalletConfig> findByMchCode(String mchCode){
return this.findByField(WalletConfig::getMchCode,mchCode);
}
}

View File

@@ -7,9 +7,6 @@ import cn.bootx.platform.daxpay.service.dto.channel.wallet.WalletConfigDto;
import cn.bootx.platform.daxpay.service.param.channel.wechat.WalletConfigParam;
import cn.bootx.table.modify.annotation.DbColumn;
import cn.bootx.table.modify.annotation.DbTable;
import cn.bootx.table.modify.mysql.annotation.DbMySqlIndex;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -29,17 +26,6 @@ import java.math.BigDecimal;
@TableName("pay_wallet_config")
public class WalletConfig extends MpBaseEntity implements EntityBaseFunction<WalletConfigDto> {
/** 商户编码 */
@TableField(updateStrategy = FieldStrategy.NEVER)
@DbColumn(comment = "商户编码")
private String mchCode;
/** 商户应用编码 */
@TableField(updateStrategy = FieldStrategy.NEVER)
@DbMySqlIndex(comment = "商户应用编码唯一索引")
@DbColumn(comment = "商户应用编码")
private String mchAppCode;
/** 默认余额 */
@DbColumn(comment = "默认余额")
private BigDecimal defaultBalance;

View File

@@ -3,7 +3,6 @@ package cn.bootx.platform.daxpay.service.core.channel.wallet.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.daxpay.service.core.channel.wallet.dao.WalletConfigManager;
import cn.bootx.platform.daxpay.service.core.channel.wallet.entity.WalletConfig;
import cn.bootx.platform.daxpay.service.dto.channel.wallet.WalletConfigDto;
import cn.bootx.platform.daxpay.service.param.channel.wechat.WalletConfigParam;
import cn.hutool.core.bean.BeanUtil;
import lombok.RequiredArgsConstructor;
@@ -21,15 +20,6 @@ import org.springframework.stereotype.Service;
public class WalletConfigService {
private final WalletConfigManager walletConfigManager;
/**
* 根据应用编码获取钱包配置
*/
public WalletConfigDto findByMchCode(String mchCode){
return walletConfigManager.findByMchCode(mchCode)
.map(WalletConfig::toDto)
.orElse(new WalletConfigDto());
}
/**
* 新增或更新
*/

View File

@@ -63,6 +63,11 @@ public class WeChatPayConfig extends MpBaseEntity implements EntityBaseFunction<
@DbColumn(comment = "同步通知路径")
private String returnUrl;
/** 接口版本, 使用v2还是v3接口 */
@DbColumn(comment = "接口版本")
private String apiVersion;
/** 商户平台「API安全」中的 APIv2 密钥 */
@TableField(updateStrategy = FieldStrategy.ALWAYS)
@BigField

View File

@@ -152,7 +152,7 @@ public class WeChatPayService {
Map<String, String> packageParams = WxPayKit.miniAppPrepayIdCreateSign(weChatPayConfig.getWxAppId(), prepayId,
weChatPayConfig.getApiKeyV2(), SignType.HMACSHA256);
String jsonStr = JacksonUtil.toJson(packageParams);
log.info("小程序支付的参数:" + jsonStr);
log.info("Jsapi支付的参数:" + jsonStr);
return jsonStr ;
}

View File

@@ -68,7 +68,7 @@ public class PayChannelOrder extends MpCreateEntity implements EntityBaseFunctio
* @see WalletPayParam
*/
@DbColumn(comment = "附加支付参数")
@TableField(updateStrategy = FieldStrategy.NEVER)
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String channelExtra;
/**

View File

@@ -47,7 +47,7 @@ public class PayRefundChannelOrder extends MpCreateEntity implements EntityBaseF
private Integer amount;
@DbColumn(comment = "剩余可退余额")
@TableField(updateStrategy = FieldStrategy.NEVER)
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private Integer refundableAmount;
/**

View File

@@ -0,0 +1,88 @@
package cn.bootx.platform.daxpay.service.core.payment.assist.service;
import cn.bootx.platform.daxpay.param.assist.WxAccessTokenParam;
import cn.bootx.platform.daxpay.param.assist.WxAuthUrlParam;
import cn.bootx.platform.daxpay.param.assist.WxJsapiPrePayParam;
import cn.bootx.platform.daxpay.result.assist.WxAccessTokenResult;
import cn.bootx.platform.daxpay.result.assist.WxAuthUrlResult;
import cn.bootx.platform.daxpay.result.assist.WxJsapiPrePayResult;
import cn.bootx.platform.daxpay.service.code.WeChatPayCode;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayConfigService;
import cn.hutool.core.bean.BeanUtil;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.WxPayKit;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.Objects;
/**
* 支付支撑服务类
* @author xxm
* @since 2024/2/10
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UniPayAssistService {
private final WeChatPayConfigService weChatPayConfigService;
/**
* 构建微信oauth2授权的url连接
*/
public WxAuthUrlResult getWxAuthUrl(WxAuthUrlParam param) {
WxMpService wxMpService = this.getWxMpService();
String url = wxMpService.getOAuth2Service()
.buildAuthorizationUrl(param.getUrl(), WxConsts.OAuth2Scope.SNSAPI_BASE, param.getState());
return new WxAuthUrlResult().setUrl(url);
}
/**
* 获取微信AccessToken数据
*/
@SneakyThrows
public WxAccessTokenResult getWxAccessToken(WxAccessTokenParam wxAccessToken){
WxMpService wxMpService = this.getWxMpService();
WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(wxAccessToken.getCode());
return new WxAccessTokenResult()
.setAccessToken(accessToken.getAccessToken())
.setOpenId(accessToken.getOpenId());
}
/**
* 获取微信预支付信息
*/
public WxJsapiPrePayResult getWxJsapiPrePay(WxJsapiPrePayParam param){
WeChatPayConfig config = weChatPayConfigService.getConfig();
String apiKey;
if (Objects.equals(config.getApiKeyV2(), WeChatPayCode.API_V3)){
apiKey = config.getApiKeyV3();
} else {
apiKey = config.getApiKeyV2();
}
Map<String, String> map = WxPayKit.prepayIdCreateSign(param.getPrepayId(), config.getWxAppId(), apiKey, SignType.HMACSHA256);
return BeanUtil.toBean(map, WxJsapiPrePayResult.class);
}
/**
* 获取微信公众号API的Service
*/
private WxMpService getWxMpService() {
WeChatPayConfig config = weChatPayConfigService.getConfig();
WxMpService wxMpService = new WxMpServiceImpl();
WxMpDefaultConfigImpl wxMpConfig = new WxMpDefaultConfigImpl();
wxMpConfig.setAppId(config.getWxAppId()); // 设置微信公众号的appid
wxMpConfig.setSecret(config.getAppSecret()); // 设置微信公众号的app corpSecret
wxMpService.setWxMpConfigStorage(wxMpConfig);
return wxMpService;
}
}

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.service.core.payment.common.aop;
import cn.bootx.platform.common.core.util.ValidationUtil;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.PayCommonParam;
import cn.bootx.platform.daxpay.param.PaymentCommonParam;
import cn.bootx.platform.daxpay.service.annotation.PaymentApi;
import cn.bootx.platform.daxpay.service.core.payment.common.service.PaymentSignService;
import lombok.RequiredArgsConstructor;
@@ -32,11 +32,11 @@ public class PaymentVerifySignAop {
throw new PayFailureException("支付方法至少有一个参数,并且需要签名支付参数需要放在第一位");
}
Object param = args[0];
if (param instanceof PayCommonParam){
if (param instanceof PaymentCommonParam){
// 参数校验
ValidationUtil.validateParam(param);
// 验签
paymentSignService.verifySign((PayCommonParam) param);
paymentSignService.verifySign((PaymentCommonParam) param);
} else {
throw new PayFailureException("支付参数需要继承PayCommonParam");
}

View File

@@ -6,7 +6,7 @@ import cn.bootx.platform.daxpay.service.common.context.RequestLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.system.config.entity.PlatformConfig;
import cn.bootx.platform.daxpay.service.core.system.config.service.PlatformConfigService;
import cn.bootx.platform.daxpay.param.pay.PayCommonParam;
import cn.bootx.platform.daxpay.param.PaymentCommonParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
@@ -27,9 +27,9 @@ public class PaymentAssistService {
/**
* 初始化上下文
*/
public void initContext(PayCommonParam payCommonParam){
public void initContext(PaymentCommonParam paymentCommonParam){
this.initPlatform();
this.initRequest(payCommonParam);
this.initRequest(paymentCommonParam);
}
/**
@@ -49,13 +49,13 @@ public class PaymentAssistService {
/**
* 初始化请求相关信息上下文
*/
private void initRequest(PayCommonParam payCommonParam){
private void initRequest(PaymentCommonParam paymentCommonParam){
RequestLocal request = PaymentContextLocal.get().getRequestInfo();
request.setClientIp(payCommonParam.getClientIp())
.setAttach(payCommonParam.getAttach())
.setSign(payCommonParam.getSign())
.setVersion(payCommonParam.getVersion())
.setReqTime(payCommonParam.getReqTime())
request.setClientIp(paymentCommonParam.getClientIp())
// .setAttach(paymentCommonParam.getAttach())
.setSign(paymentCommonParam.getSign())
.setVersion(paymentCommonParam.getVersion())
.setReqTime(paymentCommonParam.getReqTime())
.setReqId(MDC.get(CommonCode.TRACE_ID));
}
}

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.service.core.payment.common.service;
import cn.bootx.platform.daxpay.code.PaySignTypeEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.PayCommonParam;
import cn.bootx.platform.daxpay.param.PaymentCommonParam;
import cn.bootx.platform.daxpay.service.common.context.ApiInfoLocal;
import cn.bootx.platform.daxpay.service.common.context.PlatformLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
@@ -28,7 +28,7 @@ public class PaymentSignService {
/**
* 签名
*/
public void verifySign(PayCommonParam param) {
public void verifySign(PaymentCommonParam param) {
// 先触发上下文的初始化
paymentAssistService.initContext(param);
ApiInfoLocal apiInfo = PaymentContextLocal.get().getApiInfo();

View File

@@ -65,14 +65,13 @@ public class PayRefundAssistService {
PlatformLocal platform = PaymentContextLocal.get().getPlatformInfo();
// 异步回调
if (!param.isNotNotify()){
noticeInfo.setNotifyUrl(param.getReturnUrl());
noticeInfo.setNotifyUrl(param.getNotifyUrl());
if (StrUtil.isNotBlank(param.getNotifyUrl())){
noticeInfo.setNotifyUrl(platform.getNotifyUrl());
}
}
}
/**
* 根据退款参数获取支付订单
*/

View File

@@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.service.core.system.config.entity;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import cn.bootx.platform.daxpay.service.code.PayApiCallBackTypeEnum;
import cn.bootx.platform.daxpay.service.core.system.config.convert.PayApiConfigConvert;
import cn.bootx.platform.daxpay.service.dto.system.config.PayApiConfigDto;
import cn.bootx.table.modify.annotation.DbColumn;
@@ -37,6 +38,14 @@ public class PayApiConfig extends MpBaseEntity implements EntityBaseFunction<Pay
@TableField(updateStrategy = FieldStrategy.NEVER)
private String name;
/**
* 支持回调通知
* @see PayApiCallBackTypeEnum
*/
@DbColumn(comment = "支持回调通知")
@TableField(updateStrategy = FieldStrategy.NEVER)
private boolean noticeSupport;
@DbColumn(comment = "是否启用")
private boolean enable;

View File

@@ -1,6 +1,7 @@
package cn.bootx.platform.daxpay.service.dto.system.config;
import cn.bootx.platform.common.core.rest.dto.BaseDto;
import cn.bootx.platform.daxpay.service.code.PayApiCallBackTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -26,6 +27,13 @@ public class PayApiConfigDto extends BaseDto {
@Schema(description = "名称")
private String name;
/**
* 是否支持回调通知
* @see PayApiCallBackTypeEnum
*/
@Schema(description = "是否支持回调通知")
private boolean noticeSupport;
@Schema(description = "是否启用")
private boolean enable;