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:
@@ -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 充值记录列表
|
||||
|
||||
Reference in New Issue
Block a user