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

215 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
})
}