Files
yunzerwebsiteallinone/go/controllers/platform_payment_reconcile.go
T
2026-09-15 12:46:29 +08:00

189 lines
6.1 KiB
Go

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})
}