feat 支付流程支持商户和应用

This commit is contained in:
xxm1995
2023-06-14 17:23:09 +08:00
parent 61d5b8746d
commit 802c381876
52 changed files with 428 additions and 355 deletions

View File

@@ -146,7 +146,7 @@
<dependency>
<groupId>cn.bootx</groupId>
<artifactId>mybatis-table-modify-mysql-boot-starter</artifactId>
<version>1.5.3.alpha1</version>
<version>${mybatis-table-modify.version}</version>
</dependency>
<!-- 监控 -->

View File

@@ -1,6 +1,7 @@
package cn.bootx.platform.daxpay;
import cn.hutool.core.util.StrUtil;
import io.minio.credentials.MinioClientConfigProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;

View File

@@ -0,0 +1,38 @@
package cn.bootx.platform.daxpay.code;
/**
* 商户和应用相关编码
*
* @author xxm
* @date 2023/6/12
*/
public interface MchAndAppCode {
/* 商户状态 */
/** 正常 */
String MCH_STATE_NORMAL = "normal";
/** 停用 */
String MCH_STATE_FORBIDDEN = "forbidden";
/** 封禁 */
String MCH_STATE_BANNED = "banned";
/* 商户应用状态 */
/** 正常 */
String MCH_APP_STATE_NORMAL = "normal";
/** 停用 */
String MCH_APP_STATE_FORBIDDEN = "forbidden";
/** 封禁 */
String MCH_APP_STATE_BANNED = "banned";
/* 应用关联支付配置状态 */
/** 正常 */
String PAY_CONFIG_STATE_NORMAL = "normal";
/** 停用 */
String PAY_CONFIG_STATE_FORBIDDEN = "forbidden";
}

View File

@@ -42,20 +42,6 @@ public class AlipayConfigController {
return Res.ok();
}
@Operation(summary = "启用指定的支付宝配置")
@PostMapping("/setUpActivity")
public ResResult<Void> setUpActivity(Long id) {
alipayConfigService.setUpActivity(id);
return Res.ok();
}
@Operation(summary = "清除指定的支付宝配置")
@PostMapping("/clearActivity")
public ResResult<Void> clearActivity(Long id) {
alipayConfigService.clearActivity(id);
return Res.ok();
}
@Operation(summary = "分页")
@GetMapping("/page")
public ResResult<PageResult<AlipayConfigDto>> page(PageParam pageParam, AlipayConfigQuery param) {

View File

@@ -44,10 +44,10 @@ public class CashierController {
}
@Operation(summary = "扫码聚合支付(单渠道)")
@GetMapping("/aggregatePay")
public ModelAndView aggregatePay(String key, @RequestHeader(USER_AGENT) String ua) {
@GetMapping("/aggregatePay/{mchAppCode}")
public ModelAndView aggregatePay(String key,@PathVariable String mchAppCode, @RequestHeader(USER_AGENT) String ua) {
try {
String url = cashierService.aggregatePay(key, ua);
String url = cashierService.aggregatePay(key, mchAppCode, ua);
return new ModelAndView("redirect:" + url);
}
catch (PayUnsupportedMethodException e) {
@@ -56,9 +56,9 @@ public class CashierController {
}
@Operation(summary = "微信jsapi支付(回调)")
@GetMapping("/wxJsapiPay")
public ModelAndView wxJsapiPay(String code, String state) {
Map<String, String> map = cashierService.wxJsapiPay(code, state);
@GetMapping("/wxJsapiPay/{mchAppCode}")
public ModelAndView wxJsapiPay(String code, @PathVariable String mchAppCode, String state) {
Map<String, String> map = cashierService.wxJsapiPay(code,mchAppCode,state);
// 跳转页面, 调起微信jsapi支付
return new ModelAndView("wechatJsapiPay").addAllObjects(map);
}

View File

@@ -5,7 +5,7 @@ import cn.bootx.platform.common.core.rest.Res;
import cn.bootx.platform.common.core.rest.ResResult;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.daxpay.core.merchant.service.MchAppPayConfigService;
import cn.bootx.platform.daxpay.core.merchant.service.MchApplicationService;
import cn.bootx.platform.daxpay.core.merchant.service.MchAppService;
import cn.bootx.platform.daxpay.dto.merchant.MchAppPayConfigResult;
import cn.bootx.platform.daxpay.dto.merchant.MchApplicationDto;
import cn.bootx.platform.daxpay.param.merchant.MchApplicationParam;
@@ -28,7 +28,7 @@ import java.util.List;
@RequiredArgsConstructor
public class MchApplicationController {
private final MchApplicationService applicationService;
private final MchAppService applicationService;
private final MchAppPayConfigService appPayConfigService;

View File

@@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -36,19 +37,19 @@ public class PayCallbackController {
@SneakyThrows
@Operation(summary = "支付宝回调")
@PostMapping("/alipay")
public String aliPay(HttpServletRequest request) {
@PostMapping("/alipay/{appCode}")
public String aliPay(@PathVariable String appCode, HttpServletRequest request) {
Map<String, String> stringStringMap = AliPayApi.toMap(request);
return aliPayCallbackService.payCallback(stringStringMap);
return aliPayCallbackService.payCallback(appCode, stringStringMap);
}
@SneakyThrows
@Operation(summary = "微信支付回调")
@PostMapping("/wechat")
public String wechat(HttpServletRequest request) {
@PostMapping("/wechat/{appCode}")
public String wechat(@PathVariable String appCode, HttpServletRequest request) {
String xmlMsg = HttpKit.readData(request);
Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
return weChatPayCallbackService.payCallback(params);
return weChatPayCallbackService.payCallback(appCode, params);
}
}

View File

@@ -24,7 +24,7 @@ import org.springframework.web.bind.annotation.RestController;
*/
@Tag(name = "统一支付")
@RestController
@RequestMapping("/uni_pay")
@RequestMapping("/uniPay")
@AllArgsConstructor
public class PayController {

View File

@@ -41,20 +41,6 @@ public class WeChatPayConfigController {
return Res.ok();
}
@Operation(summary = "设置启用的微信支付配置")
@PostMapping("/setUpActivity")
public ResResult<Void> setUpActivity(Long id) {
weChatPayConfigService.setUpActivity(id);
return Res.ok();
}
@Operation(summary = "清除指定的微信支付配置")
@PostMapping("/clearActivity")
public ResResult<Void> clearActivity(Long id) {
weChatPayConfigService.clearActivity(id);
return Res.ok();
}
@Operation(summary = "分页")
@GetMapping("/page")
public ResResult<PageResult<WeChatPayConfigDto>> page(PageParam pageParam, WeChatPayConfigParam param) {

View File

@@ -68,8 +68,8 @@ public class CashierService {
}
// 构建支付方式参数
PayWayParam payWayParam = new PayWayParam().setPayChannel(param.getPayChannel())
.setPayWay(param.getPayWay())
.setAmount(param.getAmount());
.setPayWay(param.getPayWay())
.setAmount(param.getAmount());
// 处理附加参数
HashMap<String, String> map = new HashMap<>(1);
@@ -80,8 +80,8 @@ public class CashierService {
payWayParam.setExtraParamsJson(extraParamsJson);
PayParam payParam = new PayParam().setTitle(param.getTitle())
.setBusinessId(param.getBusinessId())
.setPayWayList(Collections.singletonList(payWayParam));
.setBusinessId(param.getBusinessId())
.setPayWayList(Collections.singletonList(payWayParam));
PayResult payResult = payService.pay(payParam);
if (Objects.equals(PayStatusCode.TRADE_REFUNDED, payResult.getPayStatus())) {
@@ -93,16 +93,17 @@ public class CashierService {
/**
* 扫码发起自动支付
*/
public String aggregatePay(String key, String ua) {
public String aggregatePay(String key, String mchAppCode, String ua) {
CashierSinglePayParam cashierSinglePayParam = new CashierSinglePayParam()
.setPayWay(PayWayEnum.QRCODE.getCode());
.setMchAppCode(mchAppCode)
.setPayWay(PayWayEnum.QRCODE.getCode());
// 判断是哪种支付方式
if (ua.contains(PayChannelEnum.UA_ALI_PAY)) {
cashierSinglePayParam.setPayChannel(PayChannelEnum.ALI.getCode());
}
else if (ua.contains(PayChannelEnum.UA_WECHAT_PAY)) {
// 跳转微信授权页面, 调用jsapi进行支付
return this.wxJsapiAuth(key);
return this.wxJsapiAuth(key,mchAppCode);
}
else {
throw new PayUnsupportedMethodException();
@@ -110,8 +111,8 @@ public class CashierService {
AggregatePayInfo aggregatePayInfo = aggregateService.getAggregateInfo(key);
cashierSinglePayParam.setTitle(aggregatePayInfo.getTitle())
.setAmount(aggregatePayInfo.getAmount())
.setBusinessId(aggregatePayInfo.getBusinessId());
.setAmount(aggregatePayInfo.getAmount())
.setBusinessId(aggregatePayInfo.getBusinessId());
PayResult payResult = this.singlePay(cashierSinglePayParam);
return payResult.getAsyncPayInfo().getPayBody();
}
@@ -119,13 +120,13 @@ public class CashierService {
/**
* 微信jsapi支付 - 跳转到授权页面
*/
private String wxJsapiAuth(String key) {
WeChatPayConfig config = weChatPayConfigManager.findActivity()
.orElseThrow(() -> new PayFailureException("未找到启用的微信支付配置"));
WxMpService wxMpService = getWxMpService(config.getAppId(), config.getAppSecret());
private String wxJsapiAuth(String key, String mchAppCode) {
WeChatPayConfig config = weChatPayConfigManager.findByMchAppCode(mchAppCode)
.orElseThrow(() -> new PayFailureException("未找到启用的微信支付配置"));
WxMpService wxMpService = getWxMpService(config.getWxAppId(), config.getAppSecret());
// 回调地址为 结算台微信jsapi支付的回调地址
SystemParameter systemParameter = systemParamManager.findByParamKey(WeChatPayCode.JSAPI_REDIRECT_URL)
.orElseThrow(() -> new PayFailureException("微信支付回调地址参数不存在"));
.orElseThrow(() -> new PayFailureException("微信支付回调地址参数不存在"));
String url = systemParameter.getValue() + "cashier/wxJsapiPay";
return wxMpService.getOAuth2Service().buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_BASE, key);
}
@@ -133,28 +134,29 @@ public class CashierService {
/**
* 微信jsapi支付 - 回调发起预支付, 同时调起微信页面jsapi支付
* @param code 微信授权码, 用来获取id
* @param mchAppCode 商户应用编码
* @param state 聚合支付参数记录的key
* @return 页面中调起jsapi支付的参数
*/
@SneakyThrows
public Map<String, String> wxJsapiPay(String code, String state) {
WeChatPayConfig config = weChatPayConfigManager.findActivity()
.orElseThrow(() -> new PayFailureException("未找到启用的微信支付配置"));
WxMpService wxMpService = this.getWxMpService(config.getAppId(), config.getAppSecret());
public Map<String, String> wxJsapiPay(String code, String mchAppCode, String state) {
WeChatPayConfig config = weChatPayConfigManager.findByMchAppCode(mchAppCode)
.orElseThrow(() -> new PayFailureException("未找到启用的微信支付配置"));
WxMpService wxMpService = this.getWxMpService(config.getWxAppId(), config.getAppSecret());
WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
String openId = accessToken.getOpenId();
AggregatePayInfo aggregatePayInfo = aggregateService.getAggregateInfo(state);
// 构造微信API支付参数
CashierSinglePayParam cashierSinglePayParam = new CashierSinglePayParam()
.setPayChannel(PayChannelEnum.WECHAT.getCode())
.setPayWay(PayWayEnum.JSAPI.getCode())
.setTitle(aggregatePayInfo.getTitle())
.setAmount(aggregatePayInfo.getAmount())
.setOpenId(openId)
.setBusinessId(aggregatePayInfo.getBusinessId());
.setPayChannel(PayChannelEnum.WECHAT.getCode())
.setPayWay(PayWayEnum.JSAPI.getCode())
.setTitle(aggregatePayInfo.getTitle())
.setAmount(aggregatePayInfo.getAmount())
.setOpenId(openId)
.setBusinessId(aggregatePayInfo.getBusinessId());
PayResult payResult = this.singlePay(cashierSinglePayParam);
return WxPayKit.prepayIdCreateSign(payResult.getAsyncPayInfo().getPayBody(), config.getAppId(),
return WxPayKit.prepayIdCreateSign(payResult.getAsyncPayInfo().getPayBody(), config.getWxAppId(),
config.getApiKeyV2(), SignType.HMACSHA256);
}
@@ -183,8 +185,8 @@ public class CashierService {
}
// 发起支付
PayParam payParam = new PayParam().setTitle(param.getTitle())
.setBusinessId(param.getBusinessId())
.setPayWayList(param.getPayWayList());
.setBusinessId(param.getBusinessId())
.setPayWayList(param.getPayWayList());
PayResult payResult = payService.pay(payParam);
if (Objects.equals(PayStatusCode.TRADE_REFUNDED, payResult.getPayStatus())) {

View File

@@ -10,7 +10,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.Objects;
import java.util.Optional;
/**
@@ -23,28 +22,11 @@ import java.util.Optional;
@RequiredArgsConstructor
public class AlipayConfigManager extends BaseManager<AlipayConfigMapper, AlipayConfig> {
private Optional<AlipayConfig> alipayConfig;
@Override
public AlipayConfig saveOrUpdate(AlipayConfig entity) {
this.clearCache();
return super.saveOrUpdate(entity);
}
@Override
public AlipayConfig updateById(AlipayConfig alipayConfig) {
this.clearCache();
return super.updateById(alipayConfig);
}
/**
* 获取启用的支付宝配置
* 获取关联的的支付宝配置
*/
public Optional<AlipayConfig> findActivity() {
if (Objects.isNull(alipayConfig)) {
alipayConfig = findByField(AlipayConfig::getActivity, Boolean.TRUE);
}
return alipayConfig;
public Optional<AlipayConfig> findByMchAppCode(String mchAppCOde) {
return findByField(AlipayConfig::getMchAppCode, mchAppCOde);
}
/**
@@ -58,21 +40,4 @@ public class AlipayConfigManager extends BaseManager<AlipayConfigMapper, AlipayC
.page(mpPage);
}
/**
* 清除所有启用的支付配置
*/
public void removeAllActivity() {
this.clearCache();
lambdaUpdate().eq(AlipayConfig::getActivity, Boolean.TRUE)
.set(AlipayConfig::getActivity, Boolean.FALSE)
.update();
}
/**
* 清除缓存
*/
public void clearCache() {
alipayConfig = null;
}
}

View File

@@ -2,10 +2,12 @@ package cn.bootx.platform.daxpay.core.channel.alipay.entity;
import cn.bootx.mybatis.table.modify.annotation.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbTable;
import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlIndex;
import cn.bootx.platform.common.core.annotation.BigField;
import cn.bootx.platform.common.core.annotation.EncryptionField;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import cn.bootx.platform.daxpay.code.paymodel.AliPayCode;
import cn.bootx.platform.daxpay.core.channel.alipay.convert.AlipayConvert;
import cn.bootx.platform.daxpay.dto.channel.alipay.AlipayConfigDto;
import cn.bootx.platform.daxpay.param.channel.alipay.AlipayConfigParam;
@@ -35,13 +37,16 @@ public class AlipayConfig extends MpBaseEntity implements EntityBaseFunction<Ali
@DbColumn(comment = "名称")
private String name;
/** 商户Id */
@DbColumn(comment = "商户Id")
private Long merchantId;
/** 商户编码 */
@TableField(updateStrategy = FieldStrategy.NEVER)
@DbColumn(comment = "商户编码")
private String mchCode;
/** 商户应用Id */
@DbColumn(comment = "商户应用Id")
private Long mchAppId;
/** 商户应用编码 */
@TableField(updateStrategy = FieldStrategy.NEVER)
@DbMySqlIndex(comment = "商户应用编码唯一索引")
@DbColumn(comment = "商户应用编码")
private String mchAppCode;
/** 支付宝商户appId */
@DbColumn(comment = "支付宝商户appId")
@@ -61,7 +66,10 @@ public class AlipayConfig extends MpBaseEntity implements EntityBaseFunction<Ali
@DbColumn(comment = "")
private String serverUrl;
/** 认证类型 证书/公钥 */
/**
* 认证类型 证书/公钥
* @see AliPayCode#AUTH_TYPE_KEY
*/
@DbColumn(comment = "认证类型")
private Integer authType;
@@ -111,13 +119,9 @@ public class AlipayConfig extends MpBaseEntity implements EntityBaseFunction<Ali
@DbColumn(comment = "可用支付方式")
private String payWays;
/** 是否启用 */
@DbColumn(comment = "是否启用")
private Boolean activity;
/** 状态 暂时没什么用 */
@DbColumn(comment = "状态 暂时没什么用")
private Integer state;
/** 状态 */
@DbColumn(comment = "状态")
private String state;
/** 备注 */
@DbColumn(comment = "备注")

View File

@@ -57,9 +57,13 @@ public class AliPayCallbackService extends AbsPayCallbackStrategy {
return PayStatusCode.NOTIFY_TRADE_FAIL;
}
/**
* 验证信息格式
* @param mchAppCode 商户应用编码
*/
@SneakyThrows
@Override
public boolean verifyNotify() {
public boolean verifyNotify(String mchAppCode) {
Map<String, String> params = PARAMS.get();
String callReq = JSONUtil.toJsonStr(params);
String appId = params.get(AliPayCode.APP_ID);
@@ -67,7 +71,8 @@ public class AliPayCallbackService extends AbsPayCallbackStrategy {
log.error("支付宝回调报文 appId 为空 {}", callReq);
return false;
}
AlipayConfig alipayConfig = alipayConfigManager.findActivity().orElseThrow(DataNotExistException::new);
AlipayConfig alipayConfig = alipayConfigManager.findByMchAppCode(mchAppCode)
.orElseThrow(DataNotExistException::new);
if (alipayConfig == null) {
log.error("支付宝支付配置不存在: {}", callReq);
return false;

View File

@@ -1,15 +1,19 @@
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.KeyValue;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.daxpay.code.pay.PayChannelEnum;
import cn.bootx.platform.daxpay.code.paymodel.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.core.merchant.dao.MchAppPayConfigManager;
import cn.bootx.platform.daxpay.core.merchant.entity.MchAppPayConfig;
import cn.bootx.platform.daxpay.core.merchant.service.MchAppService;
import cn.bootx.platform.daxpay.dto.channel.alipay.AlipayConfigDto;
import cn.bootx.platform.daxpay.exception.payment.PayFailureException;
import cn.bootx.platform.daxpay.param.channel.alipay.AlipayConfigParam;
import cn.bootx.platform.daxpay.param.channel.alipay.AlipayConfigQuery;
import cn.hutool.core.bean.BeanUtil;
@@ -21,7 +25,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -37,42 +40,28 @@ public class AlipayConfigService {
private final AlipayConfigManager alipayConfigManager;
private final MchAppService mchAppService;
private final MchAppPayConfigManager mchAppPayConfigManager;
/**
* 添加支付宝配置
*/
@Transactional(rollbackFor = Exception.class)
public void add(AlipayConfigParam param) {
// 是否有管理关系判断
if (mchAppService.checkMatch(param.getMchCode(), param.getMchAppCode())) {
throw new BizException("应用信息与商户信息不匹配");
}
AlipayConfig alipayConfig = AlipayConfig.init(param);
alipayConfig.setActivity(false).setState(1);
alipayConfigManager.save(alipayConfig);
}
/**
* 设置启用的支付宝配置
*/
@Transactional(rollbackFor = Exception.class)
public void setUpActivity(Long id) {
AlipayConfig alipayConfig = alipayConfigManager.findById(id).orElseThrow(DataNotExistException::new);
if (Objects.equals(alipayConfig.getActivity(), Boolean.TRUE)) {
return;
}
alipayConfigManager.removeAllActivity();
alipayConfig.setActivity(true);
alipayConfigManager.updateById(alipayConfig);
}
/**
* 清除启用状态
*/
@Transactional(rollbackFor = Exception.class)
public void clearActivity(Long id) {
AlipayConfig alipayConfig = alipayConfigManager.findById(id)
.orElseThrow(() -> new PayFailureException("支付宝配置不存在"));
if (Objects.equals(alipayConfig.getActivity(), Boolean.FALSE)) {
return;
}
alipayConfig.setActivity(false);
alipayConfigManager.updateById(alipayConfig);
// 保存关联关系
MchAppPayConfig mchAppPayConfig = new MchAppPayConfig().setAppCode(alipayConfig.getMchAppCode())
.setConfigId(alipayConfig.getId())
.setChannel(PayChannelEnum.ALI.getCode())
.setState(alipayConfig.getState());
mchAppPayConfigManager.save(mchAppPayConfig);
}
/**

View File

@@ -11,7 +11,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.Objects;
import java.util.Optional;
/**
@@ -24,30 +23,10 @@ import java.util.Optional;
@RequiredArgsConstructor
public class WeChatPayConfigManager extends BaseManager<WeChatPayConfigMapper, WeChatPayConfig> {
private Optional<WeChatPayConfig> weChatPayConfig;
@Override
public WeChatPayConfig saveOrUpdate(WeChatPayConfig entity) {
this.clearCache();
return super.saveOrUpdate(entity);
public Optional<WeChatPayConfig> findByMchAppCode(String mchAppCode){
return findByField(WeChatPayConfig::getMchAppCode,mchAppCode);
}
@Override
public WeChatPayConfig updateById(WeChatPayConfig weChatPayConfig) {
this.clearCache();
return super.updateById(weChatPayConfig);
}
/**
* 获取启用的微信配置
*/
public Optional<WeChatPayConfig> findActivity() {
if (Objects.isNull(weChatPayConfig)) {
weChatPayConfig = findByField(WeChatPayConfig::getActivity, Boolean.TRUE);
}
return weChatPayConfig;
}
/**
* 分页
*/
@@ -55,25 +34,9 @@ public class WeChatPayConfigManager extends BaseManager<WeChatPayConfigMapper, W
Page<WeChatPayConfig> mpPage = MpUtil.getMpPage(pageParam, WeChatPayConfig.class);
return lambdaQuery().select(WeChatPayConfig.class, MpUtil::excludeBigField)
.like(StrUtil.isNotBlank(param.getName()), WeChatPayConfig::getName, param.getName())
.like(StrUtil.isNotBlank(param.getAppId()), WeChatPayConfig::getAppId, param.getAppId())
.like(StrUtil.isNotBlank(param.getAppId()), WeChatPayConfig::getMchId, param.getMchId())
.like(StrUtil.isNotBlank(param.getAppId()), WeChatPayConfig::getWxAppId, param.getAppId())
.like(StrUtil.isNotBlank(param.getAppId()), WeChatPayConfig::getWxMchId, param.getMchId())
.orderByDesc(MpIdEntity::getId)
.page(mpPage);
}
/**
* 清除所有的被启用的
*/
public void removeAllActivity() {
this.clearCache();
lambdaUpdate().eq(WeChatPayConfig::getActivity, Boolean.TRUE).set(WeChatPayConfig::getActivity, Boolean.FALSE);
}
/**
* 清除缓存
*/
public void clearCache() {
weChatPayConfig = null;
}
}

View File

@@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.core.channel.wechat.entity;
import cn.bootx.mybatis.table.modify.annotation.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbTable;
import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlIndex;
import cn.bootx.platform.common.core.annotation.BigField;
import cn.bootx.platform.common.core.annotation.EncryptionField;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
@@ -35,24 +36,28 @@ public class WeChatPayConfig extends MpBaseEntity implements EntityBaseFunction<
@DbColumn(comment = "名称")
private String name;
/** 微信商户号 */
@DbColumn(comment = "微信商户号")
private String mchId;
/** 商户编码 */
@TableField(updateStrategy = FieldStrategy.NEVER)
@DbColumn(comment = "商户编码")
private String mchCode;
/** 商户应用Id */
/** 商户应用编码 */
@TableField(updateStrategy = FieldStrategy.NEVER)
@DbMySqlIndex(comment = "商户应用编码唯一索引")
@DbColumn(comment = "商户应用编码")
private String mchAppCode;
/** 微信商户Id */
@DbColumn(comment = "微信商户号")
private String wxMchId;
/** 微信商户应用Id */
@DbColumn(comment = "商户应用Id")
private Long mchAppId;
private Long wxMchAppId;
/** 微信应用appId */
@DbColumn(comment = "微信应用appId")
private String appId;
// /**
// * api版本
// * @see WeChatPayCode#API_V2
// */
// @DbColumn(comment = "api版本")
// private String apiVersion;
private String wxAppId;
/** 商户平台「API安全」中的 APIv2 密钥 */
@TableField(updateStrategy = FieldStrategy.IGNORED)
@@ -116,13 +121,12 @@ public class WeChatPayConfig extends MpBaseEntity implements EntityBaseFunction<
@DbColumn(comment = "可用支付方式")
private String payWays;
/** 是否启用 */
@DbColumn(comment = "是否启用")
private Boolean activity;
/** 状态 */
/**
* 状态
* @see cn.bootx.platform.daxpay.code.MchAndAppCode#PAY_CONFIG_STATE_NORMAL
*/
@DbColumn(comment = "状态")
private Integer state;
private String state;
/** 备注 */
@DbColumn(comment = "备注")

View File

@@ -73,7 +73,7 @@ public class WeChatPayCallbackService extends AbsPayCallbackStrategy {
* 验证回调消息
*/
@Override
public boolean verifyNotify() {
public boolean verifyNotify(String mchAppCode) {
Map<String, String> params = PARAMS.get();
String callReq = JSONUtil.toJsonStr(params);
log.info("微信发起回调 报文: {}", callReq);
@@ -83,8 +83,8 @@ public class WeChatPayCallbackService extends AbsPayCallbackStrategy {
log.warn("微信回调报文 appId 为空 {}", callReq);
return false;
}
//
WeChatPayConfig weChatPayConfig = weChatPayConfigManager.findActivity().orElseThrow(DataNotExistException::new);
WeChatPayConfig weChatPayConfig = weChatPayConfigManager.findByMchAppCode(mchAppCode).orElseThrow(DataNotExistException::new);
if (weChatPayConfig == null) {
log.warn("微信支付配置不存在: {}", callReq);
return false;

View File

@@ -45,8 +45,8 @@ public class WeChatPayCancelService {
public void cancelRemote(Payment payment, WeChatPayConfig weChatPayConfig) {
// 只有部分需要调用微信网关进行关闭
Map<String, String> params = CloseOrderModel.builder()
.appid(weChatPayConfig.getAppId())
.mch_id(weChatPayConfig.getMchId())
.appid(weChatPayConfig.getWxAppId())
.mch_id(weChatPayConfig.getWxMchId())
.out_trade_no(String.valueOf(payment.getId()))
.nonce_str(WxPayKit.generateStr())
.build()
@@ -66,8 +66,8 @@ public class WeChatPayCancelService {
// 设置退款号
AsyncRefundLocal.set(IdUtil.getSnowflakeNextIdStr());
Map<String, String> params = RefundModel.builder()
.appid(weChatPayConfig.getAppId())
.mch_id(weChatPayConfig.getMchId())
.appid(weChatPayConfig.getWxAppId())
.mch_id(weChatPayConfig.getWxMchId())
.out_trade_no(String.valueOf(payment.getId()))
.out_refund_no(AsyncRefundLocal.get())
.total_fee(totalFee)
@@ -79,7 +79,7 @@ public class WeChatPayCancelService {
byte[] fileBytes = uploadService.getFileBytes(weChatPayConfig.getP12());
ByteArrayInputStream inputStream = new ByteArrayInputStream(fileBytes);
// 证书密码为 微信商户号
String xmlResult = WxPayApi.orderRefund(false, params, inputStream, weChatPayConfig.getMchId());
String xmlResult = WxPayApi.orderRefund(false, params, inputStream, weChatPayConfig.getWxMchId());
Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
this.verifyErrorMsg(result);
}

View File

@@ -1,13 +1,18 @@
package cn.bootx.platform.daxpay.core.channel.wechat.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.KeyValue;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.daxpay.code.pay.PayChannelEnum;
import cn.bootx.platform.daxpay.code.paymodel.WeChatPayWay;
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.merchant.dao.MchAppPayConfigManager;
import cn.bootx.platform.daxpay.core.merchant.entity.MchAppPayConfig;
import cn.bootx.platform.daxpay.core.merchant.service.MchAppService;
import cn.bootx.platform.daxpay.dto.channel.wechat.WeChatPayConfigDto;
import cn.bootx.platform.daxpay.exception.payment.PayFailureException;
import cn.bootx.platform.daxpay.param.channel.wechat.WeChatPayConfigParam;
@@ -20,7 +25,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -35,15 +39,27 @@ import java.util.stream.Collectors;
public class WeChatPayConfigService {
private final WeChatPayConfigManager weChatPayConfigManager;
private final MchAppService mchAppService;
private final MchAppPayConfigManager mchAppPayConfigManager;
/**
* 添加微信支付配置
*/
@Transactional(rollbackFor = Exception.class)
public void add(WeChatPayConfigParam param) {
// 是否有管理关系判断
if (mchAppService.checkMatch(param.getMchCode(), param.getMchAppCode())) {
throw new BizException("应用信息与商户信息不匹配");
}
WeChatPayConfig weChatPayConfig = WeChatPayConfig.init(param);
weChatPayConfig.setActivity(false);
weChatPayConfigManager.save(weChatPayConfig);
// 保存关联关系
MchAppPayConfig mchAppPayConfig = new MchAppPayConfig().setAppCode(weChatPayConfig.getMchAppCode())
.setConfigId(weChatPayConfig.getId())
.setChannel(PayChannelEnum.WECHAT.getCode())
.setState(weChatPayConfig.getState());
mchAppPayConfigManager.save(mchAppPayConfig);
}
/**
@@ -72,35 +88,6 @@ public class WeChatPayConfigService {
return MpUtil.convert2DtoPageResult(weChatPayConfigManager.page(pageParam, param));
}
/**
* 设置启用的支付宝配置
*/
@Transactional(rollbackFor = Exception.class)
public void setUpActivity(Long id) {
WeChatPayConfig weChatPayConfig = weChatPayConfigManager.findById(id)
.orElseThrow(() -> new PayFailureException("微信支付配置不存在"));
if (Objects.equals(weChatPayConfig.getActivity(), Boolean.TRUE)) {
return;
}
weChatPayConfigManager.removeAllActivity();
weChatPayConfig.setActivity(true);
weChatPayConfigManager.updateById(weChatPayConfig);
}
/**
* 清除启用状态
*/
@Transactional(rollbackFor = Exception.class)
public void clearActivity(Long id) {
WeChatPayConfig weChatPayConfig = weChatPayConfigManager.findById(id)
.orElseThrow(() -> new PayFailureException("微信支付配置不存在"));
if (Objects.equals(weChatPayConfig.getActivity(), Boolean.TRUE)) {
return;
}
weChatPayConfig.setActivity(false);
weChatPayConfigManager.updateById(weChatPayConfig);
}
/**
* 获取
*/

View File

@@ -169,8 +169,8 @@ public class WeChatPayService {
*/
private String barCode(String amount, Payment payment, String authCode, WeChatPayConfig weChatPayConfig) {
Map<String, String> params = MicroPayModel.builder()
.appid(weChatPayConfig.getAppId())
.mch_id(weChatPayConfig.getMchId())
.appid(weChatPayConfig.getWxAppId())
.mch_id(weChatPayConfig.getWxMchId())
.nonce_str(WxPayKit.generateStr())
.body(payment.getTitle())
.auth_code(authCode)
@@ -226,8 +226,8 @@ public class WeChatPayService {
// 过期时间
payment.setExpiredTime(PayWaylUtil.getPaymentExpiredTime(weChatPayConfig.getExpireTime()));
return UnifiedOrderModel.builder()
.appid(weChatPayConfig.getAppId())
.mch_id(weChatPayConfig.getMchId())
.appid(weChatPayConfig.getWxAppId())
.mch_id(weChatPayConfig.getWxMchId())
.nonce_str(WxPayKit.generateStr())
.time_start(LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN))
// 反正v2版本的超时时间无效

View File

@@ -32,8 +32,8 @@ public class WeChatPaySyncService {
public PaySyncResult syncPayStatus(Long paymentId, WeChatPayConfig weChatPayConfig) {
PaySyncResult paySyncResult = new PaySyncResult().setPaySyncStatus(PaySyncStatus.FAIL);
Map<String, String> params = UnifiedOrderModel.builder()
.appid(weChatPayConfig.getAppId())
.mch_id(weChatPayConfig.getMchId())
.appid(weChatPayConfig.getWxAppId())
.mch_id(weChatPayConfig.getWxMchId())
.nonce_str(WxPayKit.generateStr())
.out_trade_no(String.valueOf(paymentId))
.build()

View File

@@ -21,4 +21,4 @@ public interface MchApplicationConvert {
MchApplicationDto convert(MchApplication in);
}
}

View File

@@ -11,6 +11,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 商户应用
*
@@ -19,7 +21,14 @@ import org.springframework.stereotype.Repository;
*/
@Repository
@RequiredArgsConstructor
public class MchApplicationManager extends BaseManager<MchApplicationMapper, MchApplication> {
public class MchAppManager extends BaseManager<MchApplicationMapper, MchApplication> {
/**
* 根据编码查询
*/
public Optional<MchApplication> findByCode(String code) {
return findByField(MchApplication::getCode, code);
}
/**
* 分页
@@ -27,8 +36,7 @@ public class MchApplicationManager extends BaseManager<MchApplicationMapper, Mch
public Page<MchApplication> page(PageParam pageParam, MchApplicationParam param) {
Page<MchApplication> mpPage = MpUtil.getMpPage(pageParam, MchApplication.class);
QueryWrapper<MchApplication> wrapper = QueryGenerator.generator(param, this.getEntityClass());
wrapper.select(this.getEntityClass(), MpUtil::excludeBigField)
.orderByDesc(MpUtil.getColumnName(MchApplication::getId));
wrapper.select(this.getEntityClass(), MpUtil::excludeBigField).orderByDesc(MpUtil.getColumnName(MchApplication::getId));
return this.page(mpPage, wrapper);
}

View File

@@ -13,4 +13,4 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MchApplicationMapper extends BaseMapper<MchApplication> {
}
}

View File

@@ -13,6 +13,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -25,6 +26,13 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class MerchantInfoManager extends BaseManager<MerchantInfoMapper, MerchantInfo> {
/**
* 根据编码查询
*/
public Optional<MerchantInfo> findByCode(String code) {
return findByField(MerchantInfo::getCode, code);
}
/**
* 分页
*/
@@ -40,10 +48,10 @@ public class MerchantInfoManager extends BaseManager<MerchantInfoMapper, Merchan
* 下拉列表
*/
public List<KeyValue> findDropdown() {
return lambdaQuery().select(MerchantInfo::getMchNo, MerchantInfo::getMchName)
return lambdaQuery().select(MerchantInfo::getCode, MerchantInfo::getName)
.list()
.stream()
.map(mch -> new KeyValue(mch.getMchNo(), mch.getMchName()))
.map(mch -> new KeyValue(mch.getCode(), mch.getName()))
.collect(Collectors.toList());
}

View File

@@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.core.merchant.entity;
import cn.bootx.mybatis.table.modify.annotation.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbTable;
import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlIndex;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -21,9 +22,10 @@ import lombok.experimental.Accessors;
@TableName("pay_mch_app_config")
public class MchAppPayConfig extends MpBaseEntity {
/** 关联应用ID */
@DbColumn(comment = "关联应用ID")
private Long appId;
/** 关联应用编码 */
@DbMySqlIndex(comment = "关联应用编码索引")
@DbColumn(comment = "关联应用编码")
private String appCode;
/** 关联配置ID */
@DbColumn(comment = "关联配置ID")

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.core.merchant.entity;
import cn.bootx.mybatis.table.modify.annotation.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbTable;
import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.MySqlIndex;
import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlIndex;
import cn.bootx.mybatis.table.modify.mybatis.mysq.constants.MySqlIndexType;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
@@ -15,6 +15,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import lombok.experimental.FieldNameConstants;
/**
* 商户应用
@@ -24,16 +25,17 @@ import lombok.experimental.Accessors;
*/
@DbTable(comment = "商户应用")
@EqualsAndHashCode(callSuper = true)
@MySqlIndex(columns = "app_no", type = MySqlIndexType.UNIQUE, comment = "应用编码唯一索引")
@DbMySqlIndex(fields = MchApplication.Fields.code, type = MySqlIndexType.UNIQUE, comment = "应用编码唯一索引")
@Data
@FieldNameConstants
@Accessors(chain = true)
@TableName("pay_mch_app")
public class MchApplication extends MpBaseEntity implements EntityBaseFunction<MchApplicationDto> {
/** 应用编码 */
@DbColumn(comment = "应用编码")
@TableField(updateStrategy = FieldStrategy.IGNORED)
private String appNo;
@TableField(updateStrategy = FieldStrategy.NEVER)
private String code;
/** 名称 */
@DbColumn(comment = "名称")
@@ -41,11 +43,14 @@ public class MchApplication extends MpBaseEntity implements EntityBaseFunction<M
/** 商户号 */
@DbColumn(comment = "商户号")
@TableField(updateStrategy = FieldStrategy.IGNORED)
private String mchNo;
@TableField(updateStrategy = FieldStrategy.NEVER)
private String mchCode;
/** 状态类型 */
@DbColumn(comment = "状态类型")
/**
* 状态
* @see cn.bootx.platform.daxpay.code.MchAndAppCode
*/
@DbColumn(comment = "状态")
private String state;
/** 备注 */

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.core.merchant.entity;
import cn.bootx.mybatis.table.modify.annotation.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbTable;
import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.MySqlIndex;
import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlIndex;
import cn.bootx.mybatis.table.modify.mybatis.mysq.constants.MySqlIndexType;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
@@ -17,13 +17,15 @@ import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import lombok.experimental.FieldNameConstants;
import static cn.bootx.platform.daxpay.core.merchant.entity.MerchantInfo.Fields.code;
/**
* 商户
*
* @author xxm
* @date 2023-05-17
*/
@MySqlIndex(columns = "mch_no", type = MySqlIndexType.UNIQUE, comment = "商户号唯一索引")
@DbMySqlIndex(fields = code, type = MySqlIndexType.UNIQUE, comment = "商户号唯一索引")
@EqualsAndHashCode(callSuper = true)
@FieldNameConstants
@Data
@@ -35,15 +37,15 @@ public class MerchantInfo extends MpBaseEntity implements EntityBaseFunction<Mer
/** 商户号 */
@DbColumn(comment = "商户号")
@TableField(updateStrategy = FieldStrategy.IGNORED)
private String mchNo;
private String code;
/** 商户名称 */
@DbColumn(comment = "商户名称")
private String mchName;
private String name;
/** 商户简称 */
@DbColumn(comment = "商户简称")
private String mchShortName;
private String shortName;
/** 类型 */
@DbColumn(comment = "类型")
@@ -57,7 +59,10 @@ public class MerchantInfo extends MpBaseEntity implements EntityBaseFunction<Mer
@DbColumn(comment = "联系人手机号")
private String contactTel;
/** 状态类型 */
/**
* 状态类型
* @see cn.bootx.platform.daxpay.code.MchAndAppCode
*/
@DbColumn(comment = "状态类型")
private String state;

View File

@@ -34,7 +34,7 @@ public class MchAppPayConfigService {
* 根据应用ID删除
*/
public void deleteByAppId(Long appId) {
mchAppPayConfigManager.deleteByField(MchAppPayConfig::getAppId, appId);
mchAppPayConfigManager.deleteByField(MchAppPayConfig::getAppCode, appId);
}
/**
@@ -45,7 +45,7 @@ public class MchAppPayConfigService {
List<PayChannelConfig> channels = channelConfigManager.findAllByOrder();
// 查询当前应用所拥有的配置, 进行合并生成相关信息
val mchAppPayConfigMap = mchAppPayConfigManager.findAllByField(MchAppPayConfig::getAppId, appId)
val mchAppPayConfigMap = mchAppPayConfigManager.findAllByField(MchAppPayConfig::getAppCode, appId)
.stream()
.collect(Collectors.toMap(MchAppPayConfig::getChannel, Function.identity()));
// 进行排序并返回

View File

@@ -5,8 +5,10 @@ import cn.bootx.platform.common.core.rest.PageResult;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.common.core.util.ResultConvertUtil;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.daxpay.core.merchant.dao.MchApplicationManager;
import cn.bootx.platform.daxpay.core.merchant.dao.MchAppManager;
import cn.bootx.platform.daxpay.core.merchant.dao.MerchantInfoManager;
import cn.bootx.platform.daxpay.core.merchant.entity.MchApplication;
import cn.bootx.platform.daxpay.core.merchant.entity.MerchantInfo;
import cn.bootx.platform.daxpay.dto.merchant.MchApplicationDto;
import cn.bootx.platform.daxpay.param.merchant.MchApplicationParam;
import cn.hutool.core.bean.BeanUtil;
@@ -18,6 +20,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
/**
* 商户应用
@@ -28,9 +31,11 @@ import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class MchApplicationService {
public class MchAppService {
private final MchApplicationManager mchApplicationManager;
private final MchAppManager mchAppManager;
private final MerchantInfoManager mchManager;
private final MchAppPayConfigService appPayConfigService;
@@ -38,40 +43,39 @@ public class MchApplicationService {
* 添加
*/
public void add(MchApplicationParam param) {
MchApplication mchApplication = MchApplication.init(param);
mchApplication.setAppNo(IdUtil.getSnowflakeNextIdStr());
mchApplicationManager.save(mchApplication);
MchApplication mchApp = MchApplication.init(param);
mchApp.setCode(IdUtil.getSnowflakeNextIdStr());
mchAppManager.save(mchApp);
}
/**
* 修改
*/
public void update(MchApplicationParam param) {
MchApplication mchApplication = mchApplicationManager.findById(param.getId())
.orElseThrow(DataNotExistException::new);
BeanUtil.copyProperties(param, mchApplication, CopyOptions.create().ignoreNullValue());
mchApplicationManager.updateById(mchApplication);
MchApplication mchApp = mchAppManager.findById(param.getId()).orElseThrow(DataNotExistException::new);
BeanUtil.copyProperties(param, mchApp, CopyOptions.create().ignoreNullValue());
mchAppManager.updateById(mchApp);
}
/**
* 分页
*/
public PageResult<MchApplicationDto> page(PageParam pageParam, MchApplicationParam mchApplicationParam) {
return MpUtil.convert2DtoPageResult(mchApplicationManager.page(pageParam, mchApplicationParam));
return MpUtil.convert2DtoPageResult(mchAppManager.page(pageParam, mchApplicationParam));
}
/**
* 获取单条
*/
public MchApplicationDto findById(Long id) {
return mchApplicationManager.findById(id).map(MchApplication::toDto).orElseThrow(DataNotExistException::new);
return mchAppManager.findById(id).map(MchApplication::toDto).orElseThrow(DataNotExistException::new);
}
/**
* 获取全部
*/
public List<MchApplicationDto> findAll() {
return ResultConvertUtil.dtoListConvert(mchApplicationManager.findAll());
return ResultConvertUtil.dtoListConvert(mchAppManager.findAll());
}
/**
@@ -80,7 +84,21 @@ public class MchApplicationService {
@Transactional(rollbackFor = Exception.class)
public void delete(Long id) {
appPayConfigService.deleteByAppId(id);
mchApplicationManager.deleteById(id);
mchAppManager.deleteById(id);
}
/**
* 验证商户号和商户应用是否匹配
*/
public boolean checkMatch(String mchCode, String mchAppCode) {
MerchantInfo merchantInfo = mchManager.findByCode(mchCode).orElseThrow(DataNotExistException::new);
MchApplication mchApp = mchAppManager.findByCode(mchAppCode).orElseThrow(DataNotExistException::new);
// 商户与应用是否有关联关系
if (!Objects.equals(mchApp.getMchCode(), merchantInfo.getCode())) {
return false;
}
return true;
}
}

View File

@@ -36,7 +36,7 @@ public class MerchantInfoService {
*/
public void add(MerchantInfoParam param) {
MerchantInfo merchantInfo = MerchantInfo.init(param);
merchantInfo.setMchNo("M" + System.currentTimeMillis());
merchantInfo.setCode("M" + System.currentTimeMillis());
merchantInfoManager.save(merchantInfo);
}

View File

@@ -1,5 +1,9 @@
package cn.bootx.platform.daxpay.core.notify.entity;
import cn.bootx.mybatis.table.modify.annotation.DbComment;
import cn.bootx.mybatis.table.modify.annotation.DbTable;
import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlFieldType;
import cn.bootx.mybatis.table.modify.mybatis.mysq.constants.MySqlFieldTypeEnum;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import cn.bootx.platform.daxpay.code.pay.PayChannelEnum;
@@ -21,32 +25,44 @@ import java.time.LocalDateTime;
*/
@EqualsAndHashCode(callSuper = true)
@Data
@DbTable(comment = "回调记录")
@Accessors(chain = true)
@TableName("pay_pay_notify_record")
public class PayNotifyRecord extends MpBaseEntity implements EntityBaseFunction<PayNotifyRecordDto> {
/** 支付记录id */
@DbComment("支付记录id")
private Long paymentId;
/** 商户应用编码 */
@DbComment("商户应用编码")
private String mchAppCode;
/**
* 支付通道
* @see PayChannelEnum#getCode()
*/
@DbComment("支付通道")
private String payChannel;
/** 通知消息 */
@DbMySqlFieldType(MySqlFieldTypeEnum.LONGTEXT)
@DbComment("通知消息")
private String notifyInfo;
/**
* 处理状态
* @see PayStatusCode#NOTIFY_PROCESS_SUCCESS
*/
@DbComment("处理状态")
private String status;
/** 提示信息 */
@DbComment("提示信息")
private String msg;
/** 回调时间 */
@DbComment("回调时间")
private LocalDateTime notifyTime;
@Override

View File

@@ -41,6 +41,8 @@ public class PaymentBuilder {
String ip = ServletUtil.getClientIP(request);
// 基础信息
payment.setBusinessId(payParam.getBusinessId())
.setMchCode(payParam.getMchCode())
.setMchAppCode(payment.getMchAppCode())
.setTitle(payParam.getTitle())
.setDescription(payParam.getDescription());

View File

@@ -36,12 +36,12 @@ public abstract class AbsPayCallbackStrategy {
/**
* 支付回调
*/
public String payCallback(Map<String, String> params) {
public String payCallback(String appCode, Map<String, String> params) {
PARAMS.set(params);
try {
log.info("支付回调处理: {}", params);
// 验证消息
if (!this.verifyNotify()) {
if (!this.verifyNotify(appCode)) {
return null;
}
// 去重处理
@@ -49,9 +49,10 @@ public abstract class AbsPayCallbackStrategy {
return this.getReturnMsg();
}
// 调用统一回调处理
PayCallbackResult result = payCallbackService.callback(this.getPaymentId(), this.getTradeStatus(), params);
PayCallbackResult result = payCallbackService.callback(appCode, this.getPaymentId(), this.getTradeStatus(),
params);
// 记录回调记录
this.saveNotifyRecord(result);
this.saveNotifyRecord(appCode, result);
}
finally {
PARAMS.remove();
@@ -76,8 +77,9 @@ public abstract class AbsPayCallbackStrategy {
/**
* 验证信息格式
* @param mchAppCode 商户应用编码
*/
public abstract boolean verifyNotify();
public abstract boolean verifyNotify(String mchAppCode);
/**
* 获取paymentId
@@ -98,10 +100,11 @@ public abstract class AbsPayCallbackStrategy {
/**
* 保存回调记录
*/
public void saveNotifyRecord(PayCallbackResult result) {
public void saveNotifyRecord(String appCode, PayCallbackResult result) {
PayNotifyRecord payNotifyRecord = new PayNotifyRecord().setNotifyInfo(JSONUtil.toJsonStr(PARAMS.get()))
.setNotifyTime(LocalDateTime.now())
.setPaymentId(this.getPaymentId())
.setMchAppCode(appCode)
.setPayChannel(this.getPayChannel().getCode())
.setStatus(result.getCode())
.setMsg(result.getMsg());

View File

@@ -44,10 +44,11 @@ public class PayCallbackService {
/**
* 统一回调处理
* @see PayStatusCode
* @param appCode
* @param tradeStatus 支付状态
* @see PayStatusCode
*/
public PayCallbackResult callback(Long paymentId, String tradeStatus, Map<String, String> map) {
public PayCallbackResult callback(String appCode, Long paymentId, String tradeStatus, Map<String, String> map) {
// 获取payment和paymentParam数据
Payment payment = paymentService.findById(paymentId).orElse(null);

View File

@@ -1,8 +1,15 @@
package cn.bootx.platform.daxpay.core.pay.service;
import cn.bootx.platform.common.core.exception.BizException;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.common.core.util.ValidationUtil;
import cn.bootx.platform.daxpay.code.MchAndAppCode;
import cn.bootx.platform.daxpay.code.pay.PayChannelEnum;
import cn.bootx.platform.daxpay.core.merchant.dao.MchAppManager;
import cn.bootx.platform.daxpay.core.merchant.dao.MerchantInfoManager;
import cn.bootx.platform.daxpay.core.merchant.entity.MchApplication;
import cn.bootx.platform.daxpay.core.merchant.entity.MerchantInfo;
import cn.bootx.platform.daxpay.core.pay.builder.PayEventBuilder;
import cn.bootx.platform.daxpay.core.pay.builder.PaymentBuilder;
import cn.bootx.platform.daxpay.core.pay.factory.PayStrategyFactory;
@@ -15,8 +22,8 @@ import cn.bootx.platform.daxpay.exception.payment.PayFailureException;
import cn.bootx.platform.daxpay.exception.payment.PayNotExistedException;
import cn.bootx.platform.daxpay.exception.payment.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.mq.PaymentEventSender;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.util.PayWaylUtil;
import cn.hutool.core.collection.CollectionUtil;
import lombok.RequiredArgsConstructor;
@@ -45,6 +52,10 @@ public class PayService {
private final PaymentEventSender eventSender;
private final MchAppManager mchAppManager;
private final MerchantInfoManager mchManager;
/**
* 支付方法(同步/异步/组合支付) 同步支付:都只会在第一次执行中就完成支付,例如钱包、积分都是调用完就进行了扣减,完成了支付记录
* 异步支付:例如支付宝、微信,发起支付后还需要跳转第三方平台进行支付,支付后通常需要进行回调,之后才完成支付记录
@@ -58,7 +69,8 @@ public class PayService {
ValidationUtil.validateParam(payParam);
// 异步支付方式检查
PayWaylUtil.validationAsyncPayMode(payParam);
// 商户和应用信息检测
this.checkMchAndApp(payParam);
// 获取并校验支付状态
Payment payment = this.getAndCheckPaymentByBusinessId(payParam.getBusinessId());
@@ -222,6 +234,30 @@ public class PayService {
return paymentService.save(payment);
}
/**
* 商户和应用信息检测
*/
private void checkMchAndApp(PayParam payParam) {
MerchantInfo merchantInfo = mchManager.findByCode(payParam.getMchCode())
.orElseThrow(DataNotExistException::new);
MchApplication mchApp = mchAppManager.findByCode(payParam.getMchAppCode()).orElseThrow(DataNotExistException::new);
// 商户与应用是否有关联关系
if (!Objects.equals(mchApp.getMchCode(), merchantInfo.getCode())) {
throw new BizException("商户应用编码与商户不匹配");
}
// 商户是否可用状态
if (!Objects.equals(MchAndAppCode.MCH_STATE_NORMAL, merchantInfo.getState())) {
throw new BizException("商户状态不可用");
}
// 应用是否可用状态
if (!Objects.equals(MchAndAppCode.MCH_APP_STATE_NORMAL, mchApp.getState())) {
throw new BizException("商户应用状态不可用");
}
}
/**
* 校验支付状态,支付成功则返回,支付失败则抛出对应的异常
*/
@@ -229,17 +265,17 @@ public class PayService {
// 根据订单查询支付记录
Payment payment = paymentService.findByBusinessId(businessId).orElse(null);
if (Objects.nonNull(payment)) {
// 支付失败
List<String> trades = Arrays.asList(TRADE_FAIL, TRADE_CANCEL);
if (trades.contains(payment.getPayStatus())) {
// 支付失败类型状态
List<String> tradesStatus = Arrays.asList(TRADE_FAIL, TRADE_CANCEL);
if (tradesStatus.contains(payment.getPayStatus())) {
throw new PayFailureException("支付失败或已经被撤销");
}
// 退款状态
trades = Arrays.asList(TRADE_REFUNDING, TRADE_REFUNDED);
if (trades.contains(payment.getPayStatus())) {
// 退款类型状态
tradesStatus = Arrays.asList(TRADE_REFUNDING, TRADE_REFUNDED);
if (tradesStatus.contains(payment.getPayStatus())) {
throw new PayFailureException("支付失败或已经被撤销");
}
// 支付超时
// 支付超时状态
if (Objects.nonNull(payment.getExpiredTime())
&& LocalDateTimeUtil.ge(LocalDateTime.now(), payment.getExpiredTime())) {
throw new PayFailureException("支付已超时");

View File

@@ -91,13 +91,13 @@ public class AliPayStrategy extends AbsPayStrategy {
throw new PayAmountAbnormalException();
}
// 检查并获取支付宝支付配置
this.initAlipayConfig();
this.initAlipayConfig(this.getPayParam().getMchAppCode());
aliPayService.validation(this.getPayWayParam(), alipayConfig);
// 如果没有显式传入同步回调地址, 使用默认配置
if (StrUtil.isBlank(aliPayParam.getReturnUrl())) {
aliPayParam.setReturnUrl(alipayConfig.getReturnUrl());
}
this.initAlipayConfig();
this.initAlipayConfig(this.getPayParam().getMchAppCode());
}
/**
@@ -148,7 +148,7 @@ public class AliPayStrategy extends AbsPayStrategy {
*/
@Override
public void doCancelHandler() {
this.initAlipayConfig();
this.initAlipayConfig(this.getPayParam().getMchAppCode());
// 撤销支付
aliPayCancelService.cancelRemote(this.getPayment());
// 调用关闭本地支付记录
@@ -168,7 +168,7 @@ public class AliPayStrategy extends AbsPayStrategy {
*/
@Override
public void doRefundHandler() {
this.initAlipayConfig();
this.initAlipayConfig(this.getPayParam().getMchAppCode());
aliPayCancelService.refund(this.getPayment(), this.getPayWayParam().getAmount());
aliPaymentService.updatePayRefund(this.getPayment().getId(), this.getPayWayParam().getAmount());
paymentService.updateRefundSuccess(this.getPayment(), this.getPayWayParam().getAmount(), PayChannelEnum.ALI);
@@ -179,16 +179,17 @@ public class AliPayStrategy extends AbsPayStrategy {
*/
@Override
public PaySyncResult doSyncPayStatusHandler() {
this.initAlipayConfig();
this.initAlipayConfig(this.getPayParam().getMchAppCode());
return alipaySyncService.syncPayStatus(this.getPayment());
}
/**
* 初始化支付宝配置信息
*/
private void initAlipayConfig() {
private void initAlipayConfig(String mchAppCode) {
// 检查并获取支付宝支付配置
this.alipayConfig = alipayConfigManager.findActivity().orElseThrow(() -> new PayFailureException("支付配置不存在"));
this.alipayConfig = alipayConfigManager.findByMchAppCode(mchAppCode)
.orElseThrow(() -> new PayFailureException("支付配置不存在"));
this.initApiConfig(this.alipayConfig);
}

View File

@@ -95,7 +95,7 @@ public class WeChatPayStrategy extends AbsPayStrategy {
}
// 检查并获取微信支付配置
this.initWeChatPayConfig();
this.initWeChatPayConfig(this.getPayParam().getMchAppCode());
weChatPayService.validation(this.getPayWayParam(), weChatPayConfig);
}
@@ -148,7 +148,7 @@ public class WeChatPayStrategy extends AbsPayStrategy {
@Override
public void doCancelHandler() {
// 检查并获取微信支付配置
this.initWeChatPayConfig();
this.initWeChatPayConfig(this.getPayParam().getMchAppCode());
weChatPayCancelService.cancelRemote(this.getPayment(), weChatPayConfig);
// 调用关闭本地支付记录
this.doCloseHandler();
@@ -167,7 +167,7 @@ public class WeChatPayStrategy extends AbsPayStrategy {
*/
@Override
public void doRefundHandler() {
this.initWeChatPayConfig();
this.initWeChatPayConfig(this.getPayParam().getMchAppCode());
WeChatPayment weChatPayment = weChatPaymentManager.findByPaymentId(this.getPayment().getId())
.orElseThrow(() -> new PayFailureException("微信支付记录不存在"));
weChatPayCancelService.refund(this.getPayment(), weChatPayment, this.getPayWayParam().getAmount(),
@@ -182,17 +182,17 @@ public class WeChatPayStrategy extends AbsPayStrategy {
@Override
public PaySyncResult doSyncPayStatusHandler() {
// 检查并获取微信支付配置
this.initWeChatPayConfig();
this.initWeChatPayConfig(this.getPayParam().getMchAppCode());
return weChatPaySyncService.syncPayStatus(this.getPayment().getId(), this.weChatPayConfig);
}
/**
* 初始化微信支付
*/
private void initWeChatPayConfig() {
private void initWeChatPayConfig(String appCode) {
// 检查并获取微信支付配置
this.weChatPayConfig = Optional.ofNullable(this.weChatPayConfig)
.orElse(weChatPayConfigManager.findActivity().orElseThrow(() -> new PayFailureException("支付配置不存在")));
.orElse(weChatPayConfigManager.findByMchAppCode(appCode).orElseThrow(() -> new PayFailureException("支付配置不存在")));
}
}

View File

@@ -1,5 +1,7 @@
package cn.bootx.platform.daxpay.core.payment.entity;
import cn.bootx.mybatis.table.modify.annotation.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbTable;
import cn.bootx.platform.common.core.annotation.BigField;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
@@ -26,6 +28,7 @@ import java.util.List;
* @author xxm
* @date 2020/12/8
*/
@DbTable(isAppend = true)
@EqualsAndHashCode(callSuper = true)
@Data
@FieldNameConstants
@@ -36,6 +39,14 @@ public class Payment extends MpBaseEntity implements EntityBaseFunction<PaymentD
/** 关联的业务id */
private String businessId;
/** 商户编码 */
@DbColumn(comment = "商户编码")
private String mchCode;
/** 商户应用编码 */
@DbColumn(comment = "商户应用编码")
private String mchAppCode;
/** 标题 */
private String title;

View File

@@ -25,6 +25,12 @@ public class AlipayConfigDto extends BaseDto implements Serializable {
@Schema(description = "名称")
private String name;
@Schema(description = "商户编码")
private String mchCode;
@Schema(description = "商户应用编码")
private String mchAppCode;
@Schema(description = "支付宝商户appId")
@SensitiveInfo
private String appId;
@@ -77,7 +83,7 @@ public class AlipayConfigDto extends BaseDto implements Serializable {
private Boolean activity;
@Schema(description = "状态")
private Integer state;
private String state;
@Schema(description = "备注")
private String remark;

View File

@@ -1,7 +1,6 @@
package cn.bootx.platform.daxpay.dto.channel.wechat;
import cn.bootx.platform.common.core.rest.dto.BaseDto;
import cn.bootx.platform.daxpay.code.paymodel.WeChatPayCode;
import cn.bootx.platform.starter.data.perm.sensitive.SensitiveInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -32,12 +31,6 @@ public class WeChatPayConfigDto extends BaseDto implements Serializable {
@SensitiveInfo
private String appId;
/**
* @see WeChatPayCode#API_V2
*/
// @Schema(description = "api版本")
// private String apiVersion;
@Schema(description = "商户平台「API安全」中的 APIv2 密钥")
@SensitiveInfo
private String apiKeyV2;

View File

@@ -19,7 +19,7 @@ import lombok.experimental.Accessors;
public class MchApplicationDto extends BaseDto {
@Schema(description = "应用编码")
private String appNo;
private String code;
@Schema(description = "名称")
private String name;

View File

@@ -19,13 +19,13 @@ import lombok.experimental.Accessors;
public class MerchantInfoDto extends BaseDto {
@Schema(description = "商户号")
private String mchNo;
private String code;
@Schema(description = "商户名称")
private String mchName;
private String name;
@Schema(description = "商户简称")
private String mchShortName;
private String shortName;
@Schema(description = "类型")
private String type;

View File

@@ -24,12 +24,15 @@ public class PaymentDto extends BaseDto implements Serializable {
private static final long serialVersionUID = 3269223993950227228L;
@Schema(description = "用户ID")
private Long userId;
@Schema(description = "关联的业务id")
private String businessId;
@Schema(description = "商户编码")
private String mchNo;
@Schema(description = "商户应用编码")
private String mchAppNo;
@Schema(description = "标题")
private String title;
@@ -40,10 +43,10 @@ public class PaymentDto extends BaseDto implements Serializable {
private boolean asyncPayMode;
/**
* @see PayChannelCode
* @see cn.bootx.platform.daxpay.code.pay.PayChannelEnum#ASYNC_TYPE_CODE
*/
@Schema(description = "异步支付通道")
private Integer asyncPayChannel;
private String asyncPayChannel;
/**
* @see PayStatusCode

View File

@@ -19,6 +19,9 @@ import java.util.List;
@Schema(title = "结算台组合支付参数")
public class CashierCombinationPayParam {
@Schema(description = "商户应用编码")
private String mchAppCode;
@Schema(description = "标题")
private String title;

View File

@@ -17,6 +17,9 @@ import java.math.BigDecimal;
@Schema(title = "结算台单支付参数")
public class CashierSinglePayParam {
@Schema(description = "商户应用编码")
private String mchAppCode;
@Schema(description = "标题")
private String title;

View File

@@ -22,6 +22,12 @@ public class AlipayConfigParam implements Serializable {
@Schema(description = "名称")
private String name;
@Schema(description = "商户编码")
private String mchCode;
@Schema(description = "商户应用编码")
private String mchAppCode;
@Schema(description = "支付宝商户appId")
private String appId;
@@ -65,7 +71,7 @@ public class AlipayConfigParam implements Serializable {
private boolean sandbox;
@Schema(description = "状态")
private Integer state;
private String state;
@Schema(description = "备注")
private String remark;

View File

@@ -24,6 +24,12 @@ public class WeChatPayConfigParam {
@Schema(description = "名称")
private String name;
@Schema(description = "商户编码")
private String mchCode;
@Schema(description = "商户应用编码")
private String mchAppCode;
@Schema(description = "微信商户号")
private String mchId;

View File

@@ -23,15 +23,15 @@ public class MerchantInfoParam {
@QueryParam(type = LIKE)
@Schema(description = "商户号")
private String mchNo;
private String code;
@QueryParam(type = LIKE)
@Schema(description = "商户名称")
private String mchName;
private String name;
@QueryParam(type = LIKE)
@Schema(description = "商户简称")
private String mchShortName;
private String shortName;
@Schema(description = "类型")
private String type;

View File

@@ -23,6 +23,10 @@ public class PayParam implements Serializable {
private static final long serialVersionUID = 3895679513150533566L;
@Schema(description = "商户编码")
@NotEmpty(message = "商户应用不可为空")
private String mchCode;
@Schema(description = "商户应用编码")
@NotEmpty(message = "商户应用编码不可为空")
private String mchAppCode;

View File

@@ -58,7 +58,9 @@ public class PayWaylUtil {
* 判断是否有异步支付
*/
public boolean isNotSync(List<PayWayParam> payWayParams) {
return payWayParams.stream().map(PayWayParam::getPayChannel).noneMatch(PayChannelEnum.ASYNC_TYPE_CODE::contains);
return payWayParams.stream()
.map(PayWayParam::getPayChannel)
.noneMatch(PayChannelEnum.ASYNC_TYPE_CODE::contains);
}
/**
@@ -107,7 +109,7 @@ public class PayWaylUtil {
*/
public void validationAmount(List<PayWayParam> payModeList) {
for (PayWayParam payWayParam : payModeList) {
// 同时满足支付金额小于等于零
// 支付金额小于等于零
if (BigDecimalUtil.compareTo(payWayParam.getAmount(), BigDecimal.ZERO) < 1) {
throw new PayAmountAbnormalException();
}

View File

@@ -71,7 +71,7 @@
<ding-talk.version>1.3.81</ding-talk.version>
<lock4j.version>2.2.4</lock4j.version>
<ip2region.version>2.7.0</ip2region.version>
<mybatis-table-modify.version>1.5.3.alpha1</mybatis-table-modify.version>
<mybatis-table-modify.version>1.5.3</mybatis-table-modify.version>
</properties>
<!-- 项目依赖版本管理 -->