Files
apskel-pos-backend/internal/models/wallet.go
T
efrilmandClaude Opus 5.5 2c9753fae7 feat(loyalty): remove paying with EnakPoint
EnakPoint can only be redeemed for vouchers now: it can no longer pay for
orders and is never cashed out (docs/enakgame-prd.md §3.2, EG-001,
EG-002). No order was ever paid with EnakPoint, so there is no data to
move.

Removed:
- POST /customer/wallet/payment-code, POST /customer/orders/:id/pay-with-points
  and GET /orders/:id/point-payment/preview, with their processors,
  repositories, services, handlers and tests.
- The point payment method type: paying, splitting and refunding with it,
  the outlet filter on the method list, and the system-method guard.
- points and payment_code on CreatePayment; points_used and point_value
  on payments; accepts_point_payment on the customer outlets.
- The outlet point_payment settings. A PUT that still sends them is
  rejected as an unknown field.
- The EnakPoint split in the payment method analytics.
- PAYMENT and PAYMENT_REFUND from the wallet type rules. Tests that used
  them as a generic EnakPoint debit use REWARD_REDEEM.
- The EnakPoint-paid part from the earning basis, which is
  subtotal − discount again.

Migration 000102 drops the trigger, the point methods and their index,
the payments columns, and the outlet settings, and restores the method
type CHECK without point. payments.payment_method_id is ON DELETE
RESTRICT, so it fails rather than lose a payment made with EnakPoint.

The integration docs list the removed endpoints and fields, and the
EnakPoint & EnakCoin PRD and tasks note what is superseded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:48:29 +07:00

148 lines
5.7 KiB
Go

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"`
}
// 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"`
}
// CustomerWalletExpiringList is GET /customer/wallet/expiring (docs/prd-point-coin.md
// F6): everything that will expire, per currency and day, soonest first.
type CustomerWalletExpiringList struct {
Point []CustomerWalletExpiring `json:"point"`
Coin []CustomerWalletExpiring `json:"coin"`
}