收口审计治理与套餐任务进展

Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展
Confidence: medium
Scope-risk: broad
Directive: 后续修改需保持审计事件与业务事务边界一致
Tested: git diff --cached --check
Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
2026-08-05 14:30:54 +08:00
parent b3499adfca
commit 5e552d99bc
178 changed files with 16797 additions and 5674 deletions

View File

@@ -8,6 +8,8 @@ import (
"strings"
"time"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
"github.com/break/junhong_cmp_fiber/internal/middleware"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
@@ -46,6 +48,8 @@ type ClientWalletHandler struct {
iotCardStore *postgres.IotCardStore
deviceStore *postgres.DeviceStore
paymentMethodPolicy ClientPaymentMethodPolicy
auditWriter *audit.Writer
paymentIntegration *integrationlog.Repository
}
// SetPaymentMethodPolicy 注入 C 端支付方式策略。
@@ -53,6 +57,12 @@ func (h *ClientWalletHandler) SetPaymentMethodPolicy(policy ClientPaymentMethodP
h.paymentMethodPolicy = policy
}
// SetPaymentAudit 注入充值支付审计与外部交互日志接缝。
func (h *ClientWalletHandler) SetPaymentAudit(writer *audit.Writer, integration *integrationlog.Repository) {
h.auditWriter = writer
h.paymentIntegration = integration
}
// NewClientWalletHandler 创建 C 端钱包处理器
func NewClientWalletHandler(
assetService *asset.Service,
@@ -352,6 +362,10 @@ func (h *ClientWalletHandler) createWechatRecharge(
// 先初始化生效支付通道并创建预支付订单,确认支付通道可用
// 避免先写入充值记录后支付初始化失败,导致产生孤儿记录
attempt, startedAt, err := h.startRechargePaymentAttempt(resolved.SkipPermissionCtx, config, paymentNo, rechargeNo, req.Amount)
if err != nil {
return err
}
payConfig, err := h.createClientRechargePayConfig(
resolved.SkipPermissionCtx,
config,
@@ -363,6 +377,12 @@ func (h *ClientWalletHandler) createWechatRecharge(
int(req.Amount),
)
if err != nil {
if completeErr := h.completeRechargePaymentAttempt(resolved.SkipPermissionCtx, attempt, startedAt, constants.IntegrationResultUnknown, "request_unknown", "充值支付预下单结果未知"); completeErr != nil {
return completeErr
}
return err
}
if err := h.completeRechargePaymentAttempt(resolved.SkipPermissionCtx, attempt, startedAt, constants.IntegrationResultSuccess, "SUCCESS", ""); err != nil {
return err
}
@@ -381,10 +401,6 @@ func (h *ClientWalletHandler) createWechatRecharge(
OperatorType: constants.OperatorTypePersonalCustomer,
Generation: resolved.Generation,
}
if err := h.rechargeOrderStore.Create(resolved.SkipPermissionCtx, rechargeOrder); err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建充值订单失败")
}
payment := &model.Payment{
PaymentNo: paymentNo,
OrderID: rechargeOrder.ID,
@@ -394,8 +410,17 @@ func (h *ClientWalletHandler) createWechatRecharge(
Status: model.PaymentRecordStatusPending,
PaymentConfigID: &config.ID,
}
if err := h.paymentStore.Create(resolved.SkipPermissionCtx, payment); err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建支付记录失败")
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
if err := h.paymentStore.CreateWithTx(resolved.SkipPermissionCtx, tx, payment); err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建支付记录失败")
}
return h.appendRechargePaymentCreatedAudit(resolved.SkipPermissionCtx, tx, payment, rechargeOrder)
}); err != nil {
return err
}
return response.Success(c, &dto.ClientRechargeResponse{
@@ -456,14 +481,17 @@ func (h *ClientWalletHandler) createAlipayRecharge(
return errors.Wrap(errors.CodeDatabaseError, err, "创建充值订单失败")
}
payment.OrderID = rechargeOrder.ID
return h.paymentStore.CreateWithTx(resolved.SkipPermissionCtx, tx, payment)
if err := h.paymentStore.CreateWithTx(resolved.SkipPermissionCtx, tx, payment); err != nil {
return err
}
return h.appendRechargePaymentCreatedAudit(resolved.SkipPermissionCtx, tx, payment, rechargeOrder)
}); err != nil {
return err
}
wapURL, err := alipay.BuildWapPayURL(resolved.SkipPermissionCtx, config, payment, "资产钱包充值")
if err != nil {
if updateErr := h.paymentStore.UpdateStatus(resolved.SkipPermissionCtx, payment.ID, model.PaymentRecordStatusFailed); updateErr != nil {
if updateErr := h.markRechargePaymentFailed(resolved.SkipPermissionCtx, payment); updateErr != nil {
h.logger.Warn("标记支付宝支付单 failed 失败",
zap.String("payment_no", paymentNo),
zap.Error(updateErr),

View File

@@ -0,0 +1,100 @@
package app
import (
"context"
"strconv"
"time"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
func (h *ClientWalletHandler) appendRechargePaymentCreatedAudit(ctx context.Context, tx *gorm.DB, payment *model.Payment, recharge *model.RechargeOrder) error {
if h.auditWriter == nil {
return errors.New(errors.CodeInvalidStatus, "充值支付统一审计接缝未配置")
}
rechargeID := strconv.FormatUint(uint64(recharge.ID), 10)
resources := []audit.ResourceInput{
audit.PaymentResource(payment, constants.AuditResourceRelationPrimary, constants.AuditResourceRolePaymentTarget, nil, map[string]any{"status": payment.Status}),
{
Type: constants.AuditResourceRechargeOrder, ID: &rechargeID, Key: recharge.RechargeOrderNo, DisplayName: recharge.RechargeOrderNo,
Relation: constants.AuditResourceRelationReference, Role: constants.AuditResourceRolePaymentBusinessOrder,
IdentitySnapshot: map[string]any{
"id": recharge.ID, "recharge_order_no": recharge.RechargeOrderNo, "user_id": recharge.UserID,
"asset_wallet_id": recharge.AssetWalletID, "resource_type": recharge.ResourceType,
"resource_id": recharge.ResourceID, "amount": recharge.Amount, "status": recharge.Status,
},
SubjectVisibility: constants.AuditSubjectResult, SubjectSummary: "资产充值支付已创建",
},
}
references, err := audit.AssetRechargeReferences(ctx, tx, recharge)
if err != nil {
return err
}
resources = append(resources, references...)
return h.auditWriter.Append(ctx, tx, audit.AppendInput{
ActionCode: constants.AuditActionPaymentCreated, Summary: "创建资产充值支付记录",
ScopeType: constants.AuditScopePlatform, Result: constants.AuditResultSuccess,
CorrelationID: payment.PaymentNo,
Resources: resources,
})
}
func (h *ClientWalletHandler) startRechargePaymentAttempt(ctx context.Context, config *model.WechatConfig, paymentNo, rechargeNo string, amount int64) (*model.IntegrationLog, time.Time, error) {
if h.paymentIntegration == nil {
return nil, time.Time{}, errors.New(errors.CodeInvalidStatus, "充值支付 Integration Log 接缝未配置")
}
provider := constants.IntegrationProviderWechatPay
if config.ProviderType == model.ProviderTypeFuiou {
provider = constants.IntegrationProviderFuiou
}
series := "payment:" + paymentNo + ":" + constants.IntegrationOperationPaymentPreCreate
log, err := h.paymentIntegration.Start(ctx, integrationlog.Attempt{
Provider: provider, Direction: constants.IntegrationDirectionOutbound,
Operation: constants.IntegrationOperationPaymentPreCreate,
ResourceType: constants.IntegrationResourceTypePayment, ResourceKey: &paymentNo, ExternalID: &paymentNo,
TriggerSeries: &series, CorrelationID: &rechargeNo,
RequestSummary: map[string]any{"payment_config_id": config.ID, "amount": amount},
})
return log, time.Now(), err
}
func (h *ClientWalletHandler) completeRechargePaymentAttempt(ctx context.Context, log *model.IntegrationLog, startedAt time.Time, result, providerCode, safeMessage string) error {
completion := integrationlog.Completion{
Result: result, ProviderCode: providerCode, SafeProviderMessage: safeMessage,
ResponseSummary: map[string]any{"success": result == constants.IntegrationResultSuccess},
DurationMS: time.Since(startedAt).Milliseconds(),
}
if result == constants.IntegrationResultUnknown {
completion.RecoveryStrategy = "使用原支付单号向支付渠道查单,确认结果后再推进本地充值状态"
}
_, err := h.paymentIntegration.Complete(ctx, log.IntegrationID, completion)
return err
}
func (h *ClientWalletHandler) markRechargePaymentFailed(ctx context.Context, payment *model.Payment) error {
return h.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
result := tx.Model(&model.Payment{}).Where("id = ? AND status = ?", payment.ID, model.PaymentRecordStatusPending).
Update("status", model.PaymentRecordStatusFailed)
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "关闭失败支付记录失败")
}
if result.RowsAffected == 0 {
return nil
}
after := *payment
after.Status = model.PaymentRecordStatusFailed
return h.auditWriter.Append(ctx, tx, audit.AppendInput{
ActionCode: constants.AuditActionPaymentFailed, Summary: "支付宝支付链接生成失败,关闭支付记录",
ScopeType: constants.AuditScopePlatform, Result: constants.AuditResultSuccess,
CorrelationID: payment.PaymentNo,
Resources: []audit.ResourceInput{audit.PaymentResource(&after, constants.AuditResourceRelationPrimary, constants.AuditResourceRolePaymentTarget,
map[string]any{"status": payment.Status}, map[string]any{"status": after.Status})},
})
})
}