openapi: 3.1.0
info:
  title: Better Payment OneAPI
  version: "3.19.93"
  summary: Payment processing, refunds, payouts, checkout sessions, network tokens, and webhooks.
  description: |
    Better Payment **OneAPI** provides endpoints to create payments, manage their
    lifecycle (authorize → capture → refund / reverse), generate hosted checkout
    sessions, manage card-network tokens, and receive asynchronous status
    notifications via webhooks (postbacks).

    All endpoints accept and return `application/json` unless noted otherwise.
    All requests are authenticated using HTTP Basic with your `api_key` as the
    user and `api_password` as the password.

    See the published documentation at https://docs.betterpayment.de for
    end-to-end integration guides, error codes and transaction status reference.
  contact:
    name: Better Payment Support
    url: https://www.betterpayment.de
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary

servers:
  - url: https://api.betterpayment.de
    description: Production
  - url: https://testapi.betterpayment.de
    description: Test / Sandbox

tags:
  - name: Transactions
    description: Create and manage payment transactions, authorizations, captures, refunds, reversals, payouts, and registrations.
  - name: Checkout
    description: Create hosted checkout sessions where the customer selects a payment method on a Better Payment-hosted page.
  - name: Subscriptions
    description: Manage PayPal subscriptions created via PayPal Plans (cancel and revise).
  - name: Network Tokens
    description: Provision, retrieve and archive card network tokens (Visa Token Service, Mastercard MDES) for card-on-file processing.
  - name: Webhooks
    description: Asynchronous outbound notifications (postbacks) sent from Better Payment to merchant systems.

security:
  - basicAuth: []

paths:
  /rest/payment:
    post:
      tags: [Transactions]
      summary: Create a payment
      operationId: createPayment
      description: |
        Creates a new payment transaction. Authorization and capture happen in a
        single step — funds are captured immediately on success.

        Use `/rest/authorize` instead if you need to separate authorization from
        capture. The accepted parameter blocks (billing address, redirection
        URLs, cardholder data, wallet cryptogram, POS data, recurring) depend on
        the `payment_type`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentRequest"
            examples:
              cardEcom:
                summary: Card e-commerce (hosted page redirect)
                value:
                  payment_type: cardecom
                  order_id: "122"
                  amount: 15.90
                  currency: EUR
                  postback_url: https://merchant.example.com/postback
                  success_url: https://merchant.example.com/success
                  error_url: https://merchant.example.com/error
                  first_name: John
                  last_name: Doe
                  email: john.doe@email.com
                  address: Rosenthalerstr. 8
                  city: Berlin
                  postal_code: "10178"
                  country: DE
              applePayWithCryptogram:
                summary: Apple Pay (Server-to-Server, decrypted token)
                value:
                  payment_type: applepay
                  order_id: APAY-67890
                  amount: 25.90
                  currency: EUR
                  postback_url: https://merchant.example.com/postback
                  success_url: https://merchant.example.com/success
                  error_url: https://merchant.example.com/error
                  card_number: "4761070000000509"
                  card_holder: John Doe
                  card_expiry_month: "12"
                  card_expiry_year: "2026"
                  threeds_wallet_cryptogram: AgAAAAAABk4DWZ4C28yUQAAAAAA=
                  threeds_wallet_cryptogram_eci: "07"
              posContactless:
                summary: POS card-present (SoftPOS contactless)
                value:
                  payment_type: cardpos
                  order_id: POS-12345678
                  amount: 25.50
                  currency: EUR
                  postback_url: https://merchant.example.com/postback
                  pos_data:
                    type: { terminal_entry_mode: contactless }
                    card:
                      number: "4761739001010010"
                      expiry_month: 12
                      expiry_year: 2027
                      track2_data: 4761739001010010D27121200000000000F
                      emv_data: 9F26081234567890ABCDEF9F100706010A03A000009F3602000195050000000000
                    terminal:
                      id: "87654321"
                      type: softpos
                      attended: true
                      capabilities: [contactlessChip, onlinePin, noCvm]
              posPinRetry:
                summary: POS retry after `request_pin` (Single Tap)
                value:
                  payment_type: cardpos
                  order_id: POS-12345678
                  amount: 25.50
                  currency: EUR
                  postback_url: https://merchant.example.com/postback
                  pos_data:
                    type:
                      terminal_entry_mode: contactless
                      sequence: subsequent
                    card:
                      number: "4761739001010010"
                      expiry_month: 12
                      expiry_year: 2027
                      track2_data: 4761739001010010D27121200000000000F
                      emv_data: 9F26081234567890ABCDEF9F100706010A03A000009F3602000195050000000000
                    pin_block:
                      format: ISO-0
                      block: D766AAA13ED5CA50
                      zone_id: pos-gateway
                      key_id: "0001"
                    terminal:
                      id: "87654321"
                      type: softpos
                      attended: true
                      capabilities: [contactlessChip, onlinePin, noCvm]
              recurringRepeat:
                summary: Recurring repeat payment using original_transaction_id
                value:
                  payment_type: cardecom
                  order_id: REC-002
                  amount: 9.99
                  currency: EUR
                  postback_url: https://merchant.example.com/postback
                  recurring: 1
                  original_transaction_id: 5e02903b-0fbf-4266-affd-dc58f2749cd1
      responses:
        "200":
          description: Payment created. The response may include a `client_action` requiring follow-up redirect or POST form on the customer's browser.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
              examples:
                redirect:
                  summary: Redirect required (e.g. card e-commerce, Wero, iDEAL)
                  value:
                    transaction_id: 88593bcc-d819-4347-84d1-ebfec3806a24
                    payment_type: cardecom
                    status_code: 1
                    status: started
                    order_id: "123000"
                    amount: 15.9
                    included_fees: "0.00"
                    error_code: 0
                    client_action: redirect
                    action_data:
                      url: https://hosted-page.example.com/pay/88593bcc
                completed:
                  summary: Completed instantly (Apple Pay with cryptogram, recurring CC)
                  value:
                    transaction_id: a2c4e6f8-1234-5678-abcd-ef0123456789
                    payment_type: applepay
                    status_code: 3
                    status: completed
                    order_id: APAY-67890
                    included_fees: "0.00"
                    error_code: 0
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /rest/authorize:
    post:
      tags: [Transactions]
      summary: Authorize a payment (without capture)
      operationId: authorizePayment
      description: |
        Reserves funds without capturing them. Use the Capture endpoint to
        capture later, or the Reverse endpoint to release. Accepts the same
        request body as `/rest/payment`.

        Both `/rest/payment` and `/rest/authorize` accept `amount=0` for Card,
        Apple Pay and Google Pay payment methods to perform an account status
        check (or to "save" a card for future use when combined with
        `recurring=1`).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentRequest"
      responses:
        "200":
          description: Authorization created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /rest/capture:
    post:
      tags: [Transactions]
      summary: Capture an authorized transaction
      operationId: captureTransaction
      description: |
        Captures funds from a previously authorized transaction. Partial
        captures are supported as long as the cumulative captured amount does
        not exceed the original authorization.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CaptureRequest"
      responses:
        "200":
          description: Capture processed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CaptureResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /rest/reverse:
    post:
      tags: [Transactions]
      summary: Reverse (cancel) an authorized transaction
      operationId: reverseTransaction
      description: |
        Reverses an authorized transaction that has not yet been captured.
        Partial reversals are supported when the configured acquirer allows
        them. After capture, use `/rest/refund` instead.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReversalRequest"
      responses:
        "200":
          description: Reversal processed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReversalResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /rest/refund:
    post:
      tags: [Transactions]
      summary: Refund a captured transaction
      operationId: refundTransaction
      description: |
        Issues a full or partial refund for a captured transaction. Multiple
        refunds are allowed per transaction as long as their combined amount
        does not exceed the original captured amount. Partial refund support
        depends on the payment method.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RefundRequest"
      responses:
        "200":
          description: Refund processed. Status fields reflect the refund object, not the originating transaction.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RefundResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /rest/payout:
    post:
      tags: [Transactions]
      summary: Send an unreferenced payout
      operationId: createPayout
      description: |
        Sends money from the merchant account to a customer account without
        referencing an existing payment (useful for disbursements, rewards,
        unreferenced refunds). Only specific payment methods support payouts.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PayoutRequest"
      responses:
        "200":
          description: Payout created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayoutResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /rest/cancel:
    post:
      tags: [Subscriptions]
      summary: Cancel a PayPal subscription
      operationId: cancelSubscription
      description: |
        Cancels an active PayPal subscription. Pass the `transaction_id` of
        the original subscription setup transaction (the initial `/rest/payment`
        with `recurring=1` and `paypal_plan_id`).

        This endpoint is for PayPal Subscriptions only. To release an uncaptured
        authorization use `/rest/reverse` instead.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CancelRequest"
      responses:
        "200":
          description: Subscription cancelled.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CancelResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /rest/revise:
    post:
      tags: [Subscriptions]
      summary: Revise a PayPal subscription
      operationId: reviseSubscription
      description: |
        Updates an existing PayPal subscription (for example, change plan or
        quantity). Pass the `transaction_id` of the original subscription setup
        transaction.

        Revising a subscription may require the customer to approve the change
        again. When approval is required, the response carries
        `client_action: redirect` and an `action_data.url` pointing to PayPal.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReviseRequest"
      responses:
        "200":
          description: Subscription revision processed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReviseResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /rest/register:
    post:
      tags: [Transactions]
      summary: Register a card for future payments
      operationId: registerCard
      description: |
        Registers card data for future charging without blocking funds. The
        returned `transaction_id` is later used as `original_transaction_id`
        in calls to `/rest/payment`.

        Request and response bodies are identical to `/rest/payment`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentRequest"
      responses:
        "200":
          description: "Registration created. Typically returns `client_action: redirect` so the customer can enter card data on the hosted page."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TransactionResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /rest/create_mandate_reference:
    post:
      tags: [Transactions]
      summary: Create a SEPA mandate reference
      operationId: createMandateReference
      description: |
        Creates a SEPA mandate reference before a direct debit payment is
        submitted. Use with `payment_type=dd` (B2C) or `payment_type=dd_b2b`
        (B2B).

        Alternatively, provide your own mandate reference as `sepa_mandate` in
        `/rest/payment` and skip this endpoint.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MandateReferenceRequest"
            examples:
              b2cRecurring:
                summary: SEPA Direct Debit B2C (recurring mandate)
                value:
                  payment_type: dd
                  recurring: 1
                  first_name: Max
                  last_name: Mustermann
                  account_holder: Max Mustermann
                  iban: DE89370400440532013000
              b2b:
                summary: SEPA Direct Debit B2B
                value:
                  payment_type: dd_b2b
                  account_holder: Muster GmbH
                  iban: DE89370400440532013000
      responses:
        "200":
          description: "Mandate reference created. Returns `status_code: 9` (`registered`). Use `token` as the mandate reference shown to the customer and as `sepa_mandate` in later payment requests. For recurring collections, store `transaction_id` as `original_transaction_id`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MandateReferenceResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /rest/checkouts:
    post:
      tags: [Checkout]
      summary: Create a hosted checkout session
      operationId: createCheckout
      description: |
        Creates a checkout session that returns a hosted payment-page URL.
        Customers can pick from any payment method enabled on the merchant
        account, including Apple Pay, Google Pay and Click to Pay.

        Click to Pay is available only through this hosted checkout and requires a
        full-page redirect to the hosted payment page — it is not offered when the
        checkout is embedded in an `<iframe>`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CheckoutRequest"
      responses:
        "200":
          description: Checkout session created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CheckoutResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /rest/transactions:
    get:
      tags: [Transactions]
      summary: List transactions
      operationId: listTransactions
      description: |
        Lists transactions for the authenticated merchant. Defaults to the 50
        most recent transactions; narrow the list using query parameters.
      parameters:
        - in: query
          name: from
          schema: { type: string, format: date-time }
          description: Earliest transaction date (ISO 8601).
          example: "2016-10-05T17:50:28+02:00"
        - in: query
          name: to
          schema: { type: string, format: date-time }
          description: Latest transaction date (ISO 8601).
          example: "2016-10-26T18:00:00+02:00"
        - in: query
          name: status
          schema: { type: string }
          description: Filter by status code, or comma-separated list of status codes (e.g. `2,3`). See `TransactionStatusCode`.
          example: "2,3"
        - in: query
          name: currency
          schema:
            $ref: "#/components/schemas/CurrencyCode"
          description: Filter by 3-letter ISO 4217 currency code.
        - in: query
          name: order_id
          schema: { type: string }
          description: Filter by merchant order ID.
      responses:
        "200":
          description: Array of transactions.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Transaction"
        "401": { $ref: "#/components/responses/Unauthorized" }

  /rest/transactions/{transaction_id}:
    get:
      tags: [Transactions]
      summary: Get transaction details
      operationId: getTransaction
      parameters:
        - $ref: "#/components/parameters/TransactionIdPath"
      responses:
        "200":
          description: A single transaction.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Transaction"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /rest/transactions/{transaction_id}/log:
    get:
      tags: [Transactions]
      summary: Get transaction event log
      operationId: getTransactionEventLog
      description: |
        Returns the chronological list of lifecycle events for a transaction
        (precheck, authorization, capture, refund, postback, etc.). Some
        attributes are not applicable to every event type — for example,
        postback events store amount as zero.
      parameters:
        - $ref: "#/components/parameters/TransactionIdPath"
      responses:
        "200":
          description: Array of transaction events.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/TransactionEvent"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /rest/transactions/{transaction_id}/refunds:
    get:
      tags: [Transactions]
      summary: Get transaction refunds
      operationId: getTransactionRefunds
      description: |
        Returns the list of refunds for a given transaction. Some attributes
        are only included if they were defined when creating the refund, such
        as `merchant_refund_id` and `merchant_reference`.

        Depending on the payment processor, refunds may include extra
        attributes — for example, Wero refunds may include `reason_code`,
        `remittance_info` or `wallet_activity_reference`.
      parameters:
        - $ref: "#/components/parameters/TransactionIdPath"
      responses:
        "200":
          description: Array of refunds for the transaction.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/TransactionRefund"
              example:
                - created_at: "2026-04-30T11:25:38.982+02:00"
                  refund_id: 2da779fd-fe0a-44c2-8cbc-0a40d5bae89a
                  amount: 2
                  status_code: 1
                  status: successful
                - created_at: "2026-04-30T11:26:02.575+02:00"
                  refund_id: 88c0185f-2703-4757-8f70-948cc58901c4
                  amount: 3.95
                  status_code: 1
                  status: successful
                  merchant_reference: abc123
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v2/network_tokens:
    post:
      tags: [Network Tokens]
      summary: Create a network token
      operationId: createNetworkToken
      description: |
        Provisions a network token for a given card via the configured
        tokenization provider (Visa Token Service or Mastercard MDES). The
        token is returned with status `requested` (newly created) or `active`
        (already exists for this card and enrollment).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NetworkTokenCreateRequest"
      responses:
        "200":
          description: Network token created or already active.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkTokenEnvelope"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "400": { $ref: "#/components/responses/BadRequest" }
    get:
      tags: [Network Tokens]
      summary: List network tokens
      operationId: listNetworkTokens
      parameters:
        - in: query
          name: status
          schema: { $ref: "#/components/schemas/NetworkTokenStatus" }
          description: Filter by token status.
        - in: query
          name: network
          schema: { $ref: "#/components/schemas/NetworkTokenNetwork" }
          description: Filter by card network.
        - in: query
          name: created_at[min]
          schema: { type: string, format: date-time }
          description: Earliest creation timestamp (inclusive).
        - in: query
          name: created_at[max]
          schema: { type: string, format: date-time }
          description: Latest creation timestamp (inclusive).
        - in: query
          name: page
          schema: { type: integer, minimum: 1, default: 1 }
        - in: query
          name: per
          schema: { type: integer, minimum: 1, default: 20 }
      responses:
        "200":
          description: Paginated list of network tokens.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkTokenList"
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v2/network_tokens/{id}:
    parameters:
      - $ref: "#/components/parameters/NetworkTokenIdPath"
    get:
      tags: [Network Tokens]
      summary: Get a network token
      operationId: getNetworkToken
      description: Returns a single network token. The latest state is synced from the tokenization provider on read.
      responses:
        "200":
          description: A network token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NetworkTokenEnvelope"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [Network Tokens]
      summary: Archive a network token
      operationId: archiveNetworkToken
      description: |
        Archives a network token. **Irreversible** — once archived the token
        cannot be used for further transactions. Returns `202 Accepted` with
        an empty body.
      responses:
        "202":
          description: Archive request accepted.
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v2/network_tokens/{id}/data:
    parameters:
      - $ref: "#/components/parameters/NetworkTokenIdPath"
    get:
      tags: [Network Tokens]
      summary: Get network token data
      operationId: getNetworkTokenData
      description: |
        Returns sensitive token data (token PAN, expiry, PAR). This response
        does **not** include a cryptogram — use the cryptogram endpoint for
        that. Treat the returned `number` like a real card PAN.
      responses:
        "200":
          description: Token data.
          content:
            application/json:
              schema:
                type: object
                required: [result, data]
                properties:
                  result: { type: string, example: OK }
                  data:
                    $ref: "#/components/schemas/NetworkTokenData"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v2/network_tokens/{id}/cryptogram:
    parameters:
      - $ref: "#/components/parameters/NetworkTokenIdPath"
    post:
      tags: [Network Tokens]
      summary: Create a cryptogram for a network token
      operationId: createNetworkTokenCryptogram
      description: |
        Generates a DSRP or TAVV cryptogram for the referenced network token.
        Cryptograms should be generated for each initial CIT and MIT charge
        and for all subsequent CIT charges.
      responses:
        "200":
          description: Cryptogram created.
          content:
            application/json:
              schema:
                type: object
                required: [result, data]
                properties:
                  result: { type: string, example: OK }
                  data:
                    $ref: "#/components/schemas/NetworkTokenCryptogram"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }

webhooks:
  paymentPostback:
    post:
      tags: [Webhooks]
      summary: Payment status notification
      operationId: paymentPostback
      description: |
        Sent to the merchant's configured `postback_url` whenever a
        transaction reaches a terminal state (completed, declined, etc.) or
        when its status materially changes.

        The request body is `application/json` and is signed with the merchant
        webhook key — see the `Gateway-Webhook-Signature` header below.

        The merchant endpoint must respond with HTTP 200. Any other status
        (or no response at all) triggers a retry every 10 minutes, for up to
        10 attempts in total.
      parameters:
        - in: header
          name: Gateway-Webhook-Signature
          required: true
          schema: { type: string }
          description: Base64-encoded HMAC-SHA256 of the raw request body, signed with the merchant webhook key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentPostback"
            examples:
              completed:
                summary: Completed payment
                value:
                  transaction_id: d1bf9fdf-7268-406f-9e08-8d5a9540ab97
                  payment_type: wero
                  status_code: 3
                  status: completed
                  order_id: "12345678"
                  message: CAPTURE SETTLED
                  remittance_info: "12345678 - Test Shop"
                  wallet_activity_reference: WARf47ac10b58cc
                  wallet_account_reference: WAC50000aW12
                  payer_location: DE
                  amount: 15.90
                  included_fees: "0.00"
                  external_transaction_id: 0I66TOoXap2bNLZ2m89tm1
                  checksum: 73a93b8a2d5260f592d98e386c4e5784ee551d1c
              declined:
                summary: Declined payment
                value:
                  transaction_id: bfe9cff7-8cd9-4976-9236-21253c1f40c1
                  payment_type: wero
                  status_code: 6
                  status: declined
                  order_id: "123010"
                  message: CAPTURE REJECTED
                  reason_code: AC01
                  wallet_activity_reference: WARf47ac10b58cc
                  wallet_account_reference: WAC50000aW12
                  payer_location: DE
                  amount: 10.00
                  included_fees: "0.00"
                  checksum: d56fe31a1a38e06337672ca781e4cb6f3b83fd11
      responses:
        "200":
          description: Acknowledged. Anything other than 2xx triggers retries.

  refundPostback:
    post:
      tags: [Webhooks]
      summary: Refund status notification
      operationId: refundPostback
      description: |
        Sent to the merchant's configured `postback_url` for refund lifecycle
        events. Same signature, retry, and acknowledgement semantics as the
        payment postback.
      parameters:
        - in: header
          name: Gateway-Webhook-Signature
          required: true
          schema: { type: string }
          description: Base64-encoded HMAC-SHA256 of the raw request body, signed with the merchant webhook key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RefundPostback"
            examples:
              completed:
                summary: Refund completed
                value:
                  transaction_id: 609c412f-53bf-4185-bdb4-7c34225607a6
                  payment_type: wero
                  status_code: 7
                  status: refunded
                  order_id: "123000"
                  message: REFUND SETTLED
                  refund_remittance_info: "12345678 - Test Shop"
                  refund_wallet_activity_reference: RWARb23dc80e71af
                  payer_location: DE
                  amount: 10.0
                  refund_status_code: 1
                  refund_id: 58dd2ec9-c6b4-40ec-92d2-047d2913b209
                  included_fees: "0.00"
                  checksum: a7227a1ee524d9da88515cb162656db21701cc74
              declined:
                summary: Refund declined
                value:
                  transaction_id: 609c412f-53bf-4185-bdb4-7c34225607a6
                  payment_type: wero
                  status_code: 7
                  status: refunded
                  order_id: "123000"
                  message: REFUND REJECTED
                  refund_reason_code: AC04
                  refund_wallet_activity_reference: RWARb23dc80e71af
                  payer_location: DE
                  amount: 10.0
                  refund_status_code: 5
                  refund_id: 58dd2ec9-c6b4-40ec-92d2-047d2913b209
                  included_fees: "0.00"
                  checksum: a7227a1ee524d9da88515cb162656db21701cc74
      responses:
        "200":
          description: Acknowledged.

  networkTokenPostback:
    post:
      tags: [Webhooks]
      summary: Network token status notification
      operationId: networkTokenPostback
      description: |
        Sent when a network token's status changes (e.g. `requested` → `active`,
        `active` → `suspended`). Body is `application/x-www-form-urlencoded`.

        The destination URL is configured at merchant level (default) or
        per-token via `postback_url` on token creation. If neither is set, no
        postback is sent.

        Failed deliveries are retried up to **10 times**. Each attempt has a
        5-second timeout.
      parameters:
        - in: header
          name: Gateway-Webhook-Signature
          required: true
          schema: { type: string }
          description: Base64-encoded HMAC-SHA256 of the raw request body, signed with the merchant `incoming_key`.
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/NetworkTokenPostback"
            example:
              network_token_id: f47ac10b-58cc-4372-a567-0e02b2c3d479
              merchant_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
              status: active
              network: visa
              provider: vts
              created_at: "2026-03-31T10:15:22Z"
              updated_at: "2026-03-31T14:31:46Z"
              checksum: f8b7c2d1a4e2d8...
      responses:
        "200":
          description: Acknowledged.

components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
      description: |
        HTTP Basic authentication. Use your `api_key` as the username and your
        `api_password` as the password. Both are issued by Better Payment.

  parameters:
    TransactionIdPath:
      in: path
      name: transaction_id
      required: true
      schema: { type: string, format: uuid }
      description: ID of the transaction.
      example: 07c8b0a4-186f-4b56-a929-17d13b29c695
    NetworkTokenIdPath:
      in: path
      name: id
      required: true
      schema: { type: string, format: uuid }
      description: UUID of the network token.

  responses:
    Unauthorized:
      description: Authentication failed (invalid `api_key` or `api_password`).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
          example:
            error_code: 144
            message: Unauthorized.
    BadRequest:
      description: Validation error or business rule violation. The `error_code` field identifies the specific cause; see the Errors reference in the docs.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
          example:
            error_code: 125
            message: Invalid or missing return URLs.
    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
          example:
            error_code: 102
            message: Transaction not found.

  schemas:
    # ---------------------------------------------------------------- Enums
    PaymentType:
      type: string
      description: Identifier of the payment method used in the request.
      enum:
        - cardecom
        - cardpos
        - applepay
        - googlepay
        - clicktopay
        - ideal
        - pbb
        - paypal
        - dd
        - dd_b2b
        - wero
      x-enum-descriptions:
        cardecom: Card e-commerce
        cardpos: Card present (POS terminal)
        applepay: Apple Pay
        googlepay: Google Pay
        clicktopay: "Click to Pay (Secure Remote Commerce). Available only through the hosted checkout (`/rest/checkouts`), not as a direct `/rest/payment` integration."
        ideal: iDEAL
        pbb: Pay by Bank (Request to Pay)
        paypal: PayPal
        dd: SEPA Direct Debit B2C (consumer scheme)
        dd_b2b: SEPA Direct Debit B2B (business scheme)
        wero: Wero

    CurrencyCode:
      type: string
      description: 3-letter ISO 4217 currency code.
      pattern: "^[A-Z]{3}$"
      example: EUR

    CountryCode:
      type: string
      description: 2-letter ISO 3166-1 alpha-2 country code.
      pattern: "^[A-Z]{2}$"
      example: DE

    TransactionStatus:
      type: string
      description: Textual transaction status. See the Transaction Status reference in the docs for the canonical list.
      enum:
        - started
        - pending
        - completed
        - error
        - canceled
        - declined
        - refunded
        - authorized
        - registered
        - debt_collection
        - debt_paid
        - reversed
        - chargeback
        - factoring
        - debt_declined
        - factoring_declined

    TransactionStatusCode:
      type: integer
      description: |
        Numeric transaction status code. Mapping:
        1 started, 2 pending, 3 completed, 4 error, 5 canceled, 6 declined,
        7 refunded, 8 authorized, 9 registered, 10 debt_collection,
        11 debt_paid, 12 reversed, 13 chargeback, 14 factoring,
        15 debt_declined, 16 factoring_declined.
      enum: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

    ClientAction:
      type: string
      description: Required client-side action after a payment request.
      enum:
        - redirect
        - postForm
      x-enum-descriptions:
        redirect: Redirect the customer to `action_data.url`.
        postForm: Build and submit a POST form to `action_data.url` with the key/value pairs in `action_data.fields`.

    ActionData:
      type: object
      description: Companion to `client_action`. Carries the redirect target, post-form fields, or POS terminal hints depending on the payment method.
      properties:
        url:
          type: string
          format: uri
        fields:
          type: object
          description: Key/value pairs to submit when `client_action` is `postForm`.
          additionalProperties: { type: string }
        terminal_action:
          type: string
          enum: [request_pin, switch_interface]
          description: |
            For POS responses — terminal-side action required before the transaction can complete.
            - `request_pin`: capture cardholder PIN, encrypt it, and retry with `pos_data.pin_block` and `pos_data.type.sequence: "subsequent"`.
            - `switch_interface`: prompt the cardholder to insert the card and retry with `pos_data.type.terminal_entry_mode: "chip"`.
        new_emv_data:
          type: string
          description: |
            For POS chip transactions — issuer-supplied EMV scripts that must be passed back to the
            integrated circuit card (ICC) without modification. Hex-encoded.
          example: 9F26081234567890ABCDEF

    # ------------------------------------------------------ Shared blocks
    BillingAddress:
      type: object
      description: Billing address fields. Required for most payment methods.
      properties:
        address:
          type: string
          maxLength: 255
          description: Street address.
        address2:
          type: string
          maxLength: 255
          description: Second address line.
        city: { type: string, maxLength: 255 }
        postal_code: { type: string, maxLength: 255 }
        state:
          type: string
          maxLength: 255
          description: County, state, or region.
        country: { $ref: "#/components/schemas/CountryCode" }
        first_name: { type: string, maxLength: 255 }
        last_name: { type: string, maxLength: 255 }
        email: { type: string, format: email, maxLength: 255 }
        phone: { type: string, maxLength: 255 }

    RedirectionUrls:
      type: object
      description: Required for hosted-page or redirect-based payment methods.
      properties:
        success_url:
          type: string
          format: uri
          description: Redirect target after a successful transaction. `order_id` and `transaction_id` are appended as query parameters.
        error_url:
          type: string
          format: uri
          description: Redirect target after a failed transaction.
        wero_checkout_url:
          type: string
          format: uri
          description: Where the user is coming from before making a Wero payment. Required only for Wero payments.

    DirectDebitData:
      type: object
      description: |
        SEPA Direct Debit bank account and mandate fields. Required when
        `payment_type=dd` (B2C) or `payment_type=dd_b2b` (B2B). See the SEPA
        Direct Debit payment-method guides for the full flow.
      properties:
        account_holder:
          type: string
          maxLength: 255
          description: Bank account holder's name.
        iban:
          type: string
          maxLength: 31
          description: IBAN of the bank account.
        bic:
          type: string
          maxLength: 11
          description: BIC of the bank account. Optional for SEPA IBANs.
        sepa_mandate:
          type: string
          maxLength: 35
          description: Mandate reference number. When omitted, a mandate reference is generated automatically.

    MandateReferenceRequest:
      type: object
      description: |
        Request body for `/rest/create_mandate_reference`. Creates a SEPA
        mandate reference before a direct debit payment is submitted.
      required: [payment_type]
      allOf:
        - type: object
          properties:
            payment_type:
              type: string
              enum: [dd, dd_b2b]
              description: Use `dd` for SEPA Direct Debit B2C or `dd_b2b` for B2B.
            recurring:
              type: integer
              enum: [0, 1]
              description: Set to `1` for a recurring mandate.
        - $ref: "#/components/schemas/BillingAddress"
        - type: object
          properties:
            account_holder:
              type: string
              maxLength: 255
              description: Bank account holder's name.
            iban:
              type: string
              maxLength: 31
              description: IBAN of the bank account.
            bic:
              type: string
              maxLength: 11
              description: BIC of the bank account.

    MandateReferenceResponse:
      type: object
      description: Response body for `/rest/create_mandate_reference`.
      properties:
        transaction_id:
          type: string
          format: uuid
          description: ID of the created transaction. For recurring SEPA payments, use as `original_transaction_id`.
        token:
          type: string
          description: Mandate reference token. Display to the customer and use as `sepa_mandate` in payment requests.
        status_code: { $ref: "#/components/schemas/TransactionStatusCode" }
        status: { $ref: "#/components/schemas/TransactionStatus" }
        order_id: { type: string }
        error_code: { type: integer }
        error_message: { type: string }

    CardholderData:
      type: object
      description: |
        Card data and 3DS / processing controls. Used for Server-to-Server card
        processing and for unmanaged Apple Pay / Google Pay flows. Server-to-
        Server card processing requires PCI DSS compliance.
      properties:
        card_number: { type: string, description: Full PAN. }
        card_holder: { type: string }
        card_cvv: { type: string, description: 3-digit CVV. }
        card_expiry_year: { type: string, description: 4-digit year. }
        card_expiry_month: { type: string, description: 1–12. }
        card_entry:
          type: string
          enum: [e-commerce, manual, card-on-file, token, token-on-file]
          description: Classification of how the card data was entered.
        charge_order:
          type: string
          enum: [checkout, unspecified, mail, phone, one-click, recurring, ucof, installment, standing-order]
        clearing_mode:
          type: string
          enum: [auto, manual]
          description: Controls clearing. Defaults to `manual` for authorizations and `auto` for payments.
        clear_after:
          type: string
          format: date-time
          description: ISO 8601 cut-off after which the transaction is cleared automatically.
        sca_exemption_request:
          type: string
          enum: [tra, low-value, scp]
          description: Strong Customer Authentication exemption request.
        threeds_authentication_value:
          type: string
          description: Authentication value from the merchant's own 3DS MPI. Must be sent together with `threeds_directory_server_trans_id`.
        threeds_directory_server_trans_id:
          type: string
          description: Directory Server Transaction ID from the merchant's own 3DS MPI.
        threeds_wallet_cryptogram:
          type: string
          description: Cryptogram from a decrypted Apple Pay or Google Pay token.
        threeds_wallet_cryptogram_eci:
          type: string
          description: ECI indicator from the decrypted wallet token.

    RecurringFields:
      type: object
      description: Mark a payment as recurring and (for repeats) link it to the original transaction.
      properties:
        recurring:
          type: integer
          enum: [0, 1]
          description: Set to 1 to flag the payment as recurring.
        original_transaction_id:
          type: string
          format: uuid
          description: Required for repeat recurring payments — the `transaction_id` of the initial recurring transaction or registration.

    PayPalSubscriptionFields:
      type: object
      description: |
        PayPal subscription fields. Sent with `/rest/payment` when
        `payment_type=paypal` and `recurring=1` to create a PayPal Subscription.
        Set `amount=0`; future billing amounts come from the PayPal Plan. PayPal
        does not support merchant-initiated recurring via `original_transaction_id`.
      properties:
        paypal_plan_id:
          type: string
          description: ID of the PayPal Plan created on PayPal (outside this API).
        paypal_subscription_start_time:
          type: string
          format: date-time
          description: Subscription start time in RFC 3339 format. When omitted, the subscription starts immediately.
        paypal_subscription_quantity:
          type: integer
          description: Quantity of items within the plan. Only when the plan supports quantities.

    PosData:
      type: object
      description: |
        POS / card-present data. Required when `payment_type=cardpos`. See the Card POS
        payment-method guide for end-to-end flow examples.
      required: [type, card, terminal]
      properties:
        type:
          type: object
          required: [terminal_entry_mode]
          properties:
            terminal_entry_mode:
              type: string
              enum: [chip, contactless, magstripe, magstripe-fallback, manual-entry]
              description: How the card was read at the terminal.
            sequence:
              type: string
              enum: [subsequent]
              description: |
                Only valid value is `subsequent`. Send this on the retry call after the previous
                Payment response carried `action_data.terminal_action: "request_pin"` (Mastercard
                Single Tap online-PIN flow).
            clearing_mode:
              type: string
              enum: [auto, manual]
              description: |
                Defaults to `auto` for payments and `manual` for authorize. Use `manual` to support
                post-authorization gratuity (capture with a different final amount).
        card:
          type: object
          required: [number, expiry_month, expiry_year]
          description: |
            Cardholder data. For chip / contactless / magstripe transactions the PAN must still be
            sent here in addition to the raw `track2_data` / `emv_data` bytes.
          properties:
            number:
              type: string
              description: Primary Account Number (PAN).
            expiry_month:
              type: integer
              minimum: 1
              maximum: 12
            expiry_year:
              type: integer
              minimum: 2000
            track2_data:
              type: string
              description: |
                Track2 data from the card. Required when `terminal_entry_mode` is `chip`,
                `contactless`, `magstripe`, or `magstripe-fallback`. Omitted for `manual-entry`.
            emv_data:
              type: string
              description: |
                EMV data from the chip; submit unaltered as received from the terminal.
                Required for chip transactions.
            card_sequence_number:
              type: string
              pattern: "^[0-9]{1,3}$"
              description: Card sequence number if EMV tag 5F34 is personalized on the chip.
        terminal:
          type: object
          required: [id, type, attended, capabilities]
          properties:
            id:
              type: string
              pattern: "^[a-zA-Z0-9]{1,8}$"
              description: Terminal identifier, alphanumeric, 1–8 chars. Issued by Better Payment onboarding.
              example: "87654321"
            type:
              type: string
              enum: [pos, softpos]
              description: |
                `pos` — physical attended/unattended terminal. `softpos` — software-only
                terminal running on a mobile device; `attended` must be `true`.
            attended:
              type: boolean
              description: Whether the terminal is operated by staff. For `softpos`, must be `true`.
            capabilities:
              type: array
              items:
                type: string
                enum:
                  - contactChip
                  - contactlessChip
                  - contactMagStripe
                  - contactlessMagStripe
                  - offlineEncipheredPin
                  - offlinePlaintextPin
                  - onlinePin
                  - signature
                  - keyEntry
                  - noCvm
                  - oneTap
              description: |
                Entry / verification modes the terminal supports. For `type: softpos` only the
                subset `[contactlessChip, onlinePin, signature, noCvm, oneTap]` is permitted.
                If `capabilities` includes `keyEntry` or `signature`, `attended` must be `true`.
        pin_block:
          type: object
          description: |
            Required only when online PIN verification is needed (typically Mastercard Single
            Tap above the contactless CVM limit). When present, all four sub-fields are required.
          required: [format, block, zone_id, key_id]
          properties:
            format:
              type: string
              enum: [ISO-0, ISO-1, ISO-4]
              description: PIN block encoding format. `ISO-0` / `ISO-1` use a 16-hex block; `ISO-4` uses a 32-hex block.
            block:
              type: string
              description: Encrypted PIN block received from the terminal (hex).
            zone_id:
              type: string
              description: Encryption zone identifier, assigned to your terminal during onboarding.
            key_id:
              type: string
              description: Encryption key identifier within the zone, assigned during onboarding.
        allow_partial_approval:
          type: boolean
          default: false
          description: |
            Indicates the terminal supports partial approvals (e.g. for prepaid cards with
            insufficient balance). Visa and Mastercard only. Cannot be combined with cashback.

    # ----------------------------------------------------- Payment request
    PaymentRequest:
      type: object
      description: |
        Unified request body for `/rest/payment`, `/rest/authorize`, and
        `/rest/register`. The `payment_type` selects which optional blocks
        apply — billing address, redirection URLs, cardholder data, direct
        debit, wallet cryptogram, POS data, recurring.
      required:
        - payment_type
        - order_id
        - amount
        - currency
        - postback_url
      allOf:
        - type: object
          properties:
            payment_type: { $ref: "#/components/schemas/PaymentType" }
            order_id:
              type: string
              maxLength: 255
              description: Alphanumeric merchant order identifier.
            invoice_id:
              type: string
              maxLength: 20
              description: Invoice number (factoring / debt collection).
            invoice_date:
              type: string
              format: date
              description: Invoice date (YYYY-MM-DD).
            customer_id:
              type: string
              maxLength: 40
            merchant_reference:
              type: string
              maxLength: 255
              description: Free-form reference. Effective string length limit varies by payment method (255 max).
            amount:
              type: number
              format: float
              description: Total amount, including shipping and VAT.
              minimum: 0
            shipping_costs:
              type: number
              format: float
            VAT:
              type: number
              format: float
            currency: { $ref: "#/components/schemas/CurrencyCode" }
            postback_url:
              type: string
              format: uri
              description: HTTPS URL where transaction status postbacks will be delivered.
            locale:
              type: string
              description: Language code for hosted-page payment forms (ISO 639-1).
            additional_transaction_data:
              type: string
              description: Free-form metadata returned in postbacks and on transaction queries.
        - $ref: "#/components/schemas/BillingAddress"
        - $ref: "#/components/schemas/RedirectionUrls"
        - $ref: "#/components/schemas/CardholderData"
        - $ref: "#/components/schemas/DirectDebitData"
        - $ref: "#/components/schemas/RecurringFields"
        - $ref: "#/components/schemas/PayPalSubscriptionFields"
        - type: object
          properties:
            pos_data:
              $ref: "#/components/schemas/PosData"

    # ----------------------------------------------------- Capture/Reverse
    CaptureRequest:
      type: object
      required: [transaction_id]
      properties:
        transaction_id:
          type: string
          format: uuid
        amount:
          type: number
          format: float
          description: When omitted, the full authorized amount is captured. Cannot exceed it.
        vat:
          type: number
          format: float

    CaptureResponse:
      type: object
      properties:
        transaction_id: { type: string, format: uuid }
        status_code: { $ref: "#/components/schemas/TransactionStatusCode" }
        status: { $ref: "#/components/schemas/TransactionStatus" }
        order_id: { type: string }
        message: { type: string }
        card_last_four: { type: string }
        card_expiry_year: { type: integer }
        card_expiry_month: { type: integer }
        card_brand: { type: string }
        error_code: { type: integer }

    ReversalRequest:
      type: object
      required: [transaction_id]
      properties:
        transaction_id: { type: string, format: uuid }
        amount:
          type: number
          format: float
          description: When omitted, reverses the full authorization. Specify a smaller amount for a partial reversal (when supported by the acquirer).

    ReversalResponse:
      type: object
      properties:
        transaction_id: { type: string, format: uuid }
        status_code: { $ref: "#/components/schemas/TransactionStatusCode" }
        status: { $ref: "#/components/schemas/TransactionStatus" }
        order_id: { type: string }
        message: { type: string }
        error_code: { type: integer }

    # ------------------------------------------------------------- Refund
    RefundRequest:
      type: object
      required: [transaction_id]
      properties:
        transaction_id: { type: string, format: uuid }
        merchant_reference:
          type: string
          maxLength: 255
        amount:
          type: number
          format: float
          description: When omitted, refunds the full original amount. Combined refunds across multiple calls cannot exceed the captured amount.
        execution_date:
          type: string
          format: date
        comment:
          type: string
          maxLength: 255
          description: Internal-only refund note.

    RefundResponse:
      type: object
      description: Response is attributed to the refund object — `status_code`/`status` reflect the refund, not the original transaction.
      properties:
        transaction_id: { type: string, format: uuid }
        refund_id: { type: string, format: uuid }
        status_code:
          type: integer
          description: Refund status code. 0 STARTED, 1 SUCCESS, 2 ERROR, 3 LOCAL, 4 REVERSED, 5 DECLINED.
          enum: [0, 1, 2, 3, 4, 5]
        status:
          type: string
          description: |
            Textual refund status. Known values include `started`, `successful`,
            `error`, `reversed`, `declined`. Always interpret using
            `status_code` for canonical mapping.
        error_code: { type: integer }

    # ------------------------------------------------------------- Payout
    PayoutRequest:
      type: object
      required: [payment_type, order_id, amount, currency, merchant_reference, postback_url]
      properties:
        payment_type: { $ref: "#/components/schemas/PaymentType" }
        order_id: { type: string, maxLength: 255 }
        amount: { type: number, format: float }
        currency: { $ref: "#/components/schemas/CurrencyCode" }
        merchant_reference: { type: string, maxLength: 255 }
        postback_url: { type: string, format: uri }
        first_name: { type: string, maxLength: 255 }
        last_name: { type: string, maxLength: 255 }
        address: { type: string, maxLength: 255 }
        city: { type: string, maxLength: 255 }
        country: { $ref: "#/components/schemas/CountryCode" }
        postal_code: { type: string, maxLength: 255 }
        email: { type: string, format: email, maxLength: 255 }
        payout_email:
          type: string
          format: email
          maxLength: 255
          description: Recipient PayPal account email. Required for PayPal (`payment_type=paypal`) payouts.
        account_holder:
          type: string
          maxLength: 255
          description: Recipient bank account holder's name. Required for SEPA Direct Debit (`payment_type=dd` / `dd_b2b`) payouts.
        iban:
          type: string
          maxLength: 31
          description: Recipient IBAN. Required for SEPA Direct Debit payouts.
        bic:
          type: string
          maxLength: 11
          description: Recipient BIC. Optional for SEPA IBANs.
        pos_data:
          $ref: "#/components/schemas/PosData"

    PayoutResponse:
      type: object
      properties:
        transaction_id: { type: string, format: uuid }
        status_code: { $ref: "#/components/schemas/TransactionStatusCode" }
        status: { $ref: "#/components/schemas/TransactionStatus" }
        order_id: { type: string }
        error_code: { type: integer }

    # ----------------------------------------------- PayPal subscriptions
    CancelRequest:
      type: object
      required: [transaction_id]
      properties:
        transaction_id:
          type: string
          format: uuid
          description: Transaction ID of the original subscription setup transaction.
        reason:
          type: string
          description: Reason for cancellation. Defaults to `NO REASON` when omitted.

    CancelResponse:
      type: object
      properties:
        transaction_id: { type: string, format: uuid }
        order_id: { type: string }
        status_code: { $ref: "#/components/schemas/TransactionStatusCode" }
        status: { $ref: "#/components/schemas/TransactionStatus" }
        paypal_subscription_id: { type: string }
        paypal_plan_id: { type: string }
        error_code: { type: integer }

    ReviseRequest:
      type: object
      required: [transaction_id, paypal_plan_id]
      properties:
        transaction_id:
          type: string
          format: uuid
          description: Transaction ID of the original subscription setup transaction.
        paypal_plan_id:
          type: string
          description: PayPal Plan ID to apply to the subscription.
        paypal_subscription_quantity:
          type: integer
          description: Subscription quantity. Only when the plan supports quantities.
        effective_time:
          type: string
          format: date-time
          description: When changes take effect (RFC 3339). Defaults to the current time.
        shipping_costs:
          type: number
          format: float
          description: Shipping costs if the order includes shipping.
        shipping_address: { type: string }
        shipping_address2: { type: string }
        shipping_company: { type: string }
        shipping_city: { type: string }
        shipping_postal_code: { type: string }
        shipping_state: { type: string }
        shipping_country: { $ref: "#/components/schemas/CountryCode" }
        shipping_first_name: { type: string }
        shipping_last_name: { type: string }

    ReviseResponse:
      type: object
      properties:
        transaction_id: { type: string, format: uuid }
        order_id: { type: string }
        error_code: { type: integer }
        status_code: { $ref: "#/components/schemas/TransactionStatusCode" }
        status: { $ref: "#/components/schemas/TransactionStatus" }
        client_action: { $ref: "#/components/schemas/ClientAction" }
        action_data: { $ref: "#/components/schemas/ActionData" }
        paypal_subscription_id: { type: string }
        paypal_plan_id: { type: string }

    # --------------------------------------------------- Transaction (read)
    TransactionResponse:
      type: object
      description: Response body for `/rest/payment`, `/rest/authorize`, and `/rest/register`.
      properties:
        transaction_id: { type: string, format: uuid }
        payment_type: { $ref: "#/components/schemas/PaymentType" }
        status_code: { $ref: "#/components/schemas/TransactionStatusCode" }
        status: { $ref: "#/components/schemas/TransactionStatus" }
        order_id: { type: string }
        message: { type: [string, "null"] }
        additional_transaction_data: { type: [string, "null"] }
        amount: { type: number, format: float }
        included_fees: { type: string }
        error_code: { type: integer }
        client_action: { $ref: "#/components/schemas/ClientAction" }
        action_data: { $ref: "#/components/schemas/ActionData" }
        # POS-specific extras returned in some responses
        card_last_four: { type: string }
        card_expiry_year: { type: integer }
        card_expiry_month: { type: integer }
        card_brand: { type: string }
        card_type: { type: string }
        card_country: { type: string }
        # SEPA Direct Debit extras (payment_type dd / dd_b2b)
        mandate_id:
          type: string
          description: Mandate reference used for the SEPA collection.
        iban:
          type: string
          description: IBAN of the bank account. Returned for SEPA Direct Debit.
        bic:
          type: string
          description: BIC of the bank account. Returned for SEPA Direct Debit.
        recurring_mandate:
          type: integer
          enum: [0, 1]
          description: "`1` if the SEPA mandate is recurring, `0` otherwise."
        execution_date:
          type: string
          description: Date the SEPA collection is scheduled for execution.

    Transaction:
      type: object
      description: Persisted transaction representation returned by the listing and details endpoints.
      properties:
        transaction_id: { type: string, format: uuid }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        status_code: { $ref: "#/components/schemas/TransactionStatusCode" }
        status: { $ref: "#/components/schemas/TransactionStatus" }
        amount: { type: number, format: float }
        currency: { $ref: "#/components/schemas/CurrencyCode" }
        order_id: { type: [string, "null"] }
        recurring:
          type: integer
          enum: [0, 1]
        sepa_mandate: { type: [string, "null"] }
        parent_id: { type: [string, "null"] }
        payment_method: { $ref: "#/components/schemas/PaymentType" }
        message: { type: [string, "null"] }
        sepa_msg_id: { type: [string, "null"] }
        captured_amount:
          type: [number, "null"]
          format: float
        additional_transaction_data: { type: [string, "null"] }

    TransactionRefund:
      type: object
      description: A single refund belonging to a transaction.
      properties:
        created_at: { type: string, format: date-time }
        refund_id: { type: string, format: uuid }
        amount: { type: number, format: float }
        status_code:
          type: integer
          description: "Refund status code: 0 Started, 1 Success, 2 Error."
          enum: [0, 1, 2]
        status: { type: string }
        merchant_reference:
          type: string
          description: Merchant reference, if provided when creating the refund.

    TransactionEvent:
      type: object
      properties:
        created_at: { type: string, format: date-time }
        type:
          type: string
          enum: [precheck, authorization, reauthorization, capture, refund, reversal, postback]
        status: { type: integer }
        amount: { type: number, format: float }
        execution_date:
          type: [string, "null"]
          format: date
        message: { type: [string, "null"] }

    # ----------------------------------------------------------- Checkout
    PrefillCustomerInfo:
      type: object
      properties:
        email: { type: string, format: email }
        phone: { type: string }
        company: { type: string }
        first_name: { type: string }
        last_name: { type: string }
        address: { type: string }
        address2: { type: string }
        city: { type: string }
        state: { type: string }
        postal_code: { type: string }
        country: { $ref: "#/components/schemas/CountryCode" }
        iban: { type: string, description: Customer IBAN for bank account prefill. }
        bic: { type: string, description: Customer BIC for bank account prefill. }
        account_holder: { type: string, description: Bank account holder name. }
        sepa_mandate: { type: string, description: Mandate reference for bank-based payments. }

    CheckoutRequest:
      type: object
      required:
        - merchant_name
        - order_id
        - currency
        - amount
        - total_amount
        - success_url
        - error_url
        - postback_url
      properties:
        merchant_name: { type: string, description: Merchant name displayed on the hosted checkout page. }
        order_id: { type: string }
        reference: { type: string }
        description: { type: string }
        currency: { $ref: "#/components/schemas/CurrencyCode" }
        amount: { type: number, format: float }
        amount_taxes: { type: number, format: float }
        amount_shipping: { type: number, format: float }
        amount_fees: { type: number, format: float, description: Fee amount included in the total. }
        total_amount:
          type: number
          format: float
          description: Total including taxes and shipping.
        recurring:
          type: integer
          enum: [0, 1]
          description: |
            Enable recurring context for the checkout. With `recurring_optional: 0`,
            only recurring-capable payment methods are shown and every payment is
            created as recurring.
        recurring_optional:
          type: integer
          enum: [0, 1]
          description: |
            Let the payer choose whether to save payment details for future
            recurring charges. Requires `recurring: 1`. Shows a recurring badge
            and opt-in checkbox on recurring-capable payment methods.
        copyright_text: { type: string, description: Copyright text displayed on the checkout page footer. }
        copyright_link: { type: string, format: uri, description: URL linked from the copyright text. }
        imprint_url: { type: string, format: uri, description: URL to the merchant imprint page. }
        data_privacy_url: { type: string, format: uri, description: URL to the merchant data privacy policy. }
        terms_and_conditions_url: { type: string, format: uri, description: URL to the merchant terms and conditions. }
        collect_dob: { type: integer, enum: [0, 1] }
        collect_phone: { type: integer, enum: [0, 1] }
        collect_gender: { type: integer, enum: [0, 1] }
        collect_shipping_address: { type: integer, enum: [0, 1] }
        collect_billing_address: { type: integer, enum: [0, 1] }
        display_transaction_details: { type: integer, enum: [0, 1] }
        display_manual_payment_instruction_to_customer:
          type: integer
          enum: [0, 1]
        payment_instruction_merchant_account_holder: { type: string }
        payment_instruction_merchant_iban: { type: string }
        payment_instruction_merchant_bic: { type: string }
        success_url: { type: string, format: uri }
        error_url: { type: string, format: uri }
        postback_url: { type: string, format: uri }
        prefill_customer_info:
          $ref: "#/components/schemas/PrefillCustomerInfo"

    CheckoutResponse:
      type: object
      properties:
        checkout_id: { type: string, format: uuid }
        error_code: { type: integer }
        status:
          type: string
          enum: [open, completed, expired]
        client_action: { $ref: "#/components/schemas/ClientAction" }
        action_data: { $ref: "#/components/schemas/ActionData" }

    # -------------------------------------------------- Network tokens
    NetworkTokenStatus:
      type: string
      enum: [requested, active, suspended, failed, archive_pending, archived]

    NetworkTokenNetwork:
      type: string
      enum: [visa, mastercard]

    NetworkTokenCreateRequest:
      type: object
      required: [card_number, expiry_month, expiry_year]
      properties:
        card_number:
          type: string
          description: Full PAN (12–19 digits).
          pattern: "^[0-9]{12,19}$"
        expiry_month:
          type: integer
          minimum: 1
          maximum: 12
        expiry_year:
          type: integer
          description: 4-digit year.
        card_cvv: { type: string, description: 3-digit CVV. }
        card_holder:
          type: string
          maxLength: 120
        email: { type: string, format: email }
        phone: { type: string }
        billing_country: { $ref: "#/components/schemas/CountryCode" }
        billing_city: { type: string }
        billing_line1: { type: string }
        billing_line2: { type: string }
        billing_postal_code:
          type: string
          description: Alphanumeric, no spaces.
        billing_state: { type: string }
        postback_url:
          type: string
          format: uri
          description: HTTPS URL to receive postback notifications for this token. Overrides the merchant-level default.

    NetworkToken:
      type: object
      properties:
        id: { type: string, format: uuid }
        merchant_id: { type: string, format: uuid }
        status: { $ref: "#/components/schemas/NetworkTokenStatus" }
        type:
          type: string
          enum: [merchant]
        network: { $ref: "#/components/schemas/NetworkTokenNetwork" }
        provider:
          type: string
          description: Tokenization provider identifier (e.g. `vts` for Visa Token Service).
        last_token_refresh:
          type: [string, "null"]
          format: date-time
        error:
          type: object
          description: Present only when `status=failed`.
          properties:
            code: { type: string }
            detail: { type: string }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    NetworkTokenEnvelope:
      type: object
      required: [result, data]
      properties:
        result:
          type: string
          example: OK
        data:
          $ref: "#/components/schemas/NetworkToken"

    NetworkTokenList:
      type: object
      required: [result, data_set, metadata, links]
      properties:
        result: { type: string, example: OK }
        data_set:
          type: array
          items:
            $ref: "#/components/schemas/NetworkToken"
        metadata:
          type: object
          properties:
            per_page: { type: integer }
            total_items: { type: integer }
            current_page: { type: integer }
            total_pages: { type: integer }
        links:
          type: object
          properties:
            self: { $ref: "#/components/schemas/Link" }
            first: { $ref: "#/components/schemas/Link" }
            last: { $ref: "#/components/schemas/Link" }
            prev: { $ref: "#/components/schemas/Link" }
            next: { $ref: "#/components/schemas/Link" }

    Link:
      type: object
      properties:
        href:
          type: string
          format: uri

    NetworkTokenData:
      type: object
      properties:
        id: { type: string, format: uuid }
        number:
          type: string
          description: The network token PAN. Treat as sensitive — handle like a real card PAN.
        expiry_month: { type: integer, minimum: 1, maximum: 12 }
        expiry_year: { type: integer }
        payment_account_reference:
          type: string
          description: Payment Account Reference (PAR) — unique identifier for the underlying account.

    NetworkTokenCryptogram:
      type: object
      properties:
        id: { type: string, format: uuid }
        authentication_data:
          type: string
          description: Base64-encoded cryptogram (up to 32 chars).
        pan_sequence_number:
          type: string
          description: PAN sequence number (2 digits).
        track2_equivalent:
          type: string
          description: Track 2 equivalent data (hex-encoded, up to 40 chars).
        created:
          type: string
          format: date-time

    # ------------------------------------------------------ Webhook bodies
    PaymentPostback:
      type: object
      description: |
        Postback delivered to `postback_url` for transaction lifecycle events
        (started, pending, completed, declined, …). Field set varies with the
        event:

        - `reason_code` is only present for declines / errors.
        - `remittance_info` is sent when a Capture becomes settled (Wero only).
        - `wallet_activity_reference`, `wallet_account_reference`, and
          `payer_location` are specific to Wero and only included when available.
        - `captured_amount` and `currency` are sent on capture-action postbacks.
        - `network_token_id` and `network_token_status` are present only when
          the payment was processed via Network Tokenization.
        - Card-* fields are present only for card-based payment methods (`cardecom`,
          `cardpos`, `applepay`, `googlepay`) once the transaction has masked
          card data available.

        All values are serialized as strings on the wire (form-style encoding)
        — types below describe the logical shape.
      required: [transaction_id, payment_type, status_code, status, checksum]
      properties:
        transaction_id: { type: string, format: uuid }
        payment_type: { $ref: "#/components/schemas/PaymentType" }
        status_code: { $ref: "#/components/schemas/TransactionStatusCode" }
        status: { $ref: "#/components/schemas/TransactionStatus" }
        order_id: { type: string, maxLength: 255 }
        message: { type: [string, "null"] }
        recurring:
          type: integer
          enum: [0, 1]
          description: Whether the transaction is part of a recurring sequence.
        checkout_id:
          type: string
          format: uuid
          description: Present when the transaction was created from a hosted checkout session.
        currency:
          $ref: "#/components/schemas/CurrencyCode"
        captured_amount:
          type: string
          description: Captured amount (decimal, sent as a string). Present on capture-action postbacks.
        remittance_info:
          type: string
          description: Sent when a Capture becomes settled.
        reason_code:
          type: string
          description: ISO rejection reason code (e.g. AC01) on declines.
        wallet_activity_reference:
          type: string
          description: Wallet activity reference. Specific to Wero. Only included when available.
        wallet_account_reference:
          type: string
          description: Wallet account reference. Specific to Wero. Only included when available.
        payer_location:
          type: string
          description: Payer location. Specific to Wero. Only included when available.
        additional_transaction_data: { type: [string, "null"] }
        amount: { type: number, format: float }
        included_fees: { type: string }
        external_transaction_id:
          type: string
          description: Reference ID assigned by the payment processor / network. Not available for all payment methods.
        network_token_id:
          type: string
          format: uuid
          description: Present only when the payment was processed using Network Tokenization.
        network_token_status:
          $ref: "#/components/schemas/NetworkTokenStatus"
        # Card-* fields, present for card-based payment methods (cc, applepay, googlepay, pos)
        card_last_four: { type: string }
        card_expiry_year: { type: string }
        card_expiry_month: { type: string }
        card_brand: { type: string }
        card_type: { type: string }
        card_country: { type: string }
        # SEPA Direct Debit fields, present for dd / dd_b2b payment methods
        mandate_id:
          type: string
          description: Mandate reference used for the SEPA collection. Specific to SEPA Direct Debit.
        iban:
          type: string
          description: IBAN of the bank account. Specific to SEPA Direct Debit.
        bic:
          type: string
          description: BIC of the bank account. Specific to SEPA Direct Debit.
        recurring_mandate:
          type: integer
          enum: [0, 1]
          description: "`1` if the SEPA mandate is recurring, `0` otherwise. Specific to SEPA Direct Debit."
        execution_date:
          type: string
          description: Date the SEPA collection is scheduled for execution. Specific to SEPA Direct Debit.
        checksum:
          type: string
          description: Legacy SHA1-hex checksum of the parameters using the merchant webhook key. The authoritative signature is the `Gateway-Webhook-Signature` header.

    RefundPostback:
      type: object
      description: Postback delivered to `postback_url` for refund lifecycle events.
      required: [transaction_id, payment_type, refund_id, refund_status_code, checksum]
      properties:
        transaction_id: { type: string, format: uuid }
        payment_type: { $ref: "#/components/schemas/PaymentType" }
        status_code: { $ref: "#/components/schemas/TransactionStatusCode" }
        status: { $ref: "#/components/schemas/TransactionStatus" }
        order_id: { type: string }
        message: { type: [string, "null"] }
        refund_remittance_info:
          type: string
          description: Sent when the refund becomes completed.
        refund_reason_code:
          type: string
          description: ISO rejection reason code for refund transfers (e.g. AC04).
        refund_wallet_activity_reference:
          type: string
          description: Wallet activity reference for the refund. Specific to Wero. Only included when available.
        payer_location:
          type: string
          description: Payer location. Specific to Wero. Only included when available.
        additional_transaction_data: { type: [string, "null"] }
        amount: { type: number, format: float }
        refund_status_code:
          type: integer
          description: 0 STARTED, 1 SUCCESS, 2 ERROR, 3 LOCAL, 4 REVERSED, 5 DECLINED.
          enum: [0, 1, 2, 3, 4, 5]
        refund_id: { type: string, format: uuid }
        included_fees: { type: string }
        checksum: { type: string }

    NetworkTokenPostback:
      type: object
      description: Form-encoded payload delivered when a network token's lifecycle status changes.
      required: [network_token_id, merchant_id, status, checksum]
      properties:
        network_token_id: { type: string, format: uuid }
        merchant_id: { type: string, format: uuid }
        status: { $ref: "#/components/schemas/NetworkTokenStatus" }
        network: { $ref: "#/components/schemas/NetworkTokenNetwork" }
        provider: { type: string }
        error_code:
          type: string
          description: Present only when `status=failed`.
        error_detail:
          type: string
          description: Present only when `status=failed`.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        checksum:
          type: string
          description: SHA1 hex digest of the payload — see signature header for HMAC verification.

    # ---------------------------------------------------------- API errors
    ApiError:
      type: object
      description: |
        Common error envelope. Most synchronous errors are returned with the
        same shape as a successful response but with a non-zero `error_code`
        and a populated `message`.
      properties:
        error_code:
          type: integer
          description: Numeric error code. See the Errors reference in the docs for the full list.
        message:
          type: string
        status_code: { $ref: "#/components/schemas/TransactionStatusCode" }
        status: { $ref: "#/components/schemas/TransactionStatus" }
