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

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

@@ -123,6 +123,12 @@ func (h *PaymentHandler) WechatPayCallback(c *fiber.Ctx) error {
return errors.Wrap(errors.CodeWechatCallbackInvalid, err, "微信 v2 回调验签失败")
}
if result.TradeState != "SUCCESS" {
if err := h.recordIgnoredPaymentCallback(ctx, verifiedPaymentCallback{
PaymentNo: result.OutTradeNo, TransactionID: result.TransactionID,
Provider: constants.IntegrationProviderWechatPay, RawPayload: body, ContentType: c.Get("Content-Type"),
}, result.TradeState); err != nil {
return err
}
return h.wechatV2SuccessResponse(c)
}
// TotalFee 为字符串格式的分,解析失败则降级为 0后续 handlePaymentCallback 会记录日志)
@@ -145,7 +151,10 @@ func (h *PaymentHandler) WechatPayCallback(c *fiber.Ctx) error {
fasthttpadaptor.ConvertRequest(c.Context(), &httpReq, true)
_, err := h.wechatPayment.HandlePaymentNotify(&httpReq, func(result *wechat.PaymentNotifyResult) error {
if result.TradeState != "SUCCESS" {
return nil
return h.recordIgnoredPaymentCallback(ctx, verifiedPaymentCallback{
PaymentNo: result.OutTradeNo, TransactionID: result.TransactionID,
Provider: constants.IntegrationProviderWechatPay, RawPayload: body, ContentType: c.Get("Content-Type"),
}, result.TradeState)
}
paidAt, _ := time.Parse(time.RFC3339, result.SuccessTime)
return h.dispatchWechatCallback(ctx, verifiedPaymentCallback{
@@ -170,19 +179,35 @@ func (h *PaymentHandler) dispatchPaymentRecordCallback(ctx context.Context, call
if h.paymentStore != nil {
payment, err := h.paymentStore.GetByPaymentNo(ctx, callback.PaymentNo)
if err == nil {
log, err := h.recordPaymentCallback(ctx, callback, payment)
if err != nil {
return true, err
}
var processErr error
switch payment.OrderType {
case model.PaymentOrderTypePackage:
return true, h.orderService.HandlePaymentRecordCallback(ctx, callback.PaymentNo, callback.PaymentMethod, callback.TransactionID, callback.Amount)
processErr = h.orderService.HandlePaymentRecordCallback(ctx, callback.PaymentNo, callback.PaymentMethod, callback.TransactionID, callback.Amount)
case model.PaymentOrderTypeRecharge:
if h.rechargeOrderService != nil {
return true, h.rechargeOrderService.HandlePaymentCallback(ctx, callback.PaymentNo, callback.PaymentMethod, callback.TransactionID)
processErr = h.rechargeOrderService.HandlePaymentCallback(ctx, callback.PaymentNo, callback.PaymentMethod, callback.TransactionID)
} else {
processErr = errors.New(errors.CodeInternalError, "充值订单服务未配置")
}
return true, errors.New(errors.CodeInternalError, "充值订单服务未配置")
case model.PaymentOrderTypeAgentRecharge:
return true, h.confirmAgentRechargePayment(ctx, callback)
return true, h.confirmAgentRechargePayment(ctx, callback, log)
default:
return true, errors.New(errors.CodeInvalidStatus, "未知支付记录类型")
processErr = errors.New(errors.CodeInvalidStatus, "未知支付记录类型")
}
completion := integrationlog.Completion{Result: constants.IntegrationResultSuccess, ResponseSummary: map[string]any{"confirmed": true}}
if processErr != nil {
completion.Result = constants.IntegrationResultFailed
completion.SafeProviderMessage = "支付回调业务确认失败"
completion.ResponseSummary = map[string]any{"confirmed": false}
} else if current, currentErr := h.paymentStore.GetByPaymentNo(ctx, callback.PaymentNo); currentErr == nil {
completion.StateChanged = payment.Status != model.PaymentRecordStatusPaid && current.Status == model.PaymentRecordStatusPaid
}
h.completePaymentCallbackLog(ctx, log, completion)
return true, processErr
}
if err != gorm.ErrRecordNotFound {
return true, errors.Wrap(errors.CodeDatabaseError, err, "查询支付记录失败")
@@ -198,31 +223,14 @@ func (h *PaymentHandler) dispatchWechatCallback(ctx context.Context, callback ve
return err
}
switch {
case strings.HasPrefix(callback.PaymentNo, "ORD"):
return h.orderService.HandlePaymentCallback(ctx, callback.PaymentNo, model.PaymentMethodWechat, callback.Amount)
case strings.HasPrefix(callback.PaymentNo, constants.AssetRechargeOrderPrefix):
if h.rechargeOrderService != nil {
return h.rechargeOrderService.HandlePaymentCallback(ctx, callback.PaymentNo, model.PaymentByWechat, callback.TransactionID)
}
return errors.New(errors.CodeInternalError, "充值订单服务未配置")
case strings.HasPrefix(callback.PaymentNo, constants.AgentRechargeOrderPrefix):
if h.agentRechargeService != nil {
return h.agentRechargeService.HandlePaymentCallback(ctx, callback.PaymentNo, model.PaymentMethodWechat, callback.TransactionID, callback.Amount)
}
return errors.New(errors.CodeInternalError, "代理充值服务未配置")
default:
return errors.New(errors.CodeInvalidStatus, "未知订单号前缀")
}
return h.dispatchLegacyPaymentCallback(ctx, callback)
}
func (h *PaymentHandler) confirmAgentRechargePayment(ctx context.Context, callback verifiedPaymentCallback) error {
if h.agentPaymentConfirm == nil || h.integration == nil {
return errors.New(errors.CodeInternalError, "代理充值支付回调能力未配置")
func (h *PaymentHandler) dispatchLegacyPaymentCallback(ctx context.Context, callback verifiedPaymentCallback) error {
if h.integration == nil {
return errors.New(errors.CodeInvalidStatus, "支付回调 Integration Log 接缝未配置")
}
resourceKey, correlationID := callback.PaymentNo, callback.PaymentNo
ctx = auditcontext.With(ctx, auditcontext.Context{CorrelationID: correlationID})
linkage := auditcontext.From(ctx)
idempotencyKey := callback.TransactionID
if idempotencyKey == "" {
idempotencyKey = callback.PaymentNo
@@ -230,13 +238,51 @@ func (h *PaymentHandler) confirmAgentRechargePayment(ctx context.Context, callba
log, _, err := h.integration.RecordInbound(ctx, integrationlog.InboundAttempt{
IdempotencyKey: idempotencyKey, Provider: callback.Provider,
Operation: constants.IntegrationOperationPaymentCallback, ExternalID: callback.TransactionID,
ResourceType: constants.IntegrationResourceTypeAgentRechargePayment, ResourceKey: &resourceKey,
ResourceType: constants.IntegrationResourceTypePayment, ResourceKey: &resourceKey,
RawPayload: callback.RawPayload, ContentType: callback.ContentType,
RequestID: pkgmiddleware.GetRequestIDFromContext(ctx), CorrelationID: &correlationID,
})
if err != nil {
return err
}
if err := h.preparePaymentCallbackRetry(ctx, log); err != nil {
return err
}
var processErr error
switch {
case strings.HasPrefix(callback.PaymentNo, "ORD"):
processErr = h.orderService.HandlePaymentCallback(ctx, callback.PaymentNo, callback.PaymentMethod, callback.Amount)
case strings.HasPrefix(callback.PaymentNo, constants.AssetRechargeOrderPrefix):
if h.rechargeOrderService != nil {
processErr = h.rechargeOrderService.HandlePaymentCallback(ctx, callback.PaymentNo, callback.PaymentMethod, callback.TransactionID)
} else {
processErr = errors.New(errors.CodeInternalError, "充值订单服务未配置")
}
case strings.HasPrefix(callback.PaymentNo, constants.AgentRechargeOrderPrefix):
if h.agentRechargeService != nil {
processErr = h.agentRechargeService.HandlePaymentCallback(ctx, callback.PaymentNo, callback.PaymentMethod, callback.TransactionID, callback.Amount)
} else {
processErr = errors.New(errors.CodeInternalError, "代理充值服务未配置")
}
default:
processErr = errors.New(errors.CodeInvalidStatus, "未知订单号前缀")
}
completion := integrationlog.Completion{Result: constants.IntegrationResultSuccess, ResponseSummary: map[string]any{"confirmed": processErr == nil}}
if processErr != nil {
completion.Result = constants.IntegrationResultFailed
completion.SafeProviderMessage = "旧支付回调业务确认失败"
}
h.completePaymentCallbackLog(ctx, log, completion)
return processErr
}
func (h *PaymentHandler) confirmAgentRechargePayment(ctx context.Context, callback verifiedPaymentCallback, log *model.IntegrationLog) error {
if h.agentPaymentConfirm == nil || h.integration == nil {
return errors.New(errors.CodeInternalError, "代理充值支付回调能力未配置")
}
correlationID := callback.PaymentNo
ctx = auditcontext.With(ctx, auditcontext.Context{CorrelationID: correlationID})
linkage := auditcontext.From(ctx)
result, confirmErr := h.agentPaymentConfirm.Execute(ctx, agentrechargeApp.ConfirmOnlinePaymentCommand{
PaymentNo: callback.PaymentNo, PaymentMethod: callback.PaymentMethod, ConfigID: callback.ConfigID,
MerchantIdentity: callback.MerchantIdentity, ThirdPartyTradeNo: callback.TransactionID,
@@ -269,6 +315,69 @@ func (h *PaymentHandler) confirmAgentRechargePayment(ctx context.Context, callba
return nil
}
func (h *PaymentHandler) recordPaymentCallback(ctx context.Context, callback verifiedPaymentCallback, payment *model.Payment) (*model.IntegrationLog, error) {
if h.integration == nil || payment == nil {
return nil, errors.New(errors.CodeInvalidStatus, "支付回调 Integration Log 接缝未配置")
}
resourceID, resourceKey, correlationID := strconv.FormatUint(uint64(payment.ID), 10), payment.PaymentNo, payment.PaymentNo
idempotencyKey := callback.TransactionID
if idempotencyKey == "" {
idempotencyKey = callback.PaymentNo
}
ctx = auditcontext.With(ctx, auditcontext.Context{CorrelationID: correlationID})
log, _, err := h.integration.RecordInbound(ctx, integrationlog.InboundAttempt{
IdempotencyKey: idempotencyKey, Provider: callback.Provider,
Operation: constants.IntegrationOperationPaymentCallback, ExternalID: callback.TransactionID,
ResourceType: constants.IntegrationResourceTypePayment, ResourceID: &resourceID, ResourceKey: &resourceKey,
RawPayload: callback.RawPayload, ContentType: callback.ContentType,
RequestID: pkgmiddleware.GetRequestIDFromContext(ctx), CorrelationID: &correlationID,
})
if err != nil {
return nil, err
}
if err := h.preparePaymentCallbackRetry(ctx, log); err != nil {
return nil, err
}
return log, nil
}
func (h *PaymentHandler) preparePaymentCallbackRetry(ctx context.Context, log *model.IntegrationLog) error {
if log == nil || log.Result != constants.IntegrationResultFailed {
return nil
}
claimed, err := h.integration.ClaimFailedInbound(ctx, log.IntegrationID)
if err != nil {
return err
}
if claimed {
log.Result = constants.IntegrationResultPending
}
return nil
}
func (h *PaymentHandler) recordIgnoredPaymentCallback(ctx context.Context, callback verifiedPaymentCallback, providerCode string) error {
if h.integration == nil {
return errors.New(errors.CodeInvalidStatus, "支付回调 Integration Log 接缝未配置")
}
resourceKey, correlationID := callback.PaymentNo, callback.PaymentNo
idempotencyKey := callback.PaymentNo + ":" + providerCode
log, _, err := h.integration.RecordInbound(ctx, integrationlog.InboundAttempt{
IdempotencyKey: idempotencyKey, Provider: callback.Provider,
Operation: constants.IntegrationOperationPaymentCallback, ExternalID: callback.TransactionID,
ResourceType: constants.IntegrationResourceTypePayment, ResourceKey: &resourceKey,
RawPayload: callback.RawPayload, ContentType: callback.ContentType,
RequestID: pkgmiddleware.GetRequestIDFromContext(ctx), CorrelationID: &correlationID,
})
if err != nil || log.Result != constants.IntegrationResultPending {
return err
}
_, err = h.integration.Complete(ctx, log.IntegrationID, integrationlog.Completion{
Result: constants.IntegrationResultIgnored, ProviderCode: providerCode,
ResponseSummary: map[string]any{"confirmed": false},
})
return err
}
func (h *PaymentHandler) completePaymentCallbackLog(ctx context.Context, log *model.IntegrationLog, completion integrationlog.Completion) {
if log == nil || log.Result != constants.IntegrationResultPending {
return
@@ -346,6 +455,12 @@ func (h *PaymentHandler) AlipayCallback(c *fiber.Ctx) error {
zap.String("out_trade_no", outTradeNo),
zap.String("trade_status", tradeStatus),
)
if err := h.recordIgnoredPaymentCallback(ctx, verifiedPaymentCallback{
PaymentNo: outTradeNo, TransactionID: notification.TradeNo,
Provider: constants.IntegrationProviderAlipay, RawPayload: c.Body(), ContentType: c.Get("Content-Type"),
}, tradeStatus); err != nil {
return err
}
return c.SendString("success")
}
@@ -409,57 +524,13 @@ func (h *PaymentHandler) AlipayCallback(c *fiber.Ctx) error {
return errors.New(errors.CodeWechatCallbackInvalid, "支付金额校验失败")
}
// 新代理充值由统一确认事务原子写入交易号,旧业务保持原处理方式。
if payment.OrderType != model.PaymentOrderTypeAgentRecharge {
if err := h.paymentStore.UpdatePaymentInfo(ctx, payment.ID, notification.TradeNo, nil); err != nil {
h.logger.Error("支付宝回调:写入 third_party_trade_no 失败",
zap.String("out_trade_no", outTradeNo),
zap.String("trade_no", notification.TradeNo),
zap.Error(err),
)
// 不中断存量业务,继续由原幂等业务层处理状态。
}
callback := verifiedPaymentCallback{
PaymentNo: outTradeNo, PaymentMethod: model.PaymentByAlipay,
TransactionID: notification.TradeNo, Amount: notifyAmountFen, ConfigID: cfg.ID,
MerchantIdentity: notification.AppId, Provider: constants.IntegrationProviderAlipay,
RawPayload: c.Body(), ContentType: c.Get("Content-Type"),
}
// 按支付单 order_type 分发业务
switch payment.OrderType {
case model.PaymentOrderTypePackage:
if err := h.orderService.HandlePaymentRecordCallback(ctx, outTradeNo, model.PaymentByAlipay, notification.TradeNo, payment.Amount); err != nil {
h.logger.Error("支付宝回调:推进套餐订单失败",
zap.String("out_trade_no", outTradeNo),
zap.String("trade_no", notification.TradeNo),
zap.Error(err),
)
return errors.Wrap(errors.CodeInternalError, err, "处理支付宝支付回调失败")
}
h.logger.Info("支付宝回调:套餐订单支付成功",
zap.String("out_trade_no", outTradeNo),
zap.String("trade_no", notification.TradeNo),
zap.String("order_type", payment.OrderType),
)
case model.PaymentOrderTypeRecharge:
if h.rechargeOrderService == nil {
h.logger.Error("支付宝回调:充值订单服务未配置",
zap.String("out_trade_no", outTradeNo),
)
return errors.New(errors.CodeInternalError, "充值订单服务未配置")
}
if err := h.rechargeOrderService.HandlePaymentCallback(ctx, outTradeNo, model.PaymentByAlipay, notification.TradeNo); err != nil {
h.logger.Error("支付宝回调:推进充值订单失败",
zap.String("out_trade_no", outTradeNo),
zap.String("trade_no", notification.TradeNo),
zap.Error(err),
)
return errors.Wrap(errors.CodeInternalError, err, "处理支付宝充值回调失败")
}
h.logger.Info("支付宝回调:充值订单支付成功",
zap.String("out_trade_no", outTradeNo),
zap.String("trade_no", notification.TradeNo),
zap.String("order_type", payment.OrderType),
)
case model.PaymentOrderTypeAgentRecharge:
if payment.OrderType == model.PaymentOrderTypeAgentRecharge {
paidAt, parseErr := time.ParseInLocation("2006-01-02 15:04:05", notification.GmtPayment, time.Local)
if parseErr != nil {
h.logger.Error("支付宝回调:付款时间格式无效",
@@ -468,25 +539,13 @@ func (h *PaymentHandler) AlipayCallback(c *fiber.Ctx) error {
)
return errors.New(errors.CodeWechatCallbackInvalid, "支付宝付款时间格式错误")
}
if err := h.confirmAgentRechargePayment(ctx, verifiedPaymentCallback{
PaymentNo: outTradeNo, PaymentMethod: model.PaymentByAlipay,
TransactionID: notification.TradeNo, Amount: notifyAmountFen, ConfigID: cfg.ID,
MerchantIdentity: notification.AppId, PaidAt: paidAt, Provider: constants.IntegrationProviderAlipay,
RawPayload: c.Body(), ContentType: c.Get("Content-Type"),
}); err != nil {
h.logger.Error("支付宝回调:确认代理充值支付失败",
zap.String("out_trade_no", outTradeNo),
zap.Error(err),
)
return errors.Wrap(errors.CodeInternalError, err, "处理支付宝代理充值回调失败")
}
default:
h.logger.Error("支付宝回调:未知支付记录类型",
zap.String("out_trade_no", outTradeNo),
zap.String("order_type", payment.OrderType),
)
return errors.New(errors.CodeInternalError, "未知支付记录类型")
callback.PaidAt = paidAt
}
if handled, dispatchErr := h.dispatchPaymentRecordCallback(ctx, callback); dispatchErr != nil {
h.logger.Error("支付宝回调:确认支付失败", zap.String("out_trade_no", outTradeNo), zap.Error(dispatchErr))
return errors.Wrap(errors.CodeInternalError, dispatchErr, "处理支付宝支付回调失败")
} else if !handled {
return errors.New(errors.CodeInternalError, "支付记录分发失败")
}
return c.SendString("success")
@@ -563,6 +622,12 @@ func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error {
h.logger.Warn("富友回调:非成功结果",
zap.String("result_code", notify.ResultCode),
zap.String("result_msg", notify.ResultMsg))
if recordErr := h.recordIgnoredPaymentCallback(ctx, verifiedPaymentCallback{
PaymentNo: notify.MchntOrderNo, TransactionID: notify.TransactionId,
Provider: constants.IntegrationProviderFuiou, RawPayload: body, ContentType: c.Get("Content-Type"),
}, notify.ResultCode); recordErr != nil {
return c.Send(fuiou.BuildNotifyFailResponse("integration log failed"))
}
return c.Send(fuiou.BuildNotifySuccessResponse())
}
h.logger.Error("富友回调:验签或解析失败",
@@ -581,9 +646,10 @@ func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error {
orderNo := notify.MchntOrderNo
// OrderAmt 为字符串格式的分,解析失败则降级为 0
orderAmt, _ := strconv.ParseInt(notify.OrderAmt, 10, 64)
paidAt, _ := time.ParseInLocation("20060102150405", notify.TxnFinTs, time.Local)
if handled, err := h.dispatchPaymentRecordCallback(ctx, verifiedPaymentCallback{
PaymentNo: orderNo, PaymentMethod: "fuiou", TransactionID: notify.TransactionId, Amount: orderAmt,
ConfigID: cfg.ID, MerchantIdentity: cfg.FyMchntCd, Provider: model.ProviderTypeFuiou,
ConfigID: cfg.ID, MerchantIdentity: cfg.FyMchntCd, PaidAt: paidAt, Provider: constants.IntegrationProviderFuiou,
RawPayload: body, ContentType: c.Get("Content-Type"),
}); err != nil {
return c.Send(fuiou.BuildNotifyFailResponse(err.Error()))
@@ -591,27 +657,11 @@ func (h *PaymentHandler) FuiouPayCallback(c *fiber.Ctx) error {
return c.Send(fuiou.BuildNotifySuccessResponse())
}
switch {
case strings.HasPrefix(orderNo, "ORD"):
if err := h.orderService.HandlePaymentCallback(ctx, orderNo, "fuiou", orderAmt); err != nil {
return c.Send(fuiou.BuildNotifyFailResponse(err.Error()))
}
case strings.HasPrefix(orderNo, constants.AssetRechargeOrderPrefix):
if h.rechargeOrderService != nil {
if err := h.rechargeOrderService.HandlePaymentCallback(ctx, orderNo, "fuiou", notify.TransactionId); err != nil {
return c.Send(fuiou.BuildNotifyFailResponse(err.Error()))
}
return c.Send(fuiou.BuildNotifySuccessResponse())
}
return c.Send(fuiou.BuildNotifyFailResponse("充值订单服务未配置"))
case strings.HasPrefix(orderNo, constants.AgentRechargeOrderPrefix):
if h.agentRechargeService != nil {
if err := h.agentRechargeService.HandlePaymentCallback(ctx, orderNo, model.ProviderTypeFuiou, notify.TransactionId, orderAmt); err != nil {
return c.Send(fuiou.BuildNotifyFailResponse(err.Error()))
}
}
default:
return c.Send(fuiou.BuildNotifyFailResponse("unknown order prefix"))
if err := h.dispatchLegacyPaymentCallback(ctx, verifiedPaymentCallback{
PaymentNo: orderNo, PaymentMethod: model.ProviderTypeFuiou, TransactionID: notify.TransactionId, Amount: orderAmt,
Provider: constants.IntegrationProviderFuiou, RawPayload: body, ContentType: c.Get("Content-Type"),
}); err != nil {
return c.Send(fuiou.BuildNotifyFailResponse(err.Error()))
}
return c.Send(fuiou.BuildNotifySuccessResponse())