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> <dependency>
<groupId>cn.bootx</groupId> <groupId>cn.bootx</groupId>
<artifactId>mybatis-table-modify-mysql-boot-starter</artifactId> <artifactId>mybatis-table-modify-mysql-boot-starter</artifactId>
<version>1.5.3.alpha1</version> <version>${mybatis-table-modify.version}</version>
</dependency> </dependency>
<!-- 监控 --> <!-- 监控 -->

View File

@@ -1,6 +1,7 @@
package cn.bootx.platform.daxpay; package cn.bootx.platform.daxpay;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import io.minio.credentials.MinioClientConfigProvider;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan; 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(); 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 = "分页") @Operation(summary = "分页")
@GetMapping("/page") @GetMapping("/page")
public ResResult<PageResult<AlipayConfigDto>> page(PageParam pageParam, AlipayConfigQuery param) { public ResResult<PageResult<AlipayConfigDto>> page(PageParam pageParam, AlipayConfigQuery param) {

View File

@@ -44,10 +44,10 @@ public class CashierController {
} }
@Operation(summary = "扫码聚合支付(单渠道)") @Operation(summary = "扫码聚合支付(单渠道)")
@GetMapping("/aggregatePay") @GetMapping("/aggregatePay/{mchAppCode}")
public ModelAndView aggregatePay(String key, @RequestHeader(USER_AGENT) String ua) { public ModelAndView aggregatePay(String key,@PathVariable String mchAppCode, @RequestHeader(USER_AGENT) String ua) {
try { try {
String url = cashierService.aggregatePay(key, ua); String url = cashierService.aggregatePay(key, mchAppCode, ua);
return new ModelAndView("redirect:" + url); return new ModelAndView("redirect:" + url);
} }
catch (PayUnsupportedMethodException e) { catch (PayUnsupportedMethodException e) {
@@ -56,9 +56,9 @@ public class CashierController {
} }
@Operation(summary = "微信jsapi支付(回调)") @Operation(summary = "微信jsapi支付(回调)")
@GetMapping("/wxJsapiPay") @GetMapping("/wxJsapiPay/{mchAppCode}")
public ModelAndView wxJsapiPay(String code, String state) { public ModelAndView wxJsapiPay(String code, @PathVariable String mchAppCode, String state) {
Map<String, String> map = cashierService.wxJsapiPay(code, state); Map<String, String> map = cashierService.wxJsapiPay(code,mchAppCode,state);
// 跳转页面, 调起微信jsapi支付 // 跳转页面, 调起微信jsapi支付
return new ModelAndView("wechatJsapiPay").addAllObjects(map); 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.ResResult;
import cn.bootx.platform.common.core.rest.param.PageParam; 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.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.MchAppPayConfigResult;
import cn.bootx.platform.daxpay.dto.merchant.MchApplicationDto; import cn.bootx.platform.daxpay.dto.merchant.MchApplicationDto;
import cn.bootx.platform.daxpay.param.merchant.MchApplicationParam; import cn.bootx.platform.daxpay.param.merchant.MchApplicationParam;
@@ -28,7 +28,7 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public class MchApplicationController { public class MchApplicationController {
private final MchApplicationService applicationService; private final MchAppService applicationService;
private final MchAppPayConfigService appPayConfigService; private final MchAppPayConfigService appPayConfigService;

View File

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

View File

@@ -41,20 +41,6 @@ public class WeChatPayConfigController {
return Res.ok(); 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 = "分页") @Operation(summary = "分页")
@GetMapping("/page") @GetMapping("/page")
public ResResult<PageResult<WeChatPayConfigDto>> page(PageParam pageParam, WeChatPayConfigParam param) { 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()) PayWayParam payWayParam = new PayWayParam().setPayChannel(param.getPayChannel())
.setPayWay(param.getPayWay()) .setPayWay(param.getPayWay())
.setAmount(param.getAmount()); .setAmount(param.getAmount());
// 处理附加参数 // 处理附加参数
HashMap<String, String> map = new HashMap<>(1); HashMap<String, String> map = new HashMap<>(1);
@@ -80,8 +80,8 @@ public class CashierService {
payWayParam.setExtraParamsJson(extraParamsJson); payWayParam.setExtraParamsJson(extraParamsJson);
PayParam payParam = new PayParam().setTitle(param.getTitle()) PayParam payParam = new PayParam().setTitle(param.getTitle())
.setBusinessId(param.getBusinessId()) .setBusinessId(param.getBusinessId())
.setPayWayList(Collections.singletonList(payWayParam)); .setPayWayList(Collections.singletonList(payWayParam));
PayResult payResult = payService.pay(payParam); PayResult payResult = payService.pay(payParam);
if (Objects.equals(PayStatusCode.TRADE_REFUNDED, payResult.getPayStatus())) { 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() CashierSinglePayParam cashierSinglePayParam = new CashierSinglePayParam()
.setPayWay(PayWayEnum.QRCODE.getCode()); .setMchAppCode(mchAppCode)
.setPayWay(PayWayEnum.QRCODE.getCode());
// 判断是哪种支付方式 // 判断是哪种支付方式
if (ua.contains(PayChannelEnum.UA_ALI_PAY)) { if (ua.contains(PayChannelEnum.UA_ALI_PAY)) {
cashierSinglePayParam.setPayChannel(PayChannelEnum.ALI.getCode()); cashierSinglePayParam.setPayChannel(PayChannelEnum.ALI.getCode());
} }
else if (ua.contains(PayChannelEnum.UA_WECHAT_PAY)) { else if (ua.contains(PayChannelEnum.UA_WECHAT_PAY)) {
// 跳转微信授权页面, 调用jsapi进行支付 // 跳转微信授权页面, 调用jsapi进行支付
return this.wxJsapiAuth(key); return this.wxJsapiAuth(key,mchAppCode);
} }
else { else {
throw new PayUnsupportedMethodException(); throw new PayUnsupportedMethodException();
@@ -110,8 +111,8 @@ public class CashierService {
AggregatePayInfo aggregatePayInfo = aggregateService.getAggregateInfo(key); AggregatePayInfo aggregatePayInfo = aggregateService.getAggregateInfo(key);
cashierSinglePayParam.setTitle(aggregatePayInfo.getTitle()) cashierSinglePayParam.setTitle(aggregatePayInfo.getTitle())
.setAmount(aggregatePayInfo.getAmount()) .setAmount(aggregatePayInfo.getAmount())
.setBusinessId(aggregatePayInfo.getBusinessId()); .setBusinessId(aggregatePayInfo.getBusinessId());
PayResult payResult = this.singlePay(cashierSinglePayParam); PayResult payResult = this.singlePay(cashierSinglePayParam);
return payResult.getAsyncPayInfo().getPayBody(); return payResult.getAsyncPayInfo().getPayBody();
} }
@@ -119,13 +120,13 @@ public class CashierService {
/** /**
* 微信jsapi支付 - 跳转到授权页面 * 微信jsapi支付 - 跳转到授权页面
*/ */
private String wxJsapiAuth(String key) { private String wxJsapiAuth(String key, String mchAppCode) {
WeChatPayConfig config = weChatPayConfigManager.findActivity() WeChatPayConfig config = weChatPayConfigManager.findByMchAppCode(mchAppCode)
.orElseThrow(() -> new PayFailureException("未找到启用的微信支付配置")); .orElseThrow(() -> new PayFailureException("未找到启用的微信支付配置"));
WxMpService wxMpService = getWxMpService(config.getAppId(), config.getAppSecret()); WxMpService wxMpService = getWxMpService(config.getWxAppId(), config.getAppSecret());
// 回调地址为 结算台微信jsapi支付的回调地址 // 回调地址为 结算台微信jsapi支付的回调地址
SystemParameter systemParameter = systemParamManager.findByParamKey(WeChatPayCode.JSAPI_REDIRECT_URL) SystemParameter systemParameter = systemParamManager.findByParamKey(WeChatPayCode.JSAPI_REDIRECT_URL)
.orElseThrow(() -> new PayFailureException("微信支付回调地址参数不存在")); .orElseThrow(() -> new PayFailureException("微信支付回调地址参数不存在"));
String url = systemParameter.getValue() + "cashier/wxJsapiPay"; String url = systemParameter.getValue() + "cashier/wxJsapiPay";
return wxMpService.getOAuth2Service().buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_BASE, key); return wxMpService.getOAuth2Service().buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_BASE, key);
} }
@@ -133,28 +134,29 @@ public class CashierService {
/** /**
* 微信jsapi支付 - 回调发起预支付, 同时调起微信页面jsapi支付 * 微信jsapi支付 - 回调发起预支付, 同时调起微信页面jsapi支付
* @param code 微信授权码, 用来获取id * @param code 微信授权码, 用来获取id
* @param mchAppCode 商户应用编码
* @param state 聚合支付参数记录的key * @param state 聚合支付参数记录的key
* @return 页面中调起jsapi支付的参数 * @return 页面中调起jsapi支付的参数
*/ */
@SneakyThrows @SneakyThrows
public Map<String, String> wxJsapiPay(String code, String state) { public Map<String, String> wxJsapiPay(String code, String mchAppCode, String state) {
WeChatPayConfig config = weChatPayConfigManager.findActivity() WeChatPayConfig config = weChatPayConfigManager.findByMchAppCode(mchAppCode)
.orElseThrow(() -> new PayFailureException("未找到启用的微信支付配置")); .orElseThrow(() -> new PayFailureException("未找到启用的微信支付配置"));
WxMpService wxMpService = this.getWxMpService(config.getAppId(), config.getAppSecret()); WxMpService wxMpService = this.getWxMpService(config.getWxAppId(), config.getAppSecret());
WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code); WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
String openId = accessToken.getOpenId(); String openId = accessToken.getOpenId();
AggregatePayInfo aggregatePayInfo = aggregateService.getAggregateInfo(state); AggregatePayInfo aggregatePayInfo = aggregateService.getAggregateInfo(state);
// 构造微信API支付参数 // 构造微信API支付参数
CashierSinglePayParam cashierSinglePayParam = new CashierSinglePayParam() CashierSinglePayParam cashierSinglePayParam = new CashierSinglePayParam()
.setPayChannel(PayChannelEnum.WECHAT.getCode()) .setPayChannel(PayChannelEnum.WECHAT.getCode())
.setPayWay(PayWayEnum.JSAPI.getCode()) .setPayWay(PayWayEnum.JSAPI.getCode())
.setTitle(aggregatePayInfo.getTitle()) .setTitle(aggregatePayInfo.getTitle())
.setAmount(aggregatePayInfo.getAmount()) .setAmount(aggregatePayInfo.getAmount())
.setOpenId(openId) .setOpenId(openId)
.setBusinessId(aggregatePayInfo.getBusinessId()); .setBusinessId(aggregatePayInfo.getBusinessId());
PayResult payResult = this.singlePay(cashierSinglePayParam); 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); config.getApiKeyV2(), SignType.HMACSHA256);
} }
@@ -183,8 +185,8 @@ public class CashierService {
} }
// 发起支付 // 发起支付
PayParam payParam = new PayParam().setTitle(param.getTitle()) PayParam payParam = new PayParam().setTitle(param.getTitle())
.setBusinessId(param.getBusinessId()) .setBusinessId(param.getBusinessId())
.setPayWayList(param.getPayWayList()); .setPayWayList(param.getPayWayList());
PayResult payResult = payService.pay(payParam); PayResult payResult = payService.pay(payParam);
if (Objects.equals(PayStatusCode.TRADE_REFUNDED, payResult.getPayStatus())) { 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 lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
/** /**
@@ -23,28 +22,11 @@ import java.util.Optional;
@RequiredArgsConstructor @RequiredArgsConstructor
public class AlipayConfigManager extends BaseManager<AlipayConfigMapper, AlipayConfig> { 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() { public Optional<AlipayConfig> findByMchAppCode(String mchAppCOde) {
if (Objects.isNull(alipayConfig)) { return findByField(AlipayConfig::getMchAppCode, mchAppCOde);
alipayConfig = findByField(AlipayConfig::getActivity, Boolean.TRUE);
}
return alipayConfig;
} }
/** /**
@@ -58,21 +40,4 @@ public class AlipayConfigManager extends BaseManager<AlipayConfigMapper, AlipayC
.page(mpPage); .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.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbTable; 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.BigField;
import cn.bootx.platform.common.core.annotation.EncryptionField; import cn.bootx.platform.common.core.annotation.EncryptionField;
import cn.bootx.platform.common.core.function.EntityBaseFunction; import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity; 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.core.channel.alipay.convert.AlipayConvert;
import cn.bootx.platform.daxpay.dto.channel.alipay.AlipayConfigDto; 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.AlipayConfigParam;
@@ -35,13 +37,16 @@ public class AlipayConfig extends MpBaseEntity implements EntityBaseFunction<Ali
@DbColumn(comment = "名称") @DbColumn(comment = "名称")
private String name; private String name;
/** 商户Id */ /** 商户编码 */
@DbColumn(comment = "商户Id") @TableField(updateStrategy = FieldStrategy.NEVER)
private Long merchantId; @DbColumn(comment = "商户编码")
private String mchCode;
/** 商户应用Id */ /** 商户应用编码 */
@DbColumn(comment = "商户应用Id") @TableField(updateStrategy = FieldStrategy.NEVER)
private Long mchAppId; @DbMySqlIndex(comment = "商户应用编码唯一索引")
@DbColumn(comment = "商户应用编码")
private String mchAppCode;
/** 支付宝商户appId */ /** 支付宝商户appId */
@DbColumn(comment = "支付宝商户appId") @DbColumn(comment = "支付宝商户appId")
@@ -61,7 +66,10 @@ public class AlipayConfig extends MpBaseEntity implements EntityBaseFunction<Ali
@DbColumn(comment = "") @DbColumn(comment = "")
private String serverUrl; private String serverUrl;
/** 认证类型 证书/公钥 */ /**
* 认证类型 证书/公钥
* @see AliPayCode#AUTH_TYPE_KEY
*/
@DbColumn(comment = "认证类型") @DbColumn(comment = "认证类型")
private Integer authType; private Integer authType;
@@ -111,13 +119,9 @@ public class AlipayConfig extends MpBaseEntity implements EntityBaseFunction<Ali
@DbColumn(comment = "可用支付方式") @DbColumn(comment = "可用支付方式")
private String payWays; private String payWays;
/** 是否启用 */ /** 状态 */
@DbColumn(comment = "是否启用") @DbColumn(comment = "状态")
private Boolean activity; private String state;
/** 状态 暂时没什么用 */
@DbColumn(comment = "状态 暂时没什么用")
private Integer state;
/** 备注 */ /** 备注 */
@DbColumn(comment = "备注") @DbColumn(comment = "备注")

View File

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

View File

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

View File

@@ -11,7 +11,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
/** /**
@@ -24,30 +23,10 @@ import java.util.Optional;
@RequiredArgsConstructor @RequiredArgsConstructor
public class WeChatPayConfigManager extends BaseManager<WeChatPayConfigMapper, WeChatPayConfig> { public class WeChatPayConfigManager extends BaseManager<WeChatPayConfigMapper, WeChatPayConfig> {
private Optional<WeChatPayConfig> weChatPayConfig;
@Override public Optional<WeChatPayConfig> findByMchAppCode(String mchAppCode){
public WeChatPayConfig saveOrUpdate(WeChatPayConfig entity) { return findByField(WeChatPayConfig::getMchAppCode,mchAppCode);
this.clearCache();
return super.saveOrUpdate(entity);
} }
@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); Page<WeChatPayConfig> mpPage = MpUtil.getMpPage(pageParam, WeChatPayConfig.class);
return lambdaQuery().select(WeChatPayConfig.class, MpUtil::excludeBigField) return lambdaQuery().select(WeChatPayConfig.class, MpUtil::excludeBigField)
.like(StrUtil.isNotBlank(param.getName()), WeChatPayConfig::getName, param.getName()) .like(StrUtil.isNotBlank(param.getName()), WeChatPayConfig::getName, param.getName())
.like(StrUtil.isNotBlank(param.getAppId()), WeChatPayConfig::getAppId, param.getAppId()) .like(StrUtil.isNotBlank(param.getAppId()), WeChatPayConfig::getWxAppId, param.getAppId())
.like(StrUtil.isNotBlank(param.getAppId()), WeChatPayConfig::getMchId, param.getMchId()) .like(StrUtil.isNotBlank(param.getAppId()), WeChatPayConfig::getWxMchId, param.getMchId())
.orderByDesc(MpIdEntity::getId) .orderByDesc(MpIdEntity::getId)
.page(mpPage); .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.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbTable; 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.BigField;
import cn.bootx.platform.common.core.annotation.EncryptionField; import cn.bootx.platform.common.core.annotation.EncryptionField;
import cn.bootx.platform.common.core.function.EntityBaseFunction; import cn.bootx.platform.common.core.function.EntityBaseFunction;
@@ -35,24 +36,28 @@ public class WeChatPayConfig extends MpBaseEntity implements EntityBaseFunction<
@DbColumn(comment = "名称") @DbColumn(comment = "名称")
private String name; private String name;
/** 微信商户号 */ /** 商户编码 */
@DbColumn(comment = "微信商户号") @TableField(updateStrategy = FieldStrategy.NEVER)
private String mchId; @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") @DbColumn(comment = "商户应用Id")
private Long mchAppId; private Long wxMchAppId;
/** 微信应用appId */ /** 微信应用appId */
@DbColumn(comment = "微信应用appId") @DbColumn(comment = "微信应用appId")
private String appId; private String wxAppId;
// /**
// * api版本
// * @see WeChatPayCode#API_V2
// */
// @DbColumn(comment = "api版本")
// private String apiVersion;
/** 商户平台「API安全」中的 APIv2 密钥 */ /** 商户平台「API安全」中的 APIv2 密钥 */
@TableField(updateStrategy = FieldStrategy.IGNORED) @TableField(updateStrategy = FieldStrategy.IGNORED)
@@ -116,13 +121,12 @@ public class WeChatPayConfig extends MpBaseEntity implements EntityBaseFunction<
@DbColumn(comment = "可用支付方式") @DbColumn(comment = "可用支付方式")
private String payWays; private String payWays;
/** 是否启用 */ /**
@DbColumn(comment = "是否启用") * 状态
private Boolean activity; * @see cn.bootx.platform.daxpay.code.MchAndAppCode#PAY_CONFIG_STATE_NORMAL
*/
/** 状态 */
@DbColumn(comment = "状态") @DbColumn(comment = "状态")
private Integer state; private String state;
/** 备注 */ /** 备注 */
@DbColumn(comment = "备注") @DbColumn(comment = "备注")

View File

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

View File

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

View File

@@ -1,13 +1,18 @@
package cn.bootx.platform.daxpay.core.channel.wechat.service; 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.exception.DataNotExistException;
import cn.bootx.platform.common.core.rest.PageResult; import cn.bootx.platform.common.core.rest.PageResult;
import cn.bootx.platform.common.core.rest.dto.KeyValue; import cn.bootx.platform.common.core.rest.dto.KeyValue;
import cn.bootx.platform.common.core.rest.param.PageParam; import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.common.mybatisplus.util.MpUtil; 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.code.paymodel.WeChatPayWay;
import cn.bootx.platform.daxpay.core.channel.wechat.dao.WeChatPayConfigManager; import cn.bootx.platform.daxpay.core.channel.wechat.dao.WeChatPayConfigManager;
import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig; import cn.bootx.platform.daxpay.core.channel.wechat.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.dto.channel.wechat.WeChatPayConfigDto;
import cn.bootx.platform.daxpay.exception.payment.PayFailureException; import cn.bootx.platform.daxpay.exception.payment.PayFailureException;
import cn.bootx.platform.daxpay.param.channel.wechat.WeChatPayConfigParam; 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 org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -35,15 +39,27 @@ import java.util.stream.Collectors;
public class WeChatPayConfigService { public class WeChatPayConfigService {
private final WeChatPayConfigManager weChatPayConfigManager; private final WeChatPayConfigManager weChatPayConfigManager;
private final MchAppService mchAppService;
private final MchAppPayConfigManager mchAppPayConfigManager;
/** /**
* 添加微信支付配置 * 添加微信支付配置
*/ */
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void add(WeChatPayConfigParam param) { public void add(WeChatPayConfigParam param) {
// 是否有管理关系判断
if (mchAppService.checkMatch(param.getMchCode(), param.getMchAppCode())) {
throw new BizException("应用信息与商户信息不匹配");
}
WeChatPayConfig weChatPayConfig = WeChatPayConfig.init(param); WeChatPayConfig weChatPayConfig = WeChatPayConfig.init(param);
weChatPayConfig.setActivity(false);
weChatPayConfigManager.save(weChatPayConfig); 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)); 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) { private String barCode(String amount, Payment payment, String authCode, WeChatPayConfig weChatPayConfig) {
Map<String, String> params = MicroPayModel.builder() Map<String, String> params = MicroPayModel.builder()
.appid(weChatPayConfig.getAppId()) .appid(weChatPayConfig.getWxAppId())
.mch_id(weChatPayConfig.getMchId()) .mch_id(weChatPayConfig.getWxMchId())
.nonce_str(WxPayKit.generateStr()) .nonce_str(WxPayKit.generateStr())
.body(payment.getTitle()) .body(payment.getTitle())
.auth_code(authCode) .auth_code(authCode)
@@ -226,8 +226,8 @@ public class WeChatPayService {
// 过期时间 // 过期时间
payment.setExpiredTime(PayWaylUtil.getPaymentExpiredTime(weChatPayConfig.getExpireTime())); payment.setExpiredTime(PayWaylUtil.getPaymentExpiredTime(weChatPayConfig.getExpireTime()));
return UnifiedOrderModel.builder() return UnifiedOrderModel.builder()
.appid(weChatPayConfig.getAppId()) .appid(weChatPayConfig.getWxAppId())
.mch_id(weChatPayConfig.getMchId()) .mch_id(weChatPayConfig.getWxMchId())
.nonce_str(WxPayKit.generateStr()) .nonce_str(WxPayKit.generateStr())
.time_start(LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN)) .time_start(LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN))
// 反正v2版本的超时时间无效 // 反正v2版本的超时时间无效

View File

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

View File

@@ -11,6 +11,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.Optional;
/** /**
* 商户应用 * 商户应用
* *
@@ -19,7 +21,14 @@ import org.springframework.stereotype.Repository;
*/ */
@Repository @Repository
@RequiredArgsConstructor @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) { public Page<MchApplication> page(PageParam pageParam, MchApplicationParam param) {
Page<MchApplication> mpPage = MpUtil.getMpPage(pageParam, MchApplication.class); Page<MchApplication> mpPage = MpUtil.getMpPage(pageParam, MchApplication.class);
QueryWrapper<MchApplication> wrapper = QueryGenerator.generator(param, this.getEntityClass()); QueryWrapper<MchApplication> wrapper = QueryGenerator.generator(param, this.getEntityClass());
wrapper.select(this.getEntityClass(), MpUtil::excludeBigField) wrapper.select(this.getEntityClass(), MpUtil::excludeBigField).orderByDesc(MpUtil.getColumnName(MchApplication::getId));
.orderByDesc(MpUtil.getColumnName(MchApplication::getId));
return this.page(mpPage, wrapper); return this.page(mpPage, wrapper);
} }

View File

@@ -13,6 +13,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -25,6 +26,13 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class MerchantInfoManager extends BaseManager<MerchantInfoMapper, MerchantInfo> { 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() { public List<KeyValue> findDropdown() {
return lambdaQuery().select(MerchantInfo::getMchNo, MerchantInfo::getMchName) return lambdaQuery().select(MerchantInfo::getCode, MerchantInfo::getName)
.list() .list()
.stream() .stream()
.map(mch -> new KeyValue(mch.getMchNo(), mch.getMchName())) .map(mch -> new KeyValue(mch.getCode(), mch.getName()))
.collect(Collectors.toList()); .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.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbTable; 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 cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
@@ -21,9 +22,10 @@ import lombok.experimental.Accessors;
@TableName("pay_mch_app_config") @TableName("pay_mch_app_config")
public class MchAppPayConfig extends MpBaseEntity { public class MchAppPayConfig extends MpBaseEntity {
/** 关联应用ID */ /** 关联应用编码 */
@DbColumn(comment = "关联应用ID") @DbMySqlIndex(comment = "关联应用编码索引")
private Long appId; @DbColumn(comment = "关联应用编码")
private String appCode;
/** 关联配置ID */ /** 关联配置ID */
@DbColumn(comment = "关联配置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.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbTable; 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.mybatis.table.modify.mybatis.mysq.constants.MySqlIndexType;
import cn.bootx.platform.common.core.function.EntityBaseFunction; import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity; import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
@@ -15,6 +15,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import lombok.experimental.FieldNameConstants;
/** /**
* 商户应用 * 商户应用
@@ -24,16 +25,17 @@ import lombok.experimental.Accessors;
*/ */
@DbTable(comment = "商户应用") @DbTable(comment = "商户应用")
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@MySqlIndex(columns = "app_no", type = MySqlIndexType.UNIQUE, comment = "应用编码唯一索引") @DbMySqlIndex(fields = MchApplication.Fields.code, type = MySqlIndexType.UNIQUE, comment = "应用编码唯一索引")
@Data @Data
@FieldNameConstants
@Accessors(chain = true) @Accessors(chain = true)
@TableName("pay_mch_app") @TableName("pay_mch_app")
public class MchApplication extends MpBaseEntity implements EntityBaseFunction<MchApplicationDto> { public class MchApplication extends MpBaseEntity implements EntityBaseFunction<MchApplicationDto> {
/** 应用编码 */ /** 应用编码 */
@DbColumn(comment = "应用编码") @DbColumn(comment = "应用编码")
@TableField(updateStrategy = FieldStrategy.IGNORED) @TableField(updateStrategy = FieldStrategy.NEVER)
private String appNo; private String code;
/** 名称 */ /** 名称 */
@DbColumn(comment = "名称") @DbColumn(comment = "名称")
@@ -41,11 +43,14 @@ public class MchApplication extends MpBaseEntity implements EntityBaseFunction<M
/** 商户号 */ /** 商户号 */
@DbColumn(comment = "商户号") @DbColumn(comment = "商户号")
@TableField(updateStrategy = FieldStrategy.IGNORED) @TableField(updateStrategy = FieldStrategy.NEVER)
private String mchNo; private String mchCode;
/** 状态类型 */ /**
@DbColumn(comment = "状态类型") * 状态
* @see cn.bootx.platform.daxpay.code.MchAndAppCode
*/
@DbColumn(comment = "状态")
private String state; 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.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbTable; 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.mybatis.table.modify.mybatis.mysq.constants.MySqlIndexType;
import cn.bootx.platform.common.core.function.EntityBaseFunction; import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity; import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
@@ -17,13 +17,15 @@ import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
import static cn.bootx.platform.daxpay.core.merchant.entity.MerchantInfo.Fields.code;
/** /**
* 商户 * 商户
* *
* @author xxm * @author xxm
* @date 2023-05-17 * @date 2023-05-17
*/ */
@MySqlIndex(columns = "mch_no", type = MySqlIndexType.UNIQUE, comment = "商户号唯一索引") @DbMySqlIndex(fields = code, type = MySqlIndexType.UNIQUE, comment = "商户号唯一索引")
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@FieldNameConstants @FieldNameConstants
@Data @Data
@@ -35,15 +37,15 @@ public class MerchantInfo extends MpBaseEntity implements EntityBaseFunction<Mer
/** 商户号 */ /** 商户号 */
@DbColumn(comment = "商户号") @DbColumn(comment = "商户号")
@TableField(updateStrategy = FieldStrategy.IGNORED) @TableField(updateStrategy = FieldStrategy.IGNORED)
private String mchNo; private String code;
/** 商户名称 */ /** 商户名称 */
@DbColumn(comment = "商户名称") @DbColumn(comment = "商户名称")
private String mchName; private String name;
/** 商户简称 */ /** 商户简称 */
@DbColumn(comment = "商户简称") @DbColumn(comment = "商户简称")
private String mchShortName; private String shortName;
/** 类型 */ /** 类型 */
@DbColumn(comment = "类型") @DbColumn(comment = "类型")
@@ -57,7 +59,10 @@ public class MerchantInfo extends MpBaseEntity implements EntityBaseFunction<Mer
@DbColumn(comment = "联系人手机号") @DbColumn(comment = "联系人手机号")
private String contactTel; private String contactTel;
/** 状态类型 */ /**
* 状态类型
* @see cn.bootx.platform.daxpay.code.MchAndAppCode
*/
@DbColumn(comment = "状态类型") @DbColumn(comment = "状态类型")
private String state; private String state;

View File

@@ -34,7 +34,7 @@ public class MchAppPayConfigService {
* 根据应用ID删除 * 根据应用ID删除
*/ */
public void deleteByAppId(Long appId) { 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(); List<PayChannelConfig> channels = channelConfigManager.findAllByOrder();
// 查询当前应用所拥有的配置, 进行合并生成相关信息 // 查询当前应用所拥有的配置, 进行合并生成相关信息
val mchAppPayConfigMap = mchAppPayConfigManager.findAllByField(MchAppPayConfig::getAppId, appId) val mchAppPayConfigMap = mchAppPayConfigManager.findAllByField(MchAppPayConfig::getAppCode, appId)
.stream() .stream()
.collect(Collectors.toMap(MchAppPayConfig::getChannel, Function.identity())); .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.rest.param.PageParam;
import cn.bootx.platform.common.core.util.ResultConvertUtil; import cn.bootx.platform.common.core.util.ResultConvertUtil;
import cn.bootx.platform.common.mybatisplus.util.MpUtil; 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.MchApplication;
import cn.bootx.platform.daxpay.core.merchant.entity.MerchantInfo;
import cn.bootx.platform.daxpay.dto.merchant.MchApplicationDto; import cn.bootx.platform.daxpay.dto.merchant.MchApplicationDto;
import cn.bootx.platform.daxpay.param.merchant.MchApplicationParam; import cn.bootx.platform.daxpay.param.merchant.MchApplicationParam;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
@@ -18,6 +20,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* 商户应用 * 商户应用
@@ -28,9 +31,11 @@ import java.util.List;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class MchApplicationService { public class MchAppService {
private final MchApplicationManager mchApplicationManager; private final MchAppManager mchAppManager;
private final MerchantInfoManager mchManager;
private final MchAppPayConfigService appPayConfigService; private final MchAppPayConfigService appPayConfigService;
@@ -38,40 +43,39 @@ public class MchApplicationService {
* 添加 * 添加
*/ */
public void add(MchApplicationParam param) { public void add(MchApplicationParam param) {
MchApplication mchApplication = MchApplication.init(param); MchApplication mchApp = MchApplication.init(param);
mchApplication.setAppNo(IdUtil.getSnowflakeNextIdStr()); mchApp.setCode(IdUtil.getSnowflakeNextIdStr());
mchApplicationManager.save(mchApplication); mchAppManager.save(mchApp);
} }
/** /**
* 修改 * 修改
*/ */
public void update(MchApplicationParam param) { public void update(MchApplicationParam param) {
MchApplication mchApplication = mchApplicationManager.findById(param.getId()) MchApplication mchApp = mchAppManager.findById(param.getId()).orElseThrow(DataNotExistException::new);
.orElseThrow(DataNotExistException::new); BeanUtil.copyProperties(param, mchApp, CopyOptions.create().ignoreNullValue());
BeanUtil.copyProperties(param, mchApplication, CopyOptions.create().ignoreNullValue()); mchAppManager.updateById(mchApp);
mchApplicationManager.updateById(mchApplication);
} }
/** /**
* 分页 * 分页
*/ */
public PageResult<MchApplicationDto> page(PageParam pageParam, MchApplicationParam mchApplicationParam) { 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) { 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() { 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) @Transactional(rollbackFor = Exception.class)
public void delete(Long id) { public void delete(Long id) {
appPayConfigService.deleteByAppId(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) { public void add(MerchantInfoParam param) {
MerchantInfo merchantInfo = MerchantInfo.init(param); MerchantInfo merchantInfo = MerchantInfo.init(param);
merchantInfo.setMchNo("M" + System.currentTimeMillis()); merchantInfo.setCode("M" + System.currentTimeMillis());
merchantInfoManager.save(merchantInfo); merchantInfoManager.save(merchantInfo);
} }

View File

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

View File

@@ -41,6 +41,8 @@ public class PaymentBuilder {
String ip = ServletUtil.getClientIP(request); String ip = ServletUtil.getClientIP(request);
// 基础信息 // 基础信息
payment.setBusinessId(payParam.getBusinessId()) payment.setBusinessId(payParam.getBusinessId())
.setMchCode(payParam.getMchCode())
.setMchAppCode(payment.getMchAppCode())
.setTitle(payParam.getTitle()) .setTitle(payParam.getTitle())
.setDescription(payParam.getDescription()); .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); PARAMS.set(params);
try { try {
log.info("支付回调处理: {}", params); log.info("支付回调处理: {}", params);
// 验证消息 // 验证消息
if (!this.verifyNotify()) { if (!this.verifyNotify(appCode)) {
return null; return null;
} }
// 去重处理 // 去重处理
@@ -49,9 +49,10 @@ public abstract class AbsPayCallbackStrategy {
return this.getReturnMsg(); 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 { finally {
PARAMS.remove(); PARAMS.remove();
@@ -76,8 +77,9 @@ public abstract class AbsPayCallbackStrategy {
/** /**
* 验证信息格式 * 验证信息格式
* @param mchAppCode 商户应用编码
*/ */
public abstract boolean verifyNotify(); public abstract boolean verifyNotify(String mchAppCode);
/** /**
* 获取paymentId * 获取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())) PayNotifyRecord payNotifyRecord = new PayNotifyRecord().setNotifyInfo(JSONUtil.toJsonStr(PARAMS.get()))
.setNotifyTime(LocalDateTime.now()) .setNotifyTime(LocalDateTime.now())
.setPaymentId(this.getPaymentId()) .setPaymentId(this.getPaymentId())
.setMchAppCode(appCode)
.setPayChannel(this.getPayChannel().getCode()) .setPayChannel(this.getPayChannel().getCode())
.setStatus(result.getCode()) .setStatus(result.getCode())
.setMsg(result.getMsg()); .setMsg(result.getMsg());

View File

@@ -44,10 +44,11 @@ public class PayCallbackService {
/** /**
* 统一回调处理 * 统一回调处理
* @see PayStatusCode * @param appCode
* @param tradeStatus 支付状态 * @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和paymentParam数据
Payment payment = paymentService.findById(paymentId).orElse(null); Payment payment = paymentService.findById(paymentId).orElse(null);

View File

@@ -1,8 +1,15 @@
package cn.bootx.platform.daxpay.core.pay.service; 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.LocalDateTimeUtil;
import cn.bootx.platform.common.core.util.ValidationUtil; 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.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.PayEventBuilder;
import cn.bootx.platform.daxpay.core.pay.builder.PaymentBuilder; import cn.bootx.platform.daxpay.core.pay.builder.PaymentBuilder;
import cn.bootx.platform.daxpay.core.pay.factory.PayStrategyFactory; 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.PayNotExistedException;
import cn.bootx.platform.daxpay.exception.payment.PayUnsupportedMethodException; import cn.bootx.platform.daxpay.exception.payment.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.mq.PaymentEventSender; 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.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.util.PayWaylUtil; import cn.bootx.platform.daxpay.util.PayWaylUtil;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -45,6 +52,10 @@ public class PayService {
private final PaymentEventSender eventSender; private final PaymentEventSender eventSender;
private final MchAppManager mchAppManager;
private final MerchantInfoManager mchManager;
/** /**
* 支付方法(同步/异步/组合支付) 同步支付:都只会在第一次执行中就完成支付,例如钱包、积分都是调用完就进行了扣减,完成了支付记录 * 支付方法(同步/异步/组合支付) 同步支付:都只会在第一次执行中就完成支付,例如钱包、积分都是调用完就进行了扣减,完成了支付记录
* 异步支付:例如支付宝、微信,发起支付后还需要跳转第三方平台进行支付,支付后通常需要进行回调,之后才完成支付记录 * 异步支付:例如支付宝、微信,发起支付后还需要跳转第三方平台进行支付,支付后通常需要进行回调,之后才完成支付记录
@@ -58,7 +69,8 @@ public class PayService {
ValidationUtil.validateParam(payParam); ValidationUtil.validateParam(payParam);
// 异步支付方式检查 // 异步支付方式检查
PayWaylUtil.validationAsyncPayMode(payParam); PayWaylUtil.validationAsyncPayMode(payParam);
// 商户和应用信息检测
this.checkMchAndApp(payParam);
// 获取并校验支付状态 // 获取并校验支付状态
Payment payment = this.getAndCheckPaymentByBusinessId(payParam.getBusinessId()); Payment payment = this.getAndCheckPaymentByBusinessId(payParam.getBusinessId());
@@ -222,6 +234,30 @@ public class PayService {
return paymentService.save(payment); 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); Payment payment = paymentService.findByBusinessId(businessId).orElse(null);
if (Objects.nonNull(payment)) { if (Objects.nonNull(payment)) {
// 支付失败 // 支付失败类型状态
List<String> trades = Arrays.asList(TRADE_FAIL, TRADE_CANCEL); List<String> tradesStatus = Arrays.asList(TRADE_FAIL, TRADE_CANCEL);
if (trades.contains(payment.getPayStatus())) { if (tradesStatus.contains(payment.getPayStatus())) {
throw new PayFailureException("支付失败或已经被撤销"); throw new PayFailureException("支付失败或已经被撤销");
} }
// 退款状态 // 退款类型状态
trades = Arrays.asList(TRADE_REFUNDING, TRADE_REFUNDED); tradesStatus = Arrays.asList(TRADE_REFUNDING, TRADE_REFUNDED);
if (trades.contains(payment.getPayStatus())) { if (tradesStatus.contains(payment.getPayStatus())) {
throw new PayFailureException("支付失败或已经被撤销"); throw new PayFailureException("支付失败或已经被撤销");
} }
// 支付超时 // 支付超时状态
if (Objects.nonNull(payment.getExpiredTime()) if (Objects.nonNull(payment.getExpiredTime())
&& LocalDateTimeUtil.ge(LocalDateTime.now(), payment.getExpiredTime())) { && LocalDateTimeUtil.ge(LocalDateTime.now(), payment.getExpiredTime())) {
throw new PayFailureException("支付已超时"); throw new PayFailureException("支付已超时");

View File

@@ -91,13 +91,13 @@ public class AliPayStrategy extends AbsPayStrategy {
throw new PayAmountAbnormalException(); throw new PayAmountAbnormalException();
} }
// 检查并获取支付宝支付配置 // 检查并获取支付宝支付配置
this.initAlipayConfig(); this.initAlipayConfig(this.getPayParam().getMchAppCode());
aliPayService.validation(this.getPayWayParam(), alipayConfig); aliPayService.validation(this.getPayWayParam(), alipayConfig);
// 如果没有显式传入同步回调地址, 使用默认配置 // 如果没有显式传入同步回调地址, 使用默认配置
if (StrUtil.isBlank(aliPayParam.getReturnUrl())) { if (StrUtil.isBlank(aliPayParam.getReturnUrl())) {
aliPayParam.setReturnUrl(alipayConfig.getReturnUrl()); aliPayParam.setReturnUrl(alipayConfig.getReturnUrl());
} }
this.initAlipayConfig(); this.initAlipayConfig(this.getPayParam().getMchAppCode());
} }
/** /**
@@ -148,7 +148,7 @@ public class AliPayStrategy extends AbsPayStrategy {
*/ */
@Override @Override
public void doCancelHandler() { public void doCancelHandler() {
this.initAlipayConfig(); this.initAlipayConfig(this.getPayParam().getMchAppCode());
// 撤销支付 // 撤销支付
aliPayCancelService.cancelRemote(this.getPayment()); aliPayCancelService.cancelRemote(this.getPayment());
// 调用关闭本地支付记录 // 调用关闭本地支付记录
@@ -168,7 +168,7 @@ public class AliPayStrategy extends AbsPayStrategy {
*/ */
@Override @Override
public void doRefundHandler() { public void doRefundHandler() {
this.initAlipayConfig(); this.initAlipayConfig(this.getPayParam().getMchAppCode());
aliPayCancelService.refund(this.getPayment(), this.getPayWayParam().getAmount()); aliPayCancelService.refund(this.getPayment(), this.getPayWayParam().getAmount());
aliPaymentService.updatePayRefund(this.getPayment().getId(), this.getPayWayParam().getAmount()); aliPaymentService.updatePayRefund(this.getPayment().getId(), this.getPayWayParam().getAmount());
paymentService.updateRefundSuccess(this.getPayment(), this.getPayWayParam().getAmount(), PayChannelEnum.ALI); paymentService.updateRefundSuccess(this.getPayment(), this.getPayWayParam().getAmount(), PayChannelEnum.ALI);
@@ -179,16 +179,17 @@ public class AliPayStrategy extends AbsPayStrategy {
*/ */
@Override @Override
public PaySyncResult doSyncPayStatusHandler() { public PaySyncResult doSyncPayStatusHandler() {
this.initAlipayConfig(); this.initAlipayConfig(this.getPayParam().getMchAppCode());
return alipaySyncService.syncPayStatus(this.getPayment()); 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); 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); weChatPayService.validation(this.getPayWayParam(), weChatPayConfig);
} }
@@ -148,7 +148,7 @@ public class WeChatPayStrategy extends AbsPayStrategy {
@Override @Override
public void doCancelHandler() { public void doCancelHandler() {
// 检查并获取微信支付配置 // 检查并获取微信支付配置
this.initWeChatPayConfig(); this.initWeChatPayConfig(this.getPayParam().getMchAppCode());
weChatPayCancelService.cancelRemote(this.getPayment(), weChatPayConfig); weChatPayCancelService.cancelRemote(this.getPayment(), weChatPayConfig);
// 调用关闭本地支付记录 // 调用关闭本地支付记录
this.doCloseHandler(); this.doCloseHandler();
@@ -167,7 +167,7 @@ public class WeChatPayStrategy extends AbsPayStrategy {
*/ */
@Override @Override
public void doRefundHandler() { public void doRefundHandler() {
this.initWeChatPayConfig(); this.initWeChatPayConfig(this.getPayParam().getMchAppCode());
WeChatPayment weChatPayment = weChatPaymentManager.findByPaymentId(this.getPayment().getId()) WeChatPayment weChatPayment = weChatPaymentManager.findByPaymentId(this.getPayment().getId())
.orElseThrow(() -> new PayFailureException("微信支付记录不存在")); .orElseThrow(() -> new PayFailureException("微信支付记录不存在"));
weChatPayCancelService.refund(this.getPayment(), weChatPayment, this.getPayWayParam().getAmount(), weChatPayCancelService.refund(this.getPayment(), weChatPayment, this.getPayWayParam().getAmount(),
@@ -182,17 +182,17 @@ public class WeChatPayStrategy extends AbsPayStrategy {
@Override @Override
public PaySyncResult doSyncPayStatusHandler() { public PaySyncResult doSyncPayStatusHandler() {
// 检查并获取微信支付配置 // 检查并获取微信支付配置
this.initWeChatPayConfig(); this.initWeChatPayConfig(this.getPayParam().getMchAppCode());
return weChatPaySyncService.syncPayStatus(this.getPayment().getId(), this.weChatPayConfig); return weChatPaySyncService.syncPayStatus(this.getPayment().getId(), this.weChatPayConfig);
} }
/** /**
* 初始化微信支付 * 初始化微信支付
*/ */
private void initWeChatPayConfig() { private void initWeChatPayConfig(String appCode) {
// 检查并获取微信支付配置 // 检查并获取微信支付配置
this.weChatPayConfig = Optional.ofNullable(this.weChatPayConfig) 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; 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.annotation.BigField;
import cn.bootx.platform.common.core.function.EntityBaseFunction; import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity; import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
@@ -26,6 +28,7 @@ import java.util.List;
* @author xxm * @author xxm
* @date 2020/12/8 * @date 2020/12/8
*/ */
@DbTable(isAppend = true)
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
@FieldNameConstants @FieldNameConstants
@@ -36,6 +39,14 @@ public class Payment extends MpBaseEntity implements EntityBaseFunction<PaymentD
/** 关联的业务id */ /** 关联的业务id */
private String businessId; private String businessId;
/** 商户编码 */
@DbColumn(comment = "商户编码")
private String mchCode;
/** 商户应用编码 */
@DbColumn(comment = "商户应用编码")
private String mchAppCode;
/** 标题 */ /** 标题 */
private String title; private String title;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -71,7 +71,7 @@
<ding-talk.version>1.3.81</ding-talk.version> <ding-talk.version>1.3.81</ding-talk.version>
<lock4j.version>2.2.4</lock4j.version> <lock4j.version>2.2.4</lock4j.version>
<ip2region.version>2.7.0</ip2region.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> </properties>
<!-- 项目依赖版本管理 --> <!-- 项目依赖版本管理 -->