diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/appplus/pay/wechat/ThirdAppPlusPayDto.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/appplus/pay/wechat/ThirdAppPlusPayDto.java index 6c074dc17..9047ae877 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/appplus/pay/wechat/ThirdAppPlusPayDto.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/appplus/pay/wechat/ThirdAppPlusPayDto.java @@ -4,20 +4,25 @@ import com.bsd.pjcloud.thirdparty.constant.pay.ThirdPayOrderBizTypeEnum; import lombok.Data; /** - * app支付参数 + * 第三方渠道支付创建参数,仅由服务端支付编排层构造并传给具体渠道实现。 */ @Data public class ThirdAppPlusPayDto { + /** + * 收款商户主键,来源于服务端已校验的业务订单或商户资料。 + */ private Long mchId; /** - * 支付创建前由服务端解析的已绑定平台分佣关系主键;客户端不得提交或覆盖。 + * 支付创建前由服务端解析的平台分佣关系主键;客户端不得提交或覆盖。 + * 为空只表示本单关闭分账,不代表支付宝直付通可以省略 {@link #subMchid}。 */ private Long fzRelationId; /** - * 子商户id + * 订单创建时由服务端从默认关系复制的渠道二级商户标识。 + * 支付宝直付通无论是否分账都必须携带,客户端不得提交或覆盖。 */ private String subMchid; diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/model/pay/ThirdAggregatePayCreateContext.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/model/pay/ThirdAggregatePayCreateContext.java index 87f7767a8..03247a301 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/model/pay/ThirdAggregatePayCreateContext.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/model/pay/ThirdAggregatePayCreateContext.java @@ -38,7 +38,10 @@ public class ThirdAggregatePayCreateContext { */ private Long mchId; - /** 创建子订单时固定的平台分佣关系主键;由服务端在平台选择后填充。 */ + /** + * 创建子订单时固定的平台分佣关系主键,由服务端在平台选择后填充。 + * 为空只表示本单关闭分账,支付宝直付通仍必须从订单级渠道快照读取 smid/ip_role_id。 + */ private Long fzRelationId; /** diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdMchFzRelationService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdMchFzRelationService.java index 214f939fb..8c4619038 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdMchFzRelationService.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdMchFzRelationService.java @@ -32,7 +32,7 @@ public interface ThirdMchFzRelationService { /** * 删除关系;已绑定关系只标记为历史关系,删除默认关系后允许当前支付配置暂时没有默认关系。 - * 支付宝在没有默认关系时按普通支付处理;微信服务商支付仍需默认关系提供子商户路由。 + * 支付宝直付通和微信服务商支付都需要默认关系提供二级商户路由,缺失期间新支付将被拒绝。 * 历史订单继续使用原关系快照完成退款或退分账。 * * @param ids 关系主键列表 diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAggregateAliPayServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAggregateAliPayServiceImpl.java index c24397af1..f59434dc6 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAggregateAliPayServiceImpl.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAggregateAliPayServiceImpl.java @@ -156,11 +156,10 @@ public class ThirdAggregateAliPayServiceImpl implements ThirdAggregateAliPayServ model.setProductCode(PRODUCT_CODE); model.setTimeExpire(LocalDateTimeUtil.format(context.getExpireTime(), DatePattern.NORM_DATETIME_PATTERN)); boolean enableFz = context.getFzRelationId() != null; - if (enableFz) { - SubMerchant subMerchant = new SubMerchant(); - subMerchant.setMerchantId(aliPaySetting.getSubMchid()); - model.setSubMerchant(subMerchant); - } + // fzRelationId 只控制平台抽佣,直付通无论是否分账都必须携带二级商户标识。 + SubMerchant subMerchant = new SubMerchant(); + subMerchant.setMerchantId(aliPaySetting.getSubMchid()); + model.setSubMerchant(subMerchant); if (StrUtil.isNotBlank(identity.getOpenId())) { model.setBuyerOpenId(identity.getOpenId()); } else { @@ -171,7 +170,7 @@ public class ThirdAggregateAliPayServiceImpl implements ThirdAggregateAliPayServ AppPlusPayConfig.AliPayConfig aliPayConfig = appPlusPayConfig.getAliPayConfig(); request.setNotifyUrl(aliPayConfig.getBaseCallbackUrl() + aliPayConfig.getPayCallbackUrl()); request.setBizModel(model); - // 普通支付不携带关系专属授权;直付通仅在配置了代调用授权时发送 token。 + // 关闭分账不携带平台抽佣专属授权;开启分账且配置了代调用授权时才发送 token。 if (enableFz && StrUtil.isNotBlank(aliPaySetting.getAppAuthToken())) { request.putOtherTextParam(APP_AUTH_TOKEN_PARAM, aliPaySetting.getAppAuthToken()); } @@ -317,20 +316,20 @@ public class ThirdAggregateAliPayServiceImpl implements ThirdAggregateAliPayServ } /** - * 校验商户支付宝配置并解析可选 app_auth_token;普通支付允许配置 JSON 为空。 + * 校验订单创建时生成的支付宝渠道快照并解析可选 app_auth_token。 * * @param paySetting 当前商户启用的支付宝支付配置 - * @return 支付宝配置;无关系配置时返回空对象 + * @return 包含必填 smid/ip_role_id 的支付宝订单级渠道配置 */ private MchPaySettingEntity.AliPaySetting parseAliPaySetting(MchPaySettingVo paySetting) { - if (paySetting == null) { - throw new RuntimeException("商户支付宝支付配置不存在"); - } - if (StrUtil.isBlank(paySetting.getSettingJson())) { - return new MchPaySettingEntity.AliPaySetting(); + if (paySetting == null || StrUtil.isBlank(paySetting.getSettingJson())) { + throw new RuntimeException("商户支付宝支付配置不完整"); } MchPaySettingEntity.AliPaySetting setting = JSONUtil.toBean(paySetting.getSettingJson(), MchPaySettingEntity.AliPaySetting.class); - return setting == null ? new MchPaySettingEntity.AliPaySetting() : setting; + if (setting == null || StrUtil.isBlank(setting.getSubMchid())) { + throw new RuntimeException("商户支付宝直付通二级商户标识未配置"); + } + return setting; } /** 聚合支付底层再次校验分佣关系,防止绕过编排层直接创建无关系直付通订单。 */ diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAggregateOrderPayServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAggregateOrderPayServiceImpl.java index faa7cbcc5..f6557662c 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAggregateOrderPayServiceImpl.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAggregateOrderPayServiceImpl.java @@ -1208,7 +1208,7 @@ public class ThirdAggregateOrderPayServiceImpl implements ThirdAggregateOrderPay * 聚合支付选定平台后解析可选默认关系,并生成只属于当前子订单的渠道配置快照。 * *

默认关系始终提供渠道商户路由;只有关系明确开启分账时才写入分账关系主键。 - * 未配置默认关系时支付宝按普通支付创建子订单。

+ * 支付宝直付通没有默认关系时无法取得可信 smid/ip_role_id,必须拒绝创建子订单。

* * @param setting 已确认启用的支付配置 * @param context 聚合子订单创建上下文 @@ -1218,6 +1218,9 @@ public class ThirdAggregateOrderPayServiceImpl implements ThirdAggregateOrderPay ThirdMchFzRelationEntity relation = mchFzRelationService.findBoundDefault(setting); if (relation == null) { context.setFzRelationId(null); + if (ObjectUtil.equals(setting.getPlatformType(), AppPlusPlatformPayType.ALI_PAY.getType())) { + throw new IllegalStateException("商户未配置支付宝直付通二级商户关系"); + } return setting; } context.setFzRelationId(YesNoEnum.NO.getCode().equals(relation.getEnableFz()) ? null : relation.getId()); diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusAliPayServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusAliPayServiceImpl.java index d1ef3531f..8899c5704 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusAliPayServiceImpl.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusAliPayServiceImpl.java @@ -352,11 +352,10 @@ public class ThirdAppPlusAliPayServiceImpl implements ThirdAppPlusAliPayService model.setSubject(ORDER_CODE_SUBJECT); model.setProductCode(ORDER_CODE_PRODUCT_CODE); model.setTimeExpire(LocalDateTimeUtil.format(expireTime, DatePattern.NORM_DATETIME_PATTERN)); - if (enableFz) { - SubMerchant subMerchant = new SubMerchant(); - subMerchant.setMerchantId(aliPaySetting.getSubMchid()); - model.setSubMerchant(subMerchant); - } + // fzRelationId 只控制平台抽佣,直付通无论是否分账都必须携带二级商户标识。 + SubMerchant subMerchant = new SubMerchant(); + subMerchant.setMerchantId(aliPaySetting.getSubMchid()); + model.setSubMerchant(subMerchant); payOrder.setPayRep(JSONUtil.toJsonStr(model)); request.setBizModel(model); @@ -422,8 +421,8 @@ public class ThirdAppPlusAliPayServiceImpl implements ThirdAppPlusAliPayService /** * 创建支付宝 App 支付订单。 * - *

只有订单携带已绑定默认关系时才创建直付通请求;没有关系时按平台普通支付创建, - * 不发送二级商户、延迟结算或商户授权参数。

+ *

{@code fzRelationId} 只控制平台抽佣;直付通路由始终使用订单级关系快照中的 + * smid/ip_role_id,关闭分账也不得降级为不携带二级商户标识的普通支付。

* * @param dto 已由编排层校验的支付参数,金额单位为人民币分 * @param setting 当前商户启用的支付宝支付配置或关系快照 @@ -485,7 +484,7 @@ public class ThirdAppPlusAliPayServiceImpl implements ThirdAppPlusAliPayService model.setSubMerchant(subMerchant); SettleInfo directPaySettleInfo = new SettleInfo(); directPaySettleInfo.setSettlePeriodTime("1d"); - // 直付通延迟结算必须填写默认结算明细,普通支付不能携带该参数。 + // 直付通延迟结算必须填写默认结算明细,关闭平台抽佣不改变直付通结算模型。 ArrayList settleDetailInfoList = new ArrayList<>(); SettleDetailInfo defaultSettleDetailInfo = new SettleDetailInfo(); defaultSettleDetailInfo.setTransInType("defaultSettle"); @@ -496,6 +495,9 @@ public class ThirdAppPlusAliPayServiceImpl implements ThirdAppPlusAliPayService payOrder.setPayRep(JSONUtil.toJsonStr(model)); request.setBizModel(model); + if (enableFz) { + putAppAuthToken(request::putOtherTextParam, aliPaySetting); + } if (paymentOrderReservationService != null) { paymentOrderReservationService.reserveAppPay(payOrder); } @@ -1779,20 +1781,20 @@ public class ThirdAppPlusAliPayServiceImpl implements ThirdAppPlusAliPayService } /** - * 解析当前商户支付宝配置;普通支付允许没有关系专属配置。 + * 解析订单创建时生成的支付宝渠道快照,并校验直付通二级商户标识。 * * @param setting 当前商户支付配置 - * @return 支付宝商户授权配置;配置 JSON 为空时返回空配置对象 + * @return 包含必填 smid/ip_role_id 的支付宝订单级渠道配置 */ private MchPaySettingEntity.AliPaySetting parseAliPaySetting(MchPaySettingVo setting) { - if (setting == null) { - throw new RuntimeException("商户支付宝支付配置不存在"); - } - if (StrUtil.isBlank(setting.getSettingJson())) { - return new MchPaySettingEntity.AliPaySetting(); + if (setting == null || StrUtil.isBlank(setting.getSettingJson())) { + throw new RuntimeException("商户支付宝支付配置不完整"); } MchPaySettingEntity.AliPaySetting aliPaySetting = JSONUtil.toBean(setting.getSettingJson(), MchPaySettingEntity.AliPaySetting.class); - return aliPaySetting == null ? new MchPaySettingEntity.AliPaySetting() : aliPaySetting; + if (aliPaySetting == null || StrUtil.isBlank(aliPaySetting.getSubMchid())) { + throw new RuntimeException("商户支付宝直付通二级商户标识未配置"); + } + return aliPaySetting; } /** diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusServiceImpl.java index 69d40742a..e8dcbfb41 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusServiceImpl.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusServiceImpl.java @@ -746,7 +746,7 @@ public class ThirdAppPlusServiceImpl implements ThirdAppPlusService { * 解析可选默认分账关系并生成订单级渠道配置快照。 * *

默认关系始终提供渠道商户路由;只有关系明确开启分账时才把关系主键传给渠道服务。 - * 未配置默认关系时不携带渠道关系参数,支付宝按普通支付处理。

+ * 支付宝直付通没有默认关系时无法取得可信 smid/ip_role_id,必须拒绝下单。

* * @param paySetting 已由商户服务确认启用的支付配置 * @param payDto 即将提交渠道服务的内部支付参数 @@ -760,6 +760,9 @@ public class ThirdAppPlusServiceImpl implements ThirdAppPlusService { if (relation == null) { payDto.setFzRelationId(null); payDto.setSubMchid(null); + if (ObjectUtil.equals(paySetting.getPlatformType(), AppPlusPlatformPayType.ALI_PAY.getType())) { + throw new IllegalStateException("商户未配置支付宝直付通二级商户关系"); + } return paySetting; } payDto.setFzRelationId(YesNoEnum.NO.getCode().equals(relation.getEnableFz()) ? null : relation.getId()); diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdMchFzRelationServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdMchFzRelationServiceImpl.java index 99265488d..ccd73ee95 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdMchFzRelationServiceImpl.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdMchFzRelationServiceImpl.java @@ -473,7 +473,7 @@ public class ThirdMchFzRelationServiceImpl implements ThirdMchFzRelationService * 删除关系;已绑定关系保留为历史事实,只移出默认候选,避免历史订单无法退分账。 * *

删除默认关系不再强制寻找替代关系。删除后当前支付配置可以暂时没有默认关系, - * 支付宝新订单按普通支付处理;微信服务商支付仍需配置一条提供子商户路由的默认关系。 + * 但支付宝直付通和微信服务商支付都将在新下单时因缺少二级商户路由而拒付。 * 历史订单继续按原关系快照退款和退分账。

* * @param ids 关系主键 @@ -590,7 +590,7 @@ public class ThirdMchFzRelationServiceImpl implements ThirdMchFzRelationService } /** - * 查询支付配置当前可选的默认渠道关系;没有默认关系表示按渠道允许的普通支付处理。 + * 查询支付配置当前可选的默认渠道关系;没有默认关系时由具体渠道决定是否拒绝支付。 * * @param setting 商户支付配置 * @return 默认关系;关闭分账时不要求绑定,未配置默认关系时返回 {@code null} @@ -653,7 +653,7 @@ public class ThirdMchFzRelationServiceImpl implements ThirdMchFzRelationService snapshot.setSettingJson(JSONUtil.toJsonStr(channel)); } else if (ALIPAY_TYPE.equals(setting.getPlatformType())) { MchPaySettingEntity.AliPaySetting channel = new MchPaySettingEntity.AliPaySetting(); - // 关闭分账的支付宝关系只承担本地配置归属,不得把直付通商户或授权参数带入普通支付。 + // 关闭分账仍是直付通交易,smid 必须保留;仅平台抽佣专属的账号和授权令牌不再复制。 if (isProfitSharingEnabled(relation)) { channel.setAccountKey(relation.getAccountKey()); channel.setAppAuthToken(relation.getAppAuthToken()); diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdPayOrderServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdPayOrderServiceImpl.java index 48ba69e52..c7cf8db0a 100755 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdPayOrderServiceImpl.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdPayOrderServiceImpl.java @@ -18,14 +18,13 @@ import com.bsd.pjcloud.thirdparty.constant.miniplatform.AppPlusPlatformTypeEnum; import com.bsd.pjcloud.thirdparty.constant.pay.ThirdPayOrderBizTypeEnum; import com.bsd.pjcloud.thirdparty.dto.pay.ThirdPayOrderDto; import com.bsd.pjcloud.thirdparty.entity.pay.ThirdFzOrderEntity; -import com.bsd.pjcloud.thirdparty.entity.pay.ThirdMchFzRelationEntity; import com.bsd.pjcloud.thirdparty.entity.pay.ThirdPayOrderEntity; import com.bsd.pjcloud.thirdparty.mapper.pay.ThirdPayOrderMapper; import com.bsd.pjcloud.thirdparty.mapper.pay.ThirdPaymentTaskMapper; import com.bsd.pjcloud.thirdparty.service.ThirdFzOrderService; -import com.bsd.pjcloud.thirdparty.service.ThirdMchFzRelationService; import com.bsd.pjcloud.thirdparty.service.ThirdPayOrderService; import com.bsd.pjcloud.thirdparty.vo.pay.ThirdPayOrderVo; +import com.bsd.pjcloud.wallet.api.entity.mch.MchPaySettingEntity; import com.bsd.pjcloud.wallet.api.feign.RemoteMchService; import com.bsd.pjcloud.wallet.api.vo.MchManageVo; import lombok.RequiredArgsConstructor; @@ -67,10 +66,6 @@ public class ThirdPayOrderServiceImpl extends ServiceImpl requestCaptor = ArgumentCaptor.forClass(AlipayTradeCreateRequest.class); verify(alipayClient).execute(requestCaptor.capture()); - Assertions.assertFalse(requestCaptor.getValue().getTextParams().containsKey("app_auth_token")); + AlipayTradeCreateRequest request = requestCaptor.getValue(); + AlipayTradeCreateModel model = (AlipayTradeCreateModel) request.getBizModel(); + Assertions.assertEquals("smid-1001", model.getSubMerchant().getMerchantId()); + Assertions.assertFalse(request.getTextParams().containsKey("app_auth_token")); } /** diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusAliPayServiceImplTest.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusAliPayServiceImplTest.java index b0bb818e4..2a68283f9 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusAliPayServiceImplTest.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusAliPayServiceImplTest.java @@ -197,24 +197,23 @@ class ThirdAppPlusAliPayServiceImplTest { Assertions.assertFalse(requestCaptor.getValue().getTextParams().containsKey("app_auth_token")); } - /** 无默认关系时 App 支付必须走普通支付宝交易,不携带任何直付通或商户授权参数。 */ + /** 关闭分账的 App 直付通仍必须携带 smid,且订单不得写入分账关系主键。 */ @Test - void appPayShouldCreateOrdinaryOrderWithoutCommissionRelation() throws Exception { + void appPayShouldKeepSubMerchantWhenProfitSharingDisabled() throws Exception { payDto.setFzRelationId(null); - paySetting.setSettingJson(null); AlipayTradeAppPayResponse response = new AlipayTradeAppPayResponse(); - response.setBody("signed-ordinary-alipay-order"); + response.setBody("signed-direct-alipay-order"); when(alipayClient.sdkExecute(any(AlipayTradeAppPayRequest.class))).thenReturn(response); String result = service.appPay(payDto, paySetting); - Assertions.assertEquals("signed-ordinary-alipay-order", result); + Assertions.assertEquals("signed-direct-alipay-order", result); ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(AlipayTradeAppPayRequest.class); verify(alipayClient).sdkExecute(requestCaptor.capture()); AlipayTradeAppPayRequest request = requestCaptor.getValue(); AlipayTradeAppPayModel model = (AlipayTradeAppPayModel) request.getBizModel(); - Assertions.assertNull(model.getSubMerchant()); - Assertions.assertNull(model.getSettleInfo()); + Assertions.assertEquals("smid-1001", model.getSubMerchant().getMerchantId()); + Assertions.assertNotNull(model.getSettleInfo()); Assertions.assertFalse(request.getTextParams().containsKey("app_auth_token")); ArgumentCaptor orderCaptor = ArgumentCaptor.forClass(ThirdPayOrderEntity.class); verify(thirdPayOrderService).save(orderCaptor.capture()); @@ -595,6 +594,7 @@ class ThirdAppPlusAliPayServiceImplTest { Assertions.assertEquals("QR_CODE_OFFLINE", model.getProductCode()); Assertions.assertEquals("12.34", model.getTotalAmount()); Assertions.assertEquals("资安信消费支付", model.getSubject()); + Assertions.assertEquals("smid-1001", model.getSubMerchant().getMerchantId()); Assertions.assertEquals(LocalDateTimeUtil.format(expireTime, DatePattern.NORM_DATETIME_PATTERN), model.getTimeExpire()); Assertions.assertEquals("merchant-auth-token", request.getTextParams().get("app_auth_token")); Assertions.assertEquals("https://pay.example.com/third-app-pay/app/pay-callback", request.getNotifyUrl()); @@ -610,6 +610,27 @@ class ThirdAppPlusAliPayServiceImplTest { Assertions.assertTrue(savedOrder.getPayRes().contains("qrCode")); } + /** + * 验证关闭分账的订单码预创建仍携带直付通 smid,但不发送平台抽佣授权令牌。 + * + * @throws Exception Mock 支付宝客户端声明的受检异常 + */ + @Test + void precreateShouldKeepSubMerchantWhenProfitSharingDisabled() throws Exception { + payDto.setFzRelationId(null); + AlipayTradePrecreateResponse response = successPrecreateResponse("https://qr.alipay.com/no-profit-sharing"); + when(alipayClient.execute(any(AlipayTradePrecreateRequest.class))).thenReturn(response); + + service.precreate(payDto, paySetting, LocalDateTime.now().plusMinutes(2)); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(AlipayTradePrecreateRequest.class); + verify(alipayClient).execute(requestCaptor.capture()); + AlipayTradePrecreateRequest request = requestCaptor.getValue(); + AlipayTradePrecreateModel model = (AlipayTradePrecreateModel) request.getBizModel(); + Assertions.assertEquals("smid-1001", model.getSubMerchant().getMerchantId()); + Assertions.assertFalse(request.getTextParams().containsKey("app_auth_token")); + } + @Test void precreateShouldSaveFailedOrderWhenAlipayRejectsRequest() throws Exception { AlipayTradePrecreateResponse response = new AlipayTradePrecreateResponse(); diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusServiceImplOrderCodePayTest.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusServiceImplOrderCodePayTest.java index f46a5de00..0b4d0d44f 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusServiceImplOrderCodePayTest.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/appplus/ThirdAppPlusServiceImplOrderCodePayTest.java @@ -226,13 +226,33 @@ class ThirdAppPlusServiceImplOrderCodePayTest { verify(pollingCoordinator).wakeOrderCodePolling(); } - /** 未配置默认关系时支付宝订单码必须按普通支付创建,不写入分账关系主键。 */ + /** 未配置默认关系时无法取得可信 smid,支付宝直付通订单码必须在渠道调用前拒付。 */ @Test - void orderCodePayShouldCreateOrdinaryAlipayOrderWithoutDefaultRelation() { + void orderCodePayShouldRejectAlipayOrderWithoutDefaultRelation() { mockNoExistingOrder(); when(remoteMchService.getById(88L)).thenReturn(R.ok(merchant)); when(mchFzRelationService.findBoundDefault(any())).thenReturn(null); - ThirdOrderCodePayVo expected = orderCodePayVo("ordinary-pay-no", "ordinary-qr", LocalDateTime.now().plusMinutes(2)); + + IllegalStateException exception = Assertions.assertThrows(IllegalStateException.class, + () -> service.orderCodePay(request)); + + Assertions.assertTrue(exception.getMessage().contains("二级商户关系")); + verify(thirdMiniAppAliPayService, never()).precreate(any(), any(), any()); + verify(mchFzRelationService, never()).createPaymentSettingSnapshot(any(), any()); + } + + /** 关闭分账的支付宝默认关系仍提供 smid,但订单不得携带分账关系主键。 */ + @Test + void orderCodePayShouldUseAlipayRoutingRelationWithoutProfitSharing() { + mockNoExistingOrder(); + when(remoteMchService.getById(88L)).thenReturn(R.ok(merchant)); + ThirdMchFzRelationEntity routingRelation = new ThirdMchFzRelationEntity(); + routingRelation.setId(9003L); + routingRelation.setChannelSubMchId("smid-routing-1001"); + routingRelation.setEnableFz(YesNoEnum.NO.getCode()); + when(mchFzRelationService.findBoundDefault(any())).thenReturn(routingRelation); + ThirdOrderCodePayVo expected = orderCodePayVo("alipay-routing-pay-no", "alipay-routing-qr", + LocalDateTime.now().plusMinutes(2)); when(thirdMiniAppAliPayService.precreate(any(), any(), any())).thenReturn(expected); ThirdOrderCodePayVo result = service.orderCodePay(request); @@ -241,7 +261,9 @@ class ThirdAppPlusServiceImplOrderCodePayTest { ArgumentCaptor payDtoCaptor = ArgumentCaptor.forClass(ThirdAppPlusPayDto.class); verify(thirdMiniAppAliPayService).precreate(payDtoCaptor.capture(), any(), any()); Assertions.assertNull(payDtoCaptor.getValue().getFzRelationId()); - verify(mchFzRelationService, never()).createPaymentSettingSnapshot(any(), any()); + Assertions.assertEquals("smid-routing-1001", payDtoCaptor.getValue().getSubMchid()); + verify(mchFzRelationService).createPaymentSettingSnapshot(any(), + org.mockito.ArgumentMatchers.eq(routingRelation)); } /** 关闭分账的微信默认关系仍提供sub_mchid,但订单不得携带分账关系主键。 */ diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdMchFzRelationServiceImplTest.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdMchFzRelationServiceImplTest.java index f3a06c767..11e6132d3 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdMchFzRelationServiceImplTest.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/test/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdMchFzRelationServiceImplTest.java @@ -124,7 +124,7 @@ class ThirdMchFzRelationServiceImplTest { verify(taskEnqueuer, never()).enqueueRelationBind(any(), any(), any(), org.mockito.ArgumentMatchers.anyInt()); } - /** 未配置默认关系表示未启用分账,支付编排应收到空值并继续创建普通支付。 */ + /** 未配置默认关系时关系查询返回空值,具体支付渠道再按二级商户路由要求决定是否拒付。 */ @Test void findBoundDefaultShouldReturnNullWhenCommissionIsNotConfigured() { when(relationMapper.selectOne(any(Wrapper.class))).thenReturn(null); @@ -151,9 +151,9 @@ class ThirdMchFzRelationServiceImplTest { verify(taskEnqueuer, never()).enqueueRelationBind(any(), any(), any(), org.mockito.ArgumentMatchers.anyInt()); } - /** 关闭分账的支付宝关系生成普通支付快照,不携带直付通二级商户和授权令牌。 */ + /** 关闭分账的支付宝关系仍复制直付通 smid,但不复制平台抽佣专属账号和授权令牌。 */ @Test - void createPaymentSettingSnapshotShouldOmitAlipayRelationFieldsWhenDisabled() { + void createPaymentSettingSnapshotShouldKeepAlipaySmidWhenProfitSharingDisabled() { MchPaySettingVo setting = alipaySetting(102L, 5D, "legacy-setting-token"); ThirdMchFzRelationEntity relation = pendingAlipayRelation(7000L); relation.setEnableFz(YesNoEnum.NO.getCode()); @@ -163,7 +163,7 @@ class ThirdMchFzRelationServiceImplTest { MchPaySettingEntity.AliPaySetting channel = JSONUtil.toBean( snapshot.getSettingJson(), MchPaySettingEntity.AliPaySetting.class); Assertions.assertAll( - () -> Assertions.assertNull(channel.getSubMchid()), + () -> Assertions.assertEquals(relation.getChannelSubMchId(), channel.getSubMchid()), () -> Assertions.assertNull(channel.getAccountKey()), () -> Assertions.assertNull(channel.getAppAuthToken())); }