Payment Intents quickstart
Server-side payment lifecycle in 5 steps. Use this path when you need explicit control over auth, capture, void, and refund — for example delayed capture (auth on order, capture on ship), subscriptions, or platform-integrator flows.
Comparing paths first? See Choose your integration. For the deep reference (every field, error code, capability gate, MIT details), see the Payment Intents reference.
Step 0: Get your test keys
Same path as the Checkout quickstart Step 0 — start at vonpay.com/developers, click Activate VORA Sandbox, and grab vp_sk_test_* (secret) and vp_pk_test_* (publishable). The server-side calls below use the secret key.
Step 1: Tokenize a card
Payment Intents charges a payment-method token, never raw card data. The standard production path pairs Payment Intents with Embedded Fields — buyer's card enters our iframe, you get back a vp_pmt_* token. This quickstart uses the default (single-use) reusability; tokens vaulted with setup_for_future_use: "on_session" (one-click upsells) or "off_session" (recurring / MIT) follow the same payment_method.id shape — see Tokenization for the reusability model.
The "Embedded Fields tokenizes → Payment Intents charges" pairing assumes the embed only mints a vp_pmt_* token and moves no money on submit. Under the embedded charge-and-save flow the embed already charges on submit (and, with a buyer on the session, also vaults a reusable vp_pmt_* in the same step). On that flow you must not also create a Payment Intent for the same session — doing so charges the buyer twice.
Confirm which embed behavior your account uses before pairing with Payment Intents. If your embed charges on submit, settle on the embed result alone and skip Step 2 below for that session.
For this quickstart, sandbox keys can auto-mint a mock token without an iframe:
curl -X POST https://checkout.vonpay.com/v1/tokens \
-H "Authorization: Bearer vp_sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{}'
{
"id": "vp_pmt_test_QAqnXEJF_TCum1jg",
"status": "active",
"card": { "brand": "visa", "last4": "4242", "exp_month": 12, "exp_year": 2030 }
}
In production you'd POST { "provider_reference": "<iframe-minted handle>" } to bind your iframe-vault token to a vp_pmt_*.
Step 2: Create a Payment Intent
Use the vp_pmt_* from Step 1 as the payment method. capture_method: "automatic" does auth + capture in one call:
Node
import { VonPayCheckout } from "@vonpay/checkout-node";
const vonpay = new VonPayCheckout(process.env.VON_PAY_SECRET_KEY);
const intent = await vonpay.paymentIntents.create(
{
amount: 1499,
currency: "USD",
captureMethod: "automatic",
paymentMethod: { id: "vp_pmt_test_QAqnXEJF_TCum1jg" },
metadata: { orderId: "ord_42" },
},
{ idempotencyKey: "ord_42_create_attempt_1" }
);
// intent.status: "succeeded" | "requires_action" | "failed"
Python
from vonpay.checkout import PaymentMethodRef
intent = vonpay.payment_intents.create(
amount=1499,
currency="USD",
capture_method="automatic",
payment_method=PaymentMethodRef(id="vp_pmt_test_QAqnXEJF_TCum1jg"),
metadata={"order_id": "ord_42"},
idempotency_key="ord_42_create_attempt_1",
)
cURL
curl -X POST https://checkout.vonpay.com/v1/payment_intents \
-H "Authorization: Bearer vp_sk_test_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ord_42_create_attempt_1" \
-d '{
"amount": 1499,
"currency": "USD",
"capture_method": "automatic",
"payment_method": { "id": "vp_pmt_test_QAqnXEJF_TCum1jg" }
}'
Use capture_method: "manual" if you want to authorize now and capture later — the intent will stop at authorized and wait for an explicit /capture call.
Step 3: Handle requires_action (3DS challenge)
If the buyer's bank requires Strong Customer Authentication, the intent returns status: "requires_action" with a next_action block:
if (intent.status === "requires_action" && intent.nextAction?.type === "redirect_to_url") {
// Top-level navigation — NOT inside an iframe (banks block this).
res.redirect(intent.nextAction.redirectToUrl.url);
}
The buyer completes the challenge on their bank's page. You learn the final status via webhook (payment_intent.succeeded / payment_intent.failed) — don't trust the buyer's browser. See the reference for full 3DS handling.
Step 4: Capture, void, or refund
For auth-only intents, capture later (empty body captures the full authorized amount):
curl -X POST https://checkout.vonpay.com/v1/payment_intents/vpi_test_abc123/capture \
-H "Authorization: Bearer vp_sk_test_xxx" \
-H "Idempotency-Key: ord_42_capture_attempt_1" \
-d '{}'
Refund (omit amount for full refund):
curl -X POST https://checkout.vonpay.com/v1/refunds \
-H "Authorization: Bearer vp_sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{ "payment_intent": "vpi_test_abc123", "reason": "requested_by_customer" }'
Void (only valid before capture):
curl -X POST https://checkout.vonpay.com/v1/payment_intents/vpi_test_abc123/void \
-H "Authorization: Bearer vp_sk_test_xxx" \
-H "Idempotency-Key: ord_42_void_attempt_1" \
-d '{}'
Some processors don't support void-after-capture; the reference covers the capability gate so you can branch up front rather than catch the error mid-flow.
Step 5: Rebill a saved card (MIT)
Charge a card on file — subscription renewals, retries, scheduled installments — by passing an mit block. You store the token, you run the scheduler. Vonpay vaults the token and relays the charge through the underlying processor when you call this endpoint; the cron, dunning, and subscription state machine live on your side.
Requires the token to have been minted with setup_for_future_use: "off_session" and the merchant's processor to have capabilities.supported_operations.mit === true.
curl -X POST https://checkout.vonpay.com/v1/payment_intents \
-H "Authorization: Bearer vp_sk_test_xxx" \
-H "Idempotency-Key: sub_8821_cyc_2026_05" \
-H "Content-Type: application/json" \
-d '{
"amount": 2999,
"currency": "USD",
"capture_method": "automatic",
"payment_method": { "id": "vp_pmt_test_R6mzKBh3_Ud8nGgf" },
"mit": {
"initiator": "merchant",
"reason": "recurring",
"original_transaction_id": "vpi_test_1stconsentintent01"
}
}'
original_transaction_id is the first cardholder-initiated intent in the chain (where consent was captured). See the Payment Intents reference — MIT for chain-validity rules and the full mit field table.
Step 6: Go live
Same swap as the Checkout path: vp_sk_test_* → vp_sk_live_*. Direct-charge with payment_method may need a per-processor activation gate on live keys — if your live calls return endpoint_not_implemented, contact your VORA point of contact to enable it for your merchant.
Next steps
- Payment Intents reference — full lifecycle reference, error envelopes, MIT block (recurring + retries), capabilities matrix
- Embedded Fields quickstart — the standard browser-side tokenization pairing
- Webhooks —
payment_intent.*event handling - Error codes — canonical error catalog