mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
110 lines
2.4 KiB
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,
|
|
})
|
|
}
|