npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@payfanout/adapter-paysafe-server

v3.0.2

Published

Server-side Paysafe adapter for PayFanout (Payments REST API: Payment Handles, Payments, Settlements, Refunds, Webhooks). Holds secrets — never bundle client-side.

Readme

@payfanout/adapter-paysafe-server

Server-side Paysafe adapter for PayFanout: Payment Handles, Payments, Settlements, Refunds, Webhooks, and Payment Scheduler subscriptions, over the Paysafe REST APIs.

Holds secrets. This package uses your Paysafe REST credentials. Never bundle it client-side.

It implements the ServerPaymentAdapter contract from @payfanout/core, so @payfanout/server drives it through the same unified API as every other PSP. It talks to the REST API directly and is edge-runtime compatible (WebCrypto only, no Node builtins), so it runs on Cloudflare Workers and Next.js edge routes.

📖 Documentation: https://donapulse.github.io/payfanout/ · Set up Paysafe · Server usage

Installation

pnpm add @payfanout/server @payfanout/adapter-paysafe-server

@payfanout/core comes in transitively.

Usage

import { PaymentService } from "@payfanout/server";
import { PaysafeServerAdapter } from "@payfanout/adapter-paysafe-server";

const paysafe = new PaysafeServerAdapter({
  username: process.env.PAYSAFE_USERNAME!,
  password: process.env.PAYSAFE_PASSWORD!,
  environment: "sandbox",                       // never inferred from credentials
  merchantAccountResolver: (currency, country) => lookupAccount(currency, country), // required
  sessionSigningKey: process.env.PAYSAFE_SESSION_KEY!,   // signs the stateless session context
  webhookHmacKey: process.env.PAYSAFE_WEBHOOK_HMAC_KEY!, // string or array for rotation
});

const payments = new PaymentService({ adapters: [paysafe] });

Pair it on the browser with @payfanout/adapter-paysafe. This is a tokenize-first PSP: the client tokenizes first, then your server finalizes the payment via completePayment (wire a server-completion route for it).

The signed, stateless session

Because PayFanout persists nothing, this adapter's session is a signed, self-contained context: amount, currency, and merchant account are HMAC-signed into pspSessionId at creation and verified at completePayment. The browser round-trips the token but cannot tamper with the amount, and every context carries an expiry (sessionTtlSeconds, default 1h) enforced at completion. encodeSessionContext / decodeSessionContext are exported for advanced use.

What's inside

  • PaysafeServerAdapter, the full server contract (create/update/complete/retrieve, captures, refunds, settlements, verification via the Verifications API, saved-card charging, native subscriptions).
  • Webhook helpers, verifyPaysafeWebhookSignature and parsePaysafeWebhookEvent, checking the Signature header against the raw request bytes and emitting a normalized UnifiedWebhookEvent. Paysafe sends no event id, so event.id is derived from the event name, resource id, status and status time, the same for every redelivery; pspPaymentId names the returned payment on a bank return and is unset on refund events, which carry refundId. Paysafe counts only a 200 or 202 as received and makes at most three attempts, so reconcile with retrievePayment for anything missed, except bank-debit returns, which no read is documented to reflect: reconcile those against the Merchant Back Office return reports.
  • mapPaysafeError, unifies Paysafe errors into PayFanoutError (business errors like declines or 3406 are never replayed), and PAYSAFE_PSP_NAME.

PSP-native subscriptions (Payment Scheduler)

The adapter declares nativeSubscriptions: { list, retrieve, create, cancel } all true, backed by Paysafe's Payment Scheduler (subscriptionsplans/v1, same hosts and the same Basic API key as the Payments API — the docs' "Back Office" is where that key is retrieved, not a separate credential).

  • Create bills an already-vaulted MULTI_USE token (savePaymentMethod's output; the scheduler rejects single-use Paysafe.js tokens). Subscriptions attach to a plan: pass planId to bill from a plan you manage (the input amount/currency/cadence must match it — mismatches reject instead of silently billing different terms), or omit it and the adapter creates a dedicated open-ended plan from the input inline.
  • Cadence is day/month/year (+ intervalCount 1-365). The scheduler has no weekly frequency and no RRULE input — interval: "week" and any schedule reject with invalid_request rather than approximating.
  • Idempotency rides merchantRefNum ("unique for this accountId"): input.merchantRefNum wins when supplied, idempotencyKey fills it otherwise, and a replayed create recovers the existing subscription by that refNum. A creation retried after a lost answer can leave an orphan inline plan (plans carry no refNum); the subscription itself stays exactly-once, so nothing double-bills.
  • Cancel PATCHes the final CANCELLED status (never the reversible SUSPENDED) and is verified-idempotent: on a rejection the adapter re-reads the subscription and treats CANCELLED/COMPLETED as success.
  • Status mapping: ACTIVE → active, CANCELLED → canceled, SUSPENDED → paused, COMPLETED → completed; anything else on the wire → unknown.
  • pspCustomerId and metadata have no scheduler channel and are withheld; the customer profile derives from the vaulted token.

Notes

  • Reads are retried on timeouts/5xx/429 with backoff (maxNetworkRetries, default 2); writes are never re-sent blindly. Paysafe rejects a repeated merchantRefNum (409, error 5031, under dupCheck) instead of answering with the original, so a write whose answer was lost is looked up by its merchantRefNum and returned when Paysafe has it. A payment, capture or refund is re-sent only after a 429; a payment handle, verification or void also once the lookup shows nothing. Card and Interac completions send dupCheck: false, so another card can follow a decline under the same key. A bank-debit completion sends dupCheck: true, so a second attempt is refused instead of debiting again while no failed attempt shows under the key, and false once one does, so corrected bank details can follow. Once a failed attempt shows, the check is off: two attempts sent together, or one resubmitted before the lookup shows the other's payment or handle, can both be debited. §10 of the setup guide covers these timings, and when a bank-debit key needs replacing. A key reused for a different amount or currency, or a different saved card or verification card, rejects with invalid_request, unless every earlier attempt under a card or Interac completion key failed; while the key's payment, capture or refund may have moved money (it has not failed, been voided, cancelled or expired), or a bank-debit key holds a spent handle whose payment the lookup does not show, the rejection carries outcomeUnknown. An original that cannot be read back rejects with a non-retryable processing_error; retry it later with the same key, or, for a full capture, whose reference does not come from the key, capture in full again later. The default requestTimeoutMs is 60000, the response timeout of Paysafe's own SDKs, and bounds each exchange rather than a whole call.
  • A capture of the whole authorization (no amount, or the authorized amount) settles under a reference derived from the payment, not under its idempotency key: payfanout-capture-<pspPaymentId>, then -a2 to -a10 once a full capture there has moved no money (failed, cancelled or expired). retrievePayment and refundPayment look those references up, and a capture that finds the payment already captured in full there answers with that settlement, whatever its key. After partial captures, a capture of the authorized amount is refused before it is sent: capture the rest with no amount, while some is left. With nothing left and no full capture showing, a capture is refused as nothing left to capture; a full capture made moments ago can trail in Paysafe's lookup, so check retrievePayment first. A partial capture settles under its idempotency key, which may not start with payfanout-capture-, and no read can find it from the payment: its amount counts in amountCaptured, but refundPayment cannot refund it. Every capture's answer carries its settlement on raw.captureSettlement: keep a partial capture's settlement id and refund it in the Paysafe portal, as you would a capture an earlier release made, which settled under the capture's key. Settlement lookups start the day before the payment instead of covering Paysafe's default 30 days, and are sent again over that window only when Paysafe refuses the range; any other lookup failure fails the call instead of reading as no settlement. See captures and refunds in the setup guide.
  • Paysafe refunds neither SEPA nor Bacs direct debits, so refundPayment rejects a payment on either rail with a non-retryable unsupported_operation once it has read the payment, before any refund request.
  • Currencies whose Paysafe exponent may not be PayFanout's are refused, as Paysafe reads amount in the minor units of its currency table and the adapter converts nothing: CLP and BYR, which the table prices with another exponent (CLP 2, where ISO 4217 gives 0), and currencies the table lacks that are not priced in hundredths (ISK, UYI and others). Sessions in them reject with a non-retryable invalid_request before any request. Saved-method charges, native subscription creates and completions reject the same way once their key is looked up, marked outcomeUnknown when Paysafe already holds something under it that an earlier release may have made, or when the lookup fails. Captures, voids and refunds of a payment Paysafe holds in one read the payment, then reject before any settlement, void or refund request: with invalid_request when they state an amount, with unsupported_operation otherwise. Reads of such a payment or subscription, or of a refund Paysafe reports in one, subscription cancels (which read the subscription first) and subscription list pages holding one reject with unsupported_operation, and webhook events Paysafe reports in one carry no amount. The adapter declares these currencies in unsupportedCurrencies, so the router skips Paysafe for a session in one and PaymentService refuses it with unsupported_operation, from the @payfanout/server release that reads the field (see currencies the adapter refuses).
  • Paysafe has no public events API (supportsEventPolling: false), so missed-webhook recovery falls back to retrievePayment per order.
  • Scheduler availability is per merchant account, like every Paysafe product option — the capability flags state what the adapter implements; an account without the scheduler answers with its own rejection.

Documentation

License

MIT