# Changelog

This page tracks API-visible changes to the Fynex Payments API: new capabilities, fixed behavior,
and anything that changes what a request or response looks like. Internal work with no effect on
what you can call or what you get back (infrastructure, internal tooling, refactors) is not listed
here. Some entries below note when a change only affects certain accounts or environments — check
those notes if a change doesn't seem to apply to you.

---

## 2026-09

### Breaking

- **`bcPaymentId` removed from the REST payout response**, effective 2026-09-02. Read
  `providerReference` instead — the supported field for this purpose, available since 2026-08-28.
  If your integration reads `bcPaymentId` from a REST payout response, that key is now absent from
  the JSON entirely (not `null`); switch to `providerReference`. This affects **REST only** — the
  GraphQL payout schema still exposes `bcPaymentId` unchanged for now.
- **Expired cards are now rejected before the provider is called**, with a dedicated
  `failureCode` (`card_expired`) and `failureCategory: requires_change` — previously this returned
  the generic provider-error code with `failureCategory: transient` and `retry: safe`, which could
  read as "try again." A new attempt with the same expired card will fail identically; the buyer
  needs a different card.

### Added

- **Self-serve sandbox accounts.** `POST /sandbox/accounts` mints an anonymous, 14-day Demo seller
  account (no email, no real money) for anyone evaluating the API — no manual provisioning
  required. The door is open on the sandbox host (`staging-api.fynex.ai`); it answers `503` on the
  production host.
- **Split-decision records.** `GET /payments-api/v1/payments/{paymentId}/split-decisions` returns a
  decision record for every split evaluation on a payment — what each line contributed, which
  lines didn't fire and why, and which policy adjusted an amount. As with every other payment read
  on this API, `paymentId` in the path is your `externalOrderRef` from `/initialize-payment`, not a
  Fynex-issued id.
- **`retry` and `failureCategory` published alongside `failureCode`.** A failed payment, refund, or
  the `PaymentCompleted` webhook now states whether re-sending the same request can succeed
  (`retry`: `safe`, `fix_first`, or `never`) and the shape of the failure (`failureCategory`:
  `hard`, `transient`, `requires_change`, `integration_error`, `cancelled`, or `unknown`).
  `integration_error` is worth watching for specifically — it means the request itself was wrong,
  not the payment method.
- **Webhook event subscriptions.** `eventTypes` can now be set when registering or updating a
  webhook to receive only the event types you choose; omitting it keeps receiving the full default
  set as before.
- **Marketplace vendor onboarding.** `POST /payments-api/v1/vendors` (plus list/get and a
  verification-link endpoint) lets a marketplace onboard the sellers it pays out to directly,
  including automated screening. A screened vendor can receive cumulative payouts up to a
  threshold before full verification is required; further payouts either flag for follow-up or are
  held for review, depending on your account's configuration, once that threshold is reached. See
  the vendors guide for the full flow. These endpoints are enabled per account — a `404` on
  `POST /payments-api/v1/vendors` means vendor onboarding isn't turned on for you yet.
- **`MarketplaceVendor*` webhook family.** Platforms using vendor onboarding above can now receive
  `MarketplaceVendorActivated`, `...VerificationRequired`, `...Blocked`, `...PayoutAllowanceChanged`
  and related events — opt-in, via `eventTypes`.
- **`Simulator` request header for deterministic failure testing.** On a Demo-mode seller account,
  sending `Simulator: force_failure=<failureCode>` on a payment initiation forces that exact
  `failureCode` onto the resulting payment record — the `initialize-payment` call itself still
  returns `502`, as every authorization-stage failure does, so read the forced code back from
  `GET /payments/{paymentId}` or the `PaymentCompleted` webhook. Silently ignored outside Demo mode
  (a Live payment is never affected); inside Demo mode, an unrecognized or unsupported code returns
  `400`. See the [test cards guide](https://api.fynex.ai/payments-api/v2/docs#tag/test-cards) for the full header contract.

### Fixed

- **AVS refusals (billing address mismatch) now return a dedicated failure code**
  (`address_verification_failed`). Previously misclassified as a generic provider error at the
  point it actually occurs (the `/payments` call, not handle creation), which surfaced to buyers as
  an unhelpful "try again" — leading some to retry with the same wrong address and stack a second
  authorization hold. Now correctly tells the integrator to prompt for a corrected billing address.
- **Insufficient-funds and provider risk-management declines are now correctly classified as hard
  declines** (`retry: never`) instead of a generic, retryable provider error (`retry: safe`).
  Both still report the ordinary `provider_declined` failure code — this API does not yet
  distinguish the specific reason for an issuer/acquirer decline — but integrators are no longer
  told, incorrectly, that resending the same request is safe. Not backfilled: payments that failed
  before this fix keep their original failure code.

---

## 2026-08

### Breaking

- **Cardholder name is now validated on submission**: ASCII letters, spaces, apostrophes, dots and
  hyphens only, up to 80 characters. A name outside this set — including one with an accented
  letter such as é, ñ, or ü — is rejected at request validation with a field-level error, where
  previously it was accepted and passed through. If you collect cardholder names from users whose
  names use such characters, transliterate to this character set before sending.

### Added

- **Billing API launched** — a new, separate public REST surface at `/billing-api/v1`
  (documented alongside this reference), authenticated with the same seller key as the Payments
  API. Covers invoice creation and management (list, detail, PDF, send), subscriptions (create,
  cancel, pause/resume, plan changes), contracts, usage ingestion and snapshots, and credit
  balances. Top-up invoices are *not* duplicated here — they remain on `/payments-api/v1` as
  before.
- **Payouts now snapshot the destination account format at creation time**, recorded on the payout
  going forward, rather than only reflecting the payee's current configuration. Editing the payout
  method after a payout is created does not change what that payout already recorded.
- **GBP payouts can dispatch via Faster Payments** (`uk_local`), in addition to existing rails.
- **Fynex-hosted checkout return page.** A merchant with no return page of their own can supply
  Fynex's hosted URL as their `returnUrl` instead of building and hosting one — a `returnUrl` is
  still required on every checkout.
- **Multibanco payment instructions** exposed for server-to-server alternative-payment-method
  flows.
- **MB WAY in-app notification flow.** In addition to the existing redirect flow (which stays
  available as a fallback, since the in-app push has a four-minute window and can go unanswered),
  buyers can now approve an MB WAY payment via an app notification without leaving the merchant's
  checkout.
- **Merchant fee annex and 3DS verdict fields** now exposed on the GraphQL payment type (backoffice
  and dashboard surfaces). Not currently exposed on the public REST payment record.
- **Payout approval is now configured separately for self-payouts vs. payee payouts.** This is set
  on your account by Fynex, not by API call. A payout awaiting review reports
  `approvalStatus: awaiting_approval` and dispatches nothing until it is resolved — poll or
  subscribe as you would for any other non-terminal payout state.

### Fixed

- **Seller-initiated top-up invoice creation is now enabled in production.**
  `POST /payments-api/v1/topup-invoices` is available as documented.

### Changed

- **Error message bodies are now standardized across the payments API.** Generic Go error text that
  could previously appear in a `5xx` response body was replaced with the platform's standard
  client-safe error messages. Status codes and the conditions that produce an error are unchanged —
  only the `message` text differs. If you matched on error message strings rather than
  `failureCode`, switch to the code.
