Revert "feat(loyalty): EnakPoint & EnakCoin" (#32)
This reverts merge commit645da30, returning main tof0ff59f. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
645da3048e
commit
4e24f9bbb0
@@ -1,157 +0,0 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/repository"
|
||||
)
|
||||
|
||||
// RefundForOrder gives EnakPoint back for the order's EnakPoint payments, as far as the
|
||||
// order has been voided or those payments refunded (docs/prd-point-coin.md F9, K7):
|
||||
//
|
||||
// - void: every EnakPoint used on the order;
|
||||
// - a refunded EnakPoint payment: floor(refunded rupiah / the frozen point value),
|
||||
// so a later change of the point value does not change how many come back, and a
|
||||
// rupiah remainder below one EnakPoint is lost (Q13).
|
||||
//
|
||||
// Never more than the payment used, and only what has not come back yet, so it can be
|
||||
// called again safely. Returned EnakPoint go back into lots with the expiry of the lots
|
||||
// they were taken from, but at least seven days from the refund (note N4, decided). It
|
||||
// returns how many came back in total.
|
||||
func (p *PointPaymentProcessor) RefundForOrder(ctx context.Context, orderID uuid.UUID) (int64, error) {
|
||||
order, err := p.repo.GetOrder(ctx, orderID, false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
payments, err := p.repo.ListPointPayments(ctx, orderID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var returned int64
|
||||
for _, payment := range payments {
|
||||
if payment.LedgerID == nil || payment.CustomerID == nil {
|
||||
continue
|
||||
}
|
||||
target := pointRefundTarget(order.IsVoid, payment)
|
||||
if target == 0 {
|
||||
continue
|
||||
}
|
||||
var n int64
|
||||
err := p.tx.WithTransaction(ctx, func(ctx context.Context) error {
|
||||
if err := p.wallet.LockWallet(ctx, *payment.CustomerID); err != nil {
|
||||
return err
|
||||
}
|
||||
allocations, err := p.repo.PaymentAllocations(ctx, *payment.LedgerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
refunded, err := p.repo.RefundedByOriginLot(ctx, *payment.LedgerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var already int64
|
||||
for _, amount := range refunded {
|
||||
already += amount
|
||||
}
|
||||
toReturn := target - already
|
||||
if toReturn <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fill the lots the payment took from, each up to what it gave.
|
||||
var lots []WalletLotInput
|
||||
left := toReturn
|
||||
for _, a := range allocations {
|
||||
if left == 0 {
|
||||
break
|
||||
}
|
||||
room := a.Amount - refunded[a.LotID]
|
||||
if room <= 0 {
|
||||
continue
|
||||
}
|
||||
take := min(room, left)
|
||||
left -= take
|
||||
lotID := a.LotID
|
||||
lots = append(lots, WalletLotInput{Amount: take, ExpiresAt: RefundExpiry(a.ExpiresAt, p.now()), OriginLotID: &lotID})
|
||||
}
|
||||
toReturn -= left
|
||||
|
||||
ledgerID := *payment.LedgerID
|
||||
if _, err := p.wallet.Credit(ctx, WalletCreditInput{
|
||||
WalletEntry: WalletEntry{
|
||||
CustomerID: *payment.CustomerID,
|
||||
Currency: constants.WalletCurrencyPoint,
|
||||
Type: constants.WalletTxTypePaymentRefund,
|
||||
Amount: toReturn,
|
||||
ReferenceType: constants.WalletRefTypePayment,
|
||||
ReferenceID: payment.PaymentID,
|
||||
ReversesTransactionID: &ledgerID,
|
||||
OutletID: payment.OutletID,
|
||||
Description: pointRefundDescription(order),
|
||||
Metadata: entities.Metadata{"point_value": payment.PointValue, "target": target, "void": order.IsVoid},
|
||||
IdempotencyKey: fmt.Sprintf("payment-refund:%s:%d", payment.PaymentID, target),
|
||||
},
|
||||
Lots: lots,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
n = toReturn
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return returned, fmt.Errorf("refunding EnakPoint payment %s: %w", payment.PaymentID, err)
|
||||
}
|
||||
returned += n
|
||||
}
|
||||
return returned, nil
|
||||
}
|
||||
|
||||
// EnsureOrderRefundAllowed refuses an order-level refund that would hand back, in cash
|
||||
// or another method, what was paid with EnakPoint (K7). The EnakPoint part is refunded
|
||||
// through its own payment, and comes back as EnakPoint.
|
||||
func (p *PointPaymentProcessor) EnsureOrderRefundAllowed(ctx context.Context, orderID uuid.UUID, amount float64) error {
|
||||
refundable, err := p.repo.RefundableByOtherMethods(ctx, orderID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paidWithPoints, err := p.repo.PaidWithPoints(ctx, orderID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if paidWithPoints == 0 {
|
||||
return nil
|
||||
}
|
||||
if toCents(amount) > toCents(refundable) {
|
||||
return fmt.Errorf("%w: at most Rp %s can be refunded this way; the part paid with EnakPoint is refunded through its EnakPoint payment and returns as EnakPoint",
|
||||
ErrPointPaymentRejected, formatRupiah(toCents(refundable)/100))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pointRefundTarget(orderVoid bool, payment repository.PointPaymentRow) int64 {
|
||||
if orderVoid {
|
||||
return payment.PointsUsed
|
||||
}
|
||||
if payment.Status != string(entities.PaymentTransactionStatusRefunded) {
|
||||
return 0
|
||||
}
|
||||
valueCents := toCents(payment.PointValue)
|
||||
if valueCents <= 0 {
|
||||
return 0
|
||||
}
|
||||
return min(payment.PointsUsed, toCents(payment.RefundAmount)/valueCents)
|
||||
}
|
||||
|
||||
func pointRefundDescription(order *repository.PointPaymentOrder) string {
|
||||
description := "Pengembalian #" + order.OrderNumber
|
||||
if order.OutletName != "" {
|
||||
description += " di " + order.OutletName
|
||||
}
|
||||
return truncateRunes(description, walletDescriptionLimit)
|
||||
}
|
||||
Reference in New Issue
Block a user