Vanilla JS Integration
The integration follows the same two-step flow as every SDK integration: your backend creates the payment, your frontend mounts the card form with the returned paymentId.
Step 1: Create a Payment (Backend)
From your backend, call POST /api/server/payment with your private API key (X-Inflow-Api-Key), using the default base URL and no card field — the SDK collects the card in the browser.
// Your own backend route — name it whatever you like.
// Its job: call the Inflow API server-side and return the paymentId.
app.post('/api/create-payment', async (req, res) => {
const response = await fetch('https://api.inflowpay.xyz/api/server/payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Inflow-Api-Key': process.env.INFLOW_PRIVATE_KEY
},
body: JSON.stringify({
products: [{ name: 'Pro Plan', price: 4999, quantity: 1 }],
currency: 'EUR',
customerEmail: req.body.email,
billingCountry: req.body.country,
purchasingAsBusiness: false,
firstName: 'John',
lastName: 'Doe',
})
});
const data = await response.json();
res.json({ paymentId: data.id });
});Required fields:
| Field | Type | Description |
|---|---|---|
products | array | List of products: name, price (in cents, tax-excluded), quantity |
currency | string | EUR or USD |
customerEmail | string | Customer's email address |
billingCountry | string | ISO 3166-1 alpha-2 country code (e.g. FR, DE, US) |
purchasingAsBusiness | boolean | true requires businessName and taxId; US customers also need postalCode |
firstName / lastName are optional but strongly recommended — they improve 3D Secure success rates. Full list of optional fields in the endpoint reference and the Server-to-Server guide.
Never expose your private API key in the browser. The payment must be created server-side; only the resulting
paymentIdis sent to the frontend.
Step 2: Mount the Card Form (Frontend)
Load the SDK from the CDN, fetch the paymentId from your backend route (the one from Step 1), and mount the card element:
<!DOCTYPE html>
<html>
<head>
<title>Checkout</title>
</head>
<body>
<h2>Complete Your Payment</h2>
<div id="card-container"></div>
<script src="https://cdn.jsdelivr.net/npm/@inflow_pay/sdk/dist/sdk.umd.js"></script>
<script>
const provider = new InflowPaySDK.InflowPayProvider({
config: { publicKey: 'inflow_pub_xxx' }
});
// Calls YOUR backend route from Step 1 — not the Inflow API directly.
fetch('/api/create-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: '[email protected]', country: 'FR' })
})
.then(res => res.json())
.then(data => {
const cardElement = provider.createCardElement({
paymentId: data.paymentId,
container: '#card-container',
onComplete: (result) => {
if (result.status === InflowPaySDK.PaymentResultStatus.SUCCESS) {
setTimeout(() => window.location.href = '/confirmation', 2000);
}
},
onError: (error) => {
console.error('SDK error:', error);
},
appearance: {
variables: {
fontFamily: 'Inter, system-ui, sans-serif',
primaryColor: '#0070F3',
buttonBackgroundColor: '#0070F3',
buttonTextColor: '#FFFFFF',
},
},
options: {
paymentMethodLayout: 'horizontalSelector',
paymentMethodOrder: ['apple_pay', 'google_pay', 'card'],
cardFieldLayout: 'compact',
buttonText: 'Pay €49.99',
wallets: {
buttonText: {
applePay: 'Pay with Apple Pay',
googlePay: 'Pay with Google Pay',
},
},
},
});
cardElement.mount();
});
</script>
</body>
</html>The SDK handles card collection, 3D Secure and payment confirmation automatically — you don't call any confirm endpoint yourself.
See Styling & Customization for the full appearance and options reference.
Using npm Instead of CDN
import { InflowPayProvider, PaymentResultStatus } from '@inflow_pay/sdk';
const provider = new InflowPayProvider({
config: { publicKey: 'inflow_pub_xxx' }
});
const cardElement = provider.createCardElement({
paymentId: 'pay_xxx',
container: '#card-container',
onComplete: (result) => {
if (result.status === PaymentResultStatus.SUCCESS) {
// Payment successful
}
}
});
cardElement.mount();API
InflowPayProvider
const provider = new InflowPayProvider({
config: {
publicKey: 'inflow_pub_xxx', // Required
locale: 'en', // Optional (defaults to browser language)
}
});createCardElement Options
| Option | Type | Required | Description |
|---|---|---|---|
paymentId | string | Yes* | Payment ID from your backend |
setupId | string | Yes* | Setup request ID for saving a card without charging (free trials, save-card) — see Free Trial & Card Setup |
container | string or HTMLElement | Yes | CSS selector or DOM element |
onComplete | function | No | Called when payment completes |
onReady | function | No | Called when form is ready |
onChange | function | No | Called on validation state change |
onError | function | No | Called on SDK errors |
appearance | object | No | Theme tokens and fonts (see Styling & Customization) |
options | object | No | Layout, width, card / wallet button labels, placeholders, success UI (see Styling & Customization) |
style | object | No | Deprecated. Legacy styling — use appearance. Still applied when appearance is omitted |
buttonText | string | No | Deprecated. Use options.buttonText |
placeholders | object | No | Deprecated. Use options.cardForm.placeholders |
showDefaultSuccessUI | boolean | No | Deprecated. Use options.showDefaultSuccessUI (default: true) |
* Provide exactly one of paymentId (charge now) or setupId (save card, no charge) — they are mutually exclusive.
Legacy style / top-level copy props remain fully supported for compatibility.
CardElement Methods
| Method | Description |
|---|---|
mount() | Mount the card form to the DOM |
destroy() | Cleanup and unmount |
Custom Success UI
By default, the SDK shows a built-in success screen. To use your own:
const cardElement = provider.createCardElement({
paymentId: 'pay_xxx',
container: '#card-container',
options: { showDefaultSuccessUI: false },
onComplete: (result) => {
if (result.status === PaymentResultStatus.SUCCESS) {
document.getElementById('card-container').innerHTML = `
<div class="custom-success">
<h2>Thank you!</h2>
<p>Order confirmed.</p>
</div>
`;
}
}
});Top-level showDefaultSuccessUI: false still works; prefer options.showDefaultSuccessUI.
The SDK API may evolve. Refer to the npm package for the latest options and types.
Updated 12 minutes ago