diff --git a/docs/superpowers/plans/2026-07-10-pay-v2-p2-pipeline.md b/docs/superpowers/plans/2026-07-10-pay-v2-p2-pipeline.md index 4c29e5c..b51a832 100644 --- a/docs/superpowers/plans/2026-07-10-pay-v2-p2-pipeline.md +++ b/docs/superpowers/plans/2026-07-10-pay-v2-p2-pipeline.md @@ -1321,7 +1321,9 @@ func (g *Gateway) Settle(ctx context.Context, ev *provider.PaidEvent) (SettleRes if errors.Is(err, store.ErrAttemptNotFound) { return SettleNotFound, nil } - return SettleNotFound, err + // 读库瞬时失败是可重试态(SettleFailed),不能与"查无此单"(终态,ack 即弃) + // 混淆——否则按 result 决定 ack 的调用方会让渠道停止重投,已付单永不入账。 + return SettleFailed, err } // 金额/币种核对:币种须一致,实付须 ≥ 应收(允许 crypto 多付,拒少付)。 if ev.PaidCurrency != att.Currency || ev.PaidAmountMinor < att.AmountMinor { @@ -2158,13 +2160,19 @@ func (h *GatewayHandler) Callback(c *gin.Context) { res, err := h.g.HandleCallback(c.Request.Context(), method, provider.CallbackInput{ Raw: raw, Headers: headers, }) - if err != nil { - // 验签失败等:回 400 让渠道按策略重投(或人工排障)。 + // 按 SettleResult(而非只看 err)分终态/可重试:amount_mismatch 是终态—— + // 重投同一回调永远解决不了,必须回 200 让渠道停投(差错单归 P6 对账/人工); + // SettleFailed/验签失败才是可重试态,回 400 让渠道重投。 + switch { + case err == nil: + c.JSON(http.StatusOK, gin.H{"result": string(res)}) + case res == gateway.SettleAmountMismatch: + log.Printf("[v2 callback] method=%s 金额/币种不符(终态,已受理停投): %v", method, err) + c.JSON(http.StatusOK, gin.H{"result": string(res)}) + default: log.Printf("[v2 callback] method=%s result=%s err=%v", method, res, err) util.RespondError(c, http.StatusBadRequest, "callback_failed", "回调处理失败") - return } - c.JSON(http.StatusOK, gin.H{"result": string(res)}) } func (h *GatewayHandler) writeCreateErr(c *gin.Context, action, method string, err error) {