task: 完成支付宝C端入口

新增支付宝支付方式支持:
- DTO:ClientPaymentLink、ClientPayOrderRequest/Response 增加 alipay;ClientCreateOrderRequest 增加 payment_method;ClientCreateOrderResponse 增加 payment_link;ClientCreateRechargeRequest 允许 alipay,app_type 改为微信必填;ClientRechargeResponse PayConfig 改为可选指针,增加 payment_link
- Service(client_order):PayOrder 新增 alipay 分支(不需要 app_type/OpenID,复用或新建 pending payment,生成 WAP URL);CreateOrder 强充路径按 payment_method 分支,alipay 不需要 app_type/OpenID;幂等命中时支付宝强充返回同一充值单对应的 payment_link;新增 getOrBuildAlipayPaymentLink(过期 payment 自动续建);新增 createAlipayForceRechargeOrder(事务创建充值单+支付单,WAP URL 生成失败则标记 failed)
- Handler(client_wallet):CreateRecharge 移除微信唯一限制,按 payment_method 分发到 createWechatRecharge / createAlipayRecharge;alipay 分支事务内创建充值单+支付单,然后生成 WAP URL,失败标记 payment failed
- 依赖 worker-1 共享 API:pkg/alipay.BuildWapPayURL/CalcExpireAt、Payment.ExpireAt、WechatConfig.AliReturnURL/AliPayExpireMinutes、PaymentStore.FindLatestPendingByOrderAndMethod;本 worktree 暂不能独立构建,待 leader 集成

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 12:18:11 +08:00
parent 53e95751b1
commit 56adc30409
4 changed files with 398 additions and 22 deletions

View File

@@ -15,6 +15,7 @@ import (
rechargeSvc "github.com/break/junhong_cmp_fiber/internal/service/recharge"
wechatConfigSvc "github.com/break/junhong_cmp_fiber/internal/service/wechat_config"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/alipay"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/fuiou"
@@ -262,10 +263,6 @@ func (h *ClientWalletHandler) CreateRecharge(c *fiber.Ctx) error {
return errors.New(errors.CodeInvalidParam)
}
if req.PaymentMethod != constants.RechargeMethodWechat {
return errors.New(errors.CodeInvalidParam)
}
resolved, err := h.resolveAssetFromIdentifier(c, req.Identifier)
if err != nil {
return err
@@ -289,6 +286,23 @@ func (h *ClientWalletHandler) CreateRecharge(c *fiber.Ctx) error {
return errors.New(errors.CodeWechatConfigUnavailable)
}
switch req.PaymentMethod {
case constants.RechargeMethodAlipay:
return h.createAlipayRecharge(c, resolved, config, wallet, req)
default:
// 微信充值默认app_type 必填
return h.createWechatRecharge(c, resolved, config, wallet, req)
}
}
// createWechatRecharge 微信充值分支
func (h *ClientWalletHandler) createWechatRecharge(
c *fiber.Ctx,
resolved *resolvedWalletAssetContext,
config *model.WechatConfig,
wallet *model.AssetWallet,
req dto.ClientCreateRechargeRequest,
) error {
appID, err := pickAppIDByType(config, req.AppType)
if err != nil {
return err
@@ -350,17 +364,99 @@ func (h *ClientWalletHandler) CreateRecharge(c *fiber.Ctx) error {
return errors.Wrap(errors.CodeDatabaseError, err, "创建支付记录失败")
}
resp := &dto.ClientRechargeResponse{
return response.Success(c, &dto.ClientRechargeResponse{
Recharge: dto.ClientRechargeResult{
RechargeID: rechargeOrder.ID,
RechargeNo: rechargeOrder.RechargeOrderNo,
Amount: rechargeOrder.Amount,
Status: rechargeOrder.Status,
},
PayConfig: payConfig,
PayConfig: &payConfig,
})
}
// createAlipayRecharge 支付宝充值分支。
// WAP URL 为本地签名生成,事务内先写 DB再生成链接链接失败则标记 payment failed。
func (h *ClientWalletHandler) createAlipayRecharge(
c *fiber.Ctx,
resolved *resolvedWalletAssetContext,
config *model.WechatConfig,
wallet *model.AssetWallet,
req dto.ClientCreateRechargeRequest,
) error {
rechargeNo := generateClientRechargeNo()
paymentNo := generateClientPaymentNo()
expireAt := alipay.CalcExpireAt(config)
rechargeOrder := &model.RechargeOrder{
RechargeOrderNo: rechargeNo,
UserID: resolved.CustomerID,
AssetWalletID: wallet.ID,
ResourceType: resolved.ResourceType,
ResourceID: resolved.Asset.AssetID,
Amount: req.Amount,
Status: model.RechargeOrderStatusPending,
PaymentConfigID: &config.ID,
ShopIDTag: wallet.ShopIDTag,
EnterpriseIDTag: wallet.EnterpriseIDTag,
OperatorType: constants.OperatorTypePersonalCustomer,
Generation: resolved.Generation,
}
payment := &model.Payment{
PaymentNo: paymentNo,
OrderType: model.PaymentOrderTypeRecharge,
PaymentMethod: model.PaymentByAlipay,
Amount: req.Amount,
Status: model.PaymentRecordStatusPending,
PaymentConfigID: &config.ID,
ExpireAt: &expireAt,
}
return response.Success(c, resp)
// WAP URL 是本地签名,不需要先调第三方,在事务内创建充值单和支付单
if err := h.db.WithContext(resolved.SkipPermissionCtx).Transaction(func(tx *gorm.DB) error {
if err := h.rechargeOrderStore.CreateWithTx(resolved.SkipPermissionCtx, tx, rechargeOrder); err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建充值订单失败")
}
payment.OrderID = rechargeOrder.ID
return h.paymentStore.CreateWithTx(resolved.SkipPermissionCtx, tx, payment)
}); err != nil {
return err
}
wapURL, err := alipay.BuildWapPayURL(resolved.SkipPermissionCtx, config, payment, "资产钱包充值", config.AliReturnURL)
if err != nil {
if updateErr := h.paymentStore.UpdateStatus(resolved.SkipPermissionCtx, payment.ID, model.PaymentRecordStatusFailed); updateErr != nil {
h.logger.Warn("标记支付宝支付单 failed 失败",
zap.String("payment_no", paymentNo),
zap.Error(updateErr),
)
}
return err
}
h.logger.Info("支付宝充值支付单已创建",
zap.String("payment_no", paymentNo),
zap.String("recharge_no", rechargeNo),
zap.Int64("amount", req.Amount),
zap.Time("expire_at", expireAt),
zap.Uint("config_id", config.ID),
)
expireStr := expireAt.Format(time.RFC3339)
return response.Success(c, &dto.ClientRechargeResponse{
Recharge: dto.ClientRechargeResult{
RechargeID: rechargeOrder.ID,
RechargeNo: rechargeOrder.RechargeOrderNo,
Amount: rechargeOrder.Amount,
Status: rechargeOrder.Status,
},
PaymentLink: &dto.ClientPaymentLink{
PaymentNo: paymentNo,
QRLink: wapURL,
CopyLink: wapURL,
PayExpireAt: expireStr,
},
})
}
// GetRechargeList C5 充值记录列表

View File

@@ -8,8 +8,10 @@ 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列表"`
// app_type 仅强充场景需要(服务端检测到强充时会直接发起微信支付);普通下单时可不传
AppType string `json:"app_type" validate:"omitempty,oneof=official_account miniapp" description:"应用类型(强充必传)(official_account:公众号, miniapp:小程序)"`
// PaymentMethod 指定支付方式强充场景必传。wechat 时 app_type 也必传alipay 时不需要 app_type
PaymentMethod string `json:"payment_method" validate:"omitempty,oneof=wechat alipay" description:"支付方式(强充必传)(wechat:微信, alipay:支付宝)"`
// AppType 仅强充 + 微信支付场景必传;支付宝强充无需传
AppType string `json:"app_type" validate:"omitempty,oneof=official_account miniapp" description:"应用类型(微信强充必传)(official_account:公众号, miniapp:小程序)"`
}
// ClientCreateOrderResponse D1 客户端创建订单响应
@@ -17,7 +19,8 @@ type ClientCreateOrderResponse struct {
OrderType string `json:"order_type" description:"订单类型 (package:套餐订单, recharge:充值订单)"`
Order *ClientOrderInfo `json:"order,omitempty" description:"套餐订单信息(普通下单时返回,无支付参数,需单独调支付接口)"`
Recharge *ClientRechargeInfo `json:"recharge,omitempty" description:"充值订单信息"`
PayConfig *ClientPayConfig `json:"pay_config,omitempty" description:"微信支付配置(强充场景返回)"`
PayConfig *ClientPayConfig `json:"pay_config,omitempty" description:"微信支付配置(强充微信支付时返回)"`
PaymentLink *ClientPaymentLink `json:"payment_link,omitempty" description:"支付宝支付链接(强充支付宝支付时返回)"`
LinkedPackageInfo *LinkedPackageInfo `json:"linked_package_info,omitempty" description:"关联套餐信息"`
Idempotent bool `json:"idempotent,omitempty" description:"幂等标志true 表示返回的是已存在的待支付充值订单)"`
}
@@ -122,17 +125,26 @@ type ClientOrderPackageItem struct {
// D4 订单支付
// ========================================
// ClientPaymentLink C 端支付宝支付链接
type ClientPaymentLink struct {
PaymentNo string `json:"payment_no" description:"支付单号"`
QRLink string `json:"qr_link" description:"二维码内容链接,前端用该链接生成二维码"`
CopyLink string `json:"copy_link" description:"复制到普通浏览器打开的支付链接(与 qr_link 相同)"`
PayExpireAt string `json:"pay_expire_at" description:"支付链接过期时间RFC3339 格式)"`
}
// ClientPayOrderRequest D4 订单支付请求
type ClientPayOrderRequest struct {
PaymentMethod string `json:"payment_method" validate:"required,oneof=wallet wechat" required:"true" description:"支付方式 (wallet:钱包, wechat:微信)"`
// AppType 仅 payment_method=wechat 时必填
PaymentMethod string `json:"payment_method" validate:"required,oneof=wallet wechat alipay" required:"true" description:"支付方式 (wallet:钱包, wechat:微信, alipay:支付宝)"`
// AppType 仅 payment_method=wechat 时必填alipay 无需传
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 时返回)"`
PaymentMethod string `json:"payment_method" description:"支付方式"`
PayConfig *ClientPayConfig `json:"pay_config,omitempty" description:"微信支付配置payment_method=wechat 时返回)"`
PaymentLink *ClientPaymentLink `json:"payment_link,omitempty" description:"支付宝支付链接payment_method=alipay 时返回)"`
}
// ClientPayOrderParams D4 订单支付文档参数(路径参数 + 请求体,仅供文档生成器使用)

View File

@@ -77,15 +77,17 @@ type ClientRechargeCheckResponse struct {
// ClientCreateRechargeRequest C4 创建充值订单请求
type ClientCreateRechargeRequest struct {
Identifier string `json:"identifier" validate:"required,min=1,max=50" required:"true" minLength:"1" maxLength:"50" description:"资产标识符SN/IMEI/虚拟号/ICCID/MSISDN"`
Amount int64 `json:"amount" validate:"required,min=100,max=10000000" required:"true" minimum:"100" maximum:"10000000" description:"充值金额(分)"`
PaymentMethod string `json:"payment_method" validate:"required,oneof=wechat" required:"true" description:"支付方式 (wechat:微信支付)"`
AppType string `json:"app_type" validate:"required,oneof=official_account miniapp" required:"true" description:"应用类型 (official_account:公众号, miniapp:小程序)"`
Amount int64 `json:"amount" validate:"required,min=1,max=10000000" required:"true" minimum:"1" maximum:"10000000" description:"充值金额(分)"`
PaymentMethod string `json:"payment_method" validate:"required,oneof=wechat alipay" required:"true" description:"支付方式 (wechat:微信支付, alipay:支付宝)"`
// AppType 仅 payment_method=wechat 时必填alipay 无需传
AppType string `json:"app_type" validate:"omitempty,oneof=official_account miniapp" description:"应用类型(微信支付必传)(official_account:公众号, miniapp:小程序)"`
}
// ClientRechargeResponse C4 创建充值订单响应
type ClientRechargeResponse struct {
Recharge ClientRechargeResult `json:"recharge" description:"充值信息"`
PayConfig ClientRechargePayConfig `json:"pay_config" description:"支付配置"`
Recharge ClientRechargeResult `json:"recharge" description:"充值信息"`
PayConfig *ClientRechargePayConfig `json:"pay_config,omitempty" description:"微信支付配置payment_method=wechat 时返回)"`
PaymentLink *ClientPaymentLink `json:"payment_link,omitempty" description:"支付宝支付链接payment_method=alipay 时返回)"`
}
// ClientRechargeResult C4 充值信息

View File

@@ -15,6 +15,7 @@ import (
asset "github.com/break/junhong_cmp_fiber/internal/service/asset"
"github.com/break/junhong_cmp_fiber/internal/service/purchase_validation"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/alipay"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/bytedance/sonic"
@@ -178,7 +179,7 @@ func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.Cli
if err == nil && strings.HasPrefix(existingValue, "CRCH") {
existingOrder, queryErr := s.rechargeOrderStore.GetByRechargeOrderNo(skipCtx, existingValue)
if queryErr == nil && existingOrder.Status == model.RechargeOrderStatusPending {
return &dto.ClientCreateOrderResponse{
resp := &dto.ClientCreateOrderResponse{
OrderType: "recharge",
Recharge: &dto.ClientRechargeInfo{
RechargeID: existingOrder.ID,
@@ -189,7 +190,23 @@ func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.Cli
AutoPurchaseStatus: existingOrder.AutoPurchaseStatus,
},
Idempotent: true,
}, nil
}
// 支付宝强充幂等:复用已有待支付 payment 或生成新链接
if req.PaymentMethod == model.PaymentMethodAlipay {
paymentLink, linkErr := s.getOrBuildAlipayPaymentLink(
skipCtx, existingOrder.ID, model.PaymentOrderTypeRecharge,
existingOrder.Amount, "余额充值",
)
if linkErr != nil {
s.logger.Warn("强充幂等复用支付宝链接失败",
zap.String("recharge_no", existingOrder.RechargeOrderNo),
zap.Error(linkErr),
)
} else {
resp.PaymentLink = paymentLink
}
}
return resp, nil
}
if queryErr == nil {
_ = s.redis.Del(skipCtx, redisKey).Err()
@@ -209,7 +226,18 @@ func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.Cli
}()
if forceRecharge.NeedForceRecharge {
// 强充场景需要微信支付,此时 app_type 必须传入
if req.PaymentMethod == model.PaymentMethodAlipay {
// 支付宝强充:不需要 app_type / OpenID
activeConfig, err := s.wechatConfigService.GetActiveConfig(skipCtx)
if err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询支付配置失败")
}
if activeConfig == nil {
return nil, errors.New(errors.CodeInvalidParam, "未找到生效的支付配置")
}
return s.createAlipayForceRechargeOrder(skipCtx, customerID, assetInfo, validationResult, activeConfig, forceRecharge, redisKey, &created)
}
// 微信强充app_type 必须传入
activeConfig, appID, err := s.resolveWechatConfig(skipCtx, req.AppType)
if err != nil {
return nil, err
@@ -813,6 +841,214 @@ func generateClientPaymentNo() string {
return fmt.Sprintf("PAY%d%06d", time.Now().UnixNano()/1e6, rand.Intn(1000000))
}
// getOrBuildAlipayPaymentLink 查找未过期的支付宝待支付单并返回链接;
// 过期则标记 failed 并创建新单;新建失败则整体返回错误。
func (s *Service) getOrBuildAlipayPaymentLink(
ctx context.Context,
orderID uint,
orderType string,
amount int64,
subject string,
) (*dto.ClientPaymentLink, error) {
activeConfig, err := s.wechatConfigService.GetActiveConfig(ctx)
if err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询支付配置失败")
}
if activeConfig == nil {
return nil, errors.New(errors.CodeInvalidParam, "未找到生效的支付配置")
}
// 查找最新的 alipay 待支付单
existing, _ := s.paymentStore.FindLatestPendingByOrderAndMethod(
ctx, orderID, orderType, model.PaymentByAlipay,
)
var payment *model.Payment
now := time.Now()
if existing != nil && existing.ExpireAt != nil && existing.ExpireAt.After(now) {
// 复用未过期的支付单
payment = existing
} else {
// 过期或不存在,标记旧单 failed 并新建
if existing != nil {
if updateErr := s.paymentStore.UpdateStatus(ctx, existing.ID, model.PaymentRecordStatusFailed); updateErr != nil {
s.logger.Warn("标记过期支付宝支付单 failed 失败",
zap.Uint("payment_id", existing.ID),
zap.Error(updateErr),
)
}
}
expireAt := alipay.CalcExpireAt(activeConfig)
newPayment := &model.Payment{
PaymentNo: generateClientPaymentNo(),
OrderID: orderID,
OrderType: orderType,
PaymentMethod: model.PaymentByAlipay,
Amount: amount,
Status: model.PaymentRecordStatusPending,
PaymentConfigID: &activeConfig.ID,
ExpireAt: &expireAt,
}
if err := s.paymentStore.Create(ctx, newPayment); err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建支付宝支付单失败")
}
payment = newPayment
s.logger.Info("创建支付宝支付单",
zap.String("payment_no", payment.PaymentNo),
zap.String("order_type", orderType),
zap.Uint("order_id", orderID),
zap.Int64("amount", amount),
zap.Time("expire_at", expireAt),
zap.Uint("config_id", activeConfig.ID),
)
}
wapURL, err := alipay.BuildWapPayURL(ctx, activeConfig, payment, subject, activeConfig.AliReturnURL)
if err != nil {
// 新建的 payment 生成链接失败,标记 failed
if existing == nil || payment.ID != existing.ID {
_ = s.paymentStore.UpdateStatus(ctx, payment.ID, model.PaymentRecordStatusFailed)
}
return nil, err
}
expireStr := ""
if payment.ExpireAt != nil {
expireStr = payment.ExpireAt.Format(time.RFC3339)
}
return &dto.ClientPaymentLink{
PaymentNo: payment.PaymentNo,
QRLink: wapURL,
CopyLink: wapURL,
PayExpireAt: expireStr,
}, nil
}
// createAlipayForceRechargeOrder 强充场景下创建支付宝充值单并返回支付链接。
func (s *Service) createAlipayForceRechargeOrder(
ctx context.Context,
customerID uint,
assetInfo *dto.AssetResolveResponse,
validationResult *purchase_validation.PurchaseValidationResult,
activeConfig *model.WechatConfig,
forceRecharge *ForceRechargeRequirement,
redisKey string,
created *bool,
) (*dto.ClientCreateOrderResponse, error) {
resourceType, resourceID, err := resolveWalletResource(validationResult)
if err != nil {
return nil, err
}
wallet, err := s.getOrCreateWallet(ctx, resourceType, resourceID, resolveSellerShopID(validationResult))
if err != nil {
return nil, err
}
linkedPackageIDs, err := sonic.Marshal(extractPackageIDs(validationResult.Packages))
if err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "序列化关联套餐失败")
}
rechargeOrderNo := generateClientRechargeNo()
var iotCardID *uint
var deviceID *uint
if validationResult.Card != nil {
iotCardID = &validationResult.Card.ID
}
if validationResult.Device != nil {
deviceID = &validationResult.Device.ID
}
rechargeOrder := &model.RechargeOrder{
RechargeOrderNo: rechargeOrderNo,
UserID: customerID,
AssetWalletID: wallet.ID,
ResourceType: resourceType,
ResourceID: resourceID,
IotCardID: iotCardID,
DeviceID: deviceID,
Amount: forceRecharge.ForceRechargeAmount,
Status: model.RechargeOrderStatusPending,
ShopIDTag: wallet.ShopIDTag,
EnterpriseIDTag: wallet.EnterpriseIDTag,
OperatorType: "personal_customer",
Generation: resolveGeneration(validationResult),
LinkedPackageIDs: datatypes.JSON(linkedPackageIDs),
LinkedOrderType: resolveOrderType(validationResult),
LinkedCarrierType: assetInfo.AssetType,
LinkedCarrierID: &resourceID,
AutoPurchaseStatus: model.AutoPurchaseStatusPending,
PaymentConfigID: &activeConfig.ID,
}
expireAt := alipay.CalcExpireAt(activeConfig)
paymentNo := generateClientPaymentNo()
payment := &model.Payment{
PaymentNo: paymentNo,
OrderType: model.PaymentOrderTypeRecharge,
PaymentMethod: model.PaymentByAlipay,
Amount: forceRecharge.ForceRechargeAmount,
Status: model.PaymentRecordStatusPending,
PaymentConfigID: &activeConfig.ID,
ExpireAt: &expireAt,
}
// 事务创建充值单 + 支付单WAP URL 本地签名,不需要先调第三方)
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := s.rechargeOrderStore.CreateWithTx(ctx, tx, rechargeOrder); err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建充值订单失败")
}
payment.OrderID = rechargeOrder.ID
return s.paymentStore.CreateWithTx(ctx, tx, payment)
}); err != nil {
return nil, err
}
wapURL, err := alipay.BuildWapPayURL(ctx, activeConfig, payment, "余额充值", activeConfig.AliReturnURL)
if err != nil {
if updateErr := s.paymentStore.UpdateStatus(ctx, payment.ID, model.PaymentRecordStatusFailed); updateErr != nil {
s.logger.Warn("标记支付宝支付单 failed 失败",
zap.String("payment_no", paymentNo),
zap.Error(updateErr),
)
}
return nil, err
}
s.logger.Info("支付宝强充支付单已创建",
zap.String("payment_no", paymentNo),
zap.String("recharge_no", rechargeOrderNo),
zap.Int64("amount", forceRecharge.ForceRechargeAmount),
zap.Time("expire_at", expireAt),
zap.Uint("config_id", activeConfig.ID),
)
s.markClientPurchaseCreated(ctx, redisKey, rechargeOrderNo)
*created = true
clientStatus := rechargeStatusToClientStatus(int(rechargeOrder.Status))
expireStr := expireAt.Format(time.RFC3339)
return &dto.ClientCreateOrderResponse{
OrderType: "recharge",
Recharge: &dto.ClientRechargeInfo{
RechargeID: rechargeOrder.ID,
RechargeNo: rechargeOrderNo,
Amount: rechargeOrder.Amount,
Status: clientStatus,
StatusName: clientRechargeStatusName(clientStatus),
AutoPurchaseStatus: rechargeOrder.AutoPurchaseStatus,
},
PaymentLink: &dto.ClientPaymentLink{
PaymentNo: paymentNo,
QRLink: wapURL,
CopyLink: wapURL,
PayExpireAt: expireStr,
},
LinkedPackageInfo: buildLinkedPackageInfo(validationResult, forceRecharge),
}, nil
}
// ListOrders 查询 C 端订单列表。
func (s *Service) ListOrders(ctx context.Context, customerID uint, req *dto.ClientOrderListRequest) ([]dto.ClientOrderListItem, int64, error) {
skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})
@@ -1058,6 +1294,36 @@ func (s *Service) PayOrder(ctx context.Context, customerID uint, orderID uint, r
PayConfig: buildClientPayConfigFromResult(paymentResult),
}, nil
case model.PaymentMethodAlipay:
// 支付宝支付:不需要 app_type / OpenID
activeConfig, err := s.wechatConfigService.GetActiveConfig(skipCtx)
if err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询支付配置失败")
}
if activeConfig == nil {
return nil, errors.New(errors.CodeInvalidParam, "未找到生效的支付配置")
}
paymentLink, err := s.getOrBuildAlipayPaymentLink(
skipCtx, orderID, model.PaymentOrderTypePackage, order.TotalAmount, "套餐购买",
)
if err != nil {
return nil, err
}
// 更新订单支付配置 ID兼容回调配置回溯
if dbErr := s.db.WithContext(skipCtx).Model(&model.Order{}).
Where("id = ?", orderID).
Update("payment_config_id", activeConfig.ID).Error; dbErr != nil {
s.logger.Warn("更新订单支付配置 ID 失败",
zap.Uint("order_id", orderID),
zap.Error(dbErr),
)
}
return &dto.ClientPayOrderResponse{
PaymentMethod: model.PaymentMethodAlipay,
PaymentLink: paymentLink,
}, nil
default:
return nil, errors.New(errors.CodeInvalidParam, "不支持的支付方式")
}