package handler import ( "strconv" "affiliate_dash/internal/middleware" "affiliate_dash/internal/pkg/response" "affiliate_dash/internal/service" "github.com/gin-gonic/gin" ) const uploadMaxBytes = 5 * 1024 * 1024 // 单张图片最大 5MB // RechargeHandler 提供积分充值申请(商户侧)与审核入账(管理员侧)能力。 type RechargeHandler struct { rechargeSvc *service.RechargeService } func NewRechargeHandler(rechargeSvc *service.RechargeService) *RechargeHandler { return &RechargeHandler{rechargeSvc: rechargeSvc} } type rechargeCreateReq struct { AmountCNY int64 `json:"amount_cny" binding:"required"` // 人民币,单位:分 Vouchers []string `json:"vouchers" binding:"required"` Note string `json:"note"` } // CreateApplication POST /api/merchant/recharge/applications func (h *RechargeHandler) CreateApplication(c *gin.Context) { var req rechargeCreateReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "参数错误:amount_cny 与 vouchers 必填") return } app, err := h.rechargeSvc.CreateRecharge(service.CreateRechargeInput{ MerchantID: middleware.GetMerchantID(c), ActorUserID: middleware.GetUserID(c), AmountCNY: req.AmountCNY, Vouchers: req.Vouchers, Note: req.Note, }) if err != nil { response.BadRequest(c, err.Error()) return } response.OK(c, app) } // ListApplications GET /api/merchant/recharge/applications(当前商户) func (h *RechargeHandler) ListApplications(c *gin.Context) { page, size := pageParams(c) list, total, err := h.rechargeSvc.ListRechargeApplications(middleware.GetMerchantID(c), page, size, c.Query("status")) if err != nil { response.ServerError(c, err.Error()) return } response.Page(c, list, total, page, size) } // AdminListApplications GET /api/platform/recharge/applications(全部商户) func (h *RechargeHandler) AdminListApplications(c *gin.Context) { page, size := pageParams(c) list, total, err := h.rechargeSvc.ListAllRechargeApplications(page, size, c.Query("status")) if err != nil { response.ServerError(c, err.Error()) return } response.Page(c, list, total, page, size) } type rechargeReviewReq struct { Approved bool `json:"approved" binding:"required"` ReviewNote string `json:"review_note"` } // AdminReviewApplication POST /api/platform/recharge/applications/:id/review // 通过后由后端按 1 元 = 100 积分自动入账到商户钱包,幂等键为申请单号。 func (h *RechargeHandler) AdminReviewApplication(c *gin.Context) { id, _ := strconv.ParseUint(c.Param("id"), 10, 64) if id == 0 { response.BadRequest(c, "申请 ID 无效") return } var req rechargeReviewReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "参数错误:approved 必填") return } app, err := h.rechargeSvc.ReviewRecharge(service.ReviewRechargeInput{ ApplicationID: uint(id), Approved: req.Approved, ReviewNote: req.ReviewNote, ActorUserID: middleware.GetUserID(c), }) if err != nil { response.BadRequest(c, err.Error()) return } h.rechargeSvc.CheckLowBalanceAndNotify(app.MerchantID) response.OK(c, app) } // GetAlertConfig GET /api/merchant/recharge/alert-config func (h *RechargeHandler) GetAlertConfig(c *gin.Context) { cfg, err := h.rechargeSvc.GetAlertConfig(middleware.GetMerchantID(c)) if err != nil { response.ServerError(c, err.Error()) return } response.OK(c, cfg) } type alertConfigReq struct { Enabled bool `json:"enabled"` ThresholdPoints int64 `json:"threshold_points"` WebhookURL string `json:"webhook_url"` } // SaveAlertConfig PUT /api/merchant/recharge/alert-config func (h *RechargeHandler) SaveAlertConfig(c *gin.Context) { var req alertConfigReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "参数错误") return } cfg, err := h.rechargeSvc.SaveAlertConfig(middleware.GetMerchantID(c), service.SaveAlertInput{ Enabled: req.Enabled, ThresholdPoints: req.ThresholdPoints, WebhookURL: req.WebhookURL, }) if err != nil { response.BadRequest(c, err.Error()) return } response.OK(c, cfg) } // Upload POST /api/upload 上传凭证图片(multipart 字段名 file)。 func (h *RechargeHandler) Upload(c *gin.Context) { fileHeader, err := c.FormFile("file") if err != nil { response.BadRequest(c, "请选择要上传的图片") return } file, err := fileHeader.Open() if err != nil { response.BadRequest(c, "读取上传文件失败") return } defer file.Close() url, err := h.rechargeSvc.SaveUploadFile(file, fileHeader.Filename, uploadMaxBytes) if err != nil { response.BadRequest(c, err.Error()) return } response.OK(c, gin.H{"url": url}) }