Documentation

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.

A foundation, not a shop

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

  1. Your plugin's route decides the price on the server and calls c.get("payments").createCheckout().
  2. Core stores a pending payment, asks the provider for a hosted checkout and returns its URL. The browser goes there; VitNode never sees card details.
  3. 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.
  4. 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.
  5. 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

StatusMeaningCan change to
pendingStored; the checkout is being created or is openprocessing, succeeded, failed, expired
processingThe customer finished, but a delayed payment method has not settledsucceeded, failed
succeededThe provider confirmed the moneynothing
failedThe provider reported a final failure, or refused to create the checkoutnothing
expiredThe checkout closed without payment, or was never created and nobody got its URLnothing

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:

src/vitnode.api.config.ts
payments: {
  providers: [
    StripePaymentAdapter({ id: 'stripe-main', /* ... */ }),
    StripePaymentAdapter({ id: 'stripe-eu', /* ... */ }),
  ],
},
  • A plugin passes the instance id when 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 with provider_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.

Guides