重构资源创建逻辑,整合支付宝,优化套餐生成与交易处理
This commit is contained in:
14
README.md
14
README.md
@@ -8,10 +8,8 @@
|
|||||||
- [ ] jwt 签发
|
- [ ] jwt 签发
|
||||||
- [x] 鉴权
|
- [x] 鉴权
|
||||||
- [x] 实名认证
|
- [x] 实名认证
|
||||||
- [ ] 对接接口
|
- [x] 对接接口
|
||||||
- [x] 充值余额
|
- [x] 充值或购买
|
||||||
- [ ] 对接接口
|
|
||||||
- [x] 选择套餐
|
|
||||||
- [ ] 对接接口
|
- [ ] 对接接口
|
||||||
- [ ] 提取记录
|
- [ ] 提取记录
|
||||||
- [x] 提取 IP
|
- [x] 提取 IP
|
||||||
@@ -24,9 +22,11 @@
|
|||||||
- [ ] Limiter
|
- [ ] Limiter
|
||||||
- [ ] Compress
|
- [ ] Compress
|
||||||
|
|
||||||
统一简化包导入别名
|
统一套餐创建逻辑
|
||||||
|
|
||||||
迁移 pkg 包下的代码,尽量放置在 web/globals 下,env 和 log 作为全局公共配置保留
|
删除账单的状态字段,状态从关联表中计算获得
|
||||||
|
|
||||||
|
统一简化包导入别名
|
||||||
|
|
||||||
更新数据库填充
|
更新数据库填充
|
||||||
|
|
||||||
@@ -47,8 +47,6 @@ channel 优化:
|
|||||||
- 端口分配时加锁
|
- 端口分配时加锁
|
||||||
- 数据存入顺序,数据库 > 缓存 > 外部接口
|
- 数据存入顺序,数据库 > 缓存 > 外部接口
|
||||||
|
|
||||||
remote 令牌问题
|
|
||||||
|
|
||||||
用对称加密处理密钥
|
用对称加密处理密钥
|
||||||
|
|
||||||
考虑将鉴权逻辑放到 handler 里,统一动静态鉴权以及解耦服务层
|
考虑将鉴权逻辑放到 handler 里,统一动静态鉴权以及解耦服务层
|
||||||
|
|||||||
9
pkg/env/env.go
vendored
9
pkg/env/env.go
vendored
@@ -193,25 +193,26 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func loadAlipay() {
|
func loadAlipay() {
|
||||||
AlipayAppId := os.Getenv("ALIPAY_APP_ID")
|
AlipayAppId = os.Getenv("ALIPAY_APP_ID")
|
||||||
if AlipayAppId == "" {
|
if AlipayAppId == "" {
|
||||||
panic("环境变量 ALIPAY_APP_ID 的值不能为空")
|
panic("环境变量 ALIPAY_APP_ID 的值不能为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
AlipayAppPrivateKey := os.Getenv("ALIPAY_APP_PRIVATE_KEY")
|
AlipayAppPrivateKey = os.Getenv("ALIPAY_APP_PRIVATE_KEY")
|
||||||
if AlipayAppPrivateKey == "" {
|
if AlipayAppPrivateKey == "" {
|
||||||
panic("环境变量 ALIPAY_APP_PRIVATE_KEY 的值不能为空")
|
panic("环境变量 ALIPAY_APP_PRIVATE_KEY 的值不能为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
AlipayPublicKey := os.Getenv("ALIPAY_PUBLIC_KEY")
|
AlipayPublicKey = os.Getenv("ALIPAY_PUBLIC_KEY")
|
||||||
if AlipayPublicKey == "" {
|
if AlipayPublicKey == "" {
|
||||||
panic("环境变量 ALIPAY_PUBLIC_KEY 的值不能为空")
|
panic("环境变量 ALIPAY_PUBLIC_KEY 的值不能为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
AlipayEncryptKey := os.Getenv("ALIPAY_ENCRYPT_KEY")
|
AlipayEncryptKey = os.Getenv("ALIPAY_ENCRYPT_KEY")
|
||||||
if AlipayEncryptKey == "" {
|
if AlipayEncryptKey == "" {
|
||||||
panic("环境变量 ALIPAY_ENCRYPT_KEY 的值不能为空")
|
panic("环境变量 ALIPAY_ENCRYPT_KEY 的值不能为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
_AlipayProduction := os.Getenv("ALIPAY_PRODUCTION")
|
_AlipayProduction := os.Getenv("ALIPAY_PRODUCTION")
|
||||||
if _AlipayProduction != "" {
|
if _AlipayProduction != "" {
|
||||||
value, err := strconv.ParseBool(_AlipayProduction)
|
value, err := strconv.ParseBool(_AlipayProduction)
|
||||||
|
|||||||
@@ -99,13 +99,13 @@ func RemoveChannels(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
auth, ok := c.Locals("auth").(*services.AuthContext)
|
authCtx, ok := c.Locals("auth").(*services.AuthContext)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("user not found")
|
return errors.New("user not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除通道
|
// 删除通道
|
||||||
err := services.Channel.RemoveChannels(c.Context(), auth, req.ByIds...)
|
err := services.Channel.RemoveChannels(c.Context(), authCtx, req.ByIds...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ func CreateChannelGet(c *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
auth := &services.AuthContext{
|
authCtx := &services.AuthContext{
|
||||||
Payload: services.Payload{
|
Payload: services.Payload{
|
||||||
Id: user.ID,
|
Id: user.ID,
|
||||||
Type: services.PayloadUser,
|
Type: services.PayloadUser,
|
||||||
@@ -188,7 +188,7 @@ func CreateChannelGet(c *fiber.Ctx) error {
|
|||||||
// 建立连接通道
|
// 建立连接通道
|
||||||
result, err := services.Channel.CreateChannel(
|
result, err := services.Channel.CreateChannel(
|
||||||
c.Context(),
|
c.Context(),
|
||||||
auth,
|
authCtx,
|
||||||
req.ResourceId,
|
req.ResourceId,
|
||||||
req.Protocol,
|
req.Protocol,
|
||||||
req.AuthType,
|
req.AuthType,
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"platform/pkg/rds"
|
||||||
"platform/pkg/u"
|
"platform/pkg/u"
|
||||||
"platform/web/auth"
|
"platform/web/auth"
|
||||||
"platform/web/common"
|
"platform/web/common"
|
||||||
|
g "platform/web/globals"
|
||||||
m "platform/web/models"
|
m "platform/web/models"
|
||||||
q "platform/web/queries"
|
q "platform/web/queries"
|
||||||
"platform/web/services"
|
"platform/web/services"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/smartwalle/alipay/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// region ListResourcePss
|
// region ListResourcePss
|
||||||
@@ -26,7 +34,7 @@ type ListResourcePssReq struct {
|
|||||||
ExpireBefore *time.Time `json:"expire_before"`
|
ExpireBefore *time.Time `json:"expire_before"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListResourcePss 获取资源列表
|
// ListResourcePss 获取套餐列表
|
||||||
func ListResourcePss(c *fiber.Ctx) error {
|
func ListResourcePss(c *fiber.Ctx) error {
|
||||||
// 检查权限
|
// 检查权限
|
||||||
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||||
@@ -40,7 +48,7 @@ func ListResourcePss(c *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询资源列表
|
// 查询套餐列表
|
||||||
do := q.Resource.
|
do := q.Resource.
|
||||||
Joins(q.Resource.Pss).
|
Joins(q.Resource.Pss).
|
||||||
Where(q.Resource.UserID.Eq(authContext.Payload.Id))
|
Where(q.Resource.UserID.Eq(authContext.Payload.Id))
|
||||||
@@ -107,7 +115,7 @@ func AllResource(c *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询资源列表
|
// 查询套餐列表
|
||||||
pss := q.ResourcePss.As(q.Resource.Pss.Name())
|
pss := q.ResourcePss.As(q.Resource.Pss.Name())
|
||||||
do := q.Resource.Debug().
|
do := q.Resource.Debug().
|
||||||
Joins(q.Resource.Pss).
|
Joins(q.Resource.Pss).
|
||||||
@@ -140,9 +148,9 @@ func AllResource(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region CreateResourceByBalance
|
// region CreateResource
|
||||||
|
|
||||||
type CreateResourceByBalanceReq struct {
|
type CreateResourceReq struct {
|
||||||
Type int32 `json:"type" validate:"required"`
|
Type int32 `json:"type" validate:"required"`
|
||||||
Live int32 `json:"live" validate:"required"`
|
Live int32 `json:"live" validate:"required"`
|
||||||
Expire int32 `json:"expire" validate:"required"`
|
Expire int32 `json:"expire" validate:"required"`
|
||||||
@@ -150,8 +158,16 @@ type CreateResourceByBalanceReq struct {
|
|||||||
DailyLimit int32 `json:"daily_limit" validate:"required"`
|
DailyLimit int32 `json:"daily_limit" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateResourceByBalance 通过余额创建资源
|
type CreateResourceResp struct {
|
||||||
func CreateResourceByBalance(c *fiber.Ctx) error {
|
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{})
|
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 {
|
if err := c.BodyParser(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = q.Q.Transaction(func(q *q.Query) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算价格
|
// 更新订单状态
|
||||||
var amount = 100
|
// _, err = q.Trade.
|
||||||
var payment = 100
|
// 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 {
|
_, err = q.Bill.
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "余额不足")
|
Where(q.Bill.TradeID.Eq(resource.ID)).
|
||||||
}
|
Updates(&m.Bill{
|
||||||
|
ResourceID: resource.ID,
|
||||||
// 更新用户余额
|
Status: 1,
|
||||||
user.Balance -= float64(payment)
|
})
|
||||||
_, err = q.User.
|
|
||||||
Where(q.User.ID.Eq(authContext.Payload.Id)).
|
|
||||||
Update(q.User.Balance, user.Balance)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建资源
|
return nil
|
||||||
resource := m.Resource{
|
})
|
||||||
UserID: authContext.Payload.Id,
|
if err != nil {
|
||||||
ResourceNo: services.ID.GenReadable("res"),
|
return err
|
||||||
}
|
}
|
||||||
err = q.Resource.Create(&resource)
|
|
||||||
if err != nil {
|
return nil
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
resourcePss := m.ResourcePss{
|
func CreateResourceByBalance(c *fiber.Ctx) error {
|
||||||
ResourceID: resource.ID,
|
|
||||||
Type: req.Type,
|
// 检查权限
|
||||||
Live: req.Live,
|
authCtx, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||||
Quota: req.Quota,
|
if err != nil {
|
||||||
Expire: common.LocalDateTime(time.Now().Add(time.Duration(req.Expire) * time.Second)),
|
return err
|
||||||
DailyLimit: req.DailyLimit,
|
}
|
||||||
}
|
|
||||||
err = q.ResourcePss.Create(&resourcePss)
|
// 解析请求参数
|
||||||
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成账单
|
// 生成账单
|
||||||
bill := m.Bill{
|
bill := m.Bill{
|
||||||
UserID: authContext.Payload.Id,
|
UserID: authCtx.Payload.Id,
|
||||||
ResourceID: resource.ID,
|
ResourceID: resource.ID,
|
||||||
BillNo: services.ID.GenReadable("bil"),
|
BillNo: services.ID.GenReadable("bil"),
|
||||||
Info: "购买套餐",
|
Info: "购买套餐 - " + resourceName(req),
|
||||||
Type: 1,
|
Type: 1,
|
||||||
Status: 1,
|
Status: 1,
|
||||||
|
Amount: amount,
|
||||||
}
|
}
|
||||||
err = q.Bill.
|
err = q.Bill.
|
||||||
Omit(q.Bill.TradeID, q.Bill.RefundID).
|
Omit(q.Bill.TradeID, q.Bill.RefundID).
|
||||||
@@ -237,6 +455,141 @@ func CreateResourceByBalance(c *fiber.Ctx) error {
|
|||||||
return nil
|
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
|
// endregion
|
||||||
|
|
||||||
// region CreateResourceByAlipayCallback
|
// region CreateResourceByAlipayCallback
|
||||||
@@ -251,7 +604,7 @@ func CreateResourceByAlipayCallback(c *fiber.Ctx) error {
|
|||||||
// 1. 支付宝或微信(即时支付)
|
// 1. 支付宝或微信(即时支付)
|
||||||
// - 更新订单状态
|
// - 更新订单状态
|
||||||
// - 生成账单
|
// - 生成账单
|
||||||
// - 生成资源
|
// - 生成套餐
|
||||||
|
|
||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
"platform/web/auth"
|
|
||||||
g "platform/web/globals"
|
|
||||||
m "platform/web/models"
|
|
||||||
q "platform/web/queries"
|
|
||||||
"platform/web/services"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/smartwalle/alipay/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
// region CreateTrade
|
|
||||||
|
|
||||||
type CreateTradeReq struct {
|
|
||||||
Type int `json:"type" validate:"required"` // 交易类型:1.充值,2.购买
|
|
||||||
Subject string `json:"subject" validate:"required"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
Amount int `json:"amount" validate:"required"`
|
|
||||||
Method int `json:"method" validate:"required"` // 支付方式:1.支付宝,2.微信
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTrade(c *fiber.Ctx) error {
|
|
||||||
// 检查权限
|
|
||||||
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析请求参数
|
|
||||||
req := new(CreateTradeReq)
|
|
||||||
if err := c.BodyParser(req); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用外部接口
|
|
||||||
|
|
||||||
// 保存交易订单
|
|
||||||
num, err := services.ID.GenSerial(c.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var trade = m.Trade{
|
|
||||||
UserID: authContext.Payload.Id,
|
|
||||||
InnerNo: strconv.FormatUint(num, 10),
|
|
||||||
Subject: req.Subject,
|
|
||||||
Remark: req.Remark,
|
|
||||||
Amount: float64(req.Amount) / 100,
|
|
||||||
Method: int32(req.Method),
|
|
||||||
}
|
|
||||||
err = q.Trade.Create(&trade)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存用户帐单
|
|
||||||
var info string
|
|
||||||
var t int32
|
|
||||||
switch req.Type {
|
|
||||||
case 1:
|
|
||||||
info = "充值余额"
|
|
||||||
t = 0
|
|
||||||
case 2:
|
|
||||||
info = "购买产品"
|
|
||||||
t = 1
|
|
||||||
}
|
|
||||||
bill := m.Bill{
|
|
||||||
UserID: authContext.Payload.Id,
|
|
||||||
TradeID: trade.ID,
|
|
||||||
BillNo: services.ID.GenReadable("bil"),
|
|
||||||
Info: info,
|
|
||||||
Type: t,
|
|
||||||
Status: 0,
|
|
||||||
}
|
|
||||||
err = q.Bill.
|
|
||||||
Omit(q.Bill.ResourceID, q.Bill.RefundID).
|
|
||||||
Create(&bill)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回结果,外部支付链接
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTradeByAlipay() (*url.URL, error) {
|
|
||||||
|
|
||||||
target, err := g.Alipay.TradePagePay(alipay.TradePagePay{
|
|
||||||
Trade: alipay.Trade{},
|
|
||||||
AuthToken: "",
|
|
||||||
QRPayMode: "",
|
|
||||||
QRCodeWidth: "",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return target, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTradeByWechat() error {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region TradeCallbackAlipay
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region TradeCallbackWechat
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
@@ -33,6 +33,10 @@ func ApplyRouters(app *fiber.App) {
|
|||||||
resource.Post("/list/pss", handlers.ListResourcePss)
|
resource.Post("/list/pss", handlers.ListResourcePss)
|
||||||
resource.Post("/all", handlers.AllResource)
|
resource.Post("/all", handlers.AllResource)
|
||||||
resource.Post("/create/balance", handlers.CreateResourceByBalance)
|
resource.Post("/create/balance", handlers.CreateResourceByBalance)
|
||||||
|
resource.Post("/prepare/alipay", handlers.PrepareResourceByAlipay)
|
||||||
|
resource.Post("/create/alipay", handlers.CreateResourceByAlipay)
|
||||||
|
resource.Post("/prepare/wechat", handlers.PrepareResourceByWechat)
|
||||||
|
resource.Post("/create/wechat", handlers.CreateResourceByWechat)
|
||||||
|
|
||||||
// 用户
|
// 用户
|
||||||
user := api.Group("/user")
|
user := api.Group("/user")
|
||||||
@@ -40,10 +44,6 @@ func ApplyRouters(app *fiber.App) {
|
|||||||
user.Post("/identify", handlers.Identify)
|
user.Post("/identify", handlers.Identify)
|
||||||
user.Post("/identify/callback", handlers.IdentifyCallback)
|
user.Post("/identify/callback", handlers.IdentifyCallback)
|
||||||
|
|
||||||
// 支付
|
|
||||||
trade := api.Group("/trade")
|
|
||||||
trade.Post("/create", handlers.CreateTrade)
|
|
||||||
|
|
||||||
// 账单
|
// 账单
|
||||||
bill := api.Group("/bill")
|
bill := api.Group("/bill")
|
||||||
bill.Post("/list", handlers.ListBill)
|
bill.Post("/list", handlers.ListBill)
|
||||||
|
|||||||
Reference in New Issue
Block a user