Skip to main content

Payment providers

Propeller stores payment records but does not process payments itself. A payment service provider (PSP) does the processing. The Storefront SDK ships two provider packages that wire a PSP to Propeller for you, so you do not implement the payment mutations by hand:

PackageProviderPSP
@propeller-commerce/propeller-v2-mollieMollieProviderMollie
@propeller-commerce/propeller-v2-mspMspProviderMultiSafepay

Both work with all three Accelerator boilerplates (Next.js, Vue and Nuxt) and with any server that runs the SDK. They expose the same shape, so switching provider is mostly swapping the class.

Server-side only

These packages hold your secret PSP API key and issue privileged Propeller mutations. Run them in your backend (a route handler or API route). Never import them into browser code.

What a provider does

A provider is framework-agnostic and owns no HTTP routes; your app owns those. Across a checkout it covers three moments:

  1. Start a payment at checkout: create the PSP payment and the matching Propeller payment record, then hand back a checkout URL to redirect to.
  2. Handle the webhook the PSP calls: re-fetch the payment from the PSP, then update the Propeller payment and order status.
  3. Resolve the outcome on your return page: look up the live status to choose what to show and whether to clear the local cart.

You provide the three routes (checkout, webhook, return); the provider does the Propeller and PSP work inside them.

Configure the client

A provider needs an SDK GraphQL client configured for server-side use, with both the API key and the order-editor API key:

import { GraphQLClient } from '@propeller-commerce/propeller-sdk-v2';

const client = new GraphQLClient({
endpoint: process.env.PROPELLER_GRAPHQL_ENDPOINT!,
apiKey: process.env.PROPELLER_API_KEY!,
orderEditorApiKey: process.env.PROPELLER_ORDER_EDITOR_API_KEY!,
securityMode: 'direct',
});

This is the server-side client that holds the keys directly, not the browser /api/graphql proxy the UI uses. The order-editor API key is required: the provider sets the order status on every webhook, and that mutation routes to the order-editor key. Without it the order-status update fails silently while the payment update appears to succeed. The Accelerator and Deployment guides cover both keys.

The three steps

The examples use Mollie in a Next.js route handler. MspProvider is identical except for the differences below.

1. Start a payment

Construct the provider once as a singleton, then create the payment at checkout. createPayment creates the PSP payment and a Propeller payment record (status OPEN with an authorization transaction) and returns the checkout URL:

import { MollieProvider } from '@propeller-commerce/propeller-v2-mollie';

export const provider = new MollieProvider(
{
liveApiKey: process.env.MOLLIE_LIVE_KEY!,
testApiKey: process.env.MOLLIE_TEST_KEY!,
testMode: process.env.MOLLIE_TEST_MODE === 'true',
},
{ client, webhookUrl: `${process.env.PUBLIC_BASE_URL}/api/mollie/webhook` },
);

const { checkoutUrl, paymentId } = await provider.createPayment({
orderId: 12345, // the order id from cartProcess
amount: '49.95', // major units, not cents
currency: 'EUR',
method: 'ideal', // Propeller method code, mapped to the PSP
description: 'Order #12345',
redirectUrl: `${process.env.PUBLIC_BASE_URL}/checkout/thank-you/12345`,
userId: 678, // or anonymousId for guests
});

// Stash paymentId for the return page, then redirect the customer to checkoutUrl.

The webhookUrl must be a public URL. A PSP cannot reach localhost, so use a tunnel such as cloudflared or ngrok in development.

2. Handle the webhook

The PSP calls your webhook route with a payment id. Pass it to handleWebhook, which re-fetches the payment from the PSP (the request body is never trusted beyond the id), then updates the Propeller payment and order. It never throws, so always return 200 and the PSP does not enter a retry loop:

// app/api/mollie/webhook/route.ts
import { NextResponse } from 'next/server';
import { provider } from '@/lib/mollie';

export async function POST(req: Request) {
const body = new URLSearchParams(await req.text()); // Mollie posts form-encoded
await provider.handleWebhook(body.get('id') ?? '');
return new NextResponse(null, { status: 200 }); // always 200
}

3. Resolve the outcome

The PSP redirects the customer back to your redirectUrl for every outcome, and the webhook that finalizes the order is asynchronous, so it may not have arrived yet. Ask the PSP directly with getPaymentStatus, passing the paymentId you stashed in step 1. It is read-only and always resolves:

const result = await provider.getPaymentStatus(paymentId);
// { ok, paymentId, status?, settled?, orderId? }

Use settled to decide the return-page UI and whether to clear the local cart. Clear it only for a captured payment; keep it for an open or failed one so the customer can retry.

OutcomesettledReturn pageLocal cart
Paid or authorizedtrueSuccessClear it
Open or pendingfalseStill processing, re-checkKeep it
Failed, canceled or expiredfalseFailure, offer retryKeep it

Mollie and MultiSafepay differences

Both providers share the constructor shape (config first, then { client, webhookUrl }) and the same three methods. They differ in a few details:

Mollie (MollieProvider)MultiSafepay (MspProvider)
EnvironmentThe key implies the environmentSeparate live and test hosts; testMode selects the key and the host
LocaleNot configurableOptional locale in the config, default en_US
Webhook idForm-encoded id in the request bodytransactionid query parameter
paymentIdMollie payment id (pay_...)The order id (MultiSafepay keys everything by order id)

See also

  • Payment integration for the payment records, statuses and transactions the provider writes, plus from-scratch or other-PSP integration
  • Checkout flow for setting addresses, shipping and payment method before order creation
  • Accelerator for the boilerplates and their environment
  • Deployment for the server-side environment variables
  • UI components for CartPaymethods and useCheckout, which collect the payment method