215 lines
6.1 KiB
Go
215 lines
6.1 KiB
Go
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,
|
||
})
|
||
}
|