Skip to main content
This page is for the judges. The rubric has five weighted dimensions; each section below links straight to the chapter and the code-level behaviour that earns it.
DimensionWeightWhere Duro earns it
Problem Relevance20%The thesis — recovery-first billing for salary-backed debit cards
Technical Execution25%The billing engine + recovery engine
Security & Reliability20%Security model + multi-tenancy
Product UX & Clarity15%API ergonomics + appearance + portal
Payment Integration Depth20%Rails & the gateway abstraction

Problem Relevance · 20%

Duro is aimed at a real, expensive, local problem most platforms ignore: involuntary churn. A renewal fails because a salary-backed debit card is empty on the wrong day — the customer wanted to stay, but the money didn’t move. It’s 20–40% of all churn, and worse where cards are debit and salaries land once a month. The entire product is organised around recovering that money: a payday-aware retry engine, rail-switching, a live “money at risk → recovered” ledger as the merchant’s hero metric. This isn’t a feature bolted onto a billing CRUD — recovery is the thesis. Read it →

Technical Execution · 25% — the heaviest weight

The depth is in the engine, and it’s real, not a stub.

A genuine billing loop

A 60-second scanner finds due subscriptions; BillingService.renew invoices, charges, advances the period, handles trial-to-active, maxCycles, scheduled plan changes, and cancel-at-period-end — with month-end date clamping.

An explicit state machine

Nine states, a transition table, illegal transitions that throw. The table is also the source of truth for webhook event names, so state and events can’t drift.

A real worker topology

Five BullMQ queues, four repeatable scanners, one consistent scan→enqueue→process pattern, dedup by job id, graceful shutdown.

A typed, OOP monorepo

strict TypeScript, no any, no free functions in business logic, exact version pins — and an integration suite that runs the whole money loop against real Postgres on every push.

Security & Reliability · 20%

Isolation by construction, plus the results of a deliberate adversarial audit.
  • Tenant isolation that can’t be forgottentest and live are separate Postgres schemas, not a mode column. Every repository is tenant-scoped at construction; idempotency keys are per-tenant.
  • Real findings, fixed — a HIGH-severity webhook SSRF-to-metadata vector, a cross-tenant idempotency replay, an unenforced blacklist, mode-scoping leaks — all found, fixed, and tested.
  • Reliability in the runtime — single-flight caching (no stampede), retries with backoff on every queue, the in_flight guard that prevents double-charges, graceful shutdown with a hard backstop.
  • Secrets done right — keys and OTPs hashed at rest, timing-safe compares, HMAC-signed webhooks, real credentials only in gitignored env.

Product UX & Clarity · 15%

Clarity for two audiences — the developer integrating, and the merchant operating.

API ergonomics

Prefixed IDs, integer minor-units, one error envelope, keyset pagination (page 500 = page 1), per-tenant idempotency, mode implied by the key — and an interactive playground on every endpoint.

Operator clarity

The recovery command center renders “at risk → recovering → recovered,” a per-failure breakdown, and a per-subscription retry timeline. The number that matters is the first thing the merchant sees.

Brand once, everywhere

A draft/publish appearance builder themes checkout, the popup, the portal, and emails from one token set.

Customer self-service

One WhatsApp login, every subscription across every merchant, pause/cancel/fix-card — with ownership enforced.

Payment Integration Depth · 20%

Payments are modelled deeply, not as a single “charge” call.
  • Rails are first-class. Card, bank transfer, USSD, virtual account, and direct-debit mandate are modelled as distinct rails — and the recovery engine relays across them when one fails, in a merchant-configurable order.
  • A clean gateway boundary. All charging goes through a single ChargeGateway interface, so the live rail drops in behind the same callers the simulator runs today. Tokenisation, charge, and transfer surfaces map onto it.
  • Tokens belong to the customer. A saved card tokenises to a phone-keyed identity, reusable across every merchant — the integration is deep enough to power a cross-merchant wallet.
  • Reconciliation is wired in. A dedicated payouts/reconcile queue is already in the worker topology, ready for the live rail in July.
The live payment rail lands in the July window; everything charges through a deterministic simulator today so the full loop — renewal, decline, dunning, recovery, webhooks — is testable end-to-end right now.

Start with the thesis, or jump straight to the recovery engine where the heaviest-weighted execution lives.