Checkout integration guide

Redirect customers to a hosted, PCI-compliant payment page and verify the result from your backend.

VINR Checkout is a hosted, PCI-compliant payment page. Create a session from your backend, redirect the customer, and we handle cards, Apple Pay, Google Pay, Click-to-Pay, and 3DS. The customer comes back to your successUrl with a session ID you can verify.

When to use Checkout

  • No engineering effort on payment UI — VINR renders and maintains the page.
  • Lowest PCI scope: SAQ-A applies because card data never touches your servers.
  • Works in any backend language — the only requirement is a single REST call.

Need to keep customers on your own page? Use Elements instead.

How it works

Create a session from your backend

POST to /checkout/session with the order amount, currency, and redirect URLs. The response includes a checkoutUrl.

const res = await fetch(`${process.env.INTENT_API_URL}/checkout/session`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Api-Key': process.env.VINR_API_KEY!,
  },
  body: JSON.stringify({ amount: 5000, currency: 'EUR',
    successUrl: 'https://yoursite.com/success',
    cancelUrl: 'https://yoursite.com/cancel' }),
});
const { sessionId, checkoutUrl } = await res.json();

Redirect your customer

window.location.href = checkoutUrl;

Customer pays on the hosted page

The customer completes payment at https://checkout.vinr.com/checkout/{sessionId}. VINR handles card entry, wallet payments, and 3DS challenges.

Verify the result on your backend

When the customer lands on successUrl?sessionId=..., fetch the session result to confirm payment status before fulfilling the order.

const result = await fetch(
  `${process.env.INTENT_API_URL}/checkout/session/${sessionId}/result`,
  { headers: { 'X-Api-Key': process.env.VINR_API_KEY! } }
).then(r => r.json());

Quickstart

Get your API key

Log in to the VINR Dashboard and copy your merchant API key from Settings → API Keys.

Store it as VINR_API_KEY in your environment. Never commit it to source control.

Create the session (backend)

Add an endpoint to your server that creates a Checkout session and returns the checkoutUrl to your frontend.

import express from 'express';
 
const router = express.Router();
 
const INTENT_API_URL = process.env.INTENT_API_URL ?? 'https://api.vinr.com';
 
router.post('/api/checkout/start', async (req, res) => {
  const sessionRes = await fetch(`${INTENT_API_URL}/checkout/session`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': process.env.VINR_API_KEY!,
    },
    body: JSON.stringify({
      amount: 5000,
      currency: 'EUR',
      customer: {
        customerId: 'cust_abc123',
        email: 'jane@example.com',
        name: 'Jane Smith',
      },
      cartItems: [
        {
          id: 'item_001',
          name: 'Wireless headphones',
          description: 'Over-ear, noise cancelling',
          price: 5000,
          quantity: 1,
          image: 'https://yoursite.com/images/headphones.jpg',
        },
      ],
      successUrl: 'https://yoursite.com/order/success',
      cancelUrl: 'https://yoursite.com/cart',
    }),
  });
 
  if (!sessionRes.ok) {
    const err = await sessionRes.json();
    return res.status(sessionRes.status).json(err);
  }
 
  const { sessionId, checkoutUrl } = await sessionRes.json();
  res.json({ sessionId, checkoutUrl });
});
 
export default router;

Redirect the customer (frontend)

Call your backend endpoint, then redirect to the hosted page.

async function startCheckout(): Promise<void> {
  const { checkoutUrl } = await fetch('/api/checkout/start', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ cartId: 'cart_xyz' }),
  }).then(r => r.json());
 
  window.location.href = checkoutUrl;
}

Verify on the success page (backend)

Read the sessionId query parameter VINR appends to successUrl, fetch the result, and branch on status.

router.get('/order/success', async (req, res) => {
  const sessionId = req.query.sessionId as string;
 
  if (!sessionId) {
    return res.status(400).send('Missing sessionId');
  }
 
  const result = await fetch(
    `${INTENT_API_URL}/checkout/session/${sessionId}/result`,
    { headers: { 'X-Api-Key': process.env.VINR_API_KEY! } }
  ).then(r => r.json());
 
  if (result.status === 'completed') {
    await fulfillOrder(result);
    return res.render('order-confirmed', { result });
  }
 
  if (result.status === 'failed') {
    return res.redirect('/cart?error=payment_failed');
  }
 
  if (result.status === 'canceled') {
    return res.redirect('/cart');
  }
 
  res.redirect('/cart?error=unknown');
});

(Optional) Listen for webhooks

Polling the result endpoint is fine for simple flows. For reliable async notifications — especially when the customer closes the tab — set up a webhook endpoint and listen for payment.completed.

Configuration options

The table below covers the most commonly used fields. See the full session reference for every field including cart items, promo codes, delivery options, and metadata.

FieldTypeRequiredDescription
amountintegerYesAmount in minor units (e.g. 5000 = €50.00)
currencystringYesISO 4217 code, e.g. "EUR"
successUrlstringYesHTTPS URL to redirect after successful payment
cancelUrlstringYesHTTPS URL to redirect when customer cancels
customerobjectNoPre-fill customer details; enables guest checkout if omitted
cartItemsarrayNoLine items shown in the checkout UI
supportedCurrenciesstring[]NoLet the customer switch currency; defaults to currency only
configIdstringNoBranding/config preset (default: "default-config")
metadataobjectNoControls UI behaviors: mode, shipping, promo codes, delivery

Payment methods

MethodCoverage
Cards (Visa / Mastercard / Amex)Global
Apple PaySafari and Apple devices
Google PayChrome and Android
Click-to-PayMastercard SRC

3DS authentication is handled automatically — you don't need to write any challenge logic.

Security and PCI scope

Card data never touches your servers. Because customers enter card details on a VINR-hosted domain, SAQ-A is the applicable PCI self-assessment questionnaire. The hosted page sets frame-ancestors 'none' so it cannot be embedded in an iframe. For a full overview of your compliance obligations, see Compliance.

Common issues

Next steps

Съдържание

Редактиране в GitHub