2026-09-30 09:15:26 +07:00
|
|
|
package models
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// CustomerWalletTransaction is one ledger row as the customer app shows it
|
|
|
|
|
// (docs/prd-point-coin.md F6).
|
|
|
|
|
type CustomerWalletTransaction struct {
|
|
|
|
|
ID uuid.UUID `json:"id"`
|
|
|
|
|
Currency string `json:"currency"`
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
// Signed: positive added to the balance, negative taken from it.
|
|
|
|
|
Amount int64 `json:"amount"`
|
|
|
|
|
BalanceAfter int64 `json:"balance_after"`
|
|
|
|
|
Description string `json:"description"`
|
|
|
|
|
// Where the value came from, set on additions.
|
|
|
|
|
Source *CustomerWalletTransactionRef `json:"source,omitempty"`
|
|
|
|
|
// Where the value went, set on deductions.
|
|
|
|
|
Destination *CustomerWalletTransactionRef `json:"destination,omitempty"`
|
|
|
|
|
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
|
|
|
|
ReversesTransactionID *uuid.UUID `json:"reverses_transaction_id,omitempty"`
|
|
|
|
|
// Shared by the two rows of an exchange or a transfer.
|
|
|
|
|
GroupID *uuid.UUID `json:"group_id,omitempty"`
|
|
|
|
|
// Additions only: the earliest expiry among the lots it created, nil when none of
|
|
|
|
|
// them expire, and the lots themselves.
|
|
|
|
|
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
|
|
|
|
Lots []CustomerWalletLot `json:"lots,omitempty"`
|
|
|
|
|
CreatedAt time.Time `json:"created_at"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CustomerWalletTransactionRef points at what a ledger row came from or went to, as
|
|
|
|
|
// listed in §8.1: ORDER, PAYMENT, WALLET_TX, GAME_PLAY, LOT, USER and so on.
|
|
|
|
|
type CustomerWalletTransactionRef struct {
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
ID uuid.UUID `json:"id"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type CustomerWalletLot struct {
|
|
|
|
|
Amount int64 `json:"amount"`
|
|
|
|
|
Remaining int64 `json:"remaining"`
|
|
|
|
|
ExpiresAt *time.Time `json:"expires_at"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CustomerWalletExpiring is how much expires on one day.
|
|
|
|
|
type CustomerWalletExpiring struct {
|
|
|
|
|
Amount int64 `json:"amount"`
|
|
|
|
|
// YYYY-MM-DD, Asia/Jakarta.
|
|
|
|
|
Date string `json:"date"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CustomerWalletNearestExpiring is the next day each currency loses balance, nil when
|
|
|
|
|
// nothing is due to expire.
|
|
|
|
|
type CustomerWalletNearestExpiring struct {
|
|
|
|
|
Point *CustomerWalletExpiring `json:"point"`
|
|
|
|
|
Coin *CustomerWalletExpiring `json:"coin"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ListCustomerWalletTransactionsQuery is GET /customer/wallet/transactions.
|
|
|
|
|
type ListCustomerWalletTransactionsQuery struct {
|
|
|
|
|
Page int `form:"page"`
|
|
|
|
|
Limit int `form:"limit"`
|
|
|
|
|
Currency string `form:"currency"`
|
|
|
|
|
// One type, or several separated by commas.
|
|
|
|
|
Type string `form:"type"`
|
|
|
|
|
// Inclusive calendar dates, YYYY-MM-DD, Asia/Jakarta.
|
|
|
|
|
From string `form:"from"`
|
|
|
|
|
To string `form:"to"`
|
|
|
|
|
}
|
2026-09-30 09:36:18 +07:00
|
|
|
|
|
|
|
|
// AdminCustomerWallet is GET /marketing/customers/:id/wallet (docs/prd-point-coin.md
|
|
|
|
|
// F7). Unlike the customer's own view it shows the raw balances next to the spendable
|
|
|
|
|
// ones, every lot that still holds something, and the real names behind each row.
|
|
|
|
|
type AdminCustomerWallet struct {
|
|
|
|
|
Customer AdminWalletCustomer `json:"customer"`
|
|
|
|
|
// Balances as the ledger has them.
|
|
|
|
|
PointBalance int64 `json:"point_balance"`
|
|
|
|
|
CoinBalance int64 `json:"coin_balance"`
|
|
|
|
|
// What can be spent now. Lower than the ledger balance only while lots that have
|
|
|
|
|
// expired wait for the expiry job.
|
|
|
|
|
SpendablePointBalance int64 `json:"spendable_point_balance"`
|
|
|
|
|
SpendableCoinBalance int64 `json:"spendable_coin_balance"`
|
|
|
|
|
Lots []AdminWalletLot `json:"lots"`
|
|
|
|
|
Transactions PaginatedResponse[AdminWalletTransaction] `json:"transactions"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AdminWalletCustomer struct {
|
|
|
|
|
ID uuid.UUID `json:"id"`
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Phone *string `json:"phone,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AdminWalletLot struct {
|
|
|
|
|
ID uuid.UUID `json:"id"`
|
|
|
|
|
Currency string `json:"currency"`
|
|
|
|
|
OriginalAmount int64 `json:"original_amount"`
|
|
|
|
|
RemainingAmount int64 `json:"remaining_amount"`
|
|
|
|
|
ExpiresAt *time.Time `json:"expires_at"`
|
|
|
|
|
Expired bool `json:"expired"`
|
|
|
|
|
SourceTransactionID uuid.UUID `json:"source_transaction_id"`
|
|
|
|
|
OriginLotID *uuid.UUID `json:"origin_lot_id,omitempty"`
|
|
|
|
|
CreatedAt time.Time `json:"created_at"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AdminWalletTransaction is a ledger row with the names the customer does not see:
|
|
|
|
|
// the real counterparty of a transfer, the admin behind an adjustment, the cashier who
|
|
|
|
|
// took a payment, and the outlet.
|
|
|
|
|
type AdminWalletTransaction struct {
|
|
|
|
|
CustomerWalletTransaction
|
|
|
|
|
Counterparty *AdminWalletNamedRef `json:"counterparty,omitempty"`
|
|
|
|
|
CreatedBy *AdminWalletNamedRef `json:"created_by,omitempty"`
|
|
|
|
|
Outlet *AdminWalletNamedRef `json:"outlet,omitempty"`
|
|
|
|
|
Reason *string `json:"reason,omitempty"`
|
|
|
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AdminWalletNamedRef struct {
|
|
|
|
|
ID uuid.UUID `json:"id"`
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WalletAdjustment is a manual correction by an admin.
|
|
|
|
|
type WalletAdjustment struct {
|
|
|
|
|
Currency string
|
|
|
|
|
// Signed: positive adds, negative takes away.
|
|
|
|
|
Amount int64
|
|
|
|
|
Reason string
|
|
|
|
|
IdempotencyKey string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AdminWalletAdjustmentResult is what POST /marketing/customers/:id/wallet/adjust returns.
|
|
|
|
|
type AdminWalletAdjustmentResult struct {
|
|
|
|
|
Transaction AdminWalletTransaction `json:"transaction"`
|
|
|
|
|
SpendablePointBalance int64 `json:"spendable_point_balance"`
|
|
|
|
|
SpendableCoinBalance int64 `json:"spendable_coin_balance"`
|
|
|
|
|
// True when the idempotency key had been used before and nothing changed.
|
|
|
|
|
Replayed bool `json:"replayed"`
|
|
|
|
|
}
|
2026-09-30 11:46:31 +07:00
|
|
|
|
|
|
|
|
// PointPaymentPreview is GET /orders/:id/point-payment/preview (docs/prd-point-coin.md
|
|
|
|
|
// F9): whether the order can be paid with EnakPoint and at most how much, for the
|
|
|
|
|
// cashier's "use maximum" button.
|
|
|
|
|
type PointPaymentPreview struct {
|
|
|
|
|
OrderID uuid.UUID `json:"order_id"`
|
|
|
|
|
CustomerID *uuid.UUID `json:"customer_id"`
|
|
|
|
|
Eligible bool `json:"eligible"`
|
|
|
|
|
// Why not, when not eligible.
|
|
|
|
|
Reason string `json:"reason,omitempty"`
|
|
|
|
|
PointBalance int64 `json:"point_balance"`
|
|
|
|
|
PointValue int64 `json:"point_value"`
|
|
|
|
|
RemainingAmount float64 `json:"remaining_amount"`
|
|
|
|
|
MinPaymentPoints int64 `json:"min_payment_points"`
|
|
|
|
|
MaxPaymentPercent int64 `json:"max_payment_percent"`
|
|
|
|
|
MaxPoints int64 `json:"max_points"`
|
|
|
|
|
// Rupiah covered by MaxPoints.
|
|
|
|
|
MaxAmount int64 `json:"max_amount"`
|
|
|
|
|
}
|