Embedded Checkout

Available via: API (private key) + SDK (public key) · @inflow_pay/sdk 1.10 or later

Overview

Embedded Checkout puts the complete Checkout V2 hosted page in an iframe on your site. The customer pays without leaving your page, and you get everything the hosted checkout already does:

  • Email and billing country collection
  • Order summary, coupons and tax
  • Local currency for one-time payments
  • Cards with 3D Secure, Apple Pay, Google Pay, and local bank transfer for one-time payments
  • One-time payments and subscriptions

The iframe resizes itself to its content, so it blends into your page like a normal block.

Hosted checkoutEmbedded CheckoutSDK payment form
Where customer paysInflow page (redirect)Your page (iframe)Your page (iframe)
UI you buildNoneNoneEverything except the payment form
Email, country, tax, summary✅✅You build it
Apple Pay / Google Pay✅✅✅ (see SDK Checkout Methods)
Local bank transfer✅ (one-time)✅ (one-time)❌
Subscriptions✅✅Via API

Prerequisites

  1. A verified domain (Settings → Domains, see Domains). Only pages on your verified domains (and their subdomains) can display the checkout. Embedded sessions are refused in production when your account has no verified domain. In sandbox, an account without verified domains can test from any page, localhost included.
  2. Your API keys: the private key on your server, the public key in your page. See Get Your API Keys.

If you set a successUrl, it must also be on one of your verified domains.

How it works

sequenceDiagram
    participant Page as Your page
    participant Server as Your server
    participant API as Inflow API
    participant Iframe as Inflow checkout (iframe)
    Page->>Server: Customer clicks "Pay"
    Server->>API: Create session with uiMode "embedded"
    API-->>Server: sessionId, clientSecret
    Server-->>Page: clientSecret
    Page->>Iframe: SDK mounts the checkout with clientSecret
    Iframe-->>Page: onComplete
    API-->>Server: checkout_session.completed webhook

Step 1: Create the session (backend)

Create a checkout session with uiMode: "embedded". The response contains a clientSecret: send it to your page, and only to the customer who will pay.

app.post('/api/checkout', async (req, res) => {
  const response = await fetch('https://api.inflowpay.com/api/checkout/sessions/one-time-payment', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Inflow-Api-Key': process.env.INFLOW_PRIVATE_KEY,
    },
    body: JSON.stringify({
      uiMode: 'embedded',
      products: [{ name: 'Pro Plan', price: 4900, quantity: 1 }],
      currency: 'EUR',
      successUrl: 'https://shop.example.com/thanks',
      // Optional, pre-fills the checkout:
      customerEmail: req.body.email,
      billingCountry: 'FR',
    }),
  });

  const session = await response.json();
  res.json({ clientSecret: session.clientSecret });
});

For a subscription, create the session for an existing offer instead:

await fetch(`https://api.inflowpay.com/api/checkout/sessions/subscription/${subscriptionOfferId}`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Inflow-Api-Key': process.env.INFLOW_PRIVATE_KEY,
  },
  body: JSON.stringify({ uiMode: 'embedded', successUrl: 'https://shop.example.com/thanks' }),
});

Every other session field from Checkout V2 (customerEmail, expiresIn, metadatas…) works the same way.

Response

{
  "sessionId": "sess_1a2b3c",
  "checkoutUrl": "https://checkout.inflowpay.com/embed/sess_1a2b3c",
  "uiMode": "embedded",
  "clientSecret": "sess_1a2b3c_secret_…"
}

Keep the clientSecret out of logs, URLs you share and analytics. It lets anyone display and pay this session. It is returned only once, at creation; Inflow stores only a hash.

Step 2: Display the checkout (frontend)

Install the SDK:

npm install @inflow_pay/sdk

React

import { useEffect, useState } from 'react';
import { InflowPayProvider, EmbeddedCheckout } from '@inflow_pay/sdk/react';

export function CheckoutPage() {
  const [clientSecret, setClientSecret] = useState<string | null>(null);

  useEffect(() => {
    fetch('/api/checkout', { method: 'POST' })
      .then((res) => res.json())
      .then((data) => setClientSecret(data.clientSecret));
  }, []);

  if (!clientSecret) return null;

  return (
    <InflowPayProvider config={{ publicKey: 'inflow_pub_xxx' }}>
      <EmbeddedCheckout
        clientSecret={clientSecret}
        onComplete={({ sessionId }) => console.log('Paid', sessionId)}
        onError={(error) => console.error(error.code, error.message)}
      />
    </InflowPayProvider>
  );
}

Vanilla JavaScript

<div id="checkout"></div>

<script src="https://cdn.jsdelivr.net/npm/@inflow_pay/sdk@1/dist/sdk.umd.js"></script>
<script>
  const provider = new InflowPaySDK.InflowPayProvider({
    config: { publicKey: 'inflow_pub_xxx' },
  });

  fetch('/api/checkout', { method: 'POST' })
    .then((res) => res.json())
    .then(({ clientSecret }) => {
      const checkout = provider.initEmbeddedCheckout({
        clientSecret,
        onComplete: ({ sessionId }) => console.log('Paid', sessionId),
        onError: (error) => console.error(error.code, error.message),
      });
      checkout.mount('#checkout');
      // Later, to remove it: checkout.destroy();
    });
</script>

Options

OptionDefaultDescription
clientSecretrequiredReturned when the session is created with uiMode: "embedded".
onComplete–Called with { sessionId, successUrl } once the customer has paid.
onErrorlogs to consoleCalled with { code, message }. See Errors.
redirectOnCompletion"always""always": your page goes to the session's successUrl after onComplete. "never": the customer stays on your page and you handle what comes next.
localeprovider localeCheckout language: en, de, es, fr, it, nl, pl, pt.
checkoutUrlfrom your public keyOnly needed in environments other than production and sandbox: pass the origin of the checkoutUrl returned with the session.

After payment

  • Fulfil the order from the webhook, not from onComplete: subscribe to checkout_session.completed (see Overview & Event Types). onComplete is only meant to update your UI, and it never fires if the customer closes the page during a bank redirect.
  • Redirects: 3D Secure runs inside the checkout. Payment methods that need to leave the page (for example some bank transfers) open in your page's window, not in the iframe, and bring the customer back to your successUrl.

Errors

CodeMeaningWhat to do
IFRAME_NOT_READYThe checkout did not load within 15 seconds. Usually the page is not on a verified domain, or the session is not an embedded one.Check that the page's domain is verified and that the session was created with uiMode: "embedded".
INVALID_CLIENT_SECRETThe value passed is not a client secret.Pass the clientSecret from the session response unchanged.
CHECKOUT_URL_REQUIREDNo default checkout host for this key's environment.Pass checkoutUrl.

On the API side, creating an embedded session in production without a verified domain returns checkout.embed_requires_verified_domain (400).

Limitations

  • Local currency (adaptive pricing) applies to one-time payments only; subscriptions are charged in the offer's currency.
  • One checkout per session: a clientSecret cannot be reused for another session, and an embedded session cannot be opened as a hosted checkout link.

Did this page help you?