Files
apskel-pos-backend/internal/processor/point_refund_db_test.go
T
2026-09-30 15:31:44 +07:00

152 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/models"
"apskel-pos-be/internal/repository"
)
func (e *pointPaymentEnv) sum(query string, args ...any) int64 {
e.t.Helper()
var n int64
require.NoError(e.t, e.db.Raw(query, args...).Scan(&n).Error)
return n
}
func (e *pointPaymentEnv) returned(order uuid.UUID) int64 {
return e.sum(`SELECT COALESCE(SUM(t.amount), 0) FROM wallet_transactions t
JOIN payments p ON p.id = t.reference_id
WHERE p.order_id = ? AND t.type = 'PAYMENT_REFUND'`, order)
}
func (e *pointPaymentEnv) assertReconciled(customers ...uuid.UUID) {
e.t.Helper()
found, err := repository.NewWalletReconciliationRepository(e.db).FindDiscrepancies(context.Background(), 1000)
require.NoError(e.t, err)
for _, d := range found {
for _, c := range customers {
assert.NotEqual(e.t, c, d.CustomerID, d.Check)
}
}
}
func TestPointRefund_VoidReturnsEverythingToItsExpiry(t *testing.T) {
e := newPointPaymentEnv(t)
customer := e.customerWith(0)
expires := time.Now().Add(30 * 24 * time.Hour).Truncate(time.Second)
require.NoError(t, repository.NewTxManager(e.db).WithTransaction(context.Background(), func(ctx context.Context) error {
_, err := NewWalletProcessor(repository.NewWalletRepository(e.db)).Credit(ctx, WalletCreditInput{
WalletEntry: WalletEntry{CustomerID: customer, Currency: constants.WalletCurrencyPoint, Type: constants.WalletTxTypeMigration,
Amount: 40000, ReferenceType: constants.WalletRefTypeLegacyPoints, ReferenceID: uuid.New(), Description: "Saldo awal"},
Lots: []WalletLotInput{{Amount: 40000, ExpiresAt: &expires}},
})
return err
}))
// Part of the order paid with EnakPoint, then the order is voided.
order := e.order(customer, 50000)
_, err := e.payPoints(order, 30000, e.code(customer))
require.NoError(t, err)
assert.Equal(t, int64(10000), e.balance(customer))
require.NoError(t, e.orders.VoidOrder(e.ctx, &models.VoidOrderRequest{OrderID: order, Type: "ALL", Reason: "batal"}, e.cashier))
assert.Equal(t, int64(30000), e.returned(order))
assert.Equal(t, int64(40000), e.balance(customer))
var expiry time.Time
require.NoError(t, e.db.Raw(`SELECT l.expires_at FROM wallet_lots l JOIN wallet_transactions t ON t.id = l.source_transaction_id
WHERE t.customer_id = ? AND t.type = 'PAYMENT_REFUND'`, customer).Scan(&expiry).Error)
assert.WithinDuration(t, expires, expiry, time.Second, "returned EnakPoint keep the expiry they had")
// Calling it again returns nothing more.
e.orders.onOrderRefunded(e.ctx, order)
assert.Equal(t, int64(30000), e.returned(order))
e.assertReconciled(customer)
}
func TestPointRefund_PartialRefundFloors(t *testing.T) {
e := newPointPaymentEnv(t)
customer := e.customerWith(100000)
order := e.order(customer, 50000)
payment, err := e.payPoints(order, 50000, e.code(customer))
require.NoError(t, err)
// Rp 12.345,67 back at Rp 1 a point: 12.345 EnakPoint; the 67 sen are lost (Q13).
require.NoError(t, e.orders.RefundPayment(e.ctx, payment.ID, 12345.67, "sebagian", e.cashier))
assert.Equal(t, int64(12345), e.returned(order))
assert.Equal(t, int64(50000+12345), e.balance(customer))
e.assertReconciled(customer)
}
func TestPointRefund_UsesTheFrozenValue(t *testing.T) {
e := newPointPaymentEnv(t)
settings := NewLoyaltySettingsProcessor(repository.NewLoyaltySettingsRepository(e.db), repository.NewTxManager(e.db))
setValue := func(v int64) {
s, err := settings.Organization(context.Background(), e.org)
require.NoError(t, err)
s.PointValue = v
_, _, err = settings.UpdateOrganization(context.Background(), e.org, e.cashier, *s)
require.NoError(t, err)
}
setValue(100)
customer := e.customerWith(1000)
order := e.order(customer, 50000)
payment, err := e.payPoints(order, 500, e.code(customer))
require.NoError(t, err)
assert.Equal(t, 50000.0, payment.Amount, "500 × Rp 100")
// The value changes before the refund; the customer still gets back what they used.
setValue(250)
require.NoError(t, e.orders.RefundPayment(e.ctx, payment.ID, 50000, "semua", e.cashier))
assert.Equal(t, int64(500), e.returned(order), "50.000 / the frozen Rp 100, not the new Rp 250")
assert.Equal(t, int64(1000), e.balance(customer))
}
func TestPointRefund_NoCashForTheEnakPointPart(t *testing.T) {
e := newPointPaymentEnv(t)
customer := e.customerWith(100000)
order := e.order(customer, 50000)
_, err := e.payPoints(order, 20000, e.code(customer))
require.NoError(t, err)
_, err = e.orders.CreatePayment(e.ctx, &models.CreatePaymentRequest{OrderID: order, PaymentMethodID: e.cash, Amount: 30000})
require.NoError(t, err)
amount := 40000.0
err = e.orders.RefundOrder(e.ctx, order, &models.RefundOrderRequest{RefundAmount: &amount}, e.cashier)
assert.ErrorIs(t, err, ErrPointPaymentRejected, "Rp 40.000 in cash would include EnakPoint")
var refunded float64
require.NoError(t, e.db.Raw(`SELECT refund_amount FROM orders WHERE id = ?`, order).Scan(&refunded).Error)
assert.Zero(t, refunded, "nothing was written")
amount = 30000
require.NoError(t, e.orders.RefundOrder(e.ctx, order, &models.RefundOrderRequest{RefundAmount: &amount}, e.cashier), "the cash part can be refunded")
assert.Zero(t, e.returned(order), "and no EnakPoint came back for it")
}
// Giving back the EnakPoint part does not take earning back: that part never earned.
func TestPointRefund_DoesNotReverseEarning(t *testing.T) {
e := newPointPaymentEnv(t)
customer := e.customerWith(100000)
order := e.order(customer, 87500)
pointPayment, err := e.payPoints(order, 20000, e.code(customer))
require.NoError(t, err)
_, err = e.orders.CreatePayment(e.ctx, &models.CreatePaymentRequest{OrderID: order, PaymentMethodID: e.cash, Amount: 67500})
require.NoError(t, err)
earned := e.sum(`SELECT COALESCE(SUM(amount), 0) FROM wallet_transactions WHERE reference_id = ? AND type = 'EARN'`, order)
require.Equal(t, int64(675), earned)
require.NoError(t, e.orders.RefundPayment(e.ctx, pointPayment.ID, 20000, "kembali", e.cashier))
assert.Equal(t, int64(20000), e.returned(order))
reversed := e.sum(`SELECT COALESCE(SUM(-amount), 0) FROM wallet_transactions WHERE reference_id = ? AND type = 'EARN_REVERSAL'`, order)
assert.Zero(t, reversed, "the EnakPoint part never earned, so giving it back takes nothing")
e.assertReconciled(customer)
}