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>
162 lines
5.6 KiB
Go
162 lines
5.6 KiB
Go
package processor
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"apskel-pos-be/internal/constants"
|
|
"apskel-pos-be/internal/entities"
|
|
"apskel-pos-be/internal/models"
|
|
"apskel-pos-be/internal/repository"
|
|
)
|
|
|
|
// walletTraceRepoFake reads the fake wallet the way the trace repository reads the
|
|
// tables.
|
|
type walletTraceRepoFake struct{ e *walletMoveEnv }
|
|
|
|
func (f walletTraceRepoFake) GetTransaction(_ context.Context, organizationID, id uuid.UUID) (*entities.WalletTransaction, error) {
|
|
for _, tx := range f.e.repo.transactions {
|
|
if tx.ID == id && tx.OrganizationID == organizationID {
|
|
c := *tx
|
|
return &c, nil
|
|
}
|
|
}
|
|
return nil, repository.ErrWalletTransactionNotFound
|
|
}
|
|
|
|
func (f walletTraceRepoFake) GetTransactions(_ context.Context, ids []uuid.UUID) ([]entities.WalletTransaction, error) {
|
|
var out []entities.WalletTransaction
|
|
for _, tx := range f.e.repo.transactions {
|
|
if containsID(ids, tx.ID) {
|
|
out = append(out, *tx)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (f walletTraceRepoFake) ListAllocations(ctx context.Context, id uuid.UUID) ([]entities.WalletLotAllocation, error) {
|
|
return f.e.repo.ListAllocationsByTransaction(ctx, id)
|
|
}
|
|
|
|
func (f walletTraceRepoFake) ListLotsBySource(ctx context.Context, id uuid.UUID) ([]entities.WalletLot, error) {
|
|
return f.e.repo.ListLotsBySourceTransaction(ctx, id)
|
|
}
|
|
|
|
func (f walletTraceRepoFake) GetLots(ctx context.Context, ids []uuid.UUID) ([]entities.WalletLot, error) {
|
|
return f.e.repo.GetLotsByIDs(ctx, ids)
|
|
}
|
|
|
|
func (f walletTraceRepoFake) CustomerNames(_ context.Context, ids []uuid.UUID) (map[uuid.UUID]string, error) {
|
|
names := map[uuid.UUID]string{}
|
|
for _, id := range ids {
|
|
if c, ok := f.e.customers.byID[id]; ok {
|
|
names[id] = c.Name
|
|
}
|
|
}
|
|
return names, nil
|
|
}
|
|
|
|
func containsID(ids []uuid.UUID, id uuid.UUID) bool {
|
|
for _, v := range ids {
|
|
if v == id {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func findRow(t *testing.T, e *walletMoveEnv, customerID uuid.UUID, txType string) *entities.WalletTransaction {
|
|
t.Helper()
|
|
for _, tx := range e.repo.transactions {
|
|
if tx.CustomerID == customerID && tx.Type == txType {
|
|
return tx
|
|
}
|
|
}
|
|
t.Fatalf("no %s row for %s", txType, customerID)
|
|
return nil
|
|
}
|
|
|
|
// The example of §8: A has 100 from #ORD-1 and 50 from #ORD-2, sends 120 to B, and B
|
|
// redeems 30. Tracing B's redemption leads to A's order #ORD-1.
|
|
func TestWalletTrace_RedemptionLeadsBackToTheSendersOrder(t *testing.T) {
|
|
e := newWalletMoveEnv(t)
|
|
a := e.member("Anita", "081200005678")
|
|
b := e.member("Budi Santoso", "081234561234")
|
|
ord1 := earn(a, 100, e.at(30*24*time.Hour))
|
|
ord1.Description = "Belanja #ORD-1"
|
|
ord2 := earn(a, 50, e.at(60*24*time.Hour))
|
|
ord2.Description = "Belanja #ORD-2"
|
|
e.credit(t, ord1)
|
|
e.credit(t, ord2)
|
|
_, err := e.transfers(nil).Transfer(e.ctx, a, sendPoints(120, "081234561234"), "482913", "key-1", models.CustomerPinRequestInfo{})
|
|
require.NoError(t, err)
|
|
payment, err := e.p.Debit(e.ctx, redeem(b, 30))
|
|
require.NoError(t, err)
|
|
|
|
p := NewWalletTraceProcessor(walletTraceRepoFake{e})
|
|
trace, err := p.Trace(e.ctx, e.org, payment.Transaction.ID)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, constants.WalletTxTypeRewardRedeem, trace.Transaction.Type)
|
|
assert.Equal(t, "Budi Santoso", trace.Transaction.Customer.Name)
|
|
require.Len(t, trace.Lots, 1)
|
|
assert.Equal(t, int64(30), trace.Lots[0].Amount)
|
|
chain := trace.Lots[0].Chain
|
|
require.Len(t, chain, 2)
|
|
assert.Equal(t, constants.WalletTxTypeTransferIn, chain[0].Source.Type)
|
|
assert.Equal(t, b, chain[0].Source.Customer.ID)
|
|
assert.Equal(t, constants.WalletTxTypeEarn, chain[1].Source.Type)
|
|
assert.Equal(t, "Anita", chain[1].Source.Customer.Name)
|
|
assert.Equal(t, "Belanja #ORD-1", chain[1].Source.Description)
|
|
assert.Equal(t, constants.WalletRefTypeOrder, chain[1].Source.ReferenceType)
|
|
assert.Equal(t, ord1.ReferenceID, chain[1].Source.ReferenceID)
|
|
assert.Nil(t, chain[1].Lot.OriginLotID, "the chain ends at the lot the order created")
|
|
assert.Equal(t, *chain[0].Lot.OriginLotID, chain[1].Lot.ID)
|
|
}
|
|
|
|
func TestWalletTrace_DebitAndCreditOfATransfer(t *testing.T) {
|
|
e := newWalletMoveEnv(t)
|
|
a := e.member("Anita", "081200005678")
|
|
b := e.member("Budi", "081234561234")
|
|
e.credit(t, earn(a, 100, e.at(time.Hour)))
|
|
e.credit(t, earn(a, 50, nil))
|
|
_, err := e.transfers(nil).Transfer(e.ctx, a, sendPoints(120, "081234561234"), "482913", "key-1", models.CustomerPinRequestInfo{})
|
|
require.NoError(t, err)
|
|
p := NewWalletTraceProcessor(walletTraceRepoFake{e})
|
|
|
|
// The sending side took from both of A's lots, which were A's own earnings.
|
|
out, err := p.Trace(e.ctx, e.org, findRow(t, e, a, constants.WalletTxTypeTransferOut).ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, out.Lots, 2)
|
|
assert.Equal(t, []int64{100, 20}, []int64{out.Lots[0].Amount, out.Lots[1].Amount})
|
|
for _, lot := range out.Lots {
|
|
require.Len(t, lot.Chain, 1)
|
|
assert.Equal(t, constants.WalletTxTypeEarn, lot.Chain[0].Source.Type)
|
|
}
|
|
|
|
// The receiving side created two lots, each carried over from one of A's.
|
|
in, err := p.Trace(e.ctx, e.org, findRow(t, e, b, constants.WalletTxTypeTransferIn).ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, in.Lots, 2)
|
|
for _, lot := range in.Lots {
|
|
require.Len(t, lot.Chain, 2)
|
|
assert.Equal(t, b, lot.Chain[0].Source.Customer.ID)
|
|
assert.Equal(t, a, lot.Chain[1].Source.Customer.ID)
|
|
assert.Equal(t, lot.Chain[1].Lot.ExpiresAt, lot.Chain[0].Lot.ExpiresAt)
|
|
}
|
|
}
|
|
|
|
func TestWalletTrace_OtherOrganizationsRowsAreNotFound(t *testing.T) {
|
|
e := newWalletMoveEnv(t)
|
|
a := e.member("Anita", "081200005678")
|
|
res := e.credit(t, earn(a, 10, nil))
|
|
|
|
_, err := NewWalletTraceProcessor(walletTraceRepoFake{e}).Trace(e.ctx, uuid.New(), res.Transaction.ID)
|
|
assert.ErrorIs(t, err, repository.ErrWalletTransactionNotFound)
|
|
}
|