增加支付功能

This commit is contained in:
2026-09-15 12:46:29 +08:00
parent 943c3708b0
commit a82b300b1a
46 changed files with 11675 additions and 21 deletions
+214
View File
@@ -0,0 +1,214 @@
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,
})
}