From 358755628feef7e2fcf6dbcfb2d9b7e33aea171d Mon Sep 17 00:00:00 2001 From: DaxPay Dev Date: Sat, 25 Jul 2026 15:48:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(merchant):=20=E5=95=86=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E8=B0=83=E8=AF=95=E5=8A=9F=E8=83=BD(?= =?UTF-8?q?=E4=BA=A4=E6=98=93/=E7=BD=91=E5=85=B3/=E7=AD=BE=E5=90=8D/?= =?UTF-8?q?=E8=AE=A4=E8=AF=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端仅提供签名与元数据辅助, 真实交易由前端模拟商户请求直调 /unipay/pay, 与正式对接走同一入口避免后门; 复用 payment-core 的 PayProviderMethodService/PayRouteStrategyCapabilitySupport/PlatformAuthService 等 bean。 update-datas.sql 新增商户端 develop 菜单(顶级目录 91600 + 4 子菜单, sort=5 排在回调通知之后)。 --- _config/sql/update-datas.sql | 17 ++++ .../develop/DevelopAuthController.java | 80 +++++++++++++++++++ .../develop/DevelopGatewayController.java | 37 +++++++++ .../develop/DevelopSignController.java | 42 ++++++++++ .../develop/DevelopTradeController.java | 69 ++++++++++++++++ .../merchant/param/develop/DevelopParam.java | 23 ++++++ .../param/develop/DevelopSignParam.java | 20 +++++ .../param/develop/DevelopVerifyParam.java | 24 ++++++ .../result/develop/DevelopSignResult.java | 20 +++++ .../service/develop/DevelopAuthService.java | 61 ++++++++++++++ .../develop/DevelopGatewayService.java | 32 ++++++++ .../service/develop/DevelopSignService.java | 40 ++++++++++ .../service/develop/DevelopTradeService.java | 58 ++++++++++++++ 13 files changed, 523 insertions(+) create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopAuthController.java create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopGatewayController.java create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopSignController.java create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopTradeController.java create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopParam.java create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopSignParam.java create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopVerifyParam.java create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/result/develop/DevelopSignResult.java create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopAuthService.java create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopGatewayService.java create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopSignService.java create mode 100644 daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopTradeService.java diff --git a/_config/sql/update-datas.sql b/_config/sql/update-datas.sql index e69de29bb..b9c2af10c 100644 --- a/_config/sql/update-datas.sql +++ b/_config/sql/update-datas.sql @@ -0,0 +1,17 @@ +-- ============================================================ +-- 升级数据脚本 (上一个版本 -> 当前版本) +-- 执行顺序: update-tables.sql -> update-datas.sql +-- 注意: 不可跨版本升级, 仅限相邻版本间执行 +-- ============================================================ + +-- ---------------------------- +-- 商户端 开发调试(develop) 菜单 +-- 顶级目录 + 4 子菜单(交易/网关/签名/认证调试) +-- 对应前端 apps/daxpay-merchant, 后端 /mch/develop/* 接口 +-- 排序: 顶级目录 sort=5, 位于「回调通知/交易记录」(sort=4.5)之后 +-- ---------------------------- +INSERT INTO "public"."iam_perm_menu" VALUES (91600, NULL, NULL, 'merchant', 'Develop', 'menu.develop', 'lucide:wrench', 'f', 'f', NULL, '/develop', '/develop/trade', 5, 'f', 't', 'f', 0, NULL, 0, 'f', 'catalog', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (91601, 91600, 'develop:trade', 'merchant', 'DevelopTrade', 'menu.develop.trade', 'lucide:credit-card', 'f', 'f', '/payment/develop/trade/DevelopTrade', '/develop/trade', NULL, 1, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (91602, 91600, 'develop:gateway', 'merchant', 'DevelopGateway', 'menu.develop.gateway', 'lucide:globe', 'f', 'f', '/payment/develop/gateway/DevelopGateway', '/develop/gateway', NULL, 2, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (91603, 91600, 'develop:sign', 'merchant', 'DevelopSign', 'menu.develop.sign', 'lucide:pen-tool', 'f', 'f', '/payment/develop/sign/DevelopSign', '/develop/sign', NULL, 3, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00'); +INSERT INTO "public"."iam_perm_menu" VALUES (91604, 91600, 'develop:auth', 'merchant', 'ChannelAuth', 'menu.develop.auth', 'lucide:shield-check', 'f', 'f', '/payment/develop/auth/ChannelAuth', '/develop/auth', NULL, 4, 'f', 'f', 'f', 0, NULL, 0, 'f', 'menu', NULL, NULL, NULL, NULL, NULL, NULL, '2026-07-25 15:10:00+00', '2026-07-25 15:10:00+00'); diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopAuthController.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopAuthController.java new file mode 100644 index 000000000..9a498f69a --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopAuthController.java @@ -0,0 +1,80 @@ +package cn.daxpay.open.payment.merchant.controller.develop; + +import cn.daxpay.open.payment.merchant.service.develop.DevelopAuthService; +import cn.daxpay.open.payment.unipay.param.assist.GenerateAuthUrlParam; +import cn.daxpay.open.payment.unipay.result.assist.AuthResult; +import cn.daxpay.open.payment.unipay.result.assist.AuthUrlResult; +import cn.daxpay.open.platform.core.annotation.PermCode; +import cn.daxpay.open.platform.core.code.PermCodes; +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 jakarta.validation.constraints.NotBlank; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +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; + +/// 认证调试(商户端) +/// +/// 调试入口: +/// - 支付宝H5平台级配置(中间页授权链接) +/// - 微信公众号配置(平台级 OAuth) +/// - 抖音 H5 应用配置(平台级 silent_auth 静默授权) +/// - 微信支付(直连/服务商, 需商户参数) +/// - 支付宝小程序(暂未实现) +/// - 微信小程序(商户端/运营端, 暂未实现) +/// +/// 已实现项均通过查询码轮询认证结果。 +@Validated +@PermCode(menuCode = PermCodes.Develop.Auth.MENU) +@Tag(name = "认证调试服务(商户端)") +@RestController +@RequestMapping("/mch/develop/auth") +@RequiredArgsConstructor +public class DevelopAuthController { + + private final DevelopAuthService developAuthService; + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "生成支付宝H5授权链接") + @PostMapping("/generate-alipay-auth-url") + public Result generateAlipayAuthUrl() { + return Res.ok(developAuthService.generateAlipayAuthUrl()); + } + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "生成微信公众号配置授权链接") + @PostMapping("/generate-wechat-mp-auth-url") + public Result generateWechatMpAuthUrl() { + return Res.ok(developAuthService.generateWechatMpAuthUrl()); + } + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "生成抖音H5授权链接") + @PostMapping("/generate-douyin-auth-url") + public Result generateDouyinAuthUrl() { + return Res.ok(developAuthService.generateDouyinAuthUrl()); + } + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "生成微信支付授权链接") + @PostMapping("/generate-channel-auth-url") + public Result generateChannelAuthUrl(@RequestBody GenerateAuthUrlParam param) { + // 不加 @Valid: GenerateAuthUrlParam 继承 PaymentCommonParam.reqTime(@NotNull), 但认证不走签名/防重放, 无需 reqTime; + // channel/mchNo 由 ChannelProductAuthService 业务层兜底校验, 与 unipay ChannelAuthController 同类接口保持一致 + return Res.ok(developAuthService.generateChannelAuthUrl(param)); + } + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "通过查询码获取认证结果") + @GetMapping("/query-auth-result") + public Result queryAuthResult( + @NotBlank(message = "{validation.field.queryCode.notBlank}") String queryCode) { + return Res.ok(developAuthService.queryAuthResult(queryCode)); + } +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopGatewayController.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopGatewayController.java new file mode 100644 index 000000000..704764382 --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopGatewayController.java @@ -0,0 +1,37 @@ +package cn.daxpay.open.payment.merchant.controller.develop; + +import cn.daxpay.open.payment.merchant.param.develop.DevelopParam; +import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult; +import cn.daxpay.open.payment.merchant.service.develop.DevelopGatewayService; +import cn.daxpay.open.payment.unipay.param.gateway.GatewayPrePayParam; +import cn.daxpay.open.platform.core.annotation.PermCode; +import cn.daxpay.open.platform.core.code.PermCodes; +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 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; + +/// 网关支付开发调试(商户端) +/// +/// 仅提供签名辅助, 真实预下单由前端模拟商户请求调用 `/unipay/gateway/pre-pay`。 +@PermCode(menuCode = PermCodes.Develop.Gateway.MENU) +@Tag(name = "网关支付开发调试服务(商户端)") +@RestController +@RequestMapping("/mch/develop/gateway") +@RequiredArgsConstructor +public class DevelopGatewayController { + + private final DevelopGatewayService developGatewayService; + + @PermCode(code = PermCodes.Action.SIGN) + @Operation(summary = "网关预下单参数签名") + @PostMapping("/sign") + public Result sign(@RequestBody DevelopParam param) { + return Res.ok(developGatewayService.sign(param)); + } +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopSignController.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopSignController.java new file mode 100644 index 000000000..7bd24944d --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopSignController.java @@ -0,0 +1,42 @@ +package cn.daxpay.open.payment.merchant.controller.develop; + +import cn.daxpay.open.platform.core.code.PermCodes; +import cn.daxpay.open.payment.merchant.param.develop.DevelopSignParam; +import cn.daxpay.open.payment.merchant.param.develop.DevelopVerifyParam; +import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult; +import cn.daxpay.open.payment.merchant.service.develop.DevelopSignService; +import cn.daxpay.open.platform.core.annotation.PermCode; +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 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; + +/// 签名调试(商户端) +@PermCode(menuCode = PermCodes.Develop.Sign.MENU) +@Tag(name = "签名调试服务(商户端)") +@RestController +@RequestMapping("/mch/develop/sign") +@RequiredArgsConstructor +public class DevelopSignController { + + private final DevelopSignService developSignService; + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "生成签名") + @PostMapping("/gen") + public Result sign(@RequestBody DevelopSignParam param) { + return Res.ok(developSignService.sign(param)); + } + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "验签") + @PostMapping("/verify") + public Result verify(@RequestBody DevelopVerifyParam param) { + return Res.ok(developSignService.verify(param)); + } +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopTradeController.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopTradeController.java new file mode 100644 index 000000000..07e80f9fa --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/controller/develop/DevelopTradeController.java @@ -0,0 +1,69 @@ +package cn.daxpay.open.payment.merchant.controller.develop; + +import cn.daxpay.open.payment.merchant.param.develop.DevelopParam; +import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult; +import cn.daxpay.open.payment.merchant.service.develop.DevelopTradeService; +import cn.daxpay.open.payment.masterdata.result.provider.PayProviderMethodResult; +import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam; +import cn.daxpay.open.platform.core.annotation.PermCode; +import cn.daxpay.open.platform.core.code.PermCodes; +import cn.daxpay.open.platform.core.rest.Res; +import cn.daxpay.open.platform.core.rest.dto.ChannelMchOption; +import cn.daxpay.open.platform.core.rest.dto.LabelValue; +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 jakarta.validation.constraints.NotBlank; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/// 交易开发调试(商户端) +/// +/// 仅提供签名与元数据辅助, 真实支付由前端模拟商户请求调用 `/unipay/pay`。 +@PermCode(menuCode = PermCodes.Develop.Trade.MENU) +@Tag(name = "交易开发调试服务(商户端)") +@RestController +@RequestMapping("/mch/develop/trade") +@RequiredArgsConstructor +public class DevelopTradeController { + + private final DevelopTradeService developTradeService; + + @PermCode(code = PermCodes.Action.SIGN) + @Operation(summary = "支付参数签名") + @PostMapping("/sign") + public Result sign(@RequestBody DevelopParam param) { + return Res.ok(developTradeService.sign(param)); + } + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "已启用渠道支付方式目录") + @GetMapping("/method-directory") + public Result> methodDirectory() { + return Res.ok(developTradeService.listMethodDirectory()); + } + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "直接指定通道商户候选") + @GetMapping("/channel-mch-candidates") + public Result> channelMchCandidates( + @NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo, + @RequestParam(required = false) String provider) { + return Res.ok(developTradeService.listChannelMchCandidates(mchNo, provider)); + } + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "直接指定支付能力候选") + @GetMapping("/capability-candidates") + public Result> capabilityCandidates( + @NotBlank(message = "{validation.field.channelMchNo.notBlank}") String channelMchNo) { + return Res.ok(developTradeService.listCapabilityCandidates(channelMchNo)); + } +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopParam.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopParam.java new file mode 100644 index 000000000..295d4cc14 --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopParam.java @@ -0,0 +1,23 @@ +package cn.daxpay.open.payment.merchant.param.develop; + +import cn.daxpay.open.payment.unipay.param.MerchantPaymentCommonParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 开发调试参数包装 +/// +/// 包装业务参数与调试用手填私钥, 私钥仅用于本次签名生成, 不入库 +@Data +@Accessors(chain = true) +@Schema(title = "开发调试参数包装") +public class DevelopParam { + + /// 业务参数 + @Schema(description = "业务参数") + private T param; + + /// 生成签名使用的私钥(PEM 格式, 调试页手填, 不入库) + @Schema(description = "生成签名使用的私钥(PEM格式)") + private String privateKey; +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopSignParam.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopSignParam.java new file mode 100644 index 000000000..c771bb897 --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopSignParam.java @@ -0,0 +1,20 @@ +package cn.daxpay.open.payment.merchant.param.develop; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 签名调试-生成签名参数 +@Data +@Accessors(chain = true) +@Schema(title = "签名调试-生成签名参数") +public class DevelopSignParam { + + /// 待签名 JSON 字符串 + @Schema(description = "待签名JSON字符串") + private String json; + + /// 私钥(PEM 格式) + @Schema(description = "私钥(PEM格式)") + private String privateKey; +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopVerifyParam.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopVerifyParam.java new file mode 100644 index 000000000..2ca5e2151 --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/param/develop/DevelopVerifyParam.java @@ -0,0 +1,24 @@ +package cn.daxpay.open.payment.merchant.param.develop; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 签名调试-验签参数 +@Data +@Accessors(chain = true) +@Schema(title = "签名调试-验签参数") +public class DevelopVerifyParam { + + /// 待验签 JSON 字符串(不含 sign 字段) + @Schema(description = "待验签JSON字符串(不含sign字段)") + private String json; + + /// 签名值 + @Schema(description = "签名值") + private String sign; + + /// 公钥(PEM 格式) + @Schema(description = "公钥(PEM格式)") + private String publicKey; +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/result/develop/DevelopSignResult.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/result/develop/DevelopSignResult.java new file mode 100644 index 000000000..ce0c21b19 --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/result/develop/DevelopSignResult.java @@ -0,0 +1,20 @@ +package cn.daxpay.open.payment.merchant.result.develop; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 签名调试结果 +@Data +@Accessors(chain = true) +@Schema(title = "签名调试结果") +public class DevelopSignResult { + + /// 待签名原文(键值拼接串) + @Schema(description = "待签名原文") + private String signStr; + + /// 签名值 + @Schema(description = "签名值") + private String sign; +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopAuthService.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopAuthService.java new file mode 100644 index 000000000..464c38fee --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopAuthService.java @@ -0,0 +1,61 @@ +package cn.daxpay.open.payment.merchant.service.develop; + +import cn.daxpay.open.payment.auth.AuthSessionStore; +import cn.daxpay.open.payment.auth.ChannelProductAuthService; +import cn.daxpay.open.payment.auth.PlatformAuthService; +import cn.daxpay.open.payment.unipay.param.assist.GenerateAuthUrlParam; +import cn.daxpay.open.payment.unipay.result.assist.AuthResult; +import cn.daxpay.open.payment.unipay.result.assist.AuthUrlResult; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/// # 认证调试服务 +/// +/// 调试入口, 按认证来源分别委托: +/// - **支付宝(平台级)**: 委托 [PlatformAuthService] 生成 OAuth 授权链接, 轮询 queryCode 取结果 +/// - **微信公众号配置(平台级)**: 委托 [PlatformAuthService], OAuth 重定向取 openId, 仅验证配置是否正确 +/// - **抖音H5(平台级)**: 委托 [PlatformAuthService], silent_auth 静默授权取 openId, 仅验证配置是否正确 +/// - **微信支付(直连/服务商)**: 委托 [ChannelProductAuthService] 按支付产品路由认证策略, +/// 依赖商户上下文(channelMchNo/产品/能力) +/// - **支付宝小程序**: 暂未实现 +/// - **微信小程序(商户端/运营端)**: 暂未实现 +/// +/// 已实现项共用 queryCode 轮询机制(由 [AuthSessionStore#queryAuthResult] 统一查询)。 +@Slf4j +@Service +@RequiredArgsConstructor +public class DevelopAuthService { + + private final PlatformAuthService platformAuthService; + private final ChannelProductAuthService channelProductAuthService; + private final AuthSessionStore authSessionStore; + + /// 生成支付宝授权链接(平台级 OAuth + queryCode 轮询) + public AuthUrlResult generateAlipayAuthUrl() { + return platformAuthService.generateAlipayAuthUrl(); + } + + /// 生成微信公众号配置授权链接(平台级, 仅调试) + public AuthUrlResult generateWechatMpAuthUrl() { + return platformAuthService.generateWechatMpAuthUrl(); + } + + /// 生成抖音 H5 授权链接(平台级, 仅调试) + public AuthUrlResult generateDouyinAuthUrl() { + return platformAuthService.generateDouyinAuthUrl(); + } + + /// 生成微信支付(直连/服务商)授权链接 + /// + /// 委托 [ChannelProductAuthService#generateAuthUrl], 按支付产品路由对应认证策略: + /// 直连(WECHAT_PAY) → WechatDirectAuthStrategy; 服务商(WECHAT_ISV) → WechatIsvAuthStrategy。 + public AuthUrlResult generateChannelAuthUrl(GenerateAuthUrlParam param) { + return channelProductAuthService.generateAuthUrl(param); + } + + /// 通过查询码获取认证结果 + public AuthResult queryAuthResult(String queryCode) { + return authSessionStore.queryAuthResult(queryCode); + } +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopGatewayService.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopGatewayService.java new file mode 100644 index 000000000..b13eea37b --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopGatewayService.java @@ -0,0 +1,32 @@ +package cn.daxpay.open.payment.merchant.service.develop; + +import cn.daxpay.open.payment.common.util.ObjectSignStrUtil; +import cn.daxpay.open.payment.common.util.PaySignUtil; +import cn.daxpay.open.payment.merchant.param.develop.DevelopParam; +import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult; +import cn.daxpay.open.payment.unipay.param.gateway.GatewayPrePayParam; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/// # 网关支付开发调试服务 +/// +/// 仅提供组参辅助与签名能力, **不发起真实交易**。 +/// 真实预下单由商户端调试页模拟商户 HTTP 请求调用 `/unipay/gateway/pre-pay` 完成, +/// 与正式对接走同一入口, 避免内部直调支付核心形成后门。 +@Slf4j +@Service +@RequiredArgsConstructor +public class DevelopGatewayService { + + /// 生成网关预下单参数签名(与正式签名逻辑一致) + /// + /// 调用方需在 param 中自备 reqTime / nonceStr 等公共字段, 本方法只负责签名串与签名值。 + public DevelopSignResult sign(DevelopParam param) { + // 签名串(与 PaySignUtil 内部一致) + String signStr = ObjectSignStrUtil.buildSignStr(param.getParam()); + // 签名值 + String sign = PaySignUtil.sign(param.getParam(), param.getPrivateKey()); + return new DevelopSignResult().setSignStr(signStr).setSign(sign); + } +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopSignService.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopSignService.java new file mode 100644 index 000000000..667613d27 --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopSignService.java @@ -0,0 +1,40 @@ +package cn.daxpay.open.payment.merchant.service.develop; + +import cn.daxpay.open.payment.common.util.JsonSignStrUtil; +import cn.daxpay.open.payment.merchant.param.develop.DevelopSignParam; +import cn.daxpay.open.payment.merchant.param.develop.DevelopVerifyParam; +import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult; +import cn.daxpay.open.platform.core.util.RsaSignUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/// # 签名调试服务 +/// +/// 提供参数签名生成与验签功能, 便于商户对接联调 +@Slf4j +@Service +@RequiredArgsConstructor +public class DevelopSignService { + + /// 生成签名 + public DevelopSignResult sign(DevelopSignParam param) { + // 待签名原文(扁平化排序后的键值拼接串) + String signStr = JsonSignStrUtil.buildSignStr(param.getJson()); + // 使用私钥生成签名 + String sign = RsaSignUtil.sign(signStr, param.getPrivateKey()); + return new DevelopSignResult().setSignStr(signStr).setSign(sign); + } + + /// 验签 + public boolean verify(DevelopVerifyParam param) { + String signStr = JsonSignStrUtil.buildSignStr(param.getJson()); + try { + return RsaSignUtil.verify(signStr, param.getSign(), param.getPublicKey()); + } catch (Exception e) { + // 验签失败不抛异常, 返回 false 便于前端展示 + log.warn("验签失败: {}", e.getMessage()); + return false; + } + } +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopTradeService.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopTradeService.java new file mode 100644 index 000000000..685435a87 --- /dev/null +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/develop/DevelopTradeService.java @@ -0,0 +1,58 @@ +package cn.daxpay.open.payment.merchant.service.develop; + +import cn.daxpay.open.payment.masterdata.result.provider.PayProviderMethodResult; +import cn.daxpay.open.payment.masterdata.service.provider.PayProviderMethodService; +import cn.daxpay.open.payment.merchant.param.develop.DevelopParam; +import cn.daxpay.open.payment.merchant.result.develop.DevelopSignResult; +import cn.daxpay.open.payment.common.util.ObjectSignStrUtil; +import cn.daxpay.open.payment.common.util.PaySignUtil; +import cn.daxpay.open.payment.route.service.support.PayRouteStrategyCapabilitySupport; +import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam; +import cn.daxpay.open.platform.core.rest.dto.ChannelMchOption; +import cn.daxpay.open.platform.core.rest.dto.LabelValue; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/// # 交易开发调试服务 +/// +/// 仅提供组参辅助与签名能力, **不发起真实交易**。 +/// 真实支付由商户端调试页模拟商户 HTTP 请求调用 `/unipay/pay` 完成, +/// 与正式对接走同一入口, 避免内部直调支付核心形成后门。 +@Slf4j +@Service +@RequiredArgsConstructor +public class DevelopTradeService { + + private final PayProviderMethodService payProviderMethodService; + private final PayRouteStrategyCapabilitySupport payRouteStrategyCapabilitySupport; + + /// 生成支付参数签名(与正式签名逻辑一致) + /// + /// 调用方需在 param 中自备 reqTime / nonceStr 等公共字段, 本方法只负责签名串与签名值。 + public DevelopSignResult sign(DevelopParam param) { + // 签名串(与 PaySignUtil 内部一致) + String signStr = ObjectSignStrUtil.buildSignStr(param.getParam()); + // 签名值 + String sign = PaySignUtil.sign(param.getParam(), param.getPrivateKey()); + return new DevelopSignResult().setSignStr(signStr).setSign(sign); + } + + /// 已启用渠道支付方式目录(供调试页支付方式下拉) + public List listMethodDirectory() { + return payProviderMethodService.listDirectoryFlat(); + } + + /// 直接指定:商户全部启用通道商户候选, provider 为空返回全部, + /// 非空按支付渠道过滤(如 wechat 返回所有声明支持微信的产品通道商户, 含官方与三方聚合通道) + public List listChannelMchCandidates(String mchNo, String provider) { + return payRouteStrategyCapabilitySupport.listDirectChannelMchCandidates(mchNo, provider); + } + + /// 直接指定:按通道商户(产品)返回全部启用支付能力候选 + public List listCapabilityCandidates(String channelMchNo) { + return payRouteStrategyCapabilitySupport.listDirectCapabilityCandidates(channelMchNo); + } +}