Skip to content

feat(payments): prune unpaid payments after two days and keep their count - #534

Merged
rahXephonz merged 2 commits into
feat/small-payment-feefrom
feat/prune-unpaid-payments
Oct 27, 2026
Merged

rahXephonz merged 2 commits into
feat/small-payment-feefrom
feat/prune-unpaid-payments

Conversation

@rahXephonz

@rahXephonz rahXephonz commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #533 (feat/small-payment-fee): migration 0072 follows 0071. Retarget to main once that merges.

Why

Payments nobody pays (expired, or never confirmed by the buyer) pile up in the database and in the dashboard list, with no detail worth opening.

What

  • The chain worker deletes unpaid payments (CREATED, CONFIRMED, EXPIRED) once they are EXPIRED_PAYMENT_RETENTION_DAYS past expiry. Default 2, 0 turns it off. 100 per hour.
  • Two days is beyond the watcher's 24 hour retention, so a late deposit is still found by LateDepositService first.
  • A payment with any sign of funds is never deleted: a confirmed or seen deposit at its address, a clearing step past PAYMENT_PENDING, a provider reference, or a provider funding. FAILED payments are kept.
  • One transaction per batch: webhook deliveries, clearing events, deposit addresses, risk assessments, clearing transaction, the payment, and the count. Deposit address indexes come from a sequence, so a deleted one is never reused.
  • Analytics keeps counting them: pruned_payment_counts (merchant, creation day, chain, asset). buildAnalytics adds it to checkouts, failed and expired, and per-network and per-asset performance, and respects the chain and asset filters. Conversion and success rate do not move when a payment is pruned.

Rollout

  • Migration 0072 creates pruned_payment_counts.
  • The first pass after deploy starts clearing the existing backlog, 100 an hour.
  • What is lost on a pruned payment: its id, chosen rail and merchant reference. Only the daily count stays.

Tests

  • Analytics test for pruned counts, range and chain filter. bun run typecheck and bun test pass.
  • packages/db/test/postgres.test.ts has a pruner test (prunes an unpaid payment and its children, keeps one with a deposit, counts both). It is opt-in like that suite and has not been run: it needs a _test database.

@rahXephonz
rahXephonz added this pull request to stack #537 October 27, 2026 02:22
@rahXephonz
rahXephonz force-pushed the feat/prune-unpaid-payments branch from ce90f4c to 0615330 Compare October 27, 2026 02:29
…ount

The chain worker deletes payments nobody paid (CREATED, CONFIRMED, EXPIRED)
once they are EXPIRED_PAYMENT_RETENTION_DAYS past expiry (default 2, 0 turns it
off), a batch of 100 an hour. Two days is beyond the watcher's 24 hours, so a
late deposit is still found first. A payment with any sign of funds is never
deleted: a deposit at its address, a clearing step past PAYMENT_PENDING, a
provider reference or a provider funding. FAILED payments are kept.

Each batch is one transaction that removes the child rows, the payment and
writes the count to pruned_payment_counts, per merchant, creation day and rail.
Analytics adds those counts back to checkouts, failed and expired and the
per-network and per-asset performance, so deleting a payment does not change
conversion or success rate.

Migration 0072 adds pruned_payment_counts. The Postgres test for the pruner is
opt-in like the rest of that suite.
…ongs to someone else

The payment detail page answered a 404 with "No payment with that id belongs to
this merchant". Since unpaid payments are now deleted two days after they expire,
the same 404 also means that, so the message names both.
@rahXephonz
rahXephonz force-pushed the feat/prune-unpaid-payments branch from 0615330 to e982538 Compare October 27, 2026 02:33
@rahXephonz
rahXephonz merged commit 40ae662 into main Oct 27, 2026
9 checks passed
@rahXephonz
rahXephonz deleted the feat/prune-unpaid-payments branch October 27, 2026 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant