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" "go.ipao.vip/gen/field" "go.ipao.vip/gen/types" "gorm.io/gorm" ) // @provider type wallet struct{} func (s *wallet) GetWallet(ctx context.Context, tenantID, 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) if tenantID > 0 { q = q.Where(tbl.Status.Eq(consts.OrderStatusPaid)). Where(field.Or( field.And(tbl.UserID.Eq(userID), tbl.TenantID.Eq(tenantID)), field.And(tbl.UserID.Eq(userID), tbl.Type.Eq(consts.OrderTypeRecharge)), )) } else { q = q.Where(tbl.UserID.Eq(userID), tbl.Status.Eq(consts.OrderStatusPaid)) } orders, err := q. 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: 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, tenantID int64, 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, tenantID, order.ID, "mock_auto_pay"); err != nil { return nil, err } // Mock Pay Params return &user_dto.RechargeResponse{ PayParams: "mock_paid_success", OrderID: order.ID, }, nil }