All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[1.0.0] - 2026-06-21
Verified
- The one remaining
paysafe-specific Dialyzer note after the spec fix above —client.ex: the catch-all clause in Paysafe.Client.ensure_map/1 can never match— was confirmed viareq's own published documentation to be a test-double limitation, not a real defect:Req.Response.t()'sbodyfield is officially typed asbinary() | %Req.Response.Async{} | term(), i.e. genuinely unconstrained, because differentreqdecode steps can leave it as a map, raw binary, async stream struct, ornil. No finite-clause stand-in module can express that, so Dialyzer narrows its inferred type to only the shapes the stand-in actually constructs — making the real library's necessary defensive catch-all look dead code only in this constrained local analysis. The catch-all is correct, intentional defensive code against the real dependency and was deliberately left unchanged.
Fixed (Dialyzer audit)
Paysafe.Telemetry.span/6had its 3rd and 4th@specparameter types transposed — declared as(Config.t(), atom(), String.t(), atom(), String.t(), fun)when the actual function head and every call site pass(config, api, method, path, operation, fun), i.e.atom()thenString.t(). Since Elixir doesn't enforce specs at runtime this never caused incorrect behavior, but it broke Dialyzer's contract checking for every call intoTelemetry.span/6— and becausePaysafe.Client.request/6(used by every public API function in the library) routes through it, one transposed spec fanned out into 200+ "has no local return" warnings across nearly the entire codebase. Verified the fix collapses the warning count from 268 to 59 (all 59 remaining are pre-existing Elixir 1.14 standard-library warnings unrelated to this package, confirmed by running the same Dialyzer pass against a clean checkout of Elixir's own stdlib in isolation) when run with a complete PLT against the real Elixir/OTP standard library.
Fixed (critical — core Payments API URL structure)
The entire core Payments API surface (PaymentHandles, Payments,
Settlements, Refunds, Payouts, Verifications, Customers) was
built with /accounts/{account_id}/...-nested URLs, modeled on the legacy
Alternative Payments API convention. Verification against eight+
independently fetched real request/response examples confirmed the modern
Payments API actually uses flat URLs with no account ID anywhere in the
path — accountId is instead an optional field inside the JSON request
body, used only when an API key has multiple accounts configured for the
same payment method/currency combination. This was the single
highest-impact bug in the library, since it affected the most-used part of
the surface. Specific corrections:
PaymentHandles.create/3:POST /accounts/{id}/paymenthandles→POST /paymenthandleswithaccountIdmerged into the body.Payments.create/3,get/3,list/2,cancel/3: all/accounts/{id}/payments...paths → flat/payments...(payments are scoped by the payment handle token, not an account ID).Settlements.create/4: flattened to/payments/{paymentId}/settlements(still nested under the payment, just without the account prefix).Settlements.get/3,cancel/3: changed from/accounts/{id}/payments/{paymentId}/settlements/{settlementId}(3 path params) to the flat/settlements/{settlementId}(1 path param) — function arity changed, dropping the now-unnecessarypayment_idargument.Refunds.create/4: structural correction, not just a path-prefix fix — refunds are nested under/settlements/{settlementId}/refunds, never under/payments/{paymentId}/refunds. The settlement ID equals the payment ID only whensettleWithAuthwastrueon the original payment.Refunds.get/3,cancel/3: changed to the flat/refunds/{refundId}— function arity changed, dropping thepayment_idargument.Payouts.standalone_credit/2,original_credit/2: flattened to/standalonecreditsand/originalcreditswithaccountIdin the body.Verifications.create/2: flattened to/verifications.Customers.create/2,get/2,update/3,delete/2, and all payment-handle sub-resources: flattened to/customers..., withaccountIdin the body forcreate/2.Paysafe.cancel_settlement/3,Paysafe.create_refund/3,Paysafe.cancel_refund/3facade functions: arity changed to match the corrected underlying module signatures.
Added
Customers.get_by_merchant_customer_id/2—GET /customers?merchantCustomerId={id}, previously missing entirely.Customers.create_single_use_customer_token/2—POST /customers/{id}/singleusecustomertokens, a real, previously unimplemented endpoint used to tokenize a customer's entire saved profile (cards, addresses, bank mandates) for 900 seconds, e.g. for one-time CVV re-collection on a saved card.
Fixed (post-release audit against verified API examples — earlier round)
- Applications API: base path corrected from the fabricated
/accountmanagement/v1to the verified/merchant/v1; added the missingsubmit/3(PATCH) andget_terms_and_conditions/3operations. - Customer Identity API: base path corrected from
/paymenthub/v1/accounts/{id}/customeridentityto the verified flat resource/customeridentification/v1/identityprofiles(no account ID in the path);decisionenum corrected from a fabricated:success | :error | :pendingto the verified:success | :error | :fail | :outsort; added thererun/3operation with a documented warning not to rerun:faildecisions. - Bank Account Validation API: base path corrected from
/paymenthub/v1/...to the verified/bankaccountvalidator/v1/accounts/{id}/verifications; the entire request/response shape was rebuilt from a fabricated micro-deposit-style flow to the verified redirect-based open-banking session flow. - Payment Scheduler API: base path corrected from the fabricated
/recurring/v1to the verified/subscriptionsplans/v1.
Removed
Paysafe.NetworkTokenization— this was calling fabricated provision/get/delete endpoints that do not exist. Network tokenization is not a separate API; it is accessed viacard.network_tokenfields onPaysafe.Payments.PaymentHandles.create/3, now documented there instead.Paysafe.AccountUpdater's REST functions — this product has no HTTP/JSON API at all; it is delivered via SFTP + PGP-encrypted batch files or automatic back-office configuration. The module now exists solely as a@moduledocexplaining this and pointing to the real process.
Added
Paysafe.InteracVerificationService— Interac AML Assist identity verification (Canada), verified against real endpoint examples.Paysafe.Types.BankVerification,Paysafe.Types.IdentityProfile,Paysafe.Types.Application— typed structs for the corrected APIs above.Config.bank_account_validator_url/1andConfig.customer_identification_url/1.- Documentation for the Partial Authorization Service (PAS) fields
(
allow_partial_auth,group_id) onPayments.create/3— confirmed to be parameters on the existing payment call, not a separate endpoint. - A "Known limitations" section in the README documenting two products (Merchant Termination Inquiry API, PayFac Sub-merchant API) whose API reference pages render client-side and expose no verifiable endpoint shape through any available documentation source — these were deliberately left unimplemented rather than guessed.
Added (initial release)
- Initial release.
- Payments API: Payment Handles, Payments, Settlements, Refunds, Payouts (standalone credits & original credits), Verifications, and Customer Vault (profiles, multi-use payment handles).
- Payment Scheduler API: Plans and Subscriptions with full lifecycle management (suspend, reactivate, cancel).
- Applications API: Programmatic merchant onboarding, document upload.
- Value Added Services: FX Rates, Customer Identity (KYC), Bank Account Validation, Network Tokenization, Account Updater.
- Webhooks: HMAC-SHA256 signature verification with constant-time comparison, typed event parsing, and topic-based event routing.
- Configurable HTTP client with exponential backoff retry, token-bucket rate
limiting (
ex_rated), and fullTelemetryinstrumentation. - Strongly-typed response structs for every API resource (
Paysafe.Types.*). - Structured, typed error handling via
Paysafe.Errorwith retryability classification. - Config validation via
NimbleOptions, including abase_url_overrideescape hatch for testing and proxying. - Comprehensive test suite (unit tests +
Bypass-based HTTP integration tests) covering every public function.