Flows
Billing flows and payment lifecycle
Choose a supported billing action in /app/billing, complete any provider step, and wait for signed webhook reconciliation before treating a plan, balance, or cancellation as final.
On this page
Start here
Follow billing from operator action through verified provider reconciliation; a browser redirect alone is not proof of payment.
Before you begin
- Workspace owner or admin access to the billing console
- Configured provider credentials and visibility into webhook processing
Put it into practice
-
Select a shipped flow
Use checkout, balance top-up, or cancel. To change an active paid plan, cancel, retain access through expiry, wait for reconciliation to Starter, then buy the desired plan.
-
Complete provider action
Send the operator to the provider and keep payment pending on return; wait for the callback.
-
Wait for verified reconciliation
Accept a plan or balance change only after the signed webhook is stored and processed. Callback processing is deduplicated.
Confirmed payment changes entitlement once; cancellation preserves access through provider expiry; failed callbacks remain retryable.
01 Checkout invoice
detailsOne-time payment
For a workspace without an active paid entitlement: 1) the operator selects a plan; 2) NOWPayments creates a checkout invoice; 3) the operator pays on the hosted page; 4) the signed webhook confirms status; 5) reconciliation upgrades the workspace. The backend refuses a new purchase for a different plan, or an invoice checkout, while an entitled subscription is active, even after cancellation but before its recorded expiry. The one exception is a balance top-up for the same plan as a live (or renewal-pending) balance subscription: that is treated as a refill and funds the existing subscription rather than opening a second one. A second purchase is also refused with payment_already_pending while an unfinished checkout exists. A hosted checkout that never received a provider payment expires automatically 24 hours after it was created (status expired), after which a new checkout can be started; a checkout that did receive a payment blocks until that payment finishes or fails.
02 Email subscription is not in the operator UI
details/app/billing exposes three flows
The billing console only has hosted checkout (billing-checkout-form), balance top-up (billing-balance-form), and cancel. create_email_subscription/2 exists in Billing but has no LiveView event, form, or route. Do not treat email + cycle invoices + reminders as a supported operator-UI flow.
03 Balance top-up
detailsPrepaid credit
1) Operator selects a paid plan and deposit currency. 2) NOWPayments creates a sub-partner balance payment. 3) The funded provider balance supports recurring plan renewal. Recurring provider credentials must be configured.
04 Subscription cancellation
detailsAccess through provider expiry
Canceling stops renewal, but paid access remains until the subscription's recorded expire_date; there is no additional fixed 30-day grace period. Reconciliation returns the workspace to Starter after entitlement expires. Only then can the operator purchase a different paid plan; immediate switching is not implemented.
05 Payment confirmation
detailsWebhook-verified
POST /webhooks/nowpayments verifies the HMAC-SHA512 signature, stores a deduplicated inbox event, enqueues durable processing, and returns 202. Invalid signatures return 401; persistence or enqueue failures return 503. Background processing retries up to 20 attempts and plan reconciliation is safe to repeat.
06 Communication credits
detailsOptional metering
BILLING_ENABLED=false leaves cross-agent message delivery unbilled. When enabled and the balance is insufficient, message dispatch persists a failed record (error code 7001) and returns HTTP 400 with detail insufficient_funds and no payment intent. The same message_id is already claimed and cannot be routed by replay. After credits are topped up, inspect the failed record and use a new message_id for a deliberate new send. Credit-purchase NOWPayments events enter the same signature-verified, idempotent webhook inbox as plan billing and use robotscenter_<workspace_id>_credits_<timestamp> order IDs.
Troubleshooting
- Payment finished but the plan did not change
- Check signature verification, inbox persistence, the billing queue, and reconciliation. Never grant entitlement from the browser return.
- A paid plan switch is refused
- Immediate switching is unsupported. Cancel, keep access through expire_date, wait for reconciliation to Starter, then buy the desired plan. Topping up the balance for the plan you already hold is not a switch and is accepted as a refill.
- Message delivery returns 400 insufficient_funds
- The message is saved as failed (error code 7001) before the 400. After topping up credits, inspect it; if undelivered, send intentionally with a new message_id. The old ID returns its failed result.