refactor(loyalty): remove what is left of tokens

Tokens are EnakCoin and no app uses the token names any more, so their
compatibility layer goes:

- GET /customer/tokens and its handler, service, processor and response
  types.
- total_tokens and tokens_history on GET /customer/wallet; last_updated
  now comes from the most recent row of either currency.
- token_used and tokens_remaining on game and spin responses, and
  sort_by=token_used on the game play list.
- TOKENS as a campaign type and reward type, with the mapping to COINS:
  migration 000092 already renamed the stored values.

The customer_tokens table and its entity stay, as cmd/wallet-migrate still
reads them, and LEGACY_TOKENS stays as the reference of the MIGRATION rows
it wrote. The docs list the removed names and their replacements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
efrilm
2026-09-30 16:55:27 +07:00
co-authored by Claude Opus 5.5
parent 9b21af8892
commit c988a79d3b
26 changed files with 57 additions and 268 deletions
+4 -4
View File
@@ -70,7 +70,7 @@ func (p *campaignProcessor) ListCampaigns(ctx context.Context, req *contract.Lis
Page: req.Page,
Limit: req.Limit,
Search: req.Search,
Type: string(entities.NormalizeCampaignType(req.Type)),
Type: req.Type,
IsActive: req.IsActive,
ShowOnApp: req.ShowOnApp,
StartDate: req.StartDate,
@@ -178,7 +178,7 @@ func (p *campaignRuleProcessor) CreateCampaignRule(ctx context.Context, req *con
CampaignID: req.CampaignID,
RuleType: entities.RuleType(req.RuleType),
ConditionValue: req.ConditionValue,
RewardType: entities.NormalizeCampaignRewardType(req.RewardType),
RewardType: entities.CampaignRewardType(req.RewardType),
RewardValue: req.RewardValue,
RewardSubtype: (*entities.RewardSubtype)(req.RewardSubtype),
RewardRefID: req.RewardRefID,
@@ -218,7 +218,7 @@ func (p *campaignRuleProcessor) ListCampaignRules(ctx context.Context, req *cont
Limit: req.Limit,
CampaignID: req.CampaignID,
RuleType: req.RuleType,
RewardType: string(entities.NormalizeCampaignRewardType(req.RewardType)),
RewardType: req.RewardType,
}
// Get from repository
@@ -247,7 +247,7 @@ func (p *campaignRuleProcessor) UpdateCampaignRule(ctx context.Context, req *con
CampaignID: req.CampaignID,
RuleType: entities.RuleType(req.RuleType),
ConditionValue: req.ConditionValue,
RewardType: entities.NormalizeCampaignRewardType(req.RewardType),
RewardType: entities.CampaignRewardType(req.RewardType),
RewardValue: req.RewardValue,
RewardSubtype: (*entities.RewardSubtype)(req.RewardSubtype),
RewardRefID: req.RewardRefID,
@@ -22,7 +22,7 @@ func NewCustomerPointsProcessor(walletQuery *WalletQueryProcessor, gameRepo *rep
}
}
// The three endpoints below are the customer app's balance screens. They read the
// The endpoints below are the customer app's balance screens. They read the
// EnakPoint & EnakCoin wallet; customer_points and customer_tokens are no longer used
// for balances (docs/prd-point-coin.md §9).
@@ -42,22 +42,6 @@ func (p *CustomerPointsProcessor) GetCustomerTotalPointsAPI(ctx context.Context,
}, nil
}
func (p *CustomerPointsProcessor) GetCustomerTotalTokensAPI(ctx context.Context, customerID string) (*models.GetCustomerTokensResponse, error) {
id, err := parseWalletCustomerID(customerID)
if err != nil {
return nil, err
}
data, err := p.walletQuery.LegacyTokens(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get customer tokens: %w", err)
}
return &models.GetCustomerTokensResponse{
Status: "SUCCESS",
Message: "Customer tokens retrieved successfully.",
Data: data,
}, nil
}
func (p *CustomerPointsProcessor) GetCustomerWalletAPI(ctx context.Context, customerID string) (*models.GetCustomerWalletResponse, error) {
id, err := parseWalletCustomerID(customerID)
if err != nil {
@@ -99,10 +99,6 @@ func (p *GamePlayProcessor) ListGamePlays(ctx context.Context, query *models.Lis
}
offset := (query.Page - 1) * query.Limit
// The column was renamed; the old name still sorts the same way.
if query.SortBy == "token_used" {
query.SortBy = "coins_used"
}
// Get game plays from repository
gamePlays, total, err := p.gamePlayRepo.List(
+2 -35
View File
@@ -82,10 +82,6 @@ func (p *WalletQueryProcessor) Summary(ctx context.Context, customerID uuid.UUID
if err != nil {
return nil, err
}
tokens, err := p.legacyTokens(ctx, customerID, balances)
if err != nil {
return nil, err
}
data := &models.GetCustomerWalletResponseData{
PointBalance: balances[constants.WalletCurrencyPoint],
@@ -94,13 +90,10 @@ func (p *WalletQueryProcessor) Summary(ctx context.Context, customerID uuid.UUID
PointDiscountValue: balances[constants.WalletCurrencyPoint] * pointValue,
RecentTransactions: recent,
TotalPoints: points.TotalPoints,
TotalTokens: tokens.TotalTokens,
PointsHistory: points.PointsHistory,
TokensHistory: tokens.TokensHistory,
LastUpdated: points.LastUpdated,
}
if tokens.LastUpdated.After(data.LastUpdated) {
data.LastUpdated = tokens.LastUpdated
if len(recent) > 0 {
data.LastUpdated = recent[0].CreatedAt
}
for _, e := range expiring {
entry := &models.CustomerWalletExpiring{Amount: e.Amount, Date: e.Date}
@@ -161,32 +154,6 @@ func (p *WalletQueryProcessor) legacyPoints(ctx context.Context, customerID uuid
return data, nil
}
// LegacyTokens is the pre-wallet GET /customer/tokens. Tokens are EnakCoin now.
func (p *WalletQueryProcessor) LegacyTokens(ctx context.Context, customerID uuid.UUID) (*models.GetCustomerTokensResponseData, error) {
balances, err := p.repo.SpendableBalances(ctx, customerID, p.now())
if err != nil {
return nil, err
}
return p.legacyTokens(ctx, customerID, balances)
}
func (p *WalletQueryProcessor) legacyTokens(ctx context.Context, customerID uuid.UUID, balances map[string]int64) (*models.GetCustomerTokensResponseData, error) {
recent, err := p.recent(ctx, customerID, constants.WalletCurrencyCoin, walletLegacyHistorySize)
if err != nil {
return nil, err
}
data := &models.GetCustomerTokensResponseData{TotalTokens: balances[constants.WalletCurrencyCoin]}
for _, tx := range recent {
data.TokensHistory = append(data.TokensHistory, models.TokensHistoryItem{
ID: tx.ID.String(), Tokens: tx.Amount, Type: tx.Type, Description: tx.Description, CreatedAt: tx.CreatedAt,
})
}
if len(recent) > 0 {
data.LastUpdated = recent[0].CreatedAt
}
return data, nil
}
func (p *WalletQueryProcessor) recent(ctx context.Context, customerID uuid.UUID, currency string, limit int) ([]models.CustomerWalletTransaction, error) {
rows, _, err := p.repo.ListTransactions(ctx, repository.WalletTransactionFilter{
CustomerID: customerID, Currency: currency, Limit: limit,
@@ -122,7 +122,6 @@ func TestWalletQueryProcessor_SummaryShowsWhereEachRowCameFromOrWent(t *testing.
// The pre-wallet fields are still filled, from the wallet.
assert.Equal(t, int64(250), data.TotalPoints)
assert.Equal(t, int64(8), data.TotalTokens)
require.Len(t, data.PointsHistory, 2)
assert.Equal(t, int64(-50), data.PointsHistory[0].Points)
assert.Equal(t, constants.WalletTxTypePayment, data.PointsHistory[0].Type)
@@ -174,12 +173,6 @@ func TestWalletQueryProcessor_LegacyEndpointsSplitByCurrency(t *testing.T) {
assert.Equal(t, int64(100), points.TotalPoints)
require.Len(t, points.PointsHistory, 1)
assert.Equal(t, int64(100), points.PointsHistory[0].Points)
tokens, err := p.LegacyTokens(context.Background(), uuid.New())
require.NoError(t, err)
assert.Equal(t, int64(8), tokens.TotalTokens)
require.Len(t, tokens.TokensHistory, 1)
assert.Equal(t, int64(8), tokens.TokensHistory[0].Tokens)
}
func TestWalletQueryProcessor_TransactionsQuery(t *testing.T) {