PATCH /subscriptions/{id} to CANCELLED — the FINAL status ("cannot be re-activated"); SUSPENDED is the reversible pause and is deliberately not used here. Verified-idempotent: the scheduler does not document patching an already-terminal subscription, so on any rejection the adapter re-fetches and treats CANCELLED/COMPLETED as success — billing that is already stopped never fails a replayed cancel, except in a currency the adapter refuses (below). The PATCH carries no merchantRefNum (the scheduler has none there); replay safety comes from CANCELLED being absorbing plus that re-fetch, which is why the required idempotencyKey has no wire mapping on this call.
The subscription is read before the PATCH, one more request on every
cancel, and a failed read fails the cancel before anything is sent. One
in a currency the adapter refuses is refused there with
unsupported_operation, since the answer could not report its amounts,
even when it is already stopped and its cancel would otherwise succeed.
Voids the remaining authorization. This also works AFTER partial settlements (multi-capture flows) — settled funds stay settled and the returned PaymentInfo reports them (status "succeeded"), derived from the amount the void released; only a payment with no settlements at all comes back "canceled". Partial captures settle under the caller's keys, which no read can rediscover (see capturePayment), so LATER retrievePayment calls lose that split once the void has consumed availableToSettle (documented limitation).
Settles an authorization, in full or in part. Refunds come out of settlements, and Paysafe finds a settlement again only by its merchantRefNum, so the reference decides what refundPayment can reach later.
A full capture, amount omitted (everything that remains) or equal to
the authorized amount, settles under a reference derived from the
payment, never under idempotencyKey: payfanout-capture-<payment id>,
or, once the settlements there have moved no money (failed, cancelled or
expired), the next of -a2 to -a10. Those references are read
first: a settlement there that moved money means the payment is captured
in full, and the call answers with it whatever its key, unless it asks
for the authorized amount and that settlement settled another (the rest,
after partial captures), which is refused as already captured in full.
With no such settlement, a capture of the authorized amount that the
payment read shows partial captures have left short is refused before
it is sent, and one with nothing left to settle is refused as nothing
left to capture. retrievePayment and refundPayment read the same
references.
A partial capture, any other amount, settles under idempotencyKey,
which must be unique across the account and may not start with
payfanout-capture-. No read finds its settlement from the payment: its
amount counts in amountCaptured, but refundPayment cannot refund it.
The answer is the payment as read after the capture, with no settlement
lookup after the write, so an outage then cannot fail a capture that
settled. Its raw is the payment with one more property,
captureSettlement: the settlement this capture made, or the one it
read back or met. Keep a partial capture's id to find that settlement
in the Paysafe portal and refund it there.
Merchant-initiated charge of a MULTI_USE token. storedCredential carries the networks' credential-on-file semantics (INITIAL while the customer is present, SUBSEQUENT for recurring). An unscheduled charge is TOPUP, Paysafe's "unscheduled merchant-initiated request"; ADHOC is its consumer-initiated type, which a merchant-initiated charge is not.
Tokenize-first completion (§4a): create the actual Payment from the client's Payment Handle token.
Reality check: POST /payments strictly rejects
webhook/returnLinks fields (error 5023 "field not recognized") — those
belong on the payment HANDLE. Webhook endpoints are configured in the
Paysafe portal; webhookUrl/returnUrl stay in the session context for
handle-level flows (redirect methods) and future API support.
Creates the PSP-side customer that saved instruments attach to.
Creates a subscription the scheduler bills itself, against an
already-vaulted MULTI_USE token (savePaymentMethod's output — a
single-use Paysafe.js token is rejected by the scheduler). Subscriptions
attach to a PLAN: pass planId to bill from a host-managed plan (the
input amount/currency/cadence must match it — the adapter verifies and
rejects a mismatch rather than silently billing something else), or omit
it and the adapter creates a dedicated open-ended plan
(numberOfCycles 0 = infinite) from the input inline.
Idempotency: merchantRefNum is the scheduler's dedupe field ("unique
for this accountId") and doubles as the lookup key, so input.merchantRefNum
wins when supplied — passing it transfers the idempotency duty to it —
and idempotencyKey fills it otherwise. A replayed create, or one whose
answer was lost, recovers the existing subscription by that refNum
instead of failing; neither POST is re-sent after a timeout or 5xx. The
inline PLAN has no refNum channel, so a creation retried after a lost
answer can leave an orphan plan behind; the subscription itself stays
exactly-once, so no double billing can result.
pspCustomerId and metadata have no scheduler channel and are
withheld: the customer profile derives from the vaulted token, and the
subscription object carries no metadata field.
No PSP call happens here: Paysafe.js tokenizes client-side first. The "session" is a signed, self-contained context (amount, currency, merchant account, webhookUrl, ...) that completePayment later verifies and trusts. Paysafe's per-session webhook registration requirement is honored by carrying webhookUrl in this context into the /payments request.
Deletion needs the handle's internal id; hosts only hold the token — resolved here.
Pages the Payment Scheduler's subscriptions as unified records. The scheduler paginates by offset (limit default 10, max 50); the opaque cursor is the next offset. A full page always yields a nextCursor — the follow-up page comes back empty with none — because the spec does not pin whether meta.numberOfRecords counts the total or the page, and a wrong guess would either drop records or never terminate.
A page holding a subscription in a currency the adapter refuses fails
whole with unsupported_operation: raw.records names each such
subscription, and raw.nextCursor is the cursor the page would have
carried, so later pages stay reachable (a limit of 1 steps past each).
Optionalinput: ListNativeSubscriptionsInputThrows PayFanoutError (code "invalid_request") on unparseable payloads; maps genuinely unknown-but-valid event types to type "unknown" rather than throwing.
Paysafe refunds settle against a settlement, not the payment — resolved here so callers keep one API. The settlement is the first with money left that findSettlements finds: a settle-with-auth payment's, or a full capture's. A partial capture's settlement, under the capture's own key, cannot be found from the payment, so it is not refunded here, and nor is a capture an earlier release settled under its key. A SEPA or Bacs payment is refused once the payment read shows its type, before anything else is sent: Paysafe refunds neither rail.
GET /subscriptions/{id}. Paysafe keys by id alone — savedPaymentMethodToken is not needed.
Only present if getCapabilities().supportsPaymentRetrieval. Push-only PSPs expose no read for a single payment — their hosts follow payment state through webhook deliveries instead.
Polls an async refund to a terminal state — sandbox refunds in particular sit PENDING until the overnight settlement batch runs.
A refund that states no currencyCode is reported as it comes: Paysafe
does not require one (its card refund example carries none), and a
refund names no payment or settlement to read the currency from.
Converts the client's SINGLE_USE handle (from Paysafe.js tokenize) into a permanent MULTI_USE token under the customer. This is the tokenize-first counterpart of Stripe's save-during-checkout.
Stateless session update: verify + merge + re-sign. Paysafe has no PSP object yet (tokenize-first), so the "update" re-issues the signed context with the changes and a fresh TTL. The returned session carries a NEW pspSessionId/clientSecret — the old token keeps its own (original) expiry, so hosts should hand the new one to the client promptly.
"Test connection" probe: one side-effect-free read against the Customer
Vault — look up an all-but-certainly-absent profile. This endpoint is an
object-or-404 point lookup, so authentication is settled BEFORE the resource
is resolved: only 401/403 means bad credentials, while every other status —
a 2xx match, the expected 404 "no such profile", or any business 4xx —
proves the credentials authenticated. Classified so a host UI can tell a
wrong key (auth) from a transient outage (network). It is a single call,
never retried (an auth rejection must not be replayed), and the credentials
never leak into the result.
Zero-amount verification via Paysafe's Verifications API — no charge, nothing stored.
Webhook handling — same adapter, separate concerns. Both async: future PSPs may need remote key retrieval; the contract stays uniform. MUST operate on the RAW request body bytes/string — re-serializing a parsed body breaks signatures (the conformance suite tests this).
The Paysafe server adapter: the Payments API, Customer Vault and Payment Scheduler over REST, tokenize-first.
Paysafe reads amounts in the minor units of its currency table, and the adapter sends PayFanout's unchanged and converts none, so it refuses a currency the table gives another exponent than PayFanout (CLP, BYR), or one the table lacks that is not priced in hundredths (ISK, UYI and others; see "Currencies the adapter refuses" in the setup guide), and declares them in
unsupportedCurrencies, so the router skips Paysafe for a session in one and PaymentService refuses it withunsupported_operation(from the@payfanout/serverrelease that reads the field). createPaymentSession and updatePaymentSession refuse one withinvalid_requestbefore any request. completePayment, chargeSavedPaymentMethod and createNativeSubscription, which earlier releases sent in such currencies, look their key up first, a read that moves no money, and refuse withinvalid_request, marked outcomeUnknown when Paysafe holds something under the key or the lookup fails. capturePayment and refundPayment read the payment first and refuse withinvalid_requestwhen they carry an amount, which is in PayFanout's minor units, and withunsupported_operationwhen they carry none, as cancelPayment does: those send Paysafe's own amounts, or none, but answer with amounts that cannot be reported in PayFanout's minor units. cancelNativeSubscription reads the subscription first and refuses the same way. retrievePayment, retrieveRefund (for a refund Paysafe reports in such a currency), retrieveNativeSubscription and listNativeSubscriptions refuse withunsupported_operation, and webhook events in one carry noamount.