添加微信支付支持,重构资源创建逻辑,更新支付宝相关配置,移除账单状态字段
This commit is contained in:
@@ -1,24 +1,19 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"platform/pkg/rds"
|
||||
"platform/pkg/env"
|
||||
"platform/pkg/u"
|
||||
"platform/web/auth"
|
||||
"platform/web/common"
|
||||
g "platform/web/globals"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
"platform/web/services"
|
||||
s "platform/web/services"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/native"
|
||||
)
|
||||
|
||||
// region ListResourcePss
|
||||
@@ -37,7 +32,7 @@ type ListResourcePssReq struct {
|
||||
// ListResourcePss 获取套餐列表
|
||||
func ListResourcePss(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
authContext, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -110,7 +105,7 @@ type AllResourceReq struct {
|
||||
|
||||
func AllResource(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
authContext, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -148,14 +143,10 @@ func AllResource(c *fiber.Ctx) error {
|
||||
|
||||
// endregion
|
||||
|
||||
// region CreateResource
|
||||
// region CreateResourcePrepared
|
||||
|
||||
type CreateResourceReq struct {
|
||||
Type int32 `json:"type" validate:"required"`
|
||||
Live int32 `json:"live" validate:"required"`
|
||||
Expire int32 `json:"expire" validate:"required"`
|
||||
Quota int32 `json:"quota" validate:"required"`
|
||||
DailyLimit int32 `json:"daily_limit" validate:"required"`
|
||||
s.CreateResourceData
|
||||
}
|
||||
|
||||
type CreateResourceResp struct {
|
||||
@@ -170,7 +161,7 @@ type PaidCreateResourceReq struct {
|
||||
func PrepareResourceByAlipay(c *fiber.Ctx) error {
|
||||
|
||||
// 检查权限
|
||||
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
authContext, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -182,7 +173,7 @@ func PrepareResourceByAlipay(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 生成订单
|
||||
amount, tradeNo, err := prepareResource(c.Context(), req)
|
||||
tradeNo, err := s.ID.GenSerial(c.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -192,8 +183,8 @@ func PrepareResourceByAlipay(c *fiber.Ctx) error {
|
||||
QRPayMode: "4",
|
||||
Trade: alipay.Trade{
|
||||
OutTradeNo: tradeNo,
|
||||
TotalAmount: strconv.FormatFloat(amount, 'f', 2, 64),
|
||||
Subject: "购买套餐",
|
||||
TotalAmount: strconv.FormatFloat(req.GetPrice(), 'f', 2, 64),
|
||||
Subject: "购买套餐 - " + req.GetName(),
|
||||
ProductCode: "FAST_INSTANT_TRADE_PAY",
|
||||
TimeExpire: time.Now().Add(30 * time.Minute).Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
@@ -203,7 +194,7 @@ func PrepareResourceByAlipay(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// 保存交易信息
|
||||
err = savePrepareResource(c.Context(), req, amount, tradeNo, authContext.Payload.Id, 1)
|
||||
err = s.Resource.PrepareResource(c.Context(), &req.CreateResourceData, authContext.Payload.Id, tradeNo, 1)
|
||||
|
||||
// 返回结果
|
||||
return c.JSON(CreateResourceResp{
|
||||
@@ -215,7 +206,7 @@ func PrepareResourceByAlipay(c *fiber.Ctx) error {
|
||||
func PrepareResourceByWechat(c *fiber.Ctx) error {
|
||||
|
||||
// 检查权限
|
||||
authContext, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
authContext, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -226,29 +217,27 @@ func PrepareResourceByWechat(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 生成订单
|
||||
amount, tradeNo, err := prepareResource(c.Context(), req)
|
||||
// 生成订单号
|
||||
tradeNo, err := s.ID.GenSerial(c.Context())
|
||||
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"),
|
||||
wechatPayResp, _, err := g.WechatPay.Native.Prepay(c.Context(), native.PrepayRequest{
|
||||
Mchid: &env.WechatPayMchId,
|
||||
Appid: &env.WechatPayAppId,
|
||||
Description: u.P("购买套餐 - " + req.GetName()),
|
||||
OutTradeNo: &tradeNo,
|
||||
TimeExpire: u.P(time.Now().Add(30 * time.Minute)),
|
||||
NotifyUrl: &env.WechatPayCallbackUrl,
|
||||
Amount: &native.Amount{
|
||||
Total: u.P(int64(req.GetPrice() * 100)),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存交易信息
|
||||
err = savePrepareResource(c.Context(), req, amount, tradeNo, authContext.Payload.Id, 2)
|
||||
err = s.Resource.PrepareResource(c.Context(), &req.CreateResourceData, authContext.Payload.Id, tradeNo, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -256,13 +245,13 @@ func PrepareResourceByWechat(c *fiber.Ctx) error {
|
||||
// 返回结果
|
||||
return c.JSON(CreateResourceResp{
|
||||
TradeNo: tradeNo,
|
||||
PayURL: alipayResp.String(),
|
||||
PayURL: *wechatPayResp.CodeUrl,
|
||||
})
|
||||
}
|
||||
|
||||
func CreateResourceByAlipay(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
_, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -284,6 +273,7 @@ func CreateResourceByAlipay(c *fiber.Ctx) error {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "支付未完成,请确认后重试")
|
||||
}
|
||||
|
||||
// 创建套餐
|
||||
payment, err := strconv.ParseFloat(alipayResp.ReceiptAmount, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -294,53 +284,7 @@ func CreateResourceByAlipay(c *fiber.Ctx) error {
|
||||
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
|
||||
})
|
||||
err = s.Resource.CreateResourcePrepared(c.Context(), req.TradeNo, alipayResp.TradeNo, payment, paidAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -350,63 +294,50 @@ func CreateResourceByAlipay(c *fiber.Ctx) error {
|
||||
|
||||
func CreateResourceByWechat(c *fiber.Ctx) error {
|
||||
// 检查权限
|
||||
authCtx, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
_, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
req := new(CreateResourceReq)
|
||||
req := new(PaidCreateResourceReq)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = q.Q.Transaction(func(q *q.Query) error {
|
||||
|
||||
// 保存套餐
|
||||
resource, err := saveResourceBalance(req, 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
|
||||
// 验证支付结果
|
||||
wechatPayResp, _, err := g.WechatPay.Native.QueryOrderByOutTradeNo(c.Context(), native.QueryOrderByOutTradeNoRequest{
|
||||
OutTradeNo: &req.TradeNo,
|
||||
Mchid: &env.WechatPayMchId,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *wechatPayResp.TradeState != "SUCCESS" {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "支付未完成,请确认后重试")
|
||||
}
|
||||
|
||||
// 创建套餐
|
||||
payment := float64(*wechatPayResp.Amount.PayerTotal) / 100
|
||||
|
||||
paidAt, err := time.Parse(time.RFC3339, *wechatPayResp.SuccessTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Resource.CreateResourcePrepared(c.Context(), req.TradeNo, *wechatPayResp.OutTradeNo, payment, paidAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateResourceByBalance(c *fiber.Ctx) error {
|
||||
|
||||
// 检查权限
|
||||
authCtx, err := auth.Protect(c, []services.PayloadType{services.PayloadUser}, []string{})
|
||||
authCtx, err := auth.Protect(c, []s.PayloadType{s.PayloadUser}, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -417,208 +348,13 @@ func CreateResourceByBalance(c *fiber.Ctx) error {
|
||||
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: authCtx.Payload.Id,
|
||||
ResourceID: resource.ID,
|
||||
BillNo: services.ID.GenReadable("bil"),
|
||||
Info: "购买套餐 - " + resourceName(req),
|
||||
Type: 1,
|
||||
Status: 1,
|
||||
Amount: amount,
|
||||
}
|
||||
err = q.Bill.
|
||||
Omit(q.Bill.TradeID, q.Bill.RefundID).
|
||||
Create(&bill)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
err = s.Resource.CreateResourceImmediately(&req.CreateResourceData, authCtx.Payload.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return 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
|
||||
|
||||
type CreateResourceByAlipayCallbackReq struct {
|
||||
}
|
||||
|
||||
// CreateResourceByAlipayCallback 支付宝支付回调
|
||||
func CreateResourceByAlipayCallback(c *fiber.Ctx) error {
|
||||
|
||||
// 根据支付类型执行不同流程:
|
||||
// 1. 支付宝或微信(即时支付)
|
||||
// - 更新订单状态
|
||||
// - 生成账单
|
||||
// - 生成套餐
|
||||
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region CreateResourceByWechatCallback
|
||||
|
||||
type CreateResourceByWechatCallbackReq struct {
|
||||
}
|
||||
|
||||
// CreateResourceByWechatCallback 微信支付回调
|
||||
func CreateResourceByWechatCallback(c *fiber.Ctx) error {
|
||||
return errors.New("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
Reference in New Issue
Block a user