Review Integration examples
Checkout SDK provides a flexible way to integrate secure card payments into a Merchant's website using Hosted Payment Fields. This article covers the core integration flow, from initialising the SDK and mounting card fields to submitting payments and handling results, as well as advanced customisation options for styling, validation, 3D Secure, events, and error handling.
Basic example = Quick start
The minimal working integration: four card fields, payment, result handling. The markup must contain the field containers before the initFeature('hpf') call (hide the form with display:none ; do not remove it from the DOM).
HTML
<button id="pay-with-card">Pay with card</button>
<div id="card-form" style="display: none">
<div id="card-number"></div>
<div id="expiry-date"></div>
<div id="cvv"></div>
<div id="cardholder-name"></div>
<button id="pay">Pay</button>
</div> Typescript
import { init } from '@corefy/checkout-sdk';
import { hpfFeature } from '@corefy/checkout-sdk/features/hpf';
import type { CheckoutInstance } from '@corefy/checkout-sdk';
async function boot(): Promise<void> {
// 1. Initialization: brings up the session but does NOT create any feature
// (the iframe fields are not mounted yet).
const checkout: CheckoutInstance = await init({
apiKey: 'pk_…',
merchantAccountId: 'ma_…',
paymentRequestId: 'pr_…',
baseUrl: 'https://pay.example.com',
features: [hpfFeature], // npm mode; not passed in CDN mode
featureOptions: {
hpf: {
fields: {
cardNumber: { container: '#card-number' },
expiryDate: { container: '#expiry-date' },
cvv: { container: '#cvv' },
cardholderName: { container: '#cardholder-name' },
},
},
},
});
// 2. React to the payment flow directives.
checkout.on('next_action', ({ action }) => {
switch (action.type) {
case 'awaiting_details': showForm(); break; // show the form
case 'poll': showLoader(); break; // show the loader
case 'complete': showResult(action.status); break; // 'success' | 'fail' | 'pending'
case 'retry': showDeclineAndForm(action.message); break;
}
});
checkout.on('error', ({ error }) => {
console.error('[checkout]', error.type, error.message);
});
// 3. The customer picked "Card" — mount the fields (the only moment they appear).
document.querySelector('#pay-with-card')!.addEventListener('click', async () => {
await checkout.initFeature('hpf'); // idempotent: a repeated click remounts nothing
(document.querySelector('#card-form') as HTMLElement).style.display = 'block';
});
// 4. Payment.
document.querySelector('#pay')!.addEventListener('click', async () => {
await checkout.submit({
paymentMethodSchemeId: 'pss_…', // issued by YOUR backend (private API / admin panel)
featureType: 'hpf',
});
});
}
boot(); The complete event is only a UI signal ("show the result screen"). It happens in the customer's browser and is not proof of payment. Deliver the goods/services only after a server-side check (a Webhook or a Payment Request status query by paymentRequestId via the private API).
Advanced example = Full customisation
This example demonstrates every customisation point: global and per-field styles, placeholders, 3-DS in iframe mode with modal styling, local validation, handling of all directives and typed errors, individual field events, and dynamic style updates.
import { init } from '@corefy/checkout-sdk';
import { hpfFeature } from '@corefy/checkout-sdk/features/hpf';
import { CheckoutErrorType } from '@corefy/checkout-sdk/contracts';
import type {
CheckoutInstance, CheckoutError, NextAction, HpfHandle,
} from '@corefy/checkout-sdk';
const checkout: CheckoutInstance = await init({
// --- required identifiers ------------------------------------------------
apiKey: 'pk_…',
merchantAccountId: 'ma_…',
paymentRequestId: 'pr_…',
baseUrl: 'https://pay.example.com',
// --- features (npm mode) --------------------------------------------------
features: [hpfFeature],
// --- card field customization ---------------------------------------------
featureOptions: {
hpf: {
// Global base style — applied to every field;
// the per-field style is layered on top and wins.
style: {
fontSize: '16px',
fontFamily: "'Inter', sans-serif",
color: '#1a2233',
background: 'transparent',
placeholderColor: '#9aa3b2',
height: '44px',
paddingTop: '10px',
paddingBottom: '10px',
paddingLeft: '12px',
paddingRight: '12px',
},
fields: {
cardNumber: {
container: '#card-number',
placeholder: '1234 1234 1234 1234',
style: { letterSpacing: '1px' }, // overrides only this property
},
expiryDate: { container: '#expiry-date', placeholder: 'MM/YY' },
cvv: { container: '#cvv', placeholder: 'CVC' },
cardholderName: { container: '#cardholder-name', placeholder: 'Name on card' },
},
},
},
// --- 3-D Secure challenge presentation --------------------------------------
challenge: {
acsMode: 'iframe', // challenge in a modal on this page, no navigation
// container: '#threeds-slot', // alternative: into YOUR OWN container, no SDK modal
styles: {
overlay: { background: 'rgba(12, 16, 24, 0.8)' }, // backdrop dimming
container: { width: '420px', height: '640px', borderRadius: '12px' },
iframe: { borderRadius: '12px' },
},
classNames: {
overlay: 'my-overlay',
container: 'my-modal-card elevated', // multiple classes separated by spaces
iframe: 'my-frame',
},
},
// --- timeouts ---------------------------------------------------------------
timeout: 30000, // RPC to the bridge, ms
initTimeout: 30000, // bridge handshake during init(), ms
});
// ===== Lifecycle events ======================================================
checkout.on('ready', ({ sessionConfig }) => {
// The session is loaded, no features exist yet — render the payment method selection.
renderAmount(sessionConfig.payment_request.amount, sessionConfig.payment_request.currency);
});
checkout.on('next_action', ({ action }: { action: NextAction }) => {
switch (action.type) {
case 'awaiting_details': showForm(); break;
case 'poll': showLoader(); break;
// redirect / acs_redirect are executed by the SDK ITSELF — no handlers needed.
case 'retry':
// Declined; the customer may try again. Show the reason
// (action.message — ready-made EN text; action.messageKey / action.reason /
// action.resolutionGroup — for your own localization) and bring the form back.
// The next submit() IS the retry (fields are re-read and re-tokenized).
showDecline(action.message);
showForm();
break;
case 'auto_retry':
// The platform is ALREADY retrying on the server. Only show a countdown;
// do NOT call checkout.retry() — that would create a parallel attempt.
startCountdown(action.countdownSec);
break;
case 'complete':
// 'success' | 'fail' | 'pending' — UI only; deliver the goods after
// a server-side check (webhook / private API).
showResult(action.status, action.message);
break;
}
});
checkout.on('error', ({ error }: { error: CheckoutError }) => {
if (error.type === CheckoutErrorType.InvalidConfig) {
// e.g. challenge.container was not found — the SDK has already opened its own modal
reportToMonitoring(error);
return;
}
if (error.retryable) showToast('A temporary error occurred, please try again.');
else showFatal(error.message);
});
// ===== Mounting fields after the method is chosen ============================
await checkout.initFeature('hpf');
const hpf = checkout.feature<HpfHandle>('hpf')!; // undefined before initFeature('hpf')
// Individual field events: focus / blur / input / validity.
hpf.field('cardNumber').on('validity', ({ valid, error }) => {
toggleFieldError('#card-number', valid ? null : error?.message ?? null);
});
hpf.field('cardNumber').on('focus', () => highlight('#card-number', true));
hpf.field('cardNumber').on('blur', () => highlight('#card-number', false));
// Dynamic update without remounting (the style is passed AS A WHOLE, no merging):
hpf.field('cvv').update({ placeholder: '***', style: { fontSize: '16px', color: '#1a2233' } });
// ===== Payment ===============================================================
document.querySelector('#pay')!.addEventListener('click', async () => {
hpf.setDisabled(true); // lock the fields while processing
try {
const validation = await hpf.validateAll();
if (!validation.valid) {
validation.errors.forEach(({ field, message, messageKey, params }) => {
// message — ready-made EN text; messageKey + params — for your i18n
renderFieldError(field, message);
});
return;
}
await checkout.submit({ paymentMethodSchemeId: 'pss_…', featureType: 'hpf' });
} catch (e) {
const err = e as CheckoutError;
if (err.type === CheckoutErrorType.InvalidSubmitOptions) fixIntegration(err);
else showToast(err.message);
} finally {
hpf.setDisabled(false);
}
});
// ===== Cleanup (SPA: page unmount) ==========================================
window.addEventListener('beforeunload', () => checkout.destroy());Updated about 19 hours ago

