> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.paymentkit.com/guides/integration/sdk-reference/payment-kit-js/buy-now-pay-later/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paymentkit.com/_mcp/server.
# Buy now, pay later
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.
#### CDN
```html
```
#### NPM/ES Modules
```typescript
import PaymentKit from '@payment-kit-js/vanilla';
import KlarnaPaymentMethod from '@payment-kit-js/vanilla/payment-methods/klarna';
import AfterpayPaymentMethod from '@payment-kit-js/vanilla/payment-methods/afterpay';
import AffirmPaymentMethod from '@payment-kit-js/vanilla/payment-methods/affirm';
const paymentKit = PaymentKit({
environment: 'sandbox',
secureToken: 'aBcDeFgHiJkLmNoPqRsTuVwXyZ123456', // From your backend
paymentMethods: [KlarnaPaymentMethod, AfterpayPaymentMethod, AffirmPaymentMethod]
});
```
# 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'`:
```typescript
document.getElementById('klarna-button').addEventListener('click', () => {
paymentKit.submit({
fields: {
customer_name: 'Jane Smith',
customer_email: 'jane@example.com',
customer_country: 'US',
customer_zip_code: '94102'
},
paymentMethod: 'klarna', // or 'afterpay' / 'affirm'
options: {
processorId: 'proc_abc123',
customerInfo: {
first_name: 'Jane',
last_name: 'Smith',
email: 'jane@example.com'
}
},
onSuccess: (result) => {
console.log('ID:', result.id);
console.log('Checkout Attempt ID:', result.checkoutAttemptId);
console.log('Checkout Session ID:', result.checkoutSessionId);
console.log('State:', result.state);
window.location.href = '/success';
},
onError: (errors) => {
console.error(errors);
}
});
});
```
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.
> **Warning**
>
> The popup must be triggered by a direct user action (button click) to avoid being blocked by popup blockers.
> **Info**
>
> Need to run your own validation before the charge? Pass `beforeConfirm` in `options`. The popup opens synchronously on tap, but PaymentKit waits for `beforeConfirm` before navigating it to the provider — reject and the popup closes without the provider ever seeing the request. See [Pre-charge validation](/guides/integration/sdk-reference/payment-kit-js/pre-charge-validation).
# Options
| Option | Type | Required | Methods | Description |
| ------------------------- | --------- | -------- | ----------- | ----------------------------------------------------------------------------- |
| `processorId` | `string` | Yes | All | Your processor ID (BNPL payments are processed through your Stripe processor) |
| `customerInfo.first_name` | `string` | Yes | All | Customer's first name |
| `customerInfo.last_name` | `string` | Yes | All | Customer's last name |
| `customerInfo.email` | `string` | No | All | Customer's email address |
| `setupFutureUsage` | `boolean` | No | Klarna only | Save 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:
```typescript
paymentKit.submit({
fields: { /* ... */ },
paymentMethod: 'klarna',
options: {
processorId: 'proc_abc123',
customerInfo: {
first_name: 'Jane',
last_name: 'Smith'
},
setupFutureUsage: true
},
onSuccess: (result) => { /* ... */ },
onError: (errors) => { /* ... */ }
});
```
> **Info**
>
> `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`:
```typescript
onError: (errors) => {
if (errors.processor_id) {
// Missing processor ID
console.error(errors.processor_id); // "Processor ID is required"
} else if (errors.customer_name) {
// Missing customer name
console.error(errors.customer_name); // "Customer first and last name are required"
} else if (errors.klarna) {
// Klarna-specific errors (same patterns apply to errors.afterpay / errors.affirm)
if (errors.klarna.includes('popup')) {
// Popup was blocked or closed
alert('Please allow popups for this site');
} else if (errors.klarna.includes('cancelled')) {
// Customer cancelled in the provider's flow
}
console.error(errors.klarna);
}
}
```
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 Field | Error Message | Cause |
| --------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `processor_id` | `Processor ID is required` | Missing `processorId` in options |
| `customer_name` | `Customer first and last name are required` | Missing `customerInfo.first_name` or `customerInfo.last_name` |
| `klarna` | `Failed to start Klarna checkout ({status})` | The start request failed (the backend error detail is used when available) |
| `klarna` | `Failed to open Klarna popup. Please allow popups for this site.` | Browser blocked the popup window |
| `klarna` | `Klarna popup closed by user` | Customer closed the popup without completing payment |
| `klarna` | `Failed to check Klarna status` | Error while polling for payment completion |
| `klarna` | `Klarna checkout cancelled` | Payment was cancelled by the customer |
| `klarna` | `Klarna checkout failed` | Payment failed on the provider's side |
| `klarna` | `Polling error: {error}` | Network error during status polling |
| `klarna` | `Klarna checkout error: {error}` | General checkout error |
> Accept Klarna, Afterpay, and Affirm payments using a popup-based approval flow.