Accept a one-time payment
Create a payment, present checkout, and confirm the result end-to-end.
This guide takes you from zero to a confirmed payment using VINR-hosted Checkout. It's runnable against the sandbox — swap your test keys in and follow along.
OverviewAsk
your server VINR customer
│ create payment │ │
│─────────────────►│ │
│ checkoutUrl │ │
│◄─────────────────│ redirect │
│──────────────────────────────────────►│ pays on hosted page
│ payment.completed (webhook) │
│◄─────────────────│◄──────────────────│
│ fulfil order │ │You'll create the payment on your server, send the customer to the hosted page, and fulfil on the payment.completed webhook.
Create a paymentAsk
On your backend, create a payment and read the checkoutUrl from the response.
import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });
export async function POST(req: Request) {
const { orderId } = await req.json();
const payment = await vinr.payments.create(
{
amount: 4999, // €49.99
currency: 'EUR',
description: `Order ${orderId}`,
returnUrl: `https://yoursite.com/orders/${orderId}/complete`,
metadata: { orderId },
},
{ idempotencyKey: `order-${orderId}` }, // safe to retry
);
return Response.json({ checkoutUrl: payment.checkoutUrl });
}The idempotencyKey guarantees that a retried request (timeout, refresh) reuses the same payment instead of charging twice. See Idempotency.
Collect payment detailsAsk
Redirect the customer to the hosted page. VINR handles card entry, wallets, 3D Secure, and any local methods you've enabled.
const { checkoutUrl } = await fetch('/api/checkout', {
method: 'POST',
body: JSON.stringify({ orderId: '1234' }),
}).then((r) => r.json());
window.location.href = checkoutUrl;Confirm & handle the resultAsk
When the customer returns to your returnUrl, don't trust the redirect alone — confirm the status server-side before showing success.
const payment = await vinr.payments.retrieve(paymentId);
if (payment.status === 'completed') {
// show success — but fulfilment happens on the webhook (below)
}Verify with a webhookAsk
Fulfil the order from the webhook so it happens exactly once, even if the customer closes the tab. See the payment lifecycle for every event.
export async function POST(req: Request) {
const event = vinr.webhooks.verify(
await req.text(),
req.headers.get('x-vinr-signature'),
);
switch (event.type) {
case 'payment.completed':
await fulfillOrder(event.data.metadata.orderId); // idempotent!
break;
case 'payment.failed':
await notifyCustomer(event.data.metadata.orderId);
break;
}
return new Response('OK', { status: 200 });
}Test itAsk
Use these sandbox cards on the hosted page:
| Card | Result |
|---|---|
4242 4242 4242 4242 | Success |
4000 0000 0000 0002 | Declined |
4000 0000 0000 3220 | 3D Secure challenge |
Go liveAsk
Swap to live keys
Replace your sandbox VINR_SECRET_KEY with the live key from the Dashboard.
Register your production webhook
Point a webhook endpoint at your live URL and store the signing secret.
Walk the go-live checklist
Confirm error handling, idempotency, and monitoring with the go-live checklist.
Next stepsAsk
Save & reuse cards
Charge returning customers in one click.
Handle 3D Secure
Complete SCA where required.
Earn loyalty at checkout
Award points on this payment.
Last updated on