Skip to main content

CHANGELOG.md

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [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 via `req`'s own published documentation
  to be a test-double limitation, not a real defect: `Req.Response.t()`'s
  `body` field is officially typed as `binary() | %Req.Response.Async{} |
  term()`, i.e. genuinely unconstrained, because different `req` decode
  steps can leave it as a map, raw binary, async stream struct, or `nil`.
  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/6` had its 3rd and 4th `@spec` parameter 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()` then `String.t()`. Since
  Elixir doesn't enforce specs at runtime this never caused incorrect
  behavior, but it broke Dialyzer's contract checking for every call into
  `Telemetry.span/6` — and because `Paysafe.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 /paymenthandles` with `accountId` merged 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-unnecessary `payment_id` argument.
- `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 when `settleWithAuth` was `true` on the original payment.
- `Refunds.get/3`, `cancel/3`: changed to the flat `/refunds/{refundId}` —
  **function arity changed**, dropping the `payment_id` argument.
- `Payouts.standalone_credit/2`, `original_credit/2`: flattened to
  `/standalonecredits` and `/originalcredits` with `accountId` in 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...`, with
  `accountId` in the body for `create/2`.
- `Paysafe.cancel_settlement/3`, `Paysafe.create_refund/3`,
  `Paysafe.cancel_refund/3` facade 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/v1` to the verified `/merchant/v1`; added the missing
  `submit/3` (PATCH) and `get_terms_and_conditions/3` operations.
- **Customer Identity API**: base path corrected from `/paymenthub/v1/accounts/{id}/customeridentity`
  to the verified flat resource `/customeridentification/v1/identityprofiles`
  (no account ID in the path); `decision` enum corrected from a fabricated
  `:success | :error | :pending` to the verified `:success | :error | :fail | :outsort`;
  added the `rerun/3` operation with a documented warning not to rerun `:fail` decisions.
- **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/v1` to 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 via `card.network_token` fields on
  `Paysafe.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
  `@moduledoc` explaining 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/1` and `Config.customer_identification_url/1`.
- Documentation for the Partial Authorization Service (PAS) fields
  (`allow_partial_auth`, `group_id`) on `Payments.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 full `Telemetry` instrumentation.
- Strongly-typed response structs for every API resource (`Paysafe.Types.*`).
- Structured, typed error handling via `Paysafe.Error` with retryability
  classification.
- Config validation via `NimbleOptions`, including a `base_url_override`
  escape hatch for testing and proxying.
- Comprehensive test suite (unit tests + `Bypass`-based HTTP integration
  tests) covering every public function.