Skip to main content
Business & GrowthPayment Services253 lines

Checkout Com

Accept payments with Checkout.com. Use this skill when the project needs to

Quick Summary33 lines
You are a payments specialist who integrates Checkout.com into projects.
Checkout.com is a cloud-based payment platform offering direct API access to
card networks, alternative payment methods, and payouts across 150+ currencies.

## Key Points

- Dashboard: `hub.sandbox.checkout.com`
- Approved card: `4242 4242 4242 4242`
- Declined card: `4242 4242 4242 4241`
- 3DS card: `4242 4242 4242 4000`
- Expiry: any future date, CVV: `100`
- Always include `Idempotency-Key` headers on payment requests to prevent duplicates
- Use Frames.js for PCI SAQ-A compliance — card data never touches your server
- Verify webhook signatures using HMAC-SHA256 before processing events
- Store `payment.id` from every transaction for captures, refunds, and reconciliation
- Enable 3D Secure on all card payments for liability shift
- Use separate authorize and capture for physical goods; auto-capture for digital
- Set `metadata` on payments to link back to your internal models

## Quick Example

```bash
npm install checkout-sdk-node
```

```typescript
await cko.payments.capture(paymentId, {
  amount: 2999, // can partial-capture
  reference: `capture-${orderId}`,
});
```
skilldb get payment-services-skills/Checkout ComFull skill: 253 lines
Paste into your CLAUDE.md or agent config

Checkout.com Payment Integration

You are a payments specialist who integrates Checkout.com into projects. Checkout.com is a cloud-based payment platform offering direct API access to card networks, alternative payment methods, and payouts across 150+ currencies.

Core Philosophy

Direct API gives full control

Checkout.com exposes raw payment primitives. You control the entire flow — from tokenizing cards client-side with Frames.js to capturing funds server-side. This flexibility suits merchants who need fine-grained control.

Separate authorize and capture

By default, payments are authorized but not captured. This lets you validate orders before taking funds. Use auto-capture or explicit capture depending on your fulfillment model.

Idempotency prevents duplicate charges

Every payment request should include an Idempotency-Key header to safely retry failed requests without double-charging customers.

Setup

Install

npm install checkout-sdk-node

Initialize

import { Checkout } from 'checkout-sdk-node';

const cko = new Checkout(process.env.CKO_SECRET_KEY, {
  pk: process.env.CKO_PUBLIC_KEY,
  environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox',
});

Key Techniques

Tokenize cards with Frames.js (client-side)

<script src="https://cdn.checkout.com/js/framesv2.min.js"></script>

<form id="payment-form">
  <div class="card-frame"></div>
  <button type="submit">Pay</button>
</form>

<script>
  Frames.init({
    publicKey: 'pk_sbox_xxx',
    localization: 'EN-GB',
  });

  Frames.addEventHandler(Frames.Events.CARD_VALIDATION_CHANGED, (event) => {
    document.querySelector('button').disabled = !Frames.isCardValid();
  });

  document.getElementById('payment-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const { token } = await Frames.submitCard();
    // Send token to your server
    await fetch('/api/pay', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token, amount: 2999 }),
    });
  });
</script>

Request a payment (server-side)

const payment = await cko.payments.request({
  source: {
    type: 'token',
    token: cardToken, // from Frames.js
  },
  amount: 2999, // minor units
  currency: 'USD',
  reference: `order-${orderId}`,
  customer: {
    email: 'shopper@example.com',
    name: 'Alice Smith',
  },
  metadata: { userId, orderId },
  '3ds': { enabled: true },
  capture: false, // authorize only; capture later
}, {
  idempotencyKey: `pay-${orderId}`,
});

if (payment.status === 'Pending') {
  // 3DS redirect required
  return { redirectUrl: payment._links.redirect.href };
}

// payment.id is the payment ID for capture/refund

Capture a payment

await cko.payments.capture(paymentId, {
  amount: 2999, // can partial-capture
  reference: `capture-${orderId}`,
});

Refund a payment

await cko.payments.refund(paymentId, {
  amount: 2999, // can partial-refund
  reference: `refund-${orderId}`,
});

Save a card for future payments

// First payment — request a reusable source
const payment = await cko.payments.request({
  source: { type: 'token', token: cardToken },
  amount: 2999,
  currency: 'USD',
  customer: { email: 'shopper@example.com' },
  metadata: { userId },
  capture: true,
});

// Store payment.source.id (src_xxx) for future charges
const savedSourceId = payment.source.id;

// Subsequent charges — use the stored source
const recurring = await cko.payments.request({
  source: { type: 'id', id: savedSourceId },
  amount: 2999,
  currency: 'USD',
  customer: { email: 'shopper@example.com' },
  payment_type: 'Recurring',
  capture: true,
}, {
  idempotencyKey: `renewal-${Date.now()}`,
});

Hosted payment page (Flow)

const hostedSession = await cko.hostedPayments.create({
  amount: 2999,
  currency: 'USD',
  reference: `order-${orderId}`,
  billing: { address: { country: 'US' } },
  customer: { email: 'shopper@example.com' },
  success_url: 'https://yourdomain.com/success',
  cancel_url: 'https://yourdomain.com/cancel',
  failure_url: 'https://yourdomain.com/failure',
});

// Redirect user to hostedSession._links.redirect.href

Webhook Processing

EventAction
payment_approvedMark order as paid
payment_capturedConfirm funds captured, fulfill order
payment_declinedMark order failed, notify customer
payment_refundedProcess refund in your system
payment_voidedCancel order
payment_capture_declinedRetry capture or escalate
dispute_receivedHandle chargeback
import crypto from 'crypto';

export async function POST(req: Request) {
  const body = await req.text();
  const signature = req.headers.get('cko-signature');

  // Verify signature
  const expected = crypto
    .createHmac('sha256', process.env.CKO_WEBHOOK_SECRET)
    .update(body)
    .digest('hex');

  if (signature !== expected) {
    return new Response('Invalid signature', { status: 401 });
  }

  const event = JSON.parse(body);

  switch (event.type) {
    case 'payment_approved':
      await markOrderPaid(event.data.reference, event.data.id);
      break;

    case 'payment_captured':
      await fulfillOrder(event.data.reference);
      break;

    case 'payment_declined':
      await markOrderFailed(event.data.reference, event.data.response_summary);
      break;

    case 'payment_refunded':
      await processRefund(event.data.reference, event.data.amount);
      break;
  }

  return new Response('OK', { status: 200 });
}

Testing

Checkout.com sandbox environment:

  • Dashboard: hub.sandbox.checkout.com
  • Approved card: 4242 4242 4242 4242
  • Declined card: 4242 4242 4242 4241
  • 3DS card: 4242 4242 4242 4000
  • Expiry: any future date, CVV: 100

Best Practices

  • Always include Idempotency-Key headers on payment requests to prevent duplicates
  • Use Frames.js for PCI SAQ-A compliance — card data never touches your server
  • Verify webhook signatures using HMAC-SHA256 before processing events
  • Store payment.id from every transaction for captures, refunds, and reconciliation
  • Enable 3D Secure on all card payments for liability shift
  • Use separate authorize and capture for physical goods; auto-capture for digital
  • Set metadata on payments to link back to your internal models

Anti-Patterns

  • Relying on the synchronous payment response without webhook confirmation
  • Omitting Idempotency-Key — network retries will duplicate charges
  • Capturing immediately for physical goods before confirming stock/shipping
  • Not handling 3DS redirects — the payment stays in Pending status forever
  • Storing raw card numbers instead of using Frames.js tokenization
  • Ignoring payment_declined webhooks — customer sees no feedback
  • Using the secret key client-side — it must only be used server-side

Install this skill directly: skilldb add payment-services-skills

Get CLI access →