package handler import ( "encoding/json" "errors" "net/http" "github.com/gin-gonic/gin" "gorm.io/gorm" "github.com/wangjia/pay/internal/gateway" "github.com/wangjia/pay/internal/model" "github.com/wangjia/pay/internal/provider" "github.com/wangjia/pay/internal/util" ) // DevMockHandler is a dev-only handler (registered only when // config.C.MockChannelEnabled=true, see main.go) that lets local integration // testing simulate a successful payment on the fake channel with a single // HTTP request — no manual sqlite surgery or hand-crafted callback body needed. type DevMockHandler struct { db *gorm.DB g *gateway.Gateway } func NewDevMockHandler(db *gorm.DB, g *gateway.Gateway) *DevMockHandler { return &DevMockHandler{db: db, g: g} } // fakeCallbackBody mirrors provider/fake.Provider.VerifyCallback's expected JSON shape. type fakeCallbackBody struct { ProviderRef string `json:"provider_ref"` Status string `json:"status"` AmountMinor int64 `json:"amount_minor"` Currency string `json:"currency"` } // MarkPaid POST /api/v2/dev/orders/:order_no/mark-paid —— dev-only「模拟付款成功」。 // 按 out_trade_no 取该单在 fake 渠道最近一次 attempt(provider_ref + amount_minor/currency), // 原样组一个 fake 渠道回调体,内部直接走 gateway.HandleCallback("fake", ...)(与 // POST /api/v2/callback/fake 同一入口),触发 Settle→webhook 入队。金额/币种从 attempt // 读,不由调用方传,保证与订单一致、免手工对账。 func (h *DevMockHandler) MarkPaid(c *gin.Context) { orderNo := c.Param("order_no") var att model.Attempt err := h.db.Where("out_trade_no = ? AND channel = ?", orderNo, "fake"). Order("id DESC").First(&att).Error if errors.Is(err, gorm.ErrRecordNotFound) { util.RespondError(c, http.StatusNotFound, "order_not_found", "订单不存在或未走 fake 渠道下单") return } if err != nil { util.RespondError(c, http.StatusInternalServerError, "internal_error", "查询订单失败") return } body, err := json.Marshal(fakeCallbackBody{ ProviderRef: att.ProviderRef, Status: "succeeded", AmountMinor: att.AmountMinor, Currency: att.Currency, }) if err != nil { util.RespondError(c, http.StatusInternalServerError, "internal_error", "构造回调体失败") return } res, err := h.g.HandleCallback(c.Request.Context(), "fake", provider.CallbackInput{Raw: body}) if err != nil && res != gateway.SettleAmountMismatch { util.RespondError(c, http.StatusInternalServerError, "mark_paid_failed", "模拟付款失败: "+err.Error()) return } c.JSON(http.StatusOK, gin.H{"ok": true, "out_trade_no": orderNo, "result": string(res)}) }