package controllers import ( "context" "encoding/json" "fmt" "io" "strconv" "strings" "time" "server/models" "server/pkg/jwtutil" "server/services/payment" beego "github.com/beego/beego/v2/server/web" ) // PlatformPaymentReconcileController 平台端对账管理(差异列表 / 标记处理 / 账单导入 / 执行对账) type PlatformPaymentReconcileController struct { beego.Controller } func (c *PlatformPaymentReconcileController) platformClaims() (*jwtutil.Claims, error) { return paymentPlatformClaims(&c.Controller) } func (c *PlatformPaymentReconcileController) jsonErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func (c *PlatformPaymentReconcileController) ok(data interface{}) { c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} _ = c.ServeJSON() } func reconcileDiffDTO(row *models.PlatformPaymentReconcileDiff) map[string]interface{} { return map[string]interface{}{ "id": row.ID, "channel": row.Channel, "bill_date": row.BillDate, "batch_no": row.BatchNo, "local_trade_no": row.LocalTradeNo, "channel_trade_no": row.ChannelTradeNo, "local_amount": row.LocalAmount, "channel_amount": row.ChannelAmount, "diff_amount": row.DiffAmount, "diff_type": row.DiffType, "handle_status": row.HandleStatus, "handle_user_id": row.HandleUserID, "handle_user_name": row.HandleUserName, "handle_time": row.HandleTime, "remark": row.Remark, "create_time": row.CreateTime, "update_time": row.UpdateTime, } } // ListReconcile GET /platform/payment/reconcile 对账差异列表 func (c *PlatformPaymentReconcileController) ListReconcile() { if _, err := c.platformClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } page, _ := strconv.Atoi(c.GetString("page", "1")) pageSize, _ := strconv.Atoi(c.GetString("pageSize", "10")) rows, total, err := payment.ListReconcileDiffs(payment.ReconcileListInput{ Channel: strings.TrimSpace(c.GetString("channel")), BillDate: strings.TrimSpace(c.GetString("bill_date")), DiffType: strings.TrimSpace(c.GetString("diff_type")), HandleStatus: strings.TrimSpace(c.GetString("handle_status")), Page: page, PageSize: pageSize, }) if err != nil { c.jsonErr(500, 500, "查询失败: "+err.Error()) return } if rows == nil { rows = []models.PlatformPaymentReconcileDiff{} } list := make([]map[string]interface{}, 0, len(rows)) for i := range rows { list = append(list, reconcileDiffDTO(&rows[i])) } c.ok(map[string]interface{}{"list": list, "total": total, "page": page, "pageSize": pageSize}) } // HandleReconcile POST /platform/payment/reconcile/:id/handle 标记差异处理 func (c *PlatformPaymentReconcileController) HandleReconcile() { claims, err := c.platformClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.jsonErr(400, 400, "无效ID") return } body, _ := io.ReadAll(c.Ctx.Request.Body) var p struct { HandleStatus string `json:"handle_status"` Remark string `json:"remark"` } if err := json.Unmarshal(body, &p); err != nil { c.jsonErr(400, 400, "参数错误") return } row, err := payment.MarkDiffHandled(context.Background(), payment.MarkDiffHandledInput{ ID: id, HandleStatus: strings.TrimSpace(p.HandleStatus), Remark: strings.TrimSpace(p.Remark), OperatorID: fmt.Sprintf("%d", claims.UserID), OperatorName: claims.Username, }) if err != nil { c.jsonErr(400, 400, err.Error()) return } c.ok(reconcileDiffDTO(row)) } // RunReconcile POST /platform/payment/reconcile/run 执行对账比对 func (c *PlatformPaymentReconcileController) RunReconcile() { if _, err := c.platformClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } body, _ := io.ReadAll(c.Ctx.Request.Body) var p struct { Channel string `json:"channel"` BillDate string `json:"bill_date"` } if err := json.Unmarshal(body, &p); err != nil { c.jsonErr(400, 400, "参数错误") return } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() summary, err := payment.RunReconcile(ctx, strings.TrimSpace(p.Channel), strings.TrimSpace(p.BillDate)) if err != nil { c.jsonErr(400, 400, err.Error()) return } c.ok(summary) } // ImportBill POST /platform/payment/reconcile/import 导入渠道账单 // 两种方式:multipart 上传 CSV(字段 file + channel + bill_date),或 JSON {channel, bill_date, rows:[...]} func (c *PlatformPaymentReconcileController) ImportBill() { if _, err := c.platformClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } var channel, billDate string var rows []payment.BillRow contentType := c.Ctx.Request.Header.Get("Content-Type") if strings.Contains(contentType, "multipart/form-data") { channel = strings.TrimSpace(c.GetString("channel")) billDate = strings.TrimSpace(c.GetString("bill_date")) file, header, ferr := c.GetFile("file") if ferr != nil || header == nil { c.jsonErr(400, 400, "请选择账单文件") return } defer func() { _ = file.Close() }() data, rerr := io.ReadAll(file) if rerr != nil { c.jsonErr(400, 400, "读取账单文件失败: "+rerr.Error()) return } rows, _ = payment.ParseChannelBillCSV(data) } else { body, _ := io.ReadAll(c.Ctx.Request.Body) var p struct { Channel string `json:"channel"` BillDate string `json:"bill_date"` Rows []payment.BillRow `json:"rows"` } if err := json.Unmarshal(body, &p); err != nil { c.jsonErr(400, 400, "参数错误") return } channel, billDate, rows = strings.TrimSpace(p.Channel), strings.TrimSpace(p.BillDate), p.Rows } if channel == "" || billDate == "" { c.jsonErr(400, 400, "channel 与 bill_date 不能为空") return } imported, err := payment.ImportChannelBill(context.Background(), channel, billDate, rows) if err != nil { c.jsonErr(400, 400, err.Error()) return } c.ok(map[string]interface{}{"imported": imported, "channel": channel, "bill_date": billDate}) }