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:
2025-12-18 16:24:37 +08:00
parent 435e541dbe
commit 3249e405ac
13 changed files with 990 additions and 33 deletions

View File

@@ -0,0 +1,31 @@
package dto
import (
"time"
"quyun/v2/app/requests"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
)
// MyLedgerListFilter 定义“我的余额流水”查询条件。
type MyLedgerListFilter struct {
// Pagination 分页参数page/limit
requests.Pagination `json:",inline" query:",inline"`
// Type 按流水类型过滤(可选)。
Type *consts.TenantLedgerType `json:"type,omitempty" query:"type"`
// OrderID 按关联订单过滤(可选)。
OrderID *int64 `json:"order_id,omitempty" query:"order_id"`
// CreatedAtFrom 创建时间起(可选)。
CreatedAtFrom *time.Time `json:"created_at_from,omitempty" query:"created_at_from"`
// CreatedAtTo 创建时间止(可选)。
CreatedAtTo *time.Time `json:"created_at_to,omitempty" query:"created_at_to"`
}
// MyLedgerItem 返回一条余额流水,并补充展示字段。
type MyLedgerItem struct {
// Ledger 流水记录(租户内隔离)。
Ledger *models.TenantLedger `json:"ledger"`
// TypeDescription 流水类型中文说明(用于前端展示)。
TypeDescription string `json:"type_description"`
}

View File

@@ -0,0 +1,19 @@
package dto
import (
"time"
"quyun/v2/pkg/consts"
)
// MeBalanceResponse 返回当前用户在当前租户下的余额信息(租户内隔离)。
type MeBalanceResponse struct {
// Currency 币种:当前固定 CNY金额单位为分
Currency consts.Currency `json:"currency"`
// Balance 可用余额:可用于购买/消费。
Balance int64 `json:"balance"`
// BalanceFrozen 冻结余额:用于下单冻结/争议期等。
BalanceFrozen int64 `json:"balance_frozen"`
// UpdatedAt 更新时间:余额变更时更新。
UpdatedAt time.Time `json:"updated_at"`
}

View File

@@ -2,7 +2,10 @@ package tenant
import (
"quyun/v2/app/http/tenant/dto"
"quyun/v2/app/requests"
"quyun/v2/app/services"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
"github.com/gofiber/fiber/v3"
)
@@ -32,3 +35,45 @@ func (*me) get(ctx fiber.Ctx, tenant *models.Tenant, user *models.User, tenantUs
TenantUser: tenantUser,
}, nil
}
// balance
//
// @Summary 当前租户余额信息
// @Tags Tenant
// @Accept json
// @Produce json
// @Param tenantCode path string true "Tenant Code"
// @Success 200 {object} dto.MeBalanceResponse
//
// @Router /t/:tenantCode/v1/me/balance [get]
// @Bind tenant local key(tenant)
// @Bind user local key(user)
func (*me) balance(ctx fiber.Ctx, tenant *models.Tenant, user *models.User) (*dto.MeBalanceResponse, error) {
m, err := services.Ledger.MyBalance(ctx.Context(), tenant.ID, user.ID)
if err != nil {
return nil, err
}
return &dto.MeBalanceResponse{
Currency: consts.CurrencyCNY,
Balance: m.Balance,
BalanceFrozen: m.BalanceFrozen,
UpdatedAt: m.UpdatedAt,
}, nil
}
// ledgers
//
// @Summary 当前租户余额流水(分页)
// @Tags Tenant
// @Accept json
// @Produce json
// @Param tenantCode path string true "Tenant Code"
// @Success 200 {object} requests.Pager{items=dto.MyLedgerItem}
//
// @Router /t/:tenantCode/v1/me/ledgers [get]
// @Bind tenant local key(tenant)
// @Bind user local key(user)
// @Bind filter query
func (*me) ledgers(ctx fiber.Ctx, tenant *models.Tenant, user *models.User, filter *dto.MyLedgerListFilter) (*requests.Pager, error) {
return services.Ledger.MyLedgerPage(ctx.Context(), tenant.ID, user.ID, filter)
}

View File

@@ -116,6 +116,19 @@ func (r *Routes) Register(router fiber.Router) {
Local[*models.User]("user"),
Local[*models.TenantUser]("tenant_user"),
))
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/me/balance -> me.balance")
router.Get("/t/:tenantCode/v1/me/balance"[len(r.Path()):], DataFunc2(
r.me.balance,
Local[*models.Tenant]("tenant"),
Local[*models.User]("user"),
))
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/me/ledgers -> me.ledgers")
router.Get("/t/:tenantCode/v1/me/ledgers"[len(r.Path()):], DataFunc3(
r.me.ledgers,
Local[*models.Tenant]("tenant"),
Local[*models.User]("user"),
Query[dto.MyLedgerListFilter]("filter"),
))
// Register routes for controller: order
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/contents/:contentID/purchase -> order.purchaseContent")
router.Post("/t/:tenantCode/v1/contents/:contentID/purchase"[len(r.Path()):], DataFunc4(