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,103 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPaymentCodeTaken means the code is already live for someone; draw another.
|
||||
ErrPaymentCodeTaken = errors.New("payment code already in use")
|
||||
// ErrPaymentCodeNotFound means the code does not exist: never issued, expired, or
|
||||
// already used.
|
||||
ErrPaymentCodeNotFound = errors.New("payment code not found")
|
||||
// ErrPaymentCodeWrongCustomer means the code belongs to another customer.
|
||||
ErrPaymentCodeWrongCustomer = errors.New("payment code belongs to another customer")
|
||||
)
|
||||
|
||||
// PaymentCodeRepository keeps one-time EnakPoint payment codes in Redis
|
||||
// (docs/prd-point-coin.md F9). A code expires by TTL and is removed when used.
|
||||
type PaymentCodeRepository interface {
|
||||
// Save stores a code for a customer for ttl, and retires the customer's previous
|
||||
// code so only the newest one works. ErrPaymentCodeTaken if the code is live.
|
||||
Save(ctx context.Context, code string, customerID uuid.UUID, ttl time.Duration) error
|
||||
// Consume uses a code up if it belongs to the customer. A code of another customer
|
||||
// is left in place, so a cashier scanning it against the wrong order does not burn
|
||||
// it for its owner.
|
||||
Consume(ctx context.Context, code string, customerID uuid.UUID) error
|
||||
}
|
||||
|
||||
type paymentCodeRepository struct {
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
func NewPaymentCodeRepository(client *redis.Client) PaymentCodeRepository {
|
||||
return &paymentCodeRepository{client: client}
|
||||
}
|
||||
|
||||
func paymentCodeKey(code string) string { return "wallet:paycode:" + code }
|
||||
|
||||
func paymentCodeCustomerKey(customerID uuid.UUID) string {
|
||||
return "wallet:paycode:customer:" + customerID.String()
|
||||
}
|
||||
|
||||
func (r *paymentCodeRepository) Save(ctx context.Context, code string, customerID uuid.UUID, ttl time.Duration) error {
|
||||
ok, err := r.client.SetNX(ctx, paymentCodeKey(code), customerID.String(), ttl).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to store payment code: %w", err)
|
||||
}
|
||||
if !ok {
|
||||
return ErrPaymentCodeTaken
|
||||
}
|
||||
previous, err := r.client.GetSet(ctx, paymentCodeCustomerKey(customerID), code).Result()
|
||||
if err != nil && !errors.Is(err, redis.Nil) {
|
||||
return fmt.Errorf("failed to track payment code: %w", err)
|
||||
}
|
||||
r.client.Expire(ctx, paymentCodeCustomerKey(customerID), ttl)
|
||||
if previous != "" && previous != code {
|
||||
// Only if it is still that customer's: the number may have been reissued.
|
||||
if err := r.compareAndDelete(ctx, previous, customerID); err != nil && !errors.Is(err, ErrPaymentCodeNotFound) && !errors.Is(err, ErrPaymentCodeWrongCustomer) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// consumeScript deletes a code only if it belongs to the given customer, in one step.
|
||||
// Returns 1 when used up, 0 when missing, -1 when it belongs to someone else.
|
||||
var consumeScript = redis.NewScript(`
|
||||
local owner = redis.call('GET', KEYS[1])
|
||||
if not owner then return 0 end
|
||||
if owner ~= ARGV[1] then return -1 end
|
||||
redis.call('DEL', KEYS[1])
|
||||
return 1
|
||||
`)
|
||||
|
||||
func (r *paymentCodeRepository) Consume(ctx context.Context, code string, customerID uuid.UUID) error {
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" {
|
||||
return ErrPaymentCodeNotFound
|
||||
}
|
||||
return r.compareAndDelete(ctx, code, customerID)
|
||||
}
|
||||
|
||||
func (r *paymentCodeRepository) compareAndDelete(ctx context.Context, code string, customerID uuid.UUID) error {
|
||||
result, err := consumeScript.Run(ctx, r.client, []string{paymentCodeKey(code)}, customerID.String()).Int()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to use payment code: %w", err)
|
||||
}
|
||||
switch result {
|
||||
case 1:
|
||||
return nil
|
||||
case -1:
|
||||
return ErrPaymentCodeWrongCustomer
|
||||
default:
|
||||
return ErrPaymentCodeNotFound
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user