Dunning System

4-stage failed payment recovery pipeline: warning emails, grace periods, suspension, cancellation, and auto-fallback to free edition.

The dunning system is SCRIPE's automated failed-payment recovery pipeline. When a payment fails, the system does not immediately suspend the tenant — instead, it moves through 4 graduated stages over 10-14 days, giving tenants ample opportunity to update their payment method before access is restricted. Each stage sends a targeted email and escalates the urgency.

4-Stage Dunning Pipeline

Dunning stages are managed by two background jobs: SubscriptionReconciliationJob (daily at 3:00 AM UTC) and DunningNotificationJob (daily at 4:00 AM UTC).

Payment fails
Stage 1: PaymentFailed email (Day 0)
Stripe Smart Retry (Days 1-3)
Still failing? → Stage 2: GraceWarning email
PastDue status, yellow banner shown
≤2 days left → Stage 3: GraceFinalWarning email
Grace expires → Stage 4: Suspend subscription
All tenant admins deactivated
Payment recovered? → Reactivate
Extended grace expires → Cancel + Fallback edition

Stage 1: Payment Failed (Day 0)

Triggered immediately when the invoice.payment_failed webhook arrives. Stripe's Smart Retry system attempts the payment again over days 1-3. SCRIPE sends the PaymentFailed email with a link to update the payment method via the Customer Portal.

Stage 2: Grace Warning (Mid-Grace)

If the payment is still failing at mid-grace period, DunningNotificationJob sends the GraceWarning email. The subscription enters PastDue status. The tenant retains access but sees a colored GracePeriodBanner in the admin panel (yellow warning).

Stage 3: Final Warning (≤2 Days Left)

When less than 2 days remain in the grace period, DunningNotificationJob sends the GraceFinalWarning email. The banner turns orange/red. This is the last chance before suspension.

Stage 4: Suspend or Cancel

On grace period expiry, SubscriptionReconciliationJob suspends the subscription (Suspended status) and deactivates all tenant admins. If suspension continues past an extended grace period, the subscription is Cancelled and the tenant falls back to the FallbackEdition (typically the Free edition).

Background Jobs

Two jobs handle the dunning lifecycle. Both are fault-isolated — a failure for one tenant does not prevent processing of other tenants.

JobScheduleResponsibility
SubscriptionReconciliationJobDaily 3:00 AM UTCExpiry detection, suspension, fallback edition assignment
DunningNotificationJobDaily 4:00 AM UTCGrace warning and final warning emails for PastDue subscriptions
appsettings.jsonJSON
{
  "BackgroundJobs": {
    "Jobs": {
      "entitlements-subscription-reconciliation": {
        "Enabled": true,
        "CronExpression": "0 3 * * *"
      },
      "entitlements-dunning-notification": {
        "Enabled": true,
        "CronExpression": "0 4 * * *"
      }
    }
  }
}

Email Deduplication

A DunningNotification entity tracks which emails have been sent per subscription per stage. The jobs check this table before sending to prevent duplicate emails if the job runs multiple times.

Cross-Module Integration

When a subscription is suspended, the SubscriptionChangedEventHandler fires and deactivates all tenant admins with DeactivationReason='SubscriptionSuspended'. When the subscription is resumed (payment recovered), only admins with that specific reason are reactivated — admins manually deactivated by the tenant admin remain off.

Auto-Fallback Edition

Each Edition has an optional FallbackEditionId. When a subscription is cancelled due to non-payment, the tenant is automatically moved to the FallbackEdition (usually the Free tier). This keeps the tenant's data intact while restricting premium features.

Promo Codes & Proration

Promo codes are validated against the EditionPromotion entity before being passed to Stripe during checkout. Stripe handles proration automatically on upgrades/downgrades — the amount is calculated by Stripe and synchronized back via the customer.subscription.updated webhook.

Promotion Expiry on Renewal

When a promotion has DurationDays > 0, the system calculates a PromotionExpiresAt timestamp. On each renewal (New Row Pattern), the handler checks if the promotion has expired. Expired promotions are NOT carried forward to the new subscription row — the tenant pays full price from the next billing cycle.

Dunning Email Templates

SCRIPE includes 4 HTML email templates for the dunning pipeline, all with subdomain-aware CTA links and bilingual support (EN + AR).

  • payment-failed — Sent immediately on invoice.payment_failed. Links to Stripe Customer Portal.
  • grace-warning — Sent at mid-grace. Shows days remaining and links to update payment.
  • grace-final-warning — Sent ≤2 days before suspension. Maximum urgency.
  • subscription-expired — Sent after grace expires. Informs tenant of downgrade to free tier.