Payments
Payments
How VitNode takes one-time payments through a provider's hosted checkout - provider instances, payment statuses, webhooks and what payments do not cover yet.
VitNode payments let a plugin charge a signed-in member a fixed, server-decided amount through a provider's hosted checkout page, and find out reliably whether the money arrived. Use it when a plugin sells something simple: a download, a badge, access to a page.
Payments cover one-time, positive amounts and nothing else. There is no catalog, cart, tax, invoice, refund, subscription or saved card, and no AdminCP screen for providers. Build those in your plugin, or wait for later releases.
How a payment flows
- Your plugin's route decides the price on the server and calls
c.get("payments").createCheckout(). - Core stores a
pendingpayment, asks the provider for a hosted checkout and returns its URL. The browser goes there; VitNode never sees card details. - The provider sends the customer back to a page on your site. That redirect proves nothing, so the page shows the status the server knows.
- The provider posts a webhook to
/api/@vitnode/core/payments/webhooks/{providerId}. Core verifies its signature against the exact bytes received, then records the new status. - Your plugin reads the stored payment to decide what the member owns.
If a webhook never arrives, core asks the provider itself: when your plugin
reads the payment with getForUser({ refresh: true }), for example on its
status page (at most every 15 seconds per payment), and from the
reconcile-payments cron job (every 5 minutes, for open payments unchanged for
10 minutes).
Statuses
| Status | Meaning | Can change to |
|---|---|---|
pending | Stored; the checkout is being created or is open | processing, succeeded, failed, expired |
processing | The customer finished, but a delayed payment method has not settled | succeeded, failed |
succeeded | The provider confirmed the money | nothing |
failed | The provider reported a final failure, or refused to create the checkout | nothing |
expired | The checkout closed without payment, or was never created and nobody got its URL | nothing |
Final statuses never change, so a late, duplicated or out-of-order webhook
cannot undo a success. A declined card is not failed: the customer stays on
the hosted page and can try another card.
Provider instances
A provider instance is one configured provider with its own id, credentials
and webhook URL. You can run several at once, for example two Stripe accounts:
payments: {
providers: [
StripePaymentAdapter({ id: 'stripe-main', /* ... */ }),
StripePaymentAdapter({ id: 'stripe-eu', /* ... */ }),
],
},- A plugin passes the instance
idwhen it creates a payment. The browser may suggest one, but the server only accepts configured ids. - A payment stays bound to its instance. Core never retries it elsewhere.
- Missing, malformed or duplicate ids stop the API from starting.
- With no providers, the rest of VitNode works and
createCheckout()fails withprovider_not_found.
payments.checkoutExpiresInMinutes sets how long a checkout accepts payment.
It defaults to 60, and values outside 30 to 1380 minutes (23 hours) are clamped
to that range.
Events
Core emits payment.processing, payment.succeeded, payment.failed and
payment.expired after the status change is committed. See
built-in events. The
default events adapter runs listeners inside the request that changed the
status (a webhook, a status refresh or the cron job) and never retries them, so
do not grant anything from a listener alone: read the stored payment instead.
Activity Logs
Every attempt and every status change lands in the Activity Logs under Admin logs, written in the same transaction as the change. Starting a checkout is credited to the customer; a status move comes from the provider, so it is credited to the system. Each entry keeps the amount (in minor units), currency, provider, reference and the plugin that charged. Card details never reach VitNode, so they never reach the log either.