Skip to main content

Payment Retries

The retry system automatically processes charge attempts for overdue invoices.

Attempt Statuses

StatusDescription
pendingAwaiting processing
processingBeing processed (claimed by worker)
succeededPayment successful
failedPayment declined, more attempts available
exhaustedMaximum attempts reached

Attempt Fields

FieldDescription
InvoiceAssociated invoice
PaymentCreated payment record
Attempt numberSequential (1, 2, 3...)
Payment methodMethod used for the attempt
Amount (cents)Amount to be charged
Failure codeDecline reason
Failure messageDetailed error description
Scheduled forAttempt date/time
Processed atProcessing date/time
Next retry atWhen the next attempt will be made

Retry Worker

The worker runs every 6 hours and:

  1. Finds attempts with status=pending and scheduledFor ≤ now
  2. Atomically claims each attempt (pending → processing) to avoid duplication
  3. For each attempt:
    • Checks if the invoice is already paid (skips)
    • Creates a payment record
    • Processes the payment via gateway
  4. On success:
    • Marks the invoice as paid
    • Creates a recovery record
    • Removes the account from the delinquent list
  5. On failure:
    • If attempts remain: schedules the next one
    • If attempts exhausted: marks as exhausted and suspends the account (if configured)

Race Condition Prevention

The system uses atomic updateMany with status verification to ensure only one worker processes each attempt:

WHERE id = X AND status = 'pending'
SET status = 'processing'

If count = 0, another worker has already claimed the attempt.

Retry for Installments

Retries can also target specific installments:

  • The metadata.installmentId identifies the installment
  • The retry amount is for the installment, not the entire invoice