package ledger import ( "fmt" "time" ) type SettlementStatus string const ( SettlementPending SettlementStatus = "PENDING" SettlementSubmitted SettlementStatus = "SUBMITTED" SettlementConfirmed SettlementStatus = "CONFIRMED" SettlementRetryable SettlementStatus = "RETRYABLE" SettlementManualReview SettlementStatus = "MANUAL_REVIEW" ) type Settlement struct { ID string SourceService string SourceTxID string IdempotencyKey string Status SettlementStatus Network string TransactionHash string Attempts int AvailableAt time.Time LastError string SignedTransactionXDR string } type SettlementStats struct { Pending int64 Retryable int64 ManualReview int64 OldestPendingSeconds int64 } func (s Settlement) Validate() error { if s.ID == "" || s.SourceService == "" || s.SourceTxID == "" || s.IdempotencyKey == "" { return fmt.Errorf("settlement identity fields are required") } if s.Status != SettlementPending && s.Status != SettlementSubmitted && s.Status != SettlementConfirmed && s.Status != SettlementRetryable && s.Status != SettlementManualReview { return fmt.Errorf("invalid settlement status %q", s.Status) } if s.Attempts < 0 || s.AvailableAt.IsZero() { return fmt.Errorf("invalid settlement retry state") } return nil } func (s Settlement) Retry(now time.Time, maxAttempts int, err error) Settlement { s.LastError = err.Error() s.Status = SettlementRetryable if maxAttempts > 0 && s.Attempts >= maxAttempts { s.Status = SettlementManualReview } delay := time.Second for i := 1; i < s.Attempts && delay < 15*time.Minute; i++ { delay *= 2 } if delay > 15*time.Minute { delay = 15 * time.Minute } s.AvailableAt = now.UTC().Add(delay) return s }