package handler import ( "strconv" "affiliate_dash/internal/middleware" "affiliate_dash/internal/model" "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"` NotifyIntervalMinutes int `json:"notify_interval_minutes"` MaxNotifications int `json:"max_notifications"` } // 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, NotifyIntervalMinutes: req.NotifyIntervalMinutes, MaxNotifications: req.MaxNotifications, }) if err != nil { response.BadRequest(c, err.Error()) return } response.OK(c, cfg) } // ListAlertChannels GET /api/merchant/alert-channels func (h *RechargeHandler) ListAlertChannels(c *gin.Context) { list, err := h.rechargeSvc.ListAlertChannels(middleware.GetMerchantID(c)) if err != nil { response.ServerError(c, err.Error()) return } response.OK(c, list) } type alertChannelReq struct { ChannelType string `json:"channel_type" binding:"required"` Name string `json:"name"` WebhookURL string `json:"webhook_url"` Server string `json:"server"` Key string `json:"key"` Enabled *bool `json:"enabled"` } func (r alertChannelReq) toInput() service.AlertChannelInput { enabled := true if r.Enabled != nil { enabled = *r.Enabled } return service.AlertChannelInput{ ChannelType: r.ChannelType, Name: r.Name, Enabled: enabled, Config: model.AlertChannelConfig{ WebhookURL: r.WebhookURL, Server: r.Server, Key: r.Key, }, } } // CreateAlertChannel POST /api/merchant/alert-channels func (h *RechargeHandler) CreateAlertChannel(c *gin.Context) { var req alertChannelReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "参数错误:channel_type 必填") return } channel, err := h.rechargeSvc.CreateAlertChannel(middleware.GetMerchantID(c), req.toInput()) if err != nil { response.BadRequest(c, err.Error()) return } response.OK(c, channel) } // UpdateAlertChannel PUT /api/merchant/alert-channels/:id func (h *RechargeHandler) UpdateAlertChannel(c *gin.Context) { id, _ := strconv.ParseUint(c.Param("id"), 10, 64) if id == 0 { response.BadRequest(c, "渠道 ID 无效") return } var req alertChannelReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "参数错误:channel_type 必填") return } channel, err := h.rechargeSvc.UpdateAlertChannel(middleware.GetMerchantID(c), uint(id), req.toInput()) if err != nil { response.BadRequest(c, err.Error()) return } response.OK(c, channel) } // DeleteAlertChannel DELETE /api/merchant/alert-channels/:id func (h *RechargeHandler) DeleteAlertChannel(c *gin.Context) { id, _ := strconv.ParseUint(c.Param("id"), 10, 64) if id == 0 { response.BadRequest(c, "渠道 ID 无效") return } if err := h.rechargeSvc.DeleteAlertChannel(middleware.GetMerchantID(c), uint(id)); err != nil { response.ServerError(c, err.Error()) return } response.OK(c, nil) } // TestAlertChannel POST /api/merchant/alert-channels/:id/test func (h *RechargeHandler) TestAlertChannel(c *gin.Context) { id, _ := strconv.ParseUint(c.Param("id"), 10, 64) if id == 0 { response.BadRequest(c, "渠道 ID 无效") return } if err := h.rechargeSvc.TestAlertChannel(middleware.GetMerchantID(c), uint(id)); err != nil { response.BadRequest(c, err.Error()) return } response.OK(c, nil) } // 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}) }