# Vendure

import { Aside, LinkButton } from "@astrojs/starlight/components";

The [SumUp plugin for Vendure](https://github.com/sumup/sumup-plugin-vendure) adds SumUp as a payment integration for [Vendure](https://vendure.io/). It creates SumUp checkouts from Vendure's payment flow while keeping your SumUp credentials on the server side.

The plugin supports:

- Hosted Checkout, where the customer is redirected to a SumUp-hosted payment page
- Widget-oriented storefront integrations that use a returned `checkoutId`
- Webhook-driven payment updates through Vendure's payment flow

<Aside type="note">
  The plugin supports SumUp online payment flows only. It does not support
  terminal or other card-present integrations.
</Aside>

<LinkButton
  href="https://github.com/sumup/sumup-plugin-vendure"
  icon="external"
>
  View Vendure Plugin Repository
</LinkButton>

## Prerequisites

- Active [SumUp account](https://me.sumup.com)
- SumUp [API key](/tools/authorization/api-keys/#create-an-api-key)
- SumUp merchant code
- Vendure `^3.6.4`
- A Vendure server with payment methods enabled

Before going live, make sure your SumUp account is fully verified and your business model is supported according to our [allowed businesses article](https://help.sumup.com/en-GB/articles/3G8ZHjdgFZjmKWOmDWS52m).

## Install the Plugin

Install the package in your Vendure project:

```bash
npm install @sumup/vendure-plugin
```

## Configure Vendure

Register the plugin and payment handler in your Vendure config:

```ts
import { VendureConfig } from "@vendure/core";

import {
  SumUpPlugin,
  sumUpPaymentHandler,
} from "@sumup/vendure-plugin";

export const config: VendureConfig = {
  plugins: [
    SumUpPlugin.init({
      apiKey: process.env.SUMUP_API_KEY!,
      merchantCode: process.env.SUMUP_MERCHANT_CODE!,
      checkoutMode: "hosted",
      returnUrl: "https://your-vendure.example/payments/sumup/webhook",
      redirectUrl: "https://storefront.example/checkout/sumup/return",
    }),
  ],
  paymentOptions: {
    paymentMethodHandlers: [sumUpPaymentHandler],
  },
};
```

`returnUrl` should be a publicly reachable URL that SumUp can call with checkout status updates. In most setups that should be your Vendure server's `/payments/sumup/webhook` route.

## Create the Payment Method

Create a payment method in the Vendure Admin UI with:

- `Code`: `sumup`
- `Handler`: `sumup`

Optional handler arguments:

- `merchantCode`
- `checkoutMode`
- `returnUrl`
- `redirectUrl`
- `paymentDescription`

Global defaults can be defined in `SumUpPlugin.init()` and overridden per payment method when needed.

## Storefront Flow

Once the order is in `ArrangingPayment`, call `addPaymentToOrder` with `method: "sumup"` and any SumUp-specific metadata you need:

```graphql
mutation AddPaymentToOrder {
  addPaymentToOrder(
    input: {
      method: "sumup"
      metadata: {
        checkout_mode: "hosted"
        checkout_reference: "ORDER-1001"
      }
    }
  ) {
    ... on Order {
      id
      state
      payments {
        transactionId
        metadata
      }
    }
    ... on ErrorResult {
      errorCode
      message
    }
  }
}
```

The plugin stores SumUp data on the Vendure payment and exposes a safe subset through `payments[].metadata.public`.

## Choose a Checkout Flow

### Hosted Checkout

Use `checkout_mode: "hosted"` or set `checkoutMode: "hosted"` in the plugin or payment-method config.

After `addPaymentToOrder`, redirect the shopper to:

```text
payments[].metadata.public.hostedCheckoutUrl
```

Use your backend payment state as the source of truth. The redirect alone should not be treated as proof of a successful payment.

### Widget-Oriented Flow

Use `checkout_mode: "widget"` if your storefront will mount SumUp's checkout UI itself.

After `addPaymentToOrder`, read:

```text
payments[].metadata.public.checkoutId
```

Use that `checkoutId` in your storefront's SumUp client integration. The plugin still treats the webhook callback or a later checkout lookup as the source of truth for the final payment state.

If you need a lower-level widget implementation reference, see the [Payment Widget guide](/online-payments/checkouts/card-widget/).

## Webhooks

The plugin exposes a notification endpoint at:

```text
POST /payments/sumup/webhook
```

When SumUp calls this endpoint, the plugin re-fetches the checkout from SumUp and updates the matching Vendure payment from the checkout state.

## Public Payment Metadata

The plugin exposes these fields in `payments[].metadata.public`:

| Field | Description |
| --- | --- |
| `checkoutId` | SumUp checkout ID |
| `checkoutReference` | Merchant checkout reference sent to SumUp |
| `checkoutMode` | `hosted` or `widget` |
| `hostedCheckoutUrl` | Hosted Checkout URL when SumUp returns one |
| `redirectUrl` | Redirect URL associated with the checkout |

## Configuration Options

| Option | Required | Description |
| --- | --- | --- |
| `apiKey` | Yes | SumUp API key or access token. Keep it server-side. |
| `merchantCode` | Yes | SumUp merchant code that receives the payment. |
| `defaultLanguageCode` | No | Language used for the handler description shown in Vendure. |
| `checkoutMode` | No | Default checkout mode: `hosted` or `widget`. Defaults to `hosted`. |
| `returnUrl` | No | Backend callback URL used by SumUp for checkout status updates. |
| `redirectUrl` | No | URL the shopper is sent to after redirect-based payment flows. |
| `paymentDescription` | No | Default SumUp checkout description. |
| `timeout` | No | SumUp SDK request timeout in milliseconds. |
| `maxRetries` | No | SumUp SDK retry count. |
| `supportedCurrencies` | No | Override the built-in supported currency allowlist. |
| `client` | No | Inject a custom SumUp client implementation. Useful for tests. |

## Payment State Mapping

The plugin maps SumUp checkout state to Vendure payment state like this:

- Successful transaction or `PAID` checkout -> `Settled`
- `PENDING` -> `Authorized`
- `FAILED` -> `Declined`
- `EXPIRED` -> `Cancelled`
- Anything else -> `Created`

## Sandbox Checklist

Before enabling the plugin in production, verify the following in a sandbox environment:

- One successful Hosted Checkout payment
- One successful widget-oriented payment flow
- At least one webhook-driven payment update
- SumUp's deliberate failure path with amount `11`
- Expired or canceled checkouts mapping cleanly back into Vendure payment state

<Aside type="note">
  The plugin does not add Admin UI extensions or extend Vendure's GraphQL
  schema. It uses the standard `addPaymentToOrder` payment metadata flow.
</Aside>