重构资源创建逻辑,整合支付宝,优化套餐生成与交易处理
This commit is contained in:
@@ -1,16 +1,24 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"platform/pkg/rds"
|
||||
"platform/pkg/u"
|
||||
"platform/web/auth"
|
||||
"platform/web/common"
|
||||
g "platform/web/globals"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
"platform/web/services"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
)
|
||||
|
||||
// region ListResourcePss
|
||||
@@ -26,7 +34,7 @@ type ListResourcePssReq struct {
|
||||
ExpireBefore *time.Time `json:"expire_before"`
|
||||
}
|
||||
|
||||
// ListResourcePss 获取资源列表
|
||||
// ListResourcePss 获取套餐列表
|
||||
func ListResourcePss(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
@@ -40,7 +48,7 @@ func ListResourcePss(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 查询资源列表
|
||||
// 查询套餐列表
|
||||
do := q.Resource.
|
||||
Joins(q.Resource.Pss).
|
||||
Where(q.Resource.UserID.Eq(authContext.Payload.Id))
|
||||
@@ -107,7 +115,7 @@ func AllResource(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 查询资源列表
|
||||
// 查询套餐列表
|
||||
pss := q.ResourcePss.As(q.Resource.Pss.Name())
|
||||
do := q.Resource.Debug().
|
||||
Joins(q.Resource.Pss).
|
||||
@@ -140,9 +148,9 @@ func AllResource(c *fiber.Ctx) error {
|
||||
|
||||
// endregion
|
||||
|
||||
// region CreateResourceByBalance
|
||||
// region CreateResource
|
||||
|
||||
type CreateResourceByBalanceReq struct {
|
||||
type CreateResourceReq struct {
|
||||
Type int32 `json:"type" validate:"required"`
|
||||
Live int32 `json:"live" validate:"required"`
|
||||
Expire int32 `json:"expire" validate:"required"`
|
||||
@@ -150,8 +158,16 @@ type CreateResourceByBalanceReq struct {
|
||||
DailyLimit int32 `json:"daily_limit" validate:"required"`
|
||||
}
|
||||
|
||||
// CreateResourceByBalance 通过余额创建资源
|
||||
func CreateResourceByBalance(c *fiber.Ctx) error {
|
||||
type CreateResourceResp struct {
|
||||
TradeNo string `json:"trade_no"`
|
||||
PayURL string `json:"pay_url"`
|
||||
}
|
||||
|
||||
type PaidCreateResourceReq struct {
|
||||
TradeNo string `json:"trade_no" validate:"required"`
|
||||
}
|
||||
|
||||
func PrepareResourceByAlipay(c *fiber.Ctx) error {
|
||||
|
||||
// 检查权限
|
||||
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
@@ -160,66 +176,268 @@ func CreateResourceByBalance(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(CreateResourceByBalanceReq)
|
||||
req := new(CreateResourceReq)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 生成订单
|
||||
amount, tradeNo, err := prepareResource(c.Context(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 调用外部接口
|
||||
alipayResp, err := g.Alipay.TradePagePay(alipay.TradePagePay{
|
||||
QRPayMode: "4",
|
||||
Trade: alipay.Trade{
|
||||
OutTradeNo: tradeNo,
|
||||
TotalAmount: strconv.FormatFloat(amount, 'f', 2, 64),
|
||||
Subject: "购买套餐",
|
||||
ProductCode: "FAST_INSTANT_TRADE_PAY",
|
||||
TimeExpire: time.Now().Add(30 * time.Minute).Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存交易信息
|
||||
err = savePrepareResource(c.Context(), req, amount, tradeNo, authContext.Payload.Id, 1)
|
||||
|
||||
// 返回结果
|
||||
return c.JSON(CreateResourceResp{
|
||||
TradeNo: tradeNo,
|
||||
PayURL: alipayResp.String(),
|
||||
})
|
||||
}
|
||||
|
||||
func PrepareResourceByWechat(c *fiber.Ctx) error {
|
||||
|
||||
// 检查权限
|
||||
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(CreateResourceReq)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 生成订单
|
||||
amount, tradeNo, err := prepareResource(c.Context(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 调用外部接口
|
||||
alipayResp, err := g.Alipay.TradePagePay(alipay.TradePagePay{
|
||||
QRPayMode: "3",
|
||||
Trade: alipay.Trade{
|
||||
OutTradeNo: tradeNo,
|
||||
TotalAmount: strconv.FormatFloat(amount, 'f', 2, 64),
|
||||
Subject: "购买套餐",
|
||||
ProductCode: "FAST_INSTANT_TRADE_PAY",
|
||||
TimeExpire: time.Now().Add(30 * time.Minute).Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存交易信息
|
||||
err = savePrepareResource(c.Context(), req, amount, tradeNo, authContext.Payload.Id, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return c.JSON(CreateResourceResp{
|
||||
TradeNo: tradeNo,
|
||||
PayURL: alipayResp.String(),
|
||||
})
|
||||
}
|
||||
|
||||
func CreateResourceByAlipay(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(PaidCreateResourceReq)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证支付结果
|
||||
alipayResp, err := g.Alipay.TradeQuery(c.Context(), alipay.TradeQuery{
|
||||
OutTradeNo: req.TradeNo,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if alipayResp.TradeStatus != "TRADE_SUCCESS" {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "支付未完成,请确认后重试")
|
||||
}
|
||||
|
||||
payment, err := strconv.ParseFloat(alipayResp.ReceiptAmount, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paidAt, err := time.Parse("2006-01-02 15:04:05", alipayResp.SendPayDate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取请求缓存
|
||||
reqStr, err := rds.Client.GetDel(c.Context(), req.TradeNo).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqCreate := new(CreateResourceReq)
|
||||
if err := json.Unmarshal([]byte(reqStr), reqCreate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存交易信息
|
||||
err = q.Q.Transaction(func(q *q.Query) error {
|
||||
|
||||
// 保存套餐
|
||||
resource, err := saveResourceBalance(reqCreate, authCtx.Payload.Id, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
_, err = q.Trade.
|
||||
Where(q.Trade.InnerNo).
|
||||
Select(q.Trade.OuterNo, q.Trade.Payment, q.Trade.Status, q.Trade.PaidAt).
|
||||
Updates(&m.Trade{
|
||||
OuterNo: alipayResp.TradeNo,
|
||||
Payment: payment,
|
||||
Status: 1,
|
||||
PaidAt: common.LocalDateTime(paidAt),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新账单状态
|
||||
_, err = q.Bill.
|
||||
Where(q.Bill.TradeID.Eq(resource.ID)).
|
||||
Updates(&m.Bill{
|
||||
ResourceID: resource.ID,
|
||||
Status: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateResourceByWechat(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(CreateResourceReq)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = q.Q.Transaction(func(q *q.Query) error {
|
||||
// 检查用户
|
||||
user, err := q.User.Where(q.User.ID.Eq(authContext.Payload.Id)).Take()
|
||||
|
||||
// 保存套餐
|
||||
resource, err := saveResourceBalance(req, authCtx.Payload.Id, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 计算价格
|
||||
var amount = 100
|
||||
var payment = 100
|
||||
// 更新订单状态
|
||||
// _, err = q.Trade.
|
||||
// Where(q.Trade.InnerNo).
|
||||
// Select(q.Trade.OuterNo, q.Trade.Payment, q.Trade.Status, q.Trade.PaidAt).
|
||||
// Updates(&m.Trade{
|
||||
// OuterNo: alipayResp.TradeNo,
|
||||
// Payment: payment,
|
||||
// Status: 1,
|
||||
// PaidAt: common.LocalDateTime(paidAt),
|
||||
// })
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// 检查余额
|
||||
if user.Balance < float64(amount)/100 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "余额不足")
|
||||
}
|
||||
|
||||
// 更新用户余额
|
||||
user.Balance -= float64(payment)
|
||||
_, err = q.User.
|
||||
Where(q.User.ID.Eq(authContext.Payload.Id)).
|
||||
Update(q.User.Balance, user.Balance)
|
||||
// 更新账单状态
|
||||
_, err = q.Bill.
|
||||
Where(q.Bill.TradeID.Eq(resource.ID)).
|
||||
Updates(&m.Bill{
|
||||
ResourceID: resource.ID,
|
||||
Status: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建资源
|
||||
resource := m.Resource{
|
||||
UserID: authContext.Payload.Id,
|
||||
ResourceNo: services.ID.GenReadable("res"),
|
||||
}
|
||||
err = q.Resource.Create(&resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resourcePss := m.ResourcePss{
|
||||
ResourceID: resource.ID,
|
||||
Type: req.Type,
|
||||
Live: req.Live,
|
||||
Quota: req.Quota,
|
||||
Expire: common.LocalDateTime(time.Now().Add(time.Duration(req.Expire) * time.Second)),
|
||||
DailyLimit: req.DailyLimit,
|
||||
}
|
||||
err = q.ResourcePss.Create(&resourcePss)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateResourceByBalance(c *fiber.Ctx) error {
|
||||
|
||||
// 检查权限
|
||||
authCtx, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(CreateResourceReq)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 计算价格
|
||||
var amount = calcResourcePrice(req)
|
||||
|
||||
// 保存交易信息
|
||||
err = q.Q.Transaction(func(q *q.Query) error {
|
||||
|
||||
// 保存套餐
|
||||
resource, err := saveResourceBalance(req, authCtx.Payload.Id, amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 生成账单
|
||||
bill := m.Bill{
|
||||
UserID: authContext.Payload.Id,
|
||||
UserID: authCtx.Payload.Id,
|
||||
ResourceID: resource.ID,
|
||||
BillNo: services.ID.GenReadable("bil"),
|
||||
Info: "购买套餐",
|
||||
Info: "购买套餐 - " + resourceName(req),
|
||||
Type: 1,
|
||||
Status: 1,
|
||||
Amount: amount,
|
||||
}
|
||||
err = q.Bill.
|
||||
Omit(q.Bill.TradeID, q.Bill.RefundID).
|
||||
@@ -237,6 +455,141 @@ func CreateResourceByBalance(c *fiber.Ctx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func calcResourcePrice(req *CreateResourceReq) float64 {
|
||||
return 100
|
||||
}
|
||||
|
||||
func prepareResource(ctx context.Context, req *CreateResourceReq) (amount float64, tradeNo string, err error) {
|
||||
|
||||
// todo 计算价格
|
||||
amount = calcResourcePrice(req)
|
||||
|
||||
// 生成订单号
|
||||
tradeNoUint, err := services.ID.GenSerial(ctx)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
tradeNo = strconv.FormatUint(tradeNoUint, 10)
|
||||
|
||||
return amount, tradeNo, nil
|
||||
}
|
||||
|
||||
func savePrepareResource(ctx context.Context, req *CreateResourceReq, amount float64, tradeNo string, uid int32, method int32) error {
|
||||
// 缓存交易信息
|
||||
reqStr, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = rds.Client.Set(ctx, tradeNo, reqStr, 30*time.Minute).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
err = q.Q.Transaction(func(q *q.Query) error {
|
||||
// 创建交易订单
|
||||
var trade = m.Trade{
|
||||
UserID: uid,
|
||||
InnerNo: tradeNo,
|
||||
Subject: "购买套餐 - " + resourceName(req),
|
||||
Method: method,
|
||||
Type: 1,
|
||||
Status: 0,
|
||||
Amount: amount,
|
||||
}
|
||||
err = q.Trade.Create(&trade)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存用户帐单
|
||||
bill := m.Bill{
|
||||
UserID: uid,
|
||||
TradeID: trade.ID,
|
||||
BillNo: services.ID.GenReadable("bil"),
|
||||
Info: "购买产品",
|
||||
Type: 1,
|
||||
Status: 0,
|
||||
Amount: -amount,
|
||||
}
|
||||
err = q.Bill.
|
||||
Omit(q.Bill.ResourceID, q.Bill.RefundID).
|
||||
Create(&bill)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveResourceBalance(req *CreateResourceReq, uid int32, amount float64) (*m.Resource, error) {
|
||||
// 检查用户
|
||||
user, err := q.User.
|
||||
Where(
|
||||
q.User.ID.Eq(uid),
|
||||
q.User.Status.Eq(1),
|
||||
).
|
||||
Take()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查余额
|
||||
if user.Balance < amount {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "余额不足")
|
||||
}
|
||||
|
||||
// 创建套餐
|
||||
resource := &m.Resource{
|
||||
UserID: user.ID,
|
||||
ResourceNo: services.ID.GenReadable("res"),
|
||||
Active: true,
|
||||
Type: 1,
|
||||
Pss: &m.ResourcePss{
|
||||
Type: req.Type,
|
||||
Live: req.Live,
|
||||
Quota: req.Quota,
|
||||
Expire: common.LocalDateTime(time.Now().Add(time.Duration(req.Expire) * time.Second)),
|
||||
DailyLimit: req.DailyLimit,
|
||||
},
|
||||
}
|
||||
err = q.Resource.Create(resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新用户余额
|
||||
user.Balance -= amount
|
||||
_, err = q.User.
|
||||
Where(q.User.ID.Eq(uid)).
|
||||
Update(q.User.Balance, user.Balance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
func resourceName(req *CreateResourceReq) string {
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString("短效动态")
|
||||
switch req.Type {
|
||||
case 1:
|
||||
sb.WriteString("包时 ")
|
||||
case 2:
|
||||
sb.WriteString("包量 ")
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%d 分钟", req.Live/60))
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region CreateResourceByAlipayCallback
|
||||
@@ -251,7 +604,7 @@ func CreateResourceByAlipayCallback(c *fiber.Ctx) error {
|
||||
// 1. 支付宝或微信(即时支付)
|
||||
// - 更新订单状态
|
||||
// - 生成账单
|
||||
// - 生成资源
|
||||
// - 生成套餐
|
||||
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user