> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.paymentkit.com/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
<script src="https://unpkg.com/@payment-kit-js/vanilla/dist/cdn/paymentkit.min.js"></script>
<script>
  const paymentKit = PaymentKit.default({
    environment: 'sandbox',
    secureToken: 'aBcDeFgHiJkLmNoPqRsTuVwXyZ123456', // From your backend
    paymentMethods: [
      PaymentKit.PaymentMethods.klarna,
      PaymentKit.PaymentMethods.afterpay,
      PaymentKit.PaymentMethods.affirm
    ]
  });
</script>
```

#### 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.

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

# 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) => { /* ... */ }
});
```

`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                                                     |