From cbb12ea13a6a562fd42c321fc9f2b252e9d4aad0 Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 14 Jul 2026 18:41:32 +0800 Subject: [PATCH] fix: route Aslan merchant recharge to freight wallet --- .../pay/web/PayWebH5OptionsQryExe.java | 19 ++-- .../pay/web/PayWebH5PlaceOrderCmdExe.java | 4 + .../pay/web/PayWebH5WalletTypeResolver.java | 67 ++++++++++++ .../pay/web/PayWebH5OptionsQryExeTest.java | 45 ++++++++ .../web/PayWebH5WalletTypeResolverTest.java | 102 ++++++++++++++++++ 5 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5WalletTypeResolver.java create mode 100644 rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/command/pay/web/PayWebH5OptionsQryExeTest.java create mode 100644 rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/command/pay/web/PayWebH5WalletTypeResolverTest.java diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5OptionsQryExe.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5OptionsQryExe.java index 1030ec19..1460dc01 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5OptionsQryExe.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5OptionsQryExe.java @@ -47,12 +47,15 @@ public class PayWebH5OptionsQryExe { private final SysCountryCodeClient sysCountryCodeClient; private final UserProfileAndCountryQueryExe userProfileAndCountryQueryExe; private final PayApplicationCommodityService payApplicationCommodityService; + private final PayWebH5WalletTypeResolver walletTypeResolver; public PayWebH5ContextCO context(PayWebUserCmd cmd) { defaultType(cmd); UserProfileAndCountryCO source = userProfileAndCountryQueryExe.h5Context(cmd); WebSiteUseProfileCO userProfile = source.getUserProfile(); ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_RECORD_INFO, userProfile); + PayWebH5WalletTypeResolver.WalletType walletType = walletTypeResolver.resolve( + userProfile.getId()); return new PayWebH5ContextCO() .setAuthMode("display_user_id") @@ -65,9 +68,9 @@ public class PayWebH5OptionsQryExe { .setSysOrigin(userProfile.getSysOrigin()) .setCountryName(userProfile.getCountryName()) .setCountryCode(userProfile.getCountryCode()) - .setFreightAgent(userProfile.getFreightAgent()) - .setSuperFreightAgent(userProfile.getSuperFreightAgent()) - .setAudienceType(resolveAudienceType(userProfile))) + .setFreightAgent(walletType.isFreightAgent()) + .setSuperFreightAgent(walletType.isSuperFreightAgent()) + .setAudienceType(walletType.getAudienceType())) .setCountryList(source.getCountryList()) .setRegionId(source.getRegionId()); } @@ -169,16 +172,6 @@ public class PayWebH5OptionsQryExe { return amount.multiply(BigDecimal.valueOf(scale)).setScale(0, RoundingMode.DOWN).longValue(); } - private String resolveAudienceType(WebSiteUseProfileCO userProfile) { - if (Boolean.TRUE.equals(userProfile.getSuperFreightAgent())) { - return "super_freight_agent"; - } - if (Boolean.TRUE.equals(userProfile.getFreightAgent())) { - return "coin_seller"; - } - return "normal"; - } - private void defaultType(PayWebUserCmd cmd) { if (Objects.isNull(cmd.getType())) { cmd.setType(PayApplicationCommodityType.GOLD); diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5PlaceOrderCmdExe.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5PlaceOrderCmdExe.java index 191371d6..127251be 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5PlaceOrderCmdExe.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5PlaceOrderCmdExe.java @@ -59,6 +59,7 @@ public class PayWebH5PlaceOrderCmdExe { private final InAppPurchaseDetailsService inAppPurchaseDetailsService; private final PayApplicationCommodityService payApplicationCommodityService; private final V5PayOrderStateService v5PayOrderStateService; + private final PayWebH5WalletTypeResolver walletTypeResolver; public PayWebH5OrderCO execute(PayWebH5PlaceOrderCmd cmd) { String providerCode = Objects.toString(cmd.getProviderCode(), "").trim() @@ -122,6 +123,9 @@ public class PayWebH5PlaceOrderCmdExe { Boolean.TRUE.equals(commodity.getShelf())); ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR, Objects.isNull(cmd.getType()) || Objects.equals(cmd.getType().name(), commodity.getType())); + // 以服务端钱包身份约束商品类型,防止旧 H5 或伪造请求把币商订单送入个人钱包。 + ResponseAssert.isTrue(ResponseErrorCode.REQUEST_PARAMETER_ERROR, + walletTypeResolver.matchesCommodityType(cmd.getUserId(), commodity.getType())); PayCountry payCountry = payCountryService.getById(cmd.getPayCountryId()); ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_RECORD_INFO, payCountry); diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5WalletTypeResolver.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5WalletTypeResolver.java new file mode 100644 index 00000000..1047ee45 --- /dev/null +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/PayWebH5WalletTypeResolver.java @@ -0,0 +1,67 @@ +package com.red.circle.order.app.command.pay.web; + +import com.red.circle.framework.core.asserts.ResponseAssert; +import com.red.circle.order.inner.model.enums.PayApplicationCommodityType; +import com.red.circle.wallet.inner.endpoint.freight.FreightGoldClient; +import com.red.circle.wallet.inner.model.dto.UserFreightBalanceDTO; +import java.util.Objects; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * Aslan H5 充值用户钱包类型解析器。 + * + * @author tf + */ +@Component +@RequiredArgsConstructor +public class PayWebH5WalletTypeResolver { + + private final FreightGoldClient freightGoldClient; + + /** + * 以币商钱包记录判定入账钱包,避免用户资料未填充币商标记时错充到个人金币钱包。 + */ + public WalletType resolve(Long userId) { + UserFreightBalanceDTO balance = ResponseAssert.requiredSuccess( + freightGoldClient.getByUserId(userId)); + if (Objects.isNull(balance) || !Boolean.FALSE.equals(balance.getClose())) { + return WalletType.NORMAL; + } + return Boolean.TRUE.equals(balance.getSuperDealer()) + ? WalletType.SUPER_FREIGHT + : WalletType.FREIGHT; + } + + /** + * 以当前有效钱包身份校验订单商品,防止币商订单生成个人金币快照。 + */ + public boolean matchesCommodityType(Long userId, String commodityType) { + return resolve(userId).getCommodityType().eq(commodityType); + } + + /** + * H5 币商和超级币商统一购买货运金币,支付回调才会进入币商钱包分支。 + */ + @Getter + public enum WalletType { + NORMAL("normal", false, false, PayApplicationCommodityType.GOLD), + FREIGHT("coin_seller", true, false, PayApplicationCommodityType.FREIGHT_GOLD), + SUPER_FREIGHT("super_freight_agent", true, true, + PayApplicationCommodityType.FREIGHT_GOLD); + + private final String audienceType; + private final boolean freightAgent; + private final boolean superFreightAgent; + private final PayApplicationCommodityType commodityType; + + WalletType(String audienceType, boolean freightAgent, boolean superFreightAgent, + PayApplicationCommodityType commodityType) { + this.audienceType = audienceType; + this.freightAgent = freightAgent; + this.superFreightAgent = superFreightAgent; + this.commodityType = commodityType; + } + } +} diff --git a/rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/command/pay/web/PayWebH5OptionsQryExeTest.java b/rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/command/pay/web/PayWebH5OptionsQryExeTest.java new file mode 100644 index 00000000..0336bf6e --- /dev/null +++ b/rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/command/pay/web/PayWebH5OptionsQryExeTest.java @@ -0,0 +1,45 @@ +package com.red.circle.order.app.command.pay.web; + +import com.red.circle.order.app.dto.clientobject.pay.PayWebH5ContextCO; +import com.red.circle.order.app.dto.clientobject.pay.UserProfileAndCountryCO; +import com.red.circle.order.app.dto.clientobject.pay.WebSiteUseProfileCO; +import com.red.circle.order.app.dto.cmd.PayWebUserCmd; +import com.red.circle.order.infra.database.rds.service.pay.PayApplicationCommodityService; +import com.red.circle.order.infra.database.rds.service.pay.PayApplicationService; +import com.red.circle.order.infra.database.rds.service.pay.PayCountryService; +import com.red.circle.other.inner.endpoint.sys.SysCountryCodeClient; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +public class PayWebH5OptionsQryExeTest { + + @Test + public void contextMustUseWalletResolverWhenUserProfileHasNoFreightFlags() { + UserProfileAndCountryQueryExe userQuery = Mockito.mock(UserProfileAndCountryQueryExe.class); + PayWebH5WalletTypeResolver walletTypeResolver = Mockito.mock( + PayWebH5WalletTypeResolver.class); + PayWebH5OptionsQryExe executor = new PayWebH5OptionsQryExe( + Mockito.mock(PayCountryService.class), + Mockito.mock(PayApplicationService.class), + Mockito.mock(SysCountryCodeClient.class), + userQuery, + Mockito.mock(PayApplicationCommodityService.class), + walletTypeResolver); + WebSiteUseProfileCO userProfile = new WebSiteUseProfileCO() + .setId(10001L) + .setAccount("7777") + .setFreightAgent(null) + .setSuperFreightAgent(null); + Mockito.when(userQuery.h5Context(Mockito.any())) + .thenReturn(new UserProfileAndCountryCO().setUserProfile(userProfile)); + Mockito.when(walletTypeResolver.resolve(10001L)) + .thenReturn(PayWebH5WalletTypeResolver.WalletType.SUPER_FREIGHT); + + PayWebH5ContextCO context = executor.context(new PayWebUserCmd()); + + Assert.assertEquals("super_freight_agent", context.getAccount().getAudienceType()); + Assert.assertTrue(context.getAccount().getFreightAgent()); + Assert.assertTrue(context.getAccount().getSuperFreightAgent()); + } +} diff --git a/rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/command/pay/web/PayWebH5WalletTypeResolverTest.java b/rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/command/pay/web/PayWebH5WalletTypeResolverTest.java new file mode 100644 index 00000000..e62d7be2 --- /dev/null +++ b/rc-service/rc-service-order/order-application/src/test/java/com/red/circle/order/app/command/pay/web/PayWebH5WalletTypeResolverTest.java @@ -0,0 +1,102 @@ +package com.red.circle.order.app.command.pay.web; + +import com.red.circle.framework.dto.ResultResponse; +import com.red.circle.order.inner.model.enums.PayApplicationCommodityType; +import com.red.circle.wallet.inner.endpoint.freight.FreightGoldClient; +import com.red.circle.wallet.inner.model.dto.UserFreightBalanceDTO; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class PayWebH5WalletTypeResolverTest { + + private static final Long USER_ID = 10001L; + + private FreightGoldClient freightGoldClient; + private PayWebH5WalletTypeResolver resolver; + + @Before + public void setUp() { + freightGoldClient = Mockito.mock(FreightGoldClient.class); + resolver = new PayWebH5WalletTypeResolver(freightGoldClient); + } + + @Test + public void activeFreightWalletMustUseFreightGold() { + mockBalance(new UserFreightBalanceDTO() + .setClose(false) + .setSuperDealer(false)); + + PayWebH5WalletTypeResolver.WalletType walletType = resolver.resolve(USER_ID); + + Assert.assertEquals("coin_seller", walletType.getAudienceType()); + Assert.assertTrue(walletType.isFreightAgent()); + Assert.assertFalse(walletType.isSuperFreightAgent()); + Assert.assertEquals(PayApplicationCommodityType.FREIGHT_GOLD, + walletType.getCommodityType()); + } + + @Test + public void activeSuperFreightWalletMustUseFreightGold() { + mockBalance(new UserFreightBalanceDTO() + .setClose(false) + .setSuperDealer(true)); + + PayWebH5WalletTypeResolver.WalletType walletType = resolver.resolve(USER_ID); + + Assert.assertEquals("super_freight_agent", walletType.getAudienceType()); + Assert.assertTrue(walletType.isFreightAgent()); + Assert.assertTrue(walletType.isSuperFreightAgent()); + Assert.assertEquals(PayApplicationCommodityType.FREIGHT_GOLD, + walletType.getCommodityType()); + } + + @Test + public void activeFreightWalletMustRejectPersonalGoldCommodity() { + mockBalance(new UserFreightBalanceDTO() + .setClose(false) + .setSuperDealer(false)); + + Assert.assertTrue(resolver.matchesCommodityType(USER_ID, "FREIGHT_GOLD")); + Assert.assertFalse(resolver.matchesCommodityType(USER_ID, "GOLD")); + } + + @Test + public void missingFreightWalletMustUsePersonalGold() { + mockBalance(null); + + PayWebH5WalletTypeResolver.WalletType walletType = resolver.resolve(USER_ID); + + Assert.assertEquals("normal", walletType.getAudienceType()); + Assert.assertFalse(walletType.isFreightAgent()); + Assert.assertEquals(PayApplicationCommodityType.GOLD, walletType.getCommodityType()); + } + + @Test + public void missingFreightWalletMustRejectFreightGoldCommodity() { + mockBalance(null); + + Assert.assertTrue(resolver.matchesCommodityType(USER_ID, "GOLD")); + Assert.assertFalse(resolver.matchesCommodityType(USER_ID, "FREIGHT_GOLD")); + } + + @Test + public void closedFreightWalletMustUsePersonalGold() { + mockBalance(new UserFreightBalanceDTO() + .setClose(true) + .setSuperDealer(true)); + + PayWebH5WalletTypeResolver.WalletType walletType = resolver.resolve(USER_ID); + + Assert.assertEquals("normal", walletType.getAudienceType()); + Assert.assertFalse(walletType.isFreightAgent()); + Assert.assertFalse(walletType.isSuperFreightAgent()); + Assert.assertEquals(PayApplicationCommodityType.GOLD, walletType.getCommodityType()); + } + + private void mockBalance(UserFreightBalanceDTO balance) { + Mockito.when(freightGoldClient.getByUserId(USER_ID)) + .thenReturn(ResultResponse.success(balance)); + } +}