package controllers import ( "context" "encoding/json" "fmt" "io" "strconv" "strings" "time" "server/models" "server/pkg/jwtutil" "server/services/payment" "github.com/beego/beego/v2/client/orm" beego "github.com/beego/beego/v2/server/web" ) // BackendPaymentController 租户端支付接口(下单页 / 收银台) // 租户端身份为 jwt UserType=backend,TenantId 即租户ID。 type BackendPaymentController struct { beego.Controller } func (c *BackendPaymentController) backendClaims() (*jwtutil.Claims, error) { auth := c.Ctx.Request.Header.Get("Authorization") if auth == "" { return nil, fmt.Errorf("未登录") } parts := strings.SplitN(auth, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { return nil, fmt.Errorf("认证信息格式错误") } claims, err := jwtutil.ParseToken(parts[1]) if err != nil { return nil, fmt.Errorf("无效的token") } if claims.UserType != "backend" { return nil, fmt.Errorf("无权访问") } return claims, nil } func (c *BackendPaymentController) 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 *BackendPaymentController) ok(data interface{}) { c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} _ = c.ServeJSON() } func (c *BackendPaymentController) tenantID() string { claims, _ := c.backendClaims() if claims == nil { return "" } return strconv.Itoa(claims.TenantId) } // tenantName 查询租户名称(下单快照用) func (c *BackendPaymentController) tenantName() string { claims, err := c.backendClaims() if err != nil || claims.TenantId <= 0 { return "" } tenant := &models.SystemTenant{} if err := models.Orm.QueryTable(new(models.SystemTenant)). Filter("id", uint64(claims.TenantId)). Filter("delete_time__isnull", true). One(tenant); err != nil { return "" } return tenant.TenantName } // Channels GET /backend/payment/channels 收银台可用渠道(已启用且参数完整) func (c *BackendPaymentController) Channels() { if _, err := c.backendClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } var rows []models.PlatformPaymentChannel _, err := models.Orm.QueryTable(new(models.PlatformPaymentChannel)). Filter("enabled", 1). Filter("delete_time__isnull", true). OrderBy("id"). All(&rows) if err != nil && err != orm.ErrNoRows { c.jsonErr(500, 500, "查询渠道失败: "+err.Error()) return } list := make([]map[string]interface{}, 0, len(rows)) for i := range rows { cfg, err := payment.LoadChannelConfig(rows[i].Channel) if err != nil || len(cfg.Params) == 0 { continue // 参数不完整的渠道不下发 } extra := map[string]interface{}{} for k, v := range cfg.Extra { extra[k] = v } list = append(list, map[string]interface{}{ "channel": rows[i].Channel, "name": rows[i].Name, "extra": extra, }) } c.ok(map[string]interface{}{"list": list}) } // Create POST /backend/payment/create 创建支付单并返回渠道支付参数 // 入参:{ out_trade_no, order_type, amount(分), channel, subject, return_url, pay_type, openid, // // promoter_id?, promoter_name?, promoter_type?, expire_minutes? } // // 出参:{ pay_no, channel, pay_params } func (c *BackendPaymentController) Create() { claims, err := c.backendClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } body, _ := io.ReadAll(c.Ctx.Request.Body) var p struct { OutTradeNo string `json:"out_trade_no"` OrderType string `json:"order_type"` Amount int64 `json:"amount"` Channel string `json:"channel"` Subject string `json:"subject"` ReturnURL string `json:"return_url"` PayType string `json:"pay_type"` OpenID string `json:"openid"` PromoterID string `json:"promoter_id"` PromoterName string `json:"promoter_name"` PromoterType string `json:"promoter_type"` ExpireMinutes int `json:"expire_minutes"` } if err := json.Unmarshal(body, &p); err != nil { c.jsonErr(400, 400, "参数错误") return } if strings.TrimSpace(p.OutTradeNo) == "" { c.jsonErr(400, 400, "业务订单号不能为空") return } if p.Amount <= 0 { c.jsonErr(400, 400, "支付金额必须大于 0") return } if strings.TrimSpace(p.Channel) == "" { c.jsonErr(400, 400, "请选择支付渠道") return } if p.OrderType == "" { p.OrderType = models.CommissionOrderTypePlatformUsage } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() order, params, err := payment.CreatePayment(ctx, payment.CreateInput{ OutTradeNo: strings.TrimSpace(p.OutTradeNo), OrderType: p.OrderType, TenantID: strconv.Itoa(claims.TenantId), TenantName: c.tenantName(), Amount: p.Amount, Channel: strings.TrimSpace(p.Channel), Subject: strings.TrimSpace(p.Subject), ReturnURL: strings.TrimSpace(p.ReturnURL), ClientIP: c.Ctx.Input.IP(), PayType: p.PayType, OpenID: p.OpenID, PromoterID: strings.TrimSpace(p.PromoterID), PromoterName: strings.TrimSpace(p.PromoterName), PromoterType: strings.TrimSpace(p.PromoterType), ExpireMinutes: p.ExpireMinutes, }) if err != nil { c.jsonErr(400, 400, err.Error()) return } c.ok(map[string]interface{}{ "pay_no": order.PayNo, "channel": order.Channel, "status": order.Status, "pay_params": params, }) } // Query GET /backend/payment/query/:payNo 查询支付单(仅本租户) func (c *BackendPaymentController) Query() { if _, err := c.backendClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } payNo := c.Ctx.Input.Param(":payNo") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() row, err := payment.QueryPayment(ctx, payNo, true) if err != nil { c.jsonErr(404, 404, "支付单不存在") return } if row.TenantID != c.tenantID() { c.jsonErr(403, 403, "无权访问该支付单") return } c.ok(map[string]interface{}{ "pay_no": row.PayNo, "out_trade_no": row.OutTradeNo, "amount": row.Amount, "channel": row.Channel, "status": row.Status, "paid_at": row.PaidAt, "create_time": row.CreateTime, }) }