mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-09 14:36:00 +08:00
feat(wechat): 服务商(ISV)支付链路 + Product策略归类 + 移除sandbox
This commit is contained in:
@@ -41,4 +41,26 @@ public interface WechatChannelClient {
|
||||
/// 退款同步(查询退款状态)
|
||||
@PostExchange("/channel/wechat/refund-sync")
|
||||
DaxResult<WechatRefundSyncResp> refundSync(@RequestBody WechatRefundSyncReq req);
|
||||
|
||||
// ===== 服务商模式(isv), 对应子应用 /channel/wechat/isv/* 端点 =====
|
||||
|
||||
/// 服务商支付下单
|
||||
@PostExchange("/channel/wechat/isv/pay")
|
||||
DaxResult<WechatPayResp> isvPay(@RequestBody WechatPayReq req);
|
||||
|
||||
/// 服务商支付同步(查询微信订单状态)
|
||||
@PostExchange("/channel/wechat/isv/sync")
|
||||
DaxResult<WechatSyncResp> isvSync(@RequestBody WechatSyncReq req);
|
||||
|
||||
/// 服务商关闭微信订单
|
||||
@PostExchange("/channel/wechat/isv/close")
|
||||
DaxResult<WechatCloseResp> isvClose(@RequestBody WechatCloseReq req);
|
||||
|
||||
/// 服务商退款
|
||||
@PostExchange("/channel/wechat/isv/refund")
|
||||
DaxResult<WechatRefundResp> isvRefund(@RequestBody WechatRefundReq req);
|
||||
|
||||
/// 服务商退款同步(查询退款状态)
|
||||
@PostExchange("/channel/wechat/isv/refund-sync")
|
||||
DaxResult<WechatRefundSyncResp> isvRefundSync(@RequestBody WechatRefundSyncReq req);
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@ import lombok.Data;
|
||||
/// 下发给子应用构建 WxJava [com.github.binarywang.wxpay.service.WxPayService]。
|
||||
@Data
|
||||
public class WechatSdkCredential {
|
||||
/// 微信商户号
|
||||
/// 微信商户号(服务商模式下为服务商商户号 sp_mchid)
|
||||
private String wxMchId;
|
||||
/// 微信应用ID(公众号 / 小程序 / APP 的 appId)
|
||||
/// 微信应用ID(公众号 / 小程序 / APP 的 appId; 服务商模式下为服务商 AppId sp_appid)
|
||||
private String wxAppId;
|
||||
/// 微信特约商户号(服务商模式 sub_mchid; 直连模式留空)
|
||||
private String subMchId;
|
||||
/// 微信子应用 AppId(服务商模式 sub_appid; 直连模式留空)
|
||||
private String subAppId;
|
||||
/// API V3 密钥
|
||||
private String apiKeyV3;
|
||||
/// 商户私钥(PEM 格式 PKCS#8 字符串)
|
||||
@@ -25,6 +29,4 @@ public class WechatSdkCredential {
|
||||
private String publicKey;
|
||||
/// 支付公钥ID
|
||||
private String publicKeyId;
|
||||
/// 是否沙箱环境
|
||||
private Boolean sandbox;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvChannelMerchant;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/// # 微信服务商通道商户绑定
|
||||
///
|
||||
@Repository
|
||||
@@ -16,4 +18,11 @@ public class WechatIsvChannelMerchantManager extends BaseManager<WechatIsvChanne
|
||||
.eq(WechatIsvChannelMerchant::getSubMchId, subMchId)
|
||||
.exists();
|
||||
}
|
||||
|
||||
/// 根据通道商户号查询
|
||||
public Optional<WechatIsvChannelMerchant> findByChannelMchNo(String channelMchNo) {
|
||||
return lambdaQuery()
|
||||
.eq(WechatIsvChannelMerchant::getChannelMchNo, channelMchNo)
|
||||
.oneOpt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ import cn.daxpay.open.channel.wechat.dao.direct.WechatDirectChannelMerchantManag
|
||||
import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectApp;
|
||||
import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectChannelMerchant;
|
||||
import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectKeyConfig;
|
||||
import cn.daxpay.open.payment.masterdata.constants.product.dao.PayProductConfigManager;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
|
||||
import cn.daxpay.open.platform.core.enums.pay.config.PayEnvEnum;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -33,7 +30,6 @@ public class WechatDirectConfigAssembler {
|
||||
private final WechatDirectChannelMerchantManager wechatDirectChannelMerchantManager;
|
||||
private final WechatDirectKeyConfigService wechatDirectKeyConfigService;
|
||||
private final WechatDirectAppCapabilityService wechatDirectAppCapabilityService;
|
||||
private final PayProductConfigManager payProductConfigManager;
|
||||
|
||||
/// 组装直连商户的通道调用凭证(下发给子应用)
|
||||
///
|
||||
@@ -61,11 +57,6 @@ public class WechatDirectConfigAssembler {
|
||||
// 支付公钥新模式(为空则子应用走平台证书模式)
|
||||
credential.setPublicKey(keyConfig.getPublicKey());
|
||||
credential.setPublicKeyId(keyConfig.getPublicKeyId());
|
||||
// 读取支付产品当前生效环境, 判断是否沙箱
|
||||
boolean sandbox = payProductConfigManager.findByProduct(ProductEnum.WECHAT_PAY.getCode())
|
||||
.map(c -> PayEnvEnum.SANDBOX.getCode().equals(c.getActiveEnv()))
|
||||
.orElse(false);
|
||||
credential.setSandbox(sandbox);
|
||||
return credential;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package cn.daxpay.open.channel.wechat.service.isv;
|
||||
|
||||
import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential;
|
||||
import cn.daxpay.open.channel.wechat.dao.isv.WechatIsvChannelMerchantManager;
|
||||
import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvApp;
|
||||
import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvChannelMerchant;
|
||||
import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvKeyConfig;
|
||||
import cn.daxpay.open.channel.wechat.entity.isv.WechatIsvMchApp;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/// # 微信服务商通道凭证组装器
|
||||
///
|
||||
/// 从服务商密钥配置([WechatIsvKeyConfig]) + 服务商应用([WechatIsvApp]) +
|
||||
/// 特约商户绑定([WechatIsvChannelMerchant]) + 子商户应用([WechatIsvMchApp]) 组装通道调用凭证,
|
||||
/// 下发给子应用构建 WxJava 服务商模式 [com.github.binarywang.wxpay.service.WxPayService]。
|
||||
///
|
||||
/// 字段映射(对齐微信支付 V3 服务商接口):
|
||||
/// - sp_mchid ← [WechatIsvKeyConfig.wxMchId] (服务商商户号, 全局唯一)
|
||||
/// - sp_appid ← [WechatIsvApp.wxAppId] (服务商应用AppId, 按能力解析)
|
||||
/// - sub_mchid ← [WechatIsvChannelMerchant.subMchId] (特约商户号)
|
||||
/// - sub_appid ← [WechatIsvMchApp.wxAppId] (子商户应用AppId, 可选; 未配置留空)
|
||||
///
|
||||
/// 服务商应用解析(委托 [WechatIsvAppCapabilityService.resolveApp]): 显式配置 > appType自动推导 > 首个兜底。
|
||||
/// 子商户应用解析(委托 [WechatIsvMchAppCapabilityService.resolveApp]): 同优先级, 未命中返回 empty(sub_appid 留空)。
|
||||
///
|
||||
/// 供支付策略([cn.daxpay.open.channel.wechat.strategy.isv.*])组装通道调用凭证。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvConfigAssembler {
|
||||
|
||||
private final WechatIsvChannelMerchantManager wechatIsvChannelMerchantManager;
|
||||
private final WechatIsvKeyConfigService wechatIsvKeyConfigService;
|
||||
private final WechatIsvAppCapabilityService wechatIsvAppCapabilityService;
|
||||
private final WechatIsvMchAppCapabilityService wechatIsvMchAppCapabilityService;
|
||||
|
||||
/// 组装服务商模式的通道调用凭证(下发给子应用)
|
||||
///
|
||||
/// @param mchNo 商户号(保留参数对齐直连签名, 服务商密钥全局唯一不依赖此字段)
|
||||
/// @param channelMchNo 通道商户号(特约商户绑定主键)
|
||||
/// @param capability 支付能力编码(用于解析应用)
|
||||
/// @return 微信 SDK 凭证(服务商模式, subMchId/subAppId 已填充)
|
||||
public WechatSdkCredential buildConfig(String mchNo, String channelMchNo, String capability) {
|
||||
// 服务商密钥(全局唯一, 含 sp_mchid 与证书)
|
||||
WechatIsvKeyConfig keyConfig = wechatIsvKeyConfigService.findByProduct(ProductEnum.WECHAT_ISV.getCode());
|
||||
// 特约商户绑定(取 sub_mchid)
|
||||
WechatIsvChannelMerchant channelMerchant = wechatIsvChannelMerchantManager.findByChannelMchNo(channelMchNo)
|
||||
// 微信: 通道商户配置不存在
|
||||
.orElseThrow(() -> new DataNotExistException("error.payment.channel.channelMerchantNotExist"));
|
||||
// 服务商应用(取 sp_appid, 必须存在)
|
||||
WechatIsvApp isvApp = wechatIsvAppCapabilityService.resolveApp(capability)
|
||||
// 微信: 服务商应用不存在
|
||||
.orElseThrow(() -> new DataNotExistException("error.channel.wechat.appNotFound"));
|
||||
// 子商户应用(取 sub_appid, 可选; 未配置时留空, SDK 仅用 sp_appid + sub_mchid 走服务商模式)
|
||||
Optional<WechatIsvMchApp> mchApp = wechatIsvMchAppCapabilityService.resolveApp(channelMchNo, capability);
|
||||
|
||||
WechatSdkCredential credential = new WechatSdkCredential();
|
||||
// 服务商身份(sp_mchid / sp_appid)
|
||||
credential.setWxMchId(keyConfig.getWxMchId());
|
||||
credential.setWxAppId(isvApp.getWxAppId());
|
||||
// 特约商户身份(sub_mchid / sub_appid)
|
||||
credential.setSubMchId(channelMerchant.getSubMchId());
|
||||
credential.setSubAppId(mchApp.map(WechatIsvMchApp::getWxAppId).orElse(null));
|
||||
// 服务商密钥与证书
|
||||
credential.setApiKeyV3(keyConfig.getApiKeyV3());
|
||||
credential.setPrivateKey(keyConfig.getPrivateKey());
|
||||
credential.setPrivateCert(keyConfig.getPrivateCert());
|
||||
credential.setCertSerialNo(keyConfig.getCertSerialNo());
|
||||
// 支付公钥新模式(为空则子应用走平台证书模式)
|
||||
credential.setPublicKey(keyConfig.getPublicKey());
|
||||
credential.setPublicKeyId(keyConfig.getPublicKeyId());
|
||||
return credential;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package cn.daxpay.open.channel.wechat.service.payment.isv;
|
||||
|
||||
import cn.daxpay.open.channel.wechat.client.WechatChannelClient;
|
||||
import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential;
|
||||
import cn.daxpay.open.channel.wechat.client.req.WechatCloseReq;
|
||||
import cn.daxpay.open.channel.wechat.client.resp.WechatCloseResp;
|
||||
import cn.daxpay.open.payment.common.result.DaxResult;
|
||||
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
|
||||
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
|
||||
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 微信服务商支付关闭业务服务
|
||||
///
|
||||
/// 通过 [WechatChannelClient] 调用子应用 dax-pay-channel-one 服务商端点关闭微信订单。
|
||||
/// 微信 V3 仅提供关单接口(closePartnerOrderV3), 无撤销接口(useCancel 参数忽略, 统一返回 [CloseTypeEnum.CLOSE])。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvCloseService {
|
||||
|
||||
private final WechatChannelClient wechatChannelClient;
|
||||
|
||||
/// 执行微信服务商订单关闭
|
||||
///
|
||||
/// @param trade 支付订单(tradeNo 作为 out_trade_no)
|
||||
/// @param credential 通道调用凭证(服务商模式)
|
||||
/// @param useCancel 是否使用撤销方式(微信无撤销接口, 忽略)
|
||||
/// @return 关闭方式(微信固定返回 [CloseTypeEnum.CLOSE])
|
||||
public CloseTypeEnum close(PayTrade trade, WechatSdkCredential credential, boolean useCancel) {
|
||||
// 构建请求(与直连一致)
|
||||
WechatCloseReq req = new WechatCloseReq();
|
||||
req.setOutTradeNo(trade.getTradeNo());
|
||||
req.setTransactionId(trade.getOutOrderNo());
|
||||
req.setCredential(credential);
|
||||
|
||||
// 调用子应用服务商端点
|
||||
DaxResult<WechatCloseResp> result = wechatChannelClient.isvClose(req);
|
||||
if (result.getCode() != 0) {
|
||||
throw new BizInfoException(DaxPayErrorCode.OPERATION_FAIL, "error.channel.wechat.closeFailed", result.getMsg());
|
||||
}
|
||||
|
||||
// 微信仅支持关闭, 无撤销
|
||||
return CloseTypeEnum.CLOSE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package cn.daxpay.open.channel.wechat.service.payment.isv;
|
||||
|
||||
import cn.daxpay.open.channel.wechat.client.WechatChannelClient;
|
||||
import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential;
|
||||
import cn.daxpay.open.channel.wechat.client.enums.WechatPayBodyType;
|
||||
import cn.daxpay.open.channel.wechat.client.enums.WechatPayMethod;
|
||||
import cn.daxpay.open.channel.wechat.client.req.WechatPayReq;
|
||||
import cn.daxpay.open.channel.wechat.client.resp.WechatPayResp;
|
||||
import cn.daxpay.open.payment.common.result.DaxResult;
|
||||
import cn.daxpay.open.payment.core.trade.bo.PayTradeResultBo;
|
||||
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
|
||||
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum;
|
||||
import cn.daxpay.open.platform.core.enums.unipay.PayBodyTypeEnum;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.system.service.config.PlatformUrlConfigService;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 微信服务商支付执行业务服务
|
||||
///
|
||||
/// 通过 [WechatChannelClient] 调用子应用 dax-pay-channel-one 完成微信服务商支付下单。
|
||||
/// 与直连模式([cn.daxpay.open.channel.wechat.service.payment.pay.WechatPayService])的核心差异:
|
||||
/// - 调用服务商端点 `/channel/wechat/isv/pay`(子应用内部走 `/v3/partner/transactions/*`)
|
||||
/// - 异步通知回调路径用 `/wechat/isv/pay`(服务商用服务商 apiV3Key 解密)
|
||||
///
|
||||
/// 请求构建、响应解析与直连模式一致(子应用返回结构相同)。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvPayService {
|
||||
|
||||
private final WechatChannelClient wechatChannelClient;
|
||||
private final PlatformUrlConfigService platformUrlConfigService;
|
||||
|
||||
/// 执行微信服务商支付
|
||||
///
|
||||
/// @param order 支付订单(tradeNo 作为 out_trade_no)
|
||||
/// @param payParam 支付参数
|
||||
/// @param credential 通道调用凭证(服务商模式, 含 sp_mchid/sub_mchid/sp_appid/sub_appid)
|
||||
/// @return 支付结果
|
||||
public PayTradeResultBo pay(PayTrade order, NormalPayParam payParam, WechatSdkCredential credential) {
|
||||
// 构建请求(与直连一致)
|
||||
WechatPayReq req = new WechatPayReq();
|
||||
// 使用支付交易号作为商户订单号透传给微信, 回调时凭此反查 PayTrade
|
||||
req.setOutTradeNo(order.getTradeNo());
|
||||
req.setAmount(payParam.getAmount());
|
||||
req.setDescription(payParam.getTitle());
|
||||
req.setMethod(mapMethod(payParam.getMethod()));
|
||||
req.setAuthCode(payParam.getAuthCode());
|
||||
req.setOpenId(payParam.getOpenId());
|
||||
req.setAttach(payParam.getAttach());
|
||||
req.setNotifyUrl(this.buildNotifyUrl(order));
|
||||
req.setExpireTime(payParam.getExpiredTime());
|
||||
if (req.getMethod() == WechatPayMethod.H5) {
|
||||
req.setWapUrl(payParam.getReturnUrl());
|
||||
req.setWapName(payParam.getTitle());
|
||||
}
|
||||
req.setCredential(credential);
|
||||
|
||||
// 调用子应用服务商端点
|
||||
DaxResult<WechatPayResp> result = wechatChannelClient.isvPay(req);
|
||||
if (result.getCode() != 0) {
|
||||
throw new BizInfoException(DaxPayErrorCode.TRADE_FAIL, "error.channel.wechat.payFailed", result.getMsg());
|
||||
}
|
||||
|
||||
return toPayResult(result.getData());
|
||||
}
|
||||
|
||||
/// 生成微信服务商支付异步通知地址(微信→平台)
|
||||
///
|
||||
/// 服务商回调路径: `{backendBaseUrl}/unipay/callback/{mchNo}/{appId}/wechat/isv/pay`
|
||||
/// (与直连 `/wechat/pay` 分离, 服务商回调用服务商 apiV3Key 解密)
|
||||
private String buildNotifyUrl(PayTrade order) {
|
||||
String base = platformUrlConfigService.getUrlConfig().getBackendBaseUrl();
|
||||
if (StrUtil.isBlank(base)) {
|
||||
// backendBaseUrl 未配置时抛清晰异常
|
||||
throw new BizInfoException(DaxPayErrorCode.CONFIG_ERROR, "error.common.backendBaseUrlNotConfigured");
|
||||
}
|
||||
return StrUtil.format("{}/unipay/callback/{}/{}/wechat/isv/pay",
|
||||
base, order.getMchNo(), order.getAppId());
|
||||
}
|
||||
|
||||
/// 平台支付方式([PayMethodEnum] code) -> 微信通道支付方式
|
||||
private static WechatPayMethod mapMethod(String methodCode) {
|
||||
PayMethodEnum m = PayMethodEnum.findByCode(methodCode);
|
||||
return switch (m) {
|
||||
case WECHAT_QR -> WechatPayMethod.NATIVE;
|
||||
case WECHAT_JSAPI -> WechatPayMethod.JSAPI;
|
||||
case WECHAT_MINI -> WechatPayMethod.MINI;
|
||||
case WECHAT_APP -> WechatPayMethod.APP;
|
||||
case WECHAT_H5 -> WechatPayMethod.H5;
|
||||
case WECHAT_BARCODE -> WechatPayMethod.MICROPAY;
|
||||
default -> throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
|
||||
"error.channel.wechat.unsupportedPayMethod", methodCode);
|
||||
};
|
||||
}
|
||||
|
||||
/// 解析子应用响应为支付结果 BO
|
||||
private PayTradeResultBo toPayResult(WechatPayResp resp) {
|
||||
PayTradeResultBo bo = new PayTradeResultBo()
|
||||
.setOutOrderNo(resp.getTransactionId())
|
||||
.setComplete(Boolean.TRUE.equals(resp.getComplete()))
|
||||
.setPayBody(resp.getPayBody());
|
||||
|
||||
// 支付内容类型映射(子应用 WechatPayBodyType -> 平台 PayBodyTypeEnum)
|
||||
WechatPayBodyType bodyType = resp.getPayBodyType();
|
||||
if (bodyType != null) {
|
||||
switch (bodyType) {
|
||||
case LINK -> bo.setPayBodyType(PayBodyTypeEnum.LINK);
|
||||
case QR_CODE -> bo.setPayBodyType(PayBodyTypeEnum.QR_CODE);
|
||||
case IDENTIFIER, APP_ORDER_STR -> bo.setPayBodyType(PayBodyTypeEnum.IDENTIFIER);
|
||||
}
|
||||
}
|
||||
|
||||
// 完成时间(MICROPAY 同步成功时返回)
|
||||
bo.setFinishTime(resp.getFinishTime());
|
||||
// 金额(MICROPAY 同步成功时返回)
|
||||
bo.setTotalAmount(resp.getTotalAmount());
|
||||
bo.setRealAmount(resp.getPayerTotal());
|
||||
bo.setBuyerPayAmount(resp.getPayerTotal());
|
||||
// 用户标识
|
||||
bo.setBuyerId(resp.getOpenId());
|
||||
return bo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package cn.daxpay.open.channel.wechat.service.payment.isv;
|
||||
|
||||
import cn.daxpay.open.channel.wechat.client.WechatChannelClient;
|
||||
import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential;
|
||||
import cn.daxpay.open.channel.wechat.client.req.WechatRefundReq;
|
||||
import cn.daxpay.open.channel.wechat.client.resp.WechatRefundResp;
|
||||
import cn.daxpay.open.payment.common.enums.RefundOrderStatusEnum;
|
||||
import cn.daxpay.open.payment.common.result.DaxResult;
|
||||
import cn.daxpay.open.payment.core.trade.bo.RefundResultBo;
|
||||
import cn.daxpay.open.payment.core.trade.entity.PayRefundOrder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 微信服务商退款业务服务
|
||||
///
|
||||
/// 通过 [WechatChannelClient] 调用子应用 dax-pay-channel-one 服务商端点发起微信退款。
|
||||
/// 退款状态判定与直连模式一致。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvRefundService {
|
||||
|
||||
private final WechatChannelClient wechatChannelClient;
|
||||
|
||||
/// 微信退款状态
|
||||
private static final String STATUS_SUCCESS = "SUCCESS";
|
||||
private static final String STATUS_CLOSED = "CLOSED";
|
||||
|
||||
/// 执行微信服务商退款
|
||||
public RefundResultBo refund(PayRefundOrder refundOrder, WechatSdkCredential credential) {
|
||||
// 构建请求(与直连一致)
|
||||
WechatRefundReq req = new WechatRefundReq();
|
||||
req.setOutTradeNo(refundOrder.getOrderNo());
|
||||
req.setTransactionId(refundOrder.getOutOrderNo());
|
||||
req.setOutRefundNo(refundOrder.getRefundNo());
|
||||
// 微信退款需原订单总额(orderAmount)与退款金额(amount)
|
||||
req.setTotalAmount(refundOrder.getOrderAmount());
|
||||
req.setRefundAmount(refundOrder.getAmount());
|
||||
req.setReason(refundOrder.getReason());
|
||||
req.setNotifyUrl(refundOrder.getNotifyUrl());
|
||||
req.setCredential(credential);
|
||||
|
||||
// 调用子应用服务商端点
|
||||
DaxResult<WechatRefundResp> result = wechatChannelClient.isvRefund(req);
|
||||
if (result.getCode() != 0) {
|
||||
log.error("微信服务商通道退款失败: refundNo={}, msg={}", refundOrder.getRefundNo(), result.getMsg());
|
||||
RefundResultBo bo = new RefundResultBo();
|
||||
bo.setComplete(false)
|
||||
.setStatus(RefundOrderStatusEnum.FAIL)
|
||||
.setSyncSuccess(false)
|
||||
.setSyncErrorMsg(result.getMsg());
|
||||
return bo;
|
||||
}
|
||||
|
||||
return toRefundResult(result.getData());
|
||||
}
|
||||
|
||||
/// 解析子应用响应
|
||||
private RefundResultBo toRefundResult(WechatRefundResp resp) {
|
||||
RefundResultBo bo = new RefundResultBo();
|
||||
bo.setOutRefundNo(resp.getOutRefundNo());
|
||||
bo.setFinishTime(resp.getFinishTime());
|
||||
bo.setRefundAmount(resp.getRefundAmount());
|
||||
// SUCCESS / CLOSED → 退款终态成功; 否则退款中, 需同步查询确认
|
||||
if (STATUS_SUCCESS.equals(resp.getStatus()) || STATUS_CLOSED.equals(resp.getStatus())) {
|
||||
bo.setComplete(true)
|
||||
.setStatus(RefundOrderStatusEnum.SUCCESS);
|
||||
} else {
|
||||
bo.setComplete(false)
|
||||
.setStatus(RefundOrderStatusEnum.PROGRESS);
|
||||
}
|
||||
return bo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package cn.daxpay.open.channel.wechat.service.payment.isv;
|
||||
|
||||
import cn.daxpay.open.channel.wechat.client.WechatChannelClient;
|
||||
import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential;
|
||||
import cn.daxpay.open.channel.wechat.client.req.WechatRefundSyncReq;
|
||||
import cn.daxpay.open.channel.wechat.client.resp.WechatRefundSyncResp;
|
||||
import cn.daxpay.open.payment.common.enums.RefundOrderStatusEnum;
|
||||
import cn.daxpay.open.payment.common.result.DaxResult;
|
||||
import cn.daxpay.open.payment.core.trade.bo.RefundResultBo;
|
||||
import cn.daxpay.open.payment.core.trade.entity.PayRefundOrder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 微信服务商退款同步业务服务
|
||||
///
|
||||
/// 通过 [WechatChannelClient] 调用子应用 dax-pay-channel-one 服务商端点查询微信退款状态,
|
||||
/// 将微信退款 status 映射为平台 [RefundOrderStatusEnum]。映射规则与直连模式一致。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvRefundSyncService {
|
||||
|
||||
private final WechatChannelClient wechatChannelClient;
|
||||
|
||||
/// 微信退款状态
|
||||
private static final String STATUS_SUCCESS = "SUCCESS";
|
||||
private static final String STATUS_CLOSED = "CLOSED";
|
||||
private static final String STATUS_ABNORMAL = "ABNORMAL";
|
||||
|
||||
/// 执行微信服务商退款同步查询
|
||||
public RefundResultBo sync(PayRefundOrder refundOrder, WechatSdkCredential credential) {
|
||||
// 构建请求(与直连一致)
|
||||
var req = new WechatRefundSyncReq();
|
||||
req.setOutRefundNo(refundOrder.getRefundNo());
|
||||
req.setCredential(credential);
|
||||
|
||||
// 调用子应用服务商端点
|
||||
DaxResult<WechatRefundSyncResp> result = wechatChannelClient.isvRefundSync(req);
|
||||
if (result.getCode() != 0) {
|
||||
log.error("微信服务商通道退款同步失败: refundNo={}, msg={}", refundOrder.getRefundNo(), result.getMsg());
|
||||
RefundResultBo bo = new RefundResultBo();
|
||||
bo.setSyncSuccess(false)
|
||||
.setSyncErrorMsg(result.getMsg());
|
||||
return bo;
|
||||
}
|
||||
|
||||
return toSyncResult(result.getData());
|
||||
}
|
||||
|
||||
/// 解析子应用响应, 映射 status → [RefundOrderStatusEnum]
|
||||
private RefundResultBo toSyncResult(WechatRefundSyncResp resp) {
|
||||
RefundResultBo bo = new RefundResultBo();
|
||||
bo.setOutRefundNo(resp.getOutRefundNo());
|
||||
String status = resp.getStatus();
|
||||
|
||||
// 退款成功
|
||||
if (STATUS_SUCCESS.equals(status)) {
|
||||
return bo.setComplete(true)
|
||||
.setStatus(RefundOrderStatusEnum.SUCCESS)
|
||||
.setFinishTime(resp.getFinishTime())
|
||||
.setRefundAmount(resp.getRefundAmount());
|
||||
}
|
||||
|
||||
// 退款关闭
|
||||
if (STATUS_CLOSED.equals(status)) {
|
||||
return bo.setComplete(true)
|
||||
.setStatus(RefundOrderStatusEnum.CLOSE)
|
||||
.setFinishTime(resp.getFinishTime());
|
||||
}
|
||||
|
||||
// 退款异常
|
||||
if (STATUS_ABNORMAL.equals(status)) {
|
||||
return bo.setComplete(true)
|
||||
.setStatus(RefundOrderStatusEnum.FAIL);
|
||||
}
|
||||
|
||||
// 退款中(PROCESSING 或未知)
|
||||
return bo.setComplete(false)
|
||||
.setStatus(RefundOrderStatusEnum.PROGRESS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package cn.daxpay.open.channel.wechat.service.payment.isv;
|
||||
|
||||
import cn.daxpay.open.channel.wechat.client.WechatChannelClient;
|
||||
import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential;
|
||||
import cn.daxpay.open.channel.wechat.client.req.WechatSyncReq;
|
||||
import cn.daxpay.open.channel.wechat.client.resp.WechatSyncResp;
|
||||
import cn.daxpay.open.payment.common.enums.PayFundStatusEnum;
|
||||
import cn.daxpay.open.payment.common.result.DaxResult;
|
||||
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
|
||||
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 微信服务商支付同步业务服务
|
||||
///
|
||||
/// 通过 [WechatChannelClient] 调用子应用 dax-pay-channel-one 服务商端点查询微信订单状态,
|
||||
/// 将微信 trade_state 映射为平台 [PayFundStatusEnum]。映射规则与直连模式一致。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvSyncService {
|
||||
|
||||
private final WechatChannelClient wechatChannelClient;
|
||||
|
||||
/// 微信交易状态
|
||||
private static final String STATE_SUCCESS = "SUCCESS";
|
||||
private static final String STATE_REFUND = "REFUND";
|
||||
private static final String STATE_NOTPAY = "NOTPAY";
|
||||
private static final String STATE_USERPAYING = "USERPAYING";
|
||||
private static final String STATE_CLOSED = "CLOSED";
|
||||
private static final String STATE_REVOKED = "REVOKED";
|
||||
private static final String STATE_PAYERROR = "PAYERROR";
|
||||
private static final String STATE_ACCEPT = "ACCEPT";
|
||||
|
||||
/// 执行微信服务商支付同步
|
||||
public PaySyncResultBo sync(PayTrade trade, WechatSdkCredential credential) {
|
||||
// 构建请求(与直连一致)
|
||||
WechatSyncReq req = new WechatSyncReq();
|
||||
req.setOutTradeNo(trade.getTradeNo());
|
||||
req.setTransactionId(trade.getOutOrderNo());
|
||||
req.setCredential(credential);
|
||||
|
||||
// 调用子应用服务商端点
|
||||
DaxResult<WechatSyncResp> result = wechatChannelClient.isvSync(req);
|
||||
if (result.getCode() != 0) {
|
||||
log.error("微信服务商通道同步失败: outTradeNo={}, msg={}", trade.getTradeNo(), result.getMsg());
|
||||
PaySyncResultBo bo = new PaySyncResultBo();
|
||||
bo.setSyncSuccess(false)
|
||||
.setSyncErrorMsg(result.getMsg());
|
||||
return bo;
|
||||
}
|
||||
|
||||
return toSyncResult(result.getData());
|
||||
}
|
||||
|
||||
/// 解析子应用响应, 映射 trade_state → [PayFundStatusEnum]
|
||||
private PaySyncResultBo toSyncResult(WechatSyncResp resp) {
|
||||
PaySyncResultBo bo = new PaySyncResultBo();
|
||||
bo.setOutOrderNo(resp.getTransactionId());
|
||||
|
||||
String tradeState = resp.getTradeState();
|
||||
|
||||
// 支付成功(REFUND 表示原订单已支付, 后续发起退款, 资金状态视为 SUCCESS)
|
||||
if (STATE_SUCCESS.equals(tradeState) || STATE_REFUND.equals(tradeState)) {
|
||||
return bo.setPayStatus(PayFundStatusEnum.SUCCESS)
|
||||
.setFinishTime(resp.getSuccessTime())
|
||||
.setRealAmount(resp.getPayerTotal())
|
||||
.setAmount(resp.getTotalAmount())
|
||||
.setBuyerId(resp.getOpenId());
|
||||
}
|
||||
|
||||
// 待支付 / 用户支付中 / 已接收(等待扣款)
|
||||
if (STATE_NOTPAY.equals(tradeState) || STATE_USERPAYING.equals(tradeState) || STATE_ACCEPT.equals(tradeState)) {
|
||||
return bo.setPayStatus(PayFundStatusEnum.PROCESSING);
|
||||
}
|
||||
|
||||
// 已关闭
|
||||
if (STATE_CLOSED.equals(tradeState)) {
|
||||
return bo.setPayStatus(PayFundStatusEnum.CLOSE);
|
||||
}
|
||||
|
||||
// 已撤销(付款码支付撤销)
|
||||
if (STATE_REVOKED.equals(tradeState)) {
|
||||
return bo.setPayStatus(PayFundStatusEnum.CANCEL);
|
||||
}
|
||||
|
||||
// 支付失败
|
||||
if (STATE_PAYERROR.equals(tradeState)) {
|
||||
return bo.setPayStatus(PayFundStatusEnum.FAIL);
|
||||
}
|
||||
|
||||
// 未知状态
|
||||
return bo.setSyncSuccess(false)
|
||||
.setSyncErrorMsg(StrUtil.blankToDefault(resp.getTradeStateDesc(), "微信服务商同步查询未知状态: " + tradeState));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.daxpay.open.channel.wechat.strategy;
|
||||
package cn.daxpay.open.channel.wechat.strategy.direct;
|
||||
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ChannelApiCallMode;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ChannelPayIdType;
|
||||
@@ -36,11 +36,6 @@ public class WechatDirectProductStrategy extends AbsProductStrategy {
|
||||
return ProductEnum.WECHAT_PAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSandbox() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelApiCallMode getApiCallMode() {
|
||||
return ChannelApiCallMode.MCH;
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.daxpay.open.channel.wechat.strategy.isv;
|
||||
|
||||
import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential;
|
||||
import cn.daxpay.open.channel.wechat.service.isv.WechatIsvConfigAssembler;
|
||||
import cn.daxpay.open.channel.wechat.service.payment.isv.WechatIsvCloseService;
|
||||
import cn.daxpay.open.payment.core.strategy.pay.AbsPayCloseStrategy;
|
||||
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
|
||||
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
|
||||
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
|
||||
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 微信服务商支付关闭策略
|
||||
///
|
||||
/// 微信服务商模式(ProductEnum.WECHAT_ISV)下的支付关闭策略。
|
||||
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
|
||||
/// 组装服务商通道凭证(委托 [WechatIsvConfigAssembler]), 关闭执行委托给 [WechatCloseService]。
|
||||
///
|
||||
/// 注意: 微信 V3 仅提供关单接口, 无撤销接口(useCancel 参数由 service 层忽略)。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvCloseStrategy extends AbsPayCloseStrategy {
|
||||
|
||||
private final WechatIsvCloseService wechatIsvCloseService;
|
||||
private final WechatIsvConfigAssembler wechatIsvConfigAssembler;
|
||||
|
||||
@Override
|
||||
public ProductEnum getProduct() {
|
||||
return ProductEnum.WECHAT_ISV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloseTypeEnum doClose(PayStrategyContext context, boolean useCancel) {
|
||||
// 从上下文容器读取通道路由参数, 用于凭证解析
|
||||
NormalPayOrder normalOrder = context.getContainer();
|
||||
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
|
||||
String capability = normalOrder != null ? normalOrder.getCapability() : null;
|
||||
PayTrade trade = context.getTrade();
|
||||
|
||||
// 组装服务商通道调用凭证
|
||||
WechatSdkCredential credential = wechatIsvConfigAssembler.buildConfig(
|
||||
trade.getMchNo(), channelMchNo, capability);
|
||||
|
||||
return wechatIsvCloseService.close(trade, credential, useCancel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package cn.daxpay.open.channel.wechat.strategy.isv;
|
||||
|
||||
import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential;
|
||||
import cn.daxpay.open.channel.wechat.service.isv.WechatIsvConfigAssembler;
|
||||
import cn.daxpay.open.channel.wechat.service.payment.isv.WechatIsvPayService;
|
||||
import cn.daxpay.open.payment.core.strategy.pay.AbsNormalPayStrategy;
|
||||
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
|
||||
import cn.daxpay.open.payment.core.trade.bo.PayTradeResultBo;
|
||||
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 微信服务商支付策略
|
||||
///
|
||||
/// 微信服务商模式(ProductEnum.WECHAT_ISV)下发起支付的具体执行策略。
|
||||
/// 配置组装在 [doBeforePay] 阶段完成(委托 [WechatIsvConfigAssembler]), 支付执行委托给 [WechatPayService]。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvPayStrategy extends AbsNormalPayStrategy {
|
||||
|
||||
private final WechatIsvPayService wechatIsvPayService;
|
||||
private final WechatIsvConfigAssembler wechatIsvConfigAssembler;
|
||||
|
||||
@Override
|
||||
public ProductEnum getProduct() {
|
||||
return ProductEnum.WECHAT_ISV;
|
||||
}
|
||||
|
||||
/// 支付前预处理: 从请求参数读取通道路由参数, 组装服务商通道凭证写入上下文
|
||||
/// 配置缺失在此阶段抛异常, fail-fast, 不会进入 doPay
|
||||
@Override
|
||||
public void doBeforePay(PayStrategyContext context) {
|
||||
NormalPayParam payParam = context.getPayParam();
|
||||
WechatSdkCredential credential = wechatIsvConfigAssembler.buildConfig(
|
||||
payParam.getMchNo(), payParam.getChannelMchNo(), payParam.getCapability());
|
||||
context.setChannelConfig(credential);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayTradeResultBo doPay(PayStrategyContext context) {
|
||||
// 直接使用 doBeforePay 预组装的服务商通道凭证
|
||||
WechatSdkCredential credential = context.getChannelConfig(WechatSdkCredential.class);
|
||||
return wechatIsvPayService.pay(context.getTrade(), context.getPayParam(), credential);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.daxpay.open.channel.wechat.strategy;
|
||||
package cn.daxpay.open.channel.wechat.strategy.isv;
|
||||
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ChannelApiCallMode;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ChannelPayIdType;
|
||||
@@ -39,9 +39,6 @@ public class WechatIsvProductStrategy extends AbsProductStrategy {
|
||||
@Override
|
||||
public boolean isIsv() { return true; }
|
||||
|
||||
@Override
|
||||
public boolean isSandbox() { return false; }
|
||||
|
||||
@Override
|
||||
public ChannelApiCallMode getApiCallMode() { return ChannelApiCallMode.MIX; }
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.daxpay.open.channel.wechat.strategy.isv;
|
||||
|
||||
import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential;
|
||||
import cn.daxpay.open.channel.wechat.service.isv.WechatIsvConfigAssembler;
|
||||
import cn.daxpay.open.channel.wechat.service.payment.isv.WechatIsvRefundService;
|
||||
import cn.daxpay.open.payment.core.strategy.refund.AbsRefundStrategy;
|
||||
import cn.daxpay.open.payment.core.trade.bo.RefundResultBo;
|
||||
import cn.daxpay.open.payment.core.trade.entity.PayRefundOrder;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 微信服务商退款策略
|
||||
///
|
||||
/// 微信服务商模式(ProductEnum.WECHAT_ISV)下的退款策略。
|
||||
/// 从退款订单读取通道路由参数(channelMchNo / capability),
|
||||
/// 组装服务商通道凭证(委托 [WechatIsvConfigAssembler]), 退款执行委托给 [WechatRefundService]。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvRefundStrategy extends AbsRefundStrategy {
|
||||
|
||||
private final WechatIsvRefundService wechatIsvRefundService;
|
||||
private final WechatIsvConfigAssembler wechatIsvConfigAssembler;
|
||||
|
||||
@Override
|
||||
public ProductEnum getProduct() {
|
||||
return ProductEnum.WECHAT_ISV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefundResultBo doRefund(PayRefundOrder refundOrder) {
|
||||
// 组装服务商通道调用凭证
|
||||
WechatSdkCredential credential = wechatIsvConfigAssembler.buildConfig(
|
||||
refundOrder.getMchNo(), refundOrder.getChannelMchNo(), refundOrder.getCapability());
|
||||
return wechatIsvRefundService.refund(refundOrder, credential);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.daxpay.open.channel.wechat.strategy.isv;
|
||||
|
||||
import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential;
|
||||
import cn.daxpay.open.channel.wechat.service.isv.WechatIsvConfigAssembler;
|
||||
import cn.daxpay.open.channel.wechat.service.payment.isv.WechatIsvRefundSyncService;
|
||||
import cn.daxpay.open.payment.core.strategy.refund.AbsSyncRefundStrategy;
|
||||
import cn.daxpay.open.payment.core.trade.bo.RefundResultBo;
|
||||
import cn.daxpay.open.payment.core.trade.entity.PayRefundOrder;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 微信服务商退款同步策略
|
||||
///
|
||||
/// 微信服务商模式(ProductEnum.WECHAT_ISV)下的退款同步策略。
|
||||
/// 组装服务商通道凭证(委托 [WechatIsvConfigAssembler]), 同步执行委托给 [WechatRefundSyncService]。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvSyncRefundStrategy extends AbsSyncRefundStrategy {
|
||||
|
||||
private final WechatIsvRefundSyncService wechatIsvRefundSyncService;
|
||||
private final WechatIsvConfigAssembler wechatIsvConfigAssembler;
|
||||
|
||||
@Override
|
||||
public ProductEnum getProduct() {
|
||||
return ProductEnum.WECHAT_ISV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefundResultBo doSync(PayRefundOrder refundOrder) {
|
||||
// 组装服务商通道调用凭证
|
||||
WechatSdkCredential credential = wechatIsvConfigAssembler.buildConfig(
|
||||
refundOrder.getMchNo(), refundOrder.getChannelMchNo(), refundOrder.getCapability());
|
||||
return wechatIsvRefundSyncService.sync(refundOrder, credential);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cn.daxpay.open.channel.wechat.strategy.isv;
|
||||
|
||||
import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential;
|
||||
import cn.daxpay.open.channel.wechat.service.isv.WechatIsvConfigAssembler;
|
||||
import cn.daxpay.open.channel.wechat.service.payment.isv.WechatIsvSyncService;
|
||||
import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
|
||||
import cn.daxpay.open.payment.core.strategy.sync.AbsSyncPayOrderStrategy;
|
||||
import cn.daxpay.open.payment.core.trade.bo.PaySyncResultBo;
|
||||
import cn.daxpay.open.payment.core.trade.entity.NormalPayOrder;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/// # 微信服务商支付同步策略
|
||||
///
|
||||
/// 微信服务商模式(ProductEnum.WECHAT_ISV)下的支付同步策略。
|
||||
/// 从上下文容器读取通道路由参数(channelMchNo / capability),
|
||||
/// 组装服务商通道凭证(委托 [WechatIsvConfigAssembler]), 同步执行委托给 [WechatSyncService]。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class WechatIsvSyncStrategy extends AbsSyncPayOrderStrategy {
|
||||
|
||||
private final WechatIsvSyncService wechatIsvSyncService;
|
||||
private final WechatIsvConfigAssembler wechatIsvConfigAssembler;
|
||||
|
||||
@Override
|
||||
public ProductEnum getProduct() {
|
||||
return ProductEnum.WECHAT_ISV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaySyncResultBo doSync(PayStrategyContext context) {
|
||||
// 从上下文容器读取通道路由参数, 用于凭证解析
|
||||
NormalPayOrder normalOrder = context.getContainer();
|
||||
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
|
||||
String capability = normalOrder != null ? normalOrder.getCapability() : null;
|
||||
|
||||
// 组装服务商通道调用凭证
|
||||
WechatSdkCredential credential = wechatIsvConfigAssembler.buildConfig(
|
||||
context.getTrade().getMchNo(), channelMchNo, capability);
|
||||
|
||||
return wechatIsvSyncService.sync(context.getTrade(), credential);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user