Payment providers, set up a PSP
PayFanout installs every PSP the same shape: a server adapter that holds your secret credentials, a client adapter that holds only a browser-safe key, and (optionally) a webhook endpoint. Application code never learns which PSP is active, so "install a PSP" is the same job whether it is one we ship or one you write yourself.
Pick the PSP you're wiring up:
| PSP | Set-up guide | Server package | Client package | Completion shape |
|---|---|---|---|---|
| Stripe | Set up Stripe | @payfanout/adapter-stripe-server | @payfanout/adapter-stripe | Confirm-on-client |
| Paysafe | Set up Paysafe | @payfanout/adapter-paysafe-server | @payfanout/adapter-paysafe | Tokenize-first (needs a server-completion route) |
| GoCardless | Set up GoCardless | @payfanout/adapter-gocardless-server | @payfanout/adapter-gocardless | Confirm-on-client (redirect to hosted bank authorisation) |
| PayPal | Set up PayPal | @payfanout/adapter-paypal-server | @payfanout/adapter-paypal | Tokenize-first (needs a server-completion route) |
| PayZen (Lyra) | Set up PayZen | @payfanout/adapter-payzen-server | @payfanout/adapter-payzen | Confirm-on-client |
| Worldline (Direct) | Set up Worldline | @payfanout/adapter-worldline-server | @payfanout/adapter-worldline | Tokenize-first (needs a server-completion route) |
| Adyen | Set up Adyen | @payfanout/adapter-adyen-server | @payfanout/adapter-adyen | Tokenize-first (needs a server-completion route) |
New to the packages themselves? Installation covers prerequisites, which packages to add, and the env-var mechanics first. This page and the guides below are about wiring a specific PSP end to end.
The four steps, for every PSP
The guides follow the identical arc, only the credential names and a few quirks differ:
- Get credentials. Every PSP has a sandbox and a live set. PayFanout never infers which you're using from a key prefix, you pass
environment: "sandbox" | "live"explicitly, and the adapter throws if you don't. - Wire the server adapter. Construct it from environment variables and register it on a
PaymentService. The constructor validates its config and throws at startup on a missing required field, a misconfiguration can never reach checkout. - Wire the client adapter. Construct it with the browser-safe key and hand it to
<PayFanoutProvider>. The PSP's browser SDK loads lazily from the PSP's CDN, there is nothing extra topnpm add. - Register the webhook endpoint. Point the PSP at your
/webhooks/<psp>route and give the adapter the signing secret. Hand the handler the exact raw request bytes, see Webhooks for the raw-body requirement that every framework fights you on.
Which side do I need?
Server-only backend? Steps 1, 2, 4. Adding the embedded card fields? Also step 3. You do not have to install every PSP, add only the one(s) you use.
Sandbox first, always
Do the whole integration against the PSP's sandbox, environment: "sandbox", test keys, test cards. Nothing about going live changes your PayFanout code except the credentials and the environment string; each set-up guide ends with a Go live checklist that spells out exactly what swaps. Because PayFanout is stateless, there is no data migration between sandbox and live, the switch is credentials only.
Content-Security-Policy on a page with several PSPs
The set-up guides list the sources each PSP's browser SDK needs and what the client adapter's cspNonce option covers on a page whose policy allows scripts and styles by nonce. A page that can mount more than one PSP, side by side or one after another as the router fails over, needs one policy that allows what every one of them loads, and one CSP rule reaches across PSPs: a nonce or a hash in a directive turns 'unsafe-inline' off for that whole directive. CSP3 states it as "If expression matches the nonce-source or hash-source grammar, return "Does Not Allow"", and Chromium applies it: under style-src 'nonce-…' 'unsafe-inline' it blocks a <style> element without the nonce and any style attribute.
So giving style-src a nonce for one PSP, as a PSP's own nonce-based policy may ask, also blocks the inline styles that every other PSP on the page adds without that nonce, and no nonce can cover a style attribute. A nonce in script-src does the same to inline scripts. Each guide says which inline styles its SDK adds. Before a page that mounts several PSPs gives a directive a nonce, check every PSP it can mount, and if one of them needs 'unsafe-inline' there, keep 'unsafe-inline' with no nonce or hash beside it.
Trusted Types
A page that enforces Trusted Types (require-trusted-types-for 'script') needs a default policy whose createScriptURL returns the URL only for the hosts each set-up guide lists, and nothing for any other. The client adapters insert their SDK's <script> with a plain URL, and PayFanout creates no policy of its own: when the page has no default policy, or its policy rejects the URL, loadSdk() rejects with a non-retryable invalid_request, carrying on raw the exception the browser threw. The directive also governs HTML and script-text sinks, so an SDK that writes to one goes through the same policy and fails on its own terms if the policy refuses it: try each PSP's SDK on a page that enforces the policy before going live. The specification warns that "a lax, no-op default policy defeats all the benefits of using Trusted Types" and advises a default policy "in a transitional period only".
Installing a PSP we don't ship yet
Another PSP is a new adapter package, not a fork. You implement the ServerPaymentAdapter / ClientPaymentAdapter contracts from @payfanout/core, prove the adapter against @payfanout/conformance, the same suite every shipped adapter passes, and it drops into the exact four steps above with zero changes to core, server, React, or your application code. The step-by-step build is Writing an adapter, and Conformance is how "extensible" stays a guarantee rather than a hope.