Billing & Payments
Billing and Payments
Last reviewed: 2026-05-14.
This document is the line between current billing/accounting code and real
payment acceptance. The current code can measure usage and compute model cost.
It can debit completed usage through an immutable wallet ledger and run a local
mock top-up/refund lifecycle for sandbox verification. It also has a YooKassa
hosted-checkout/webhook contract that credits wallet only after provider
re-fetch verification, an admin YooKassa refund path that debits the local
wallet only after provider refund success, and durable one-object plus bounded
batch reconciliation for missed payment/refund provider-state transitions. It
also has a bounded fiscal receipt submission/reconciliation seam and an
admin-only /api/doctor/launch readiness report that redacts secrets while
showing which payment, fiscal, scheduler, provider availability, live
calibration and legal launch evidence is still missing. pnpm -C web smoke:launch-readiness records non-admin denial evidence and can validate the
admin readiness payload when an admin token is supplied; scripts/smoke.sh
now supplies a local-only smoke admin token and requires that payload check in
the main smoke path. It is not paid-launch ready until live/sandbox provider
E2E, approved fiscal provider proof, real scheduler secrets/operator dry run
and legal checkout requirements are finished.
This is not legal advice. Before launch, the payment, tax, refund, and personal data sections need review by an accountant/lawyer for the exact operating entity.
Current Billing Implementation
Implemented:
CreditWallettable storesbalanceMicroRubper user./api/balancereads wallet balance.- Dev superaccount can show unlimited balance.
UsageEventtable stores completed stream usage.web/lib/billing.tscomputes canonical usage in integer micro-RUB.web/lib/pricing.tsloads model prices fromPLYRUM_PRICING_PATH.web/lib/fx.tsloads an FX snapshot fromPLYRUM_FX_SNAPSHOT_PATH, with a stale MVP fallback if missing./api/stream/runreserves and updates usage rows, records canonical usage, cache read/write tokens, reasoning tokens, FX fields, and cost status/source.WalletLedgerEntrystores immutable balance movements.Payment,PaymentRefund,PaymentWebhookEvent,FiscalReceipt, andInvoicetables define the durable DB contract for the checkout/webhook/refund, fiscal receipt, and B2B invoice lifecycle.BalanceReservationholds estimated spend before provider calls whenPLYRUM_BILLING_ENFORCE_BALANCE=1.web/lib/wallet-ledger.tsatomically converts completed positive-costUsageEventrows into idempotentusage_debitledger entries and updatesCreditWallet.balanceMicroRubin a Prisma transaction. Active reservations are consumed by the debit or released on failed/zero-cost runs./api/usageexposes completed usage with schema version 2 and pagination headers.GET /api/billing/ledgerexposes the current user's ledger entries with schema version 1 and pagination headers.POST /api/payments/checkoutexists as a safe checkout contract. It is disabled by default and createsPayment(status=pending)rows whenPAYMENTS_PROVIDER=mockorPAYMENTS_PROVIDER=yookassa. YooKassa checkout uses hosted redirect confirmation, provider idempotency, and local metadata linking provider payments back to Plyrum payment ids.POST /api/payments/webhook/mockexists for sandbox-only payment lifecycle testing. It requiresPAYMENTS_PROVIDER=mockandPAYMENTS_MOCK_WEBHOOK_SECRET, storesPaymentWebhookEventrows, rejects amount mismatches, marks canceled mock payments without crediting wallet, and idempotently credits successful mock payments throughWalletLedgerEntry(type=topup).POST /api/payments/webhook/yookassaaccepts YooKassa payment notifications, re-fetches payment state from YooKassa before wallet mutation, rejects amount or metadata mismatches, and idempotently credits successful payments throughWalletLedgerEntry(type=topup).POST /api/payments/refundexists for admin-only refunds. In mock mode it tests sandbox refund accounting for successful mock payments and rejects wallets with insufficient refundable balance. In YooKassa mode it calls/v3/refundswithIdempotence-Key, debits the wallet only when the provider refund status issucceeded, rejects provider amount mismatches, and allows the wallet to go negative because the real provider refund has already returned money to the customer. YooKassa provider refund responses are persisted inPaymentRefund, including pending/canceled/failed attempts, so an operator can reconcile pending refunds later throughPOST /api/payments/reconcile. The same route also supports bounded batch reconciliation for pending YooKassa payments/refunds.- Admin credit, refund and reconciliation routes emit redacted web audit events
through
web/lib/audit-log.ts. With a database configured, the durableWebAuditEventstore is queryable and exportable through admin-onlyGET /api/audit;POST /api/auditprunes rows older than an explicit cutoff or retention window. JSONL audit files remain an optional fallback, and in tests the file writer is disabled unlessPLYRUM_WEB_AUDIT_DIRis explicitly set. GET /api/paymentsexposes the current user's payment history with schema version 1 and pagination headers./api/stream/runcan reject requests before provider calls whenPLYRUM_BILLING_ENFORCE_BALANCE=1and the wallet balance is below the estimated route cost. Dev superaccount is exempt for local smoke./billingshows recent usage and a MoA/direct savings summary.
Money unit:
- Internal money unit is
micro-RUB. 1 RUB = 1_000_000 micro-RUB.- Billing math uses integers and rejects unsafe/rounded values.
What Is Not Implemented Yet
Not implemented:
- Live provider checkout proof with real YooKassa sandbox credentials.
- Provider refund receipt creation, fiscal reconciliation and live/sandbox browser proof.
- External fiscalization, refund receipt delivery, and receipt reconciliation.
- Customer invoices/acts for B2B.
- Payment method selection UI.
- Browser-level payment sandbox E2E.
The existing /billing/topup page is still a sandbox-oriented checkout form.
It can create a mock pending payment when PAYMENTS_PROVIDER=mock, or a
YooKassa hosted checkout when server-side YooKassa env vars are configured. It
shows provider return query state and recent payment rows. It does not expose
mock webhook secrets in the browser. Do not present it as paid-launch ready
until YooKassa sandbox browser E2E, legal checkbox, provider refund receipts,
fiscal receipt handling and reconciliation jobs are complete.
The checkout checkbox, accept_terms API gate, and product legal links now
exist. /legal/offer, /legal/privacy, /legal/refund, and /legal/tariffs
are still draft product documents and need final legal review, seller details,
public contacts and fiscal receipt policy before real payments.
The existing mock checkout and mock webhook APIs are for contract/sandbox testing only. They can credit the local wallet only after a secret-protected mock webhook, and must not be presented as real payment acceptance.
Most important risk:
UsageEvent.costMicroRubrecords what a run costs and completed positive-cost rows are debited into the wallet ledger.- Hard preflight rejection exists behind
PLYRUM_BILLING_ENFORCE_BALANCE=1. - Balance reservation/hold also exists behind
PLYRUM_BILLING_ENFORCE_BALANCE=1; it creates activeBalanceReservationrows inside a per-user transaction lock before provider calls and exposes spendable balance as wallet balance minus active holds. - Before paid launch, payment top-up/webhook/refund paths must use the same immutable ledger discipline and reconciliation checks.
Recommended Product Billing Model
For MVP, use prepaid balance:
- User tops up balance.
- Payment webhook confirms money received.
- System credits wallet via an immutable ledger entry.
- Each completed model run creates usage ledger debit.
- Balance is derived from ledger or updated atomically with ledger append.
/billingshows payment history, usage charges, refunds, and current balance.
Do not launch subscriptions first. Subscriptions add recurring billing, proration, unpaid invoice handling, cancellation semantics, and more support burden. Prepaid balance is easier to reason about and fits model-token usage.
Recommended Payment Methods
For Russian B2C users:
- Bank card, including MIR where supported by the acquiring provider.
- SBP (
sbp) because it is common, fast, and has low friction. - SberPay and T-Pay where available through the provider.
- Hosted payment form only; Plyrum should not collect card numbers directly.
For B2B users:
- Invoice / bank transfer.
- Manual or admin-approved balance credit after funds arrive.
- Monthly closing documents if required by the legal entity.
Avoid for the first paid launch:
- Crypto payments.
- P2P card transfers.
- Manually asking users to send receipts in chat.
- Storing card PAN/CVV.
- Custom acquiring form unless there is a strong reason.
Provider Shortlist
Option A: YooKassa
Best default for a Russia-focused MVP.
Reasons:
- Supports common local methods through one integration: bank cards, MIR/Mir Pay, SberPay, T-Pay, Alfa Pay, SBP, YooMoney, and B2B SberBank Business Online depending on contract settings.
- Has API docs for payment creation and method-specific flows.
- Has documented 54-FZ receipt/fiscalization support paths.
- Hosted confirmation keeps Plyrum out of direct card-data handling.
Tradeoffs:
- Merchant onboarding and contract settings decide which methods are actually available.
- Fiscal receipt payloads must be correct.
- Webhook/idempotency/reconciliation still must be implemented carefully.
Option B: CloudPayments
Useful alternative if it fits the operating entity and desired checkout UX.
Reasons:
- Common payment widget/API integration style.
- Payment method set can include local app payments depending on configuration.
Tradeoffs:
- Need to verify exact current method availability, fees, fiscalization, and settlement for the company before choosing it.
Option C: T-Bank acquiring
Useful if the company already banks with T-Bank or wants T-Pay/SBP-first checkout.
Reasons:
- Strong local acquiring and SBP/T-Pay flows.
- Good fit for Russian business accounts.
Tradeoffs:
- May be less provider-neutral than an aggregator.
- Need separate evaluation of receipts, refunds, API ergonomics, and support.
Suggested MVP Choice
Default recommendation:
- Use YooKassa hosted checkout for B2C top-up.
- Add B2B invoice/manual credit as an admin workflow.
- Keep provider abstraction small: one active provider at first, but design the
DB rows with
providerandproviderPaymentIdso a second provider can be added later.
Database State And Required Additions
Do not rely only on CreditWallet.balanceMicroRub; the immutable ledger is now
part of the schema for usage debits, and active BalanceReservation rows track
holds. Payment lifecycle tables are also present as a DB contract. Mock
checkout, mock webhook, YooKassa checkout/webhook, sandbox refund, and
admin/manual credit flows write the contract today. YooKassa provider refund
creation is implemented for admin refunds. Pending fiscal/refund receipt records
are created on accepted top-ups and refunds. POST /api/payments/fiscal-receipts/reconcile can submit/reconcile those records
through a mock fiscal provider or a guarded HTTP fiscalization endpoint, and
pnpm -C web reconcile:fiscal wraps the bounded batch contract for an external
scheduler. .github/workflows/payment-reconciliation.yml provides six-hour
scheduled YooKassa/fiscal reconciliation and skips safely when deployment
vars/secrets are absent. Live/sandbox provider E2E, approved fiscal provider
proof, real scheduler secrets/operator dry run, and B2B invoice workflows are
still missing.
BalanceReservation
Purpose: hold estimated spend before provider calls so concurrent requests cannot spend the same wallet balance.
Status: implemented behind PLYRUM_BILLING_ENFORCE_BALANCE=1 in
web/prisma/migrations/20260513012000_add_balance_reservations/migration.sql.
Fields:
iduserIdwalletIdusageEventIdestimatedMicroRubactualMicroRubstatus:active,released,consumedreasoncreatedAtupdatedAtreleasedAt
Indexes:
- unique
usageEventId - index
userId, status - index
walletId, status
WalletLedgerEntry
Purpose: source of truth for balance movements.
Status: implemented for usage debits in
web/prisma/migrations/20260513010000_add_wallet_ledger/migration.sql.
Fields:
iduserIdwalletIdtype:topup,usage_debit,refund,admin_adjustment,promo_credit,reversalamountMicroRub: signed integer; positive credits, negative debitsbalanceAfterMicroRubsourceType:payment,usage_event,refund,admin,promosourceIdidempotencyKeymetadatacreatedAt
Indexes:
- unique
idempotencyKey - index
userId, createdAt - index
sourceType, sourceId
Payment
Purpose: top-up/payment lifecycle.
Status: DB table implemented in
web/prisma/migrations/20260513011000_add_payment_lifecycle_tables/migration.sql.
Mock checkout, mock webhook processing, YooKassa checkout/webhook processing,
YooKassa refund processing/reconciliation, and admin-only mock refund
processing exist for sandbox testing. Accepted top-ups and refunds create
FiscalReceipt(status=pending) rows for later fiscalization/reconciliation.
External fiscal/refund receipt integration and reconciliation jobs are still
missing.
Fields:
iduserIdprovider:yookassa,cloudpayments,tbank, etc.providerPaymentIdamountMicroRubcurrency:RUBstatus:pending,waiting_for_capture,succeeded,canceled,refunded,failedpaymentMethod:bank_card,sbp,sberbank,tinkoff_bank, etc.confirmationUrldescriptioncreatedAtpaidAtcanceledAtrawProviderStatusmetadata
Indexes:
- unique
provider, providerPaymentId - index
userId, createdAt - index
status, createdAt
PaymentWebhookEvent
Purpose: replay-safe webhook audit.
Status: DB table implemented in
web/prisma/migrations/20260513011000_add_payment_lifecycle_tables/migration.sql.
POST /api/payments/webhook/mock writes replay-safe mock events. Real provider
verification is still missing.
Fields:
idprovidereventIdor deterministic hash if provider has no event idproviderPaymentIdpayloadHashreceivedAtprocessedAtstatus:received,processed,ignored,failederrormetadata
Indexes:
- unique
provider, eventId - index
providerPaymentId - index
status, receivedAt
FiscalReceipt
Purpose: receipt status for 54-FZ/fiscalization.
Status: DB table implemented in
web/prisma/migrations/20260513011000_add_payment_lifecycle_tables/migration.sql.
Runtime pending receipt creation exists for successful mock/YooKassa top-ups and
accepted mock/YooKassa refunds. Admin fiscal receipt submission/reconciliation
exists at POST /api/payments/fiscal-receipts/reconcile: it supports one
receipt or bounded batch mode, PLYRUM_FISCAL_RECEIPTS_PROVIDER=mock for
sandbox proof, and PLYRUM_FISCAL_RECEIPTS_PROVIDER=http with a guarded
PLYRUM_FISCAL_RECEIPTS_ENDPOINT plus optional status endpoint for external
fiscalization. Production still needs approved provider credentials, live or
sandbox fiscal receipt proof, deployment scheduling, and operator runbook
evidence.
Fields:
idpaymentIdproviderReceiptIdtype:payment,refundstatusvatCodetaxSystemCodecustomerEmailcreatedAtrawPayload
Optional: Invoice
Purpose: B2B manual payment.
Status: DB table implemented in
web/prisma/migrations/20260513011000_add_payment_lifecycle_tables/migration.sql.
B2B invoice/admin workflow is still missing.
Fields:
idorgIdoruserIdnumberamountMicroRubstatuspdfUrlpaidAtcreatedAt
Required API Endpoints
POST /api/payments/checkout
Auth: required.
Status: implemented for PAYMENTS_PROVIDER=mock and
PAYMENTS_PROVIDER=yookassa. Without a configured provider it returns
503 payments_not_configured. Unsupported providers return
503 payments_provider_not_implemented.
Input:
amount_rub- optional
method - optional
return_url
Behavior:
- Validate amount against min/max top-up.
- Create local
Payment(status=pending). - In mock mode, store a sandbox provider payment id and confirmation URL.
- In YooKassa mode, call
/v3/paymentsusing Basic auth andIdempotence-Key, withconfirmation.type=redirect,return_url, and Plyrum metadata. - Return redirect URL.
- Do not credit wallet from checkout itself. Wallet credit happens only through a verified webhook/admin flow.
POST /api/payments/webhook/mock
Auth: sandbox webhook secret header.
Status: implemented for local/provider-mock testing only.
Required environment:
PAYMENTS_PROVIDER=mockPAYMENTS_MOCK_WEBHOOK_SECRET
Required header:
x-plyrum-mock-webhook-secret
Input:
event_idpayment_idstatus:succeededorcanceled- optional
amount_micro_rub
Behavior:
- Reject if mock payments or mock webhook secret are not configured.
- Store a
PaymentWebhookEventwith payload hash. - Deduplicate by
(provider=mock, event_id)and reject same-event payload mismatches. - Reject amount mismatches without wallet credit.
- For
canceled, mark the payment canceled and do not credit wallet. - For
succeeded, atomically:- create the user's wallet if missing;
- append
WalletLedgerEntry(type=topup, sourceType=payment); - increment
CreditWallet.balanceMicroRub; - mark
Paymentsucceeded; - mark the webhook processed;
- invalidate balance cache.
- Replayed successful events return duplicate success without double crediting.
POST /api/payments/webhook/yookassa
Auth: provider-specific verification.
Behavior:
- Parse event.
- Store raw webhook event hash.
- Deduplicate by event/provider payment id.
- Re-fetch payment from provider if needed.
- If provider says payment succeeded, atomically:
- mark
Paymentsucceeded; - append
WalletLedgerEntry(type=topup); - update
CreditWallet.balanceMicroRub; - invalidate balance cache.
- mark
- Return 200 for duplicates after confirming they were already processed.
Current implementation details:
- The webhook endpoint is
POST /api/payments/webhook/yookassa. - It accepts
payment.succeededandpayment.canceled. - It re-fetches
/v3/payments/{id}from YooKassa before any wallet mutation. - It rejects amount mismatches and
metadata.plyrum_payment_idmismatches. - It deduplicates by
event:providerPaymentId. - It credits wallet exactly once with
WalletLedgerEntry(type=topup).
GET /api/payments
Auth: required.
Returns payment/top-up history for the current user.
Status: implemented, user-scoped, schema version 1.
GET /api/billing/ledger
Auth: required.
Returns top-ups, usage debits, refunds, and adjustments.
Status: implemented for existing ledger rows. Today rows are produced by usage debits, sandbox mock top-ups, admin/manual credits, and admin-only sandbox mock refunds.
POST /api/billing/admin/credit
Auth: admin only.
Use for B2B invoices, promo credits, and support adjustments. It creates a
ledger entry and a redacted web audit event. Compliance-heavy production
operations should run with DB-backed audit storage enabled, use /api/audit
for filtered JSON/NDJSON export, and document the retention prune policy.
POST /api/payments/refund
Auth: admin only.
Status: implemented for provider=mock sandbox payments and provider=yookassa
admin provider refunds.
Input:
payment_idreasonidempotency_key
Behavior:
- Require an admin user through
PLYRUM_ADMIN_USER_IDSor the local dev superaccount outside production. - Require a succeeded payment.
- For mock payments, reject pending, canceled, already-refunded, non-mock, missing, or insufficient-balance refunds.
- For YooKassa payments, call
/v3/refundswith Basic auth andIdempotence-Key, then mutate the local wallet only when the provider refund returnsstatus=succeeded. - For YooKassa payments, reject provider amount mismatches and do not mutate
the wallet for
canceledor non-final statuses. - Persist a
PaymentRefundrow for every YooKassa provider refund response, keyed bypaymentId + idempotencyKey,provider + providerRefundId, and the ledger idempotency key. - Deduplicate wallet mutation by
payment_refund:<payment_id>:<idempotency_key>. - In a transaction:
- append
WalletLedgerEntry(type=refund, sourceType=refund, amount=-payment.amountMicroRub); - decrement
CreditWallet.balanceMicroRub; - mark
Payment.status=refunded; - invalidate balance cache.
- append
This endpoint proves the wallet ledger/refund accounting path and the YooKassa
provider refund creation contract. It also creates durable PaymentRefund
state and pending refund receipt tracking rows. It does not yet submit fiscal
refund receipts to an external provider, run scheduled provider reconciliation
jobs, or prove the flow with real YooKassa sandbox browser E2E.
POST /api/payments/reconcile
Auth: admin only.
Status: implemented for one YooKassa payment/refund at a time and for bounded batch reconciliation.
Input:
payment_id- or
refund_id - or
provider_refund_id - or
batch: truewith optionallimitfrom 1 to 100
Behavior:
- Require an admin user through
PLYRUM_ADMIN_USER_IDSor the local dev superaccount outside production. - Require
PAYMENTS_PROVIDER=yookassa. - Load the local YooKassa payment/refund and re-fetch
/v3/payments/{id}or/v3/refunds/{id}. - For provider
succeeded, reuse the same idempotent processing path as the YooKassapayment.succeededwebhook: create at most one top-up ledger entry, mark the paymentsucceeded, create a pending payment receipt and invalidate balance cache. - For provider
canceled, reuse the same processing path as thepayment.canceledwebhook and do not credit the wallet. - For provider non-final states, update
rawProviderStatus, return202, and leave wallet balance unchanged. - For refund reconciliation, validate provider payment id and amount, then:
succeeded: call the same idempotent provider refund ledger primitive and updatePaymentRefund.ledgerEntryId/status/completedAt;pending: updatePaymentRefund.rawProviderStatus, return202, no wallet mutation;canceled: markPaymentRefundcanceled, no wallet mutation.
- For batch reconciliation, scan pending YooKassa payments and pending
YooKassa refunds up to
limit, then return per-object status bodies. Batch mode is bounded and suitable for an operator action or external scheduler;pnpm -C web reconcile:yookassawraps this contract for cron/Render/CI withPLYRUM_RECONCILE_BASE_URL,PLYRUM_RECONCILE_TOKEN, andPLYRUM_RECONCILE_LIMIT. - Reject amount/currency/metadata mismatches without wallet mutation.
This closes missed provider-state recovery for a single payment/refund and provides the API entrypoint plus runner script for batch reconciliation. It does not yet provide deployment-level scheduled job configuration, live external fiscal provider proof or live YooKassa sandbox proof.
Current Usage Debit Flow
On completed stream:
- Compute canonical usage.
- Persist/update
UsageEvent. - If usage is completed and
costMicroRub > 0, callrecordUsageDebit. - In the same DB transaction:
- deduplicate by
usage_debit:<UsageEvent.id>; - append
WalletLedgerEntry(type=usage_debit, amount=-costMicroRub); - update wallet balance with a decrement;
- return the ledger entry and balance-after value.
- deduplicate by
- Invalidate
/api/balanceRedis cache after a newly-created debit.
Current limitation:
- Hard balance rejection is an opt-in runtime flag until real top-up exists.
- The flow reserves estimated balance before provider calls only when
PLYRUM_BILLING_ENFORCE_BALANCE=1; keep the flag disabled for non-dev users until real top-up/payment flow exists. UsageEventdoes not yet have a separatebilledAtorledgerEntryIdcolumn; idempotency is currently enforced by the ledger key.
Open product decision:
- Should Plyrum allow a run to start when balance is insufficient?
- Recommended MVP: allow small free/trial quota, otherwise reject before
provider call with a clear
402 payment_requiredresponse.
Top-Up Page UX
/billing/topup should become:
- amount presets: 500, 1000, 3000, 10000 RUB;
- legal checkbox: offer, privacy, refund rules (checkbox/gate exists; real document links still need final publication);
- real provider redirect after checkout creation;
- provider-specific success page reconciliation with balance refresh;
- provider-specific failure/cancel retry path;
- richer payment history below the form.
Required Tests
Unit tests:
- money unit conversion and min/max amount validation;
- ledger append computes correct
balanceAfterMicroRub; - duplicate idempotency key cannot double-credit;
- webhook replay does not double-credit;
- failed/canceled payment does not credit wallet;
- refund creates negative ledger entry and updates payment status;
- duplicate refund idempotency key cannot double-refund;
- mock refund rejects pending payments and insufficient refundable wallet balance;
- YooKassa refund calls provider
/v3/refunds, debits exactly once after provider success, allows negative wallet after real provider refund, and does not mutate wallet on pending/canceled/amount-mismatch provider responses; - successful top-ups and accepted refunds create pending
FiscalReceiptrows; - fiscal receipt reconciliation submits payment/refund receipts through mock or guarded HTTP providers and supports bounded batch processing;
- usage debit cannot run twice for same
UsageEvent; - enforced low-balance preflight returns non-retryable
402 billing; - dev superaccount bypasses balance preflight for local smoke;
- balance cache invalidated after top-up and debit.
Integration tests:
- mock checkout endpoint creates local pending payment and returns confirmation URL without crediting wallet;
- provider sandbox/mock succeeded webhook credits wallet;
- provider sandbox/mock canceled webhook does not credit wallet;
- sandbox/mock refund debits wallet exactly once and marks payment refunded;
- YooKassa provider refund debits wallet exactly once after provider success and marks payment refunded;
- admin YooKassa reconciliation can recover a missed succeeded/canceled payment state and is idempotent on replay;
- admin YooKassa reconciliation can recover a pending refund that later becomes
succeeded/canceled, persists
PaymentRefund, and is idempotent on replay; - admin YooKassa batch reconciliation can process pending payments/refunds with bounded per-object results;
GET /api/paymentsis user-scoped;GET /api/billing/ledgeris user-scoped;- dev superaccount does not create fake paid top-ups.
E2E:
- login;
- open billing;
- choose top-up amount;
- complete provider sandbox/mock payment;
- return to Plyrum;
- balance updated;
- run a model call;
- balance decreases and usage row appears.
Security tests:
- webhook with invalid signature/verification rejected;
- webhook with mismatched amount rejected;
- webhook with mismatched user/payment rejected;
- card data is never logged or stored;
- provider secrets are redacted from logs.
Current Billing Environment Variables
PLYRUM_PRICING_PATHPLYRUM_FX_SNAPSHOT_PATHPLYRUM_BILLING_ENFORCE_BALANCE=1to reject requests whose estimated route cost exceeds wallet balance. Keep disabled until real top-up/payment flow is available for non-dev users.PAYMENTS_PROVIDER=yookassaYOOKASSA_SHOP_IDYOOKASSA_SECRET_KEYYOOKASSA_RETURN_URLYOOKASSA_API_BASE_URLoptional; defaults tohttps://api.yookassa.ru/v3.PAYMENTS_MIN_TOPUP_RUBPAYMENTS_MAX_TOPUP_RUBPAYMENTS_LIVE=1requires an HTTPS YooKassa return URL.
Payment Environment Variables Still To Add
For a paid YooKassa launch:
YOOKASSA_VAT_CODEYOOKASSA_TAX_SYSTEM_CODE- Fiscal receipt product/service metadata.
- Refund/reconciliation job controls.
Keep payment env vars in web/server runtime only. They must not appear in Rust crates or client bundles.
Legal and Accounting Docs Needed
Before real payments:
- Public offer / terms of service.
- Privacy policy.
- Consent to personal data processing, if required by the final flow.
- Payment and refund policy.
- Tariff/pricing page.
- Receipt/fiscalization policy: what appears on the receipt, VAT treatment, seller legal entity.
- B2B invoice terms.
- Support contact and refund SLA.
- Data deletion policy.
External References
Reviewed payment-provider documentation:
- YooKassa payment methods: https://yookassa.ru/developers/payment-acceptance/getting-started/payment-methods
- YooKassa refunds: https://yookassa.ru/developers/payment-acceptance/after-the-payment/refunds
- YooKassa 54-FZ/fiscal receipt guide: https://yookassa.ru/docs/payment-solution/payments/supplementary/guides/54fz
- T-Bank acquiring payment methods: https://www.tbank.ru/business/help/business-payments/internet-acquiring/how-involve/payment-methods/
- CloudPayments payment methods: https://cloudpayments.ru/help/payments/payment-methods