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:
| Package | Class | PSP | Docs |
|---|---|---|---|
@propeller-commerce/propeller-v2-mollie | MollieProvider | Mollie | README |
@propeller-commerce/propeller-v2-msp | MspProvider | MultiSafepay | README |
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.
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:
| Route | Step |
|---|---|
/api/mollie/create-payment or /api/msp/create-payment | Start a payment |
/api/mollie/webhook or /api/msp/webhook | Receive the PSP's webhook |
/api/mollie/payment-status or /api/msp/payment-status | Look 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:
- Start a payment after the order is created (
cartProcess):createPaymentcreates the PSP payment and a Propeller payment record with statusOPENand an authorization transaction, and returns the PSP's checkout URL to send the customer to. Keep the returnedpaymentIdfor the return page. - Handle the webhook the PSP calls:
handleWebhooktakes 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 answer200and the PSP does not keep retrying. - 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.
| Outcome | settled | Return page | Local cart |
|---|---|---|---|
| Paid or authorized | true | Success | Clear it |
| Open or pending | false | Still processing, check again | Keep it |
| Failed, canceled or expired | false | Failure, offer a retry | Keep it |
Mollie and MultiSafepay differences
Mollie (MollieProvider) | MultiSafepay (MspProvider) | |
|---|---|---|
| Environment | The key implies the environment | Separate live and test hosts; testMode selects the key and the host |
| Locale | Not configurable | Optional locale in the config, default en_US |
| Webhook id | Form-encoded id in the request body | transactionid query parameter |
paymentId | The Mollie payment id (tr_...) | The order id (MultiSafepay keys everything by order id) |
See also
- Payment integration for the payment records, statuses and transactions a provider writes, and for other PSPs
- Checkout flow for addresses, shipping and the payment method before the order is created
- Keys and security for the order-editor key
- Accelerator for the payment settings per stack