Implement server-driven Payments

Use the Checkout Transactions and Payment Intents APIs for programmatic Payments.

Ask about this Page
Copy for LLM
View as Markdown

After completing this page, you should be able to:

  • Use the Transactions API to initiate Payments programmatically.

  • Apply the Payment Intents API to manage Payment actions.

  • Implement server-driven Payment flows for subscriptions and automated operations.

After a Customer completes the first checkout, subsequent Payment actions don't need to be customer-driven through an online checkout page.

Many real-world business cases initiate Payment on the server side, such as in the following scenarios:

  • Subscriptions and recurring billing
  • Automated retries after a temporary failure
  • Back-office operations such as delayed capture or partial refunds

Server-driven payments let your backend securely take control of these scenarios while keeping consistency with UI-driven checkout flows.

In this section of the module, you'll learn how to implement server-driven payments by using commercetools Checkout. You'll use the following two APIs:

  • Transactions API to start a Payment programmatically (without the Checkout UI)
  • Payment Intents API to manage what happens after the Payment has been authorized — including capturing, cancelling, or refunding

Transactions API

The Checkout Transactions API lets you initiate tokenized Payments that use a previously stored, secure payment token, without relying on the Checkout UI. This is especially useful for scenarios like Subscriptions, warranty extensions, or phone orders where the user isn't interacting with the storefront directly.

Here's how the API works for tokenized payments:

  1. After a customer completes an initial Checkout Session by using their Payment method (like a credit card), the payment service provider (PSP) can return a token that represents the Payment method.
  2. You can securely store this token in your system (for example, linked to the Customer profile) and reuse it for future transactions without requiring the user to re-enter their Payment details.
  3. By using the Transactions API, you can then trigger a new Payment at any time by sending a request that contains the token along with a reference to the Cart and the required Payment details.
  4. The API communicates with the same Payment Connector that's used in standard Checkout flows, and the same Payment lifecycle applies—including Payment status updates and Checkout Messages.

This API enables fully programmatic, headless Payments while still leveraging the robust and secure commercetools Checkout backend—perfect for automating recurring charges or processing deferred payments.

To start a payment, your BFF calls the Transactions API to link the Cart with a specific Payment Integration like Stripe, Adyen, or Paypal.

Create a transaction with the Transactions APIts
async createTransaction(input: {
  cartId: string;
  centAmount: number;
  currencyCode: string;
  paymentIntegrationId: string;
  applicationKey?: string;
  idempotencyKey?: string;
}) {
  const token = await this.getCheckoutToken(
    `manage_checkout_transactions:${this.projectKey} manage_project:${this.projectKey}`
  );
  const body = {
    application: { typeId: 'application', key: input.applicationKey ?? 'demo-key' },
    cart: { typeId: 'cart', id: input.cartId },
    transactionItems: [
      {
        paymentIntegration: { typeId: 'payment-integration', id: input.paymentIntegrationId },
        amount: { centAmount: input.centAmount, currencyCode: input.currencyCode },
      },
    ],
  };

  const resp = await fetch(`${this.checkoutHost}/${this.projectKey}/transactions`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      ...(input.idempotencyKey ? { 'Idempotency-Key': input.idempotencyKey } : {}),
    },
    body: JSON.stringify(body),
  });

  if (!resp.ok) throw new Error(`Create transaction failed: ${resp.status} ${await resp.text()}`);
  return resp.json();
}


This code creates the Transaction resource and kicks off the Connector flow, which results in a Payment resource that you can use for the Intent API later to manage the rest of the Payment cycle.

The following diagram shows how a tokenized Payment flow works by using the Checkout Transactions API. This flow lets you trigger secure, headless Payments-such as for Subscriptions or phone orders—by reusing a stored Payment token without involving the Checkout UI:

Even though the Transactions API bypasses the visual Checkout UI, it still uses the same underlying Payment lifecycle as a standard Checkout Session, including Authorization, Payment capture, Authorization cancellation, and Refund. commercetools Checkout tracks and manages these stages.

This consistency ensures the following benefits:

  • You can rely on the same Messages and events for downstream processing.
  • Payment statuses are updated in real time.
  • Any business logic or automation that depends on these events (like Order creation or notifications) continues to work seamlessly.

In short, the Transactions API gives you full control and flexibility while preserving the trusted and robust Checkout backend behavior.

Payment Intents API

After you create a Payment resource (whether by using a Transaction API call or through a traditional checkout flow), the Payment Intents API lets you control what happens next in the Payment lifecycle.

With this API, you can perform the following key actions that normally occur after a Payment has been authorized:

  • capturePayment: settle the funds that were previously authorized, typically after goods are shipped.
  • cancelAuthorization: void an authorization if the Payment shouldn't proceed, such as when a Customer cancels an Order before shipment.
  • refundPayment: return money to a Customer, either partially or fully, after a Payment has already been captured.

These operations are performed against the Payment resource that Checkout creates during Payment authorization. This ensures that you're always working within commercetools' consistent Payment model.

To capture an amount that has been previously authorized, use the capturePayment action to settle the funds.
Capture a payment using the Payment Intents APIts
async capturePayment(paymentId: string, centAmount?: number, currencyCode?: string) {
  const { hasAuthSuccess, fullAmount } = await this.getPaymentState(paymentId);

  if (!hasAuthSuccess) throw new Error('Payment is not authorized yet.');

  const token = await this.getCheckoutToken(
    `manage_checkout_payment_intents:${this.projectKey} manage_project:${this.projectKey}`
  );

  const amount = {
    centAmount: centAmount ?? fullAmount.centAmount,
    currencyCode: currencyCode ?? fullAmount.currencyCode,
  };

  const r = await fetch(`${this.checkoutHost}/${this.projectKey}/payment-intents/${paymentId}`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ actions: [{ action: 'capturePayment', amount }] }),
  });

  if (!r.ok) throw new Error(`Capture failed: ${r.status} ${await r.text()}`);
  return r.json();
}
If a customer cancels an Order before shipment or you need to cancel an Order due to fulfillment issues, you can cancel the authorization by using the cancelAuthorization action to release the funds.
Cancel an authorization using the Payment Intents APIts
async cancelAuthorization(paymentId: string) {
  const token = await this.getCheckoutToken(
    `manage_checkout_payment_intents:${this.projectKey} manage_project:${this.projectKey}`
  );

  const r = await fetch(`${this.checkoutHost}/${this.projectKey}/payment-intents/${paymentId}`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ actions: [{ action: 'cancelPayment' }] }),
  });

  if (!r.ok) throw new Error(`Cancel authorization failed: ${r.status} ${await r.text()}`);
  return r.json();
}
Similarly, if a Customer returns an item, you can issue a partial or full refund against the captured Payment by using the refundPayment action.
Refund a payment using the Payment Intents APIts
async refundPayment(paymentId: string, centAmount: number, currencyCode = 'EUR') {
  const token = await this.getCheckoutToken(
    `manage_checkout_payment_intents:${this.projectKey} manage_project:${this.projectKey}`
  );

  const r = await fetch(`${this.checkoutHost}/${this.projectKey}/payment-intents/${paymentId}`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ actions: [{ action: 'refundPayment', amount: { centAmount, currencyCode } }] }),
  });

  if (!r.ok) throw new Error(`Refund failed: ${r.status} ${await r.text()}`);
  return r.json();
}

Key takeaways

  • The Transactions API enables fully programmatic, headless Payments by letting you initiate tokenized Payments without the Checkout UI.
  • Store Payment tokens securely after an initial Checkout Session to reuse for future transactions like Subscriptions or recurring billing.
  • The Transactions API communicates with the same Payment Connectors used in standard Checkout flows, ensuring consistency in Payment lifecycle management.
  • The Payment Intents API provides control over post-authorization actions including capture, cancellation, and refunds.
  • Use capturePayment to settle authorized funds, typically after goods are shipped or services are delivered.
  • Use cancelAuthorization to release funds if an Order is cancelled before shipment or due to fulfillment issues.
  • Use refundPayment to return money to Customers after a Payment has been captured, supporting both partial and full refunds.
  • Server-driven Payments maintain the same Payment lifecycle stages as UI-driven checkouts, including Payment status updates and Checkout messages.

Test your knowledge