From dc4f3cb7baa05f4a5f0f6616a5b464a7fa758d1a Mon Sep 17 00:00:00 2001 From: huang Date: Tue, 31 Mar 2026 12:43:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20C=E7=AB=AF=E8=AE=A2=E5=8D=95=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E6=8B=86=E5=88=86=20=E2=80=94=20=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E4=B8=8E=E5=8F=91=E8=B5=B7=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E5=88=86=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CreateOrder 改造:普通下单只建单(无支付参数),强充场景保持原有微信支付一步完成 - 新增 POST /orders/:id/pay 统一支付入口,支持 wallet(钱包)和 wechat(微信 JSAPI) - 移除 POST /orders/:id/wallet-pay,合并至统一支付接口 - app_type 改为可选字段(强充场景必传,普通下单无需传) - CreateOrderResponse.pay_config 改为 omitempty(普通下单不返回支付参数) --- docs/admin-openapi.yaml | 94 +++++++++++- internal/bootstrap/handlers.go | 1 + internal/handler/app/client_order.go | 28 +++- internal/model/dto/client_order_dto.go | 32 ++++- internal/routes/personal.go | 8 ++ internal/service/client_order/service.go | 175 ++++++++++++++++------- 6 files changed, 276 insertions(+), 62 deletions(-) diff --git a/docs/admin-openapi.yaml b/docs/admin-openapi.yaml index 0306a6d..c37ded7 100644 --- a/docs/admin-openapi.yaml +++ b/docs/admin-openapi.yaml @@ -1597,7 +1597,7 @@ components: DtoClientCreateOrderRequest: properties: app_type: - description: 应用类型 (official_account:公众号, miniapp:小程序) + description: 应用类型(强充必传)(official_account:公众号, miniapp:小程序) type: string identifier: description: 资产标识符(SN/IMEI/虚拟号/ICCID/MSISDN) @@ -1614,7 +1614,6 @@ components: required: - identifier - package_ids - - app_type type: object DtoClientCreateOrderResponse: properties: @@ -1849,6 +1848,25 @@ components: description: 时间戳 type: string type: object + DtoClientPayOrderParams: + properties: + app_type: + description: 应用类型(微信支付必传)(official_account:公众号, miniapp:小程序) + type: string + payment_method: + description: 支付方式 (wallet:钱包, wechat:微信) + type: string + required: + - payment_method + type: object + DtoClientPayOrderResponse: + properties: + pay_config: + $ref: '#/components/schemas/DtoClientPayConfig' + payment_method: + description: 支付方式 + type: string + type: object DtoClientRechargeCheckResponse: properties: force_recharge_amount: @@ -22879,6 +22897,78 @@ paths: summary: 订单详情 tags: - 个人客户 - 订单 + /api/c/v1/orders/{id}/pay: + post: + parameters: + - description: ID + in: path + name: id + required: true + schema: + description: ID + minimum: 0 + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DtoClientPayOrderParams' + responses: + "200": + content: + application/json: + schema: + properties: + code: + description: 响应码 + example: 0 + type: integer + data: + $ref: '#/components/schemas/DtoClientPayOrderResponse' + msg: + description: 响应消息 + example: success + type: string + timestamp: + description: 时间戳 + format: date-time + type: string + required: + - code + - msg + - data + - timestamp + type: object + description: 成功 + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 请求参数错误 + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 未认证或认证已过期 + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 无权访问 + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + description: 服务器内部错误 + security: + - BearerAuth: [] + summary: 订单支付 + tags: + - 个人客户 - 订单 /api/c/v1/orders/create: post: requestBody: diff --git a/internal/bootstrap/handlers.go b/internal/bootstrap/handlers.go index cb88817..d6e1bd6 100644 --- a/internal/bootstrap/handlers.go +++ b/internal/bootstrap/handlers.go @@ -35,6 +35,7 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers { personalCustomerDeviceStore, personalCustomerOpenIDStore, svc.WechatConfig, + svc.Order, packageSeriesStore, shopSeriesAllocationStore, iotCardStore, diff --git a/internal/handler/app/client_order.go b/internal/handler/app/client_order.go index 7c4a21e..7a2a310 100644 --- a/internal/handler/app/client_order.go +++ b/internal/handler/app/client_order.go @@ -14,7 +14,7 @@ import ( ) // ClientOrderHandler C 端订单处理器 -// 提供 D1~D3 下单、列表、详情接口。 +// 提供 D1~D4 下单、列表、详情、支付接口。 type ClientOrderHandler struct { clientOrderService *clientorder.Service logger *zap.Logger @@ -83,6 +83,32 @@ func (h *ClientOrderHandler) ListOrders(c *fiber.Ctx) error { return response.SuccessWithPagination(c, list, total, req.Page, req.PageSize) } +// PayOrder D4 订单支付。 +// POST /api/c/v1/orders/:id/pay +func (h *ClientOrderHandler) PayOrder(c *fiber.Ctx) error { + customerID, ok := middleware.GetCustomerID(c) + if !ok || customerID == 0 { + return errors.New(errors.CodeUnauthorized) + } + + orderID, err := strconv.ParseUint(c.Params("id"), 10, 64) + if err != nil || orderID == 0 { + return errors.New(errors.CodeInvalidParam) + } + + var req dto.ClientPayOrderRequest + if err := c.BodyParser(&req); err != nil { + return errors.New(errors.CodeInvalidParam) + } + + resp, err := h.clientOrderService.PayOrder(c.UserContext(), customerID, uint(orderID), &req) + if err != nil { + return err + } + + return response.Success(c, resp) +} + // GetOrderDetail D3 订单详情。 // GET /api/c/v1/orders/:id func (h *ClientOrderHandler) GetOrderDetail(c *fiber.Ctx) error { diff --git a/internal/model/dto/client_order_dto.go b/internal/model/dto/client_order_dto.go index 9c8a8ce..f0c4585 100644 --- a/internal/model/dto/client_order_dto.go +++ b/internal/model/dto/client_order_dto.go @@ -8,15 +8,16 @@ package dto type ClientCreateOrderRequest struct { Identifier string `json:"identifier" validate:"required,min=1,max=50" required:"true" minLength:"1" maxLength:"50" description:"资产标识符(SN/IMEI/虚拟号/ICCID/MSISDN)"` PackageIDs []uint `json:"package_ids" validate:"required,min=1,dive,gt=0" required:"true" description:"套餐ID列表"` - AppType string `json:"app_type" validate:"required,oneof=official_account miniapp" required:"true" description:"应用类型 (official_account:公众号, miniapp:小程序)"` + // app_type 仅强充场景需要(服务端检测到强充时会直接发起微信支付);普通下单时可不传 + AppType string `json:"app_type" validate:"omitempty,oneof=official_account miniapp" description:"应用类型(强充必传)(official_account:公众号, miniapp:小程序)"` } // ClientCreateOrderResponse D1 客户端创建订单响应 type ClientCreateOrderResponse struct { OrderType string `json:"order_type" description:"订单类型 (package:套餐订单, recharge:充值订单)"` - Order *ClientOrderInfo `json:"order,omitempty" description:"套餐订单信息"` + Order *ClientOrderInfo `json:"order,omitempty" description:"套餐订单信息(普通下单时返回,无支付参数,需单独调支付接口)"` Recharge *ClientRechargeInfo `json:"recharge,omitempty" description:"充值订单信息"` - PayConfig *ClientPayConfig `json:"pay_config" description:"支付配置"` + PayConfig *ClientPayConfig `json:"pay_config,omitempty" description:"微信支付配置(仅强充场景返回)"` LinkedPackageInfo *LinkedPackageInfo `json:"linked_package_info,omitempty" description:"关联套餐信息"` } @@ -38,7 +39,7 @@ type ClientRechargeInfo struct { AutoPurchaseStatus string `json:"auto_purchase_status" description:"自动购包状态"` } -// ClientPayConfig D1 支付配置 +// ClientPayConfig 微信支付配置 type ClientPayConfig struct { AppID string `json:"app_id" description:"应用ID"` Timestamp string `json:"timestamp" description:"时间戳"` @@ -111,3 +112,26 @@ type ClientOrderPackageItem struct { Price int64 `json:"price" description:"单价(分)"` Quantity int `json:"quantity" description:"数量"` } + +// ======================================== +// D4 订单支付 +// ======================================== + +// ClientPayOrderRequest D4 订单支付请求 +type ClientPayOrderRequest struct { + PaymentMethod string `json:"payment_method" validate:"required,oneof=wallet wechat" required:"true" description:"支付方式 (wallet:钱包, wechat:微信)"` + // AppType 仅 payment_method=wechat 时必填 + AppType string `json:"app_type" validate:"omitempty,oneof=official_account miniapp" description:"应用类型(微信支付必传)(official_account:公众号, miniapp:小程序)"` +} + +// ClientPayOrderResponse D4 订单支付响应 +type ClientPayOrderResponse struct { + PaymentMethod string `json:"payment_method" description:"支付方式"` + PayConfig *ClientPayConfig `json:"pay_config,omitempty" description:"微信支付配置(payment_method=wechat 时返回)"` +} + +// ClientPayOrderParams D4 订单支付文档参数(路径参数 + 请求体,仅供文档生成器使用) +type ClientPayOrderParams struct { + IDReq + ClientPayOrderRequest +} diff --git a/internal/routes/personal.go b/internal/routes/personal.go index 77218be..96ed62a 100644 --- a/internal/routes/personal.go +++ b/internal/routes/personal.go @@ -214,6 +214,14 @@ func RegisterPersonalCustomerRoutes(router fiber.Router, doc *openapi.Generator, Output: &dto.ClientOrderDetailResponse{}, }) + Register(authGroup, doc, basePath, "POST", "/orders/:id/pay", handlers.ClientOrder.PayOrder, RouteSpec{ + Summary: "订单支付", + Tags: []string{"个人客户 - 订单"}, + Auth: true, + Input: &dto.ClientPayOrderParams{}, + Output: &dto.ClientPayOrderResponse{}, + }) + Register(authGroup, doc, basePath, "GET", "/exchange/pending", handlers.ClientExchange.GetPending, RouteSpec{ Summary: "查询待处理换货单", Tags: []string{"个人客户 - 换货"}, diff --git a/internal/service/client_order/service.go b/internal/service/client_order/service.go index 2e4878d..9eb3b51 100644 --- a/internal/service/client_order/service.go +++ b/internal/service/client_order/service.go @@ -34,6 +34,12 @@ type WechatConfigServiceInterface interface { GetActiveConfig(ctx context.Context) (*model.WechatConfig, error) } +// OrderWalletPayServiceInterface 订单钱包支付服务接口。 +// 用于将钱包扣款、套餐激活、佣金计算等核心逻辑委托给 B 端 order.Service 处理。 +type OrderWalletPayServiceInterface interface { + WalletPay(ctx context.Context, orderID uint, buyerType string, buyerID uint) error +} + // ForceRechargeRequirement 强充要求。 type ForceRechargeRequirement struct { NeedForceRecharge bool @@ -50,6 +56,7 @@ type Service struct { personalDeviceStore *postgres.PersonalCustomerDeviceStore openIDStore *postgres.PersonalCustomerOpenIDStore wechatConfigService WechatConfigServiceInterface + orderPaymentService OrderWalletPayServiceInterface packageSeriesStore *postgres.PackageSeriesStore shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore iotCardStore *postgres.IotCardStore @@ -69,6 +76,7 @@ func New( personalDeviceStore *postgres.PersonalCustomerDeviceStore, openIDStore *postgres.PersonalCustomerOpenIDStore, wechatConfigService WechatConfigServiceInterface, + orderPaymentService OrderWalletPayServiceInterface, packageSeriesStore *postgres.PackageSeriesStore, shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore, iotCardStore *postgres.IotCardStore, @@ -86,6 +94,7 @@ func New( personalDeviceStore: personalDeviceStore, openIDStore: openIDStore, wechatConfigService: wechatConfigService, + orderPaymentService: orderPaymentService, packageSeriesStore: packageSeriesStore, shopSeriesAllocationStore: shopSeriesAllocationStore, iotCardStore: iotCardStore, @@ -97,6 +106,8 @@ func New( } // CreateOrder 创建客户端订单。 +// 普通套餐下单:仅创建待支付订单,不发起支付,需后续调用 POST /orders/:id/pay 支付。 +// 强充场景:检测到需要强充时,直接创建充值单并发起微信支付(一步完成),此时 app_type 必传。 func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.ClientCreateOrderRequest) (*dto.ClientCreateOrderResponse, error) { if req == nil { return nil, errors.New(errors.CodeInvalidParam) @@ -105,17 +116,17 @@ func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.Cli return nil, errors.New(errors.CodeInternalError, "Redis 服务未配置") } - skipPermissionCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{}) - assetInfo, err := s.assetService.Resolve(skipPermissionCtx, strings.TrimSpace(req.Identifier)) + skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{}) + assetInfo, err := s.assetService.Resolve(skipCtx, strings.TrimSpace(req.Identifier)) if err != nil { return nil, err } - if err := s.checkAssetOwnership(skipPermissionCtx, customerID, assetInfo.VirtualNo); err != nil { + if err := s.checkAssetOwnership(skipCtx, customerID, assetInfo.VirtualNo); err != nil { return nil, err } - validationResult, err := s.validatePurchase(skipPermissionCtx, assetInfo, req.PackageIDs) + validationResult, err := s.validatePurchase(skipCtx, assetInfo, req.PackageIDs) if err != nil { return nil, err } @@ -125,21 +136,14 @@ func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.Cli return nil, errors.New(errors.CodeNeedRealname) } - activeConfig, appID, err := s.resolveWechatConfig(skipPermissionCtx, req.AppType) - if err != nil { - return nil, err - } + // 先判断是否需要强充,避免普通下单时不必要地校验微信配置 + forceRecharge := s.checkForceRechargeRequirement(skipCtx, validationResult) - openID, err := s.resolveCustomerOpenID(skipPermissionCtx, customerID, appID) - if err != nil { - return nil, err - } - - businessKey := buildClientPurchaseBusinessKey(customerID, assetInfo, req) + businessKey := buildClientPurchaseBusinessKey(customerID, assetInfo, req.PackageIDs) redisKey := constants.RedisClientPurchaseIdempotencyKey(businessKey) lockKey := constants.RedisClientPurchaseLockKey(assetInfo.AssetType, assetInfo.AssetID) - lockAcquired, err := s.redis.SetNX(skipPermissionCtx, lockKey, time.Now().String(), clientPurchaseLockTTL).Result() + lockAcquired, err := s.redis.SetNX(skipCtx, lockKey, time.Now().String(), clientPurchaseLockTTL).Result() if err != nil { s.logger.Warn("获取客户端购买分布式锁失败,继续尝试幂等标记", zap.Error(err), @@ -150,16 +154,16 @@ func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.Cli return nil, errors.New(errors.CodeTooManyRequests, "订单正在创建中,请勿重复提交") } - claimed, err := s.redis.SetNX(skipPermissionCtx, redisKey, "processing", clientPurchaseIdempotencyTTL).Result() + claimed, err := s.redis.SetNX(skipCtx, redisKey, "processing", clientPurchaseIdempotencyTTL).Result() if err != nil { if lockAcquired { - _ = s.redis.Del(skipPermissionCtx, lockKey).Err() + _ = s.redis.Del(skipCtx, lockKey).Err() } return nil, errors.Wrap(errors.CodeInternalError, err, "设置客户端购买幂等标记失败") } if !claimed { if lockAcquired { - _ = s.redis.Del(skipPermissionCtx, lockKey).Err() + _ = s.redis.Del(skipCtx, lockKey).Err() } return nil, errors.New(errors.CodeTooManyRequests, "订单正在创建中,请勿重复提交") } @@ -167,24 +171,31 @@ func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.Cli created := false defer func() { if lockAcquired { - _ = s.redis.Del(skipPermissionCtx, lockKey).Err() + _ = s.redis.Del(skipCtx, lockKey).Err() } if !created { - _ = s.redis.Del(skipPermissionCtx, redisKey).Err() + _ = s.redis.Del(skipCtx, redisKey).Err() } }() - paymentProvider, err := s.newPaymentProvider(activeConfig, appID, req.AppType) - if err != nil { - return nil, err - } - - forceRecharge := s.checkForceRechargeRequirement(skipPermissionCtx, validationResult) if forceRecharge.NeedForceRecharge { - return s.createForceRechargeOrder(skipPermissionCtx, customerID, openID, assetInfo, validationResult, activeConfig, forceRecharge, redisKey, paymentProvider, &created) + // 强充场景需要微信支付,此时 app_type 必须传入 + activeConfig, appID, err := s.resolveWechatConfig(skipCtx, req.AppType) + if err != nil { + return nil, err + } + openID, err := s.resolveCustomerOpenID(skipCtx, customerID, appID) + if err != nil { + return nil, err + } + paymentProvider, err := s.newPaymentProvider(activeConfig, appID, req.AppType) + if err != nil { + return nil, err + } + return s.createForceRechargeOrder(skipCtx, customerID, openID, assetInfo, validationResult, activeConfig, forceRecharge, redisKey, paymentProvider, &created) } - return s.createPackageOrder(skipPermissionCtx, customerID, openID, validationResult, activeConfig, redisKey, paymentProvider, &created) + return s.createPackageOrder(skipCtx, customerID, validationResult, redisKey, &created) } func (s *Service) checkAssetOwnership(ctx context.Context, customerID uint, virtualNo string) error { @@ -269,14 +280,11 @@ func (s *Service) resolveCustomerOpenID(ctx context.Context, customerID uint, ap func (s *Service) createPackageOrder( ctx context.Context, customerID uint, - openID string, validationResult *purchase_validation.PurchaseValidationResult, - activeConfig *model.WechatConfig, redisKey string, - paymentProvider PaymentProvider, created *bool, ) (*dto.ClientCreateOrderResponse, error) { - order, err := s.buildPendingOrder(customerID, validationResult, activeConfig) + order, err := s.buildPendingOrder(customerID, validationResult) if err != nil { return nil, err } @@ -291,17 +299,6 @@ func (s *Service) createPackageOrder( } s.markClientPurchaseCreated(ctx, redisKey, order.OrderNo) - - description := "套餐购买" - if len(items) > 0 && items[0] != nil && items[0].PackageName != "" { - description = items[0].PackageName - } - - paymentResult, err := paymentProvider.CreateJSAPIPayment(ctx, order.OrderNo, description, openID, int(order.TotalAmount)) - if err != nil { - return nil, err - } - *created = true return &dto.ClientCreateOrderResponse{ @@ -313,7 +310,6 @@ func (s *Service) createPackageOrder( PaymentStatus: order.PaymentStatus, CreatedAt: formatClientServiceTime(order.CreatedAt), }, - PayConfig: buildClientPayConfigFromResult(paymentResult), }, nil } @@ -393,7 +389,7 @@ func (s *Service) createForceRechargeOrder( }, nil } -func (s *Service) buildPendingOrder(customerID uint, result *purchase_validation.PurchaseValidationResult, activeConfig *model.WechatConfig) (*model.Order, error) { +func (s *Service) buildPendingOrder(customerID uint, result *purchase_validation.PurchaseValidationResult) (*model.Order, error) { orderType := resolveOrderType(result) if orderType == "" { return nil, errors.New(errors.CodeInvalidParam) @@ -411,14 +407,12 @@ func (s *Service) buildPendingOrder(customerID uint, result *purchase_validation BuyerType: model.BuyerTypePersonal, BuyerID: customerID, TotalAmount: result.TotalPrice, - PaymentMethod: model.PaymentMethodWechat, PaymentStatus: model.PaymentStatusPending, CommissionStatus: model.CommissionStatusPending, CommissionConfigVersion: 0, Source: constants.OrderSourceClient, Generation: resolveGeneration(result), ExpiresAt: &expiresAt, - PaymentConfigID: &activeConfig.ID, } if result.Card != nil { @@ -648,17 +642,17 @@ func extractPackageIDs(packages []*model.Package) []uint { return ids } -func buildClientPurchaseBusinessKey(customerID uint, assetInfo *dto.AssetResolveResponse, req *dto.ClientCreateOrderRequest) string { - packageIDs := make([]uint, 0, len(req.PackageIDs)) - packageIDs = append(packageIDs, req.PackageIDs...) - slices.Sort(packageIDs) +func buildClientPurchaseBusinessKey(customerID uint, assetInfo *dto.AssetResolveResponse, packageIDs []uint) string { + sorted := make([]uint, len(packageIDs)) + copy(sorted, packageIDs) + slices.Sort(sorted) - parts := make([]string, 0, len(packageIDs)) - for _, packageID := range packageIDs { - parts = append(parts, strconv.FormatUint(uint64(packageID), 10)) + parts := make([]string, 0, len(sorted)) + for _, id := range sorted { + parts = append(parts, strconv.FormatUint(uint64(id), 10)) } - return fmt.Sprintf("%d:%s:%d:%s:%s", customerID, assetInfo.AssetType, assetInfo.AssetID, req.AppType, strings.Join(parts, ",")) + return fmt.Sprintf("%d:%s:%d:%s", customerID, assetInfo.AssetType, assetInfo.AssetID, strings.Join(parts, ",")) } func rechargeStatusToClientStatus(status int) int { @@ -810,6 +804,77 @@ func (s *Service) GetOrderDetail(ctx context.Context, customerID uint, orderID u }, nil } +// PayOrder D4 对待支付的 C 端订单发起支付。 +// 支持 wallet(钱包扣款)和 wechat(微信 JSAPI)两种支付方式。 +// 先校验订单归属和状态,再按 payment_method 路由到对应支付逻辑: +// - wallet:委托 B 端 WalletPay(乐观锁扣款 + 激活套餐 + 佣金入队) +// - wechat:查微信配置 + OpenID → 预下单 → 更新 payment_config_id → 返回 JS 支付参数 +func (s *Service) PayOrder(ctx context.Context, customerID uint, orderID uint, req *dto.ClientPayOrderRequest) (*dto.ClientPayOrderResponse, error) { + if req == nil { + return nil, errors.New(errors.CodeInvalidParam) + } + + // 跳过 GORM 店铺过滤,C 端订单查询不依赖 seller_shop_id 过滤 + skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{}) + + order, err := s.orderStore.GetByID(skipCtx, orderID) + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, errors.New(errors.CodeNotFound, "订单不存在") + } + return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询订单失败") + } + + if order.BuyerType != model.BuyerTypePersonal || order.BuyerID != customerID { + return nil, errors.New(errors.CodeForbidden, "无权操作此订单") + } + + if order.PaymentStatus != model.PaymentStatusPending { + return nil, errors.New(errors.CodeInvalidStatus, "订单状态不允许支付") + } + + switch req.PaymentMethod { + case model.PaymentMethodWallet: + if err := s.orderPaymentService.WalletPay(skipCtx, orderID, model.BuyerTypePersonal, customerID); err != nil { + return nil, err + } + return &dto.ClientPayOrderResponse{PaymentMethod: model.PaymentMethodWallet}, nil + + case model.PaymentMethodWechat: + activeConfig, appID, err := s.resolveWechatConfig(skipCtx, req.AppType) + if err != nil { + return nil, err + } + openID, err := s.resolveCustomerOpenID(skipCtx, customerID, appID) + if err != nil { + return nil, err + } + paymentProvider, err := s.newPaymentProvider(activeConfig, appID, req.AppType) + if err != nil { + return nil, err + } + + // 记录本次支付使用的微信配置,供支付回调验签使用 + if err := s.db.WithContext(skipCtx).Model(&model.Order{}). + Where("id = ?", orderID). + Update("payment_config_id", activeConfig.ID).Error; err != nil { + return nil, errors.Wrap(errors.CodeDatabaseError, err, "更新订单支付配置失败") + } + + paymentResult, err := paymentProvider.CreateJSAPIPayment(skipCtx, order.OrderNo, "套餐购买", openID, int(order.TotalAmount)) + if err != nil { + return nil, err + } + return &dto.ClientPayOrderResponse{ + PaymentMethod: model.PaymentMethodWechat, + PayConfig: buildClientPayConfigFromResult(paymentResult), + }, nil + + default: + return nil, errors.New(errors.CodeInvalidParam, "不支持的支付方式") + } +} + func (s *Service) getAssetGeneration(ctx context.Context, assetType string, assetID uint) (int, error) { switch assetType { case "card":