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 has two server-side packages that connect a PSP to Propeller, so you do not write the payment mutations yourself:

PackageClassPSPDocs
@propeller-commerce/propeller-v2-mollieMollieProviderMollieREADME
@propeller-commerce/propeller-v2-mspMspProviderMultiSafepayREADME

Both have the same shape, so switching PSP is mostly swapping the class. The READMEs hold the full API with code for every step. This page covers how a payment flows through Propeller.

Checked against propeller-v2-mollie 0.2.4, propeller-v2-msp 0.1.3 and the boilerplates of 18 October 2026.

Server-side only

These packages hold your secret PSP key and run privileged Propeller mutations. Use them in your server code (a route handler or API route), never in browser code.

In the Accelerator boilerplates​

All three boilerplates already wire both providers. PAYMENT_PROVIDER (mollie or multisafepay) picks the one that runs, and scaffolding with --psp removes the other. The routes are in place in every stack:

RouteStep
/api/mollie/create-payment or /api/msp/create-paymentStart a payment
/api/mollie/webhook or /api/msp/webhookReceive the PSP's webhook
/api/mollie/payment-status or /api/msp/payment-statusLook up the outcome for the return page

With the PSP keys, the order-editor API key and the site URL set, payments work. The webhook address is built from the site URL. Read on to understand the flow, or to add a provider to your own app.

The three steps​

A provider owns no routes; your app does. Across a checkout it covers three moments:

  1. Start a payment after the order is created (cartProcess): createPayment creates the PSP payment and a Propeller payment record with status OPEN and an authorization transaction, and returns the PSP's checkout URL to send the customer to. Keep the returned paymentId for the return page.
  2. Handle the webhook the PSP calls: handleWebhook takes the payment id from the request, fetches that payment from the PSP (it trusts nothing else in the request), then updates the Propeller payment and the order status. It never throws, so always answer 200 and the PSP does not keep retrying.
  3. Resolve the outcome on your return page: getPaymentStatus(paymentId) asks the PSP for the current status, because the webhook may not have arrived yet when the customer comes back.

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

The server-side client and its keys​

A provider needs an SDK client for server-side use that holds both API keys:

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 client calls the API directly with the keys, unlike the browser client that goes through /api/graphql. The order-editor API key is required: the provider sets the order status on every webhook, and that mutation runs with the order-editor key. Without it the order status is not updated, while the payment update still succeeds. See Keys and security for what each key may do.

The outcome on the return page​

The PSP sends the customer back to your return URL for every outcome. Use settled from getPaymentStatus to choose what to show and whether to clear the local cart. Clear it only for a captured payment, and keep it for an open or failed one so the customer can try again.

OutcomesettledReturn pageLocal cart
Paid or authorizedtrueSuccessClear it
Open or pendingfalseStill processing, check againKeep it
Failed, canceled or expiredfalseFailure, offer a retryKeep it

Mollie and MultiSafepay differences​

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
paymentIdThe Mollie payment id (tr_...)The order id (MultiSafepay keys everything by order id)

See also​