From 002688eeeb837e94be3cd5b76c336dad430eb2d5 Mon Sep 17 00:00:00 2001 From: DaxPay Dev Date: Tue, 23 Jun 2026 13:56:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(channel):=20=E9=80=9A=E9=81=93=E8=BF=9E?= =?UTF-8?q?=E9=80=9A=E6=80=A7=20demo=20=E9=93=BE=E8=B7=AF,=20=E5=90=AB?= =?UTF-8?q?=E5=A3=B0=E6=98=8E=E5=BC=8F=E9=80=9A=E9=81=93=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E4=B8=8E=20traceparent=20=E9=80=8F=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _config/sql/iam_perm_menu.sql | 20 ++--- .../alipay/client/AlipayChannelClient.java | 20 +++++ .../alipay/config/AlipayClientConfig.java | 34 ++++++++ .../open/channel/alipay/dto/AlipayPayReq.java | 21 +++++ .../channel/alipay/dto/AlipayPayResp.java | 17 ++++ .../alipay/service/pay/AlipayPayService.java | 64 ++++++++++++-- .../douyin/client/DouyinChannelClient.java | 20 +++++ .../douyin/config/DouyinClientConfig.java | 35 ++++++++ .../open/channel/douyin/dto/DouyinPayReq.java | 21 +++++ .../channel/douyin/dto/DouyinPayResp.java | 17 ++++ .../strategy/DouyinDirectPayStrategy.java | 41 ++++++++- daxpay-demo/pom.xml | 8 ++ .../controller/ChannelDemoController.java | 85 +++++++++++++++++++ .../channel/param/ChannelDemoPayParam.java | 38 +++++++++ .../channel/result/ChannelDemoPayResult.java | 36 ++++++++ .../old/pay/channel/ChannelPayClient.java | 8 +- .../properties/DaxpayChannelProperties.java | 33 +++++++ .../properties/PlatformCommonProperties.java | 10 --- .../common-spring/pom.xml | 5 ++ .../RestClientConfiguration.java | 10 ++- 20 files changed, 504 insertions(+), 39 deletions(-) create mode 100644 daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/client/AlipayChannelClient.java create mode 100644 daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/config/AlipayClientConfig.java create mode 100644 daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayPayReq.java create mode 100644 daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayPayResp.java create mode 100644 daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/client/DouyinChannelClient.java create mode 100644 daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/config/DouyinClientConfig.java create mode 100644 daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dto/DouyinPayReq.java create mode 100644 daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dto/DouyinPayResp.java create mode 100644 daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/controller/ChannelDemoController.java create mode 100644 daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/param/ChannelDemoPayParam.java create mode 100644 daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/result/ChannelDemoPayResult.java create mode 100644 daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/DaxpayChannelProperties.java diff --git a/_config/sql/iam_perm_menu.sql b/_config/sql/iam_perm_menu.sql index 1b317b534..2c6eff59e 100644 --- a/_config/sql/iam_perm_menu.sql +++ b/_config/sql/iam_perm_menu.sql @@ -12,7 +12,7 @@ Target Server Version : 160009 (160009) File Encoding : 65001 - Date: 17/06/2026 18:12:21 + Date: 23/06/2026 09:16:48 */ @@ -103,7 +103,6 @@ INSERT INTO "public"."iam_perm_menu" VALUES (302, 3, NULL, 'admin', 'SystemLog', INSERT INTO "public"."iam_perm_menu" VALUES (30102, 301, 'iam:perm:menu', 'admin', 'SystemMenu', '菜单管理', 'Menu Management', 'menu.system.basic.menu', 'carbon:menu', 'f', 'f', '/iam/perm/menu/MenuList', '/system/basic/menu', NULL, 0, 'f', 't', 'f', 0, 1, 2, 'f', 'menu', NULL, NULL, NULL, NULL, '', '', '2026-03-20 03:11:13.134079+00', '2026-03-31 13:39:34.260955+00'); INSERT INTO "public"."iam_perm_menu" VALUES (30201, 302, 'starter:log:login', 'admin', 'SystemLoginLog', '登录日志', 'Login Log', 'menu.system.log.login', 'lucide:log-in', 'f', 'f', '/system/log/login/LoginLogList', '/system/log/login', NULL, 1, 'f', 't', 'f', 0, 1, 1, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-03-20 03:11:13.134079+00', '2026-03-30 15:25:09.855555+00'); INSERT INTO "public"."iam_perm_menu" VALUES (2, NULL, NULL, 'admin', 'Demos', '演示', 'Demos', 'menu.demos', 'ic:baseline-view-in-ar', 'f', 'f', NULL, '/demos', NULL, 1000, 'f', 't', 'f', 0, NULL, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-03-20 03:11:13.134079+00', '2026-03-20 03:11:13.134079+00'); -INSERT INTO "public"."iam_perm_menu" VALUES (201, 2, NULL, 'admin', 'AntDesignDemos', 'Antd Next演示', 'Antd Next Demo', 'menu.demos.antd', 'lucide:box', 'f', 'f', '/demos/antd/AntdDemo', '/demos/ant-design-next', NULL, 1, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-03-20 03:11:13.134079+00', '2026-03-20 03:11:13.134079+00'); INSERT INTO "public"."iam_perm_menu" VALUES (30202, 302, 'starter:log:operate', 'admin', 'SystemOperateLog', '操作日志', 'Operate Log', 'menu.system.log.operate', 'lucide:activity', 'f', 'f', '/system/log/operate/OperateLogList', '/system/log/operate', NULL, 2, 'f', 't', 'f', 0, 1, 1, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-03-20 03:11:13.134079+00', '2026-03-30 15:24:57.076166+00'); INSERT INTO "public"."iam_perm_menu" VALUES (1, NULL, NULL, 'admin', 'Dashboard', '仪表板', 'Dashboard', 'menu.dashboard', 'lucide:layout-dashboard', 'f', 'f', NULL, '/dashboard', '/analytics', -1, 'f', 'f', 'f', 0, NULL, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-03-20 03:11:13.134079+00', '2026-03-20 03:11:13.134079+00'); INSERT INTO "public"."iam_perm_menu" VALUES (30402, 304, 'system:platform:config', 'admin', 'PlatformConfig', '平台配置', 'Platform Config', 'menu.system.config.platform', 'lucide:settings', 'f', 'f', '/system/config/platform/PlatformConfig', '/system/config/platform', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-04-07 16:00:00+00', '2026-04-07 16:00:00+00'); @@ -116,13 +115,10 @@ INSERT INTO "public"."iam_perm_menu" VALUES (40401, 404, 'payment:merchant:info' INSERT INTO "public"."iam_perm_menu" VALUES (401, 4, 'payment:platform', 'admin', 'PaymentPlatform', '支付主数据', 'Payment Master Data', 'menu.payment.platform', 'lucide:building', 'f', 'f', NULL, '/payment/platform', NULL, 1, 'f', 't', 'f', 0, 1, 3, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-04-05 16:00:00+00', '2026-05-28 06:25:42.880461+00'); INSERT INTO "public"."iam_perm_menu" VALUES (203, 2, 'demos:region', 'admin', 'RegionCascaderDemo', '行政区划选择器', 'Region Cascader', 'menu.demos.region', 'lucide:map-pin', 'f', 'f', '/demos/region/RegionCascaderDemo', '/demos/region', NULL, 3, 'f', 't', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-04-24 16:00:00+00', '2026-04-24 16:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4040103, 40401, 'payment:merchant:manage:info', 'admin', 'MchInfoManage', '商户信息', 'Merchant Info', 'menu.payment.merchant.manage.info', NULL, 't', 'f', '/payment/merchant/manage/info/MchInfoManage', '/payment/merchant/manage/info', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-01 16:00:00+00', '2026-05-01 16:00:00+00'); -INSERT INTO "public"."iam_perm_menu" VALUES (4040104, 40401, 'payment:merchant:manage:entity', 'admin', 'MchEntityManage', '主体信息', 'Entity Info', 'menu.payment.merchant.manage.entity', NULL, 't', 'f', '/payment/merchant/manage/entity/MchEntityManage', '/payment/merchant/manage/entity', NULL, 4, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-01 16:00:00+00', '2026-05-01 16:00:00+00'); -INSERT INTO "public"."iam_perm_menu" VALUES (4040105, 40401, 'payment:merchant:manage:settle', 'admin', 'MchSettleManage', '结算账户', 'Settle Account', 'menu.payment.merchant.manage.settle', NULL, 't', 'f', '/payment/merchant/manage/settle/MchSettleManage', '/payment/merchant/manage/settle', NULL, 5, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-01 16:00:00+00', '2026-05-01 16:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4, NULL, 'payment:system', 'admin', 'PaymentSystem', '平台管理', 'Platform Management', 'menu.platform', 'lucide:credit-card', 'f', 'f', NULL, '/payment', NULL, 3, 'f', 't', 'f', 0, 1, 1, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-04-05 16:00:00+00', '2026-06-13 00:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4040102, 40401, 'payment:merchant:manage:credentialConfig', 'admin', 'MerchantCredentialConfig', '对接配置', 'Credential Config', 'menu.payment.merchant.manage.credentialConfig', NULL, 't', 'f', '/payment/merchant/manage/credential/MerchantCredentialConfig', '/payment/merchant/manage/credential', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-01 16:00:00+00', '2026-05-01 16:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4040101, 40401, 'payment:merchant:manage', 'admin', 'MerchantManage', '商户管理', 'Merchant Management', 'menu.payment.merchant.manage', '', 't', 'f', '/payment/merchant/manage/workbench/MerchantManage', '/payment/merchant/manage', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-04-13 16:00:00+00', '2026-05-02 06:57:49.052168+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4040106, 40401, 'payment:merchant:channelMerchant', 'admin', 'ChannelMerchant', '通道商户', 'Channel Merchant', 'menu.payment.merchant.channelMerchant', NULL, 't', 'f', '/payment/merchant/channel-merchant/ChannelMerchantList', '/payment/merchant/channel-merchant', NULL, 6, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-03 16:00:00+00', '2026-05-03 16:00:00+00'); -INSERT INTO "public"."iam_perm_menu" VALUES (4040107, 40401, 'payment:merchant:product', 'admin', 'MchProductConfig', '支付产品', 'Payment Product', 'menu.payment.merchant.product', NULL, 't', 'f', '/payment/merchant/product/MchProductConfig', '/payment/merchant/product', NULL, 7, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-03 16:00:00+00', '2026-05-03 16:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4040108, 40401, 'payment:merchant:user', 'admin', 'MerchantUser', '商户用户', 'Merchant User', 'menu.payment.merchant.user', '', 't', 'f', '/payment/merchant/user/MerchantUserList', '/payment/merchant/user', NULL, 8, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-04 16:00:00+00', '2026-05-04 16:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4040109, 40401, 'payment:merchant:channelMerchant:create', 'admin', 'ChannelMerchantCreate', '创建通道商户', 'Create Channel Merchant', 'menu.payment.merchant.channelMerchant.create', NULL, 't', 'f', '/payment/merchant/channel-merchant/ChannelMerchantCreate', '/payment/merchant/channel-merchant/create', NULL, 9, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-05 16:00:00+00', '2026-05-05 16:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4040111, 4040110, 'payment:merchant:app:payRoute', 'admin', 'PayRouteConfig', '通道路由', 'Channel Routing', 'menu.payment.merchant.app.payRoute', NULL, 't', 'f', '/payment/merchant/route/PayRouteConfig', '/payment/merchant/route', NULL, 2, 'f', 't', 'f', 1, 1, 1, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-24 22:23:46.483985+00', '2026-05-26 20:03:06.445803+00'); @@ -130,24 +126,24 @@ INSERT INTO "public"."iam_perm_menu" VALUES (4040110, 40401, 'payment:merchant:a INSERT INTO "public"."iam_perm_menu" VALUES (601, 6, NULL, 'admin', 'TransactionIndex', '功能开发中', 'Coming Soon', 'menu.trade.index', 'lucide:construction', 'f', 'f', '/_core/fallback/coming-soon', '/trade/index', NULL, 1, 'f', 'f', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-24 16:00:00+00', '2026-05-24 16:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (7, NULL, 'terminal:system', 'admin', 'TerminalManagement', '终端管理', 'Terminal Management', 'menu.terminal', 'lucide:smartphone', 'f', 'f', NULL, '/terminal', '/terminal/index', 4.5, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-24 16:00:00+00', '2026-05-24 16:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (701, 7, NULL, 'admin', 'TerminalIndex', '功能开发中', 'Coming Soon', 'menu.terminal.index', 'lucide:construction', 'f', 'f', '/_core/fallback/coming-soon', '/terminal/index', NULL, 1, 'f', 'f', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-24 16:00:00+00', '2026-05-24 16:00:00+00'); -INSERT INTO "public"."iam_perm_menu" VALUES (40103, 401, 'payment:platform:channel', 'admin', 'PayChannelList', '支付通道', 'Payment Channel', 'menu.payment.platform.channel', 'lucide:radio-tower', 'f', 'f', '/payment/masterdata/channel/PayChannelList', '/payment/platform/pay-channel', NULL, 0, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-28 16:00:00+00', '2026-05-28 16:00:00+00'); -INSERT INTO "public"."iam_perm_menu" VALUES (40104, 401, 'payment:platform:capability', 'admin', 'PayCapabilityList', '支付能力', 'Payment Capability', 'menu.payment.platform.capability', 'lucide:zap', 'f', 'f', '/payment/masterdata/capability/PayCapabilityList', '/payment/platform/pay-capability', NULL, 3, 'f', 't', 'f', 1, 1, 4, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-26 16:00:00+00', '2026-05-28 06:43:27.505831+00'); -INSERT INTO "public"."iam_perm_menu" VALUES (40101, 401, 'payment:platform:product', 'admin', 'ProductList', '支付产品', 'Payment Product', 'menu.payment.platform.product', 'lucide:package', 'f', 'f', '/payment/masterdata/product/PayProductList', '/payment/platform/product', NULL, 2, 'f', 't', 'f', 1, 1, 3, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-04-23 16:00:00+00', '2026-05-28 06:25:42.892396+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4040112, 40401, 'payment:merchant:channelMerchant:detail', 'admin', 'ChannelMerchantDetailDispatch', '通道商户详情', 'Channel Merchant Detail', 'menu.payment.merchant.channelMerchant.detail', NULL, 't', 'f', '/payment/merchant/channel-merchant/detail/ChannelMerchantDetailDispatch', '/payment/merchant/channel-merchant/detail', NULL, 10, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-07 19:48:34.936565+00', '2026-06-07 19:48:34.936565+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4040113, 40401, 'payment:merchant:channelMerchant:wechatApp', 'admin', 'WechatMchAppManage', '微信通道商户应用', 'WeChat Channel Merchant App', 'menu.payment.merchant.channelMerchant.wechatApp', NULL, 't', 'f', '/payment/channel/wechat/manage/mch/app/WechatMchAppManage', '/payment/merchant/channel-merchant/wechat-app-manage', NULL, 11, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-07 23:57:13.620205+00', '2026-06-07 23:57:13.620205+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4040114, 40401, 'payment:merchant:channelMerchant:alipayApp', 'admin', 'AlipayMchAppManage', '支付宝通道商户应用', 'Alipay Channel Merchant App', 'menu.payment.merchant.channelMerchant.alipayApp', NULL, 't', 'f', '/payment/channel/alipay/manage/mch/app/AlipayMchAppManage', '/payment/merchant/channel-merchant/alipay-app-manage', NULL, 12, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-11 22:28:11.274785+00', '2026-06-11 22:28:11.274785+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (40104, 401, 'payment:platform:capability', 'admin', 'PayCapabilityList', '支付能力', 'Payment Capability', 'menu.payment.platform.capability', 'lucide:zap', 'f', 'f', '/payment/masterdata/capability/PayCapabilityList', '/payment/platform/pay-capability', NULL, 3, 'f', 't', 'f', 1, 1, 4, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-26 16:00:00+00', '2026-05-28 06:43:27.505831+00'); INSERT INTO "public"."iam_perm_menu" VALUES (404, NULL, 'payment:merchant', 'admin', 'PaymentMerchant', '商户管理', 'Merchant Management', 'menu.payment.merchant', 'lucide:store', 'f', 'f', NULL, '/payment/merchant', NULL, 4, 't', 't', 'f', 0, NULL, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-04-05 16:00:00+00', '2026-06-13 00:00:00+00'); -INSERT INTO "public"."iam_perm_menu" VALUES (40102, 401, 'payment:platform:provider', 'admin', 'PayProviderList', '支付方式', 'Payment Method', 'menu.payment.platform.provider', 'lucide:list-tree', 'f', 'f', '/payment/masterdata/provider/PayProviderList', '/payment/platform/pay-method', NULL, 1, 'f', 't', 'f', 1, 1, 5, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-27 20:18:01.383008+00', '2026-05-30 08:21:19.464024+00'); -INSERT INTO "public"."iam_perm_menu" VALUES (30403, 304, 'system:credential:config', 'admin', 'PlatformCredentialConfig', '对接配置', 'Credential Config', 'menu.system.config.credential', 'lucide:key-round', 'f', 'f', '/system/config/credential/PlatformCredentialConfig', '/system/config/credential', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-13 00:00:00+00', '2026-06-13 00:00:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (40102, 401, 'payment:platform:provider', 'admin', 'PayProviderList', '支付方式', 'Payment Method', 'menu.payment.platform.provider', 'lucide:list-tree', 'f', 'f', '/payment/masterdata/provider/PayMethodList', '/payment/platform/pay-method', NULL, 1, 'f', 't', 'f', 1, 1, 5, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-27 20:18:01.383008+00', '2026-05-30 08:21:19.464024+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (40103, 401, 'payment:platform:channel', 'admin', 'PayChannelList', '支付通道', 'Payment Channel', 'menu.payment.platform.channel', 'lucide:radio-tower', 'f', 'f', '/payment/masterdata/channel/PayChannelList', '/payment/platform/pay-channel', NULL, 0, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-05-28 16:00:00+00', '2026-05-28 16:00:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (40101, 401, 'payment:platform:product', 'admin', 'ProductList', '支付产品', 'Payment Product', 'menu.payment.platform.product', 'lucide:package', 'f', 'f', '/payment/masterdata/product/PayProductList', '/payment/platform/product', NULL, 2, 'f', 't', 'f', 1, 1, 3, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-04-23 16:00:00+00', '2026-05-28 06:25:42.892396+00'); INSERT INTO "public"."iam_perm_menu" VALUES (405, 4, 'payment:config', 'admin', 'PaymentConfig', '支付配置', 'Payment Config', 'menu.payment.config', 'lucide:settings-2', 'f', 'f', NULL, '/payment/config', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (40105, 405, 'payment:config:productConfig', 'admin', 'ProductConfig', '支付产品管理', 'Payment Product Management', 'menu.payment.config.productConfig', 'lucide:layout-grid', 'f', 'f', '/payment/config/product/ProductConfig', '/payment/config/product', NULL, 1, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-13 00:00:00+00', '2026-06-13 09:14:10.179605+00'); INSERT INTO "public"."iam_perm_menu" VALUES (40501, 405, 'payment:config:productDetail', 'admin', 'ProductDetailDispatch', '支付产品详情', 'Payment Product Detail', 'menu.payment.config.productDetail', NULL, 't', 'f', '/payment/config/product/detail/ProductDetailDispatch', '/payment/product-detail', NULL, 2, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (40502, 405, 'payment:alipay:isvApp', 'admin', 'AlipayIsvAppManage', '支付宝服务商应用', 'Alipay ISV Application', 'menu.payment.alipay.isvApp', NULL, 't', 'f', '/payment/channel/alipay/manage/app/AlipayIsvAppManage', '/payment/config/product/app-manage', NULL, 3, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (40503, 405, 'payment:wechat:isvApp', 'admin', 'WechatIsvAppManage', '微信服务商应用', 'WeChat ISV Application', 'menu.payment.wechat.isvApp', NULL, 't', 'f', '/payment/channel/wechat/manage/app/WechatIsvAppManage', '/payment/config/product/wechat-app-manage', NULL, 4, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-14 00:00:00+00', '2026-06-14 00:00:00+00'); INSERT INTO "public"."iam_perm_menu" VALUES (4040115, 40401, 'payment:merchant:channelMerchant:douyinApp', 'admin', 'DouyinMchAppManage', '抖音通道商户应用', 'Douyin Channel Merchant App', 'menu.payment.merchant.channelMerchant.douyinApp', NULL, 't', 'f', '/payment/channel/douyin/manage/app/DouyinMchAppManage', '/payment/merchant/channel-merchant/douyin-app-manage', NULL, 13, 'f', 't', 'f', 1, 1, 0, 'f', 'subpage', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-15 16:00:00+00', '2026-06-15 16:00:00+00'); -INSERT INTO "public"."iam_perm_menu" VALUES (4040116, 401, 'payment:platform:provider', 'admin', 'PayProviderManage', '支付渠道', 'Payment Provider', 'menu.payment.platform.provider.manage', 'lucide:wallet', 'f', 'f', '/payment/masterdata/provider/PayProviderManage', '/payment/platform/pay-provider/manage', NULL, 0.5, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-17 10:00:31.640482+00', '2026-06-17 10:00:31.640482+00'); -INSERT INTO "public"."iam_perm_menu" VALUES (4040117, 401, 'payment:platform:provider', 'admin', 'PayProviderManage', '支付渠道', 'Payment Provider', 'menu.payment.platform.provider.manage', 'lucide:wallet', 'f', 'f', '/payment/masterdata/provider/PayProviderManage', '/payment/platform/pay-provider', NULL, 0.5, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-17 10:10:32.614861+00', '2026-06-17 10:10:32.614861+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (4040116, 401, 'payment:platform:provider', 'admin', 'PayProviderManage', '支付渠道', 'Payment Provider', 'menu.payment.platform.provider.manage', 'lucide:wallet', 'f', 'f', '/payment/masterdata/provider/PayProviderList', '/payment/platform/pay-provider', NULL, 0.5, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-17 10:00:31.640482+00', '2026-06-17 10:00:31.640482+00'); INSERT INTO "public"."iam_perm_menu" VALUES (204, 2, 'demos:artemis', 'admin', 'ArtemisDemo', '消息队列演示', 'Message Queue Demo', 'menu.demos.artemis', 'lucide:send', 'f', 'f', '/demos/artemis/ArtemisDemo', '/demos/artemis', NULL, 4, 'f', 't', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-18 00:00:00+00', '2026-06-18 00:00:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (900001, 304, 'iam:social:config', 'admin', 'SocialConfig', '三方平台登录', 'Third-party Platform Login', 'menu.system.config.social', 'lucide:share-2', 'f', 'f', 'views/iam/social/social-config', '/system/config/social', NULL, 5, 'f', 't', 'f', 1, 1, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO "public"."iam_perm_menu" VALUES (205, 2, 'demos:channel', 'admin', 'ChannelDemo', '通道连通性演示', 'Channel Connectivity Demo', 'menu.demos.channel', 'lucide:radio-tower', 'f', 'f', '/demos/channel/ChannelDemo', '/demos/channel', NULL, 5, 'f', 't', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-06-23 00:00:00+00', '2026-06-23 00:00:00+00'); -- ---------------------------- -- Primary Key structure for table iam_perm_menu diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/client/AlipayChannelClient.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/client/AlipayChannelClient.java new file mode 100644 index 000000000..f7e061c1a --- /dev/null +++ b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/client/AlipayChannelClient.java @@ -0,0 +1,20 @@ +package cn.daxpay.open.channel.alipay.client; + +import cn.daxpay.open.channel.alipay.dto.AlipayPayReq; +import cn.daxpay.open.channel.alipay.dto.AlipayPayResp; +import cn.daxpay.open.payment.common.result.DaxResult; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.service.annotation.HttpExchange; +import org.springframework.web.service.annotation.PostExchange; + +/// # 支付宝通道客户端 +/// +/// 声明式 HTTP 接口, 调用子应用 dax-pay-channel-one 的支付宝通道接口。 +@HttpExchange(accept = MediaType.APPLICATION_JSON_VALUE, contentType = MediaType.APPLICATION_JSON_VALUE) +public interface AlipayChannelClient { + + /// 支付下单 + @PostExchange("/channel/pay") + DaxResult pay(@RequestBody AlipayPayReq req); +} diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/config/AlipayClientConfig.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/config/AlipayClientConfig.java new file mode 100644 index 000000000..b21301256 --- /dev/null +++ b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/config/AlipayClientConfig.java @@ -0,0 +1,34 @@ +package cn.daxpay.open.channel.alipay.config; + +import cn.daxpay.open.channel.alipay.client.AlipayChannelClient; +import cn.daxpay.open.platform.common.config.properties.DaxpayChannelProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.support.RestClientAdapter; +import org.springframework.web.service.invoker.HttpServiceProxyFactory; + +/// +/// 复用全局 [RestClient](由 common-spring 的 RestClientConfiguration 提供, 已预装: +/// OTel traceparent 透传 / BusinessContextInterceptor 业务上下文透传 / Apache HttpClient5 连接池), +/// 经 `mutate()` 派生出绑定 channel-one baseUrl 的实例。 +/// +/// baseUrl 从 [DaxpayChannelProperties] 统一读取。 +@Configuration +public class AlipayClientConfig { + + @Bean + public AlipayChannelClient alipayChannelClient( + RestClient restClient, + DaxpayChannelProperties channelProperties) { + // mutate 派生: 继承全局 RestClient 的拦截器 / observation / requestFactory, 仅覆盖 baseUrl + RestClient channelClient = restClient + .mutate() + .baseUrl(channelProperties.getOne().getBaseUrl()) + .build(); + return HttpServiceProxyFactory + .builderFor(RestClientAdapter.create(channelClient)) + .build() + .createClient(AlipayChannelClient.class); + } +} diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayPayReq.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayPayReq.java new file mode 100644 index 000000000..a9e46bab6 --- /dev/null +++ b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayPayReq.java @@ -0,0 +1,21 @@ +package cn.daxpay.open.channel.alipay.dto; + +import lombok.Data; + +import java.util.Map; + +/// # 支付宝通道支付请求 +/// +/// 与子应用 ChannelPayReq 字段一致, 通过 Jackson 序列化传输 +@Data +public class AlipayPayReq { + private String channel; + private String bizOrderNo; + private Long amount; + private String subject; + private String description; + private String method; + private String expireTime; + private String otherMethod; + private Map config; +} diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayPayResp.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayPayResp.java new file mode 100644 index 000000000..1db0fa187 --- /dev/null +++ b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayPayResp.java @@ -0,0 +1,17 @@ +package cn.daxpay.open.channel.alipay.dto; + +import lombok.Data; + +/// # 支付宝通道支付响应 +/// +/// 与子应用 ChannelPayResp 字段一致 +@Data +public class AlipayPayResp { + private String bizOrderNo; + private String outOrderNo; + private String transOrderNo; + private String payBody; + private String payBodyType; + private Boolean complete; + private String finishTime; +} diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/pay/AlipayPayService.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/pay/AlipayPayService.java index 5542442c6..0270bdc0a 100644 --- a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/pay/AlipayPayService.java +++ b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/pay/AlipayPayService.java @@ -1,24 +1,35 @@ package cn.daxpay.open.channel.alipay.service.pay; +import cn.daxpay.open.channel.alipay.client.AlipayChannelClient; +import cn.daxpay.open.channel.alipay.dto.AlipayPayReq; +import cn.daxpay.open.channel.alipay.dto.AlipayPayResp; +import cn.daxpay.open.payment.common.result.DaxResult; import cn.daxpay.open.payment.pay.bo.PayTradeResultBo; import cn.daxpay.open.payment.pay.order.entity.PayTrade; import cn.daxpay.open.payment.unipay.param.trade.pay.PayParam; +import cn.daxpay.open.platform.core.enums.unipay.PayBodyTypeEnum; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; import java.util.Map; /// # 支付宝支付执行业务服务 /// -/// 支付方式映射、通道请求组装、通道适配服务调用、支付结果转换。 -/// TODO Service 当前为占空实现, 后续补全支付执行流程逻辑。 -/// +/// 通过 [AlipayChannelClient] 调用子应用 dax-pay-channel-one 完成支付宝支付下单。 +/// 请求构建、响应解析全部在本类中完成。 @Slf4j @Service @RequiredArgsConstructor public class AlipayPayService { + private static final BigDecimal HUNDRED = new BigDecimal("100"); + + private final AlipayChannelClient alipayChannelClient; + /// 执行支付宝支付 /// /// @param order 支付订单 @@ -26,7 +37,50 @@ public class AlipayPayService { /// @param config 通道调用配置(密钥/证书/授权令牌等) /// @return 支付结果 public PayTradeResultBo pay(PayTrade order, PayParam payParam, Map config) { - // TODO 后续实现: method映射 → 组装ChannelPayReq → channelPayClient.pay → 结果转换 - throw new UnsupportedOperationException("AlipayPayService.pay 尚未实现"); + // 构建请求 + AlipayPayReq req = new AlipayPayReq(); + req.setChannel("alipay"); + req.setBizOrderNo(payParam.getBizOrderNo()); + req.setAmount(payParam.getAmount().multiply(HUNDRED).longValue()); + req.setSubject(payParam.getTitle()); + req.setDescription(payParam.getDescription()); + req.setMethod(payParam.getMethod()); + req.setConfig(config); + + // 调用子应用 + DaxResult result = alipayChannelClient.pay(req); + if (result.getCode() != 0) { + throw new IllegalStateException("支付宝通道支付失败: " + result.getMsg()); + } + + return toPayResult(result.getData()); + } + + /// 解析子应用响应为支付结果 BO + private PayTradeResultBo toPayResult(AlipayPayResp resp) { + PayTradeResultBo bo = new PayTradeResultBo() + .setOutOrderNo(resp.getOutOrderNo()) + .setComplete(Boolean.TRUE.equals(resp.getComplete())) + .setPayBody(resp.getPayBody()) + .setTransOrderNo(resp.getTransOrderNo()); + + // 支付参数体类型 + String payBodyType = resp.getPayBodyType(); + if (payBodyType != null) { + for (PayBodyTypeEnum type : PayBodyTypeEnum.values()) { + if (type.getCode().equals(payBodyType)) { + bo.setPayBodyType(type); + break; + } + } + } + + // 完成时间 + String finishTime = resp.getFinishTime(); + if (finishTime != null && !finishTime.isBlank()) { + bo.setFinishTime(OffsetDateTime.parse(finishTime, DateTimeFormatter.ISO_DATE_TIME)); + } + + return bo; } } diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/client/DouyinChannelClient.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/client/DouyinChannelClient.java new file mode 100644 index 000000000..9eaf50799 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/client/DouyinChannelClient.java @@ -0,0 +1,20 @@ +package cn.daxpay.open.channel.douyin.client; + +import cn.daxpay.open.channel.douyin.dto.DouyinPayReq; +import cn.daxpay.open.channel.douyin.dto.DouyinPayResp; +import cn.daxpay.open.payment.common.result.DaxResult; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.service.annotation.HttpExchange; +import org.springframework.web.service.annotation.PostExchange; + +/// # 抖音通道客户端 +/// +/// 声明式 HTTP 接口, 调用子应用 dax-pay-channel-one 的抖音通道接口。 +@HttpExchange(accept = MediaType.APPLICATION_JSON_VALUE, contentType = MediaType.APPLICATION_JSON_VALUE) +public interface DouyinChannelClient { + + /// 支付下单 + @PostExchange("/channel/pay") + DaxResult pay(@RequestBody DouyinPayReq req); +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/config/DouyinClientConfig.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/config/DouyinClientConfig.java new file mode 100644 index 000000000..d2fc788e1 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/config/DouyinClientConfig.java @@ -0,0 +1,35 @@ +package cn.daxpay.open.channel.douyin.config; + +import cn.daxpay.open.channel.douyin.client.DouyinChannelClient; +import cn.daxpay.open.platform.common.config.properties.DaxpayChannelProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.support.RestClientAdapter; +import org.springframework.web.service.invoker.HttpServiceProxyFactory; + +/// # 抖音通道客户端配置 +/// +/// 复用全局 [RestClient](由 common-spring 的 RestClientConfiguration 提供, 已预装: +/// OTel traceparent 透传 / BusinessContextInterceptor 业务上下文透传 / Apache HttpClient5 连接池), +/// 经 `mutate()` 派生出绑定 channel-one baseUrl 的实例。 +/// +/// baseUrl 从 [DaxpayChannelProperties] 统一读取。 +@Configuration +public class DouyinClientConfig { + + @Bean + public DouyinChannelClient douyinChannelClient( + RestClient restClient, + DaxpayChannelProperties channelProperties) { + // mutate 派生: 继承全局 RestClient 的拦截器 / observation / requestFactory, 仅覆盖 baseUrl + RestClient channelClient = restClient + .mutate() + .baseUrl(channelProperties.getOne().getBaseUrl()) + .build(); + return HttpServiceProxyFactory + .builderFor(RestClientAdapter.create(channelClient)) + .build() + .createClient(DouyinChannelClient.class); + } +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dto/DouyinPayReq.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dto/DouyinPayReq.java new file mode 100644 index 000000000..baed63c39 --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dto/DouyinPayReq.java @@ -0,0 +1,21 @@ +package cn.daxpay.open.channel.douyin.dto; + +import lombok.Data; + +import java.util.Map; + +/// # 抖音通道支付请求 +/// +/// 与子应用 ChannelPayReq 字段一致, 通过 Jackson 序列化传输 +@Data +public class DouyinPayReq { + private String channel; + private String bizOrderNo; + private Long amount; + private String subject; + private String description; + private String method; + private String expireTime; + private String otherMethod; + private Map config; +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dto/DouyinPayResp.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dto/DouyinPayResp.java new file mode 100644 index 000000000..c9376639a --- /dev/null +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/dto/DouyinPayResp.java @@ -0,0 +1,17 @@ +package cn.daxpay.open.channel.douyin.dto; + +import lombok.Data; + +/// # 抖音通道支付响应 +/// +/// 与子应用 ChannelPayResp 字段一致 +@Data +public class DouyinPayResp { + private String bizOrderNo; + private String outOrderNo; + private String transOrderNo; + private String payBody; + private String payBodyType; + private Boolean complete; + private String finishTime; +} diff --git a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/strategy/DouyinDirectPayStrategy.java b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/strategy/DouyinDirectPayStrategy.java index 5b7c39262..37f5ee377 100644 --- a/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/strategy/DouyinDirectPayStrategy.java +++ b/daxpay-channel/daxpay-channel-douyin/src/main/java/cn/daxpay/open/channel/douyin/strategy/DouyinDirectPayStrategy.java @@ -1,21 +1,32 @@ package cn.daxpay.open.channel.douyin.strategy; -import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum; +import cn.daxpay.open.channel.douyin.client.DouyinChannelClient; +import cn.daxpay.open.channel.douyin.dto.DouyinPayReq; +import cn.daxpay.open.channel.douyin.dto.DouyinPayResp; +import cn.daxpay.open.payment.common.result.DaxResult; import cn.daxpay.open.payment.pay.bo.PayTradeResultBo; import cn.daxpay.open.payment.strategy.pay.AbsPayStrategy; +import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.math.BigDecimal; +import java.util.Map; + /// # 抖音支付直连支付策略 /// -/// 抖音支付直连模式的支付策略(虚类,暂未实现具体支付逻辑)。 -/// +/// 通过 [DouyinChannelClient] 调用子应用 dax-pay-channel-one 完成抖音支付下单。 +/// TODO 通道配置获取逻辑待补充(需抖音通道配置管理实体) @Slf4j @Service @RequiredArgsConstructor public class DouyinDirectPayStrategy extends AbsPayStrategy { + private static final BigDecimal HUNDRED = new BigDecimal("100"); + + private final DouyinChannelClient douyinChannelClient; + @Override public ProductEnum getProduct() { return ProductEnum.DOUYIN_PAY; @@ -23,6 +34,28 @@ public class DouyinDirectPayStrategy extends AbsPayStrategy { @Override public PayTradeResultBo doPayHandler() { - throw new UnsupportedOperationException("抖音支付直连暂未实现"); + // 构建请求 + DouyinPayReq req = new DouyinPayReq(); + req.setChannel(ProductEnum.DOUYIN_PAY.getChannel()); + req.setBizOrderNo(getPayParam().getBizOrderNo()); + req.setAmount(getPayParam().getAmount().multiply(HUNDRED).longValue()); + req.setSubject(getPayParam().getTitle()); + req.setMethod(getPayParam().getMethod()); + // TODO 从数据库获取抖音通道配置 + req.setConfig(Map.of()); + + // 调用子应用 + DaxResult result = douyinChannelClient.pay(req); + if (result.getCode() != 0) { + throw new IllegalStateException("抖音通道支付失败: " + result.getMsg()); + } + + // 解析响应 + DouyinPayResp data = result.getData(); + return new PayTradeResultBo() + .setOutOrderNo(data.getOutOrderNo()) + .setComplete(Boolean.TRUE.equals(data.getComplete())) + .setPayBody(data.getPayBody()) + .setTransOrderNo(data.getTransOrderNo()); } } diff --git a/daxpay-demo/pom.xml b/daxpay-demo/pom.xml index d54ca80c2..6a7680c33 100644 --- a/daxpay-demo/pom.xml +++ b/daxpay-demo/pom.xml @@ -35,6 +35,14 @@ common-artemis ${project.version} + + + + + cn.daxpay.open + daxpay-channel-alipay + ${project.version} + diff --git a/daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/controller/ChannelDemoController.java b/daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/controller/ChannelDemoController.java new file mode 100644 index 000000000..eb000924d --- /dev/null +++ b/daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/controller/ChannelDemoController.java @@ -0,0 +1,85 @@ +package cn.daxpay.open.demo.channel.controller; + +import cn.daxpay.open.channel.alipay.client.AlipayChannelClient; +import cn.daxpay.open.channel.alipay.dto.AlipayPayReq; +import cn.daxpay.open.channel.alipay.dto.AlipayPayResp; +import cn.daxpay.open.demo.channel.param.ChannelDemoPayParam; +import cn.daxpay.open.demo.channel.result.ChannelDemoPayResult; +import cn.daxpay.open.platform.core.annotation.IgnoreAuth; +import cn.daxpay.open.platform.core.rest.Res; +import cn.daxpay.open.platform.core.rest.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.math.BigDecimal; +import java.util.Map; + +/// # 通道连通性 Demo 接口 +/// +/// 演示主应用通过 @HttpExchange 客户端调用子应用 dax-pay-channel-one 的完整链路。 +/// 前端发起请求 → 主应用构建 AlipayPayReq → AlipayChannelClient 发送 HTTP → 子应用接收并处理 → 响应返回。 +/// +/// 鉴权: URL 前缀 `/demo/**` 已在白名单, 类上叠加 `@IgnoreAuth` 双保险。 +@Slf4j +@IgnoreAuth +@Validated +@Tag(name = "通道连通性演示") +@RestController +@RequestMapping("/demo/channel") +@RequiredArgsConstructor +public class ChannelDemoController { + + private static final BigDecimal HUNDRED = new BigDecimal("100"); + + private final AlipayChannelClient alipayChannelClient; + + /// 发起 Demo 支付请求到子应用 + /// + /// 通过 @HttpExchange 声明式客户端将请求转发到 dax-pay-channel-one 的 `/channel/pay` 端点。 + /// config 传空 Map, 子应用进入 Demo 模式返回模拟响应。 + @Operation(summary = "发起通道 Demo 支付") + @PostMapping("/pay") + public Result pay(@Validated @RequestBody ChannelDemoPayParam param) { + log.info("🚀 Demo: 主应用发起通道支付请求, bizOrderNo={}", param.getBizOrderNo()); + + // 构建通道请求 + AlipayPayReq req = new AlipayPayReq(); + req.setChannel("alipay"); + req.setBizOrderNo(param.getBizOrderNo()); + req.setAmount(param.getAmount().multiply(HUNDRED).longValue()); + req.setSubject(param.getSubject()); + req.setMethod(param.getMethod()); + // 空配置 → 子应用进入 Demo 模式 + req.setConfig(Map.of()); + + // 调用子应用 (通过 @HttpExchange, OTel 自动透传 traceId) + var channelResult = alipayChannelClient.pay(req); + if (channelResult.getCode() != 0) { + throw new RuntimeException("通道支付失败: " + channelResult.getMsg()); + } + + AlipayPayResp resp = channelResult.getData(); + + // 构建返回结果 (包含双端 traceId 用于链路对照) + ChannelDemoPayResult result = new ChannelDemoPayResult(); + result.setBizOrderNo(resp.getBizOrderNo()); + result.setOutOrderNo(resp.getOutOrderNo()); + result.setPayBody(resp.getPayBody()); + result.setPayBodyType(resp.getPayBodyType()); + result.setMainAppTraceId(MDC.get("traceId")); + result.setSubAppTraceId(channelResult.getTraceId()); + + log.info("✅ Demo: 收到子应用响应, outOrderNo={}, subAppTraceId={}", + result.getOutOrderNo(), result.getSubAppTraceId()); + + return Res.ok(result); + } +} diff --git a/daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/param/ChannelDemoPayParam.java b/daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/param/ChannelDemoPayParam.java new file mode 100644 index 000000000..a8d26a176 --- /dev/null +++ b/daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/param/ChannelDemoPayParam.java @@ -0,0 +1,38 @@ +package cn.daxpay.open.demo.channel.param; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; + +/// # 通道连通性 Demo 支付参数 +/// +/// 前端表单提交的简化参数, 不涉及真实支付订单实体。 +@Data +@Schema(description = "通道 Demo 支付参数") +public class ChannelDemoPayParam { + + /// 商户订单号 + @Schema(description = "商户订单号") + @NotBlank(message = "{validation.field.bizOrderNo.notBlank}") + private String bizOrderNo; + + /// 支付金额(元) + @Schema(description = "支付金额(元)") + @NotNull(message = "{validation.field.amount.notNull}") + @DecimalMin(value = "0.01", message = "{validation.field.amount.min}") + private BigDecimal amount; + + /// 支付标题 + @Schema(description = "支付标题") + @NotBlank(message = "{validation.field.subject.notBlank}") + private String subject; + + /// 支付方式(alipay_wap / alipay_app / alipay_page / alipay_qr) + @Schema(description = "支付方式", example = "alipay_qr") + @NotBlank(message = "{validation.field.method.notBlank}") + private String method; +} diff --git a/daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/result/ChannelDemoPayResult.java b/daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/result/ChannelDemoPayResult.java new file mode 100644 index 000000000..a3598cfe9 --- /dev/null +++ b/daxpay-demo/src/main/java/cn/daxpay/open/demo/channel/result/ChannelDemoPayResult.java @@ -0,0 +1,36 @@ +package cn.daxpay.open.demo.channel.result; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/// # 通道连通性 Demo 支付结果 +/// +/// 展示主应用 → 子应用往返的完整信息, 包含双端 traceId 用于链路对照。 +@Data +@Schema(description = "通道 Demo 支付结果") +public class ChannelDemoPayResult { + + /// 商户订单号 + @Schema(description = "商户订单号") + private String bizOrderNo; + + /// 通道侧订单号 + @Schema(description = "通道侧订单号") + private String outOrderNo; + + /// 支付内容(支付链接 / 二维码 / 表单等) + @Schema(description = "支付内容") + private String payBody; + + /// 支付内容类型(qr_code / form / order_id) + @Schema(description = "支付内容类型") + private String payBodyType; + + /// 主应用 traceId + @Schema(description = "主应用 traceId") + private String mainAppTraceId; + + /// 子应用 traceId + @Schema(description = "子应用 traceId") + private String subAppTraceId; +} diff --git a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/old/pay/channel/ChannelPayClient.java b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/old/pay/channel/ChannelPayClient.java index e1b2226a6..de196fa32 100644 --- a/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/old/pay/channel/ChannelPayClient.java +++ b/daxpay-payment/daxpay-payment-common/src/main/java/cn/daxpay/open/payment/old/pay/channel/ChannelPayClient.java @@ -3,7 +3,7 @@ package cn.daxpay.open.payment.old.pay.channel; import cn.daxpay.open.payment.old.pay.channel.dto.ChannelPayReq; import cn.daxpay.open.payment.old.pay.channel.dto.ChannelPayResp; import cn.daxpay.open.payment.old.pay.channel.dto.ChannelResult; -import cn.daxpay.open.platform.common.config.properties.PlatformCommonProperties; +import cn.daxpay.open.platform.common.config.properties.DaxpayChannelProperties; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; @@ -13,7 +13,7 @@ import org.springframework.web.client.RestClient; /// # 通道服务 HTTP 客户端 /// /// 封装对通道适配服务(daxpay-channel-one)的 HTTP 调用,统一处理地址拼接、结果解析与异常转换。 -/// baseUrl 来自 [PlatformCommonProperties.ChannelOne] 配置。 +/// baseUrl 来自 [DaxpayChannelProperties] 配置。 /// @Slf4j @Component @@ -21,14 +21,14 @@ import org.springframework.web.client.RestClient; public class ChannelPayClient { private final RestClient restClient; - private final PlatformCommonProperties properties; + private final DaxpayChannelProperties channelProperties; /// 发起支付下单调用 /// /// @param req 通道支付请求 /// @return 通道支付响应 public ChannelPayResp pay(ChannelPayReq req) { - String url = properties.getChannelOne().getBaseUrl() + "/channel/pay"; + String url = channelProperties.getOne().getBaseUrl() + "/channel/pay"; ChannelResult result = restClient.post() .uri(url) .body(req) diff --git a/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/DaxpayChannelProperties.java b/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/DaxpayChannelProperties.java new file mode 100644 index 000000000..cbdb0bd90 --- /dev/null +++ b/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/DaxpayChannelProperties.java @@ -0,0 +1,33 @@ +package cn.daxpay.open.platform.common.config.properties; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/// # 通道适配子应用配置 +/// +/// 统一管理各通道适配子应用(dax-pay-channel-one/two/...)的连接地址。 +/// 各通道模块通过注入本类获取对应子应用的 baseUrl。 +@Data +@Accessors(chain = true) +@ConfigurationProperties(prefix = "daxpay.channel") +public class DaxpayChannelProperties { + + /// 子应用1(支付宝/微信) + private ChannelApp one = new ChannelApp(); + + /// 子应用2(银联/拉卡拉) — 未来扩展 + private ChannelApp two = new ChannelApp(); + + /// 子应用3(抖音/其他) — 未来扩展 + private ChannelApp three = new ChannelApp(); + + /// # 子应用连接配置 + @Data + @Accessors(chain = true) + public static class ChannelApp { + + /// 基础地址 + private String baseUrl = "http://127.0.0.1:20100"; + } +} diff --git a/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/PlatformCommonProperties.java b/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/PlatformCommonProperties.java index 002970131..9293c8e0d 100644 --- a/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/PlatformCommonProperties.java +++ b/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/properties/PlatformCommonProperties.java @@ -19,8 +19,6 @@ public class PlatformCommonProperties { private Spring spring = new Spring(); /// Swagger 配置 private Swagger swagger = new Swagger(); - /// 通道适配服务配置 - private ChannelOne channelOne = new ChannelOne(); /// # 缓存配置 /// @@ -100,14 +98,6 @@ public class PlatformCommonProperties { private Integer connectionRequestTimeout = 3; } } - /// # 通道适配服务配置 - /// - @Data - public static class ChannelOne { - /// 通道服务基础地址 - private String baseUrl = "http://127.0.0.1:20100"; - } - /// # Swagger 配置 /// @Data diff --git a/daxpay-platform/daxpay-platform-common/common-spring/pom.xml b/daxpay-platform/daxpay-platform-common/common-spring/pom.xml index 58662af6f..c4e89f757 100644 --- a/daxpay-platform/daxpay-platform-common/common-spring/pom.xml +++ b/daxpay-platform/daxpay-platform-common/common-spring/pom.xml @@ -18,6 +18,11 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-restclient + org.springframework.boot diff --git a/daxpay-platform/daxpay-platform-common/common-spring/src/main/java/cn/daxpay/open/platform/common/spring/configuration/RestClientConfiguration.java b/daxpay-platform/daxpay-platform-common/common-spring/src/main/java/cn/daxpay/open/platform/common/spring/configuration/RestClientConfiguration.java index 99c24ff76..b8412bd39 100644 --- a/daxpay-platform/daxpay-platform-common/common-spring/src/main/java/cn/daxpay/open/platform/common/spring/configuration/RestClientConfiguration.java +++ b/daxpay-platform/daxpay-platform-common/common-spring/src/main/java/cn/daxpay/open/platform/common/spring/configuration/RestClientConfiguration.java @@ -9,6 +9,7 @@ import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.hc.core5.util.Timeout; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; @@ -80,12 +81,13 @@ public class RestClientConfiguration { /// 创建 RestClient Bean /// - /// 使用自动配置的 RestClient.Builder 而非静态 [RestClient.builder], - /// 以便 OTel 拦截器自动注入(W3C traceparent 头自动透传)。 + /// 优先使用 Spring Boot 自动配置的 RestClient.Builder(携带 OTel 拦截器, + /// 自动透传 W3C traceparent); 若 autoconfiguration 未注册则 fallback 到 [RestClient.builder]。 @Bean - public RestClient restClient(RestClient.Builder restClientBuilder, + public RestClient restClient(ObjectProvider builderProvider, HttpComponentsClientHttpRequestFactory httpRequestFactory) { - return restClientBuilder + RestClient.Builder builder = builderProvider.getIfAvailable(RestClient::builder); + return builder .requestFactory(httpRequestFactory) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .requestInterceptor(new BusinessContextInterceptor())