Buy now, pay later

Accept Klarna, Afterpay, and Affirm payments using a popup-based approval flow.

View as Markdown

PaymentKit.js supports three buy now, pay later (BNPL) payment methods: Klarna, Afterpay, and Affirm. All three share the same flow — there are no input elements to mount. When you submit, PaymentKit.js opens a secure popup where the customer completes approval with the BNPL provider, then polls for the result and resolves your onSuccess or onError callback.

Setup

Register only the BNPL methods you plan to offer — each one is independent.

1<script src="https://unpkg.com/@payment-kit-js/vanilla/dist/cdn/paymentkit.min.js"></script>
2<script>
3 const paymentKit = PaymentKit.default({
4 environment: 'sandbox',
5 secureToken: 'aBcDeFgHiJkLmNoPqRsTuVwXyZ123456', // From your backend
6 paymentMethods: [
7 PaymentKit.PaymentMethods.klarna,
8 PaymentKit.PaymentMethods.afterpay,
9 PaymentKit.PaymentMethods.affirm
10 ]
11 });
12</script>

Submit payment

Like PayPal, BNPL methods don’t need input elements. Submit directly when the customer clicks your Klarna, Afterpay, or Affirm button, passing the matching paymentMethod name — 'klarna', 'afterpay', or 'affirm':

1document.getElementById('klarna-button').addEventListener('click', () => {
2 paymentKit.submit({
3 fields: {
4 customer_name: 'Jane Smith',
5 customer_email: 'jane@example.com',
6 customer_country: 'US',
7 customer_zip_code: '94102'
8 },
9 paymentMethod: 'klarna', // or 'afterpay' / 'affirm'
10 options: {
11 processorId: 'proc_abc123',
12 customerInfo: {
13 first_name: 'Jane',
14 last_name: 'Smith',
15 email: 'jane@example.com'
16 }
17 },
18 onSuccess: (result) => {
19 console.log('ID:', result.id);
20 console.log('Checkout Attempt ID:', result.checkoutAttemptId);
21 console.log('Checkout Session ID:', result.checkoutSessionId);
22 console.log('State:', result.state);
23 window.location.href = '/success';
24 },
25 onError: (errors) => {
26 console.error(errors);
27 }
28 });
29});

PaymentKit.js opens the provider’s approval page in a 600×700 popup window and polls the payment status every 2 seconds. If the customer closes the popup without completing approval, the submission fails with a “popup closed by user” error after a short grace period.

The popup must be triggered by a direct user action (button click) to avoid being blocked by popup blockers.

Options

OptionTypeRequiredMethodsDescription
processorIdstringYesAllYour processor ID (BNPL payments are processed through your Stripe processor)
customerInfo.first_namestringYesAllCustomer’s first name
customerInfo.last_namestringYesAllCustomer’s last name
customerInfo.emailstringNoAllCustomer’s email address
setupFutureUsagebooleanNoKlarna onlySave the Klarna payment method for future use

Saving Klarna for future payments

Klarna supports saving the payment method during checkout for future off-session charges (for example, subscriptions). Pass setupFutureUsage: true in the submit options:

1paymentKit.submit({
2 fields: { /* ... */ },
3 paymentMethod: 'klarna',
4 options: {
5 processorId: 'proc_abc123',
6 customerInfo: {
7 first_name: 'Jane',
8 last_name: 'Smith'
9 },
10 setupFutureUsage: true
11 },
12 onSuccess: (result) => { /* ... */ },
13 onError: (errors) => { /* ... */ }
14});

setupFutureUsage is only supported by Klarna. Afterpay and Affirm do not support saving the payment method — their TypeScript submit option types omit the field, and the SDK never sends it for those methods.

Error handling

Each BNPL method returns its errors under its own key — errors.klarna, errors.afterpay, or errors.affirm — as descriptive strings. Validation errors use errors.processor_id and errors.customer_name:

1onError: (errors) => {
2 if (errors.processor_id) {
3 // Missing processor ID
4 console.error(errors.processor_id); // "Processor ID is required"
5 } else if (errors.customer_name) {
6 // Missing customer name
7 console.error(errors.customer_name); // "Customer first and last name are required"
8 } else if (errors.klarna) {
9 // Klarna-specific errors (same patterns apply to errors.afterpay / errors.affirm)
10 if (errors.klarna.includes('popup')) {
11 // Popup was blocked or closed
12 alert('Please allow popups for this site');
13 } else if (errors.klarna.includes('cancelled')) {
14 // Customer cancelled in the provider's flow
15 }
16 console.error(errors.klarna);
17 }
18}

The table below uses Klarna as the example; for Afterpay and Affirm the error field is afterpay / affirm and the provider name in the message changes accordingly:

Error FieldError MessageCause
processor_idProcessor ID is requiredMissing processorId in options
customer_nameCustomer first and last name are requiredMissing customerInfo.first_name or customerInfo.last_name
klarnaFailed to start Klarna checkout ({status})The start request failed (the backend error detail is used when available)
klarnaFailed to open Klarna popup. Please allow popups for this site.Browser blocked the popup window
klarnaKlarna popup closed by userCustomer closed the popup without completing payment
klarnaFailed to check Klarna statusError while polling for payment completion
klarnaKlarna checkout cancelledPayment was cancelled by the customer
klarnaKlarna checkout failedPayment failed on the provider’s side
klarnaPolling error: {error}Network error during status polling
klarnaKlarna checkout error: {error}General checkout error