From 962e06e1e27de8612bd8653d90371d96493a2e23 Mon Sep 17 00:00:00 2001 From: febux Date: Tue, 25 Aug 2026 21:57:05 +0300 Subject: [PATCH] Plan for ADR and payments. --- CONTEXT.md | 65 +++++++++++++++++++ docs/adr/0001-charge-saved-method-port.md | 8 +++ ...subscription-currency-bound-at-purchase.md | 8 +++ 3 files changed, 81 insertions(+) create mode 100644 CONTEXT.md create mode 100644 docs/adr/0001-charge-saved-method-port.md create mode 100644 docs/adr/0002-subscription-currency-bound-at-purchase.md diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..aedb4a5 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,65 @@ +# DealDocumentScreening + +Screening of deal documents (contracts) with extraction, prescreen and analysis workers. This context covers the whole product, including billing for document consumption. + +## Language + +### Billing + +**Plan**: +A recurring subscription offering: monthly price and a monthly document quota. Seeded by code, identified by `code`. +_Avoid_: Tier, package + +**Subscription**: +A user's active relationship to one Plan for a 30-day period. At most one active or past_due Subscription per user. +_Avoid_: Membership + +**Quota**: +The per-Subscription allowance of document screenings within the current period. Does not roll over. +_Avoid_: Allowance, limit + +**Credits**: +Prepaid document screenings on a user's balance. Consumed only after Quota is exhausted; purchased credits never expire. +_Avoid_: Points, tokens + +**Topup**: +A one-time purchase of Credits. +_Avoid_: Recharge, refill + +**Invoice**: +A request for payment. Kinds: `topup`, `subscription` (first purchase), `renewal` (subsequent period). +_Avoid_: Bill, charge + +**Renewal**: +The invoice and payment that extends a Subscription into its next period. +_Avoid_: Rebill + +**Saved Payment Method**: +A card token stored with one payment provider, enabling charges without the user present. Bound to a single provider and currency; saved on every successful card payment. +_Avoid_: Recurring token, card binding + +**Dunning**: +The retry ladder after a failed renewal charge: retried on days 1, 3 and 7 after period end, then the Subscription expires. +_Avoid_: Retry loop, collection + +**Signup Bonus**: +One Credit granted at registration, only to users who registered after the bonus shipped. +_Avoid_: Welcome credits, trial + +**ЕРИП**: +Belarusian interbank payment rails offered through bePaid. Settlement is delayed by hours, no Saved Payment Method is possible, so renewal is manual. +_Avoid_: ERIP + +**Billing Hold**: +A flag on a user whose refund clawed back value; blocks further document processing until cleared by an admin. +_Avoid_: Ban, block + +**Clawback**: +Removal of unspent value (Credits or remaining period) after a refund. +_Avoid_: Chargeback + +### Document processing + +**Document Slot**: +The reservation of one Quota unit or one Credit for a single document, released when processing fails. +_Avoid_: Ticket diff --git a/docs/adr/0001-charge-saved-method-port.md b/docs/adr/0001-charge-saved-method-port.md new file mode 100644 index 0000000..e8454b7 --- /dev/null +++ b/docs/adr/0001-charge-saved-method-port.md @@ -0,0 +1,8 @@ +# Charge-saved-method port, own subscription brain + +Two payment providers must support recurring subscription renewals (YooKassa RUB, bePaid BYN), each with incompatible native recurring models (YooKassa `save_payment_method` + charge by payment id; bePaid card tokens). Rather than delegating subscription lifecycle to provider-native subscription engines (e.g. Stripe Billing style), we keep the existing worker-billing scheduler as the single subscription brain and extend the `PaymentProvider` port with one primitive: `charge_saved_method(token, amount, currency, …)`. Each adapter maps it to its provider's saved-card mechanism. This keeps renewal scheduling, dunning (day 1/3/7 retries), period anchoring and refund policy provider-agnostic, and lets a future provider (e.g. Stripe) slot in behind the same port without migrating subscription state. + +## Consequences + +- Saved Payment Methods are provider- and currency-bound; they can never be reused across providers or currencies (see ADR-0002). +- ЕРИП payments (bePaid bank rails) cannot save a method: ЕРИП-origin subscriptions are forced `auto_renew = FALSE` and renew manually via bot/email instructions. diff --git a/docs/adr/0002-subscription-currency-bound-at-purchase.md b/docs/adr/0002-subscription-currency-bound-at-purchase.md new file mode 100644 index 0000000..52c2b7c --- /dev/null +++ b/docs/adr/0002-subscription-currency-bound-at-purchase.md @@ -0,0 +1,8 @@ +# Subscription currency and provider bound at purchase + +Checkout lets the user choose currency per purchase (RUB via YooKassa, BYN via bePaid), but a Subscription permanently binds the currency and provider of its first successful payment, and every renewal charges that pair via its Saved Payment Method. Alternatives rejected: following the user's latest checkout currency (silently breaks auto-renew when it differs — a saved method cannot be charged in another currency or through another provider), and RUB-only subscriptions (excludes bePaid/ЕРИП customers). Topups remain freely per-checkout. + +## Consequences + +- BYN amounts are FX-derived (daily CBR RUB→BYN rate, cached) and frozen at invoice creation; renewals quoted early keep their quote. +- A user switching countries must let the current subscription expire (or be refunded) and repurchase in the other currency; no cross-currency migration in v1.