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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3ebc09f818
commit
2c9753fae7
@@ -300,16 +300,16 @@ func earn(customerID uuid.UUID, amount int64, expiresAt *time.Time) WalletCredit
|
||||
}
|
||||
}
|
||||
|
||||
func pay(customerID uuid.UUID, amount int64) WalletDebitInput {
|
||||
func redeem(customerID uuid.UUID, amount int64) WalletDebitInput {
|
||||
return WalletDebitInput{WalletEntry: WalletEntry{
|
||||
CustomerID: customerID,
|
||||
Currency: constants.WalletCurrencyPoint,
|
||||
Type: constants.WalletTxTypePayment,
|
||||
Type: constants.WalletTxTypeRewardRedeem,
|
||||
Amount: amount,
|
||||
ReferenceType: constants.WalletRefTypePayment,
|
||||
ReferenceType: constants.WalletRefTypeRewardRedemption,
|
||||
ReferenceID: uuid.New(),
|
||||
OutletID: ptr(uuid.New()),
|
||||
Description: "Bayar",
|
||||
Description: "Tukar voucher",
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ func TestWalletProcessor_DebitAcrossSeveralLots(t *testing.T) {
|
||||
second := e.credit(t, earn(c, 50, e.at(2*time.Hour))).Lots[0]
|
||||
third := e.credit(t, earn(c, 40, e.at(3*time.Hour))).Lots[0]
|
||||
|
||||
res, err := e.p.Debit(e.ctx, pay(c, 70))
|
||||
res, err := e.p.Debit(e.ctx, redeem(c, 70))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, int64(-70), res.Transaction.Amount)
|
||||
@@ -415,7 +415,7 @@ func TestWalletProcessor_DebitFollowsLotOrder(t *testing.T) {
|
||||
|
||||
var order []uuid.UUID
|
||||
for i := 0; i < 4; i++ {
|
||||
res, err := e.p.Debit(e.ctx, pay(c, 10))
|
||||
res, err := e.p.Debit(e.ctx, redeem(c, 10))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res.Allocations, 1)
|
||||
order = append(order, res.Allocations[0].LotID)
|
||||
@@ -425,7 +425,7 @@ func TestWalletProcessor_DebitFollowsLotOrder(t *testing.T) {
|
||||
// The expired lot still counts in the balance until the expiry job removes it,
|
||||
// but it cannot be spent (§7.3).
|
||||
assert.Equal(t, int64(10), e.balance(t, c))
|
||||
_, err := e.p.Debit(e.ctx, pay(c, 10))
|
||||
_, err := e.p.Debit(e.ctx, redeem(c, 10))
|
||||
assert.ErrorIs(t, err, repository.ErrWalletInsufficientBalance)
|
||||
}
|
||||
|
||||
@@ -434,14 +434,14 @@ func TestWalletProcessor_DebitOverBalanceChangesNothing(t *testing.T) {
|
||||
c := e.customer()
|
||||
e.credit(t, earn(c, 50, nil))
|
||||
|
||||
_, err := e.p.Debit(e.ctx, pay(c, 51))
|
||||
_, err := e.p.Debit(e.ctx, redeem(c, 51))
|
||||
assert.ErrorIs(t, err, repository.ErrWalletInsufficientBalance)
|
||||
assert.Equal(t, int64(50), e.balance(t, c))
|
||||
assert.Len(t, e.repo.transactions, 1)
|
||||
assert.Empty(t, e.repo.allocations)
|
||||
|
||||
// A customer who never had a wallet has nothing to spend.
|
||||
_, err = e.p.Debit(e.ctx, pay(e.customer(), 1))
|
||||
_, err = e.p.Debit(e.ctx, redeem(e.customer(), 1))
|
||||
assert.ErrorIs(t, err, repository.ErrWalletInsufficientBalance)
|
||||
}
|
||||
|
||||
@@ -467,7 +467,7 @@ func TestWalletProcessor_DebitUpToWithShortfall(t *testing.T) {
|
||||
e := newWalletTestEnv(t)
|
||||
c := e.customer()
|
||||
earned := e.credit(t, earn(c, 100, nil))
|
||||
_, err := e.p.Debit(e.ctx, pay(c, 70))
|
||||
_, err := e.p.Debit(e.ctx, redeem(c, 70))
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := e.p.DebitUpTo(e.ctx, reversal(c, 100, earned))
|
||||
@@ -510,7 +510,7 @@ func TestWalletProcessor_DebitRejectsSomeoneElsesLot(t *testing.T) {
|
||||
e.credit(t, earn(a, 10, nil))
|
||||
other := e.credit(t, earn(b, 10, nil))
|
||||
|
||||
in := pay(a, 5)
|
||||
in := redeem(a, 5)
|
||||
in.PreferredLotIDs = []uuid.UUID{other.Lots[0].ID}
|
||||
_, err := e.p.Debit(e.ctx, in)
|
||||
assert.ErrorIs(t, err, ErrWalletInvalidEntry)
|
||||
@@ -567,7 +567,7 @@ func TestWalletProcessor_IdempotentDebit(t *testing.T) {
|
||||
c := e.customer()
|
||||
e.credit(t, earn(c, 30, e.at(time.Hour)))
|
||||
e.credit(t, earn(c, 30, nil))
|
||||
in := pay(c, 40)
|
||||
in := redeem(c, 40)
|
||||
in.IdempotencyKey = "pay:1"
|
||||
|
||||
first, err := e.p.Debit(e.ctx, in)
|
||||
@@ -585,7 +585,7 @@ func TestWalletProcessor_IdempotentDebitUpToKeepsShortfall(t *testing.T) {
|
||||
e := newWalletTestEnv(t)
|
||||
c := e.customer()
|
||||
earned := e.credit(t, earn(c, 100, nil))
|
||||
_, err := e.p.Debit(e.ctx, pay(c, 60))
|
||||
_, err := e.p.Debit(e.ctx, redeem(c, 60))
|
||||
require.NoError(t, err)
|
||||
in := reversal(c, 100, earned)
|
||||
in.IdempotencyKey = "reverse:order-1"
|
||||
@@ -614,7 +614,7 @@ func TestWalletProcessor_IdempotencyKeyReusedForAnotherOperation(t *testing.T) {
|
||||
_, err := e.p.Credit(e.ctx, other)
|
||||
assert.ErrorIs(t, err, ErrWalletIdempotencyConflict)
|
||||
|
||||
debit := pay(c, 100)
|
||||
debit := redeem(c, 100)
|
||||
debit.IdempotencyKey = "k"
|
||||
_, err = e.p.Debit(e.ctx, debit)
|
||||
assert.ErrorIs(t, err, ErrWalletIdempotencyConflict)
|
||||
@@ -673,8 +673,8 @@ func TestWalletProcessor_RejectsEntriesThatBreakTheTypeRules(t *testing.T) {
|
||||
credits := map[string]func(*WalletCreditInput){
|
||||
"unknown type": func(in *WalletCreditInput) { in.Type = "BONUS" },
|
||||
"debit-only type as credit": func(in *WalletCreditInput) {
|
||||
in.Type = constants.WalletTxTypePayment
|
||||
in.ReferenceType = constants.WalletRefTypePayment
|
||||
in.Type = constants.WalletTxTypeRewardRedeem
|
||||
in.ReferenceType = constants.WalletRefTypeRewardRedemption
|
||||
},
|
||||
"unknown currency": func(in *WalletCreditInput) { in.Currency = "GOLD" },
|
||||
"zero amount": func(in *WalletCreditInput) { in.Amount = 0; in.Lots = nil },
|
||||
@@ -701,10 +701,6 @@ func TestWalletProcessor_RejectsEntriesThatBreakTheTypeRules(t *testing.T) {
|
||||
in.ReferenceType = constants.WalletRefTypeWalletTx
|
||||
in.CounterpartyCustomerID = ptr(uuid.New())
|
||||
},
|
||||
"PAYMENT_REFUND without source": func(in *WalletCreditInput) {
|
||||
in.Type = constants.WalletTxTypePaymentRefund
|
||||
in.ReferenceType = constants.WalletRefTypePayment
|
||||
},
|
||||
"ADJUSTMENT without reason": func(in *WalletCreditInput) {
|
||||
in.Type = constants.WalletTxTypeAdjustment
|
||||
in.ReferenceType = constants.WalletRefTypeUser
|
||||
@@ -744,7 +740,7 @@ func TestWalletProcessor_RejectsEntriesThatBreakTheTypeRules(t *testing.T) {
|
||||
in.Type = constants.WalletTxTypeMigration
|
||||
in.ReferenceType = constants.WalletRefTypeLegacyPoints
|
||||
},
|
||||
"PAYMENT in COIN": func(in *WalletDebitInput) { in.Currency = constants.WalletCurrencyCoin },
|
||||
"REWARD_REDEEM in COIN": func(in *WalletDebitInput) { in.Currency = constants.WalletCurrencyCoin },
|
||||
"GAME_SPEND in POINT": func(in *WalletDebitInput) {
|
||||
in.Type = constants.WalletTxTypeGameSpend
|
||||
in.ReferenceType = constants.WalletRefTypeGamePlay
|
||||
@@ -772,7 +768,7 @@ func TestWalletProcessor_RejectsEntriesThatBreakTheTypeRules(t *testing.T) {
|
||||
e := newWalletTestEnv(t)
|
||||
e.repo.customers[c] = e.org
|
||||
e.credit(t, earn(c, 100, nil))
|
||||
in := pay(c, 10)
|
||||
in := redeem(c, 10)
|
||||
mutate(&in)
|
||||
_, err := e.p.Debit(e.ctx, in)
|
||||
assert.ErrorIs(t, err, ErrWalletInvalidEntry)
|
||||
|
||||
Reference in New Issue
Block a user