feat: add balance and ledger endpoints for tenant
- Implemented MyBalance and MyLedgerPage methods in the ledger service to retrieve current user balance and transaction history for a specified tenant. - Added corresponding test cases for MyBalance and MyLedgerPage methods in the ledger test suite. - Created DTOs for balance response and ledger items to structure the response data. - Updated Swagger documentation to include new endpoints for retrieving tenant balance and ledgers. - Added HTTP tests for the new endpoints to ensure proper functionality.
This commit is contained in:
@@ -224,14 +224,14 @@ func (s *ContentTestSuite) Test_HasAccess() {
|
||||
|
||||
Convey("权益 active 应返回 true", func() {
|
||||
access := &models.ContentAccess{
|
||||
TenantID: tenantID,
|
||||
UserID: userID,
|
||||
ContentID: content.ID,
|
||||
OrderID: 0,
|
||||
Status: consts.ContentAccessStatusActive,
|
||||
RevokedAt: time.Time{},
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
TenantID: tenantID,
|
||||
UserID: userID,
|
||||
ContentID: content.ID,
|
||||
OrderID: 0,
|
||||
Status: consts.ContentAccessStatusActive,
|
||||
RevokedAt: time.Time{},
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
So(access.Create(ctx), ShouldBeNil)
|
||||
|
||||
@@ -241,4 +241,3 @@ func (s *ContentTestSuite) Test_HasAccess() {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,14 @@ import (
|
||||
"time"
|
||||
|
||||
"quyun/v2/app/errorx"
|
||||
"quyun/v2/app/http/tenant/dto"
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/database/models"
|
||||
"quyun/v2/pkg/consts"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.ipao.vip/gen"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
@@ -29,6 +33,84 @@ type ledger struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// MyBalance 查询当前用户在指定租户下的余额信息(可用/冻结)。
|
||||
func (s *ledger) MyBalance(ctx context.Context, tenantID, userID int64) (*models.TenantUser, error) {
|
||||
if tenantID <= 0 || userID <= 0 {
|
||||
return nil, errorx.ErrInvalidParameter.WithMsg("tenant_id/user_id must be > 0")
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"tenant_id": tenantID,
|
||||
"user_id": userID,
|
||||
}).Info("services.ledger.me.balance")
|
||||
|
||||
tbl, query := models.TenantUserQuery.QueryContext(ctx)
|
||||
m, err := query.Where(tbl.TenantID.Eq(tenantID), tbl.UserID.Eq(userID)).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errorx.ErrRecordNotFound.WithMsg("tenant user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// MyLedgerPage 分页查询当前用户在指定租户下的余额流水(用于“我的流水”)。
|
||||
func (s *ledger) MyLedgerPage(ctx context.Context, tenantID, userID int64, filter *dto.MyLedgerListFilter) (*requests.Pager, error) {
|
||||
if tenantID <= 0 || userID <= 0 {
|
||||
return nil, errorx.ErrInvalidParameter.WithMsg("tenant_id/user_id must be > 0")
|
||||
}
|
||||
if filter == nil {
|
||||
filter = &dto.MyLedgerListFilter{}
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"tenant_id": tenantID,
|
||||
"user_id": userID,
|
||||
"type": lo.FromPtr(filter.Type),
|
||||
"order_id": lo.FromPtr(filter.OrderID),
|
||||
}).Info("services.ledger.me.ledgers.page")
|
||||
|
||||
filter.Pagination.Format()
|
||||
|
||||
tbl, query := models.TenantLedgerQuery.QueryContext(ctx)
|
||||
|
||||
conds := []gen.Condition{
|
||||
tbl.TenantID.Eq(tenantID),
|
||||
tbl.UserID.Eq(userID),
|
||||
}
|
||||
if filter.Type != nil {
|
||||
conds = append(conds, tbl.Type.Eq(*filter.Type))
|
||||
}
|
||||
if filter.OrderID != nil && *filter.OrderID > 0 {
|
||||
conds = append(conds, tbl.OrderID.Eq(*filter.OrderID))
|
||||
}
|
||||
if filter.CreatedAtFrom != nil {
|
||||
conds = append(conds, tbl.CreatedAt.Gte(*filter.CreatedAtFrom))
|
||||
}
|
||||
if filter.CreatedAtTo != nil {
|
||||
conds = append(conds, tbl.CreatedAt.Lte(*filter.CreatedAtTo))
|
||||
}
|
||||
|
||||
ledgers, total, err := query.Where(conds...).Order(tbl.ID.Desc()).FindByPage(int(filter.Offset()), int(filter.Limit))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := lo.Map(ledgers, func(m *models.TenantLedger, _ int) *dto.MyLedgerItem {
|
||||
return &dto.MyLedgerItem{
|
||||
Ledger: m,
|
||||
TypeDescription: m.Type.Description(),
|
||||
}
|
||||
})
|
||||
|
||||
return &requests.Pager{
|
||||
Pagination: filter.Pagination,
|
||||
Total: total,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Freeze 将可用余额转入冻结余额,并写入账本记录。
|
||||
func (s *ledger) Freeze(ctx context.Context, tenantID, userID, orderID, amount int64, idempotencyKey, remark string, now time.Time) (*LedgerApplyResult, error) {
|
||||
return s.apply(ctx, s.db, tenantID, userID, orderID, consts.TenantLedgerTypeFreeze, amount, -amount, amount, idempotencyKey, remark, now)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"quyun/v2/app/commands/testx"
|
||||
"quyun/v2/app/errorx"
|
||||
"quyun/v2/app/http/tenant/dto"
|
||||
"quyun/v2/database"
|
||||
"quyun/v2/database/models"
|
||||
"quyun/v2/pkg/consts"
|
||||
@@ -302,3 +303,56 @@ func (s *LedgerTestSuite) Test_CreditTopupTx() {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LedgerTestSuite) Test_MyBalance() {
|
||||
Convey("Ledger.MyBalance", s.T(), func() {
|
||||
ctx := s.T().Context()
|
||||
tenantID := int64(1)
|
||||
userID := int64(2)
|
||||
|
||||
s.seedTenantUser(ctx, tenantID, userID, 1000, 200)
|
||||
|
||||
Convey("成功返回租户内余额", func() {
|
||||
m, err := Ledger.MyBalance(ctx, tenantID, userID)
|
||||
So(err, ShouldBeNil)
|
||||
So(m, ShouldNotBeNil)
|
||||
So(m.Balance, ShouldEqual, 1000)
|
||||
So(m.BalanceFrozen, ShouldEqual, 200)
|
||||
})
|
||||
|
||||
Convey("参数非法应返回错误", func() {
|
||||
_, err := Ledger.MyBalance(ctx, 0, userID)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *LedgerTestSuite) Test_MyLedgerPage() {
|
||||
Convey("Ledger.MyLedgerPage", s.T(), func() {
|
||||
ctx := s.T().Context()
|
||||
tenantID := int64(1)
|
||||
userID := int64(2)
|
||||
now := time.Now().UTC()
|
||||
|
||||
s.seedTenantUser(ctx, tenantID, userID, 1000, 0)
|
||||
|
||||
_, err := Ledger.CreditTopupTx(ctx, _db, tenantID, userID, 1, 200, "k_topup_for_page", "topup", now)
|
||||
So(err, ShouldBeNil)
|
||||
_, err = Ledger.Freeze(ctx, tenantID, userID, 2, 100, "k_freeze_for_page", "freeze", now.Add(time.Second))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("分页返回流水列表", func() {
|
||||
pager, err := Ledger.MyLedgerPage(ctx, tenantID, userID, &dto.MyLedgerListFilter{})
|
||||
So(err, ShouldBeNil)
|
||||
So(pager, ShouldNotBeNil)
|
||||
So(pager.Total, ShouldBeGreaterThanOrEqualTo, 2)
|
||||
})
|
||||
|
||||
Convey("按 type 过滤", func() {
|
||||
typ := consts.TenantLedgerTypeCreditTopup
|
||||
pager, err := Ledger.MyLedgerPage(ctx, tenantID, userID, &dto.MyLedgerListFilter{Type: &typ})
|
||||
So(err, ShouldBeNil)
|
||||
So(pager.Total, ShouldEqual, 1)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -22,6 +23,44 @@ import (
|
||||
"go.ipao.vip/gen/types"
|
||||
)
|
||||
|
||||
type PurchaseOrderSnapshot struct {
|
||||
ContentID int64 `json:"content_id"`
|
||||
ContentTitle string `json:"content_title"`
|
||||
ContentUserID int64 `json:"content_user_id"`
|
||||
ContentVisibility consts.ContentVisibility `json:"content_visibility"`
|
||||
PreviewSeconds int32 `json:"preview_seconds"`
|
||||
PreviewDownloadable bool `json:"preview_downloadable"`
|
||||
Currency consts.Currency `json:"currency"`
|
||||
PriceAmount int64 `json:"price_amount"`
|
||||
DiscountType consts.DiscountType `json:"discount_type"`
|
||||
DiscountValue int64 `json:"discount_value"`
|
||||
DiscountStartAt *time.Time `json:"discount_start_at,omitempty"`
|
||||
DiscountEndAt *time.Time `json:"discount_end_at,omitempty"`
|
||||
AmountOriginal int64 `json:"amount_original"`
|
||||
AmountDiscount int64 `json:"amount_discount"`
|
||||
AmountPaid int64 `json:"amount_paid"`
|
||||
PurchaseAt time.Time `json:"purchase_at"`
|
||||
PurchaseIdempotency string `json:"purchase_idempotency_key,omitempty"`
|
||||
PurchasePricingNotes string `json:"purchase_pricing_notes,omitempty"`
|
||||
}
|
||||
|
||||
type OrderItemSnapshot struct {
|
||||
ContentID int64 `json:"content_id"`
|
||||
ContentTitle string `json:"content_title"`
|
||||
ContentUserID int64 `json:"content_user_id"`
|
||||
AmountPaid int64 `json:"amount_paid"`
|
||||
}
|
||||
|
||||
type TopupOrderSnapshot struct {
|
||||
OperatorUserID int64 `json:"operator_user_id"`
|
||||
TargetUserID int64 `json:"target_user_id"`
|
||||
Amount int64 `json:"amount"`
|
||||
Currency consts.Currency `json:"currency"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
IdempotencyKey string `json:"idempotency_key,omitempty"`
|
||||
TopupAt time.Time `json:"topup_at"`
|
||||
}
|
||||
|
||||
// PurchaseContentParams 定义“租户内使用余额购买内容”的入参。
|
||||
type PurchaseContentParams struct {
|
||||
// TenantID 租户 ID(多租户隔离范围)。
|
||||
@@ -56,6 +95,17 @@ type order struct {
|
||||
ledger *ledger
|
||||
}
|
||||
|
||||
func marshalSnapshot(v any) types.JSON {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return types.JSON([]byte("{}"))
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return types.JSON([]byte("{}"))
|
||||
}
|
||||
return types.JSON(b)
|
||||
}
|
||||
|
||||
// AdminTopupUser 租户管理员给租户成员充值(增加该租户下的可用余额)。
|
||||
func (s *order) AdminTopupUser(
|
||||
ctx context.Context,
|
||||
@@ -111,6 +161,15 @@ func (s *order) AdminTopupUser(
|
||||
}
|
||||
|
||||
// 先落订单(paid),再写入账本(credit_topup),确保“订单可追溯 + 账本可对账”。
|
||||
snapshot := marshalSnapshot(&TopupOrderSnapshot{
|
||||
OperatorUserID: operatorUserID,
|
||||
TargetUserID: targetUserID,
|
||||
Amount: amount,
|
||||
Currency: consts.CurrencyCNY,
|
||||
Reason: reason,
|
||||
IdempotencyKey: idempotencyKey,
|
||||
TopupAt: now,
|
||||
})
|
||||
orderModel := models.Order{
|
||||
TenantID: tenantID,
|
||||
UserID: targetUserID,
|
||||
@@ -120,7 +179,7 @@ func (s *order) AdminTopupUser(
|
||||
AmountOriginal: amount,
|
||||
AmountDiscount: 0,
|
||||
AmountPaid: amount,
|
||||
Snapshot: types.JSON([]byte("{}")),
|
||||
Snapshot: snapshot,
|
||||
IdempotencyKey: idempotencyKey,
|
||||
PaidAt: now,
|
||||
CreatedAt: now,
|
||||
@@ -559,6 +618,47 @@ func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentPara
|
||||
amountPaid := s.computeFinalPrice(priceAmount, &price, now)
|
||||
out.AmountPaid = amountPaid
|
||||
|
||||
discountType := price.DiscountType
|
||||
if discountType == "" {
|
||||
discountType = consts.DiscountTypeNone
|
||||
}
|
||||
var discountStartAt *time.Time
|
||||
if !price.DiscountStartAt.IsZero() {
|
||||
t := price.DiscountStartAt
|
||||
discountStartAt = &t
|
||||
}
|
||||
var discountEndAt *time.Time
|
||||
if !price.DiscountEndAt.IsZero() {
|
||||
t := price.DiscountEndAt
|
||||
discountEndAt = &t
|
||||
}
|
||||
|
||||
purchaseSnapshot := marshalSnapshot(&PurchaseOrderSnapshot{
|
||||
ContentID: content.ID,
|
||||
ContentTitle: content.Title,
|
||||
ContentUserID: content.UserID,
|
||||
ContentVisibility: content.Visibility,
|
||||
PreviewSeconds: content.PreviewSeconds,
|
||||
PreviewDownloadable: content.PreviewDownloadable,
|
||||
Currency: consts.CurrencyCNY,
|
||||
PriceAmount: priceAmount,
|
||||
DiscountType: discountType,
|
||||
DiscountValue: price.DiscountValue,
|
||||
DiscountStartAt: discountStartAt,
|
||||
DiscountEndAt: discountEndAt,
|
||||
AmountOriginal: priceAmount,
|
||||
AmountDiscount: priceAmount - amountPaid,
|
||||
AmountPaid: amountPaid,
|
||||
PurchaseAt: now,
|
||||
PurchaseIdempotency: params.IdempotencyKey,
|
||||
})
|
||||
itemSnapshot := marshalSnapshot(&OrderItemSnapshot{
|
||||
ContentID: content.ID,
|
||||
ContentTitle: content.Title,
|
||||
ContentUserID: content.UserID,
|
||||
AmountPaid: amountPaid,
|
||||
})
|
||||
|
||||
// 免费内容:无需冻结,保持单事务写订单+权益。
|
||||
if amountPaid == 0 {
|
||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
@@ -571,7 +671,7 @@ func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentPara
|
||||
AmountOriginal: priceAmount,
|
||||
AmountDiscount: priceAmount - amountPaid,
|
||||
AmountPaid: amountPaid,
|
||||
Snapshot: types.JSON([]byte("{}")),
|
||||
Snapshot: purchaseSnapshot,
|
||||
IdempotencyKey: params.IdempotencyKey,
|
||||
PaidAt: now,
|
||||
CreatedAt: now,
|
||||
@@ -587,7 +687,7 @@ func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentPara
|
||||
ContentID: params.ContentID,
|
||||
ContentUserID: content.UserID,
|
||||
AmountPaid: amountPaid,
|
||||
Snapshot: types.JSON([]byte("{}")),
|
||||
Snapshot: itemSnapshot,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
@@ -631,7 +731,7 @@ func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentPara
|
||||
AmountOriginal: priceAmount,
|
||||
AmountDiscount: priceAmount - amountPaid,
|
||||
AmountPaid: amountPaid,
|
||||
Snapshot: types.JSON([]byte("{}")),
|
||||
Snapshot: purchaseSnapshot,
|
||||
IdempotencyKey: params.IdempotencyKey,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
@@ -646,7 +746,7 @@ func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentPara
|
||||
ContentID: params.ContentID,
|
||||
ContentUserID: content.UserID,
|
||||
AmountPaid: amountPaid,
|
||||
Snapshot: types.JSON([]byte("{}")),
|
||||
Snapshot: itemSnapshot,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
@@ -753,6 +853,45 @@ func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentPara
|
||||
amountPaid := s.computeFinalPrice(priceAmount, &price, now)
|
||||
out.AmountPaid = amountPaid
|
||||
|
||||
discountType := price.DiscountType
|
||||
if discountType == "" {
|
||||
discountType = consts.DiscountTypeNone
|
||||
}
|
||||
var discountStartAt *time.Time
|
||||
if !price.DiscountStartAt.IsZero() {
|
||||
t := price.DiscountStartAt
|
||||
discountStartAt = &t
|
||||
}
|
||||
var discountEndAt *time.Time
|
||||
if !price.DiscountEndAt.IsZero() {
|
||||
t := price.DiscountEndAt
|
||||
discountEndAt = &t
|
||||
}
|
||||
purchaseSnapshot := marshalSnapshot(&PurchaseOrderSnapshot{
|
||||
ContentID: content.ID,
|
||||
ContentTitle: content.Title,
|
||||
ContentUserID: content.UserID,
|
||||
ContentVisibility: content.Visibility,
|
||||
PreviewSeconds: content.PreviewSeconds,
|
||||
PreviewDownloadable: content.PreviewDownloadable,
|
||||
Currency: consts.CurrencyCNY,
|
||||
PriceAmount: priceAmount,
|
||||
DiscountType: discountType,
|
||||
DiscountValue: price.DiscountValue,
|
||||
DiscountStartAt: discountStartAt,
|
||||
DiscountEndAt: discountEndAt,
|
||||
AmountOriginal: priceAmount,
|
||||
AmountDiscount: priceAmount - amountPaid,
|
||||
AmountPaid: amountPaid,
|
||||
PurchaseAt: now,
|
||||
})
|
||||
itemSnapshot := marshalSnapshot(&OrderItemSnapshot{
|
||||
ContentID: content.ID,
|
||||
ContentTitle: content.Title,
|
||||
ContentUserID: content.UserID,
|
||||
AmountPaid: amountPaid,
|
||||
})
|
||||
|
||||
if amountPaid == 0 {
|
||||
orderModel := &models.Order{
|
||||
TenantID: params.TenantID,
|
||||
@@ -763,7 +902,7 @@ func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentPara
|
||||
AmountOriginal: priceAmount,
|
||||
AmountDiscount: priceAmount - amountPaid,
|
||||
AmountPaid: amountPaid,
|
||||
Snapshot: types.JSON([]byte("{}")),
|
||||
Snapshot: purchaseSnapshot,
|
||||
IdempotencyKey: "",
|
||||
PaidAt: now,
|
||||
CreatedAt: now,
|
||||
@@ -779,7 +918,7 @@ func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentPara
|
||||
ContentID: params.ContentID,
|
||||
ContentUserID: content.UserID,
|
||||
AmountPaid: amountPaid,
|
||||
Snapshot: types.JSON([]byte("{}")),
|
||||
Snapshot: itemSnapshot,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
@@ -808,7 +947,7 @@ func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentPara
|
||||
AmountOriginal: priceAmount,
|
||||
AmountDiscount: priceAmount - amountPaid,
|
||||
AmountPaid: amountPaid,
|
||||
Snapshot: types.JSON([]byte("{}")),
|
||||
Snapshot: purchaseSnapshot,
|
||||
IdempotencyKey: "",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
@@ -829,7 +968,7 @@ func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentPara
|
||||
ContentID: params.ContentID,
|
||||
ContentUserID: content.UserID,
|
||||
AmountPaid: amountPaid,
|
||||
Snapshot: types.JSON([]byte("{}")),
|
||||
Snapshot: itemSnapshot,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
@@ -78,13 +79,13 @@ func (s *OrderTestSuite) seedPublishedContent(ctx context.Context, tenantID, own
|
||||
|
||||
func (s *OrderTestSuite) seedContentPrice(ctx context.Context, tenantID, contentID, priceAmount int64) {
|
||||
p := &models.ContentPrice{
|
||||
TenantID: tenantID,
|
||||
UserID: 1,
|
||||
ContentID: contentID,
|
||||
Currency: consts.CurrencyCNY,
|
||||
PriceAmount: priceAmount,
|
||||
DiscountType: consts.DiscountTypeNone,
|
||||
DiscountValue: 0,
|
||||
TenantID: tenantID,
|
||||
UserID: 1,
|
||||
ContentID: contentID,
|
||||
Currency: consts.CurrencyCNY,
|
||||
PriceAmount: priceAmount,
|
||||
DiscountType: consts.DiscountTypeNone,
|
||||
DiscountValue: 0,
|
||||
DiscountStartAt: time.Time{},
|
||||
DiscountEndAt: time.Time{},
|
||||
}
|
||||
@@ -141,6 +142,12 @@ func (s *OrderTestSuite) Test_AdminTopupUser() {
|
||||
So(orderModel.Status, ShouldEqual, consts.OrderStatusPaid)
|
||||
So(orderModel.AmountPaid, ShouldEqual, 300)
|
||||
|
||||
var snap map[string]any
|
||||
So(json.Unmarshal([]byte(orderModel.Snapshot), &snap), ShouldBeNil)
|
||||
So(snap["operator_user_id"], ShouldEqual, float64(operatorUserID))
|
||||
So(snap["target_user_id"], ShouldEqual, float64(targetUserID))
|
||||
So(snap["amount"], ShouldEqual, float64(300))
|
||||
|
||||
var tu models.TenantUser
|
||||
So(_db.WithContext(ctx).Where("tenant_id = ? AND user_id = ?", tenantID, targetUserID).First(&tu).Error, ShouldBeNil)
|
||||
So(tu.Balance, ShouldEqual, 300)
|
||||
@@ -360,14 +367,14 @@ func (s *OrderTestSuite) Test_AdminRefundOrder() {
|
||||
So(item.Create(ctx), ShouldBeNil)
|
||||
|
||||
access := &models.ContentAccess{
|
||||
TenantID: tenantID,
|
||||
UserID: buyerUserID,
|
||||
ContentID: contentID,
|
||||
OrderID: orderModel.ID,
|
||||
Status: consts.ContentAccessStatusActive,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
RevokedAt: time.Time{},
|
||||
TenantID: tenantID,
|
||||
UserID: buyerUserID,
|
||||
ContentID: contentID,
|
||||
OrderID: orderModel.ID,
|
||||
Status: consts.ContentAccessStatusActive,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
RevokedAt: time.Time{},
|
||||
}
|
||||
So(access.Create(ctx), ShouldBeNil)
|
||||
|
||||
@@ -477,6 +484,12 @@ func (s *OrderTestSuite) Test_PurchaseContent() {
|
||||
So(res1.Access, ShouldNotBeNil)
|
||||
So(res1.Access.Status, ShouldEqual, consts.ContentAccessStatusActive)
|
||||
|
||||
var snap map[string]any
|
||||
So(json.Unmarshal([]byte(res1.Order.Snapshot), &snap), ShouldBeNil)
|
||||
So(snap["content_id"], ShouldEqual, float64(content.ID))
|
||||
So(snap["content_title"], ShouldEqual, content.Title)
|
||||
So(snap["amount_paid"], ShouldEqual, float64(0))
|
||||
|
||||
res2, err := Order.PurchaseContent(ctx, &PurchaseContentParams{
|
||||
TenantID: tenantID,
|
||||
UserID: buyerUserID,
|
||||
@@ -518,6 +531,17 @@ func (s *OrderTestSuite) Test_PurchaseContent() {
|
||||
So(res1.Access, ShouldNotBeNil)
|
||||
So(res1.Access.Status, ShouldEqual, consts.ContentAccessStatusActive)
|
||||
|
||||
var snap map[string]any
|
||||
So(json.Unmarshal([]byte(res1.Order.Snapshot), &snap), ShouldBeNil)
|
||||
So(snap["content_id"], ShouldEqual, float64(content.ID))
|
||||
So(snap["amount_paid"], ShouldEqual, float64(300))
|
||||
So(snap["amount_original"], ShouldEqual, float64(300))
|
||||
|
||||
var itemSnap map[string]any
|
||||
So(json.Unmarshal([]byte(res1.OrderItem.Snapshot), &itemSnap), ShouldBeNil)
|
||||
So(itemSnap["content_id"], ShouldEqual, float64(content.ID))
|
||||
So(itemSnap["amount_paid"], ShouldEqual, float64(300))
|
||||
|
||||
var tu models.TenantUser
|
||||
So(_db.WithContext(ctx).Where("tenant_id = ? AND user_id = ?", tenantID, buyerUserID).First(&tu).Error, ShouldBeNil)
|
||||
So(tu.Balance, ShouldEqual, 700)
|
||||
|
||||
Reference in New Issue
Block a user