diff --git a/README.md b/README.md index b4e6f8e..3991353 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Kra 是基于 Kratos 生命周期与 Wire 依赖注入、使用 Gin 提供管理 ```text cmd/ 服务入口与 Wire configs/ 运行配置 -internal/biz/ 领域对象、用例和仓储接口 +internal/biz/ 领域对象、用例和仓储接口(按 system/payment/integration/task 拆分) internal/data/ 数据库、缓存、对象存储及仓储实现 internal/server/ Gin 服务、路由、中间件和 Handler internal/service/ HTTP 输入输出与领域对象转换 diff --git a/cmd/wire.go b/cmd/wire.go index 9bcf4c4..201a474 100644 --- a/cmd/wire.go +++ b/cmd/wire.go @@ -10,7 +10,7 @@ import ( "kra/internal/app" "kra/internal/biz" - systembiz "kra/internal/biz/system" + taskbiz "kra/internal/biz/task" "kra/internal/conf" "kra/internal/data" "kra/internal/initialize" @@ -46,7 +46,7 @@ func wireApp(*conf.Server, *conf.Runtime, *slog.Logger, *logging.ReloadableLogge wire.Bind(new(initialize.Backend), new(*data.Data)), wire.Bind(new(cache.RedisProvider), new(*data.Data)), wire.Bind(new(middleware.TokenAuthenticator), new(*service.AuthService)), - wire.Bind(new(systembiz.TaskMethodRegistry), new(*platformtask.Registry)), + wire.Bind(new(taskbiz.TaskMethodRegistry), new(*platformtask.Registry)), biz.ProviderSet, service.ProviderSet, newApp, diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index 91d25e2..34c5e36 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -9,7 +9,10 @@ package main import ( "github.com/go-kratos/kratos/v3" "kra/internal/app" + integration2 "kra/internal/biz/integration" + payment2 "kra/internal/biz/payment" system2 "kra/internal/biz/system" + "kra/internal/biz/task" "kra/internal/conf" "kra/internal/data" "kra/internal/data/payment" @@ -97,20 +100,20 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger handlerEmail := handler.NewEmail(emailService) paymentRepo := payment.NewPaymentRepo(dataData) paymentOrderRepo := payment.NewPaymentOrderRepo(dataData) - paymentUsecase := system2.NewPaymentUsecase(paymentRepo, paymentOrderRepo, logger) + paymentUsecase := payment2.NewPaymentUsecase(paymentRepo, paymentOrderRepo, logger) paymentService := service.NewPaymentService(paymentUsecase) handlerPayment := handler.NewPayment(paymentService) taskRepo := system.NewTaskRepo(dataData) registry := app.TaskRegistry(catalog) - taskUsecase := system2.NewTaskUsecaseWithRegistry(taskRepo, registry) + taskUsecase := task.NewTaskUsecaseWithRegistry(taskRepo, registry) mediaRepo := system.NewMediaRepo(dataData) mediaUsecase := system2.NewMediaUsecase(mediaRepo, reloadable, runtimeSettings) taskExecutor := worker.NewTaskExecutorWithRegistry(taskUsecase, mediaUsecase, runtime, registry) taskScheduler := worker.NewTaskScheduler(taskUsecase, authorityUsecase, taskExecutor, logger) taskRuntime := worker.NewTaskRuntime(taskScheduler) - taskApplicationUsecase := system2.NewTaskApplicationUsecase(taskUsecase, taskRuntime) + taskApplicationUsecase := task.NewTaskApplicationUsecase(taskUsecase, taskRuntime) taskService := service.NewTaskService(taskApplicationUsecase) - task := handler.NewTask(taskService) + handlerTask := handler.NewTask(taskService) mediaService := service.NewMediaService(mediaUsecase, runtimeSettings) media := handler.NewMedia(mediaService) auditQueryRepo := system.NewAuditRepo(dataData) @@ -151,10 +154,10 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger integrationConfigRepo := system.NewIntegrationConfigRepo(dataData) store := data.NewIntegrationRuntime(dataData) connectivityTester := integration.NewConnectivityTester(store) - integrationConfigUsecase := system2.NewIntegrationConfigUsecase(integrationConfigRepo, connectivityTester) + integrationConfigUsecase := integration2.NewIntegrationConfigUsecase(integrationConfigRepo, connectivityTester) integrationConfigService := service.NewIntegrationConfigService(integrationConfigUsecase) integrationConfig := handler.NewIntegrationConfig(integrationConfigService) - v := handler.NewSet(authority, menu, api, permission, organization, announcement, handlerEmail, handlerPayment, task, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session, integrationConfig) + v := handler.NewSet(authority, menu, api, permission, organization, announcement, handlerEmail, handlerPayment, handlerTask, media, audit, export, version, dictionary, parameter, apiToken, systemConfig, public, user, navigation, session, integrationConfig) routes := router.NewRoutes(v) taskMethods := worker.NewTaskMethods(taskUsecase, mediaUsecase, runtime) appRuntimeContributions := runtimeContributions(routes, taskMethods) diff --git a/docs/PAYMENT.md b/docs/PAYMENT.md index 2bd3ffb..fbd1582 100644 --- a/docs/PAYMENT.md +++ b/docs/PAYMENT.md @@ -78,7 +78,7 @@ PayPal `AUTHORIZE` 意图后的授权捕获,以及账单、分账、转账等 | Provider | 实现方式 | | --- | --- | -| `alipay`、`alipay-v3`、`wechat-v2`、`wechat-v3`、`apple-iap`、`paypal`、`douyin`、`qq`、`allinpay`、`lakala`、`saobei` | 统一走上面的 GoPay v1.5.122 adapter;业务层只接收 `biz.PaymentResult` | +| `alipay`、`alipay-v3`、`wechat-v2`、`wechat-v3`、`apple-iap`、`paypal`、`douyin`、`qq`、`allinpay`、`lakala`、`saobei` | 统一走上面的 GoPay v1.5.122 adapter;业务层只接收 `paymentbiz.PaymentResult` | | `chinaums` | 配置驱动的银联商务 JSON 签名适配器 | | `sft` | 配置驱动的商福通 JSON/MD5 适配器 | | `supper-pay` | 配置驱动的 Supper Pay HMAC 适配器 | @@ -131,18 +131,20 @@ PayPal `AUTHORIZE` 意图后的授权捕获,以及账单、分账、转账等 每个业务模块先实现可信订单来源: ```go +import paymentbiz "kra/internal/biz/payment" + type GameItemPayment struct { orders GameItemOrderRepo } func (GameItemPayment) Type() string { return "game_item" } -func (p GameItemPayment) PreparePayment(ctx context.Context, provider, tradeNo, businessID string) (*biz.PaymentIntent, error) { +func (p GameItemPayment) PreparePayment(ctx context.Context, provider, tradeNo, businessID string) (*paymentbiz.PaymentIntent, error) { order, err := p.orders.FindPayable(ctx, businessID) if err != nil { return nil, err } - return &biz.PaymentIntent{ + return &paymentbiz.PaymentIntent{ Provider: provider, TradeNo: tradeNo, BusinessType: "game_item", BusinessID: businessID, Subject: order.Title, Amount: order.PayableAmount, Currency: order.Currency, @@ -153,7 +155,7 @@ func (p GameItemPayment) PreparePayment(ctx context.Context, provider, tradeNo, 然后注册按业务类型分发的发货处理器: ```go -func (p GameItemPayment) Fulfill(ctx context.Context, c *biz.PaymentConfirmation) error { +func (p GameItemPayment) Fulfill(ctx context.Context, c *paymentbiz.PaymentConfirmation) error { return p.orders.Transaction(ctx, func(tx GameItemOrderTx) error { // confirmation_id 必须有唯一约束。已处理时直接返回 nil。 if tx.HasPaymentConfirmation(c.ID) { @@ -166,7 +168,7 @@ func (p GameItemPayment) Fulfill(ctx context.Context, c *biz.PaymentConfirmation }) } -func (p GameItemPayment) AuthorizeRefund(ctx context.Context, order *biz.PaymentOrder, amount int64) error { +func (p GameItemPayment) AuthorizeRefund(ctx context.Context, order *paymentbiz.PaymentOrder, amount int64) error { return p.orders.CheckRefundable(ctx, order.BusinessID, amount) } ``` @@ -405,4 +407,4 @@ Chinaums、SFT、Supper Pay、微信小游戏和抖音小游戏没有在代码 ## 日志 -支付日志在 `internal/biz/payment_log.go` 通过 `PaymentLogger` 独立抽象,包含下单、查单失败、回调查单、金额校验、重复回调和发货结果等结构化事件。日志只记录渠道、商户订单号、业务类型、业务 ID、确认 ID 等审计字段,不记录私钥、密钥、证书内容或完整敏感回调原文。 +支付日志在 `internal/biz/payment/payment_log.go` 通过 `PaymentLogger` 独立抽象,包含下单、查单失败、回调查单、金额校验、重复回调和发货结果等结构化事件。日志只记录渠道、商户订单号、业务类型、业务 ID、确认 ID 等审计字段,不记录私钥、密钥、证书内容或完整敏感回调原文。 diff --git a/docs/system-pkg-audit.md b/docs/system-pkg-audit.md index 3fe6c8a..e10c6fd 100644 --- a/docs/system-pkg-audit.md +++ b/docs/system-pkg-audit.md @@ -10,10 +10,10 @@ | --- | --- | --- | | HTTP JSON 响应契约 | `pkg/httpx` | `Response`、`PageResult`、状态码和 Gin 响应助手;`server/httpx/response.go` 仅保留 system 适配。 | | protobuf JSON 局部合并 | `pkg/protoutil` | 与业务无关的字段归一化和局部反序列化;初始化直接使用公共包。 | -| 支付 provider/mode 标识 | `pkg/paymentkit` | provider 常量、支持列表、金额/签名/JSON 等跨模块协议;system `biz` 只保留兼容别名。 | -| 支付回调 ACK | `pkg/paymentkit` | 回调应答、失败包装和默认 provider 应答;具体渠道 SDK 仍留在 system integration。 | -| WebSocket 通用收发 | `pkg/websocket` | Melody 的连接、事件、点对点发送、广播和会话查询封装;system integration 管理配置与生命周期。 | -| 消息队列 | `pkg/mq` | Broker 无关的发布、订阅、JSON 和 QoS 接口;EMQX/Paho 与 RabbitMQ/AMQP 客户端由 system integration 管理。 | +| 支付 provider/mode 标识 | `pkg/paymentkit` | provider 常量、支持列表、金额/签名/JSON 等跨模块协议;`biz/payment` 与 `biz/integration` 直接复用。 | +| 支付回调 ACK | `pkg/paymentkit` | 回调应答、失败包装和默认 provider 应答;具体渠道 SDK 留在 `internal/integration/payment`。 | +| WebSocket 通用收发 | `pkg/websocket` | Melody 的连接、事件、点对点发送、广播和会话查询封装;`internal/integration` 管理配置与生命周期。 | +| 消息队列 | `pkg/mq` | Broker 无关的发布、订阅、JSON 和 QoS 接口;EMQX/Paho 与 RabbitMQ/AMQP 客户端由 `internal/integration` 管理。 | | 模块、任务和迁移协议 | `pkg/module`、`pkg/task`、`pkg/database/migration` | 供不同业务模块注册贡献,不带 system 业务语义。 | ## system 内部保留边界 @@ -21,7 +21,10 @@ - `app`:组合根,汇总各模块的迁移、菜单、路由和任务贡献。 - `modules/system`:system 模块的 Definition,声明迁移、管理面和默认任务。 - `modules/payment`:payment 模块的 Definition,声明支付迁移和支付管理面。 -- `biz`:用户、权限、菜单、审计、任务、支付订单和系统配置等领域模型与用例。 +- `biz/system`:用户、权限、菜单、审计、媒体和系统配置等系统领域模型与用例。 +- `biz/payment`:支付订单、支付流程、支付接口和支付日志。 +- `biz/integration`:支付/消息队列/WebSocket 集成配置定义与校验。 +- `biz/task`:定时任务模型、任务用例和任务注册协议。 - `conf`:system 配置 proto、运行时快照和生成代码。 - `data`:数据库连接、PO、仓储、system 表、支付持久化和配置 watcher。 - `initialize`:首次安装、配置迁移、种子编排和运行时重载。 @@ -40,7 +43,11 @@ internal/ app/ # 组合根和 catalog modules/ # 业务模块定义及其模块级贡献 - biz/ # DO、usecase、repo interface + biz/ + system/ # 系统领域 + payment/ # 支付领域 + integration/# 集成配置领域 + task/ # 定时任务领域 conf/ # 配置 proto/runtime data/ # PO、repo、数据库和迁移 initialize/ # 首次安装和配置编排 diff --git a/internal/README.md b/internal/README.md index c53a36a..3c1d4c1 100644 --- a/internal/README.md +++ b/internal/README.md @@ -5,7 +5,10 @@ - `app`:组合根、模块 catalog 和任务/路由运行时组合 - `modules`:按业务模块维护 Definition 等模块贡献 -- `biz`:系统领域对象、用例和仓储接口 +- `biz/system`:用户、权限、菜单、审计、媒体和系统配置领域 +- `biz/payment`:支付订单、支付流程、支付接口和支付日志 +- `biz/integration`:集成配置定义、校验和连接测试边界 +- `biz/task`:定时任务模型、用例和任务注册协议 - `conf`:基础配置 proto 与运行时配置解析 - `data`:数据库生命周期、系统仓储、系统表和支付持久化 - `initialize`:数据库首次初始化和系统种子数据编排 diff --git a/internal/biz/biz.go b/internal/biz/biz.go index 36106f9..00fa73d 100644 --- a/internal/biz/biz.go +++ b/internal/biz/biz.go @@ -1,10 +1,13 @@ package biz import ( + "kra/internal/biz/integration" + "kra/internal/biz/payment" "kra/internal/biz/system" + "kra/internal/biz/task" "github.com/google/wire" ) // ProviderSet is biz providers. -var ProviderSet = wire.NewSet(system.ProviderSet) +var ProviderSet = wire.NewSet(system.ProviderSet, payment.ProviderSet, integration.ProviderSet, task.ProviderSet) diff --git a/internal/biz/system/integration_config.go b/internal/biz/integration/integration_config.go similarity index 96% rename from internal/biz/system/integration_config.go rename to internal/biz/integration/integration_config.go index f6e5733..ed106ad 100644 --- a/internal/biz/system/integration_config.go +++ b/internal/biz/integration/integration_config.go @@ -1,10 +1,11 @@ -package system +package integration import ( "context" "encoding/json" "errors" "fmt" + paymentutil "kra/pkg/paymentkit" "sort" "strconv" "strings" @@ -290,7 +291,7 @@ func validatePaymentIntegrationConfig(provider string, values map[string]any) er } } switch provider { - case PaymentAlipayV3: + case paymentutil.ProviderAlipayV3: for _, group := range []struct { label string keys []string @@ -304,21 +305,21 @@ func validatePaymentIntegrationConfig(provider string, values map[string]any) er return fmt.Errorf("%s 缺少配置字段 %s", provider, group.label) } } - case PaymentWechatV2: + case paymentutil.ProviderWechatV2: if integrationFirst(values, "client_cert", "cert_pem", "apiclient_cert") == "" || integrationFirst(values, "client_key", "key_pem", "apiclient_key") == "" { return fmt.Errorf("%s 退款要求同时配置 client_cert 和 client_key", provider) } - case PaymentApple: + case paymentutil.ProviderApple: if integrationInt64(values, "price_divisor", 0) <= 0 { if _, exists := values["price_divisors"].(map[string]any); !exists { return fmt.Errorf("%s 缺少配置字段 price_divisor 或 price_divisors", provider) } } - case PaymentDouyin: + case paymentutil.ProviderDouyin: if integrationFirst(values, "platform_serial_no", "platform_cert_serial") == "" { return fmt.Errorf("%s 缺少配置字段 platform_serial_no", provider) } - case PaymentQQ: + case paymentutil.ProviderQQ: if integrationFirst(values, "mch_id", "merchant_id") == "" { return fmt.Errorf("%s 缺少配置字段 mch_id", provider) } @@ -329,12 +330,12 @@ func validatePaymentIntegrationConfig(provider string, values map[string]any) er if signType != "" && signType != "MD5" && signType != "HMAC-SHA256" { return fmt.Errorf("%s sign_type 必须是 MD5 或 HMAC-SHA256", provider) } - case PaymentAllinPay: + case paymentutil.ProviderAllinPay: orderType := strings.ToLower(integrationFirst(values, "query_order_type", "order_type")) if orderType != "" && orderType != "reqsn" && orderType != "trxid" { return fmt.Errorf("%s query_order_type 必须是 reqsn 或 trxid", provider) } - case PaymentChinaums, PaymentSFT, PaymentSuperPay, PaymentWechatGame, PaymentDouyinGame: + case paymentutil.ProviderChinaums, paymentutil.ProviderSFT, paymentutil.ProviderSuperPay, paymentutil.ProviderWechatGame, paymentutil.ProviderDouyinGame: if integrationFirst(values, "app_key", "merchant_key", "signing_secret", "token") == "" { return fmt.Errorf("%s 缺少签名密钥", provider) } @@ -343,7 +344,7 @@ func validatePaymentIntegrationConfig(provider string, values map[string]any) er } } if environment := strings.ToLower(integrationText(values, "environment")); environment != "" { - allowedSandbox := provider != PaymentQQ && provider != PaymentDouyin && provider != PaymentLakala + allowedSandbox := provider != paymentutil.ProviderQQ && provider != paymentutil.ProviderDouyin && provider != paymentutil.ProviderLakala if environment != "production" && environment != "prod" && (!allowedSandbox || environment != "sandbox") { return fmt.Errorf("%s environment 配置无效", provider) } diff --git a/internal/biz/system/integration_config_communication_test.go b/internal/biz/integration/integration_config_communication_test.go similarity index 99% rename from internal/biz/system/integration_config_communication_test.go rename to internal/biz/integration/integration_config_communication_test.go index 12ef8ae..23d961e 100644 --- a/internal/biz/system/integration_config_communication_test.go +++ b/internal/biz/integration/integration_config_communication_test.go @@ -1,4 +1,4 @@ -package system +package integration import ( "context" diff --git a/internal/biz/system/integration_config_definition.go b/internal/biz/integration/integration_config_definition.go similarity index 81% rename from internal/biz/system/integration_config_definition.go rename to internal/biz/integration/integration_config_definition.go index a3fec2f..61640cb 100644 --- a/internal/biz/system/integration_config_definition.go +++ b/internal/biz/integration/integration_config_definition.go @@ -1,4 +1,6 @@ -package system +package integration + +import paymentutil "kra/pkg/paymentkit" func integrationField(key, label string, required, secret bool, fieldType string) IntegrationConfigField { if fieldType == "" { @@ -144,35 +146,35 @@ var integrationDefinitions = map[string][]IntegrationConfigDefinition{ }, }, IntegrationKindPayment: { - paymentDefinition(PaymentAlipay, "支付宝", "支付宝 OpenAPI RSA2 支付", map[string]any{"app_id": "", "private_key": "", "public_key": "", "environment": "production", "sign_type": "RSA2", "gateway_url": "https://openapi.alipay.com/gateway.do", "method": "alipay.trade.create"}, + paymentDefinition(paymentutil.ProviderAlipay, "支付宝", "支付宝 OpenAPI RSA2 支付", map[string]any{"app_id": "", "private_key": "", "public_key": "", "environment": "production", "sign_type": "RSA2", "gateway_url": "https://openapi.alipay.com/gateway.do", "method": "alipay.trade.create"}, integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "应用私钥", true, true, "textarea"), integrationField("public_key", "支付宝公钥", true, true, "textarea"), integrationSelect("environment", "环境", false, "production", "sandbox"), integrationSelect("sign_type", "签名算法", false, "RSA2", "RSA"), integrationField("gateway_url", "网关地址", false, false, "url"), integrationSelect("method", "默认支付方式", false, "alipay.trade.create", "alipay.trade.pay", "alipay.trade.precreate", "alipay.trade.app.pay", "alipay.trade.page.pay", "alipay.trade.wap.pay")), - paymentDefinition(PaymentAlipayV3, "支付宝 V3", "支付宝证书模式 V3 接口", map[string]any{"app_id": "", "private_key": "", "app_cert": "", "root_cert": "", "public_cert": "", "environment": "production", "api_base_url": "https://openapi.alipay.com", "gateway_url": "https://openapi.alipay.com/gateway.do", "method": "alipay.trade.create"}, + paymentDefinition(paymentutil.ProviderAlipayV3, "支付宝 V3", "支付宝证书模式 V3 接口", map[string]any{"app_id": "", "private_key": "", "app_cert": "", "root_cert": "", "public_cert": "", "environment": "production", "api_base_url": "https://openapi.alipay.com", "gateway_url": "https://openapi.alipay.com/gateway.do", "method": "alipay.trade.create"}, integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "应用私钥", false, true, "textarea"), integrationField("app_cert", "应用公钥证书", false, true, "textarea"), integrationField("root_cert", "支付宝根证书", false, true, "textarea"), integrationField("public_cert", "支付宝公钥证书", false, true, "textarea"), integrationSelect("environment", "环境", false, "production", "sandbox"), integrationField("api_base_url", "API 地址", false, false, "url"), integrationField("gateway_url", "网关地址", false, false, "url"), integrationSelect("method", "默认支付方式", false, "alipay.trade.create", "alipay.trade.pay", "alipay.trade.precreate", "alipay.trade.app.pay", "alipay.trade.page.pay", "alipay.trade.wap.pay")), - paymentDefinition(PaymentWechatV2, "微信支付 V2", "微信支付 V2,含退款双向证书", map[string]any{"app_id": "", "merchant_id": "", "mch_key": "", "sign_type": "MD5", "trade_type": "NATIVE", "client_cert": "", "client_key": ""}, + paymentDefinition(paymentutil.ProviderWechatV2, "微信支付 V2", "微信支付 V2,含退款双向证书", map[string]any{"app_id": "", "merchant_id": "", "mch_key": "", "sign_type": "MD5", "trade_type": "NATIVE", "client_cert": "", "client_key": ""}, integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("mch_key", "API 密钥", true, true, "password"), integrationSelect("sign_type", "签名算法", false, "MD5", "HMAC-SHA256"), integrationSelect("trade_type", "默认交易类型", false, "JSAPI", "APP", "NATIVE", "MWEB", "MICROPAY"), integrationField("client_cert", "商户证书", false, true, "textarea"), integrationField("client_key", "证书私钥", false, true, "textarea")), - paymentDefinition(PaymentWechatV3, "微信支付 V3", "微信支付 API v3", map[string]any{"app_id": "", "merchant_id": "", "serial_no": "", "private_key": "", "api_v3_key": "", "platform_cert": "", "platform_serial_no": "", "trade_type": "jsapi"}, + paymentDefinition(paymentutil.ProviderWechatV3, "微信支付 V3", "微信支付 API v3", map[string]any{"app_id": "", "merchant_id": "", "serial_no": "", "private_key": "", "api_v3_key": "", "platform_cert": "", "platform_serial_no": "", "trade_type": "jsapi"}, integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("serial_no", "商户证书序列号", true, false, "text"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("api_v3_key", "API v3 密钥", true, true, "password"), integrationField("platform_cert", "平台证书", true, true, "textarea"), integrationField("platform_serial_no", "平台证书序列号", false, false, "text"), integrationSelect("trade_type", "默认交易类型", false, "jsapi", "app", "native", "h5", "codepay")), - paymentDefinition(PaymentApple, "Apple IAP", "Apple App Store Server API", map[string]any{"issuer_id": "", "key_id": "", "bundle_id": "", "private_key": "", "price_divisor": 10, "environment": "production", "test_product_id": "", "test_transaction_id": ""}, + paymentDefinition(paymentutil.ProviderApple, "Apple IAP", "Apple App Store Server API", map[string]any{"issuer_id": "", "key_id": "", "bundle_id": "", "private_key": "", "price_divisor": 10, "environment": "production", "test_product_id": "", "test_transaction_id": ""}, integrationField("issuer_id", "Issuer ID", true, false, "text"), integrationField("key_id", "Key ID", true, false, "text"), integrationField("bundle_id", "Bundle ID", true, false, "text"), integrationField("private_key", "P8 私钥", true, true, "textarea"), integrationField("price_divisor", "价格除数", false, false, "number"), integrationSelect("environment", "环境", false, "production", "sandbox"), integrationField("test_product_id", "测试商品 ID", false, false, "text"), integrationField("test_transaction_id", "沙箱交易 ID", false, false, "text")), - paymentDefinition(PaymentDouyin, "抖音支付", "抖音开放平台支付", map[string]any{"app_id": "", "merchant_id": "", "serial_no": "", "api_key": "", "private_key": "", "platform_cert": "", "platform_serial_no": "", "trade_type": "jsapi", "environment": "production"}, + paymentDefinition(paymentutil.ProviderDouyin, "抖音支付", "抖音开放平台支付", map[string]any{"app_id": "", "merchant_id": "", "serial_no": "", "api_key": "", "private_key": "", "platform_cert": "", "platform_serial_no": "", "trade_type": "jsapi", "environment": "production"}, integrationField("app_id", "应用 ID", true, false, "text"), integrationField("merchant_id", "商户号", true, false, "text"), integrationField("serial_no", "商户证书序列号", true, false, "text"), integrationField("api_key", "API 密钥", true, true, "password"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("platform_cert", "平台证书", true, true, "textarea"), integrationField("platform_serial_no", "平台证书序列号", false, false, "text"), integrationSelect("trade_type", "默认交易类型", false, "app", "jsapi", "h5", "native"), integrationSelect("environment", "环境", false, "production")), - paymentDefinition(PaymentQQ, "QQ 钱包", "QQ 钱包支付", map[string]any{"mch_id": "", "api_key": "", "sign_type": "MD5", "trade_type": "NATIVE", "cert_file": "", "key_file": "", "environment": "production"}, + paymentDefinition(paymentutil.ProviderQQ, "QQ 钱包", "QQ 钱包支付", map[string]any{"mch_id": "", "api_key": "", "sign_type": "MD5", "trade_type": "NATIVE", "cert_file": "", "key_file": "", "environment": "production"}, integrationField("mch_id", "商户号", true, false, "text"), integrationField("api_key", "API 密钥", true, true, "password"), integrationSelect("sign_type", "签名算法", false, "MD5", "HMAC-SHA256"), integrationSelect("trade_type", "默认交易类型", false, "JSAPI", "NATIVE", "APP", "MICROPAY"), integrationField("cert_file", "退款证书路径", false, false, "text"), integrationField("key_file", "退款私钥路径", false, true, "text"), integrationSelect("environment", "环境", false, "production")), - paymentDefinition(PaymentAllinPay, "通联支付", "通联收银宝支付", map[string]any{"cus_id": "", "app_id": "", "private_key": "", "public_key": "", "org_id": "", "pay_type": "W02", "query_order_type": "reqsn", "currency": "CNY", "environment": "production"}, + paymentDefinition(paymentutil.ProviderAllinPay, "通联支付", "通联收银宝支付", map[string]any{"cus_id": "", "app_id": "", "private_key": "", "public_key": "", "org_id": "", "pay_type": "W02", "query_order_type": "reqsn", "currency": "CNY", "environment": "production"}, integrationField("cus_id", "商户号", true, false, "text"), integrationField("app_id", "应用 ID", true, false, "text"), integrationField("private_key", "商户私钥", true, true, "textarea"), integrationField("public_key", "平台公钥", true, true, "textarea"), integrationField("org_id", "机构号", false, false, "text"), integrationField("pay_type", "支付类型", false, false, "text"), integrationSelect("query_order_type", "查单标识", false, "reqsn", "trxid"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production", "sandbox")), - paymentDefinition(PaymentLakala, "拉卡拉", "拉卡拉聚合支付", map[string]any{"partner_code": "", "credential_code": "", "channel": "Wechat", "method": "jsapi", "currency": "CNY", "environment": "production"}, + paymentDefinition(paymentutil.ProviderLakala, "拉卡拉", "拉卡拉聚合支付", map[string]any{"partner_code": "", "credential_code": "", "channel": "Wechat", "method": "jsapi", "currency": "CNY", "environment": "production"}, integrationField("partner_code", "合作方编号", true, false, "text"), integrationField("credential_code", "凭证码", true, true, "password"), integrationSelect("channel", "支付渠道", false, "Wechat", "Alipay", "UnionPay"), integrationSelect("method", "默认支付方式", false, "jsapi", "h5", "mini", "native", "qrcode", "native_jsapi", "sdk", "web", "retail", "retail_qrcode"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production")), - paymentDefinition(PaymentPayPal, "PayPal", "PayPal Checkout", map[string]any{"client_id": "", "client_secret": "", "webhook_id": "", "environment": "sandbox", "return_url": "", "cancel_url": "", "auto_capture": true, "test_currency": "USD"}, + paymentDefinition(paymentutil.ProviderPayPal, "PayPal", "PayPal Checkout", map[string]any{"client_id": "", "client_secret": "", "webhook_id": "", "environment": "sandbox", "return_url": "", "cancel_url": "", "auto_capture": true, "test_currency": "USD"}, integrationField("client_id", "Client ID", true, false, "text"), integrationField("client_secret", "Client Secret", true, true, "password"), integrationField("webhook_id", "Webhook ID", true, false, "text"), integrationSelect("environment", "环境", false, "sandbox", "production"), integrationField("cancel_url", "取消跳转地址", false, false, "url"), integrationField("auto_capture", "自动捕获", false, false, "switch")), - paymentDefinition(PaymentSaobei, "扫呗", "扫呗聚合支付", map[string]any{"inst_no": "", "key": "", "merchant_no": "", "terminal_id": "", "access_token": "", "pay_type": "010", "currency": "CNY", "environment": "production"}, + paymentDefinition(paymentutil.ProviderSaobei, "扫呗", "扫呗聚合支付", map[string]any{"inst_no": "", "key": "", "merchant_no": "", "terminal_id": "", "access_token": "", "pay_type": "010", "currency": "CNY", "environment": "production"}, integrationField("inst_no", "机构号", true, false, "text"), integrationField("key", "机构密钥", true, true, "password"), integrationField("merchant_no", "商户号", true, false, "text"), integrationField("terminal_id", "终端号", true, false, "text"), integrationField("access_token", "访问令牌", true, true, "password"), integrationField("pay_type", "支付类型", false, false, "text"), integrationField("currency", "币种", false, false, "text"), integrationSelect("environment", "环境", false, "production", "sandbox")), - genericPaymentDefinition(PaymentChinaums, "银联商务", "按商户协议配置的银联商务适配器"), - genericPaymentDefinition(PaymentSFT, "商福通", "按商户协议配置的商福通适配器"), - genericPaymentDefinition(PaymentSuperPay, "Supper Pay", "按商户协议配置的 Supper Pay 适配器"), - genericPaymentDefinition(PaymentWechatGame, "微信小游戏支付", "微信小游戏虚拟支付配置驱动适配器"), - genericPaymentDefinition(PaymentDouyinGame, "抖音小游戏支付", "抖音小游戏支付配置驱动适配器"), + genericPaymentDefinition(paymentutil.ProviderChinaums, "银联商务", "按商户协议配置的银联商务适配器"), + genericPaymentDefinition(paymentutil.ProviderSFT, "商福通", "按商户协议配置的商福通适配器"), + genericPaymentDefinition(paymentutil.ProviderSuperPay, "Supper Pay", "按商户协议配置的 Supper Pay 适配器"), + genericPaymentDefinition(paymentutil.ProviderWechatGame, "微信小游戏支付", "微信小游戏虚拟支付配置驱动适配器"), + genericPaymentDefinition(paymentutil.ProviderDouyinGame, "抖音小游戏支付", "抖音小游戏支付配置驱动适配器"), }, } diff --git a/internal/biz/integration/provider.go b/internal/biz/integration/provider.go new file mode 100644 index 0000000..edf53bc --- /dev/null +++ b/internal/biz/integration/provider.go @@ -0,0 +1,6 @@ +package integration + +import "github.com/google/wire" + +// ProviderSet wires communication and integration-configuration usecases. +var ProviderSet = wire.NewSet(NewIntegrationConfigUsecase) diff --git a/internal/biz/system/payment.go b/internal/biz/payment/payment.go similarity index 99% rename from internal/biz/system/payment.go rename to internal/biz/payment/payment.go index 0508516..d369450 100644 --- a/internal/biz/system/payment.go +++ b/internal/biz/payment/payment.go @@ -1,4 +1,4 @@ -package system +package payment import ( "context" diff --git a/internal/biz/system/payment_log.go b/internal/biz/payment/payment_log.go similarity index 98% rename from internal/biz/system/payment_log.go rename to internal/biz/payment/payment_log.go index dba6ae6..abb9238 100644 --- a/internal/biz/system/payment_log.go +++ b/internal/biz/payment/payment_log.go @@ -1,4 +1,4 @@ -package system +package payment import ( "context" diff --git a/internal/biz/system/payment_order.go b/internal/biz/payment/payment_order.go similarity index 99% rename from internal/biz/system/payment_order.go rename to internal/biz/payment/payment_order.go index ac7cab0..1e9d576 100644 --- a/internal/biz/system/payment_order.go +++ b/internal/biz/payment/payment_order.go @@ -1,4 +1,4 @@ -package system +package payment import ( "context" diff --git a/internal/biz/system/payment_test.go b/internal/biz/payment/payment_test.go similarity index 99% rename from internal/biz/system/payment_test.go rename to internal/biz/payment/payment_test.go index ef20df7..2361ba7 100644 --- a/internal/biz/system/payment_test.go +++ b/internal/biz/payment/payment_test.go @@ -1,4 +1,4 @@ -package system +package payment import ( "context" diff --git a/internal/biz/payment/provider.go b/internal/biz/payment/provider.go new file mode 100644 index 0000000..4ed4dd7 --- /dev/null +++ b/internal/biz/payment/provider.go @@ -0,0 +1,8 @@ +package payment + +import "github.com/google/wire" + +// ProviderSet wires the payment domain usecase independently from other +// business domains. Persistence and transport layers consume the payment +// interfaces without depending on the system package. +var ProviderSet = wire.NewSet(NewPaymentUsecase) diff --git a/internal/biz/system/provider.go b/internal/biz/system/provider.go index 08e0773..722d2b6 100644 --- a/internal/biz/system/provider.go +++ b/internal/biz/system/provider.go @@ -24,11 +24,7 @@ var ProviderSet = wire.NewSet( NewAuditUsecase, NewAuditRecorderUsecase, NewLogViewerUsecase, - NewTaskUsecaseWithRegistry, - NewTaskApplicationUsecase, NewMediaUsecase, NewAnnouncementUsecase, NewEmailUsecase, - NewPaymentUsecase, - NewIntegrationConfigUsecase, ) diff --git a/internal/biz/system/system_init.go b/internal/biz/system/system_init.go index 0bdb924..78f7001 100644 --- a/internal/biz/system/system_init.go +++ b/internal/biz/system/system_init.go @@ -30,6 +30,8 @@ type InitializationRepo interface { DiskMountPoints() []string } +// TaskReloader is the narrow scheduler boundary needed after configuration +// changes. The consumer owns this interface; worker supplies the implementation. type TaskReloader interface { Reload(context.Context) error } diff --git a/internal/biz/task/provider.go b/internal/biz/task/provider.go new file mode 100644 index 0000000..4a108cb --- /dev/null +++ b/internal/biz/task/provider.go @@ -0,0 +1,9 @@ +package task + +import "github.com/google/wire" + +// ProviderSet wires timed-task usecases independently from the system domain. +var ProviderSet = wire.NewSet( + NewTaskUsecaseWithRegistry, + NewTaskApplicationUsecase, +) diff --git a/internal/biz/system/task.go b/internal/biz/task/task.go similarity index 99% rename from internal/biz/system/task.go rename to internal/biz/task/task.go index fbd552c..b368314 100644 --- a/internal/biz/system/task.go +++ b/internal/biz/task/task.go @@ -1,4 +1,4 @@ -package system +package task import ( "context" diff --git a/internal/biz/system/task_registry.go b/internal/biz/task/task_registry.go similarity index 98% rename from internal/biz/system/task_registry.go rename to internal/biz/task/task_registry.go index 93cc46f..c19fde0 100644 --- a/internal/biz/system/task_registry.go +++ b/internal/biz/task/task_registry.go @@ -1,4 +1,4 @@ -package system +package task import platformtask "kra/pkg/task" diff --git a/internal/biz/system/task_test.go b/internal/biz/task/task_test.go similarity index 99% rename from internal/biz/system/task_test.go rename to internal/biz/task/task_test.go index d01b5d8..70cb7ae 100644 --- a/internal/biz/system/task_test.go +++ b/internal/biz/task/task_test.go @@ -1,4 +1,4 @@ -package system +package task import ( "context" diff --git a/internal/data/integration_defaults.go b/internal/data/integration_defaults.go index f000791..0baca30 100644 --- a/internal/data/integration_defaults.go +++ b/internal/data/integration_defaults.go @@ -3,7 +3,7 @@ package data import ( "encoding/json" "errors" - "kra/internal/biz/system" + integrationbiz "kra/internal/biz/integration" "gorm.io/gorm" ) @@ -13,15 +13,15 @@ func ensureCommunicationIntegrationConfigs(db *gorm.DB) error { kind string provider string }{ - {kind: system.IntegrationKindMQ, provider: "emqx"}, - {kind: system.IntegrationKindMQ, provider: "rabbitmq"}, - {kind: system.IntegrationKindWebSocket, provider: "melody"}, + {kind: integrationbiz.IntegrationKindMQ, provider: "emqx"}, + {kind: integrationbiz.IntegrationKindMQ, provider: "rabbitmq"}, + {kind: integrationbiz.IntegrationKindWebSocket, provider: "melody"}, } for _, item := range defaults { var row integrationConfigPO err := db.Where("kind = ? AND provider = ?", item.kind, item.provider).First(&row).Error if errors.Is(err, gorm.ErrRecordNotFound) { - values, marshalErr := json.Marshal(system.DefaultIntegrationConfig(item.kind, item.provider)) + values, marshalErr := json.Marshal(integrationbiz.DefaultIntegrationConfig(item.kind, item.provider)) if marshalErr != nil { return marshalErr } diff --git a/internal/data/payment/payment.go b/internal/data/payment/payment.go index 2ef89fa..0bdf17d 100644 --- a/internal/data/payment/payment.go +++ b/internal/data/payment/payment.go @@ -7,7 +7,8 @@ import ( "encoding/json" "errors" "fmt" - "kra/internal/biz/system" + integrationbiz "kra/internal/biz/integration" + bizpayment "kra/internal/biz/payment" "net/url" "strconv" "strings" @@ -21,13 +22,13 @@ import ( type paymentRepo struct{ data Provider } -func NewPaymentRepo(data Provider) system.PaymentRepo { return &paymentRepo{data: data} } +func NewPaymentRepo(data Provider) bizpayment.PaymentRepo { return &paymentRepo{data: data} } func ensurePaymentIntegrationConfigs(db *gorm.DB) error { - for _, provider := range system.SupportedPaymentProviders { + for _, provider := range bizpayment.SupportedPaymentProviders { var row integrationConfigPO err := db.Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error - defaults := system.DefaultIntegrationConfig(integrationKindPayment, provider) + defaults := integrationbiz.DefaultIntegrationConfig(integrationKindPayment, provider) if errors.Is(err, gorm.ErrRecordNotFound) { encoded, _ := json.Marshal(defaults) if err := db.Create(&integrationConfigPO{Kind: integrationKindPayment, Provider: provider, Enabled: false, Config: string(encoded)}).Error; err != nil { @@ -61,7 +62,7 @@ func (r *paymentRepo) row(ctx context.Context, provider string) (*integrationCon var row integrationConfigPO if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, nil, system.ErrPaymentProviderNotFound + return nil, nil, bizpayment.ErrPaymentProviderNotFound } return nil, nil, err } @@ -84,11 +85,11 @@ func (r *paymentRepo) adapter(ctx context.Context, provider string) (datapayment return adapter, values, err } -func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*system.PaymentTestResult, error) { +func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*bizpayment.PaymentTestResult, error) { started := time.Now() - test := &system.PaymentTestResult{Provider: provider, TradeNo: "", Passed: false, Stages: []system.PaymentTestStage{}} + test := &bizpayment.PaymentTestResult{Provider: provider, TradeNo: "", Passed: false, Stages: []bizpayment.PaymentTestStage{}} add := func(name, status, message, tradeNo string, since time.Time) { - test.Stages = append(test.Stages, system.PaymentTestStage{Name: name, Status: status, Message: message, TradeNo: tradeNo, Duration: time.Since(since).Milliseconds()}) + test.Stages = append(test.Stages, bizpayment.PaymentTestStage{Name: name, Status: status, Message: message, TradeNo: tradeNo, Duration: time.Since(since).Milliseconds()}) } values, err := r.testRow(ctx, provider) if err != nil { @@ -97,7 +98,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste } test.Mode = strings.ToLower(strings.TrimSpace(text(values, "environment"))) configStart := time.Now() - if err = system.ValidateIntegrationConfig(system.IntegrationKindPayment, provider, values); err != nil { + if err = integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, provider, values); err != nil { add("config", "failed", err.Error(), "", configStart) return test, err } @@ -117,11 +118,11 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste orders := &paymentOrderRepo{data: r.data} extra, _ := json.Marshal(req.Extra) localStart := time.Now() - order, _, err := orders.CreatePaymentOrder(ctx, &system.PaymentOrder{ + order, _, err := orders.CreatePaymentOrder(ctx, &bizpayment.PaymentOrder{ TradeNo: req.TradeNo, Provider: provider, BusinessType: req.BusinessType, BusinessID: req.BusinessID, - Subject: req.Subject, PaymentMode: system.PaymentModeExternal, OriginalAmount: req.Amount, Amount: req.Amount, - Currency: req.Currency, PaymentStatus: system.PaymentStatusInitialized, FulfillmentStatus: system.FulfillmentStatusPending, - RefundStatus: system.RefundStatusNone, ConfirmationID: uuid.NewString(), RequestFingerprint: paymentTestFingerprint(req), Extra: extra, + Subject: req.Subject, PaymentMode: bizpayment.PaymentModeExternal, OriginalAmount: req.Amount, Amount: req.Amount, + Currency: req.Currency, PaymentStatus: bizpayment.PaymentStatusInitialized, FulfillmentStatus: bizpayment.FulfillmentStatusPending, + RefundStatus: bizpayment.RefundStatusNone, ConfirmationID: uuid.NewString(), RequestFingerprint: paymentTestFingerprint(req), Extra: extra, }) if err != nil { add("local_order", "failed", err.Error(), req.TradeNo, localStart) @@ -159,7 +160,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste if queryID == "" { queryID = req.TradeNo } - if provider == system.PaymentApple { + if provider == bizpayment.PaymentApple { queryID = strings.TrimSpace(text(values, "test_transaction_id")) if queryID == "" { err = errors.New("Apple 连通性测试需要配置 test_transaction_id(沙箱交易 ID)") @@ -179,7 +180,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste add("query", "failed", err.Error(), req.TradeNo, queryStart) return test, err } - if provider != system.PaymentApple { + if provider != bizpayment.PaymentApple { if order, err = orders.ApplyPaymentResult(ctx, provider, req.TradeNo, paymentTestProviderUpdate(queried)); err != nil { add("local_order", "failed", "回写测试查单结果失败: "+err.Error(), req.TradeNo, queryStart) return test, err @@ -193,9 +194,9 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste test.Result = queried add("query", "passed", "测试订单查询成功,状态: "+queried.Status, req.TradeNo, queryStart) - if queried.Status != "success" || provider == system.PaymentApple { + if queried.Status != "success" || provider == bizpayment.PaymentApple { message := "订单尚未支付成功,已完成配置、下单和查单连通性测试;请在沙箱完成付款后重试" - if provider == system.PaymentApple { + if provider == bizpayment.PaymentApple { message = "Apple 退款由 App Store 管理,已完成配置、下单和交易查询测试" } add("refund", "skipped", message, req.TradeNo, time.Now()) @@ -208,7 +209,7 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste add("refund", "failed", beginErr.Error(), req.TradeNo, refundStart) return test, beginErr } - refund, refundErr := adapter.Refund(ctx, &system.PaymentRefundRequest{Provider: provider, TradeNo: req.TradeNo, ProviderTradeNo: order.ProviderTradeNo, QueryID: order.QueryID, RefundNo: order.RefundNo, Amount: req.Amount, TotalAmount: req.Amount, Currency: req.Currency}, values) + refund, refundErr := adapter.Refund(ctx, &bizpayment.PaymentRefundRequest{Provider: provider, TradeNo: req.TradeNo, ProviderTradeNo: order.ProviderTradeNo, QueryID: order.QueryID, RefundNo: order.RefundNo, Amount: req.Amount, TotalAmount: req.Amount, Currency: req.Currency}, values) if refundErr != nil { recordPaymentTestError(ctx, r.data, provider, req.TradeNo, refundErr) add("refund", "failed", refundErr.Error(), req.TradeNo, refundStart) @@ -230,17 +231,17 @@ func (r *paymentRepo) TestProvider(ctx context.Context, provider string) (*syste return test, nil } -func paymentTestFingerprint(req *system.PaymentRequest) string { +func paymentTestFingerprint(req *bizpayment.PaymentRequest) string { raw, _ := json.Marshal(req) hash := sha256.Sum256(raw) return hex.EncodeToString(hash[:]) } -func paymentTestProviderUpdate(result *system.PaymentResult) *system.PaymentProviderUpdate { +func paymentTestProviderUpdate(result *bizpayment.PaymentResult) *bizpayment.PaymentProviderUpdate { if result == nil { return nil } - return &system.PaymentProviderUpdate{ + return &bizpayment.PaymentProviderUpdate{ Status: result.Status, ProviderStatus: result.Status, ProviderTradeNo: result.ProviderTradeNo, QueryID: result.QueryID, Amount: result.Amount, PayerPaidAmount: result.PayerPaidAmount, CashPaidAmount: result.CashPaidAmount, PointPaidAmount: result.PointPaidAmount, DiscountAmount: result.DiscountAmount, @@ -250,21 +251,21 @@ func paymentTestProviderUpdate(result *system.PaymentResult) *system.PaymentProv } } -func validatePaymentTestResult(provider, tradeNo string, result *system.PaymentResult) error { +func validatePaymentTestResult(provider, tradeNo string, result *bizpayment.PaymentResult) error { if result == nil { return errors.New("支付渠道响应为空") } if strings.TrimSpace(result.Provider) != provider { return errors.New("支付渠道响应的 provider 不匹配") } - if value := strings.TrimSpace(result.TradeNo); provider != system.PaymentApple && value != "" && value != tradeNo { + if value := strings.TrimSpace(result.TradeNo); provider != bizpayment.PaymentApple && value != "" && value != tradeNo { return errors.New("支付渠道响应的商户订单号不匹配") } return nil } -func queryPaymentTest(ctx context.Context, adapter datapayment.Adapter, queryID string, values map[string]any) (*system.PaymentResult, error) { - var result *system.PaymentResult +func queryPaymentTest(ctx context.Context, adapter datapayment.Adapter, queryID string, values map[string]any) (*bizpayment.PaymentResult, error) { + var result *bizpayment.PaymentResult var err error for attempt := 0; attempt < 3; attempt++ { result, err = adapter.Query(ctx, queryID, values) @@ -300,7 +301,7 @@ func (r *paymentRepo) testRow(ctx context.Context, provider string) (map[string] var row integrationConfigPO if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", integrationKindPayment, provider).First(&row).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, system.ErrPaymentProviderNotFound + return nil, bizpayment.ErrPaymentProviderNotFound } return nil, err } @@ -311,17 +312,17 @@ func (r *paymentRepo) testRow(ctx context.Context, provider string) (map[string] return values, nil } -func paymentTestRequest(provider string, values map[string]any) *system.PaymentRequest { +func paymentTestRequest(provider string, values map[string]any) *bizpayment.PaymentRequest { tradeNo := "kra-test-" + time.Now().UTC().Format("20060102150405.000000000") amount := configuredInt64(values, "test_amount", 1) if amount <= 0 { amount = 1 } - req := &system.PaymentRequest{Provider: provider, TradeNo: strings.ReplaceAll(tradeNo, ".", ""), Subject: "Kra 支付渠道连通性测试", Amount: amount, Currency: strings.ToUpper(firstAny(values, "test_currency", "currency", "fee_type")), NotifyURL: text(values, "notify_url"), ReturnURL: text(values, "return_url"), BusinessType: "system_payment_test", BusinessID: uuid.NewString(), Extra: map[string]any{}} + req := &bizpayment.PaymentRequest{Provider: provider, TradeNo: strings.ReplaceAll(tradeNo, ".", ""), Subject: "Kra 支付渠道连通性测试", Amount: amount, Currency: strings.ToUpper(firstAny(values, "test_currency", "currency", "fee_type")), NotifyURL: text(values, "notify_url"), ReturnURL: text(values, "return_url"), BusinessType: "system_payment_test", BusinessID: uuid.NewString(), Extra: map[string]any{}} if req.Currency == "" { req.Currency = "CNY" } - if provider == system.PaymentApple { + if provider == bizpayment.PaymentApple { req.TradeNo = uuid.NewString() req.Extra["product_id"] = firstAny(values, "product_id", "test_product_id") } @@ -351,10 +352,10 @@ func validatePaymentTestSettings(provider string, values map[string]any) error { return fmt.Errorf("test_extra 必须是 JSON 对象: %w", err) } } - if provider == system.PaymentApple && strings.TrimSpace(text(values, "test_transaction_id")) == "" { + if provider == bizpayment.PaymentApple && strings.TrimSpace(text(values, "test_transaction_id")) == "" { return errors.New("Apple 测试需要 test_transaction_id(沙箱交易 ID)") } - if provider == system.PaymentApple && strings.TrimSpace(firstAny(values, "test_product_id", "product_id")) == "" { + if provider == bizpayment.PaymentApple && strings.TrimSpace(firstAny(values, "test_product_id", "product_id")) == "" { return errors.New("Apple 测试需要 test_product_id(沙箱商品 ID)") } return nil @@ -377,7 +378,7 @@ func testModeEnabled(values map[string]any) bool { } } -func (r *paymentRepo) Create(ctx context.Context, req *system.PaymentRequest) (*system.PaymentResult, error) { +func (r *paymentRepo) Create(ctx context.Context, req *bizpayment.PaymentRequest) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("支付下单请求为空") } @@ -396,7 +397,7 @@ func (r *paymentRepo) Create(ctx context.Context, req *system.PaymentRequest) (* func paymentProviderRequiresNotifyURL(provider string) bool { switch provider { - case system.PaymentApple, system.PaymentAllinPay, system.PaymentSaobei, system.PaymentPayPal: + case bizpayment.PaymentApple, bizpayment.PaymentAllinPay, bizpayment.PaymentSaobei, bizpayment.PaymentPayPal: return false default: return true @@ -413,15 +414,15 @@ func paymentCreateRequiresNotifyURL(provider string, extra, config map[string]an } keys := []string{"method", "pay_method", "trade_type", "pay_type", "channel"} switch provider { - case system.PaymentAlipay, system.PaymentAlipayV3: + case bizpayment.PaymentAlipay, bizpayment.PaymentAlipayV3: keys = []string{"method", "pay_method", "trade_type", "channel"} - case system.PaymentWechatV2: + case bizpayment.PaymentWechatV2: keys = []string{"trade_type", "pay_type", "method", "pay_method", "channel"} - case system.PaymentWechatV3: + case bizpayment.PaymentWechatV3: keys = []string{"trade_type", "pay_type", "method"} - case system.PaymentQQ: + case bizpayment.PaymentQQ: keys = []string{"trade_type", "pay_type", "method", "pay_method"} - case system.PaymentLakala: + case bizpayment.PaymentLakala: keys = []string{"method", "pay_method", "trade_type"} } value := firstAny(extra, keys...) @@ -430,28 +431,28 @@ func paymentCreateRequiresNotifyURL(provider string, extra, config map[string]an } normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value))) switch provider { - case system.PaymentAlipay, system.PaymentAlipayV3: + case bizpayment.PaymentAlipay, bizpayment.PaymentAlipayV3: return !contains([]string{"pay", "trade_pay", "alipay_trade_pay", "barcode", "barcode_pay", "micropay", "face_to_face"}, normalized) - case system.PaymentWechatV2: + case bizpayment.PaymentWechatV2: return !contains([]string{"micropay", "micro_pay", "barcode", "barcode_pay", "pay_code", "payment_code"}, normalized) - case system.PaymentWechatV3: + case bizpayment.PaymentWechatV3: return !contains([]string{"micropay", "micro_pay", "codepay", "code_pay", "barcode", "barcode_pay", "facepay", "face_pay"}, normalized) - case system.PaymentQQ: + case bizpayment.PaymentQQ: return !contains([]string{"micropay", "micro_pay", "barcode", "barcode_pay"}, normalized) - case system.PaymentLakala: + case bizpayment.PaymentLakala: return !contains([]string{"retail", "retail_pay", "micropay", "barcode"}, normalized) default: return true } } -func (r *paymentRepo) Query(ctx context.Context, provider, tradeNo string) (*system.PaymentResult, error) { +func (r *paymentRepo) Query(ctx context.Context, provider, tradeNo string) (*bizpayment.PaymentResult, error) { a, c, err := r.adapter(ctx, provider) if err != nil { return nil, err } return a.Query(ctx, tradeNo, c) } -func (r *paymentRepo) Refund(ctx context.Context, req *system.PaymentRefundRequest) (*system.PaymentResult, error) { +func (r *paymentRepo) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("支付退款请求为空") } @@ -461,7 +462,7 @@ func (r *paymentRepo) Refund(ctx context.Context, req *system.PaymentRefundReque } return a.Refund(ctx, req, c) } -func (r *paymentRepo) HandleCallback(ctx context.Context, callback *system.PaymentCallback) (*system.PaymentResult, error) { +func (r *paymentRepo) HandleCallback(ctx context.Context, callback *bizpayment.PaymentCallback) (*bizpayment.PaymentResult, error) { if callback == nil { return nil, errors.New("支付回调为空") } @@ -471,23 +472,23 @@ func (r *paymentRepo) HandleCallback(ctx context.Context, callback *system.Payme } result, err := a.Callback(ctx, callback, c) if err != nil { - return nil, &system.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)} + return nil, &bizpayment.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)} } if result == nil { err = errors.New("支付回调解析结果为空") - return nil, &system.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)} + return nil, &bizpayment.PaymentCallbackError{Cause: err, Ack: paymentCallbackAck(callback.Provider, c, false)} } result.SuccessAck = paymentCallbackAck(callback.Provider, c, true) result.FailureAck = paymentCallbackAck(callback.Provider, c, false) if result.Provider != callback.Provider { err = errors.New("支付回调渠道不匹配") - return nil, &system.PaymentCallbackError{Cause: err, Ack: result.FailureAck} + return nil, &bizpayment.PaymentCallbackError{Cause: err, Ack: result.FailureAck} } result.EventID = paymentCallbackEventID(callback, result) return result, nil } -func paymentCallbackEventID(callback *system.PaymentCallback, result *system.PaymentResult) string { +func paymentCallbackEventID(callback *bizpayment.PaymentCallback, result *bizpayment.PaymentResult) string { if result != nil { if eventID := strings.TrimSpace(result.EventID); eventID != "" { return eventID @@ -504,8 +505,8 @@ func paymentCallbackEventID(callback *system.PaymentCallback, result *system.Pay return hex.EncodeToString(hash[:]) } -func paymentCallbackAck(provider string, values map[string]any, success bool) system.PaymentCallbackAck { - ack := system.DefaultPaymentCallbackAck(provider, success) +func paymentCallbackAck(provider string, values map[string]any, success bool) bizpayment.PaymentCallbackAck { + ack := bizpayment.DefaultPaymentCallbackAck(provider, success) prefix := "callback_success_" if !success { prefix = "callback_failure_" @@ -524,7 +525,7 @@ func paymentCallbackAck(provider string, values map[string]any, success bool) sy return ack } -func callbackFields(callback *system.PaymentCallback) map[string]string { +func callbackFields(callback *bizpayment.PaymentCallback) map[string]string { fields := map[string]string{} for key, value := range callback.Query { fields[key] = value @@ -569,5 +570,5 @@ func contains(values []string, value string) bool { } func validatePaymentConfig(provider string, values map[string]any) error { - return system.ValidateIntegrationConfig(system.IntegrationKindPayment, provider, values) + return integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, provider, values) } diff --git a/internal/data/payment/payment_config_test.go b/internal/data/payment/payment_config_test.go index 1a6b3c9..702e23f 100644 --- a/internal/data/payment/payment_config_test.go +++ b/internal/data/payment/payment_config_test.go @@ -1,7 +1,8 @@ package payment import ( - "kra/internal/biz/system" + integrationbiz "kra/internal/biz/integration" + bizpayment "kra/internal/biz/payment" "strings" "testing" ) @@ -11,18 +12,18 @@ func TestValidatePaymentConfigRequiresDouyinAppIDWhenEnabled(t *testing.T) { "merchant_id": "merchant-douyin", "serial_no": "merchant-serial", "api_key": "01234567890123456789012345678901", "private_key": "merchant-private-key", "platform_cert": "platform-public-key", "platform_serial_no": "platform-serial", } - err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentDouyin, values) + err := integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, bizpayment.PaymentDouyin, values) if err == nil || !strings.Contains(err.Error(), "app_id") { t.Fatalf("missing app_id error = %v", err) } values["app_id"] = "douyin-app" - if err = system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentDouyin, values); err != nil { + if err = integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, bizpayment.PaymentDouyin, values); err != nil { t.Fatalf("valid Douyin configuration rejected: %v", err) } } func TestValidatePaymentConfigAcceptsProviderAliases(t *testing.T) { - err := validatePaymentConfig(system.PaymentDouyin, map[string]any{ + err := validatePaymentConfig(bizpayment.PaymentDouyin, map[string]any{ "app_id": "douyin-app", "merchant_id": "merchant-douyin", "serial_no": "merchant-serial", "api_key": "01234567890123456789012345678901", "private_key": "merchant-private-key", "platform_cert": "platform-public-key", "platform_cert_serial": "platform-serial", }) @@ -36,13 +37,13 @@ func TestValidatePaymentConfigProviderRules(t *testing.T) { name, provider, want string values map[string]any }{ - {"allinpay order type", system.PaymentAllinPay, "reqsn", map[string]any{"cus_id": "customer", "app_id": "app", "private_key": "private-key", "public_key": "public-key", "query_order_type": "payinfo"}}, - {"paypal webhook", system.PaymentPayPal, "webhook_id", map[string]any{"client_id": "client-id", "client_secret": "client-secret"}}, - {"wechat v2 refund cert", system.PaymentWechatV2, "client_cert", map[string]any{"app_id": "app", "merchant_id": "merchant", "mch_key": "key"}}, + {"allinpay order type", bizpayment.PaymentAllinPay, "reqsn", map[string]any{"cus_id": "customer", "app_id": "app", "private_key": "private-key", "public_key": "public-key", "query_order_type": "payinfo"}}, + {"paypal webhook", bizpayment.PaymentPayPal, "webhook_id", map[string]any{"client_id": "client-id", "client_secret": "client-secret"}}, + {"wechat v2 refund cert", bizpayment.PaymentWechatV2, "client_cert", map[string]any{"app_id": "app", "merchant_id": "merchant", "mch_key": "key"}}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, test.provider, test.values) + err := integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, test.provider, test.values) if err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("error = %v, want %q", err, test.want) } @@ -51,13 +52,13 @@ func TestValidatePaymentConfigProviderRules(t *testing.T) { } func TestValidatePaymentConfigGenericRequiresRuntimeFields(t *testing.T) { - values := system.DefaultIntegrationConfig(system.IntegrationKindPayment, system.PaymentChinaums) - if err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentChinaums, values); err == nil { + values := integrationbiz.DefaultIntegrationConfig(integrationbiz.IntegrationKindPayment, bizpayment.PaymentChinaums) + if err := integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, bizpayment.PaymentChinaums, values); err == nil { t.Fatal("empty generic payment config unexpectedly accepted") } values["app_id"], values["merchant_id"] = "app", "merchant" values["create_url"], values["query_url"], values["refund_url"], values["app_key"] = "https://pay.test/create", "https://pay.test/query", "https://pay.test/refund", "secret" - if err := system.ValidateIntegrationConfig(system.IntegrationKindPayment, system.PaymentChinaums, values); err == nil { + if err := integrationbiz.ValidateIntegrationConfig(integrationbiz.IntegrationKindPayment, bizpayment.PaymentChinaums, values); err == nil { t.Fatal("generic config with only identity/endpoints unexpectedly accepted") } } diff --git a/internal/data/payment/payment_native_test.go b/internal/data/payment/payment_native_test.go index 80f64aa..0a94d03 100644 --- a/internal/data/payment/payment_native_test.go +++ b/internal/data/payment/payment_native_test.go @@ -1,15 +1,16 @@ package payment import ( - "kra/internal/biz/system" + integrationbiz "kra/internal/biz/integration" + bizpayment "kra/internal/biz/payment" "strings" "testing" ) func TestPaymentDefinitionsProvideNonEmptyDefaults(t *testing.T) { - definitions := system.IntegrationDefinitions(system.IntegrationKindPayment) - if len(definitions) != len(system.SupportedPaymentProviders) { - t.Fatalf("payment definitions = %d, want %d", len(definitions), len(system.SupportedPaymentProviders)) + definitions := integrationbiz.IntegrationDefinitions(integrationbiz.IntegrationKindPayment) + if len(definitions) != len(bizpayment.SupportedPaymentProviders) { + t.Fatalf("payment definitions = %d, want %d", len(definitions), len(bizpayment.SupportedPaymentProviders)) } for _, definition := range definitions { if definition.Provider == "" || definition.Name == "" || len(definition.Fields) == 0 { diff --git a/internal/data/payment/payment_order.go b/internal/data/payment/payment_order.go index 5cbc025..7513c96 100644 --- a/internal/data/payment/payment_order.go +++ b/internal/data/payment/payment_order.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "errors" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "strings" "time" @@ -68,11 +68,11 @@ func (paymentOrderPO) TableName() string { return "pay_orders" } type paymentOrderRepo struct{ data Provider } -func NewPaymentOrderRepo(data Provider) system.PaymentOrderRepo { +func NewPaymentOrderRepo(data Provider) bizpayment.PaymentOrderRepo { return &paymentOrderRepo{data: data} } -func newPaymentOrderPO(order *system.PaymentOrder) (*paymentOrderPO, error) { +func newPaymentOrderPO(order *bizpayment.PaymentOrder) (*paymentOrderPO, error) { if order == nil { return nil, errors.New("支付订单为空") } @@ -85,16 +85,16 @@ func newPaymentOrderPO(order *system.PaymentOrder) (*paymentOrderPO, error) { ID: order.ID, TradeNo: order.TradeNo, Provider: order.Provider, ProviderTradeNo: optionalString(order.ProviderTradeNo), QueryID: order.QueryID, BusinessType: order.BusinessType, BusinessID: order.BusinessID, Subject: order.Subject, - PaymentMode: defaultString(order.PaymentMode, system.PaymentModeExternal), OriginalAmount: order.OriginalAmount, + PaymentMode: defaultString(order.PaymentMode, bizpayment.PaymentModeExternal), OriginalAmount: order.OriginalAmount, Amount: order.Amount, PaidAmount: order.PaidAmount, PayerPaidAmount: order.PayerPaidAmount, CashPaidAmount: order.CashPaidAmount, PointPaidAmount: order.PointPaidAmount, DiscountAmount: order.DiscountAmount, ProviderDiscountAmount: order.ProviderDiscountAmount, MerchantDiscountAmount: order.MerchantDiscountAmount, SettlementAmount: order.SettlementAmount, Currency: order.Currency, PayerCurrency: order.PayerCurrency, AmountBreakdownKnown: order.AmountBreakdownKnown, - PaymentStatus: defaultString(order.PaymentStatus, system.PaymentStatusInitialized), + PaymentStatus: defaultString(order.PaymentStatus, bizpayment.PaymentStatusInitialized), ProviderStatus: order.ProviderStatus, - FulfillmentStatus: defaultString(order.FulfillmentStatus, system.FulfillmentStatusPending), - RefundStatus: defaultString(order.RefundStatus, system.RefundStatusNone), + FulfillmentStatus: defaultString(order.FulfillmentStatus, bizpayment.FulfillmentStatusPending), + RefundStatus: defaultString(order.RefundStatus, bizpayment.RefundStatusNone), RefundedAmount: order.RefundedAmount, RefundRequestedAmount: order.RefundRequestedAmount, RefundNo: order.RefundNo, ConfirmationID: order.ConfirmationID, RequestFingerprint: order.RequestFingerprint, CreatePayload: createPayload, Extra: extra, LastEventID: order.LastEventID, @@ -105,15 +105,15 @@ func newPaymentOrderPO(order *system.PaymentOrder) (*paymentOrderPO, error) { }, nil } -func toBizPaymentOrder(po *paymentOrderPO) *system.PaymentOrder { +func toBizPaymentOrder(po *paymentOrderPO) *bizpayment.PaymentOrder { if po == nil { return nil } - return &system.PaymentOrder{ + return &bizpayment.PaymentOrder{ ID: po.ID, TradeNo: po.TradeNo, Provider: po.Provider, ProviderTradeNo: dereferenceString(po.ProviderTradeNo), QueryID: po.QueryID, BusinessType: po.BusinessType, BusinessID: po.BusinessID, Subject: po.Subject, - PaymentMode: defaultString(po.PaymentMode, system.PaymentModeExternal), OriginalAmount: po.OriginalAmount, + PaymentMode: defaultString(po.PaymentMode, bizpayment.PaymentModeExternal), OriginalAmount: po.OriginalAmount, Amount: po.Amount, PaidAmount: po.PaidAmount, PayerPaidAmount: po.PayerPaidAmount, CashPaidAmount: po.CashPaidAmount, PointPaidAmount: po.PointPaidAmount, DiscountAmount: po.DiscountAmount, ProviderDiscountAmount: po.ProviderDiscountAmount, MerchantDiscountAmount: po.MerchantDiscountAmount, @@ -131,7 +131,7 @@ func toBizPaymentOrder(po *paymentOrderPO) *system.PaymentOrder { } } -func (r *paymentOrderRepo) CreatePaymentOrder(ctx context.Context, order *system.PaymentOrder) (*system.PaymentOrder, bool, error) { +func (r *paymentOrderRepo) CreatePaymentOrder(ctx context.Context, order *bizpayment.PaymentOrder) (*bizpayment.PaymentOrder, bool, error) { po, err := newPaymentOrderPO(order) if err != nil { return nil, false, err @@ -154,18 +154,18 @@ func (r *paymentOrderRepo) CreatePaymentOrder(ctx context.Context, order *system return nil, false, err } -func (r *paymentOrderRepo) FindPaymentOrder(ctx context.Context, provider, tradeNo string) (*system.PaymentOrder, error) { +func (r *paymentOrderRepo) FindPaymentOrder(ctx context.Context, provider, tradeNo string) (*bizpayment.PaymentOrder, error) { var po paymentOrderPO if err := r.data.DB().WithContext(ctx).Where("provider = ? AND trade_no = ?", provider, tradeNo).First(&po).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, system.ErrPaymentOrderNotFound + return nil, bizpayment.ErrPaymentOrderNotFound } return nil, err } return toBizPaymentOrder(&po), nil } -func (r *paymentOrderRepo) ListPaymentOrders(ctx context.Context, page, pageSize int, filter system.PaymentOrderFilter) ([]*system.PaymentOrder, int64, error) { +func (r *paymentOrderRepo) ListPaymentOrders(ctx context.Context, page, pageSize int, filter bizpayment.PaymentOrderFilter) ([]*bizpayment.PaymentOrder, int64, error) { db := r.data.DB().WithContext(ctx).Model(&paymentOrderPO{}) if value := strings.TrimSpace(filter.Provider); value != "" { db = db.Where("provider = ?", value) @@ -193,14 +193,14 @@ func (r *paymentOrderRepo) ListPaymentOrders(ctx context.Context, page, pageSize if err := pagination.ApplyRequired(db.Order("id desc"), page, pageSize, 100).Find(&rows).Error; err != nil { return nil, 0, err } - items := make([]*system.PaymentOrder, 0, len(rows)) + items := make([]*bizpayment.PaymentOrder, 0, len(rows)) for i := range rows { items = append(items, toBizPaymentOrder(&rows[i])) } return items, total, nil } -func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tradeNo string, update *system.PaymentProviderUpdate) (*system.PaymentOrder, error) { +func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tradeNo string, update *bizpayment.PaymentProviderUpdate) (*bizpayment.PaymentOrder, error) { return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { if update == nil { return errors.New("支付下单结果为空") @@ -215,11 +215,11 @@ func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tr po.LastEventID = trimTo(update.EventID, 128) po.LastPayloadHash = trimTo(update.PayloadHash, 64) status := normalizeOrderPaymentStatus(update.Status) - if status == system.PaymentStatusPaid { + if status == bizpayment.PaymentStatusPaid { // Provider create responses are never sufficient proof of payment. - status = system.PaymentStatusPending + status = bizpayment.PaymentStatusPending } - if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded && status != "" { + if po.PaymentStatus != bizpayment.PaymentStatusPaid && po.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded && po.PaymentStatus != bizpayment.PaymentStatusRefunded && status != "" { po.PaymentStatus = status } po.Version++ @@ -227,7 +227,7 @@ func (r *paymentOrderRepo) RecordPaymentCreate(ctx context.Context, provider, tr }) } -func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tradeNo string, update *system.PaymentProviderUpdate) (*system.PaymentOrder, error) { +func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tradeNo string, update *bizpayment.PaymentProviderUpdate) (*bizpayment.PaymentOrder, error) { return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { if update == nil { return errors.New("支付查单结果为空") @@ -236,13 +236,13 @@ func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tra return err } if update.Amount > 0 && update.Amount != po.Amount { - return system.ErrPaymentOrderConflict + return bizpayment.ErrPaymentOrderConflict } if update.PayerPaidAmount < 0 || update.CashPaidAmount < 0 || update.PointPaidAmount < 0 || update.DiscountAmount < 0 || update.ProviderDiscountAmount < 0 || update.MerchantDiscountAmount < 0 || update.SettlementAmount < 0 { - return system.ErrPaymentOrderConflict + return bizpayment.ErrPaymentOrderConflict } if update.Currency != "" && !strings.EqualFold(update.Currency, po.Currency) { - return system.ErrPaymentOrderConflict + return bizpayment.ErrPaymentOrderConflict } po.ProviderStatus = trimTo(update.ProviderStatus, 64) po.LastEventID = trimTo(update.EventID, 128) @@ -259,22 +259,22 @@ func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tra po.AmountBreakdownKnown = true } switch normalizeOrderPaymentStatus(update.Status) { - case system.PaymentStatusPaid: - if po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded { - po.PaymentStatus = system.PaymentStatusPaid + case bizpayment.PaymentStatusPaid: + if po.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded && po.PaymentStatus != bizpayment.PaymentStatusRefunded { + po.PaymentStatus = bizpayment.PaymentStatusPaid } po.PaidAmount = po.Amount if po.PaidAt == nil { now := time.Now().UTC() po.PaidAt = &now } - case system.PaymentStatusPending: - if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded { - po.PaymentStatus = system.PaymentStatusPending + case bizpayment.PaymentStatusPending: + if po.PaymentStatus != bizpayment.PaymentStatusPaid && po.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded && po.PaymentStatus != bizpayment.PaymentStatusRefunded { + po.PaymentStatus = bizpayment.PaymentStatusPending } - case system.PaymentStatusFailed: - if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded && po.PaymentStatus != system.PaymentStatusRefunded { - po.PaymentStatus = system.PaymentStatusFailed + case bizpayment.PaymentStatusFailed: + if po.PaymentStatus != bizpayment.PaymentStatusPaid && po.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded && po.PaymentStatus != bizpayment.PaymentStatusRefunded { + po.PaymentStatus = bizpayment.PaymentStatusFailed } } po.Version++ @@ -282,27 +282,27 @@ func (r *paymentOrderRepo) ApplyPaymentResult(ctx context.Context, provider, tra }) } -func (r *paymentOrderRepo) BeginPaymentFulfillment(ctx context.Context, provider, tradeNo string, lease time.Duration) (*system.PaymentOrder, string, bool, error) { +func (r *paymentOrderRepo) BeginPaymentFulfillment(ctx context.Context, provider, tradeNo string, lease time.Duration) (*bizpayment.PaymentOrder, string, bool, error) { var token string var duplicate bool order, err := r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { - if po.FulfillmentStatus == system.FulfillmentStatusSucceeded { + if po.FulfillmentStatus == bizpayment.FulfillmentStatusSucceeded { duplicate = true return nil } - if po.PaymentStatus != system.PaymentStatusPaid { - return system.ErrPaymentOrderState + if po.PaymentStatus != bizpayment.PaymentStatusPaid { + return bizpayment.ErrPaymentOrderState } now := time.Now().UTC() - if po.FulfillmentStatus == system.FulfillmentStatusProcessing && po.FulfillmentLeaseUntil != nil && po.FulfillmentLeaseUntil.After(now) { - return system.ErrPaymentOrderBusy + if po.FulfillmentStatus == bizpayment.FulfillmentStatusProcessing && po.FulfillmentLeaseUntil != nil && po.FulfillmentLeaseUntil.After(now) { + return bizpayment.ErrPaymentOrderBusy } if lease <= 0 { lease = 10 * time.Minute } token = uuid.NewString() until := now.Add(lease) - po.FulfillmentStatus = system.FulfillmentStatusProcessing + po.FulfillmentStatus = bizpayment.FulfillmentStatusProcessing po.FulfillmentToken = token po.FulfillmentLeaseUntil = &until po.LastError = "" @@ -312,56 +312,56 @@ func (r *paymentOrderRepo) BeginPaymentFulfillment(ctx context.Context, provider return order, token, duplicate, err } -func (r *paymentOrderRepo) CompletePaymentFulfillment(ctx context.Context, provider, tradeNo, token string, success bool, message string) (*system.PaymentOrder, error) { +func (r *paymentOrderRepo) CompletePaymentFulfillment(ctx context.Context, provider, tradeNo, token string, success bool, message string) (*bizpayment.PaymentOrder, error) { return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { - if po.FulfillmentStatus != system.FulfillmentStatusProcessing || po.FulfillmentToken != token { - return system.ErrPaymentOrderBusy + if po.FulfillmentStatus != bizpayment.FulfillmentStatusProcessing || po.FulfillmentToken != token { + return bizpayment.ErrPaymentOrderBusy } po.FulfillmentToken = "" po.FulfillmentLeaseUntil = nil po.LastError = trimTo(message, 512) if success { - po.FulfillmentStatus = system.FulfillmentStatusSucceeded + po.FulfillmentStatus = bizpayment.FulfillmentStatusSucceeded now := time.Now().UTC() po.FulfilledAt = &now } else { - po.FulfillmentStatus = system.FulfillmentStatusFailed + po.FulfillmentStatus = bizpayment.FulfillmentStatusFailed } po.Version++ return tx.Save(po).Error }) } -func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tradeNo string, amount int64, lease time.Duration) (*system.PaymentOrder, string, error) { +func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tradeNo string, amount int64, lease time.Duration) (*bizpayment.PaymentOrder, string, error) { var token string order, err := r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { - if po.PaymentStatus != system.PaymentStatusPaid && po.PaymentStatus != system.PaymentStatusPartiallyRefunded { - return system.ErrPaymentOrderState + if po.PaymentStatus != bizpayment.PaymentStatusPaid && po.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded { + return bizpayment.ErrPaymentOrderState } if amount <= 0 { - return system.ErrPaymentOrderConflict + return bizpayment.ErrPaymentOrderConflict } now := time.Now().UTC() - if po.RefundStatus == system.RefundStatusProcessing && po.RefundLeaseUntil != nil && po.RefundLeaseUntil.After(now) { - return system.ErrPaymentOrderBusy + if po.RefundStatus == bizpayment.RefundStatusProcessing && po.RefundLeaseUntil != nil && po.RefundLeaseUntil.After(now) { + return bizpayment.ErrPaymentOrderBusy } - if po.RefundStatus == system.RefundStatusProcessing && po.RefundRequestedAmount != amount { - return system.ErrPaymentOrderConflict + if po.RefundStatus == bizpayment.RefundStatusProcessing && po.RefundRequestedAmount != amount { + return bizpayment.ErrPaymentOrderConflict } - if po.RefundStatus == system.RefundStatusPending { - return system.ErrPaymentOrderBusy + if po.RefundStatus == bizpayment.RefundStatusPending { + return bizpayment.ErrPaymentOrderBusy } // An expired processing lease means the provider outcome is unknown. // Retry the same refund amount with the same durable refund number. A // different amount must never reuse that operation identity. reserved := int64(0) - if po.RefundStatus == system.RefundStatusProcessing && po.RefundLeaseUntil != nil && !po.RefundLeaseUntil.After(now) { + if po.RefundStatus == bizpayment.RefundStatusProcessing && po.RefundLeaseUntil != nil && !po.RefundLeaseUntil.After(now) { reserved = 0 } else { reserved = po.RefundRequestedAmount } if amount > po.Amount-po.RefundedAmount-reserved { - return system.ErrPaymentOrderConflict + return bizpayment.ErrPaymentOrderConflict } if lease <= 0 { lease = 10 * time.Minute @@ -371,7 +371,7 @@ func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tra po.RefundNo = uuid.NewString() } until := now.Add(lease) - po.RefundStatus = system.RefundStatusProcessing + po.RefundStatus = bizpayment.RefundStatusProcessing po.RefundRequestedAmount = amount po.RefundToken = token po.RefundLeaseUntil = &until @@ -382,18 +382,18 @@ func (r *paymentOrderRepo) BeginPaymentRefund(ctx context.Context, provider, tra return order, token, err } -func (r *paymentOrderRepo) CompletePaymentRefundRequest(ctx context.Context, provider, tradeNo, token string, accepted bool, message string) (*system.PaymentOrder, error) { +func (r *paymentOrderRepo) CompletePaymentRefundRequest(ctx context.Context, provider, tradeNo, token string, accepted bool, message string) (*bizpayment.PaymentOrder, error) { return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { - if po.RefundStatus != system.RefundStatusProcessing || po.RefundToken != token { - return system.ErrPaymentOrderBusy + if po.RefundStatus != bizpayment.RefundStatusProcessing || po.RefundToken != token { + return bizpayment.ErrPaymentOrderBusy } po.RefundToken = "" po.RefundLeaseUntil = nil po.LastError = trimTo(message, 512) if accepted { - po.RefundStatus = system.RefundStatusPending + po.RefundStatus = bizpayment.RefundStatusPending } else { - po.RefundStatus = system.RefundStatusFailed + po.RefundStatus = bizpayment.RefundStatusFailed po.RefundRequestedAmount = 0 po.RefundNo = "" } @@ -402,26 +402,26 @@ func (r *paymentOrderRepo) CompletePaymentRefundRequest(ctx context.Context, pro }) } -func (r *paymentOrderRepo) ConfirmPaymentRefund(ctx context.Context, provider, tradeNo, refundNo string, amount int64, success bool, message string) (*system.PaymentOrder, error) { +func (r *paymentOrderRepo) ConfirmPaymentRefund(ctx context.Context, provider, tradeNo, refundNo string, amount int64, success bool, message string) (*bizpayment.PaymentOrder, error) { return r.withLockedOrder(ctx, provider, tradeNo, func(tx *gorm.DB, po *paymentOrderPO) error { - if po.RefundStatus != system.RefundStatusPending || po.RefundNo == "" || po.RefundNo != refundNo || po.RefundRequestedAmount != amount { - return system.ErrPaymentOrderState + if po.RefundStatus != bizpayment.RefundStatusPending || po.RefundNo == "" || po.RefundNo != refundNo || po.RefundRequestedAmount != amount { + return bizpayment.ErrPaymentOrderState } po.LastError = trimTo(message, 512) po.RefundRequestedAmount = 0 if !success { - po.RefundStatus = system.RefundStatusFailed + po.RefundStatus = bizpayment.RefundStatusFailed po.RefundNo = "" po.Version++ return tx.Save(po).Error } po.RefundedAmount += amount if po.RefundedAmount >= po.Amount { - po.PaymentStatus = system.PaymentStatusRefunded - po.RefundStatus = system.RefundStatusSucceeded + po.PaymentStatus = bizpayment.PaymentStatusRefunded + po.RefundStatus = bizpayment.RefundStatusSucceeded } else { - po.PaymentStatus = system.PaymentStatusPartiallyRefunded - po.RefundStatus = system.RefundStatusPartial + po.PaymentStatus = bizpayment.PaymentStatusPartiallyRefunded + po.RefundStatus = bizpayment.RefundStatusPartial } po.RefundNo = "" now := time.Now().UTC() @@ -431,13 +431,13 @@ func (r *paymentOrderRepo) ConfirmPaymentRefund(ctx context.Context, provider, t }) } -func (r *paymentOrderRepo) withLockedOrder(ctx context.Context, provider, tradeNo string, fn func(*gorm.DB, *paymentOrderPO) error) (*system.PaymentOrder, error) { - var result *system.PaymentOrder +func (r *paymentOrderRepo) withLockedOrder(ctx context.Context, provider, tradeNo string, fn func(*gorm.DB, *paymentOrderPO) error) (*bizpayment.PaymentOrder, error) { + var result *bizpayment.PaymentOrder err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { var po paymentOrderPO if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("provider = ? AND trade_no = ?", provider, tradeNo).First(&po).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return system.ErrPaymentOrderNotFound + return bizpayment.ErrPaymentOrderNotFound } return err } @@ -450,16 +450,16 @@ func (r *paymentOrderRepo) withLockedOrder(ctx context.Context, provider, tradeN return result, err } -func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *system.PaymentProviderUpdate) error { +func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *bizpayment.PaymentProviderUpdate) error { if update.ProviderTradeNo != "" { if po.ProviderTradeNo != nil && *po.ProviderTradeNo != update.ProviderTradeNo { - return system.ErrPaymentProviderConflict + return bizpayment.ErrPaymentProviderConflict } var other paymentOrderPO // This check is repeated under the order transaction so a platform // transaction cannot be attached to a different merchant order. if err := tx.Where("provider = ? AND provider_trade_no = ? AND id <> ?", po.Provider, update.ProviderTradeNo, po.ID).First(&other).Error; err == nil { - return system.ErrPaymentProviderConflict + return bizpayment.ErrPaymentProviderConflict } else if !errors.Is(err, gorm.ErrRecordNotFound) { return err } @@ -468,7 +468,7 @@ func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *system.Payme } if update.QueryID != "" { if po.QueryID != "" && po.QueryID != update.QueryID { - return system.ErrPaymentProviderConflict + return bizpayment.ErrPaymentProviderConflict } po.QueryID = update.QueryID } @@ -478,11 +478,11 @@ func applyProviderIdentity(tx *gorm.DB, po *paymentOrderPO, update *system.Payme func normalizeOrderPaymentStatus(value string) string { switch strings.ToLower(strings.TrimSpace(value)) { case "success", "paid", "fulfilled": - return system.PaymentStatusPaid + return bizpayment.PaymentStatusPaid case "pending", "created", "client_pending", "processing": - return system.PaymentStatusPending + return bizpayment.PaymentStatusPending case "failed", "closed", "cancelled", "canceled": - return system.PaymentStatusFailed + return bizpayment.PaymentStatusFailed default: return "" } diff --git a/internal/data/payment/payment_order_test.go b/internal/data/payment/payment_order_test.go index bfb2827..c9772fb 100644 --- a/internal/data/payment/payment_order_test.go +++ b/internal/data/payment/payment_order_test.go @@ -2,7 +2,7 @@ package payment import ( "context" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "testing" "time" ) @@ -19,11 +19,11 @@ func newPaymentOrderRepoForTest(t *testing.T) *paymentOrderRepo { return &paymentOrderRepo{data: &Data{gormDB: newReloadableDB(db, nil)}} } -func testPaymentOrder() *system.PaymentOrder { - return &system.PaymentOrder{ - TradeNo: "order-1", Provider: system.PaymentAlipay, BusinessType: "game_item", BusinessID: "item-1", - Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: system.PaymentStatusInitialized, - FulfillmentStatus: system.FulfillmentStatusPending, RefundStatus: system.RefundStatusNone, +func testPaymentOrder() *bizpayment.PaymentOrder { + return &bizpayment.PaymentOrder{ + TradeNo: "order-1", Provider: bizpayment.PaymentAlipay, BusinessType: "game_item", BusinessID: "item-1", + Subject: "item", Amount: 100, Currency: "CNY", PaymentStatus: bizpayment.PaymentStatusInitialized, + FulfillmentStatus: bizpayment.FulfillmentStatusPending, RefundStatus: bizpayment.RefundStatusNone, ConfirmationID: "11111111-1111-1111-1111-111111111111", RequestFingerprint: "fingerprint", } } @@ -35,36 +35,36 @@ func TestPaymentOrderRepositoryPersistsPaymentFulfillmentAndRefundState(t *testi if err != nil || !created { t.Fatalf("create order = %#v created=%v err=%v", order, created, err) } - update := &system.PaymentProviderUpdate{Status: "success", ProviderStatus: "TRADE_SUCCESS", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY", EventID: "event-1"} - order, err = repo.ApplyPaymentResult(ctx, system.PaymentAlipay, "order-1", update) - if err != nil || order.PaymentStatus != system.PaymentStatusPaid || order.PaidAmount != 100 { + update := &bizpayment.PaymentProviderUpdate{Status: "success", ProviderStatus: "TRADE_SUCCESS", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY", EventID: "event-1"} + order, err = repo.ApplyPaymentResult(ctx, bizpayment.PaymentAlipay, "order-1", update) + if err != nil || order.PaymentStatus != bizpayment.PaymentStatusPaid || order.PaidAmount != 100 { t.Fatalf("apply payment = %#v err=%v", order, err) } - order, token, duplicate, err := repo.BeginPaymentFulfillment(ctx, system.PaymentAlipay, "order-1", time.Minute) - if err != nil || duplicate || token == "" || order.FulfillmentStatus != system.FulfillmentStatusProcessing { + order, token, duplicate, err := repo.BeginPaymentFulfillment(ctx, bizpayment.PaymentAlipay, "order-1", time.Minute) + if err != nil || duplicate || token == "" || order.FulfillmentStatus != bizpayment.FulfillmentStatusProcessing { t.Fatalf("begin fulfillment = %#v token=%q duplicate=%v err=%v", order, token, duplicate, err) } - if _, _, _, err = repo.BeginPaymentFulfillment(ctx, system.PaymentAlipay, "order-1", time.Minute); err == nil { + if _, _, _, err = repo.BeginPaymentFulfillment(ctx, bizpayment.PaymentAlipay, "order-1", time.Minute); err == nil { t.Fatal("concurrent fulfillment was accepted") } - order, err = repo.CompletePaymentFulfillment(ctx, system.PaymentAlipay, "order-1", token, true, "") - if err != nil || order.FulfillmentStatus != system.FulfillmentStatusSucceeded { + order, err = repo.CompletePaymentFulfillment(ctx, bizpayment.PaymentAlipay, "order-1", token, true, "") + if err != nil || order.FulfillmentStatus != bizpayment.FulfillmentStatusSucceeded { t.Fatalf("complete fulfillment = %#v err=%v", order, err) } - _, _, duplicate, err = repo.BeginPaymentFulfillment(ctx, system.PaymentAlipay, "order-1", time.Minute) + _, _, duplicate, err = repo.BeginPaymentFulfillment(ctx, bizpayment.PaymentAlipay, "order-1", time.Minute) if err != nil || !duplicate { t.Fatalf("duplicate fulfillment = duplicate=%v err=%v", duplicate, err) } - order, token, err = repo.BeginPaymentRefund(ctx, system.PaymentAlipay, "order-1", 40, time.Minute) - if err != nil || token == "" || order.RefundStatus != system.RefundStatusProcessing { + order, token, err = repo.BeginPaymentRefund(ctx, bizpayment.PaymentAlipay, "order-1", 40, time.Minute) + if err != nil || token == "" || order.RefundStatus != bizpayment.RefundStatusProcessing { t.Fatalf("begin refund = %#v token=%q err=%v", order, token, err) } - order, err = repo.CompletePaymentRefundRequest(ctx, system.PaymentAlipay, "order-1", token, true, "") - if err != nil || order.RefundStatus != system.RefundStatusPending || order.RefundRequestedAmount != 40 { + order, err = repo.CompletePaymentRefundRequest(ctx, bizpayment.PaymentAlipay, "order-1", token, true, "") + if err != nil || order.RefundStatus != bizpayment.RefundStatusPending || order.RefundRequestedAmount != 40 { t.Fatalf("accept refund = %#v err=%v", order, err) } - order, err = repo.ConfirmPaymentRefund(ctx, system.PaymentAlipay, "order-1", order.RefundNo, 40, true, "") - if err != nil || order.RefundedAmount != 40 || order.PaymentStatus != system.PaymentStatusPartiallyRefunded { + order, err = repo.ConfirmPaymentRefund(ctx, bizpayment.PaymentAlipay, "order-1", order.RefundNo, 40, true, "") + if err != nil || order.RefundedAmount != 40 || order.PaymentStatus != bizpayment.PaymentStatusPartiallyRefunded { t.Fatalf("confirm refund = %#v err=%v", order, err) } } @@ -82,11 +82,11 @@ func TestPaymentOrderRepositoryRejectsProviderTradeReuse(t *testing.T) { if _, _, err := repo.CreatePaymentOrder(ctx, second); err != nil { t.Fatal(err) } - update := &system.PaymentProviderUpdate{Status: "success", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY"} - if _, err := repo.ApplyPaymentResult(ctx, system.PaymentAlipay, "order-1", update); err != nil { + update := &bizpayment.PaymentProviderUpdate{Status: "success", ProviderTradeNo: "provider-1", Amount: 100, Currency: "CNY"} + if _, err := repo.ApplyPaymentResult(ctx, bizpayment.PaymentAlipay, "order-1", update); err != nil { t.Fatal(err) } - if _, err := repo.ApplyPaymentResult(ctx, system.PaymentAlipay, "order-2", update); err != system.ErrPaymentProviderConflict { + if _, err := repo.ApplyPaymentResult(ctx, bizpayment.PaymentAlipay, "order-2", update); err != bizpayment.ErrPaymentProviderConflict { t.Fatalf("provider trade reuse err = %v", err) } } @@ -100,13 +100,13 @@ func TestPaymentOrderRepositoryListsWithFilters(t *testing.T) { } second := testPaymentOrder() second.TradeNo = "wechat-order-2" - second.Provider = system.PaymentWechatV3 + second.Provider = bizpayment.PaymentWechatV3 second.BusinessID = "item-2" second.ConfirmationID = "33333333-3333-3333-3333-333333333333" if _, _, err := repo.CreatePaymentOrder(ctx, second); err != nil { t.Fatal(err) } - items, total, err := repo.ListPaymentOrders(ctx, 1, 10, system.PaymentOrderFilter{Provider: system.PaymentWechatV3, TradeNo: "wechat", BusinessID: "item-2"}) + items, total, err := repo.ListPaymentOrders(ctx, 1, 10, bizpayment.PaymentOrderFilter{Provider: bizpayment.PaymentWechatV3, TradeNo: "wechat", BusinessID: "item-2"}) if err != nil { t.Fatal(err) } diff --git a/internal/data/repository/integration_config.go b/internal/data/repository/integration_config.go index 99880f6..510147c 100644 --- a/internal/data/repository/integration_config.go +++ b/internal/data/repository/integration_config.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "errors" - "kra/internal/biz/system" + integrationbiz "kra/internal/biz/integration" "strings" "time" @@ -31,23 +31,23 @@ type integrationRuntimeProvider interface { IntegrationRuntime() *runtimeconfig.Store } -func NewIntegrationConfigRepo(data Provider) system.IntegrationConfigRepo { +func NewIntegrationConfigRepo(data Provider) integrationbiz.IntegrationConfigRepo { return &integrationConfigRepo{data: data} } -func (r *integrationConfigRepo) ListIntegrationConfigs(ctx context.Context, kind string) ([]*system.IntegrationConfig, error) { +func (r *integrationConfigRepo) ListIntegrationConfigs(ctx context.Context, kind string) ([]*integrationbiz.IntegrationConfig, error) { var rows []integrationConfigPO if err := r.data.DB().WithContext(ctx).Where("kind = ?", kind).Order("provider ASC").Find(&rows).Error; err != nil { return nil, err } - result := make([]*system.IntegrationConfig, 0, len(rows)) + result := make([]*integrationbiz.IntegrationConfig, 0, len(rows)) for _, row := range rows { result = append(result, integrationConfigFromPO(row)) } return result, nil } -func (r *integrationConfigRepo) FindIntegrationConfig(ctx context.Context, kind, provider string) (*system.IntegrationConfig, error) { +func (r *integrationConfigRepo) FindIntegrationConfig(ctx context.Context, kind, provider string) (*integrationbiz.IntegrationConfig, error) { var row integrationConfigPO if err := r.data.DB().WithContext(ctx).Where("kind = ? AND provider = ?", kind, provider).First(&row).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -58,14 +58,14 @@ func (r *integrationConfigRepo) FindIntegrationConfig(ctx context.Context, kind, return integrationConfigFromPO(row), nil } -func (r *integrationConfigRepo) SaveIntegrationConfig(ctx context.Context, config *system.IntegrationConfig) error { +func (r *integrationConfigRepo) SaveIntegrationConfig(ctx context.Context, config *integrationbiz.IntegrationConfig) error { db := r.data.DB().WithContext(ctx) var row integrationConfigPO err := db.Where("kind = ? AND provider = ?", config.Kind, config.Provider).First(&row).Error values := integrationObject(config.Values) if errors.Is(err, gorm.ErrRecordNotFound) { if config.Enabled { - if err = system.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil { + if err = integrationbiz.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil { return err } } @@ -81,7 +81,7 @@ func (r *integrationConfigRepo) SaveIntegrationConfig(ctx context.Context, confi } mergeIntegrationSecrets(config.Kind, config.Provider, values, integrationObject(json.RawMessage(row.Config))) if config.Enabled { - if err = system.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil { + if err = integrationbiz.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil { return err } } @@ -116,11 +116,11 @@ func integrationRuntime(provider Provider) *runtimeconfig.Store { return nil } -func integrationConfigFromPO(row integrationConfigPO) *system.IntegrationConfig { +func integrationConfigFromPO(row integrationConfigPO) *integrationbiz.IntegrationConfig { values := integrationObject(json.RawMessage(row.Config)) maskIntegrationSecrets(row.Kind, row.Provider, values) encoded, _ := json.Marshal(values) - return &system.IntegrationConfig{Kind: row.Kind, Provider: row.Provider, Enabled: row.Enabled, Values: encoded} + return &integrationbiz.IntegrationConfig{Kind: row.Kind, Provider: row.Provider, Enabled: row.Enabled, Values: encoded} } func integrationObject(raw json.RawMessage) map[string]any { @@ -165,7 +165,7 @@ func mergeIntegrationSecrets(kind, provider string, values, old map[string]any) func integrationSecretFields(kind, provider string) map[string]bool { result := map[string]bool{} - if definition, ok := system.IntegrationDefinition(kind, provider); ok { + if definition, ok := integrationbiz.IntegrationDefinition(kind, provider); ok { for _, field := range definition.Fields { if field.Secret { result[field.Key] = true diff --git a/internal/data/repository/integration_config_runtime_test.go b/internal/data/repository/integration_config_runtime_test.go index 6b7c3d3..7213911 100644 --- a/internal/data/repository/integration_config_runtime_test.go +++ b/internal/data/repository/integration_config_runtime_test.go @@ -3,7 +3,7 @@ package system import ( "context" "encoding/json" - "kra/internal/biz/system" + integrationbiz "kra/internal/biz/integration" "testing" "kra/internal/integration/runtimeconfig" @@ -27,19 +27,19 @@ func TestIntegrationConfigSavePublishesUnmaskedRuntimeValues(t *testing.T) { provider := &integrationRuntimeTestProvider{Data: &Data{gormDB: newReloadableDB(db, nil)}, store: runtimeconfig.NewStore()} repo := &integrationConfigRepo{data: provider} - values := system.DefaultIntegrationConfig(system.IntegrationKindMQ, "rabbitmq") + values := integrationbiz.DefaultIntegrationConfig(integrationbiz.IntegrationKindMQ, "rabbitmq") values["password"] = "runtime-secret" raw, _ := json.Marshal(values) - if err = repo.SaveIntegrationConfig(context.Background(), &system.IntegrationConfig{Kind: system.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil { + if err = repo.SaveIntegrationConfig(context.Background(), &integrationbiz.IntegrationConfig{Kind: integrationbiz.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil { t.Fatal(err) } values["password"] = "******" raw, _ = json.Marshal(values) - if err = repo.SaveIntegrationConfig(context.Background(), &system.IntegrationConfig{Kind: system.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil { + if err = repo.SaveIntegrationConfig(context.Background(), &integrationbiz.IntegrationConfig{Kind: integrationbiz.IntegrationKindMQ, Provider: "rabbitmq", Enabled: true, Values: raw}); err != nil { t.Fatal(err) } - active, ok := provider.store.Get(system.IntegrationKindMQ, "rabbitmq") + active, ok := provider.store.Get(integrationbiz.IntegrationKindMQ, "rabbitmq") if !ok || !active.Enabled { t.Fatalf("runtime config = %#v, ok=%v", active, ok) } diff --git a/internal/data/repository/task.go b/internal/data/repository/task.go index d9b4db0..ef99b8d 100644 --- a/internal/data/repository/task.go +++ b/internal/data/repository/task.go @@ -2,7 +2,7 @@ package system import ( "context" - "kra/internal/biz/system" + taskbiz "kra/internal/biz/task" "kra/pkg/database/gormkit" "kra/pkg/database/pagination" "time" @@ -46,14 +46,14 @@ func (taskLogPO) TableName() string { return "sys_timed_task_logs" } type taskRepo struct{ data Provider } -func NewTaskRepo(data Provider) system.TaskRepo { return &taskRepo{data: data} } -func taskToPO(v *system.TimedTask) taskPO { +func NewTaskRepo(data Provider) taskbiz.TaskRepo { return &taskRepo{data: data} } +func taskToPO(v *taskbiz.TimedTask) taskPO { return taskPO{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: gormkit.JSON(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: gormkit.JSON(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} } -func taskFromPO(v taskPO) *system.TimedTask { - return &system.TimedTask{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: []byte(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: []byte(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} +func taskFromPO(v taskPO) *taskbiz.TimedTask { + return &taskbiz.TimedTask{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: []byte(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: []byte(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} } -func (r *taskRepo) CreateTask(ctx context.Context, v *system.TimedTask) error { +func (r *taskRepo) CreateTask(ctx context.Context, v *taskbiz.TimedTask) error { po := taskToPO(v) if err := r.data.DB().WithContext(ctx).Create(&po).Error; err != nil { return err @@ -70,26 +70,26 @@ func (r *taskRepo) TaskNameExists(ctx context.Context, name string, excludeID ui err := db.Count(&count).Error return count > 0, err } -func (r *taskRepo) UpdateTask(ctx context.Context, v *system.TimedTask) error { +func (r *taskRepo) UpdateTask(ctx context.Context, v *taskbiz.TimedTask) error { po := taskToPO(v) return r.data.DB().WithContext(ctx).Model(&taskPO{}).Where("id = ?", v.ID).Select("name", "description", "spec", "with_seconds", "executor_type", "method_name", "params", "http_url", "http_method", "http_header", "http_body", "http_allow_private", "enabled").Updates(&po).Error } func (r *taskRepo) DeleteTask(ctx context.Context, id uint) error { return r.data.DB().WithContext(ctx).Delete(&taskPO{}, id).Error } -func (r *taskRepo) FindTask(ctx context.Context, id uint) (*system.TimedTask, error) { +func (r *taskRepo) FindTask(ctx context.Context, id uint) (*taskbiz.TimedTask, error) { var po taskPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } return taskFromPO(po), nil } -func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *system.TimedTask) ([]*system.TimedTask, int64, error) { +func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *taskbiz.TimedTask) ([]*taskbiz.TimedTask, int64, error) { // During first-install the data layer intentionally serves a bootstrap // database without system tables. The scheduler starts before /init/initdb // and should remain idle instead of logging a missing-table SQL error. if !r.data.DatabaseReady() { - return []*system.TimedTask{}, 0, nil + return []*taskbiz.TimedTask{}, 0, nil } db := r.data.DB().WithContext(ctx).Model(&taskPO{}) if q != nil { @@ -111,7 +111,7 @@ func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *system.Time if err := pagination.Apply(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*system.TimedTask, 0, len(pos)) + out := make([]*taskbiz.TimedTask, 0, len(pos)) for _, po := range pos { out = append(out, taskFromPO(po)) } @@ -120,13 +120,13 @@ func (r *taskRepo) ListTasks(ctx context.Context, page, size int, q *system.Time func (r *taskRepo) ToggleTask(ctx context.Context, id uint, enabled bool) error { return r.data.DB().WithContext(ctx).Model(&taskPO{}).Where("id = ?", id).Update("enabled", enabled).Error } -func (r *taskRepo) RecordTaskLog(ctx context.Context, v *system.TimedTaskLog) error { +func (r *taskRepo) RecordTaskLog(ctx context.Context, v *taskbiz.TimedTaskLog) error { return r.data.DB().WithContext(ctx).Create(&taskLogPO{TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output}).Error } -func taskLogFromPO(v taskLogPO) *system.TimedTaskLog { - return &system.TimedTaskLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output} +func taskLogFromPO(v taskLogPO) *taskbiz.TimedTaskLog { + return &taskbiz.TimedTaskLog{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, TaskID: v.TaskID, TaskName: v.TaskName, TriggerType: v.TriggerType, StartedAt: v.StartedAt, FinishedAt: v.FinishedAt, DurationMS: v.DurationMS, Status: v.Status, ErrorMsg: v.ErrorMsg, Output: v.Output} } -func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint, status string) ([]*system.TimedTaskLog, int64, error) { +func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint, status string) ([]*taskbiz.TimedTaskLog, int64, error) { db := r.data.DB().WithContext(ctx).Model(&taskLogPO{}) if taskID != 0 { db = db.Where("task_id = ?", taskID) @@ -142,7 +142,7 @@ func (r *taskRepo) ListTaskLogs(ctx context.Context, page, size int, taskID uint if err := pagination.Apply(db.Order("id desc"), page, size, 100).Find(&pos).Error; err != nil { return nil, 0, err } - out := make([]*system.TimedTaskLog, 0, len(pos)) + out := make([]*taskbiz.TimedTaskLog, 0, len(pos)) for _, po := range pos { out = append(out, taskLogFromPO(po)) } diff --git a/internal/integration/connectivity.go b/internal/integration/connectivity.go index 91a453c..ef7caaa 100644 --- a/internal/integration/connectivity.go +++ b/internal/integration/connectivity.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "kra/internal/biz/system" + integrationbiz "kra/internal/biz/integration" "strings" "kra/internal/integration/mq" @@ -23,7 +23,7 @@ func NewConnectivityTester(store *runtimeconfig.Store) *ConnectivityTester { return &ConnectivityTester{store: store} } -func (t *ConnectivityTester) TestIntegration(ctx context.Context, config *system.IntegrationConfig) error { +func (t *ConnectivityTester) TestIntegration(ctx context.Context, config *integrationbiz.IntegrationConfig) error { if config == nil { return errors.New("集成配置请求为空") } @@ -39,9 +39,9 @@ func (t *ConnectivityTester) TestIntegration(ctx context.Context, config *system return fmt.Errorf("编码集成配置失败: %w", err) } switch strings.ToLower(strings.TrimSpace(config.Kind)) { - case system.IntegrationKindMQ: + case integrationbiz.IntegrationKindMQ: return mq.TestConfig(ctx, config.Provider, raw) - case system.IntegrationKindWebSocket: + case integrationbiz.IntegrationKindWebSocket: if strings.ToLower(strings.TrimSpace(config.Provider)) != websocketintegration.ProviderMelody { return fmt.Errorf("不支持的 WebSocket provider %q", config.Provider) } @@ -52,7 +52,7 @@ func (t *ConnectivityTester) TestIntegration(ctx context.Context, config *system } func (t *ConnectivityTester) restoreMaskedSecrets(kind, provider string, values map[string]any) error { - definition, ok := system.IntegrationDefinition(kind, provider) + definition, ok := integrationbiz.IntegrationDefinition(kind, provider) if !ok { return fmt.Errorf("不支持的集成 %s/%s", kind, provider) } diff --git a/internal/integration/payment/adapter.go b/internal/integration/payment/adapter.go index 25ee344..0047775 100644 --- a/internal/integration/payment/adapter.go +++ b/internal/integration/payment/adapter.go @@ -3,52 +3,52 @@ package payment import ( "context" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" ) // Adapter is the provider boundary used by the payment repository. Provider -// SDK types stay in this package and are normalized to biz.PaymentResult. +// SDK types stay in this package and are normalized to biz/payment results. type Adapter interface { - Create(context.Context, *system.PaymentRequest, map[string]any) (*system.PaymentResult, error) - Query(context.Context, string, map[string]any) (*system.PaymentResult, error) - Refund(context.Context, *system.PaymentRefundRequest, map[string]any) (*system.PaymentResult, error) - Callback(context.Context, *system.PaymentCallback, map[string]any) (*system.PaymentResult, error) + Create(context.Context, *bizpayment.PaymentRequest, map[string]any) (*bizpayment.PaymentResult, error) + Query(context.Context, string, map[string]any) (*bizpayment.PaymentResult, error) + Refund(context.Context, *bizpayment.PaymentRefundRequest, map[string]any) (*bizpayment.PaymentResult, error) + Callback(context.Context, *bizpayment.PaymentCallback, map[string]any) (*bizpayment.PaymentResult, error) } // New constructs the SDK-backed adapter for a configured provider. func New(provider string) (Adapter, error) { switch provider { - case system.PaymentAlipay: + case bizpayment.PaymentAlipay: return &alipayAdapter{}, nil - case system.PaymentAlipayV3: + case bizpayment.PaymentAlipayV3: return &alipayV3Adapter{}, nil - case system.PaymentWechatV2: + case bizpayment.PaymentWechatV2: return &wechatV2Adapter{}, nil - case system.PaymentWechatV3: + case bizpayment.PaymentWechatV3: return &wechatV3Adapter{}, nil - case system.PaymentApple: + case bizpayment.PaymentApple: return &appleAdapter{}, nil - case system.PaymentDouyin: + case bizpayment.PaymentDouyin: return &douyinAdapter{}, nil - case system.PaymentQQ: + case bizpayment.PaymentQQ: return &qqAdapter{}, nil - case system.PaymentAllinPay: + case bizpayment.PaymentAllinPay: return &allinpayAdapter{}, nil - case system.PaymentLakala: + case bizpayment.PaymentLakala: return &lakalaAdapter{}, nil - case system.PaymentPayPal: + case bizpayment.PaymentPayPal: return &paypalAdapter{}, nil - case system.PaymentSaobei: + case bizpayment.PaymentSaobei: return &saobeiAdapter{}, nil - case system.PaymentChinaums: + case bizpayment.PaymentChinaums: return newVendorAdapter(provider, vendorChinaums), nil - case system.PaymentSFT: + case bizpayment.PaymentSFT: return newVendorAdapter(provider, vendorSFT), nil - case system.PaymentSuperPay: + case bizpayment.PaymentSuperPay: return newVendorAdapter(provider, vendorSupperPay), nil - case system.PaymentWechatGame: + case bizpayment.PaymentWechatGame: return newVendorAdapter(provider, vendorWechatGame), nil - case system.PaymentDouyinGame: + case bizpayment.PaymentDouyinGame: return newVendorAdapter(provider, vendorDouyinGame), nil default: return nil, fmt.Errorf("支付渠道 %s 没有适配器", provider) diff --git a/internal/integration/payment/adapter_test.go b/internal/integration/payment/adapter_test.go index 586b8e3..52cdcb3 100644 --- a/internal/integration/payment/adapter_test.go +++ b/internal/integration/payment/adapter_test.go @@ -1,12 +1,12 @@ package payment import ( - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "testing" ) func TestEverySupportedProviderHasAdapter(t *testing.T) { - for _, provider := range system.SupportedPaymentProviders { + for _, provider := range bizpayment.SupportedPaymentProviders { t.Run(provider, func(t *testing.T) { adapter, err := New(provider) if err != nil { diff --git a/internal/integration/payment/alipay.go b/internal/integration/payment/alipay.go index 6691979..24b9cf3 100644 --- a/internal/integration/payment/alipay.go +++ b/internal/integration/payment/alipay.go @@ -7,7 +7,7 @@ import ( "errors" "fmt" "io" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/http" "net/url" "sort" @@ -38,7 +38,7 @@ type alipayResponseBody struct { io.Closer } -func (a *alipayAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *alipayAdapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("支付宝下单参数为空") } @@ -91,8 +91,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *system.PaymentRequest, if status == "success" && strings.TrimSpace(response.TradeNo) == "" { return nil, errors.New("支付宝付款码支付成功响应缺少 trade_no") } - return &system.PaymentResult{ - Provider: system.PaymentAlipay, Status: status, TradeNo: req.TradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipay, Status: status, TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(response.TradeNo), Payload: sdk.payload(rsp), }, nil case "alipay.trade.precreate": @@ -112,8 +112,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *system.PaymentRequest, } // Precreate has no Alipay trade number yet. Keep the QR code in Payload // and query later by the merchant out_trade_no. - return &system.PaymentResult{ - Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, QueryID: queryID, Payload: sdk.payload(rsp), }, nil case "alipay.trade.app.pay": @@ -124,8 +124,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *system.PaymentRequest, if strings.TrimSpace(orderString) == "" { return nil, errors.New("支付宝 APP 支付参数为空") } - return &system.PaymentResult{ - Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, Payload: mustMarshalAlipayPayload(map[string]string{"order_string": orderString}), }, nil case "alipay.trade.page.pay": @@ -140,8 +140,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *system.PaymentRequest, if err != nil { return nil, err } - return &system.PaymentResult{ - Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, Payload: mustMarshalAlipayPayload(map[string]string{"pay_url": payURL}), }, nil case "alipay.trade.wap.pay": @@ -156,8 +156,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *system.PaymentRequest, if err != nil { return nil, err } - return &system.PaymentResult{ - Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, Payload: mustMarshalAlipayPayload(map[string]string{"pay_url": payURL}), }, nil default: @@ -173,14 +173,14 @@ func (a *alipayAdapter) Create(ctx context.Context, req *system.PaymentRequest, if providerTradeNo == "" { return nil, errors.New("支付宝创建响应缺少 trade_no") } - return &system.PaymentResult{ - Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipay, Status: "created", TradeNo: req.TradeNo, ProviderTradeNo: providerTradeNo, Payload: sdk.payload(rsp), }, nil } } -func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { +func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("支付宝查单缺少 out_trade_no") @@ -195,8 +195,8 @@ func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string] return nil, err } response := rsp.Response - result := &system.PaymentResult{ - Provider: system.PaymentAlipay, Status: normalizePaymentStatus(response.TradeStatus, "pending"), + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipay, Status: normalizePaymentStatus(response.TradeStatus, "pending"), TradeNo: strings.TrimSpace(response.OutTradeNo), ProviderTradeNo: strings.TrimSpace(response.TradeNo), Payload: sdk.payload(rsp), } @@ -229,7 +229,7 @@ func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string] return result, nil } -func (a *alipayAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *alipayAdapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("支付宝退款请求为空") } @@ -257,7 +257,7 @@ func (a *alipayAdapter) Refund(ctx context.Context, req *system.PaymentRefundReq return alipayRefundResult(req, response, sdk.payload(rsp)) } -func alipayRefundResult(req *system.PaymentRefundRequest, response *gopayAlipay.TradeRefund, payload []byte) (*system.PaymentResult, error) { +func alipayRefundResult(req *bizpayment.PaymentRefundRequest, response *gopayAlipay.TradeRefund, payload []byte) (*bizpayment.PaymentResult, error) { if req == nil || response == nil { return nil, errors.New("支付宝退款响应为空") } @@ -297,13 +297,13 @@ func alipayRefundResult(req *system.PaymentRefundRequest, response *gopayAlipay. if currency == "" { currency = "CNY" } - return &system.PaymentResult{ - Provider: system.PaymentAlipay, Status: "created", TradeNo: tradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipay, Status: "created", TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Amount: amount, Currency: currency, Payload: payload, }, nil } -func (a *alipayAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { +func (a *alipayAdapter) Callback(_ context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) { if callback == nil || len(callback.Body) == 0 { return nil, errors.New("支付宝回调为空") } @@ -319,8 +319,8 @@ func (a *alipayAdapter) Callback(_ context.Context, callback *system.PaymentCall status = "success" } payload, _ := json.Marshal(fields) - return &system.PaymentResult{ - Provider: system.PaymentAlipay, Status: status, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipay, Status: status, TradeNo: first(fields, "out_trade_no"), ProviderTradeNo: first(fields, "trade_no"), Payload: payload, }, nil @@ -481,7 +481,7 @@ func firstNonEmptyAlipay(values ...string) string { return "" } -func populateAlipayBreakdown(result *system.PaymentResult, response map[string]any) error { +func populateAlipayBreakdown(result *bizpayment.PaymentResult, response map[string]any) error { if result == nil || result.Status != "success" { return nil } diff --git a/internal/integration/payment/alipay_v3.go b/internal/integration/payment/alipay_v3.go index 468a691..abe0b74 100644 --- a/internal/integration/payment/alipay_v3.go +++ b/internal/integration/payment/alipay_v3.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/http" "net/url" "os" @@ -25,7 +25,7 @@ type alipayV3Client struct { gateway *url.URL } -func (a *alipayV3Adapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *alipayV3Adapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("支付宝 V3 下单参数为空") } @@ -93,8 +93,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *system.PaymentRequest if strings.TrimSpace(rsp.QrCode) == "" { return nil, errors.New("支付宝 V3 预创建响应缺少 qr_code") } - return &system.PaymentResult{ - Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, QueryID: outTradeNo, Payload: mustJSON(rsp), }, nil case "alipay.trade.app.pay": @@ -105,8 +105,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *system.PaymentRequest if strings.TrimSpace(orderString) == "" { return nil, errors.New("支付宝 V3 APP 支付参数为空") } - return &system.PaymentResult{ - Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(map[string]string{"order_string": orderString}), }, nil case "alipay.trade.page.pay": @@ -121,8 +121,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *system.PaymentRequest if strings.TrimSpace(payURL) == "" { return nil, errors.New("支付宝 V3 电脑网站支付 URL 为空") } - return &system.PaymentResult{ - Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(map[string]string{"pay_url": payURL}), }, nil case "alipay.trade.wap.pay": @@ -137,8 +137,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *system.PaymentRequest if strings.TrimSpace(payURL) == "" { return nil, errors.New("支付宝 V3 手机网站支付 URL 为空") } - return &system.PaymentResult{ - Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(map[string]string{"pay_url": payURL}), }, nil default: @@ -166,14 +166,14 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *system.PaymentRequest if providerTradeNo == "" { return nil, errors.New("支付宝 V3 创建响应缺少 trade_no") } - return &system.PaymentResult{ - Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo, ProviderTradeNo: providerTradeNo, QueryID: outTradeNo, Payload: mustJSON(rsp), }, nil } } -func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { +func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("支付宝 V3 查单缺少 out_trade_no") @@ -196,8 +196,8 @@ func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin if outTradeNo == "" || outTradeNo != tradeNo { return nil, errors.New("支付宝 V3 查询响应的 out_trade_no 缺失或不匹配") } - result := &system.PaymentResult{ - Provider: system.PaymentAlipayV3, Status: normalizePaymentStatus(rsp.TradeStatus, "pending"), + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipayV3, Status: normalizePaymentStatus(rsp.TradeStatus, "pending"), TradeNo: outTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.TradeNo), Currency: alipayV3Currency(c, ""), Payload: mustJSON(rsp), } @@ -221,7 +221,7 @@ func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin return result, nil } -func (a *alipayV3Adapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *alipayV3Adapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("支付宝 V3 退款请求为空") } @@ -272,14 +272,14 @@ func (a *alipayV3Adapter) Refund(ctx context.Context, req *system.PaymentRefundR if refundedAmount != req.Amount { return nil, errors.New("支付宝 V3 退款响应金额不匹配") } - return &system.PaymentResult{ - Provider: system.PaymentAlipayV3, Status: "created", TradeNo: tradeNo, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Amount: refundedAmount, Currency: alipayV3Currency(c, req.Currency), Payload: mustJSON(rsp), }, nil } -func (a *alipayV3Adapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { +func (a *alipayV3Adapter) Callback(_ context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) { if callback == nil || len(callback.Body) == 0 { return nil, errors.New("支付宝 V3 回调为空") } @@ -305,13 +305,13 @@ func (a *alipayV3Adapter) Callback(_ context.Context, callback *system.PaymentCa return nil, errors.New("支付宝 V3 回调缺少 out_trade_no 或 trade_no") } payload, _ := json.Marshal(fields) - return &system.PaymentResult{ - Provider: system.PaymentAlipayV3, Status: normalizePaymentStatus(fields["trade_status"], "pending"), + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipayV3, Status: normalizePaymentStatus(fields["trade_status"], "pending"), TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Payload: payload, }, nil } -func alipayV3TradePayResult(req *system.PaymentRequest, rsp *gopayAlipayV3.TradePayRsp) (*system.PaymentResult, error) { +func alipayV3TradePayResult(req *bizpayment.PaymentRequest, rsp *gopayAlipayV3.TradePayRsp) (*bizpayment.PaymentResult, error) { if req == nil || rsp == nil { return nil, errors.New("支付宝 V3 付款码支付响应为空") } @@ -330,8 +330,8 @@ func alipayV3TradePayResult(req *system.PaymentRequest, rsp *gopayAlipayV3.Trade if amount != req.Amount { return nil, errors.New("支付宝 V3 付款码支付响应金额不匹配") } - result := &system.PaymentResult{ - Provider: system.PaymentAlipayV3, Status: "success", TradeNo: outTradeNo, + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentAlipayV3, Status: "success", TradeNo: outTradeNo, ProviderTradeNo: providerTradeNo, Amount: amount, Currency: strings.ToUpper(strings.TrimSpace(req.Currency)), Payload: mustJSON(rsp), } diff --git a/internal/integration/payment/alipay_v3_test.go b/internal/integration/payment/alipay_v3_test.go index 17babfc..656d230 100644 --- a/internal/integration/payment/alipay_v3_test.go +++ b/internal/integration/payment/alipay_v3_test.go @@ -9,7 +9,7 @@ import ( "encoding/json" "encoding/pem" "io" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "math/big" "net/http" "net/http/httptest" @@ -135,8 +135,8 @@ func TestAlipayV3CreateRESTMethodsUseGoPay(t *testing.T) { if tc.method != "" { extra["method"] = tc.method } - result, err := (&alipayV3Adapter{}).Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentAlipayV3, TradeNo: tradeNo, Subject: "subject", Amount: 1000, + result, err := (&alipayV3Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentAlipayV3, TradeNo: tradeNo, Subject: "subject", Amount: 1000, Currency: "CNY", NotifyURL: "https://merchant.example/alipay-v3/notify", Extra: extra, }, config) if err != nil { @@ -145,7 +145,7 @@ func TestAlipayV3CreateRESTMethodsUseGoPay(t *testing.T) { if requestBody == nil || requestBody["out_trade_no"] != tradeNo || requestBody["notify_url"] == "" { t.Fatalf("request body = %#v", requestBody) } - if result.Provider != system.PaymentAlipayV3 || result.Status != tc.wantStatus || result.TradeNo != tradeNo || result.ProviderTradeNo != tc.wantTrade { + if result.Provider != bizpayment.PaymentAlipayV3 || result.Status != tc.wantStatus || result.TradeNo != tradeNo || result.ProviderTradeNo != tc.wantTrade { t.Fatalf("result = %+v", result) } if tc.method == "barcode" { @@ -168,8 +168,8 @@ func TestAlipayV3GeneratedPaymentMethodsUseGoPay(t *testing.T) { config["gateway_url"] = "https://merchant-gateway.example/gateway.do" for _, method := range []string{"app", "page", "wap"} { t.Run(method, func(t *testing.T) { - result, err := (&alipayV3Adapter{}).Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentAlipayV3, TradeNo: "T-V3-" + strings.ToUpper(method), Subject: "subject", + result, err := (&alipayV3Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentAlipayV3, TradeNo: "T-V3-" + strings.ToUpper(method), Subject: "subject", Amount: 1000, Currency: "CNY", NotifyURL: "https://merchant.example/alipay-v3/notify", ReturnURL: "https://merchant.example/alipay-v3/return", Extra: map[string]any{"method": method}, }, config) @@ -272,8 +272,8 @@ func TestAlipayV3RefundValidatesResponse(t *testing.T) { defer server.Close() config := cloneAlipayV3Config(baseConfig) config["api_base_url"] = server.URL - result, err := (&alipayV3Adapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ - Provider: system.PaymentAlipayV3, TradeNo: "T-V3-REFUND", ProviderTradeNo: "P-V3-REFUND", + result, err := (&alipayV3Adapter{}).Refund(context.Background(), &bizpayment.PaymentRefundRequest{ + Provider: bizpayment.PaymentAlipayV3, TradeNo: "T-V3-REFUND", ProviderTradeNo: "P-V3-REFUND", RefundNo: "R-V3-REFUND", Amount: 123, Currency: "CNY", }, config) if tc.wantError { @@ -315,14 +315,14 @@ func TestAlipayV3CallbackUsesGoPayCertificateVerification(t *testing.T) { for key, value := range fields { values.Set(key, value) } - callback := &system.PaymentCallback{ + callback := &bizpayment.PaymentCallback{ Body: []byte(values.Encode()), Headers: map[string]string{"Content-Type": "application/x-www-form-urlencoded"}, } result, err := (&alipayV3Adapter{}).Callback(context.Background(), callback, config) if err != nil { t.Fatal(err) } - if result.Provider != system.PaymentAlipayV3 || result.Status != "success" || result.TradeNo != "T-V3-CALLBACK" || result.ProviderTradeNo != "P-V3-CALLBACK" { + if result.Provider != bizpayment.PaymentAlipayV3 || result.Status != "success" || result.TradeNo != "T-V3-CALLBACK" || result.ProviderTradeNo != "P-V3-CALLBACK" { t.Fatalf("callback result = %+v", result) } callback.Body = []byte(strings.ReplaceAll(values.Encode(), "P-V3-CALLBACK", "P-V3-TAMPERED")) diff --git a/internal/integration/payment/allinpay.go b/internal/integration/payment/allinpay.go index 9b1e6d7..4c64e6d 100644 --- a/internal/integration/payment/allinpay.go +++ b/internal/integration/payment/allinpay.go @@ -11,7 +11,7 @@ import ( "errors" "fmt" "io" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/http" "strings" "sync" @@ -120,7 +120,7 @@ func (a *allinpayAdapter) client(c map[string]any) (*allinpay.Client, error) { return client, nil } -func (a *allinpayAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *allinpayAdapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("通联支付下单请求为空") } @@ -158,7 +158,7 @@ func (a *allinpayAdapter) Create(ctx context.Context, req *system.PaymentRequest if returnedTradeNo := strings.TrimSpace(rsp.Reqsn); returnedTradeNo != "" && returnedTradeNo != strings.TrimSpace(req.TradeNo) { return nil, errors.New("通联支付扫码响应的 reqsn 不匹配") } - return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil } if method == "native" { if orderType == allinpay.OrderTypeTrxId { @@ -175,7 +175,7 @@ func (a *allinpayAdapter) Create(ctx context.Context, req *system.PaymentRequest if returnedTradeNo := strings.TrimSpace(rsp.ReqSn); returnedTradeNo != "" && returnedTradeNo != strings.TrimSpace(req.TradeNo) { return nil, errors.New("通联支付 Native 响应的 reqsn 不匹配") } - return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)}, nil } payType := firstAny(req.Extra, "paytype", "pay_type") if payType == "" { @@ -199,7 +199,7 @@ func (a *allinpayAdapter) Create(ctx context.Context, req *system.PaymentRequest if err != nil { return nil, err } - return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil } func allinpayCreateMethod(extra, config map[string]any) (string, error) { @@ -220,7 +220,7 @@ func allinpayCreateMethod(extra, config map[string]any) (string, error) { } } -func (a *allinpayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { +func (a *allinpayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { orderType, err := allinpayOrderType(c) if err != nil { return nil, err @@ -247,7 +247,7 @@ func (a *allinpayAdapter) Query(ctx context.Context, tradeNo string, c map[strin if rsp == nil { return nil, errors.New("通联支付查单响应为空") } - result := &system.PaymentResult{Provider: system.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: strings.TrimSpace(rsp.Reqsn), ProviderTradeNo: strings.TrimSpace(rsp.Trxid), Currency: strings.ToUpper(firstAny(c, "currency")), Payload: mustJSON(rsp)} + result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: strings.TrimSpace(rsp.Reqsn), ProviderTradeNo: strings.TrimSpace(rsp.Trxid), Currency: strings.ToUpper(firstAny(c, "currency")), Payload: mustJSON(rsp)} if strings.EqualFold(orderType, allinpay.OrderTypeTrxId) { if result.ProviderTradeNo == "" { return nil, errors.New("通联支付查单响应缺少 trxid") @@ -300,7 +300,7 @@ func allinpayCreateQueryID(orderType, transactionID string) (string, error) { return "", nil } -func (a *allinpayAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *allinpayAdapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("通联支付退款请求为空") } @@ -345,7 +345,7 @@ func (a *allinpayAdapter) Refund(ctx context.Context, req *system.PaymentRefundR return allinpayRefundResult(&effective, rsp, firstAny(c, "currency")) } -func allinpayRefundResult(req *system.PaymentRefundRequest, rsp *allinpay.RefundRsp, currency string) (*system.PaymentResult, error) { +func allinpayRefundResult(req *bizpayment.PaymentRefundRequest, rsp *allinpay.RefundRsp, currency string) (*bizpayment.PaymentResult, error) { if rsp == nil { return nil, errors.New("通联支付退款响应为空") } @@ -368,9 +368,9 @@ func allinpayRefundResult(req *system.PaymentRefundRequest, rsp *allinpay.Refund if refundAmount != req.Amount { return nil, errors.New("通联支付退款响应金额不匹配") } - return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: normalizeRefundState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(strings.TrimSpace(currency)), Payload: mustJSON(rsp)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentAllinPay, Status: normalizeRefundState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(strings.TrimSpace(currency)), Payload: mustJSON(rsp)}, nil } -func (a *allinpayAdapter) Callback(_ context.Context, _ *system.PaymentCallback, _ map[string]any) (*system.PaymentResult, error) { +func (a *allinpayAdapter) Callback(_ context.Context, _ *bizpayment.PaymentCallback, _ map[string]any) (*bizpayment.PaymentResult, error) { return nil, errors.New("通联支付回调没有可复用的 GoPay 验签器,请改用主动查单") } diff --git a/internal/integration/payment/allinpay_test.go b/internal/integration/payment/allinpay_test.go index f7e454b..0f7a512 100644 --- a/internal/integration/payment/allinpay_test.go +++ b/internal/integration/payment/allinpay_test.go @@ -11,7 +11,7 @@ import ( "encoding/base64" "encoding/json" "io" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net" "net/http" "net/http/httptest" @@ -120,8 +120,8 @@ func TestAllinPayTrxIDCreateRejectsResponseWithoutTransactionID(t *testing.T) { _, _ = w.Write(response) })) - _, err := (&allinpayAdapter{}).Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentAllinPay, TradeNo: "MERCHANT-TRXID-MISSING", Subject: "subject", + _, err := (&allinpayAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentAllinPay, TradeNo: "MERCHANT-TRXID-MISSING", Subject: "subject", Amount: 100, Currency: "CNY", }, allinPayTestConfig(privateKey, publicKey, allinpay.OrderTypeTrxId)) if err == nil || !strings.Contains(strings.ToLower(err.Error()), "trxid") { @@ -131,8 +131,8 @@ func TestAllinPayTrxIDCreateRejectsResponseWithoutTransactionID(t *testing.T) { func TestAllinPayNativeCreateRejectsTrxIDLookupMode(t *testing.T) { _, privateKey, publicKey := allinPayTestKeys(t) - _, err := (&allinpayAdapter{}).Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentAllinPay, TradeNo: "MERCHANT-NATIVE-TRXID", Subject: "subject", + _, err := (&allinpayAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentAllinPay, TradeNo: "MERCHANT-NATIVE-TRXID", Subject: "subject", Amount: 100, Currency: "CNY", Extra: map[string]any{"method": "native", "expiretime": "20261231235959"}, }, allinPayTestConfig(privateKey, publicKey, allinpay.OrderTypeTrxId)) if err == nil || !strings.Contains(strings.ToLower(err.Error()), "native") || !strings.Contains(strings.ToLower(err.Error()), "trxid") { @@ -141,8 +141,8 @@ func TestAllinPayNativeCreateRejectsTrxIDLookupMode(t *testing.T) { } func TestAllinPayCreateRejectsUnknownMethodBeforeClient(t *testing.T) { - _, err := (&allinpayAdapter{}).Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentAllinPay, TradeNo: "MERCHANT-UNKNOWN-METHOD", Subject: "subject", + _, err := (&allinpayAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentAllinPay, TradeNo: "MERCHANT-UNKNOWN-METHOD", Subject: "subject", Amount: 100, Currency: "CNY", Extra: map[string]any{"method": "unsupported"}, }, nil) if err == nil || !strings.Contains(err.Error(), "不支持的下单方式") { @@ -248,8 +248,8 @@ func TestAllinPayRefundUsesConfiguredLookupIdentity(t *testing.T) { _, _ = w.Write(response) })) - result, err := (&allinpayAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ - Provider: system.PaymentAllinPay, TradeNo: tc.tradeNo, QueryID: tc.queryID, + result, err := (&allinpayAdapter{}).Refund(context.Background(), &bizpayment.PaymentRefundRequest{ + Provider: bizpayment.PaymentAllinPay, TradeNo: tc.tradeNo, QueryID: tc.queryID, RefundNo: "REFUND-1", Amount: 40, TotalAmount: 100, Currency: "CNY", }, allinPayTestConfig(privateKey, publicKey, tc.orderType)) if err != nil { @@ -263,7 +263,7 @@ func TestAllinPayRefundUsesConfiguredLookupIdentity(t *testing.T) { } func TestAllinPayRefundResultBindsRefundIdentity(t *testing.T) { - req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 40, Currency: "CNY"} + req := &bizpayment.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 40, Currency: "CNY"} base := func() *allinpay.RefundRsp { return &allinpay.RefundRsp{Reqsn: "REFUND-1", Trxid: "ALLIN-REFUND-1", TrxStatus: "SUCCESS", Fee: "40"} } diff --git a/internal/integration/payment/apple.go b/internal/integration/payment/apple.go index 6438b7d..73db0ae 100644 --- a/internal/integration/payment/apple.go +++ b/internal/integration/payment/apple.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "math" "strings" @@ -15,7 +15,7 @@ import ( type appleAdapter struct{} -func (a *appleAdapter) Create(_ context.Context, req *system.PaymentRequest, _ map[string]any) (*system.PaymentResult, error) { +func (a *appleAdapter) Create(_ context.Context, req *bizpayment.PaymentRequest, _ map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("Apple 内购下单参数为空") } @@ -30,9 +30,9 @@ func (a *appleAdapter) Create(_ context.Context, req *system.PaymentRequest, _ m return nil, errors.New("Apple 内购 tradeNo 必须是用于 appAccountToken 的 UUID") } payload, _ := json.Marshal(map[string]any{"product_id": productID, "app_account_token": req.TradeNo, "client_only": true, "trade_no": req.TradeNo}) - return &system.PaymentResult{Provider: system.PaymentApple, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentApple, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil } -func (a *appleAdapter) Query(ctx context.Context, transactionID string, c map[string]any) (*system.PaymentResult, error) { +func (a *appleAdapter) Query(ctx context.Context, transactionID string, c map[string]any) (*bizpayment.PaymentResult, error) { transactionID = strings.TrimSpace(transactionID) if transactionID == "" { return nil, errors.New("Apple 查询缺少 transactionId") @@ -81,10 +81,10 @@ func (a *appleAdapter) Query(ctx context.Context, transactionID string, c map[st } return result, nil } -func (a *appleAdapter) Refund(context.Context, *system.PaymentRefundRequest, map[string]any) (*system.PaymentResult, error) { +func (a *appleAdapter) Refund(context.Context, *bizpayment.PaymentRefundRequest, map[string]any) (*bizpayment.PaymentResult, error) { return nil, errors.New("Apple 内购退款由 App Store 管理,服务端不提供主动退款接口") } -func (a *appleAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { +func (a *appleAdapter) Callback(_ context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) { if callback == nil { return nil, errors.New("Apple 回调为空") } @@ -139,7 +139,7 @@ func (a *appleAdapter) Callback(_ context.Context, callback *system.PaymentCallb if _, parseErr := uuid.Parse(tradeNo); parseErr != nil { return nil, errors.New("Apple 回调 appAccountToken 不是有效 UUID") } - return &system.PaymentResult{Provider: system.PaymentApple, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: transactionID, QueryID: transactionID, Payload: payload}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentApple, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: transactionID, QueryID: transactionID, Payload: payload}, nil } func appleClaims(value any) (map[string]any, error) { @@ -154,7 +154,7 @@ func appleClaims(value any) (map[string]any, error) { return claims, nil } -func applePaymentResult(transactionID string, claims map[string]any, payload []byte, c map[string]any) (*system.PaymentResult, error) { +func applePaymentResult(transactionID string, claims map[string]any, payload []byte, c map[string]any) (*bizpayment.PaymentResult, error) { revoked, err := appleRevoked(claims) if err != nil { return nil, err @@ -174,7 +174,7 @@ func applePaymentResult(transactionID string, claims map[string]any, payload []b if _, err := uuid.Parse(tradeNo); err != nil { return nil, errors.New("Apple 交易 appAccountToken 不是有效 UUID") } - result := &system.PaymentResult{Provider: system.PaymentApple, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, QueryID: providerTradeNo, Payload: payload} + result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentApple, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, QueryID: providerTradeNo, Payload: payload} if status != "success" { return result, nil } diff --git a/internal/integration/payment/douyin.go b/internal/integration/payment/douyin.go index fd86666..a90b956 100644 --- a/internal/integration/payment/douyin.go +++ b/internal/integration/payment/douyin.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "strings" "github.com/go-pay/gopay" @@ -34,7 +34,7 @@ func (a *douyinAdapter) client(c map[string]any) (*douyin.Client, error) { return client, nil } -func (a *douyinAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *douyinAdapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("抖音支付下单请求为空") } @@ -77,7 +77,7 @@ func (a *douyinAdapter) Create(ctx context.Context, req *system.PaymentRequest, return nil, errors.New("抖音支付 H5 下单响应缺少 h5_url") } payload, _ := json.Marshal(rsp.Response) - return &system.PaymentResult{Provider: system.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil case "native": rsp, callErr := client.NativeOrder(ctx, bm) if callErr != nil { @@ -90,7 +90,7 @@ func (a *douyinAdapter) Create(ctx context.Context, req *system.PaymentRequest, return nil, errors.New("抖音支付 Native 下单响应缺少 code_url") } payload, _ := json.Marshal(rsp.Response) - return &system.PaymentResult{Provider: system.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil case "jsapi": rsp, callErr := client.JsapiOrder(ctx, bm) if callErr != nil { @@ -125,7 +125,7 @@ func douyinCreateMethod(extra, config map[string]any) (string, error) { } } -func douyinPrepayResult(client *douyin.Client, appID, tradeNo, prepayID string, app bool) (*system.PaymentResult, error) { +func douyinPrepayResult(client *douyin.Client, appID, tradeNo, prepayID string, app bool) (*bizpayment.PaymentResult, error) { if client == nil || strings.TrimSpace(prepayID) == "" { return nil, errors.New("抖音支付下单响应缺少 prepay_id") } @@ -141,15 +141,15 @@ func douyinPrepayResult(client *douyin.Client, appID, tradeNo, prepayID string, if err != nil { return nil, fmt.Errorf("生成抖音支付客户端调起参数: %w", err) } - return &system.PaymentResult{ - Provider: system.PaymentDouyin, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentDouyin, Status: "client_pending", TradeNo: tradeNo, Payload: mustJSON(params), }, nil } -func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { +func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("抖音支付查单缺少 out_trade_no") @@ -177,7 +177,7 @@ func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string] payload = append([]byte(nil), signedBody...) } } - result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: normalizeDouyinState(order.TradeState), TradeNo: order.OutTradeNo, ProviderTradeNo: order.TransactionId, Payload: payload} + result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: normalizeDouyinState(order.TradeState), TradeNo: order.OutTradeNo, ProviderTradeNo: order.TransactionId, Payload: payload} if result.TradeNo == "" { result.TradeNo = tradeNo } else if result.TradeNo != tradeNo { @@ -189,7 +189,7 @@ func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string] return result, nil } -func (a *douyinAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *douyinAdapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("抖音支付退款请求为空") } @@ -226,7 +226,7 @@ func (a *douyinAdapter) Refund(ctx context.Context, req *system.PaymentRefundReq if err != nil { return nil, err } - result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: normalizeRefundState(r.Status), TradeNo: tradeNo, ProviderTradeNo: refundID, Payload: mustJSON(r)} + result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: normalizeRefundState(r.Status), TradeNo: tradeNo, ProviderTradeNo: refundID, Payload: mustJSON(r)} if r.Amount == nil { return nil, errors.New("抖音支付退款响应缺少金额") } @@ -277,7 +277,7 @@ func validateDouyinRefundIdentity(refund *douyin.Refund, tradeNo, refundNo strin return refundID, nil } -func (a *douyinAdapter) Callback(ctx context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { +func (a *douyinAdapter) Callback(ctx context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) { if callback == nil { return nil, errors.New("抖音支付回调为空") } @@ -316,7 +316,7 @@ func (a *douyinAdapter) Callback(ctx context.Context, callback *system.PaymentCa if err != nil { return nil, err } - result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: normalizeDouyinState(pay.TradeState), TradeNo: pay.OutTradeNo, ProviderTradeNo: pay.TransactionId, Payload: append([]byte(nil), plain...), EventID: notify.Id} + result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: normalizeDouyinState(pay.TradeState), TradeNo: pay.OutTradeNo, ProviderTradeNo: pay.TransactionId, Payload: append([]byte(nil), plain...), EventID: notify.Id} if err = populateDouyinPaymentAmount(result, pay.Amount, plain); err != nil { return nil, err } @@ -338,7 +338,7 @@ func (a *douyinAdapter) Callback(ctx context.Context, callback *system.PaymentCa } else if strings.EqualFold(refund.RefundStatus, douyin.RefundStatusClosed) || strings.EqualFold(refund.RefundStatus, douyin.RefundStatusAbnormal) { status = "failed" } - result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: status, TradeNo: refund.OutTradeNo, ProviderTradeNo: refund.RefundId, Payload: mustJSON(refund), EventID: notify.Id} + result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentDouyin, Status: status, TradeNo: refund.OutTradeNo, ProviderTradeNo: refund.RefundId, Payload: mustJSON(refund), EventID: notify.Id} if refund.Amount != nil { result.Amount = int64(refund.Amount.Refund) result.Currency = strings.ToUpper(firstAny(c, "currency")) @@ -365,7 +365,7 @@ func validateDouyinMerchantIdentity(merchantID, appID string, c map[string]any, return nil } -func populateDouyinPaymentAmount(result *system.PaymentResult, amount *douyin.AmountInfo, raw []byte) error { +func populateDouyinPaymentAmount(result *bizpayment.PaymentResult, amount *douyin.AmountInfo, raw []byte) error { if result == nil || amount == nil { return nil } diff --git a/internal/integration/payment/gopay_helpers.go b/internal/integration/payment/gopay_helpers.go index 4fa6ba4..ccbc366 100644 --- a/internal/integration/payment/gopay_helpers.go +++ b/internal/integration/payment/gopay_helpers.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/json" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/http" "strings" @@ -15,7 +15,7 @@ import ( // The adapters in this package intentionally keep GoPay request/response // models below the data boundary. The business layer only sees PaymentResult. -func callbackRequest(callback *system.PaymentCallback) (*http.Request, error) { +func callbackRequest(callback *bizpayment.PaymentCallback) (*http.Request, error) { request, err := http.NewRequest(http.MethodPost, "http://payment-callback.local", bytes.NewReader(callback.Body)) if err != nil { return nil, err diff --git a/internal/integration/payment/gopay_test.go b/internal/integration/payment/gopay_test.go index ee7fc7e..ebb3eeb 100644 --- a/internal/integration/payment/gopay_test.go +++ b/internal/integration/payment/gopay_test.go @@ -14,7 +14,7 @@ import ( "encoding/pem" "fmt" "io" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/http" "net/http/httptest" "net/url" @@ -125,8 +125,8 @@ func TestAlipayTradePayUsesGoPaySDK(t *testing.T) { })) defer server.Close() - result, err := (&alipayAdapter{}).Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentAlipay, TradeNo: "T-ALIPAY-PAY", Subject: "subject", Amount: 1000, + result, err := (&alipayAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentAlipay, TradeNo: "T-ALIPAY-PAY", Subject: "subject", Amount: 1000, Currency: "CNY", Extra: map[string]any{"method": "barcode", "auth_code": "BARCODE-1"}, }, map[string]any{ "app_id": "app-test", "private_key": privatePEM, "public_key": publicPEM, "gateway_url": server.URL, @@ -194,7 +194,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) { })) defer server.Close() - result, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{ + result, err := (&wechatV2Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ TradeNo: "T-WECHAT", Subject: "subject", Amount: 123, Currency: "CNY", NotifyURL: "http://notify.local", }, map[string]any{ "app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key, @@ -220,7 +220,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) { t.Fatal("verification mutated source values") } callbackBody := paymentutil.XMLEncode(callbackValues) - callbackResult, err := (&wechatV2Adapter{}).Callback(context.Background(), &system.PaymentCallback{Body: callbackBody}, map[string]any{ + callbackResult, err := (&wechatV2Adapter{}).Callback(context.Background(), &bizpayment.PaymentCallback{Body: callbackBody}, map[string]any{ "app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key, }) if err != nil { @@ -267,8 +267,8 @@ func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) { })) defer server.Close() - result, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123, + result, err := (&wechatV2Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123, Currency: "CNY", NotifyURL: "http://notify.local", Extra: map[string]any{"method": "barcode", "auth_code": "AUTH-CODE-1"}, }, map[string]any{ @@ -287,8 +287,8 @@ func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) { } func TestWechatV2MicropayRejectsMissingAuthCodeAndUnknownMode(t *testing.T) { - _, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO-MISSING", Subject: "subject", Amount: 1, + _, err := (&wechatV2Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO-MISSING", Subject: "subject", Amount: 1, Currency: "CNY", NotifyURL: "http://notify.local", Extra: map[string]any{"method": "micropay"}, }, map[string]any{"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": "wechat-secret"}) if err == nil || !strings.Contains(err.Error(), "auth_code") { @@ -348,8 +348,8 @@ func TestWechatV2MicropayRejectsInvalidSuccessResponse(t *testing.T) { })) defer server.Close() - _, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123, + _, err := (&wechatV2Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123, Currency: "CNY", Extra: map[string]any{"method": "micropay", "auth_code": "AUTH-CODE-1"}, }, map[string]any{ "app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key, @@ -520,7 +520,7 @@ func TestDouyinPrepayResultUsesGoPayClientSigning(t *testing.T) { if err = json.Unmarshal(result.Payload, &payload); err != nil { t.Fatal(err) } - if result.Provider != system.PaymentDouyin || result.TradeNo != "T-DOUYIN" || strings.TrimSpace(fmt.Sprint(payload["sign"])) == "" && strings.TrimSpace(fmt.Sprint(payload["paySign"])) == "" { + if result.Provider != bizpayment.PaymentDouyin || result.TradeNo != "T-DOUYIN" || strings.TrimSpace(fmt.Sprint(payload["sign"])) == "" && strings.TrimSpace(fmt.Sprint(payload["paySign"])) == "" { t.Fatalf("app=%v result=%+v payload=%#v", app, result, payload) } if app { @@ -581,7 +581,7 @@ func TestWechatV3GoPayCallbackVerificationAndDecryption(t *testing.T) { t.Fatal(err) } signature := testRSA2Sign(t, platformKey, []byte(timestamp+"\n"+headerNonce+"\n"+string(body)+"\n")) - callback := &system.PaymentCallback{ + callback := &bizpayment.PaymentCallback{ Body: body, Headers: map[string]string{ gopayWechatV3.HeaderTimestamp: timestamp, diff --git a/internal/integration/payment/identity_test.go b/internal/integration/payment/identity_test.go index 28eb56c..3d9f6c4 100644 --- a/internal/integration/payment/identity_test.go +++ b/internal/integration/payment/identity_test.go @@ -12,7 +12,7 @@ import ( "encoding/json" "encoding/pem" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net" "net/http" "net/http/httptest" @@ -116,7 +116,7 @@ func TestLakalaKeepsQueryAndFinalTradeIdentitiesSeparate(t *testing.T) { t.Fatalf("query result = %+v", result) } - refund, err := (&lakalaAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ + refund, err := (&lakalaAdapter{}).Refund(context.Background(), &bizpayment.PaymentRefundRequest{ TradeNo: "MERCHANT-LAKALA-1", QueryID: created.QueryID, RefundNo: "REFUND-LAKALA-1", Amount: 40, Currency: "JPY", }, config) @@ -216,8 +216,8 @@ func TestLakalaCreateRoutesSupportedMethods(t *testing.T) { if tc.configMethod != "" { config["method"] = tc.configMethod } - result, err := adapter.Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentLakala, TradeNo: tc.tradeNo, Subject: "subject", Amount: 100, + result, err := adapter.Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentLakala, TradeNo: tc.tradeNo, Subject: "subject", Amount: 100, Currency: "JPY", Extra: extra, }, config) if err != nil { @@ -238,8 +238,8 @@ func TestLakalaCreateRoutesSupportedMethods(t *testing.T) { } func TestLakalaCreateRejectsUnknownMethod(t *testing.T) { - _, err := (&lakalaAdapter{}).Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentLakala, TradeNo: "L-UNKNOWN", Subject: "subject", Amount: 100, + _, err := (&lakalaAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentLakala, TradeNo: "L-UNKNOWN", Subject: "subject", Amount: 100, Currency: "JPY", Extra: map[string]any{"method": "unsupported"}, }, map[string]any{"partner_code": "PART", "credential_code": "credential", "channel": "Wechat"}) if err == nil || !strings.Contains(err.Error(), "不支持的下单方式") { diff --git a/internal/integration/payment/lakala.go b/internal/integration/payment/lakala.go index 7cc4740..1791288 100644 --- a/internal/integration/payment/lakala.go +++ b/internal/integration/payment/lakala.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "strings" "github.com/go-pay/gopay" @@ -17,7 +17,7 @@ func (a *lakalaAdapter) client(c map[string]any) (*lakala.Client, error) { return lakala.NewClient(text(c, "partner_code"), text(c, "credential_code"), !strings.EqualFold(text(c, "environment"), "sandbox")) } -func (a *lakalaAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *lakalaAdapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("拉卡拉支付下单请求为空") } @@ -74,7 +74,7 @@ func lakalaCreateMethod(extra, config map[string]any) string { return strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(method))) } -func lakalaCreateResult(tradeNo string, rsp *lakala.PaymentRsp) (*system.PaymentResult, error) { +func lakalaCreateResult(tradeNo string, rsp *lakala.PaymentRsp) (*bizpayment.PaymentResult, error) { if rsp == nil || !lakalaCreateResponseOK(rsp.ReturnCode, rsp.ResultCode) { return nil, fmt.Errorf("拉卡拉支付下单失败: %v", rsp) } @@ -85,10 +85,10 @@ func lakalaCreateResult(tradeNo string, rsp *lakala.PaymentRsp) (*system.Payment if partnerOrderID := strings.TrimSpace(rsp.PartnerOrderId); partnerOrderID != "" && partnerOrderID != tradeNo { return nil, errors.New("拉卡拉支付下单响应的商户订单号不匹配") } - return &system.PaymentResult{Provider: system.PaymentLakala, Status: "created", TradeNo: tradeNo, QueryID: orderID, Payload: mustJSON(rsp)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentLakala, Status: "created", TradeNo: tradeNo, QueryID: orderID, Payload: mustJSON(rsp)}, nil } -func lakalaRetailCreateResult(tradeNo string, rsp *lakala.RetailPayRsp) (*system.PaymentResult, error) { +func lakalaRetailCreateResult(tradeNo string, rsp *lakala.RetailPayRsp) (*bizpayment.PaymentResult, error) { if rsp == nil || !lakalaRetailCreateResponseOK(rsp.ReturnCode, rsp.ResultCode) { return nil, fmt.Errorf("拉卡拉线下支付下单失败: %v", rsp) } @@ -99,15 +99,15 @@ func lakalaRetailCreateResult(tradeNo string, rsp *lakala.RetailPayRsp) (*system if partnerOrderID := strings.TrimSpace(rsp.PartnerOrderId); partnerOrderID != "" && partnerOrderID != tradeNo { return nil, errors.New("拉卡拉线下支付下单响应的商户订单号不匹配") } - result := &system.PaymentResult{ - Provider: system.PaymentLakala, Status: "created", TradeNo: tradeNo, + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentLakala, Status: "created", TradeNo: tradeNo, QueryID: orderID, Amount: int64(rsp.TotalFee), Currency: strings.ToUpper(rsp.Currency), Payload: mustJSON(rsp), } return result, nil } -func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { +func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { client, err := a.client(c) if err != nil { return nil, err @@ -131,7 +131,7 @@ func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string] if returnedTradeNo == "" { return nil, errors.New("拉卡拉支付查单响应缺少商户订单号") } - result := &system.PaymentResult{Provider: system.PaymentLakala, Status: status, TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.ChannelOrderId), QueryID: orderID, Amount: int64(rsp.TotalFee), Currency: strings.ToUpper(rsp.Currency), Payload: mustJSON(rsp)} + result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentLakala, Status: status, TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.ChannelOrderId), QueryID: orderID, Amount: int64(rsp.TotalFee), Currency: strings.ToUpper(rsp.Currency), Payload: mustJSON(rsp)} if result.Currency == "" { result.Currency = strings.ToUpper(firstAny(c, "currency", "default_currency")) } @@ -149,7 +149,7 @@ func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string] return result, nil } -func (a *lakalaAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *lakalaAdapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("拉卡拉支付退款请求为空") } @@ -181,7 +181,7 @@ func (a *lakalaAdapter) Refund(ctx context.Context, req *system.PaymentRefundReq return lakalaRefundResult(&effective, rsp) } -func lakalaRefundResult(req *system.PaymentRefundRequest, rsp *lakala.RefundRsp) (*system.PaymentResult, error) { +func lakalaRefundResult(req *bizpayment.PaymentRefundRequest, rsp *lakala.RefundRsp) (*bizpayment.PaymentResult, error) { if rsp == nil || !lakalaRefundResponseOK(rsp.ReturnCode, rsp.ResultCode) { return nil, fmt.Errorf("拉卡拉支付退款失败: %v", rsp) } @@ -204,10 +204,10 @@ func lakalaRefundResult(req *system.PaymentRefundRequest, rsp *lakala.RefundRsp) } else if requestCurrency != "" && currency != requestCurrency { return nil, errors.New("拉卡拉支付退款响应币种不匹配") } - return &system.PaymentResult{Provider: system.PaymentLakala, Status: normalizeLakalaRefundState(rsp.ResultCode), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: int64(rsp.Amount), Currency: currency, Payload: mustJSON(rsp)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentLakala, Status: normalizeLakalaRefundState(rsp.ResultCode), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: int64(rsp.Amount), Currency: currency, Payload: mustJSON(rsp)}, nil } -func (a *lakalaAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { +func (a *lakalaAdapter) Callback(_ context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) { if callback == nil { return nil, errors.New("拉卡拉支付回调为空") } @@ -222,5 +222,5 @@ func (a *lakalaAdapter) Callback(_ context.Context, callback *system.PaymentCall if err := lakala.VerifySign(notify, text(c, "partner_code"), text(c, "credential_code")); err != nil { return nil, err } - return &system.PaymentResult{Provider: system.PaymentLakala, Status: "success", TradeNo: notify.PartnerOrderId, ProviderTradeNo: strings.TrimSpace(notify.ChannelOrderId), QueryID: strings.TrimSpace(notify.OrderId), Amount: int64(notify.TotalFee), Currency: strings.ToUpper(notify.Currency), Payload: mustJSON(notify)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentLakala, Status: "success", TradeNo: notify.PartnerOrderId, ProviderTradeNo: strings.TrimSpace(notify.ChannelOrderId), QueryID: strings.TrimSpace(notify.OrderId), Amount: int64(notify.TotalFee), Currency: strings.ToUpper(notify.Currency), Payload: mustJSON(notify)}, nil } diff --git a/internal/integration/payment/lakala_test.go b/internal/integration/payment/lakala_test.go index 03fd1e2..0cf2869 100644 --- a/internal/integration/payment/lakala_test.go +++ b/internal/integration/payment/lakala_test.go @@ -1,7 +1,7 @@ package payment import ( - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "testing" "github.com/go-pay/gopay/lakala" @@ -58,7 +58,7 @@ func TestLakalaQueryStateMapping(t *testing.T) { } func TestLakalaRefundResultBindsRefundIdentity(t *testing.T) { - req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"} + req := &bizpayment.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"} base := func() *lakala.RefundRsp { return &lakala.RefundRsp{ ErrorCode: lakala.ErrorCode{ReturnCode: "SUCCESS", ResultCode: "SUCCESS"}, @@ -98,7 +98,7 @@ func TestLakalaRefundResultBindsRefundIdentity(t *testing.T) { } func TestLakalaRefundResultMapsProviderState(t *testing.T) { - req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"} + req := &bizpayment.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"} for _, tc := range []struct { code string want string diff --git a/internal/integration/payment/native_test.go b/internal/integration/payment/native_test.go index e569707..20bab7d 100644 --- a/internal/integration/payment/native_test.go +++ b/internal/integration/payment/native_test.go @@ -4,7 +4,7 @@ import ( "crypto/aes" "crypto/cipher" "encoding/base64" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/url" "strings" "testing" @@ -94,7 +94,7 @@ func TestAlipaySignContentSortedAndExcludesSign(t *testing.T) { } func TestAlipayBreakdownWithPointPayment(t *testing.T) { - result := &system.PaymentResult{Status: "success", Amount: 1000} + result := &bizpayment.PaymentResult{Status: "success", Amount: 1000} err := populateAlipayBreakdown(result, map[string]any{ "total_amount": "10.00", "buyer_pay_amount": "8.00", "point_amount": "2.00", "receipt_amount": "8.00", }) @@ -107,7 +107,7 @@ func TestAlipayBreakdownWithPointPayment(t *testing.T) { } func TestWechatV2BreakdownWithCoupon(t *testing.T) { - result := &system.PaymentResult{Status: "success", Amount: 1000} + result := &bizpayment.PaymentResult{Status: "success", Amount: 1000} err := populateWechatV2Breakdown(result, map[string]string{"cash_fee": "900", "coupon_fee": "100", "settlement_total_fee": "950"}) if err != nil { t.Fatal(err) @@ -118,7 +118,7 @@ func TestWechatV2BreakdownWithCoupon(t *testing.T) { } func TestWechatV3BreakdownWithPromotion(t *testing.T) { - result := &system.PaymentResult{Status: "success", Amount: 1000} + result := &bizpayment.PaymentResult{Status: "success", Amount: 1000} err := populateWechatV3Breakdown(result, map[string]any{ "amount": map[string]any{"payer_total": float64(800), "settlement_amount": float64(780)}, "promotion_detail": []any{map[string]any{"amount": float64(200)}}, @@ -132,7 +132,7 @@ func TestWechatV3BreakdownWithPromotion(t *testing.T) { } func TestVendorBreakdownUsesConfiguredUnits(t *testing.T) { - result := &system.PaymentResult{Status: "success", Amount: 1000, Currency: "CNY"} + result := &bizpayment.PaymentResult{Status: "success", Amount: 1000, Currency: "CNY"} object := map[string]any{"data": map[string]any{ "payer": "8.00", "cash": "6.00", "points": "2.00", "discount": "2.00", "settlement": "7.80", }} diff --git a/internal/integration/payment/paypal.go b/internal/integration/payment/paypal.go index eb8a72b..e1431c7 100644 --- a/internal/integration/payment/paypal.go +++ b/internal/integration/payment/paypal.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "math" "strconv" "strings" @@ -31,7 +31,7 @@ func (a *paypalAdapter) client(c map[string]any) (*paypal.Client, error) { return client, nil } -func (a *paypalAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *paypalAdapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("PayPal 下单参数为空") } @@ -74,7 +74,7 @@ func (a *paypalAdapter) Create(ctx context.Context, req *system.PaymentRequest, return paypalCreateResult(req, order, c) } -func (a *paypalAdapter) Query(ctx context.Context, orderID string, c map[string]any) (*system.PaymentResult, error) { +func (a *paypalAdapter) Query(ctx context.Context, orderID string, c map[string]any) (*bizpayment.PaymentResult, error) { orderID = strings.TrimSpace(orderID) if orderID == "" { return nil, errors.New("PayPal 查单缺少 order ID") @@ -153,7 +153,7 @@ func paypalAutoCaptureEnabled(c map[string]any) bool { return true } -func (a *paypalAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *paypalAdapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("PayPal 退款请求为空") } @@ -201,7 +201,7 @@ func (a *paypalAdapter) Refund(ctx context.Context, req *system.PaymentRefundReq return paypalRefundResult(req, rsp.Response, c) } -func paypalRefundResult(req *system.PaymentRefundRequest, refund *paypal.PaymentCaptureRefund, c map[string]any) (*system.PaymentResult, error) { +func paypalRefundResult(req *bizpayment.PaymentRefundRequest, refund *paypal.PaymentCaptureRefund, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil || refund == nil { return nil, errors.New("PayPal 退款响应为空") } @@ -215,8 +215,8 @@ func paypalRefundResult(req *system.PaymentRefundRequest, refund *paypal.Payment return nil, errors.New("PayPal 退款响应缺少 refund ID") } currency := strings.ToUpper(strings.TrimSpace(req.Currency)) - result := &system.PaymentResult{ - Provider: system.PaymentPayPal, + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentPayPal, Status: normalizePayPalRefundState(refund.Status), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, @@ -246,7 +246,7 @@ func paypalRefundResult(req *system.PaymentRefundRequest, refund *paypal.Payment return result, nil } -func paypalCreateResult(req *system.PaymentRequest, order *paypal.OrderDetail, c map[string]any) (*system.PaymentResult, error) { +func paypalCreateResult(req *bizpayment.PaymentRequest, order *paypal.OrderDetail, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil || order == nil { return nil, errors.New("PayPal 下单响应为空") } @@ -277,7 +277,7 @@ func paypalCreateResult(req *system.PaymentRequest, order *paypal.OrderDetail, c return result, nil } -func (a *paypalAdapter) Callback(ctx context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { +func (a *paypalAdapter) Callback(ctx context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) { if callback == nil || len(callback.Body) == 0 { return nil, errors.New("PayPal 回调为空") } @@ -325,7 +325,7 @@ func (a *paypalAdapter) Callback(ctx context.Context, callback *system.PaymentCa return result, nil } -func paypalApplicationContext(req *system.PaymentRequest, c map[string]any) map[string]any { +func paypalApplicationContext(req *bizpayment.PaymentRequest, c map[string]any) map[string]any { applicationContext := map[string]any{} if configured, ok := req.Extra["application_context"].(map[string]any); ok { for key, value := range configured { @@ -352,7 +352,7 @@ func paypalApplicationContext(req *system.PaymentRequest, c map[string]any) map[ return applicationContext } -func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo string, c map[string]any) (*system.PaymentResult, error) { +func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { if order == nil { return nil, errors.New("PayPal 订单结果为空") } @@ -364,8 +364,8 @@ func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo strin tradeNo = fallbackTradeNo } capture := paypalOrderCapture(order) - result := &system.PaymentResult{ - Provider: system.PaymentPayPal, + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentPayPal, Status: paypalOrderCaptureState(order), TradeNo: tradeNo, QueryID: queryID, @@ -498,7 +498,7 @@ func paypalOrderAmount(order *paypal.OrderDetail, c map[string]any) (int64, stri return total, currency, found, nil } -func paypalRefundCaptureID(ctx context.Context, client *paypal.Client, req *system.PaymentRefundRequest) (string, error) { +func paypalRefundCaptureID(ctx context.Context, client *paypal.Client, req *bizpayment.PaymentRefundRequest) (string, error) { if req == nil { return "", errors.New("PayPal 退款请求为空") } @@ -533,7 +533,7 @@ func paypalRefundCaptureID(ctx context.Context, client *paypal.Client, req *syst return capture.Id, nil } -func paypalWebhookResult(event *paypal.WebhookEvent) (*system.PaymentResult, error) { +func paypalWebhookResult(event *paypal.WebhookEvent) (*bizpayment.PaymentResult, error) { var resource map[string]any if err := json.Unmarshal(event.Resource, &resource); err != nil { return nil, fmt.Errorf("PayPal 回调 resource 无效: %w", err) @@ -567,8 +567,8 @@ func paypalWebhookResult(event *paypal.WebhookEvent) (*system.PaymentResult, err if tradeNo == "" || queryID == "" { return nil, errors.New("PayPal 回调缺少商户订单号或 order ID") } - return &system.PaymentResult{ - Provider: system.PaymentPayPal, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentPayPal, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, @@ -587,7 +587,7 @@ func paypalPurchaseUnitTradeNo(resource map[string]any) string { return "" } -func paypalCallbackHeader(callback *system.PaymentCallback, name string) string { +func paypalCallbackHeader(callback *bizpayment.PaymentCallback, name string) string { for key, value := range callback.Headers { if strings.EqualFold(key, name) { return strings.TrimSpace(value) diff --git a/internal/integration/payment/paypal_test.go b/internal/integration/payment/paypal_test.go index 1506d40..3d01367 100644 --- a/internal/integration/payment/paypal_test.go +++ b/internal/integration/payment/paypal_test.go @@ -3,7 +3,7 @@ package payment import ( "context" "encoding/json" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/http" "net/http/httptest" "strings" @@ -51,7 +51,7 @@ func TestPayPalCreateAndQueryKeepOrderAndCaptureIDsSeparate(t *testing.T) { adapter := &paypalAdapter{} config := paypalTestConfig(server.URL) - created, err := adapter.Create(context.Background(), &system.PaymentRequest{ + created, err := adapter.Create(context.Background(), &bizpayment.PaymentRequest{ TradeNo: "LOCAL-1", Subject: "Order one", Amount: 1099, @@ -142,7 +142,7 @@ func TestPayPalCreateRejectsMismatchedResponseIdentityAndAmount(t *testing.T) { })) defer server.Close() - _, err := (&paypalAdapter{}).Create(context.Background(), &system.PaymentRequest{ + _, err := (&paypalAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ TradeNo: "LOCAL-1", Subject: "Order one", Amount: 1099, Currency: "USD", }, paypalTestConfig(server.URL)) if err == nil || !strings.Contains(err.Error(), tc.want) { @@ -242,8 +242,8 @@ func TestPayPalRefundUsesPersistedCaptureID(t *testing.T) { config := paypalTestConfig(server.URL) config["provider_trade_no"] = "WRONG-CONFIG-CAPTURE" - result, err := (&paypalAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ - Provider: system.PaymentPayPal, TradeNo: "LOCAL-1", ProviderTradeNo: "CAPTURE-1", + result, err := (&paypalAdapter{}).Refund(context.Background(), &bizpayment.PaymentRefundRequest{ + Provider: bizpayment.PaymentPayPal, TradeNo: "LOCAL-1", ProviderTradeNo: "CAPTURE-1", QueryID: "ORDER-1", RefundNo: "REFUND-1", Amount: 123, TotalAmount: 1099, Currency: "USD", }, config) if err != nil { @@ -286,8 +286,8 @@ func TestPayPalRefundResolvesCaptureFromPersistedQueryID(t *testing.T) { config := paypalTestConfig(server.URL) config["capture_id"] = "WRONG-CONFIG-CAPTURE" - result, err := (&paypalAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ - Provider: system.PaymentPayPal, TradeNo: "LOCAL-1", QueryID: "ORDER-1", + result, err := (&paypalAdapter{}).Refund(context.Background(), &bizpayment.PaymentRefundRequest{ + Provider: bizpayment.PaymentPayPal, TradeNo: "LOCAL-1", QueryID: "ORDER-1", RefundNo: "REFUND-2", Amount: 123, TotalAmount: 1099, Currency: "USD", }, config) if err != nil { @@ -301,8 +301,8 @@ func TestPayPalRefundResolvesCaptureFromPersistedQueryID(t *testing.T) { func TestPayPalRefundRejectsStaticCaptureIDWithoutDurableIdentity(t *testing.T) { config := paypalTestConfig("https://paypal.invalid") config["capture_id"] = "CONFIG-CAPTURE" - _, err := (&paypalAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{ - Provider: system.PaymentPayPal, TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD", + _, err := (&paypalAdapter{}).Refund(context.Background(), &bizpayment.PaymentRefundRequest{ + Provider: bizpayment.PaymentPayPal, TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD", }, config) if err == nil { t.Fatal("Refund() accepted a static config capture ID") @@ -310,7 +310,7 @@ func TestPayPalRefundRejectsStaticCaptureIDWithoutDurableIdentity(t *testing.T) } func TestPayPalRefundResultBindsRefundIdentity(t *testing.T) { - req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD"} + req := &bizpayment.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD"} base := func() *paypal.PaymentCaptureRefund { return &paypal.PaymentCaptureRefund{ Id: "PAYPAL-REFUND-1", InvoiceId: "REFUND-1", Status: "PENDING", @@ -367,7 +367,7 @@ func TestPayPalCallbackUsesGoPayWebhookVerification(t *testing.T) { }`) config := paypalTestConfig(server.URL) config["webhook_id"] = "HOOK-1" - result, err := (&paypalAdapter{}).Callback(context.Background(), &system.PaymentCallback{ + result, err := (&paypalAdapter{}).Callback(context.Background(), &bizpayment.PaymentCallback{ Body: body, Headers: map[string]string{ "Paypal-Auth-Algo": "SHA256withRSA", diff --git a/internal/integration/payment/qq.go b/internal/integration/payment/qq.go index c42b036..440ab8e 100644 --- a/internal/integration/payment/qq.go +++ b/internal/integration/payment/qq.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "strings" "github.com/go-pay/gopay" @@ -60,7 +60,7 @@ func verifyQQResponse(apiKey, signType string, response any) error { return nil } -func (a *qqAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *qqAdapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("QQ 支付下单请求为空") } @@ -103,7 +103,7 @@ func (a *qqAdapter) Create(ctx context.Context, req *system.PaymentRequest, c ma if status == "pending" && strings.EqualFold(rsp.ResultCode, "SUCCESS") && strings.TrimSpace(rsp.TransactionId) != "" { status = "success" } - return &system.PaymentResult{Provider: system.PaymentQQ, Status: status, TradeNo: firstNonEmptyQQ(rsp.OutTradeNo, req.TradeNo), ProviderTradeNo: strings.TrimSpace(rsp.TransactionId), Payload: mustJSON(rsp)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentQQ, Status: status, TradeNo: firstNonEmptyQQ(rsp.OutTradeNo, req.TradeNo), ProviderTradeNo: strings.TrimSpace(rsp.TransactionId), Payload: mustJSON(rsp)}, nil } bm.Set("trade_type", tradeType) mergeGoPayExtras(bm, req.Extra, "nonce_str", "body", "out_trade_no", "total_fee", "spbill_create_ip", "notify_url", "trade_type", "mch_id", "sign_type") @@ -117,7 +117,7 @@ func (a *qqAdapter) Create(ctx context.Context, req *system.PaymentRequest, c ma if rsp == nil || !qqOK(rsp.ReturnCode, rsp.ResultCode) { return nil, fmt.Errorf("QQ 支付下单失败: %s", first(rspMap(rsp), "return_msg", "ret_msg", "err_code_des")) } - result := &system.PaymentResult{Provider: system.PaymentQQ, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)} + result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentQQ, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)} return result, nil } @@ -152,7 +152,7 @@ func firstNonEmptyQQ(values ...string) string { return "" } -func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { +func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("QQ 支付查单缺少 out_trade_no") @@ -177,7 +177,7 @@ func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) if rsp == nil || !qqOK(rsp.ReturnCode, rsp.ResultCode) { return nil, fmt.Errorf("QQ 支付查单失败: %s", first(rspMap(rsp), "return_msg", "ret_msg", "err_code_des")) } - result := &system.PaymentResult{Provider: system.PaymentQQ, Status: normalizeQQState(rsp.TradeState), TradeNo: rsp.OutTradeNo, ProviderTradeNo: rsp.TransactionId, Payload: mustJSON(rsp)} + result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentQQ, Status: normalizeQQState(rsp.TradeState), TradeNo: rsp.OutTradeNo, ProviderTradeNo: rsp.TransactionId, Payload: mustJSON(rsp)} if result.TradeNo == "" { result.TradeNo = tradeNo } else if strings.TrimSpace(result.TradeNo) != tradeNo { @@ -204,7 +204,7 @@ func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) return result, nil } -func (a *qqAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *qqAdapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("QQ 支付退款请求为空") } @@ -268,7 +268,7 @@ func (a *qqAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest if returnedTradeNo == "" { returnedTradeNo = tradeNo } - return &system.PaymentResult{Provider: system.PaymentQQ, Status: "pending", TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.RefundId), Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentQQ, Status: "pending", TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.RefundId), Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil } func validateQQRefundIdentity(tradeNo, refundNo string, rsp *gopayQQ.RefundResponse) error { @@ -289,7 +289,7 @@ func validateQQRefundIdentity(tradeNo, refundNo string, rsp *gopayQQ.RefundRespo return nil } -func (a *qqAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { +func (a *qqAdapter) Callback(_ context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) { if callback == nil { return nil, errors.New("QQ 支付回调为空") } @@ -318,5 +318,5 @@ func (a *qqAdapter) Callback(_ context.Context, callback *system.PaymentCallback status = "success" } payload, _ := json.Marshal(values) - return &system.PaymentResult{Provider: system.PaymentQQ, Status: status, TradeNo: values["out_trade_no"], ProviderTradeNo: values["transaction_id"], Payload: payload}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentQQ, Status: status, TradeNo: values["out_trade_no"], ProviderTradeNo: values["transaction_id"], Payload: payload}, nil } diff --git a/internal/integration/payment/qq_test.go b/internal/integration/payment/qq_test.go index 2e59c70..9bdc99f 100644 --- a/internal/integration/payment/qq_test.go +++ b/internal/integration/payment/qq_test.go @@ -6,7 +6,7 @@ import ( "encoding/json" "fmt" "io" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net" "net/http" "net/http/httptest" @@ -84,14 +84,14 @@ func TestQQCreateKeepsClientPaymentDataOutOfDurableIdentities(t *testing.T) { _, _ = w.Write(paymentutil.XMLEncode(encoded)) })) - result, err := (&qqAdapter{}).Create(context.Background(), &system.PaymentRequest{ + result, err := (&qqAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ TradeNo: "MERCHANT-QQ-1", Subject: "subject", Amount: 100, Currency: "CNY", ClientIP: "127.0.0.1", NotifyURL: "https://merchant.example/qq/notify", }, map[string]any{"mch_id": "QQ-MERCHANT", "api_key": "qq-api-key"}) if err != nil { t.Fatal(err) } - if result.Provider != system.PaymentQQ || result.Status != "created" || result.TradeNo != "MERCHANT-QQ-1" { + if result.Provider != bizpayment.PaymentQQ || result.Status != "created" || result.TradeNo != "MERCHANT-QQ-1" { t.Fatalf("create result = %+v", result) } if result.QueryID != "" || result.ProviderTradeNo != "" { diff --git a/internal/integration/payment/refund_boundary_test.go b/internal/integration/payment/refund_boundary_test.go index 0e8dac2..cdfefc2 100644 --- a/internal/integration/payment/refund_boundary_test.go +++ b/internal/integration/payment/refund_boundary_test.go @@ -2,7 +2,7 @@ package payment import ( "context" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "strings" "testing" @@ -15,12 +15,12 @@ import ( func TestWechatV2RefundRejectsInvalidRequestBeforeClient(t *testing.T) { for _, tc := range []struct { name string - req *system.PaymentRefundRequest + req *bizpayment.PaymentRefundRequest want string }{ - {name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1, TotalAmount: 1}, want: "out_trade_no"}, - {name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1, TotalAmount: 1}, want: "out_refund_no"}, - {name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, + {name: "blank trade number", req: &bizpayment.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1, TotalAmount: 1}, want: "out_trade_no"}, + {name: "blank refund number", req: &bizpayment.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1, TotalAmount: 1}, want: "out_refund_no"}, + {name: "invalid amount", req: &bizpayment.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, } { t.Run(tc.name, func(t *testing.T) { _, err := (&wechatV2Adapter{}).Refund(context.Background(), tc.req, nil) @@ -60,12 +60,12 @@ func TestWechatV2RefundIdentityValidation(t *testing.T) { func TestQQRefundRejectsInvalidRequestBeforeClient(t *testing.T) { for _, tc := range []struct { name string - req *system.PaymentRefundRequest + req *bizpayment.PaymentRefundRequest want string }{ - {name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "out_trade_no"}, - {name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "out_refund_no"}, - {name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"}, + {name: "blank trade number", req: &bizpayment.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "out_trade_no"}, + {name: "blank refund number", req: &bizpayment.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "out_refund_no"}, + {name: "invalid amount", req: &bizpayment.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"}, } { t.Run(tc.name, func(t *testing.T) { _, err := (&qqAdapter{}).Refund(context.Background(), tc.req, nil) @@ -105,12 +105,12 @@ func TestQQRefundIdentityValidation(t *testing.T) { func TestSaobeiRefundRejectsInvalidRequestBeforeClient(t *testing.T) { for _, tc := range []struct { name string - req *system.PaymentRefundRequest + req *bizpayment.PaymentRefundRequest want string }{ - {name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "商户订单号"}, - {name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "退款单号"}, - {name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"}, + {name: "blank trade number", req: &bizpayment.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "商户订单号"}, + {name: "blank refund number", req: &bizpayment.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "退款单号"}, + {name: "invalid amount", req: &bizpayment.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"}, } { t.Run(tc.name, func(t *testing.T) { clientCreated := false @@ -133,7 +133,7 @@ func TestSaobeiRefundRejectsMismatchedRefundNumber(t *testing.T) { adapter := newSaobeiTestAdapter(&saobeiClientStub{refund: func(context.Context, gopay.BodyMap) (*saobei.RefundRsp, error) { return &saobei.RefundRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, TerminalTrace: "OTHER", OutRefundNo: "SAOBEI-REFUND-1", RefundFee: "25"}, nil }}) - _, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 25}, nil) + _, err := adapter.Refund(context.Background(), &bizpayment.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 25}, nil) if err == nil || !strings.Contains(err.Error(), "terminal_trace") { t.Fatalf("Refund() error = %v", err) } @@ -143,7 +143,7 @@ func TestSaobeiBarcodeCreateTreatsPayingAsPending(t *testing.T) { adapter := newSaobeiTestAdapter(&saobeiClientStub{barcodePay: func(context.Context, gopay.BodyMap) (*saobei.BarcodePayRsp, error) { return &saobei.BarcodePayRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: saobei.ResultCodePaying}, OutTradeNo: "SAOBEI-ORDER-1"}, nil }}) - result, err := adapter.Create(context.Background(), &system.PaymentRequest{TradeNo: "LOCAL-1", Amount: 10, Extra: map[string]any{"method": "barcode", "auth_no": "AUTH-1"}}, nil) + result, err := adapter.Create(context.Background(), &bizpayment.PaymentRequest{TradeNo: "LOCAL-1", Amount: 10, Extra: map[string]any{"method": "barcode", "auth_no": "AUTH-1"}}, nil) if err != nil { t.Fatalf("Create() error = %v", err) } diff --git a/internal/integration/payment/response_validation_test.go b/internal/integration/payment/response_validation_test.go index 0e7aeca..5440f85 100644 --- a/internal/integration/payment/response_validation_test.go +++ b/internal/integration/payment/response_validation_test.go @@ -1,7 +1,7 @@ package payment import ( - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "strings" "testing" @@ -28,7 +28,7 @@ func TestWechatV2ResponseRequiresBusinessSuccessCode(t *testing.T) { func TestPopulateDouyinPaymentAmountUsesSignedFieldPresence(t *testing.T) { amount := &douyin.AmountInfo{Total: 100, PayerTotal: 0, Currency: "CNY", PayerCurrency: "CNY"} - result := &system.PaymentResult{} + result := &bizpayment.PaymentResult{} if err := populateDouyinPaymentAmount(result, amount, []byte(`{"amount":{"total":100,"payer_total":0,"currency":"CNY","payer_currency":"CNY"}}`)); err != nil { t.Fatal(err) } @@ -36,7 +36,7 @@ func TestPopulateDouyinPaymentAmountUsesSignedFieldPresence(t *testing.T) { t.Fatalf("explicit zero payer_total was not preserved: %+v", result) } - result = &system.PaymentResult{} + result = &bizpayment.PaymentResult{} if err := populateDouyinPaymentAmount(result, amount, []byte(`{"amount":{"total":100,"currency":"CNY"}}`)); err != nil { t.Fatal(err) } @@ -44,7 +44,7 @@ func TestPopulateDouyinPaymentAmountUsesSignedFieldPresence(t *testing.T) { t.Fatalf("missing payer_total was treated as authoritative: %+v", result) } - if err := populateDouyinPaymentAmount(&system.PaymentResult{}, &douyin.AmountInfo{Total: 100, PayerTotal: 101}, []byte(`{"amount":{"total":100,"payer_total":101}}`)); err == nil { + if err := populateDouyinPaymentAmount(&bizpayment.PaymentResult{}, &douyin.AmountInfo{Total: 100, PayerTotal: 101}, []byte(`{"amount":{"total":100,"payer_total":101}}`)); err == nil { t.Fatal("accepted payer_total greater than total") } } @@ -73,7 +73,7 @@ func TestValidateDouyinMerchantIdentity(t *testing.T) { } func TestAlipayRefundResultValidatesIdentityAndAmount(t *testing.T) { - req := &system.PaymentRefundRequest{ + req := &bizpayment.PaymentRefundRequest{ TradeNo: "T-ALIPAY", ProviderTradeNo: "P-ALIPAY", RefundNo: "R-ALIPAY", Amount: 123, Currency: "CNY", } diff --git a/internal/integration/payment/result.go b/internal/integration/payment/result.go index 948bca1..4d3c9bb 100644 --- a/internal/integration/payment/result.go +++ b/internal/integration/payment/result.go @@ -4,14 +4,14 @@ import ( "encoding/json" "errors" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/url" "strings" "kra/pkg/paymentkit" ) -func callbackFields(callback *system.PaymentCallback) map[string]string { +func callbackFields(callback *bizpayment.PaymentCallback) map[string]string { fields := map[string]string{} if callback == nil { return fields diff --git a/internal/integration/payment/result_test.go b/internal/integration/payment/result_test.go index e178848..7275983 100644 --- a/internal/integration/payment/result_test.go +++ b/internal/integration/payment/result_test.go @@ -1,12 +1,12 @@ package payment import ( - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "testing" ) func TestCallbackFieldsAcceptsCaseInsensitiveContentType(t *testing.T) { - callback := &system.PaymentCallback{ + callback := &bizpayment.PaymentCallback{ Headers: map[string]string{"content-type": "Application/X-WWW-Form-Urlencoded"}, Body: []byte("out_trade_no=ORDER-1&sign=signature"), } diff --git a/internal/integration/payment/saobei.go b/internal/integration/payment/saobei.go index 87e6b39..b051b8e 100644 --- a/internal/integration/payment/saobei.go +++ b/internal/integration/payment/saobei.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "strings" "time" @@ -34,7 +34,7 @@ func (a *saobeiAdapter) client(c map[string]any) (saobeiClient, error) { return saobei.NewClient(text(c, "inst_no"), text(c, "key"), text(c, "merchant_no"), text(c, "terminal_id"), text(c, "access_token"), !strings.EqualFold(text(c, "environment"), "sandbox")) } -func (a *saobeiAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *saobeiAdapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("扫呗支付下单请求为空") } @@ -69,7 +69,7 @@ func (a *saobeiAdapter) Create(ctx context.Context, req *system.PaymentRequest, if strings.TrimSpace(rsp.ResultCode) == saobei.ResultCodePaying { status = "pending" } - return &system.PaymentResult{Provider: system.PaymentSaobei, Status: status, TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentSaobei, Status: status, TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil } rsp, err := client.MiniPay(ctx, bm) if err != nil { @@ -81,7 +81,7 @@ func (a *saobeiAdapter) Create(ctx context.Context, req *system.PaymentRequest, if strings.TrimSpace(rsp.OutTradeNo) == "" { return nil, errors.New("扫呗支付下单响应缺少 out_trade_no") } - return &system.PaymentResult{Provider: system.PaymentSaobei, Status: "client_pending", TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentSaobei, Status: "client_pending", TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil } func saobeiCreateMethod(extra, config map[string]any) (string, error) { @@ -100,7 +100,7 @@ func saobeiCreateMethod(extra, config map[string]any) (string, error) { } } -func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { +func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("扫呗支付查单缺少 out_trade_no") @@ -140,7 +140,7 @@ func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string] if strings.TrimSpace(rsp.ResultCode) == saobei.ResultCodePaying { status = "pending" } - result := &system.PaymentResult{Provider: system.PaymentSaobei, Status: status, TradeNo: tradeNoResult, ProviderTradeNo: strings.TrimSpace(rsp.ChannelTradeNo), QueryID: queryID, Payload: mustJSON(rsp), Currency: strings.ToUpper(firstAnyOr(c, "CNY", "currency", "default_currency"))} + result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentSaobei, Status: status, TradeNo: tradeNoResult, ProviderTradeNo: strings.TrimSpace(rsp.ChannelTradeNo), QueryID: queryID, Payload: mustJSON(rsp), Currency: strings.ToUpper(firstAnyOr(c, "CNY", "currency", "default_currency"))} if rsp.TotalFee != "" { result.Amount, err = parseIntegerAmount(rsp.TotalFee) if err != nil { @@ -161,7 +161,7 @@ func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string] return result, err } -func (a *saobeiAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *saobeiAdapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("扫呗支付退款请求为空") } @@ -214,9 +214,9 @@ func (a *saobeiAdapter) Refund(ctx context.Context, req *system.PaymentRefundReq if refundAmount != req.Amount { return nil, errors.New("扫呗支付退款响应金额不匹配") } - return &system.PaymentResult{Provider: system.PaymentSaobei, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentSaobei, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil } -func (a *saobeiAdapter) Callback(_ context.Context, _ *system.PaymentCallback, _ map[string]any) (*system.PaymentResult, error) { +func (a *saobeiAdapter) Callback(_ context.Context, _ *bizpayment.PaymentCallback, _ map[string]any) (*bizpayment.PaymentResult, error) { return nil, errors.New("扫呗支付回调没有可复用的 GoPay 验签器,请改用主动查单") } diff --git a/internal/integration/payment/saobei_test.go b/internal/integration/payment/saobei_test.go index 6afbbad..622767c 100644 --- a/internal/integration/payment/saobei_test.go +++ b/internal/integration/payment/saobei_test.go @@ -2,7 +2,7 @@ package payment import ( "context" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "testing" "github.com/go-pay/gopay" @@ -65,7 +65,7 @@ func TestSaobeiCreateKeepsQueryAndProviderTradeIDsSeparate(t *testing.T) { adapter := newSaobeiTestAdapter(&saobeiClientStub{miniPay: func(context.Context, gopay.BodyMap) (*saobei.MiniPayRsp, error) { return &saobei.MiniPayRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, OutTradeNo: "SAOBEI-ORDER-1"}, nil }}) - result, err := adapter.Create(context.Background(), &system.PaymentRequest{ + result, err := adapter.Create(context.Background(), &bizpayment.PaymentRequest{ TradeNo: "LOCAL-1", Amount: 100, Extra: map[string]any{"sub_appid": "APP-1", "open_id": "OPEN-1"}, @@ -84,7 +84,7 @@ func TestSaobeiCreateKeepsQueryAndProviderTradeIDsSeparate(t *testing.T) { request = bm return &saobei.BarcodePayRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, OutTradeNo: "SAOBEI-ORDER-2", ChannelTradeNo: "CHANNEL-2"}, nil }}) - result, err := adapter.Create(context.Background(), &system.PaymentRequest{ + result, err := adapter.Create(context.Background(), &bizpayment.PaymentRequest{ TradeNo: "LOCAL-2", Amount: 200, Extra: map[string]any{"method": "barcode", "auth_code": "AUTH-2"}, @@ -116,7 +116,7 @@ func TestSaobeiCreateRejectsMissingPlatformOrderID(t *testing.T) { if method != "" { extra["method"] = method } - if _, err := adapter.Create(context.Background(), &system.PaymentRequest{TradeNo: "LOCAL-MISSING", Amount: 1, Extra: extra}, nil); err == nil { + if _, err := adapter.Create(context.Background(), &bizpayment.PaymentRequest{TradeNo: "LOCAL-MISSING", Amount: 1, Extra: extra}, nil); err == nil { t.Fatal("Create() accepted response without out_trade_no") } }) @@ -130,7 +130,7 @@ func TestSaobeiCreateRejectsUnknownMethodBeforeClient(t *testing.T) { return &saobeiClientStub{}, nil }} - _, err := adapter.Create(context.Background(), &system.PaymentRequest{ + _, err := adapter.Create(context.Background(), &bizpayment.PaymentRequest{ TradeNo: "LOCAL-UNKNOWN-METHOD", Amount: 1, Extra: map[string]any{"method": "unsupported"}, }, nil) if err == nil { @@ -229,7 +229,7 @@ func TestSaobeiRefundPrefersPersistedQueryID(t *testing.T) { return &saobei.RefundRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, TerminalTrace: "LOCAL-REFUND-1", OutTradeNo: "SAOBEI-ORDER-1", OutRefundNo: "SAOBEI-REFUND-1", RefundFee: "50"}, nil }}) - result, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{ + result, err := adapter.Refund(context.Background(), &bizpayment.PaymentRefundRequest{ TradeNo: "LOCAL-1", QueryID: "SAOBEI-ORDER-1", ProviderTradeNo: "CHANNEL-1", RefundNo: "LOCAL-REFUND-1", Amount: 50, Currency: "cny", }, map[string]any{"refund_extra": map[string]any{"out_refund_no": "MUST-NOT-BE-SENT"}}) @@ -257,7 +257,7 @@ func TestSaobeiRefundFallsBackToMerchantTradeNo(t *testing.T) { return &saobei.RefundRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, TerminalTrace: "LOCAL-REFUND-2", OutRefundNo: "SAOBEI-REFUND-2", RefundFee: "25"}, nil }}) - _, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{ + _, err := adapter.Refund(context.Background(), &bizpayment.PaymentRefundRequest{ TradeNo: "LOCAL-2", RefundNo: "LOCAL-REFUND-2", Amount: 25, Currency: "CNY", }, nil) if err != nil { @@ -281,7 +281,7 @@ func TestSaobeiRefundTreatsPayingAsPending(t *testing.T) { }, nil }}) - result, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{ + result, err := adapter.Refund(context.Background(), &bizpayment.PaymentRefundRequest{ TradeNo: "LOCAL-3", RefundNo: "LOCAL-REFUND-3", Amount: 25, Currency: "CNY", }, nil) if err != nil { @@ -302,7 +302,7 @@ func TestSaobeiRefundRejectsMismatchedAmount(t *testing.T) { }, nil }}) - if _, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{ + if _, err := adapter.Refund(context.Background(), &bizpayment.PaymentRefundRequest{ TradeNo: "LOCAL-1", RefundNo: "LOCAL-REFUND-1", Amount: 50, Currency: "CNY", }, nil); err == nil { t.Fatal("Refund() accepted a mismatched refund amount") diff --git a/internal/integration/payment/vendor.go b/internal/integration/payment/vendor.go index 30b9425..1de36d8 100644 --- a/internal/integration/payment/vendor.go +++ b/internal/integration/payment/vendor.go @@ -10,7 +10,7 @@ import ( "errors" "fmt" "io" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/http" "strings" "time" @@ -37,7 +37,7 @@ func newVendorAdapter(provider string, profile vendorProfile) Adapter { return &vendorPaymentAdapter{provider: provider, profile: profile} } -func (a *vendorPaymentAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *vendorPaymentAdapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("配置驱动支付下单请求为空") } @@ -45,16 +45,16 @@ func (a *vendorPaymentAdapter) Create(ctx context.Context, req *system.PaymentRe mergeMap(payload, req.Extra, "merchant_id", "app_id", "trade_no", "subject", "amount", "currency", "notify_url", "client_ip", "timestamp", "nonce") return a.call(ctx, "create_url", payload, req.TradeNo, c) } -func (a *vendorPaymentAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { +func (a *vendorPaymentAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { return a.call(ctx, "query_url", map[string]any{"merchant_id": text(c, "merchant_id"), "app_id": text(c, "app_id"), "trade_no": tradeNo, "timestamp": time.Now().Unix(), "nonce": nonce()}, tradeNo, c) } -func (a *vendorPaymentAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *vendorPaymentAdapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("支付退款请求为空") } return a.call(ctx, "refund_url", map[string]any{"merchant_id": text(c, "merchant_id"), "app_id": text(c, "app_id"), "trade_no": req.TradeNo, "refund_no": req.RefundNo, "amount": req.Amount, "total_amount": req.TotalAmount, "currency": req.Currency, "timestamp": time.Now().Unix(), "nonce": nonce()}, req.TradeNo, c) } -func (a *vendorPaymentAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { +func (a *vendorPaymentAdapter) Callback(_ context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) { if callback == nil || len(callback.Body) == 0 { return nil, errors.New("配置驱动支付回调为空") } @@ -98,9 +98,9 @@ func (a *vendorPaymentAdapter) Callback(_ context.Context, callback *system.Paym status = "success" } payload, _ := json.Marshal(fields) - return &system.PaymentResult{Provider: a.provider, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Payload: payload}, nil + return &bizpayment.PaymentResult{Provider: a.provider, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Payload: payload}, nil } -func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, payload map[string]any, tradeNo string, c map[string]any) (*system.PaymentResult, error) { +func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, payload map[string]any, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { endpoint := text(c, endpointKey) if endpoint == "" { return nil, fmt.Errorf("%s 未配置 %s", a.provider, endpointKey) @@ -131,7 +131,7 @@ func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, pay if response.StatusCode >= 300 { return nil, fmt.Errorf("%s HTTP %d", a.provider, response.StatusCode) } - result := &system.PaymentResult{Provider: a.provider, Status: "created", TradeNo: tradeNo, Payload: ensureJSON(body)} + result := &bizpayment.PaymentResult{Provider: a.provider, Status: "created", TradeNo: tradeNo, Payload: ensureJSON(body)} if endpointKey != "query_url" { return result, nil } @@ -167,7 +167,7 @@ func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, pay return result, nil } -func populateVendorBreakdown(result *system.PaymentResult, object map[string]any, c map[string]any) error { +func populateVendorBreakdown(result *bizpayment.PaymentResult, object map[string]any, c map[string]any) error { if result == nil || result.Status != "success" { return nil } diff --git a/internal/integration/payment/wechat_v2.go b/internal/integration/payment/wechat_v2.go index 9543ec1..89a8ca0 100644 --- a/internal/integration/payment/wechat_v2.go +++ b/internal/integration/payment/wechat_v2.go @@ -8,7 +8,7 @@ import ( "encoding/json" "errors" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/http" "net/url" "strings" @@ -48,7 +48,7 @@ func wechatV2Sign(values map[string]string, key, signType string) string { return gopayWechat.GetReleaseSign(key, wechatV2SignType(signType), body) } -func (a *wechatV2Adapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *wechatV2Adapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("微信支付 v2 下单请求为空") } @@ -98,15 +98,15 @@ func (a *wechatV2Adapter) Create(ctx context.Context, req *system.PaymentRequest if err = verifyWechatV2Response(resultValues, client.ApiKey, values["sign_type"]); err != nil { return nil, err } - return &system.PaymentResult{ - Provider: system.PaymentWechatV2, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentWechatV2, Status: "created", TradeNo: req.TradeNo, Payload: wechatV2Payload(resultValues), }, nil } -func (a *wechatV2Adapter) createMicropay(ctx context.Context, client *gopayWechat.Client, req *system.PaymentRequest, c map[string]any, values map[string]string) (*system.PaymentResult, error) { +func (a *wechatV2Adapter) createMicropay(ctx context.Context, client *gopayWechat.Client, req *bizpayment.PaymentRequest, c map[string]any, values map[string]string) (*bizpayment.PaymentResult, error) { authCode := firstAny(req.Extra, "auth_code", "authcode", "barcode", "pay_code", "payment_code") if authCode == "" { authCode = firstAny(c, "auth_code", "authcode", "barcode", "pay_code", "payment_code") @@ -145,14 +145,14 @@ func (a *wechatV2Adapter) createMicropay(ctx context.Context, client *gopayWecha return nil, errors.New("微信支付 v2 付款码支付响应的 out_trade_no 不匹配") } if strings.EqualFold(resultValues["err_code"], "USERPAYING") { - return &system.PaymentResult{Provider: system.PaymentWechatV2, Status: "pending", TradeNo: req.TradeNo, Payload: wechatV2Payload(resultValues)}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentWechatV2, Status: "pending", TradeNo: req.TradeNo, Payload: wechatV2Payload(resultValues)}, nil } providerTradeNo := strings.TrimSpace(resultValues["transaction_id"]) if providerTradeNo == "" { return nil, errors.New("微信支付 v2 付款码支付响应缺少 transaction_id") } - result := &system.PaymentResult{ - Provider: system.PaymentWechatV2, Status: "success", TradeNo: req.TradeNo, + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentWechatV2, Status: "success", TradeNo: req.TradeNo, ProviderTradeNo: providerTradeNo, Payload: wechatV2Payload(resultValues), } amount := strings.TrimSpace(resultValues["total_fee"]) @@ -201,7 +201,7 @@ func wechatV2CreateMethod(extra, config map[string]any) (string, error) { } } -func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { +func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("微信支付 v2 查单缺少 out_trade_no") @@ -226,8 +226,8 @@ func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[strin if err = verifyWechatV2Response(values, client.ApiKey, body.GetString("sign_type")); err != nil { return nil, err } - result := &system.PaymentResult{ - Provider: system.PaymentWechatV2, + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentWechatV2, Status: normalizePaymentStatus(values["trade_state"], "pending"), TradeNo: first(values, "out_trade_no"), Payload: wechatV2Payload(values), @@ -258,7 +258,7 @@ func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[strin return result, nil } -func (a *wechatV2Adapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *wechatV2Adapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("微信支付 v2 退款请求为空") } @@ -305,8 +305,8 @@ func (a *wechatV2Adapter) Refund(ctx context.Context, req *system.PaymentRefundR if err = validateWechatV2RefundIdentity(tradeNo, refundNo, rsp); err != nil { return nil, err } - result := &system.PaymentResult{ - Provider: system.PaymentWechatV2, + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentWechatV2, Status: "created", TradeNo: tradeNo, ProviderTradeNo: strings.TrimSpace(rsp.RefundId), @@ -351,7 +351,7 @@ func validateWechatV2RefundIdentity(tradeNo, refundNo string, rsp *gopayWechat.R return nil } -func (a *wechatV2Adapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { +func (a *wechatV2Adapter) Callback(_ context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) { if callback == nil || len(callback.Body) == 0 { return nil, errors.New("微信支付 v2 回调为空") } @@ -375,7 +375,7 @@ func (a *wechatV2Adapter) Callback(_ context.Context, callback *system.PaymentCa status = "success" } payload, _ := json.Marshal(values) - return &system.PaymentResult{Provider: system.PaymentWechatV2, Status: status, TradeNo: first(values, "out_trade_no"), ProviderTradeNo: values["transaction_id"], Payload: payload}, nil + return &bizpayment.PaymentResult{Provider: bizpayment.PaymentWechatV2, Status: status, TradeNo: first(values, "out_trade_no"), ProviderTradeNo: values["transaction_id"], Payload: payload}, nil } func newWechatV2Client(c map[string]any, endpoint string) (*gopayWechat.Client, error) { @@ -466,7 +466,7 @@ func wechatV2Payload(values map[string]string) []byte { return []byte(gopayWechat.GenerateXml(toGoPayBodyMap(values))) } -func populateWechatV2Breakdown(result *system.PaymentResult, values map[string]string) error { +func populateWechatV2Breakdown(result *bizpayment.PaymentResult, values map[string]string) error { if result == nil || result.Status != "success" { return nil } diff --git a/internal/integration/payment/wechat_v3.go b/internal/integration/payment/wechat_v3.go index f34d7c4..9b25868 100644 --- a/internal/integration/payment/wechat_v3.go +++ b/internal/integration/payment/wechat_v3.go @@ -8,7 +8,7 @@ import ( "encoding/pem" "errors" "fmt" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/http" "net/url" "strings" @@ -20,7 +20,7 @@ import ( type wechatV3Adapter struct{} -func (a *wechatV3Adapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *wechatV3Adapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("微信支付 v3 下单请求为空") } @@ -123,7 +123,7 @@ func normalizeWechatV3TradeType(value string) string { return strings.NewReplacer(".", "", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value))) } -func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*system.PaymentResult, error) { +func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*bizpayment.PaymentResult, error) { if rsp == nil { return nil, errors.New("微信支付 v3 付款码响应为空") } @@ -139,8 +139,8 @@ func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*syst } else if returnedTradeNo != tradeNo { return nil, errors.New("微信支付 v3 付款码响应的商户订单号不匹配") } - result := &system.PaymentResult{ - Provider: system.PaymentWechatV3, + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentWechatV3, Status: normalizePaymentStatus(order.TradeState, "pending"), TradeNo: tradeNo, ProviderTradeNo: strings.TrimSpace(order.TransactionId), @@ -179,7 +179,7 @@ func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*syst return result, nil } -func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) { +func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) { tradeNo = strings.TrimSpace(tradeNo) if tradeNo == "" { return nil, errors.New("微信支付 v3 查单缺少 out_trade_no") @@ -204,8 +204,8 @@ func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin order := rsp.Response payload := wechatV3ResponsePayload(rsp.SignInfo, order) - result := &system.PaymentResult{ - Provider: system.PaymentWechatV3, + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentWechatV3, Status: normalizePaymentStatus(order.TradeState, "pending"), TradeNo: order.OutTradeNo, ProviderTradeNo: order.TransactionId, @@ -236,7 +236,7 @@ func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin return result, nil } -func (a *wechatV3Adapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) { +func (a *wechatV3Adapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) { if req == nil { return nil, errors.New("微信支付 v3 退款请求为空") } @@ -279,8 +279,8 @@ func (a *wechatV3Adapter) Refund(ctx context.Context, req *system.PaymentRefundR if err != nil { return nil, err } - result := &system.PaymentResult{ - Provider: system.PaymentWechatV3, + result := &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentWechatV3, Status: normalizeRefundState(refund.Status), TradeNo: tradeNo, ProviderTradeNo: refundID, @@ -336,7 +336,7 @@ func validateWechatV3RefundIdentity(refund *gopayWechatV3.RefundOrderResponse, t return refundID, nil } -func (a *wechatV3Adapter) Callback(ctx context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) { +func (a *wechatV3Adapter) Callback(ctx context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) { if callback == nil { return nil, errors.New("微信支付 v3 回调为空") } @@ -395,8 +395,8 @@ func (a *wechatV3Adapter) Callback(ctx context.Context, callback *system.Payment return nil, errors.New("微信支付 v3 回调 appid 不匹配") } plain, _ := json.Marshal(pay) - return &system.PaymentResult{ - Provider: system.PaymentWechatV3, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentWechatV3, Status: status, TradeNo: pay.OutTradeNo, ProviderTradeNo: pay.TransactionId, @@ -490,22 +490,22 @@ func wechatV3PlatformSerial(certificate string) (string, error) { return strings.ToUpper(cert.SerialNumber.Text(16)), nil } -func wechatV3CreateResult(tradeNo string, code int, signInfo *gopayWechatV3.SignInfo, response any, errResponse gopayWechatV3.ErrResponse, rawError string) (*system.PaymentResult, error) { +func wechatV3CreateResult(tradeNo string, code int, signInfo *gopayWechatV3.SignInfo, response any, errResponse gopayWechatV3.ErrResponse, rawError string) (*bizpayment.PaymentResult, error) { if code != gopayWechatV3.Success { return nil, wechatV3APIError("下单", code, errResponse, rawError) } if response == nil { return nil, errors.New("微信支付 v3 下单响应缺少预支付信息") } - return &system.PaymentResult{ - Provider: system.PaymentWechatV3, + return &bizpayment.PaymentResult{ + Provider: bizpayment.PaymentWechatV3, Status: "created", TradeNo: tradeNo, Payload: wechatV3ResponsePayload(signInfo, response), }, nil } -func wechatV3PrepayCreateResult(client *gopayWechatV3.ClientV3, appID, tradeType, tradeNo string, rsp *gopayWechatV3.PrepayRsp) (*system.PaymentResult, error) { +func wechatV3PrepayCreateResult(client *gopayWechatV3.ClientV3, appID, tradeType, tradeNo string, rsp *gopayWechatV3.PrepayRsp) (*bizpayment.PaymentResult, error) { if rsp == nil { return nil, errors.New("微信支付 v3 下单响应为空") } @@ -552,7 +552,7 @@ func wechatV3ResponsePayload(signInfo *gopayWechatV3.SignInfo, response any) []b return payload } -func populateWechatV3Breakdown(result *system.PaymentResult, object map[string]any) error { +func populateWechatV3Breakdown(result *bizpayment.PaymentResult, object map[string]any) error { if result == nil || result.Status != "success" { return nil } diff --git a/internal/integration/payment/wechat_v3_codepay_test.go b/internal/integration/payment/wechat_v3_codepay_test.go index 9d42d06..d17e29c 100644 --- a/internal/integration/payment/wechat_v3_codepay_test.go +++ b/internal/integration/payment/wechat_v3_codepay_test.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "io" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "net/http" "net/http/httptest" "strings" @@ -51,8 +51,8 @@ func TestWechatV3CodePayUsesGoPaySDK(t *testing.T) { })) defer server.Close() - result, err := (&wechatV3Adapter{}).Create(context.Background(), &system.PaymentRequest{ - Provider: system.PaymentWechatV3, + result, err := (&wechatV3Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + Provider: bizpayment.PaymentWechatV3, TradeNo: "T-WECHAT-CODEPAY", Subject: "subject", Amount: 123, @@ -85,7 +85,7 @@ func TestWechatV3CodePayUsesGoPaySDK(t *testing.T) { if amount["total"] != float64(123) || amount["currency"] != "CNY" { t.Fatalf("request amount = %#v", amount) } - if result.Provider != system.PaymentWechatV3 || result.Status != "success" || result.TradeNo != "T-WECHAT-CODEPAY" || result.ProviderTradeNo != "WX-CODEPAY-1" || result.QueryID != "" { + if result.Provider != bizpayment.PaymentWechatV3 || result.Status != "success" || result.TradeNo != "T-WECHAT-CODEPAY" || result.ProviderTradeNo != "WX-CODEPAY-1" || result.QueryID != "" { t.Fatalf("result identity = %+v", result) } if result.Amount != 123 || result.Currency != "CNY" || result.PayerCurrency != "CNY" || !result.AmountBreakdownKnown || result.PayerPaidAmount != 100 || result.CashPaidAmount != 100 || result.DiscountAmount != 23 { @@ -108,7 +108,7 @@ func TestWechatV3CodePayAliasesRequireAuthCode(t *testing.T) { for _, method := range []string{"micropay", "micro_pay", "micro-pay", "codepay", "code_pay", "code-pay", "barcode", "barcode_pay", "facepay", "face_pay"} { t.Run(method, func(t *testing.T) { - _, err := (&wechatV3Adapter{}).Create(context.Background(), &system.PaymentRequest{ + _, err := (&wechatV3Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ TradeNo: "T-WECHAT-CODEPAY", Subject: "subject", Amount: 1, Currency: "CNY", Extra: map[string]any{"method": method}, }, config) diff --git a/internal/integration/payment/wechat_v3_douyin_refund_test.go b/internal/integration/payment/wechat_v3_douyin_refund_test.go index 5558c25..ef64b52 100644 --- a/internal/integration/payment/wechat_v3_douyin_refund_test.go +++ b/internal/integration/payment/wechat_v3_douyin_refund_test.go @@ -2,7 +2,7 @@ package payment import ( "context" - "kra/internal/biz/system" + bizpayment "kra/internal/biz/payment" "strings" "testing" @@ -13,12 +13,12 @@ import ( func TestWechatV3RefundRejectsBlankIdentityBeforeClient(t *testing.T) { for _, tc := range []struct { name string - req *system.PaymentRefundRequest + req *bizpayment.PaymentRefundRequest want string }{ - {name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"}, - {name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"}, - {name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, + {name: "blank trade number", req: &bizpayment.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"}, + {name: "blank refund number", req: &bizpayment.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"}, + {name: "invalid amount", req: &bizpayment.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, } { t.Run(tc.name, func(t *testing.T) { _, err := (&wechatV3Adapter{}).Refund(context.Background(), tc.req, nil) @@ -67,12 +67,12 @@ func TestWechatV3RefundIdentityValidation(t *testing.T) { func TestDouyinRefundRejectsBlankIdentityBeforeClient(t *testing.T) { for _, tc := range []struct { name string - req *system.PaymentRefundRequest + req *bizpayment.PaymentRefundRequest want string }{ - {name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"}, - {name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"}, - {name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, + {name: "blank trade number", req: &bizpayment.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"}, + {name: "blank refund number", req: &bizpayment.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"}, + {name: "invalid amount", req: &bizpayment.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"}, } { t.Run(tc.name, func(t *testing.T) { _, err := (&douyinAdapter{}).Refund(context.Background(), tc.req, nil) diff --git a/internal/integration/provider.go b/internal/integration/provider.go index f261a89..4753570 100644 --- a/internal/integration/provider.go +++ b/internal/integration/provider.go @@ -4,6 +4,7 @@ package integration import ( + integrationbiz "kra/internal/biz/integration" "kra/internal/biz/system" "kra/internal/integration/cache" "kra/internal/integration/email" @@ -21,7 +22,7 @@ var ProviderSet = wire.NewSet( email.NewEmailRepo, storage.NewFileStorage, NewConnectivityTester, - wire.Bind(new(system.IntegrationConnectionTester), new(*ConnectivityTester)), + wire.Bind(new(integrationbiz.IntegrationConnectionTester), new(*ConnectivityTester)), mqintegration.New, wire.Bind(new(mq.Client), new(*mqintegration.Reloadable)), wire.Bind(new(mq.Registry), new(*mqintegration.Reloadable)), diff --git a/internal/service/dto/integration_config.go b/internal/service/dto/integration_config.go index 98e05b4..c3d74c4 100644 --- a/internal/service/dto/integration_config.go +++ b/internal/service/dto/integration_config.go @@ -2,7 +2,7 @@ package dto import ( "encoding/json" - "kra/internal/biz/system" + integrationbiz "kra/internal/biz/integration" ) type IntegrationConfigRequest struct { @@ -11,12 +11,12 @@ type IntegrationConfigRequest struct { } type IntegrationConfigResponse struct { - Kind string `json:"kind"` - Provider string `json:"provider"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Enabled bool `json:"enabled"` - Configured bool `json:"configured"` - Config json.RawMessage `json:"config"` - Fields []system.IntegrationConfigField `json:"fields"` + Kind string `json:"kind"` + Provider string `json:"provider"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Enabled bool `json:"enabled"` + Configured bool `json:"configured"` + Config json.RawMessage `json:"config"` + Fields []integrationbiz.IntegrationConfigField `json:"fields"` } diff --git a/internal/service/integration_config.go b/internal/service/integration_config.go index 92e2c5f..993b103 100644 --- a/internal/service/integration_config.go +++ b/internal/service/integration_config.go @@ -3,16 +3,16 @@ package service import ( "context" "encoding/json" - "kra/internal/biz/system" + integrationbiz "kra/internal/biz/integration" "kra/internal/service/dto" ) type IntegrationConfigService struct { - uc *system.IntegrationConfigUsecase + uc *integrationbiz.IntegrationConfigUsecase } -func NewIntegrationConfigService(uc *system.IntegrationConfigUsecase) *IntegrationConfigService { +func NewIntegrationConfigService(uc *integrationbiz.IntegrationConfigUsecase) *IntegrationConfigService { return &IntegrationConfigService{uc: uc} } @@ -21,13 +21,13 @@ func (s *IntegrationConfigService) List(ctx context.Context, kind string) ([]*dt if err != nil { return nil, err } - byProvider := make(map[string]*system.IntegrationConfig, len(configs)) + byProvider := make(map[string]*integrationbiz.IntegrationConfig, len(configs)) for _, config := range configs { if config != nil { byProvider[config.Provider] = config } } - definitions := system.IntegrationDefinitions(kind) + definitions := integrationbiz.IntegrationDefinitions(kind) result := make([]*dto.IntegrationConfigResponse, 0, len(definitions)+len(configs)) for _, definition := range definitions { config, configured := byProvider[definition.Provider] @@ -54,7 +54,7 @@ func (s *IntegrationConfigService) Find(ctx context.Context, kind, provider stri if err != nil { return nil, err } - definition, found := system.IntegrationDefinition(config.Kind, config.Provider) + definition, found := integrationbiz.IntegrationDefinition(config.Kind, config.Provider) response := &dto.IntegrationConfigResponse{Kind: config.Kind, Provider: config.Provider, Name: config.Provider, Enabled: config.Enabled, Configured: true, Config: config.Values} if found { response.Name = definition.Name @@ -69,14 +69,14 @@ func (s *IntegrationConfigService) Save(ctx context.Context, kind, provider stri if req == nil { return s.uc.Save(ctx, nil) } - return s.uc.Save(ctx, &system.IntegrationConfig{Kind: kind, Provider: provider, Enabled: req.Enabled, Values: req.Config}) + return s.uc.Save(ctx, &integrationbiz.IntegrationConfig{Kind: kind, Provider: provider, Enabled: req.Enabled, Values: req.Config}) } func (s *IntegrationConfigService) Test(ctx context.Context, kind, provider string, req *dto.IntegrationConfigRequest) error { if req == nil { return s.uc.Test(ctx, nil) } - return s.uc.Test(ctx, &system.IntegrationConfig{Kind: kind, Provider: provider, Enabled: true, Values: req.Config}) + return s.uc.Test(ctx, &integrationbiz.IntegrationConfig{Kind: kind, Provider: provider, Enabled: true, Values: req.Config}) } func (s *IntegrationConfigService) Delete(ctx context.Context, kind, provider string) error { diff --git a/internal/service/payment.go b/internal/service/payment.go index 7e268ba..99a5dfb 100644 --- a/internal/service/payment.go +++ b/internal/service/payment.go @@ -3,14 +3,14 @@ package service import ( "context" "errors" - "kra/internal/biz/system" + paymentbiz "kra/internal/biz/payment" "kra/internal/service/dto" ) -type PaymentService struct{ uc *system.PaymentUsecase } +type PaymentService struct{ uc *paymentbiz.PaymentUsecase } -func NewPaymentService(uc *system.PaymentUsecase) *PaymentService { return &PaymentService{uc: uc} } +func NewPaymentService(uc *paymentbiz.PaymentUsecase) *PaymentService { return &PaymentService{uc: uc} } func (s *PaymentService) Order(ctx context.Context, provider, tradeNo string) (*dto.PaymentOrderResponse, error) { order, err := s.uc.Order(ctx, provider, tradeNo) if err != nil { @@ -44,7 +44,7 @@ func (s *PaymentService) Orders(ctx context.Context, req *dto.PaymentOrderListRe if pageSize <= 0 { pageSize = 10 } - items, total, err := s.uc.Orders(ctx, page, pageSize, system.PaymentOrderFilter{ + items, total, err := s.uc.Orders(ctx, page, pageSize, paymentbiz.PaymentOrderFilter{ Provider: req.Provider, TradeNo: req.TradeNo, BusinessType: req.BusinessType, BusinessID: req.BusinessID, PaymentStatus: req.PaymentStatus, RefundStatus: req.RefundStatus, }) @@ -62,7 +62,7 @@ func (s *PaymentService) Orders(ctx context.Context, req *dto.PaymentOrderListRe return result, total, page, pageSize, nil } -func paymentOrderResponse(order *system.PaymentOrder) (*dto.PaymentOrderResponse, error) { +func paymentOrderResponse(order *paymentbiz.PaymentOrder) (*dto.PaymentOrderResponse, error) { if order == nil { return nil, errors.New("支付订单为空") } @@ -84,7 +84,7 @@ func (s *PaymentService) Create(ctx context.Context, req *dto.PaymentRequest) (* if req == nil { return nil, errors.New("支付请求为空") } - result, err := s.uc.Create(ctx, &system.PaymentRequest{ + result, err := s.uc.Create(ctx, &paymentbiz.PaymentRequest{ Provider: req.Provider, TradeNo: req.TradeNo, Subject: req.Subject, Amount: req.Amount, OriginalAmount: req.OriginalAmount, Currency: req.Currency, NotifyURL: req.NotifyURL, ReturnURL: req.ReturnURL, ClientIP: req.ClientIP, @@ -130,13 +130,13 @@ func (s *PaymentService) Fulfill(ctx context.Context, req *dto.PaymentFulfillReq } func (s *PaymentService) Callback(ctx context.Context, req *dto.PaymentCallbackRequest) (dto.PaymentCallbackAck, error) { if req == nil { - return paymentCallbackAckResponse(system.DefaultPaymentCallbackAck("", false)), errors.New("支付回调请求为空") + return paymentCallbackAckResponse(paymentbiz.DefaultPaymentCallbackAck("", false)), errors.New("支付回调请求为空") } - result, err := s.uc.Callback(ctx, &system.PaymentCallback{Provider: req.Provider, Headers: req.Headers, Body: req.Body, Query: req.Query}) + result, err := s.uc.Callback(ctx, &paymentbiz.PaymentCallback{Provider: req.Provider, Headers: req.Headers, Body: req.Body, Query: req.Query}) if err != nil { - return paymentCallbackAckResponse(system.CallbackFailure(err, system.DefaultPaymentCallbackAck(req.Provider, false))), err + return paymentCallbackAckResponse(paymentbiz.CallbackFailure(err, paymentbiz.DefaultPaymentCallbackAck(req.Provider, false))), err } - ack := system.DefaultPaymentCallbackAck(req.Provider, true) + ack := paymentbiz.DefaultPaymentCallbackAck(req.Provider, true) if result != nil && result.SuccessAck.StatusCode != 0 { ack = result.SuccessAck } @@ -144,10 +144,10 @@ func (s *PaymentService) Callback(ctx context.Context, req *dto.PaymentCallbackR } func DefaultPaymentCallbackAck(provider string, success bool) dto.PaymentCallbackAck { - return paymentCallbackAckResponse(system.DefaultPaymentCallbackAck(provider, success)) + return paymentCallbackAckResponse(paymentbiz.DefaultPaymentCallbackAck(provider, success)) } -func paymentResultResponse(result *system.PaymentResult) *dto.PaymentResultResponse { +func paymentResultResponse(result *paymentbiz.PaymentResult) *dto.PaymentResultResponse { if result == nil { return nil } @@ -165,6 +165,6 @@ func paymentResultResponse(result *system.PaymentResult) *dto.PaymentResultRespo } } -func paymentCallbackAckResponse(ack system.PaymentCallbackAck) dto.PaymentCallbackAck { +func paymentCallbackAckResponse(ack paymentbiz.PaymentCallbackAck) dto.PaymentCallbackAck { return dto.PaymentCallbackAck{StatusCode: ack.StatusCode, ContentType: ack.ContentType, Body: ack.Body} } diff --git a/internal/service/task.go b/internal/service/task.go index 37959cf..508f2fd 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -3,21 +3,21 @@ package service import ( "context" "encoding/json" - "kra/internal/biz/system" "time" + taskbiz "kra/internal/biz/task" "kra/internal/service/dto" ) type TaskService struct { - uc *system.TaskApplicationUsecase + uc *taskbiz.TaskApplicationUsecase } -func NewTaskService(uc *system.TaskApplicationUsecase) *TaskService { +func NewTaskService(uc *taskbiz.TaskApplicationUsecase) *TaskService { return &TaskService{uc: uc} } -func taskDomain(v *dto.TaskRequest) *system.TimedTask { - return &system.TimedTask{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: v.Params, HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: v.HTTPHeader, HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} +func taskDomain(v *dto.TaskRequest) *taskbiz.TimedTask { + return &taskbiz.TimedTask{ID: v.ID, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: v.Params, HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: v.HTTPHeader, HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled} } func (s *TaskService) CreateRequest(ctx context.Context, req *dto.TaskRequest) (uint, error) { value := taskDomain(req) @@ -30,12 +30,12 @@ func (s *TaskService) UpdateRequest(ctx context.Context, req *dto.TaskRequest) e return s.Update(ctx, taskDomain(req)) } func (s *TaskService) ListRequest(ctx context.Context, page, size int, name, executorType string, enabled *bool) ([]*dto.TaskResponse, int64, error) { - return s.Tasks(ctx, page, size, &system.TimedTask{Name: name, ExecutorType: executorType, EnabledFilter: enabled}) + return s.Tasks(ctx, page, size, &taskbiz.TimedTask{Name: name, ExecutorType: executorType, EnabledFilter: enabled}) } -func (s *TaskService) Create(ctx context.Context, v *system.TimedTask) error { +func (s *TaskService) Create(ctx context.Context, v *taskbiz.TimedTask) error { return s.uc.Create(ctx, v) } -func (s *TaskService) Update(ctx context.Context, v *system.TimedTask) error { +func (s *TaskService) Update(ctx context.Context, v *taskbiz.TimedTask) error { return s.uc.Update(ctx, v) } func (s *TaskService) Delete(ctx context.Context, id uint) error { @@ -44,10 +44,10 @@ func (s *TaskService) Delete(ctx context.Context, id uint) error { func (s *TaskService) Toggle(ctx context.Context, id uint, enabled bool) error { return s.uc.Toggle(ctx, id, enabled) } -func taskDTO(v *system.TimedTask, next *time.Time) *dto.TaskResponse { +func taskDTO(v *taskbiz.TimedTask, next *time.Time) *dto.TaskResponse { return &dto.TaskResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, Name: v.Name, Description: v.Description, Spec: v.Spec, WithSeconds: v.WithSeconds, ExecutorType: v.ExecutorType, MethodName: v.MethodName, Params: json.RawMessage(v.Params), HTTPURL: v.HTTPURL, HTTPMethod: v.HTTPMethod, HTTPHeader: json.RawMessage(v.HTTPHeader), HTTPBody: v.HTTPBody, HTTPAllowPrivate: v.HTTPAllowPrivate, Enabled: v.Enabled, NextRunAt: next} } -func (s *TaskService) Tasks(ctx context.Context, page, size int, q *system.TimedTask) ([]*dto.TaskResponse, int64, error) { +func (s *TaskService) Tasks(ctx context.Context, page, size int, q *taskbiz.TimedTask) ([]*dto.TaskResponse, int64, error) { items, total, next, err := s.uc.List(ctx, page, size, q) if err != nil { return nil, 0, err diff --git a/internal/worker/task_executor.go b/internal/worker/task_executor.go index 77ea072..e8e5d64 100644 --- a/internal/worker/task_executor.go +++ b/internal/worker/task_executor.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "kra/internal/biz/system" "net" "net/http" "net/url" @@ -15,23 +14,25 @@ import ( "syscall" "time" + "kra/internal/biz/system" + taskbiz "kra/internal/biz/task" "kra/internal/conf" ) type TaskExecutor struct { - tasks *system.TaskUsecase + tasks *taskbiz.TaskUsecase media *system.MediaUsecase runtime *conf.Runtime - methods system.TaskMethodRegistry + methods taskbiz.TaskMethodRegistry } -func NewTaskExecutor(tasks *system.TaskUsecase, media *system.MediaUsecase, runtime *conf.Runtime) *TaskExecutor { - return NewTaskExecutorWithRegistry(tasks, media, runtime, system.DefaultTaskMethodRegistry()) +func NewTaskExecutor(tasks *taskbiz.TaskUsecase, media *system.MediaUsecase, runtime *conf.Runtime) *TaskExecutor { + return NewTaskExecutorWithRegistry(tasks, media, runtime, taskbiz.DefaultTaskMethodRegistry()) } -func NewTaskExecutorWithRegistry(tasks *system.TaskUsecase, media *system.MediaUsecase, runtime *conf.Runtime, methods system.TaskMethodRegistry) *TaskExecutor { +func NewTaskExecutorWithRegistry(tasks *taskbiz.TaskUsecase, media *system.MediaUsecase, runtime *conf.Runtime, methods taskbiz.TaskMethodRegistry) *TaskExecutor { if methods == nil { - methods = system.DefaultTaskMethodRegistry() + methods = taskbiz.DefaultTaskMethodRegistry() } return &TaskExecutor{tasks: tasks, media: media, runtime: runtime, methods: methods} } @@ -61,7 +62,7 @@ func taskHTTPClient(allowPrivate bool) *http.Client { return &http.Client{Timeout: 30 * time.Second, Transport: &http.Transport{Proxy: nil, DialContext: dialer.DialContext}} } -func (e *TaskExecutor) runHTTP(ctx context.Context, task *system.TimedTask) (string, error) { +func (e *TaskExecutor) runHTTP(ctx context.Context, task *taskbiz.TimedTask) (string, error) { parsed, err := url.Parse(task.HTTPURL) if err != nil { return "", fmt.Errorf("URL 非法: %w", err) @@ -109,13 +110,13 @@ func truncateTaskText(value string) string { return value[:limit] + "...(截断)" } -func (e *TaskExecutor) runMethod(ctx context.Context, task *system.TimedTask) error { - var method system.TaskMethodFunc +func (e *TaskExecutor) runMethod(ctx context.Context, task *taskbiz.TimedTask) error { + var method taskbiz.TaskMethodFunc var ok bool if e.methods != nil { method, ok = e.methods.Lookup(task.MethodName) } else { - method, ok = system.TaskMethodByName(task.MethodName) + method, ok = taskbiz.TaskMethodByName(task.MethodName) } if !ok { return fmt.Errorf("方法 %s 未注册(需通过 platform/task.Registry 注册)", task.MethodName) @@ -145,12 +146,12 @@ func (e *TaskExecutor) runMethod(ctx context.Context, task *system.TimedTask) er } } -func (e *TaskExecutor) Run(ctx context.Context, task *system.TimedTask, trigger string) (log *system.TimedTaskLog) { +func (e *TaskExecutor) Run(ctx context.Context, task *taskbiz.TimedTask, trigger string) (log *taskbiz.TimedTaskLog) { if ctx == nil { ctx = context.Background() } started := time.Now() - log = &system.TimedTaskLog{TaskID: task.ID, TaskName: task.Name, TriggerType: trigger, StartedAt: started, Status: "success"} + log = &taskbiz.TimedTaskLog{TaskID: task.ID, TaskName: task.Name, TriggerType: trigger, StartedAt: started, Status: "success"} defer func() { if recovered := recover(); recovered != nil { log.Status = "fail" @@ -165,9 +166,9 @@ func (e *TaskExecutor) Run(ctx context.Context, task *system.TimedTask, trigger }() var err error switch task.ExecutorType { - case system.TaskExecutorMethod: + case taskbiz.TaskExecutorMethod: err = e.runMethod(ctx, task) - case system.TaskExecutorHTTP: + case taskbiz.TaskExecutorHTTP: runCtx, cancel := context.WithTimeout(ctx, 30*time.Second) log.Output, err = e.runHTTP(runCtx, task) cancel() diff --git a/internal/worker/task_executor_test.go b/internal/worker/task_executor_test.go index d00ddde..657e535 100644 --- a/internal/worker/task_executor_test.go +++ b/internal/worker/task_executor_test.go @@ -4,11 +4,12 @@ import ( "context" "encoding/json" "errors" - "kra/internal/biz/system" "net" "strings" "testing" "time" + + taskbiz "kra/internal/biz/task" ) func TestPrivateIP(t *testing.T) { @@ -24,25 +25,25 @@ func TestPrivateIP(t *testing.T) { func TestRunMethodReportsParentDeadlineAsTimeout(t *testing.T) { const methodName = "worker-test-parent-deadline" - system.RegisterTaskMethod(methodName, "test", func(ctx context.Context, _ json.RawMessage) error { + taskbiz.RegisterTaskMethod(methodName, "test", func(ctx context.Context, _ json.RawMessage) error { <-ctx.Done() return ctx.Err() }) executor := &TaskExecutor{} ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) defer cancel() - if err := executor.runMethod(ctx, &system.TimedTask{MethodName: methodName}); !errors.Is(err, errTaskTimeout) { + if err := executor.runMethod(ctx, &taskbiz.TimedTask{MethodName: methodName}); !errors.Is(err, errTaskTimeout) { t.Fatalf("runMethod() error = %v, want errTaskTimeout", err) } } func TestRunMethodConvertsPanicToError(t *testing.T) { const methodName = "worker-test-panic" - system.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { + taskbiz.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { panic("boom") }) executor := &TaskExecutor{} - err := executor.runMethod(context.Background(), &system.TimedTask{MethodName: methodName}) + err := executor.runMethod(context.Background(), &taskbiz.TimedTask{MethodName: methodName}) if err == nil || !strings.Contains(err.Error(), "panic: boom") { t.Fatalf("runMethod() error = %v, want recovered panic", err) } @@ -51,7 +52,7 @@ func TestRunMethodConvertsPanicToError(t *testing.T) { func TestRunMethodUsesParentContext(t *testing.T) { const methodName = "worker-test-parent-context" started := make(chan struct{}) - system.RegisterTaskMethod(methodName, "test", func(ctx context.Context, _ json.RawMessage) error { + taskbiz.RegisterTaskMethod(methodName, "test", func(ctx context.Context, _ json.RawMessage) error { close(started) <-ctx.Done() return ctx.Err() @@ -60,7 +61,7 @@ func TestRunMethodUsesParentContext(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) done := make(chan error, 1) go func() { - done <- executor.runMethod(ctx, &system.TimedTask{MethodName: methodName}) + done <- executor.runMethod(ctx, &taskbiz.TimedTask{MethodName: methodName}) }() select { case <-started: @@ -81,14 +82,14 @@ func TestRunMethodUsesParentContext(t *testing.T) { func TestRunMethodHonorsDeadlineWhenMethodIgnoresContext(t *testing.T) { const methodName = "worker-test-ignores-context" release := make(chan struct{}) - system.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { + taskbiz.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { <-release return nil }) executor := &TaskExecutor{} ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) defer cancel() - if err := executor.runMethod(ctx, &system.TimedTask{MethodName: methodName}); !errors.Is(err, errTaskTimeout) { + if err := executor.runMethod(ctx, &taskbiz.TimedTask{MethodName: methodName}); !errors.Is(err, errTaskTimeout) { close(release) t.Fatalf("runMethod() error = %v, want errTaskTimeout", err) } diff --git a/internal/worker/task_registry.go b/internal/worker/task_registry.go index 9bb5afd..b1bea1c 100644 --- a/internal/worker/task_registry.go +++ b/internal/worker/task_registry.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "kra/internal/biz/system" + taskbiz "kra/internal/biz/task" "kra/internal/conf" platformtask "kra/pkg/task" @@ -12,12 +13,12 @@ import ( // TaskMethods is the system module's dependency-bearing task contribution. // Other modules provide their own contributor instead of editing this file. type TaskMethods struct { - tasks *system.TaskUsecase + tasks *taskbiz.TaskUsecase media *system.MediaUsecase runtime *conf.Runtime } -func NewTaskMethods(tasks *system.TaskUsecase, media *system.MediaUsecase, runtime *conf.Runtime) *TaskMethods { +func NewTaskMethods(tasks *taskbiz.TaskUsecase, media *system.MediaUsecase, runtime *conf.Runtime) *TaskMethods { return &TaskMethods{tasks: tasks, media: media, runtime: runtime} } @@ -28,12 +29,12 @@ func (methods *TaskMethods) RegisterTasks(registry *platformtask.Registry) { return } registry.Register(platformtask.Method{ - Name: system.TaskMethodClearDB, Description: "清理数据库过期日志(操作记录/JWT黑名单/定时任务执行日志)", Run: func(ctx context.Context, _ json.RawMessage) error { + Name: taskbiz.TaskMethodClearDB, Description: "清理数据库过期日志(操作记录/JWT黑名单/定时任务执行日志)", Run: func(ctx context.Context, _ json.RawMessage) error { return methods.tasks.CleanupLogs(ctx) }, }) registry.Register(platformtask.Method{ - Name: system.TaskMethodUploads, Description: "清理过期大文件上传会话", Run: func(ctx context.Context, _ json.RawMessage) error { + Name: taskbiz.TaskMethodUploads, Description: "清理过期大文件上传会话", Run: func(ctx context.Context, _ json.RawMessage) error { ttl := 24 config := methods.runtime.Admin() if config != nil && config.Media != nil && config.Media.SessionTtl > 0 { diff --git a/internal/worker/task_runtime_test.go b/internal/worker/task_runtime_test.go index 70697a5..7ee15c9 100644 --- a/internal/worker/task_runtime_test.go +++ b/internal/worker/task_runtime_test.go @@ -5,30 +5,31 @@ import ( "encoding/json" "errors" "io" - "kra/internal/biz/system" "log/slog" "sync" "sync/atomic" "testing" "time" + + taskbiz "kra/internal/biz/task" ) type workerTaskRepo struct { mu sync.Mutex - items []*system.TimedTask + items []*taskbiz.TimedTask listErr error - logs chan *system.TimedTaskLog + logs chan *taskbiz.TimedTaskLog } -func (r *workerTaskRepo) CreateTask(context.Context, *system.TimedTask) error { return nil } -func (r *workerTaskRepo) UpdateTask(context.Context, *system.TimedTask) error { return nil } -func (r *workerTaskRepo) DeleteTask(context.Context, uint) error { return nil } -func (r *workerTaskRepo) ToggleTask(context.Context, uint, bool) error { return nil } -func (r *workerTaskRepo) CleanupLogs(context.Context) error { return nil } +func (r *workerTaskRepo) CreateTask(context.Context, *taskbiz.TimedTask) error { return nil } +func (r *workerTaskRepo) UpdateTask(context.Context, *taskbiz.TimedTask) error { return nil } +func (r *workerTaskRepo) DeleteTask(context.Context, uint) error { return nil } +func (r *workerTaskRepo) ToggleTask(context.Context, uint, bool) error { return nil } +func (r *workerTaskRepo) CleanupLogs(context.Context) error { return nil } func (r *workerTaskRepo) TaskNameExists(context.Context, string, uint) (bool, error) { return false, nil } -func (r *workerTaskRepo) FindTask(_ context.Context, id uint) (*system.TimedTask, error) { +func (r *workerTaskRepo) FindTask(_ context.Context, id uint) (*taskbiz.TimedTask, error) { r.mu.Lock() defer r.mu.Unlock() for _, item := range r.items { @@ -38,31 +39,31 @@ func (r *workerTaskRepo) FindTask(_ context.Context, id uint) (*system.TimedTask } return nil, errors.New("task not found") } -func (r *workerTaskRepo) ListTasks(context.Context, int, int, *system.TimedTask) ([]*system.TimedTask, int64, error) { +func (r *workerTaskRepo) ListTasks(context.Context, int, int, *taskbiz.TimedTask) ([]*taskbiz.TimedTask, int64, error) { r.mu.Lock() defer r.mu.Unlock() if r.listErr != nil { return nil, 0, r.listErr } - items := make([]*system.TimedTask, 0, len(r.items)) + items := make([]*taskbiz.TimedTask, 0, len(r.items)) for _, item := range r.items { items = append(items, cloneTimedTask(item)) } return items, int64(len(items)), nil } -func (r *workerTaskRepo) RecordTaskLog(_ context.Context, value *system.TimedTaskLog) error { +func (r *workerTaskRepo) RecordTaskLog(_ context.Context, value *taskbiz.TimedTaskLog) error { if r.logs != nil { copy := *value r.logs <- © } return nil } -func (r *workerTaskRepo) ListTaskLogs(context.Context, int, int, uint, string) ([]*system.TimedTaskLog, int64, error) { +func (r *workerTaskRepo) ListTaskLogs(context.Context, int, int, uint, string) ([]*taskbiz.TimedTaskLog, int64, error) { return nil, 0, nil } func newTestTaskScheduler(repo *workerTaskRepo) *TaskScheduler { - tasks := system.NewTaskUsecase(repo) + tasks := taskbiz.NewTaskUsecase(repo) executor := &TaskExecutor{tasks: tasks} logger := slog.New(slog.NewTextHandler(io.Discard, nil)) return NewTaskScheduler(tasks, nil, executor, logger) @@ -71,7 +72,7 @@ func newTestTaskScheduler(repo *workerTaskRepo) *TaskScheduler { func TestReloadPreservesExistingScheduleOnPreparationFailure(t *testing.T) { repo := &workerTaskRepo{} scheduler := newTestTaskScheduler(repo) - old := &system.TimedTask{ID: 1, Name: "old", Spec: "0 0 * * *", Enabled: true} + old := &taskbiz.TimedTask{ID: 1, Name: "old", Spec: "0 0 * * *", Enabled: true} if err := scheduler.Schedule(old); err != nil { t.Fatalf("schedule old task: %v", err) } @@ -89,7 +90,7 @@ func TestReloadPreservesExistingScheduleOnPreparationFailure(t *testing.T) { t.Run("invalid cron", func(t *testing.T) { repo.mu.Lock() repo.listErr = nil - repo.items = []*system.TimedTask{{ID: 2, Name: "invalid", Spec: "bad cron", Enabled: true}} + repo.items = []*taskbiz.TimedTask{{ID: 2, Name: "invalid", Spec: "bad cron", Enabled: true}} repo.mu.Unlock() if err := scheduler.Reload(context.Background()); err == nil { t.Fatal("Reload() error = nil, want cron error") @@ -121,7 +122,7 @@ func TestConcurrentScheduleKeepsSingleEntry(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - if err := scheduler.Schedule(&system.TimedTask{ID: 7, Name: "same", Spec: "0 0 * * *", Enabled: true}); err != nil { + if err := scheduler.Schedule(&taskbiz.TimedTask{ID: 7, Name: "same", Spec: "0 0 * * *", Enabled: true}); err != nil { t.Errorf("Schedule() error = %v", err) } }() @@ -135,15 +136,15 @@ func TestManualTriggerSkipsOverlappingExecution(t *testing.T) { started := make(chan struct{}, 2) release := make(chan struct{}) var calls atomic.Int32 - system.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { + taskbiz.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { calls.Add(1) started <- struct{}{} <-release return nil }) - task := &system.TimedTask{ID: 9, Name: "single", ExecutorType: system.TaskExecutorMethod, MethodName: methodName} - repo := &workerTaskRepo{items: []*system.TimedTask{task}, logs: make(chan *system.TimedTaskLog, 2)} + task := &taskbiz.TimedTask{ID: 9, Name: "single", ExecutorType: taskbiz.TaskExecutorMethod, MethodName: methodName} + repo := &workerTaskRepo{items: []*taskbiz.TimedTask{task}, logs: make(chan *taskbiz.TimedTaskLog, 2)} scheduler := newTestTaskScheduler(repo) if err := scheduler.TriggerID(context.Background(), task.ID); err != nil { t.Fatalf("first manual trigger failed: %v", err) @@ -192,15 +193,15 @@ func TestAutomaticAndManualTriggersShareOverlapGate(t *testing.T) { started := make(chan struct{}, 1) release := make(chan struct{}) var calls atomic.Int32 - system.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { + taskbiz.RegisterTaskMethod(methodName, "test", func(context.Context, json.RawMessage) error { calls.Add(1) started <- struct{}{} <-release return nil }) - task := &system.TimedTask{ID: 10, Name: "shared-gate", ExecutorType: system.TaskExecutorMethod, MethodName: methodName} - repo := &workerTaskRepo{logs: make(chan *system.TimedTaskLog, 1)} + task := &taskbiz.TimedTask{ID: 10, Name: "shared-gate", ExecutorType: taskbiz.TaskExecutorMethod, MethodName: methodName} + repo := &workerTaskRepo{logs: make(chan *taskbiz.TimedTaskLog, 1)} scheduler := newTestTaskScheduler(repo) if !scheduler.dispatch(task, "auto", true) { t.Fatal("automatic dispatch was rejected while idle") diff --git a/internal/worker/task_scheduler.go b/internal/worker/task_scheduler.go index f894dfc..73e5064 100644 --- a/internal/worker/task_scheduler.go +++ b/internal/worker/task_scheduler.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "kra/internal/biz/system" + taskbiz "kra/internal/biz/task" "log/slog" "sync" "time" @@ -14,7 +15,7 @@ import ( ) type TaskScheduler struct { - tasks *system.TaskUsecase + tasks *taskbiz.TaskUsecase authorities *system.AuthorityUsecase executor *TaskExecutor logger *slog.Logger @@ -38,13 +39,13 @@ type scheduledEntry struct { entry cron.EntryID } -func NewTaskScheduler(tasks *system.TaskUsecase, authorities *system.AuthorityUsecase, executor *TaskExecutor, logger *slog.Logger) *TaskScheduler { +func NewTaskScheduler(tasks *taskbiz.TaskUsecase, authorities *system.AuthorityUsecase, executor *TaskExecutor, logger *slog.Logger) *TaskScheduler { idle := make(chan struct{}) close(idle) return &TaskScheduler{tasks: tasks, authorities: authorities, executor: executor, logger: logger.With("mod", "timedTask"), standard: cron.New(), seconds: cron.New(cron.WithSeconds()), entries: map[uint]scheduledEntry{}, running: map[uint]struct{}{}, idle: idle, subscribers: map[uint]map[chan []byte]struct{}{}} } -func NewTaskRuntime(scheduler *TaskScheduler) system.TaskRuntime { return scheduler } +func NewTaskRuntime(scheduler *TaskScheduler) taskbiz.TaskRuntime { return scheduler } func NewTaskReloader(scheduler *TaskScheduler) system.TaskReloader { return scheduler } @@ -118,7 +119,7 @@ func (s *TaskScheduler) Reload(ctx context.Context) error { return err } type preparedTask struct { - task *system.TimedTask + task *taskbiz.TimedTask schedule cron.Schedule } prepared := make([]preparedTask, 0, len(items)) @@ -183,7 +184,7 @@ func (s *TaskScheduler) finishRun(id uint) { } } -func (s *TaskScheduler) dispatch(task *system.TimedTask, trigger string, async bool) bool { +func (s *TaskScheduler) dispatch(task *taskbiz.TimedTask, trigger string, async bool) bool { ctx, ok := s.beginRun(task.ID) if !ok { s.logger.Warn("timed task skipped because it is already running or the scheduler is stopping", "task_id", task.ID, "task_name", task.Name, "trigger_type", trigger) @@ -201,7 +202,7 @@ func (s *TaskScheduler) dispatch(task *system.TimedTask, trigger string, async b return true } -func (s *TaskScheduler) run(ctx context.Context, task *system.TimedTask, trigger string) { +func (s *TaskScheduler) run(ctx context.Context, task *taskbiz.TimedTask, trigger string) { log := s.executor.Run(ctx, task, trigger) attributes := []any{"task_id", log.TaskID, "task_name", log.TaskName, "trigger_type", log.TriggerType, "status", log.Status, "duration_ms", log.DurationMS, "started_at", log.StartedAt, "finished_at", log.FinishedAt} if log.ErrorMsg != "" { @@ -224,7 +225,7 @@ func (s *TaskScheduler) run(ctx context.Context, task *system.TimedTask, trigger } } -func (s *TaskScheduler) Schedule(task *system.TimedTask) error { +func (s *TaskScheduler) Schedule(task *taskbiz.TimedTask) error { if task == nil { return errors.New("定时任务不能为空") } @@ -246,21 +247,21 @@ func (s *TaskScheduler) Schedule(task *system.TimedTask) error { return nil } -func parseTaskSchedule(task *system.TimedTask) (cron.Schedule, error) { +func parseTaskSchedule(task *taskbiz.TimedTask) (cron.Schedule, error) { if task.WithSeconds { return cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor).Parse(task.Spec) } return cron.ParseStandard(task.Spec) } -func cloneTimedTask(task *system.TimedTask) *system.TimedTask { +func cloneTimedTask(task *taskbiz.TimedTask) *taskbiz.TimedTask { copy := *task copy.Params = append([]byte(nil), task.Params...) copy.HTTPHeader = append([]byte(nil), task.HTTPHeader...) return © } -func (s *TaskScheduler) scheduleLocked(task *system.TimedTask, schedule cron.Schedule) { +func (s *TaskScheduler) scheduleLocked(task *taskbiz.TimedTask, schedule cron.Schedule) { run := func() { s.dispatch(task, "auto", false) } @@ -319,7 +320,7 @@ func (s *TaskScheduler) NextRuns() map[uint]time.Time { return out } -func (s *TaskScheduler) Trigger(task *system.TimedTask) bool { +func (s *TaskScheduler) Trigger(task *taskbiz.TimedTask) bool { if task == nil { return false }