Reapply "feat(loyalty): EnakPoint & EnakCoin" (#32)
This reverts commit 4e24f9bbb0.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"apskel-pos-be/internal/logger"
|
||||
)
|
||||
|
||||
// Lots expire at the end of their day, so running every quarter of an hour keeps any
|
||||
// lot from staying past its expiry for more than about that long (PC-503).
|
||||
const defaultWalletExpiryInterval = 15 * time.Minute
|
||||
|
||||
type walletExpiryWork interface {
|
||||
ExpireDue(ctx context.Context) (int, error)
|
||||
SendReminders(ctx context.Context) (int, error)
|
||||
}
|
||||
|
||||
// WalletExpiryJob expires the balances whose time is up and reminds customers of what
|
||||
// is about to (docs/prd-point-coin.md F12, PC-503, PC-504).
|
||||
// Unlike OmsetMilestoneScheduler it keeps no state in memory: several instances can
|
||||
// run it at once, and a restart repeats nothing, because every lot is expired under
|
||||
// its wallet's lock with an idempotency key.
|
||||
type WalletExpiryJob struct {
|
||||
expirer walletExpiryWork
|
||||
stopCh chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
func NewWalletExpiryJob(expirer walletExpiryWork) *WalletExpiryJob {
|
||||
return &WalletExpiryJob{expirer: expirer, stopCh: make(chan struct{})}
|
||||
}
|
||||
|
||||
func (j *WalletExpiryJob) Start(interval time.Duration) {
|
||||
if interval <= 0 {
|
||||
interval = defaultWalletExpiryInterval
|
||||
}
|
||||
go func() {
|
||||
j.RunOnce(context.Background())
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
j.RunOnce(context.Background())
|
||||
case <-j.stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
logger.NonContext.Infof("Wallet expiry job started (interval: %s)", interval)
|
||||
}
|
||||
|
||||
func (j *WalletExpiryJob) Stop() {
|
||||
j.stopOnce.Do(func() { close(j.stopCh) })
|
||||
}
|
||||
|
||||
// RunOnce expires what is due, sends the reminders that are due, and returns how many
|
||||
// lots it expired.
|
||||
func (j *WalletExpiryJob) RunOnce(ctx context.Context) int {
|
||||
expired, err := j.expirer.ExpireDue(ctx)
|
||||
if err != nil {
|
||||
logger.NonContext.Error("Wallet expiry failed to run", err)
|
||||
}
|
||||
if expired > 0 {
|
||||
logger.NonContext.Infof("Wallet expiry expired %d lots", expired)
|
||||
}
|
||||
reminded, err := j.expirer.SendReminders(ctx)
|
||||
if err != nil {
|
||||
logger.NonContext.Error("Wallet expiry reminders failed to run", err)
|
||||
}
|
||||
if reminded > 0 {
|
||||
logger.NonContext.Infof("Wallet expiry sent %d reminders", reminded)
|
||||
}
|
||||
return expired
|
||||
}
|
||||
Reference in New Issue
Block a user