openapi: 3.0.3
info:
  title: Wegopay Runtime Payments and Checkout API
  version: 0.1.0
  description: >-
    Payments contract for the merchant-facing payment runtime and the public
    checkout runtime, including authenticated merchant sandbox test-checkout creation.
    Reporting, control-plane, onboarding, and webhook operations have separate contracts.
    Every response, including errors, is non-cacheable.
servers:
  - url: /
tags:
  - name: merchant-payments
  - name: checkout
paths:
  /v1/payments/{id}/refund:
    get:
      operationId: getPaymentRefund
      tags: [merchant-payments]
      summary: Read the full refund request and refresh its provider status
      description: >-
        Use a server-side merchant API key for the payment environment. Only payments owned by
        that merchant in the same environment are accessible. Never expose API keys in a browser.
        One durable request is permitted per verified remaining balance. Repeated POSTs with the
        same balance and amount return that request; a different amount conflicts. Another refund
        requires a succeeded request and verified reduction of the charge balance. Pending,
        failed, canceled and uncertain outcomes require support before another request.
        Refund acceptance does not change verified charge state; reconciliation verifies the result.
      security: [{ apiKeyBearer: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/RefundResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
    post:
      operationId: createPaymentRefund
      tags: [merchant-payments]
      summary: Refund all or part of the remaining paid amount
      description: >-
        Use a server-side merchant API key for the payment environment. Only payments owned by
        that merchant in the same environment are accessible. Never expose API keys in a browser.
        One durable request is permitted per verified remaining balance. Repeated POSTs with the
        same balance and amount return that request; a different amount conflicts. Another refund
        requires a succeeded request and verified reduction of the charge balance. Pending,
        failed, canceled and uncertain outcomes require support before another request.
        Refund acceptance does not change verified charge state; reconciliation verifies the result.
      security: [{ apiKeyBearer: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [expectedAmountCents]
              properties:
                expectedAmountCents:
                  type: integer
                  format: int64
                  minimum: 1
                  maximum: 100000000
                  description: Verified remaining balance shown at confirmation; used as a duplicate and stale-state guard.
                amountCents:
                  type: integer
                  format: int64
                  minimum: 1
                  maximum: 100000000
                  description: Amount to refund, at most expectedAmountCents. Omit to refund the full remaining balance.
      responses:
        '200': { $ref: '#/components/responses/RefundResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
        '409': { $ref: '#/components/responses/CheckoutPayConflict' }
  /merchant/transactions/{id}/refund:
    get:
      operationId: getMerchantRefund
      tags: [merchant-payments]
      summary: Read the full refund request and refresh its provider status
      description: >-
        Active merchants may access only their own payments. Administrators must select a merchant
        using X-Merchant-ID. The original payment selects provider credentials and environment.
        One durable request is permitted per verified remaining balance. Repeated POSTs with the
        same balance and amount return that request; a different amount conflicts. Another refund
        requires a succeeded request and verified reduction of the charge balance. Pending,
        failed, canceled and uncertain outcomes require support before another request.
        Refund acceptance does not change verified charge state; reconciliation verifies the result.
      x-wegopay-role: [merchant, admin]
      x-wegopay-rbac: { principal: activeLocalPrincipal, allowedRoles: [merchant, admin] }
      security: [{ bearerAuth: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
        - name: X-Merchant-ID
          in: header
          required: false
          description: Required for administrators; forbidden for merchants.
          schema: { type: string, format: uuid }
      responses:
        '200': { $ref: '#/components/responses/RefundResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
    post:
      operationId: createMerchantRefund
      tags: [merchant-payments]
      summary: Refund all or part of the remaining paid amount
      description: >-
        Active merchants may access only their own payments. Administrators must select a merchant
        using X-Merchant-ID. The original payment selects provider credentials and environment.
        One durable request is permitted per verified remaining balance. Repeated POSTs with the
        same balance and amount return that request; a different amount conflicts. Another refund
        requires a succeeded request and verified reduction of the charge balance. Pending,
        failed, canceled and uncertain outcomes require support before another request.
        Refund acceptance does not change verified charge state; reconciliation verifies the result.
      x-wegopay-role: [merchant, admin]
      x-wegopay-rbac: { principal: activeLocalPrincipal, allowedRoles: [merchant, admin] }
      security: [{ bearerAuth: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
        - name: X-Merchant-ID
          in: header
          required: false
          description: Required for administrators; forbidden for merchants.
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [expectedAmountCents]
              properties:
                expectedAmountCents:
                  type: integer
                  format: int64
                  minimum: 1
                  maximum: 100000000
                  description: Verified remaining balance shown at confirmation; used as a duplicate and stale-state guard.
                amountCents:
                  type: integer
                  format: int64
                  minimum: 1
                  maximum: 100000000
                  description: Amount to refund, at most expectedAmountCents. Omit to refund the full remaining balance.
      responses:
        '200': { $ref: '#/components/responses/RefundResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
        '409': { $ref: '#/components/responses/CheckoutPayConflict' }
  /admin/live-payment-links:
    post:
      operationId: createAdminLivePaymentLink
      tags: [merchant-payments]
      summary: Create a live payment link for an active merchant
      description: >-
        Platform administrators only. Creates a pending USD payment on the live connection,
        without charging a card. No merchant API key or synthetic payer data is required.
        Merchant default webhook settings apply. Reuse the idempotency key and identical
        body to recover an uncertain response. Sandbox connections cannot serve this operation.
      x-wegopay-role: [admin]
      x-wegopay-rbac: { principal: activeLocalPrincipal, allowedRoles: [admin] }
      security: [{ bearerAuth: [] }]
      parameters:
        - { $ref: '#/components/parameters/IdempotencyKey' }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [merchantId, amountCents]
              properties:
                merchantId: { type: string, format: uuid }
                amountCents: { type: integer, format: int64, minimum: 500, maximum: 100000000 }
      responses:
        '201': { $ref: '#/components/responses/PaymentCreatedResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '409': { $ref: '#/components/responses/IdempotencyConflict' }
        '422': { $ref: '#/components/responses/ValidationFailed' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /merchant/test-payments:
    get:
      operationId: getMerchantTestCheckoutConfiguration
      tags: [merchant-payments]
      summary: Check whether the configured provider connection supports test checkouts
      x-wegopay-role: [merchant, admin]
      x-wegopay-rbac: { principal: activeLocalPrincipal, allowedRoles: [merchant, admin] }
      security: [{ bearerAuth: [] }]
      responses:
        '200':
          description: Authoritative sandbox availability; never controlled by a browser toggle.
          headers:
            Cache-Control:
              schema: { type: string, enum: [no-store] }
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [data]
                properties:
                  data:
                    type: object
                    additionalProperties: false
                    required: [enabled]
                    properties:
                      enabled: { type: boolean }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
    post:
      operationId: createMerchantTestPayment
      tags: [merchant-payments]
      summary: Create a USD 5.00 sandbox checkout for the signed-in merchant
      description: >-
        Requires an active merchant or administrator session and a server-verified external sandbox
        configuration. Merchants use their authenticated merchant scope. Administrators must supply
        X-Test-Merchant-ID to select an active merchant. Provider credentials are never accepted. Uses the normal durable payment and verification pipeline.
        Repeating the same idempotency key returns the same checkout. Live provider
        configuration refuses this operation without creating a payment.
      x-wegopay-role: [merchant, admin]
      x-wegopay-rbac: { principal: activeLocalPrincipal, allowedRoles: [merchant, admin] }
      x-wegopay-resource-scope: authenticated-merchant
      security: [{ bearerAuth: [] }]
      parameters:
        - name: X-Test-Merchant-ID
          in: header
          description: Required for administrators; forbidden for merchant sessions.
          required: false
          schema: { type: string, format: uuid }
        - { $ref: '#/components/parameters/IdempotencyKey' }
      responses:
        '201': { $ref: '#/components/responses/PaymentCreatedResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '409': { $ref: '#/components/responses/IdempotencyConflict' }
        '422': { $ref: '#/components/responses/ValidationFailed' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /v1/payments:
    post:
      operationId: createPayment
      tags: [merchant-payments]
      summary: Create one payment and its checkout session
      description: >-
        Creates a pending payment and a checkout session with five card attempts
        and an approximately thirty-minute lifetime. Idempotency is scoped to
        merchant plus this operation and retained for twenty-four hours. Reusing
        the same key with the same canonical request replays the original 201
        status and byte-equivalent body. Reuse with a different canonical request
        is idempotency_key_reused. While the first matching request has no fenced
        result, another request is idempotency_in_progress and cannot create a
        second payment. Validation failures before persistence are not replayed.
      x-wegopay-auth-boundary: merchant-api-key
      x-wegopay-resource-scope: authenticated-merchant
      x-wegopay-idempotency:
        scope: [merchantId, operationId, idempotencyKey]
        retentionHours: 24
        canonicalRequest: parsed-strict-json
        sameRequest: replay-original-201-and-body
        differentRequest: idempotency_key_reused
        inProgress: idempotency_in_progress
      security: [{ apiKeyBearer: [] }]
      parameters:
        - { $ref: '#/components/parameters/IdempotencyKey' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreatePaymentRequest' }
      responses:
        '201': { $ref: '#/components/responses/PaymentCreatedResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '409': { $ref: '#/components/responses/IdempotencyConflict' }
        '422': { $ref: '#/components/responses/ValidationFailed' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
    get:
      operationId: listPayments
      tags: [merchant-payments]
      summary: List payments owned by the authenticated merchant
      description: >-
        Results are scoped by the authenticated API key and ordered by createdAt
        descending then id descending. Date filters apply to createdAt as inclusive
        UTC dates. An out-of-range page is valid and returns an empty data array.
      x-wegopay-auth-boundary: merchant-api-key
      x-wegopay-resource-scope: authenticated-merchant
      security: [{ apiKeyBearer: [] }]
      parameters:
        - { $ref: '#/components/parameters/PaymentStatusFilter' }
        - { $ref: '#/components/parameters/FromDate' }
        - { $ref: '#/components/parameters/ToDate' }
        - { $ref: '#/components/parameters/Page' }
        - { $ref: '#/components/parameters/PerPage' }
      responses:
        '200': { $ref: '#/components/responses/PaymentList' }
        '400': { $ref: '#/components/responses/InvalidQuery' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /v1/payments/{id}:
    get:
      operationId: getPayment
      tags: [merchant-payments]
      summary: Get one payment owned by the authenticated merchant
      description: >-
        Ownership is derived only from the authenticated API key. A well-formed ID
        belonging to another merchant and an unknown ID return the identical 404
        status, code, message, headers, and observable timing class.
      x-wegopay-auth-boundary: merchant-api-key
      x-wegopay-resource-scope: authenticated-merchant
      x-wegopay-foreign-id-policy: indistinguishable-not-found
      security: [{ apiKeyBearer: [] }]
      parameters:
        - { $ref: '#/components/parameters/PaymentId' }
      responses:
        '200': { $ref: '#/components/responses/PaymentDetail' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /v1/payments/{id}/capture:
    post:
      operationId: createMerchantCapture
      tags: [merchant-payments]
      summary: Prepare short-lived payment-scoped card capture access
      description: >-
        Requires an enabled merchant-owned capture integration. Merchant and
        environment come from the API key; unknown, foreign, wrong-environment,
        and unconfigured payments return 404. Payment amount and order details
        are fixed by payment creation. Capture reference is random and payment-scoped. POST capture returns bounded
        write-only access. Each payment uses a dedicated server-owned vault encryption key;
        submitted tokens are verified in that key namespace by an authenticated metadata-only
        vault listing after capture revocation. References alone do not establish ownership. Legacy permanent capture credentials are not accepted. Card attempts, action
        confirmation, expiry, recovery, and verified payment state use the same
        durable checkout fences as hosted checkout. A 503 is an uncertain outcome;
        poll status before submitting another token. A completed 3DS challenge
        does not establish a paid payment.
      x-wegopay-auth-boundary: merchant-api-key
      x-wegopay-resource-scope: authenticated-merchant
      x-wegopay-log-policy: redact-request-body-and-response-action-material
      security: [{ apiKeyBearer: [] }]
      parameters:
        - { $ref: '#/components/parameters/PaymentId' }
      responses:
        '200': { $ref: '#/components/responses/MerchantCheckoutDetail' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '409': { $ref: '#/components/responses/CheckoutPayConflict' }
        '410': { $ref: '#/components/responses/CheckoutGone' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /v1/payments/{id}/checkout:
    get:
      operationId: getMerchantCheckout
      tags: [merchant-payments]
      summary: Get the configured merchant-owned card checkout
      description: >-
        Requires an enabled merchant-owned capture integration. Merchant and
        environment come from the API key; unknown, foreign, wrong-environment,
        and unconfigured payments return 404. Payment amount and order details
        are fixed by payment creation. Capture reference is random and payment-scoped. POST capture returns bounded
        write-only access. Each payment uses a dedicated server-owned vault encryption key;
        submitted tokens are verified in that key namespace by an authenticated metadata-only
        vault listing after capture revocation. References alone do not establish ownership. Legacy permanent capture credentials are not accepted. Card attempts, action
        confirmation, expiry, recovery, and verified payment state use the same
        durable checkout fences as hosted checkout. A 503 is an uncertain outcome;
        poll status before submitting another token. A completed 3DS challenge
        does not establish a paid payment.
      x-wegopay-auth-boundary: merchant-api-key
      x-wegopay-resource-scope: authenticated-merchant
      x-wegopay-log-policy: redact-request-body-and-response-action-material
      security: [{ apiKeyBearer: [] }]
      parameters:
        - { $ref: '#/components/parameters/PaymentId' }
      responses:
        '200': { $ref: '#/components/responses/MerchantCheckoutDetail' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /v1/payments/{id}/pay:
    post:
      operationId: payMerchantPayment
      tags: [merchant-payments]
      summary: Submit a captured card token for an existing payment
      description: >-
        Requires an enabled merchant-owned capture integration. Merchant and
        environment come from the API key; unknown, foreign, wrong-environment,
        and unconfigured payments return 404. Payment amount and order details
        are fixed by payment creation. Capture reference is random and payment-scoped. POST capture returns bounded
        write-only access. Each payment uses a dedicated server-owned vault encryption key;
        submitted tokens are verified in that key namespace by an authenticated metadata-only
        vault listing after capture revocation. References alone do not establish ownership. Legacy permanent capture credentials are not accepted. Card attempts, action
        confirmation, expiry, recovery, and verified payment state use the same
        durable checkout fences as hosted checkout. A 503 is an uncertain outcome;
        poll status before submitting another token. A completed 3DS challenge
        does not establish a paid payment.
      x-wegopay-auth-boundary: merchant-api-key
      x-wegopay-resource-scope: authenticated-merchant
      x-wegopay-log-policy: redact-request-body-and-response-action-material
      security: [{ apiKeyBearer: [] }]
      parameters:
        - { $ref: '#/components/parameters/PaymentId' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/MerchantPayRequest' }
      responses:
        '200': { $ref: '#/components/responses/CheckoutPaymentOutcome' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/CheckoutPayConflict' }
        '410': { $ref: '#/components/responses/CheckoutGone' }
        '422': { $ref: '#/components/responses/CardTokenRejected' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /v1/payments/{id}/confirm:
    post:
      operationId: confirmMerchantPayment
      tags: [merchant-payments]
      summary: Confirm the current provider authentication action
      description: >-
        Requires an enabled merchant-owned capture integration. Merchant and
        environment come from the API key; unknown, foreign, wrong-environment,
        and unconfigured payments return 404. Payment amount and order details
        are fixed by payment creation. Capture reference is random and payment-scoped. POST capture returns bounded
        write-only access. Each payment uses a dedicated server-owned vault encryption key;
        submitted tokens are verified in that key namespace by an authenticated metadata-only
        vault listing after capture revocation. References alone do not establish ownership. Legacy permanent capture credentials are not accepted. Card attempts, action
        confirmation, expiry, recovery, and verified payment state use the same
        durable checkout fences as hosted checkout. A 503 is an uncertain outcome;
        poll status before submitting another token. A completed 3DS challenge
        does not establish a paid payment.
      x-wegopay-auth-boundary: merchant-api-key
      x-wegopay-resource-scope: authenticated-merchant
      x-wegopay-log-policy: redact-request-body-and-response-action-material
      security: [{ apiKeyBearer: [] }]
      parameters:
        - { $ref: '#/components/parameters/PaymentId' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CheckoutConfirmRequest' }
      responses:
        '200': { $ref: '#/components/responses/CheckoutPaymentOutcome' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/CheckoutConfirmConflict' }
        '410': { $ref: '#/components/responses/CheckoutGone' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /v1/payments/{id}/status:
    get:
      operationId: getMerchantPaymentStatus
      tags: [merchant-payments]
      summary: Get checkout processing status and the current provider action
      description: >-
        Requires an enabled merchant-owned capture integration. Merchant and
        environment come from the API key; unknown, foreign, wrong-environment,
        and unconfigured payments return 404. Payment amount and order details
        are fixed by payment creation. Capture reference is random and payment-scoped. POST capture returns bounded
        write-only access. Each payment uses a dedicated server-owned vault encryption key;
        submitted tokens are verified in that key namespace by an authenticated metadata-only
        vault listing after capture revocation. References alone do not establish ownership. Legacy permanent capture credentials are not accepted. Card attempts, action
        confirmation, expiry, recovery, and verified payment state use the same
        durable checkout fences as hosted checkout. A 503 is an uncertain outcome;
        poll status before submitting another token. A completed 3DS challenge
        does not establish a paid payment.
      x-wegopay-auth-boundary: merchant-api-key
      x-wegopay-resource-scope: authenticated-merchant
      x-wegopay-log-policy: redact-request-body-and-response-action-material
      security: [{ apiKeyBearer: [] }]
      parameters:
        - { $ref: '#/components/parameters/PaymentId' }
      responses:
        '200': { $ref: '#/components/responses/CheckoutStatus' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/ApiKeyUnauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /checkout/{token}:
    get:
      operationId: getCheckout
      tags: [checkout]
      summary: Get public checkout render data
      description: >-
        The URL token is the only authorization credential. Paid and expired
        sessions remain readable as terminal receipts/dead ends and therefore
        return 200 with their exact status. Malformed, unknown, or unrecognized
        tokens are indistinguishable 404 responses. Production accepts only the
        high-entropy wgp_chk_ form; named demo tokens are development-loopback
        compatibility fixtures and must be rejected in every production mode.
      x-wegopay-auth-boundary: checkout-url-token
      x-wegopay-log-policy: redact-token-path-segment
      x-wegopay-legacy-demo-tokens: development-loopback-only
      security: []
      parameters:
        - { $ref: '#/components/parameters/CheckoutToken' }
      responses:
        '200': { $ref: '#/components/responses/CheckoutDetail' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /checkout/{token}/pay:
    post:
      operationId: payCheckout
      tags: [checkout]
      summary: Submit one opaque PCI-vaulted card token
      description: >-
        The request contains exactly one opaque cardToken. Accepting it atomically
        claims one of five attempts and creates a durable result fence before any
        external call. A definitive failed charge consumes that claimed attempt;
        malformed or locally rejected input does not. Dependency uncertainty keeps
        the fence in processing and returns 503, so a retry cannot double-charge;
        clients poll status. A paid session replays paid. Concurrent processing,
        an outstanding action, and another unsettled fence are 409. Expiry or zero
        attempts is terminal 410. No payment-card account data is accepted.
      x-wegopay-auth-boundary: checkout-url-token
      x-wegopay-log-policy: redact-token-path-segment-and-request-body
      x-wegopay-result-fencing:
        claim: atomic-status-version-and-durable-attempt
        externalIdempotency: checkout-session-and-attempt
        uncertainResult: retain-processing-and-reconcile
        retryRule: never-start-second-charge-until-fence-settles
      security: []
      parameters:
        - { $ref: '#/components/parameters/CheckoutToken' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CheckoutPayRequest' }
      responses:
        '200': { $ref: '#/components/responses/CheckoutPaymentOutcome' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/CheckoutPayConflict' }
        '410': { $ref: '#/components/responses/CheckoutGone' }
        '422': { $ref: '#/components/responses/CardTokenRejected' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /checkout/{token}/wallet:
    post:
      operationId: initiateCheckoutWallet
      tags: [checkout]
      summary: Create or resume a hosted wallet checkout
      description: >-
        Creates at most one durable provider-hosted wallet PaymentIntent for the
        current checkout attempt and returns only its hosted checkout URL. Replays
        return the same fenced wallet intent; uncertainty never creates a second
        intent. Provider credentials and merchant-controlled callback coordinates
        are never accepted from the browser.
      x-wegopay-auth-boundary: checkout-url-token
      x-wegopay-log-policy: redact-token-path-segment-and-response-action-material
      security: []
      parameters:
        - { $ref: '#/components/parameters/CheckoutToken' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CheckoutWalletRequest' }
      responses:
        '200': { $ref: '#/components/responses/CheckoutWalletOutcome' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/CheckoutPayConflict' }
        '410': { $ref: '#/components/responses/CheckoutGone' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /checkout/{token}/confirm:
    post:
      operationId: confirmCheckout
      tags: [checkout]
      summary: Confirm the current customer action
      description: >-
        Confirms only the currently fenced requiresAction attempt and never claims
        a new card attempt. The response may be requiresAction repeatedly, each
        time with fresh action material, until the same attempt reaches paid or a
        definitive failed result. Paid replays paid. Calls without a current action,
        concurrent confirmation, or an unsettled result fence are 409. Expiry or
        zero attempts is terminal 410.
      x-wegopay-auth-boundary: checkout-url-token
      x-wegopay-log-policy: redact-token-path-segment
      x-wegopay-result-fencing:
        claim: current-requires-action-fence
        repeatedRequiresAction: replace-action-material-within-same-attempt
        uncertainResult: retain-processing-and-reconcile
      security: []
      parameters:
        - { $ref: '#/components/parameters/CheckoutToken' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CheckoutConfirmRequest' }
      responses:
        '200': { $ref: '#/components/responses/CheckoutPaymentOutcome' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/CheckoutConfirmConflict' }
        '410': { $ref: '#/components/responses/CheckoutGone' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /checkout/{token}/status:
    get:
      operationId: getCheckoutStatus
      tags: [checkout]
      summary: Read the authoritative fenced checkout status
      description: >-
        Polling never starts work. It reports the exact durable session status,
        attempts remaining, and payment ID. Paid and expired are represented by
        200 terminal states. Malformed, unknown, and unrecognized tokens are the
        same 404 response.
      x-wegopay-auth-boundary: checkout-url-token
      x-wegopay-log-policy: redact-token-path-segment
      security: []
      parameters:
        - { $ref: '#/components/parameters/CheckoutToken' }
      responses:
        '200': { $ref: '#/components/responses/CheckoutStatus' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }
  /checkout/{token}/events:
    post:
      operationId: recordCheckoutLifecycle
      tags: [checkout]
      summary: Record an untrusted checkout interaction
      x-wegopay-auth-boundary: checkout-url-token
      x-wegopay-log-policy: redact-token-path-segment-and-response-action-material
      security: []
      parameters:
        - { $ref: '#/components/parameters/CheckoutToken' }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CheckoutLifecycleEvent' }
      responses:
        '200':
          description: Interaction recorded or duplicate already recorded
          headers:
            Cache-Control: { $ref: '#/components/headers/NoStore' }
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [data]
                properties:
                  data:
                    type: object
                    additionalProperties: false
                    required: [accepted]
                    properties:
                      accepted: { type: boolean }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '503': { $ref: '#/components/responses/ServiceUnavailable' }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    apiKeyBearer:
      type: http
      scheme: bearer
      bearerFormat: wgp_live_…
      description: Live merchant API key issued by the Wegopay control plane.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: >-
        16 to 255 visible ASCII characters. Scope is authenticated merchant plus
        operation. Records are retained for twenty-four hours.
      schema:
        type: string
        minLength: 16
        maxLength: 255
        pattern: '^[\x21-\x7E]{16,255}$'
    PaymentId:
      name: id
      in: path
      required: true
      schema: { type: string, format: uuid }
    CheckoutToken:
      name: token
      in: path
      required: true
      description: >-
        Secret URL credential. Production uses at least 256 bits of entropy in a
        wgp_chk_ token. The four named demo fixtures are accepted only by a
        development server bound to a loopback interface. Every HTTP/access/error
        log and trace must replace this path segment before export or persistence.
      x-wegopay-secret: true
      x-wegopay-storage: sha256-lookup-plus-versioned-aead-retrieval
      x-wegopay-production-pattern: '^wgp_chk_[A-Za-z0-9_-]{43,128}$'
      x-wegopay-development-fixtures: [demo-ok, demo-3ds, demo-decline, demo-expired]
      schema:
        type: string
        minLength: 7
        maxLength: 136
        pattern: '^(wgp_chk_[A-Za-z0-9_-]{43,128}|demo-(ok|3ds|decline|expired))$'
    PaymentStatusFilter:
      name: status
      in: query
      required: false
      schema: { $ref: '#/components/schemas/PaymentStatus' }
    FromDate:
      name: from
      in: query
      required: false
      description: Inclusive UTC createdAt date. Must not be after to.
      schema: { type: string, format: date }
    ToDate:
      name: to
      in: query
      required: false
      description: Inclusive UTC createdAt date. Must not be before from.
      schema: { type: string, format: date }
    Page:
      name: page
      in: query
      required: false
      schema: { type: integer, minimum: 1, default: 1 }
    PerPage:
      name: perPage
      in: query
      required: false
      schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
  headers:
    NoStore:
      required: true
      schema: { type: string, enum: [no-store] }
    ApiKeyChallenge:
      required: true
      schema:
        type: string
        enum: ['Bearer realm="wegopay-api-key"']
    RetryAfter:
      required: true
      schema: { type: integer, minimum: 1 }
  schemas:
    RefundView:
      type: object
      additionalProperties: false
      required: [eligible, remainingCents, refundedCents, request]
      properties:
        eligible: { type: boolean }
        remainingCents: { type: integer, format: int64, minimum: 0 }
        refundedCents: { type: integer, format: int64, minimum: 0 }
        request:
          nullable: true
          allOf: [{ $ref: '#/components/schemas/RefundRequest' }]
    RefundRequest:
      type: object
      additionalProperties: false
      required: [id, amountCents, status, createdAt]
      properties:
        id: { type: string, format: uuid }
        amountCents: { type: integer, format: int64, minimum: 1 }
        status: { type: string, enum: [submitting, unknown, pending, succeeded, failed, canceled, requires_action] }
        createdAt: { type: string, format: date-time }
    CheckoutLifecycleEvent:
      type: object
      additionalProperties: false
      required: [eventId, eventType]
      properties:
        errorCode:
          type: string
          enum: [not_found, checkout_expired, attempts_exhausted, rate_limited, service_unavailable, invalid_response, network_error, http_error]
          description: Sanitized browser error classification, permitted only for checkout_load_failed. Never provider text or a payment verdict.
        eventId: { type: string, format: uuid }
        eventType: { type: string, enum: [link_opened, page_loaded, page_hidden, page_visible, method_selected, method_change_clicked, pay_clicked, retry_clicked, status_check_clicked, hosted_fields_ready, hosted_fields_complete, tokenization_started, tokenization_completed, tokenization_failed, three_ds_opened, three_ds_completed, three_ds_failed, three_ds_closed, wallet_submit_clicked, wallet_redirect_started, success_redirect_started, cancel_clicked, checkout_load_failed] }
        method: { type: string, enum: [card, wallet] }
    PaymentEnvironment:
      type: string
      enum: [sandbox, live]
      description: Immutable provider account selection derived from the API key. Omit environment on creation or provide the same environment as the key; a mismatch is rejected. Test keys can only access sandbox payments and live keys can only access live payments.
    CheckoutLocale:
      type: string
      enum: [en, es, pt]
      description: Wegopay checkout language. Omission on creation selects English. Provider-hosted content is controlled separately.
    CreatePaymentRequest:
      type: object
      additionalProperties: false
      required: [amountCents, currency, reference, successUrl, cancelUrl]
      properties:
        paymentMethod:
          allOf: [{ $ref: '#/components/schemas/CheckoutPaymentMethod' }]
          description: Optional checkout restriction. Opens only this method and prevents switching. Must be enabled by the effective API-key and merchant settings, otherwise creation returns 422 validation_failed for paymentMethod. Omit to offer all configured methods. Custom card capture supports card only.
        checkoutMode:
          type: string
          enum: [hosted, custom]
          description: Defaults to hosted. Custom requires an enabled merchant capture integration. Prepare scoped access with POST capture, submit a token and captureReference, and open actionUrl in the customer browser when required.
        embeddingOrigin:
          type: string
          minLength: 1
          maxLength: 255
          pattern: '^https://'
          description: Exact approved website to embed this checkout. Required for embedding when the effective allowlist exceeds 20 sites. Omission preserves legacy embedding for lists up to 20 and otherwise permits hosted checkout only.
        locale: { $ref: '#/components/schemas/CheckoutLocale' }
        environment: { $ref: '#/components/schemas/PaymentEnvironment' }
        amountCents: { type: integer, format: int64, minimum: 500, maximum: 100000000 }
        currency: { type: string, enum: [USD] }
        reference: { type: string, minLength: 1, maxLength: 255, pattern: '.*\S.*' }
        customer: { $ref: '#/components/schemas/PaymentCustomer' }
        metadata: { $ref: '#/components/schemas/PaymentMetadata' }
        successUrl: { $ref: '#/components/schemas/AbsoluteRedirectUrl' }
        cancelUrl: { $ref: '#/components/schemas/AbsoluteRedirectUrl' }
    PaymentCustomer:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        email: { type: string, format: email, maxLength: 320 }
        name: { type: string, minLength: 1, maxLength: 255, pattern: '.*\S.*' }
        documentNumber: { type: string, pattern: '^[0-9]{11,14}$', description: Payer document number for server-side wallet initiation. }
        phone: { type: string, pattern: '^[0-9]{10,15}$', description: Payer phone number for server-side wallet initiation. }
    PaymentMetadata:
      type: object
      description: >-
        Merchant-defined JSON object. The server measures canonical UTF-8 JSON,
        including keys and structural bytes, and rejects values over 16384 bytes.
        The limit is applied before persistence and is part of idempotency hashing.
      maxProperties: 100
      additionalProperties: {}
      x-go-type: json.RawMessage
      x-wegopay-max-encoded-bytes: 16384
      x-wegopay-encoding: canonical-utf8-json
    AbsoluteRedirectUrl:
      type: string
      format: uri
      minLength: 1
      maxLength: 2048
      pattern: '^(https://[^\s]+|http://(localhost|127\.0\.0\.1|\[::1\])(?::[0-9]{1,5})?(?:/[^\s]*)?)$'
      description: >-
        Absolute URL parsed by a standards-compliant URL parser. Production
        permits HTTPS only. HTTP is accepted only when the hostname is exactly
        localhost, 127.0.0.1, or ::1 in development. Credentials and fragments
        are forbidden; hostnames are canonicalized before validation.
      x-wegopay-production-scheme: https
      x-wegopay-development-http-hosts: [localhost, 127.0.0.1, '::1']
      x-wegopay-forbid: [userinfo, fragment]
    Currency:
      type: string
      enum: [USD]
    PaymentStatus:
      type: string
      enum: [pending, requiresAction, paid, failed, refunded, partiallyRefunded, disputed, disputeWon, disputeLost]
    PaymentMethod:
      type: string
      nullable: true
      enum: [card, wallet, pix]
    CheckoutPaymentMethod:
      type: string
      enum: [card, wallet]
    CheckoutSessionStatus:
      type: string
      enum: [open, processing, requiresAction, paid, failed_retryable, expired]
    PaymentCreated:
      type: object
      additionalProperties: false
      required: [id, status, checkoutUrl, expiresAt]
      properties:
        environment: { $ref: '#/components/schemas/PaymentEnvironment' }
        id: { type: string, format: uuid }
        status: { type: string, enum: [pending] }
        checkoutUrl: { $ref: '#/components/schemas/AbsoluteRedirectUrl' }
        expiresAt: { type: string, format: date-time }
    PaymentCustomerRead:
      type: object
      additionalProperties: false
      required: [email, name]
      properties:
        email: { type: string, format: email, nullable: true, maxLength: 320 }
        name: { type: string, nullable: true, maxLength: 255 }
    Payment:
      type: object
      additionalProperties: false
      required:
        - id
        - reference
        - status
        - method
        - amountCents
        - refundedCents
        - netCents
        - currency
        - customer
        - metadata
        - checkoutUrl
        - expiresAt
        - cardBrand
        - cardBin
        - cardIssuer
        - cardLast4
        - failureCode
        - failureMessage
        - createdAt
        - paidAt
        - refundedAt
      properties:
        environment: { $ref: '#/components/schemas/PaymentEnvironment' }
        id: { type: string, format: uuid }
        reference: { type: string }
        status: { $ref: '#/components/schemas/PaymentStatus' }
        method: { $ref: '#/components/schemas/PaymentMethod' }
        amountCents: { type: integer, format: int64, minimum: 500, maximum: 100000000 }
        refundedCents: { type: integer, format: int64, minimum: 0, maximum: 100000000 }
        netCents: { type: integer, format: int64, minimum: 0, maximum: 100000000 }
        currency: { $ref: '#/components/schemas/Currency' }
        customer: { $ref: '#/components/schemas/PaymentCustomerRead' }
        metadata: { $ref: '#/components/schemas/PaymentMetadata' }
        checkoutUrl:
          allOf: [{ $ref: '#/components/schemas/AbsoluteRedirectUrl' }]
          nullable: true
        expiresAt: { type: string, format: date-time }
        cardBin: { type: string, nullable: true, pattern: '^([0-9]{6}|[0-9]{8})$', description: "Issuer prefix from authenticated PCI Vault capture matched to the verified attempt. Null when unavailable." }
        cardIssuer:
          allOf: [{ $ref: '#/components/schemas/PaymentCardIssuer' }]
          nullable: true
        cardBrand:
          type: string
          nullable: true
          description: Card brand from verified provider status; also included in signed payment webhook data. Null when unavailable.
        cardLast4:
          type: string
          nullable: true
          pattern: '^[0-9]{4}$'
          description: Last four card digits from verified provider status as a string preserving leading zeros; also included in signed payment webhook data. Null when unavailable. This is not a BIN/IIN; BIN/issuer metadata is separately available in cardBin and cardIssuer when PCI Vault capture metadata is configured.
        failureCode: { type: string, nullable: true }
        failureMessage: { type: string, nullable: true }
        createdAt: { type: string, format: date-time }
        paidAt: { type: string, format: date-time, nullable: true }
        refundedAt: { type: string, format: date-time, nullable: true }
    PaymentWebhookEvent:
      type: object
      additionalProperties: false
      description: Signed historical payment snapshot delivered at least once. Verify the exact raw bytes before reading fields. New optional fields may be added to version 1; tolerate unknown fields. Retrieve current payment state before fulfillment.
      required: [version, eventId, type, createdAt, data]
      properties:
        environment: { $ref: '#/components/schemas/PaymentEnvironment' }
        version: { type: integer, enum: [1] }
        eventId: { type: string, pattern: '^[0-9A-HJKMNP-TV-Z]{26}$' }
        type:
          type: string
          enum: [payment.paid, payment.failed, payment.refunded, payment.partially_refunded, payment.disputed, payment.dispute_won, payment.dispute_lost]
        createdAt: { type: string, format: date-time }
        data: { $ref: '#/components/schemas/PaymentWebhookData' }
    PaymentWebhookData:
      type: object
      additionalProperties: false
      description: Merchant-safe projection of verified state. Processor identities, costs, account balances, credentials, action secrets, and raw provider data are excluded. New fields may be absent from historical queued events.
      required: [id, reference, status, method, amountCents, refundedCents, netCents, currency, cardBrand, cardLast4, failureCode, failureMessage, createdAt, paidAt, refundedAt]
      properties:
        id: { type: string, format: uuid }
        reference: { type: string }
        status: { $ref: '#/components/schemas/PaymentStatus' }
        method: { $ref: '#/components/schemas/PaymentMethod' }
        amountCents: { type: integer, format: int64, minimum: 500, maximum: 100000000 }
        refundedCents: { type: integer, format: int64, minimum: 0, maximum: 100000000 }
        netCents:
          type: integer
          format: int64
          minimum: 0
          maximum: 100000000
          description: Original amount minus cumulative refunds, before processing fees.
        currency: { $ref: '#/components/schemas/Currency' }
        cardBrand: { type: string, nullable: true }
        cardLast4: { type: string, nullable: true, pattern: '^[0-9]{4}$' }
        cardBin:
          type: string
          nullable: true
          pattern: '^(?:[0-9]{6}|[0-9]{8})$'
          description: PCI Vault issuer prefix from an authenticated capture webhook bound to this checkout attempt; at most eight digits. Null when unavailable. Historical events may omit it.
        cardIssuer:
          allOf: [{ $ref: '#/components/schemas/PaymentCardIssuer' }]
          nullable: true
          description: Most-specific PCI Vault issuer match, independent of verified payment status; null when unavailable. Historical events may omit it.

        failureCode: { type: string, nullable: true }
        failureMessage: { type: string, nullable: true }
        failureIsFinal:
          type: boolean
          nullable: true
          description: Provider-reported finality flag; null if omitted by the provider. This does not replace the verified payment status or guarantee settlement or immunity from refunds/disputes.
        refunds:
          type: array
          nullable: true
          maxItems: 100
          description: Individual refund amounts when the provider supplies a complete list matching cumulative refundedCents. Null when unavailable; an empty array means a reported empty list. Processor refund identifiers are excluded.
          items: { $ref: '#/components/schemas/PaymentWebhookRefund' }
        dispute:
          allOf: [{ $ref: '#/components/schemas/PaymentWebhookDispute' }]
          nullable: true
        verifiedAt:
          type: string
          format: date-time
          description: UTC time Wegopay authenticated and verified this observation, distinct from paidAt and event createdAt.
        statusVersion: { type: integer, format: int32, minimum: 1, description: Increases when the normalized payment status changes. }
        observationVersion: { type: integer, format: int64, minimum: 1, description: Increases for each applied verified observation including public detail changes. Use this per payment to detect older snapshots; version gaps are normal. }
        createdAt: { type: string, format: date-time }
        paidAt: { type: string, format: date-time, nullable: true }
        refundedAt: { type: string, format: date-time, nullable: true }
    PaymentCardIssuer:
      type: object
      additionalProperties: false
      required: [bank, countryCode, countryName, type, level, category, regulated]
      properties:
        bank: { type: string, nullable: true, maxLength: 128 }
        countryCode: { type: string, nullable: true, pattern: '^[A-Z]{2}$' }
        countryName: { type: string, nullable: true, maxLength: 128 }
        type: { type: string, nullable: true, maxLength: 128 }
        level: { type: string, nullable: true, maxLength: 128 }
        category: { type: string, nullable: true, maxLength: 128 }
        regulated: { type: string, nullable: true, maxLength: 128 }
    PaymentWebhookRefund:
      type: object
      additionalProperties: false
      required: [amountCents]
      properties:
        amountCents: { type: integer, format: int64, minimum: 0, maximum: 100000000 }
    PaymentWebhookDispute:
      type: object
      additionalProperties: false
      required: [status, reason, amountCents, currency, createdAt]
      properties:
        status:
          type: string
          description: Provider-reported card-network dispute stage, separate from normalized payment status. Unknown stages become other.
          enum: [warning_needs_response, warning_under_review, warning_closed, needs_response, under_review, won, lost, prevented, other]
        reason:
          type: string
          description: Sanitized dispute reason code. Unknown reasons become other; raw free text is excluded.
          enum: [bank_cannot_process, check_returned, credit_not_processed, customer_initiated, debit_not_authorized, duplicate, fraudulent, general, incorrect_account_details, insufficient_funds, product_not_received, product_unacceptable, subscription_canceled, unrecognized, other]
        amountCents: { type: integer, format: int64, minimum: 0, maximum: 100000000 }
        currency: { $ref: '#/components/schemas/Currency' }
        createdAt: { type: string, format: date-time, nullable: true }
    PaginationMeta:
      type: object
      additionalProperties: false
      required: [page, perPage, total]
      properties:
        page: { type: integer, minimum: 1 }
        perPage: { type: integer, minimum: 1, maximum: 100 }
        total: { type: integer, minimum: 0 }
    CheckoutTerminalReason:
      type: string
      enum: [checkout_expired, attempts_exhausted, provider_failed]
      description: Why a checkout closed. A provider failure is not a local link timeout.
    CheckoutSession:
      type: object
      additionalProperties: false
      required:
        - paymentId
        - merchantDisplayName
        - merchantLogoUrl
        - amountCents
        - currency
        - reference
        - status
        - expiresAt
        - attemptsLeft
        - successUrl
        - cancelUrl
        - pciConfig
      properties:
        paymentMethod:
          allOf: [{ $ref: '#/components/schemas/CheckoutPaymentMethod' }]
          description: Method restriction supplied when creating the payment. Omitted for unrestricted checkouts. enabledPaymentTypes reflects this restriction and current permissions.
        walletRequiredFields:
          description: Payer fields still needed for wallet initiation. Hosted cards return an empty array because payer details are optional, allowing direct initiation with an empty request. Omitted by older servers; clients then collect all four fields.
          type: array
          maxItems: 4
          uniqueItems: true
          items: { type: string, enum: [name, email, documentNumber, phone] }
        customer:
          description: Merchant-supplied payer name and email. Document number and phone remain server-side. Omitted by older servers.
          allOf: [{ $ref: '#/components/schemas/PaymentCustomerRead' }]
        locale: { $ref: '#/components/schemas/CheckoutLocale' }
        embeddingOrigins:
          description: Selected embeddingOrigin when still approved by the effective API-key or merchant allowlist. Without a selection, returns the effective list only when it contains at most 20 sites; larger lists return empty. Empty or omitted blocks framing.
          type: array
          maxItems: 20
          items: { type: string, maxLength: 255 }
        enabledPaymentTypes:
          description: Currently enabled payment types after merchant settings, effective API-key settings and any paymentMethod restriction. Omitted only by older API versions.
          type: array
          items: { type: string }
        environment: { $ref: '#/components/schemas/PaymentEnvironment' }
        paymentId: { type: string, format: uuid }
        merchantDisplayName: { type: string, minLength: 1, maxLength: 255 }
        merchantLogoUrl:
          allOf: [{ $ref: '#/components/schemas/AbsoluteRedirectUrl' }]
          nullable: true
        amountCents: { type: integer, format: int64, minimum: 500, maximum: 100000000 }
        currency: { $ref: '#/components/schemas/Currency' }
        reference: { type: string, minLength: 1, maxLength: 255 }
        status: { $ref: '#/components/schemas/CheckoutSessionStatus' }
        terminalReason: { $ref: '#/components/schemas/CheckoutTerminalReason' }
        expiresAt: { type: string, format: date-time }
        attemptsLeft: { type: integer, minimum: 0, maximum: 5 }
        successUrl: { $ref: '#/components/schemas/AbsoluteRedirectUrl' }
        cancelUrl: { $ref: '#/components/schemas/AbsoluteRedirectUrl' }
        pciConfig: { $ref: '#/components/schemas/PciConfig' }
    PciConfig:
      type: object
      additionalProperties: false
      required: [provider, iframeUrl, vaultId, allowedOrigin]
      properties:
        provider: { type: string, enum: [mock, pciVault] }
        iframeUrl: { $ref: '#/components/schemas/AbsoluteRedirectUrl' }
        vaultId: { type: string, minLength: 1, maxLength: 255 }
        allowedOrigin:
          type: string
          format: uri
          maxLength: 255
          pattern: '^(https://[^/\s]+|http://(localhost|127\.0\.0\.1|\[::1\])(?::[0-9]{1,5})?)$'
          description: Exact postMessage origin; no path, query, fragment, wildcard, or credentials.
    CheckoutPayRequest:
      type: object
      additionalProperties: false
      required: [cardToken]
      minProperties: 1
      maxProperties: 1
      properties:
        cardToken:
          type: string
          minLength: 16
          maxLength: 2048
          pattern: '^[\x21-\x7E]+$'
          description: Opaque card token minted by the configured PCI Vault capture endpoint or hosted iframe.
          x-wegopay-opaque: true
          x-wegopay-secret: true
    CheckoutConfirmRequest:
      type: object
      additionalProperties: false
      maxProperties: 0
    CheckoutWalletRequest:
      type: object
      additionalProperties: false
      description: Payer details are optional for hosted card wallets. Omitted fields are resolved from the payment customer when available; supplied fields must be valid. An empty object can start checkout without payer details.
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
        email:
          type: string
          format: email
          minLength: 3
          maxLength: 320
        documentNumber:
          type: string
          pattern: '^[0-9]{11,14}$'
        phone:
          type: string
          pattern: '^[0-9]{10,15}$'
    WalletReadyResult:
      type: object
      additionalProperties: false
      required:
        - status
        - paymentId
        - checkoutUrl
        - expiresInSeconds
      properties:
        status: { type: string, enum: [walletReady] }
        paymentId: { type: string, format: uuid }
        checkoutUrl: { $ref: '#/components/schemas/AbsoluteRedirectUrl' }
        expiresInSeconds: { type: integer, minimum: 1, maximum: 3600 }
    PaidResult:
      type: object
      additionalProperties: false
      required: [status, paymentId]
      properties:
        status: { type: string, enum: [paid] }
        paymentId: { type: string, format: uuid }
    RequiresActionResult:
      type: object
      additionalProperties: false
      required: [status, paymentId, clientSecret, publishableKey]
      properties:
        status: { type: string, enum: [requiresAction] }
        paymentId: { type: string, format: uuid }
        clientSecret: { type: string, minLength: 1, maxLength: 2048, x-wegopay-secret: true }
        publishableKey: { type: string, minLength: 1, maxLength: 255 }
    FailedResult:
      type: object
      additionalProperties: false
      required: [status, paymentId, failureCode, message, attemptsLeft]
      properties:
        status: { type: string, enum: [failed] }
        paymentId: { type: string, format: uuid }
        failureCode: { type: string, minLength: 1, maxLength: 128 }
        message: { type: string, minLength: 1, maxLength: 500 }
        attemptsLeft: { type: integer, minimum: 0, maximum: 5 }
    CheckoutPaymentResult:
      oneOf:
        - { $ref: '#/components/schemas/PaidResult' }
        - { $ref: '#/components/schemas/RequiresActionResult' }
        - { $ref: '#/components/schemas/FailedResult' }
      discriminator:
        propertyName: status
        mapping:
          paid: '#/components/schemas/PaidResult'
          requiresAction: '#/components/schemas/RequiresActionResult'
          failed: '#/components/schemas/FailedResult'
    ThreeDSCheckoutActionData:
      type: object
      additionalProperties: false
      required: [kind, clientSecret, publishableKey]
      properties:
        kind: { type: string, enum: [threeDS] }
        clientSecret:
          type: string
          minLength: 1
          maxLength: 2048
          x-wegopay-secret: true
          description: Decrypted only for the current requiresAction fence so a checkout can resume after reload.
        publishableKey: { type: string, minLength: 1, maxLength: 255 }
    WalletCheckoutActionData:
      type: object
      additionalProperties: false
      required: [kind, checkoutUrl, expiresAt]
      properties:
        kind: { type: string, enum: [wallet] }
        checkoutUrl: { $ref: '#/components/schemas/AbsoluteRedirectUrl' }
        expiresAt: { type: string, format: date-time }
    CheckoutActionData:
      oneOf:
        - { $ref: '#/components/schemas/ThreeDSCheckoutActionData' }
        - { $ref: '#/components/schemas/WalletCheckoutActionData' }
      discriminator:
        propertyName: kind
        mapping:
          threeDS: '#/components/schemas/ThreeDSCheckoutActionData'
          wallet: '#/components/schemas/WalletCheckoutActionData'
    CheckoutStatusRead:
      type: object
      additionalProperties: false
      required: [paymentId, status, attemptsLeft, expiresAt, action]
      properties:
        paymentId: { type: string, format: uuid }
        status: { $ref: '#/components/schemas/CheckoutSessionStatus' }
        terminalReason: { $ref: '#/components/schemas/CheckoutTerminalReason' }
        attemptsLeft: { type: integer, minimum: 0, maximum: 5 }
        expiresAt: { type: string, format: date-time }
        action:
          allOf:
            - { $ref: '#/components/schemas/CheckoutActionData' }
          nullable: true
          description: Present only while status is requiresAction; null for every other status.
    PaymentCreatedEnvelope:
      type: object
      additionalProperties: false
      required: [data]
      properties:
        data: { $ref: '#/components/schemas/PaymentCreated' }
    PaymentEnvelope:
      type: object
      additionalProperties: false
      required: [data]
      properties:
        data: { $ref: '#/components/schemas/Payment' }
    PaymentListEnvelope:
      type: object
      additionalProperties: false
      required: [data, meta]
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/Payment' }
        meta: { $ref: '#/components/schemas/PaginationMeta' }
    MerchantPayRequest:
      type: object
      additionalProperties: false
      required: [cardToken, captureReference]
      properties:
        cardToken: { type: string, minLength: 16, maxLength: 4096, x-wegopay-opaque: true, x-wegopay-secret: true }
        captureReference: { type: string, minLength: 1, maxLength: 31, description: Must equal the server-issued payment capture reference and the vault capture result reference. }
    MerchantCaptureAccess:
      type: object
      additionalProperties: false
      required: [url, secret, reference, expiresAt]
      properties:
        url: { $ref: '#/components/schemas/AbsoluteRedirectUrl' }
        secret: { type: string, minLength: 16, maxLength: 4096, x-wegopay-secret: true, description: Write-only capture secret. Keep on your backend; never log. }
        reference: { type: string, minLength: 1, maxLength: 31 }
        expiresAt: { type: string, format: date-time }
    MerchantCheckoutSession:
      type: object
      additionalProperties: false
      required: [paymentId, amountCents, currency, reference, status, expiresAt, attemptsLeft, environment, pciConfig]
      properties:
        paymentId: { type: string, format: uuid }
        amountCents: { type: integer, format: int64, minimum: 500, maximum: 100000000 }
        currency: { $ref: '#/components/schemas/Currency' }
        reference: { type: string, minLength: 1, maxLength: 255 }
        status: { $ref: '#/components/schemas/CheckoutSessionStatus' }
        terminalReason: { $ref: '#/components/schemas/CheckoutTerminalReason' }
        expiresAt: { type: string, format: date-time }
        attemptsLeft: { type: integer, minimum: 0, maximum: 5 }
        environment: { $ref: '#/components/schemas/PaymentEnvironment' }
        actionUrl:
          allOf: [{ $ref: '#/components/schemas/AbsoluteRedirectUrl' }]
          description: Open in the customer browser only when payment requiresAction; existing checkout resumes the challenge without card collection.
        capture: { $ref: '#/components/schemas/MerchantCaptureAccess' }
        pciConfig:
          type: object
          additionalProperties: false
          required: [provider, captureUrl, vaultId, allowedOrigin]
          properties:
            provider: { type: string, enum: [pciVault] }
            captureUrl: { $ref: '#/components/schemas/AbsoluteRedirectUrl' }
            vaultId:
              type: string
              minLength: 1
              maxLength: 31
              description: Random payment-scoped capture reference; never your order reference. The server-owned dedicated vault key establishes payment isolation.
            allowedOrigin: { type: string, format: uri }
            iframeUrl:
              allOf: [{ $ref: '#/components/schemas/AbsoluteRedirectUrl' }]
              description: Present only with a hosted form configured for the merchant checkout origin. Direct capture credentials are returned by POST capture.
    MerchantCheckoutSessionEnvelope:
      type: object
      additionalProperties: false
      required: [data]
      properties:
        data: { $ref: '#/components/schemas/MerchantCheckoutSession' }
    CheckoutSessionEnvelope:
      type: object
      additionalProperties: false
      required: [data]
      properties:
        data: { $ref: '#/components/schemas/CheckoutSession' }
    CheckoutPaymentResultEnvelope:
      type: object
      additionalProperties: false
      required: [data]
      properties:
        data: { $ref: '#/components/schemas/CheckoutPaymentResult' }
    CheckoutWalletResultEnvelope:
      type: object
      additionalProperties: false
      required: [data]
      properties:
        data: { $ref: '#/components/schemas/WalletReadyResult' }
    CheckoutStatusEnvelope:
      type: object
      additionalProperties: false
      required: [data]
      properties:
        data: { $ref: '#/components/schemas/CheckoutStatusRead' }
    InvalidRequestError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/InvalidRequestErrorDetail' }
    InvalidRequestErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [invalid_request] }
        message: { type: string }
    InvalidQueryError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/InvalidQueryErrorDetail' }
    InvalidQueryErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [invalid_query] }
        message: { type: string }
    UnauthorizedError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/UnauthorizedErrorDetail' }
    UnauthorizedErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [unauthorized] }
        message: { type: string }
    NotFoundError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/NotFoundErrorDetail' }
    NotFoundErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [not_found] }
        message: { type: string }
    IdempotencyConflictError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/IdempotencyConflictErrorDetail' }
    IdempotencyConflictErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [idempotency_key_reused, idempotency_in_progress] }
        message: { type: string }
    CheckoutPayConflictError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/CheckoutPayConflictErrorDetail' }
    CheckoutPayConflictErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [payment_in_progress, action_required, result_pending] }
        message: { type: string }
    CheckoutConfirmConflictError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/CheckoutConfirmConflictErrorDetail' }
    CheckoutConfirmConflictErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [confirmation_not_required, confirmation_in_progress, result_pending] }
        message: { type: string }
    CheckoutGoneError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/CheckoutGoneErrorDetail' }
    CheckoutGoneErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [checkout_expired, attempts_exhausted] }
        message: { type: string }
    ValidationFailedError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/ValidationFailedErrorDetail' }
    ValidationFailedErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [validation_failed] }
        message: { type: string }
        field: { type: string }
    CardTokenRejectedError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/CardTokenRejectedErrorDetail' }
    CardTokenRejectedErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [card_token_rejected] }
        message: { type: string }
    RateLimitedError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/RateLimitedErrorDetail' }
    RateLimitedErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [rate_limited] }
        message: { type: string }
    ServiceUnavailableError:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: { $ref: '#/components/schemas/ServiceUnavailableErrorDetail' }
    ServiceUnavailableErrorDetail:
      type: object
      additionalProperties: false
      required: [code, message]
      properties:
        code: { type: string, enum: [service_unavailable] }
        message: { type: string }
  responses:
    RefundResponse:
      description: Merchant-safe refund request state; provider identifiers and credentials are never returned.
      headers:
        Cache-Control:
          schema: { type: string, enum: [no-store] }
      content:
        application/json:
          schema:
            type: object
            additionalProperties: false
            required: [data]
            properties:
              data: { $ref: '#/components/schemas/RefundView' }
    PaymentCreatedResponse:
      description: Payment and checkout session created, or exact idempotent replay.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/PaymentCreatedEnvelope' }
    PaymentDetail:
      description: Merchant-scoped payment.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/PaymentEnvelope' }
    PaymentList:
      description: Merchant-scoped payment page.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/PaymentListEnvelope' }
    MerchantCheckoutDetail:
      description: Merchant and environment scoped custom checkout state and capture coordinates.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/MerchantCheckoutSessionEnvelope' }
    CheckoutDetail:
      description: Checkout render state.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/CheckoutSessionEnvelope' }
    CheckoutPaymentOutcome:
      description: Paid, repeated customer action, or definitive retryable failure.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/CheckoutPaymentResultEnvelope' }
    CheckoutWalletOutcome:
      description: Fenced hosted-wallet redirect for this payment.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/CheckoutWalletResultEnvelope' }
    CheckoutStatus:
      description: Exact durable checkout status.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/CheckoutStatusEnvelope' }
    BadRequest:
      description: Malformed path, header, or strict JSON request.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/InvalidRequestError' }
          examples:
            invalidRequest: { value: { error: { code: invalid_request, message: Invalid request. } } }
    InvalidQuery:
      description: Invalid date, pagination, status, or from/to relation.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/InvalidQueryError' }
          examples:
            invalidQuery: { value: { error: { code: invalid_query, message: Invalid query. } } }
    ApiKeyUnauthorized:
      description: Missing, malformed, unknown, revoked, or disabled-merchant API key.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
        WWW-Authenticate: { $ref: '#/components/headers/ApiKeyChallenge' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/UnauthorizedError' }
          examples:
            unauthorized: { value: { error: { code: unauthorized, message: Authentication required. } } }
    NotFound:
      description: Unknown or unauthorized resource/token, without existence disclosure.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/NotFoundError' }
          examples:
            notFound: { value: { error: { code: not_found, message: Resource not found. } } }
    IdempotencyConflict:
      description: Idempotency key conflict or matching request still fenced.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/IdempotencyConflictError' }
          examples:
            reused: { value: { error: { code: idempotency_key_reused, message: Idempotency key was used with another request. } } }
            inProgress: { value: { error: { code: idempotency_in_progress, message: The original request is still processing. } } }
    CheckoutPayConflict:
      description: Existing checkout fence prevents another charge attempt.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/CheckoutPayConflictError' }
          examples:
            processing: { value: { error: { code: payment_in_progress, message: A payment attempt is processing. } } }
            action: { value: { error: { code: action_required, message: Confirm the current customer action. } } }
            fenced: { value: { error: { code: result_pending, message: The previous result is being reconciled. } } }
    CheckoutConfirmConflict:
      description: No confirmable action or existing confirmation/result fence.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/CheckoutConfirmConflictError' }
          examples:
            notRequired: { value: { error: { code: confirmation_not_required, message: No customer action is awaiting confirmation. } } }
            processing: { value: { error: { code: confirmation_in_progress, message: Confirmation is processing. } } }
            fenced: { value: { error: { code: result_pending, message: The previous result is being reconciled. } } }
    CheckoutGone:
      description: Checkout can no longer accept payment work.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/CheckoutGoneError' }
          examples:
            expired: { value: { error: { code: checkout_expired, message: Checkout expired. } } }
            exhausted: { value: { error: { code: attempts_exhausted, message: No payment attempts remain. } } }
    ValidationFailed:
      description: Semantically invalid payment creation field.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ValidationFailedError' }
          examples:
            validation: { value: { error: { code: validation_failed, message: Validation failed., field: successUrl } } }
    CardTokenRejected:
      description: The hosted-fields provider rejected or cannot use the opaque token.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/CardTokenRejectedError' }
          examples:
            rejected: { value: { error: { code: card_token_rejected, message: Payment details could not be tokenized. } } }
    RateLimited:
      description: Rate limit exceeded without starting payment work.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
        Retry-After: { $ref: '#/components/headers/RetryAfter' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/RateLimitedError' }
          examples:
            limited: { value: { error: { code: rate_limited, message: Too many requests. } } }
    ServiceUnavailable:
      description: Required persistence, encryption, hosted-fields, or payment processing dependency unavailable.
      headers:
        Cache-Control: { $ref: '#/components/headers/NoStore' }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ServiceUnavailableError' }
          examples:
            unavailable: { value: { error: { code: service_unavailable, message: Service unavailable. } } }
