Files
quyun-v2/backend/app/services/wallet.go

111 lines
2.8 KiB
Go

package services
import (
"context"
"errors"
"time"
"quyun/v2/app/errorx"
user_dto "quyun/v2/app/http/v1/dto"
"quyun/v2/database/fields"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
"github.com/google/uuid"
"github.com/spf13/cast"
"go.ipao.vip/gen/types"
"gorm.io/gorm"
)
// @provider
type wallet struct{}
func (s *wallet) GetWallet(ctx context.Context, userID int64) (*user_dto.WalletResponse, error) {
// Get Balance
u, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(userID)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrRecordNotFound
}
return nil, errorx.ErrDatabaseError.WithCause(err)
}
// Get Transactions (Orders)
// Both purchase (expense) and recharge (income - if paid)
tbl, q := models.OrderQuery.QueryContext(ctx)
orders, err := q.Where(tbl.UserID.Eq(userID), tbl.Status.Eq(consts.OrderStatusPaid)).
Order(tbl.CreatedAt.Desc()).
Limit(20). // Limit to recent 20
Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
var txs []user_dto.Transaction
for _, o := range orders {
var txType string
var title string
switch o.Type {
case consts.OrderTypeContentPurchase:
txType = "expense"
title = "购买内容"
case consts.OrderTypeRecharge:
txType = "income"
title = "钱包充值"
}
txs = append(txs, user_dto.Transaction{
ID: cast.ToString(o.ID),
Title: title,
Amount: float64(o.AmountPaid) / 100.0,
Type: txType,
Date: o.CreatedAt.Format(time.RFC3339),
})
}
return &user_dto.WalletResponse{
Balance: float64(u.Balance) / 100.0,
Transactions: txs,
}, nil
}
func (s *wallet) Recharge(
ctx context.Context,
userID int64,
form *user_dto.RechargeForm,
) (*user_dto.RechargeResponse, error) {
amount := int64(form.Amount * 100)
if amount <= 0 {
return nil, errorx.ErrBadRequest.WithMsg("金额无效")
}
// Create Recharge Order
order := &models.Order{
TenantID: 0, // Platform / System
UserID: userID,
Type: consts.OrderTypeRecharge,
Status: consts.OrderStatusCreated,
Currency: consts.CurrencyCNY,
AmountOriginal: amount,
AmountPaid: amount,
IdempotencyKey: uuid.NewString(),
Snapshot: types.NewJSONType(fields.OrdersSnapshot{}),
}
if err := models.OrderQuery.WithContext(ctx).Create(order); err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
// MOCK: Automatically pay for recharge order to close the loop
// In production, this would be a callback from payment gateway
if err := Order.ProcessExternalPayment(ctx, cast.ToString(order.ID), "mock_auto_pay"); err != nil {
return nil, err
}
// Mock Pay Params
return &user_dto.RechargeResponse{
PayParams: "mock_paid_success",
OrderID: cast.ToString(order.ID),
}, nil
}