Files
mpc/pkg/api/scheduler.go

110 lines
2.4 KiB
Go

package api
import (
"context"
"time"
"github.com/hanzoai/orm"
"github.com/luxfi/mpc/pkg/db"
"github.com/luxfi/mpc/pkg/logger"
)
// StartScheduler runs a background goroutine that processes due subscriptions.
func (s *Server) StartScheduler(ctx context.Context) {
ticker := time.NewTicker(60 * time.Second)
go func() {
for {
select {
case <-ctx.Done():
ticker.Stop()
return
case <-ticker.C:
s.processDueSubscriptions(ctx)
}
}
}()
}
func (s *Server) processDueSubscriptions(ctx context.Context) {
now := time.Now()
subs, err := orm.TypedQuery[db.Subscription](s.db.ORM).
Filter("status=", "active").
Limit(50).
GetAll(ctx)
if err != nil {
logger.Error("scheduler: failed to query due subscriptions", err)
return
}
for _, sub := range subs {
if sub.NextPaymentAt.After(now) {
continue
}
s.processSubscriptionPayment(ctx, sub)
}
}
func (s *Server) processSubscriptionPayment(ctx context.Context, sub *db.Subscription) {
orgID := sub.OrgID
subID := sub.Id()
walletID := ""
if sub.WalletID != nil {
walletID = *sub.WalletID
}
tokenStr := ""
if sub.Token != nil {
tokenStr = *sub.Token
}
// Create transaction
tx := orm.New[db.Transaction](s.db.ORM)
tx.OrgID = orgID
if walletID != "" {
tx.WalletID = &walletID
}
tx.TxType = "subscription_payment"
tx.Chain = sub.Chain
tx.ToAddress = nilIfEmpty(sub.RecipientAddress)
tx.Amount = nilIfEmpty(sub.Amount)
tx.Token = nilIfEmpty(tokenStr)
tx.Status = "approved"
if err := tx.Create(); err != nil {
logger.Error("scheduler: failed to create payment tx", err)
// Retry logic
if sub.RetryCount < sub.MaxRetries {
sub.RetryCount++
nextRetry := time.Now().Add(time.Hour)
sub.NextPaymentAt = nextRetry
sub.Update()
s.fireWebhook(ctx, orgID, "subscription.insufficient_funds", map[string]string{"subscription_id": subID})
} else {
sub.Status = "failed"
sub.Update()
s.fireWebhook(ctx, orgID, "subscription.failed", map[string]string{"subscription_id": subID})
}
return
}
txID := tx.Id()
// Sign and broadcast
s.signAndBroadcast(txID, orgID)
// Advance next payment
nextPayment := computeNextPayment(sub.Interval)
now := time.Now()
sub.NextPaymentAt = nextPayment
sub.LastPaymentAt = &now
sub.LastTxID = &txID
sub.RetryCount = 0
sub.Update()
s.fireWebhook(ctx, orgID, "subscription.paid", map[string]string{
"subscription_id": subID,
"tx_id": txID,
})
}