Class: Payment::DailyIssuesDigestWorker

Inherits:
Object
  • Object
show all
Includes:
Sidekiq::Job, Workers::ErrorReportingConcern
Defined in:
app/workers/payment/daily_issues_digest_worker.rb

Overview

Sidekiq worker: nightly digest of payment issues that need accounting
attention. Replaces the per-event alerts that used to fire from four
different code paths with one consolidated email, so AR doesn't get a
stream of one-off messages interleaved with their inbox.

Scans recent activity for five classes of issue:

  1. Orphan Stripe PaymentIntents (7-day window) — Stripe charged
    but Heatwave has no matching Payment in state authorized/captured;
    already-refunded charges are excluded. Detected by
    walking each Stripe account's recent succeeded PIs and joining
    against the local Payment table. (Previously emailed by the
    standalone OrphanPaymentIntentReconciliationWorker.)

  2. Overcharged invoices — captured payments on an invoice exceed
    the invoice total. Detected by scanning recently-created invoices
    with attached captured payments and comparing
    #total_captured against Invoice#total. (Previously
    emailed by PaymentCheckerWorker#detect_overcharged_invoices.)

  3. PayPal authorizations expired past the 29-day hard limit
    Payment transitioned to state expired in the last 24 hours.
    The state transition itself happens in
    #attempt_paypal_reauthorization_if_needed during the
    8-hourly PaymentCheckerWorker; this worker only reports.

  4. PayPal reauthorization failures — an OrderTransaction was
    recorded with action reauthorization and success=false in the
    last 24 hours. Same retrospective detection pattern as #3.

  5. Potential duplicate charges — two or more succeeded, unrefunded
    Stripe PIs for the same Stripe customer + amount within the last
    7 days. Catches the client-confirmed resubmit pattern (Stripe.js
    charges BEFORE the server can block the duplicate — invoice
    INV012603927 / pi_3Tw7ww, 2026-07-22) where the second charge has
    no CRM row at all, so only a Stripe-side sweep can see it. Each
    charge is annotated with its CRM payment/invoice or NO CRM RECORD.

Send is skipped entirely when all scans return empty — never
emails an "all clear."

Constant Summary collapse

WINDOW =

Constant.

24.hours
ORPHAN_WINDOW =

Orphans and duplicates look back a full week: an unresolved orphan keeps
reappearing daily until someone reconciles or refunds it, instead of
silently aging out of a 24h window (the Prestige orphan pi_3ToRZG sat
invisible for 4 weeks after its digest day passed). Duplicate pairs can
also straddle days, and refunds/reports lag the charge.

7.days
DUPLICATE_WINDOW =

Currencies.

7.days
CURRENCIES =

Constant.

%w[USD CAD].freeze

Instance Method Summary collapse

Methods included from Workers::ErrorReportingConcern

#report_worker_error, #report_worker_warning

Instance Method Details

#performObject

Runs the job.

Returns:

  • (Object)

    the result



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'app/workers/payment/daily_issues_digest_worker.rb', line 65

def perform
  issues = {
    orphan_payment_intents: safely(:find_orphan_payment_intents),
    overcharged_invoices:   safely(:find_overcharged_invoices),
    paypal_expired:         safely(:find_paypal_expired_payments),
    paypal_reauth_failed:   safely(:find_paypal_reauth_failures),
    potential_duplicates:   safely(:find_potential_duplicate_charges)
  }

  return if issues.values.all?(&:empty?)

  PaymentReconciliationMailer.daily_issues_digest(issues: issues).deliver_later
end