openapi: 3.1.0
info:
  title: VPOS.am Public Merchant API
  version: 0.1.0
  summary: Public merchant API for payment sessions and status verification.
  license:
    name: Commercial integration terms apply
    url: https://www.vpos.am/en/company/legal/
  description: >
    Sanitized public contract for approved merchant integrations. This file
    intentionally excludes console/admin endpoints, internal provider
    credentials, private callbacks, payout data and operational tokens.
servers:
  - url: https://api.vpos.am
    description: Production API host
security:
  - merchantApiKey: []
paths:
  /api/health:
    get:
      summary: Read non-secret runtime health
      operationId: getApiHealth
      tags: [Health]
      security: []
      responses:
        "200":
          description: Service health and readiness metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
        "405":
          description: Unsupported method.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/capabilities:
    get:
      summary: Read provider capability matrix
      operationId: getCapabilities
      tags: [Payments]
      responses:
        "200":
          description: Conservative provider capability matrix for the merchant context.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CapabilitiesResponse"
        "401":
          description: Missing or invalid merchant bearer token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Request origin is not trusted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limited.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/payment-links:
    post:
      summary: Create an idempotent payment link
      operationId: createPaymentLink
      tags: [Payments]
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: Stable merchant-side key for retry-safe payment-link creation.
          schema:
            type: string
            maxLength: 160
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePaymentLinkRequest"
            examples:
              invoice:
                summary: Basic invoice payment link
                value:
                  amount: "15000"
                  currency: AMD
                  providerRoute: manual
                  description: Demo invoice payment
                  expiresAt: "2099-12-31T23:59:59.000Z"
                  metadata:
                    invoiceId: inv-1001
      responses:
        "201":
          description: Payment link created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentLinkResponse"
        "200":
          description: Existing payment link returned for an idempotent replay.
          headers:
            X-Idempotent-Replay:
              schema:
                type: string
              description: Present when the response reuses a prior operation result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentLinkResponse"
        "401":
          description: Missing or invalid merchant bearer token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Request origin is not trusted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Request validation or capability check failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
        "429":
          description: Rate limited.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Payment storage or provider route is not ready.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/payment-sessions:
    post:
      summary: Create a payment session
      operationId: createPaymentSession
      tags: [Payments]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePaymentSessionRequest"
            examples:
              basic:
                summary: Basic hosted checkout session
                value:
                  merchantOrderId: order-1001
                  amountMinor: 15000
                  currency: AMD
                  provider: manual
                  returnUrl: https://merchant.example/payments/return
                  customer:
                    email: buyer@example.com
                    phone: "+37400000000"
                    name: Test Buyer
                  metadata:
                    source: custom-backend
      responses:
        "201":
          description: Payment session created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentSession"
        "200":
          description: Existing payment session returned for an idempotent replay.
          headers:
            X-Idempotent-Replay:
              schema:
                type: string
              description: Present when the response reuses a prior operation result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentSession"
        "401":
          description: Missing or invalid merchant bearer token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Request origin is not trusted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Request validation failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
        "429":
          description: Rate limited.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Payment auth, provider or storage dependency is not ready.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/payments:
    get:
      summary: Read payment status
      operationId: getPayment
      tags: [Payments]
      parameters:
        - name: paymentId
          in: query
          required: true
          schema:
            type: string
            maxLength: 120
      responses:
        "200":
          description: Current payment session state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentSession"
        "401":
          description: Missing or invalid merchant bearer token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Request origin is not trusted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Payment session was not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: paymentId is missing or invalid.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limited.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Payment storage or auth dependency is not ready.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/payments/{paymentId}/refunds:
    post:
      summary: Request a guarded refund operation
      operationId: requestPaymentRefund
      tags: [Payment operations]
      parameters:
        - $ref: "#/components/parameters/PaymentId"
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRefundRequest"
      responses:
        "409":
          description: Refunds are not enabled for the provider route or payment is not eligible.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "422":
          description: Validation failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
        "501":
          description: Refund worker is not configured for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "503":
          description: Payment operation storage is not configured.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/payments/{paymentId}/capture:
    post:
      summary: Request a guarded capture operation
      operationId: requestPaymentCapture
      tags: [Payment operations]
      parameters:
        - $ref: "#/components/parameters/PaymentId"
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCaptureRequest"
      responses:
        "409":
          description: Capture is not enabled for the provider route or payment is not authorized.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "422":
          description: Validation failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
        "501":
          description: Capture worker is not configured for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "503":
          description: Payment operation storage is not configured.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/payments/{paymentId}/void:
    post:
      summary: Request a guarded void operation
      operationId: requestPaymentVoid
      tags: [Payment operations]
      parameters:
        - $ref: "#/components/parameters/PaymentId"
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateVoidRequest"
      responses:
        "409":
          description: Void is not enabled for the provider route or payment is not authorized.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "422":
          description: Validation failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
        "501":
          description: Void worker is not configured for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "503":
          description: Payment operation storage is not configured.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/fiscal-receipts/{paymentId}/retry:
    post:
      summary: Request a guarded fiscal receipt retry
      operationId: requestFiscalRetry
      tags: [Fiscalization]
      parameters:
        - $ref: "#/components/parameters/PaymentId"
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      responses:
        "409":
          description: Fiscalization is not enabled, receipt is already issued, or payment is not eligible.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "501":
          description: Fiscal retry worker is not configured for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "503":
          description: Payment operation storage is not configured.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/payment-method-tokens:
    post:
      summary: Request a guarded saved-card tokenization intent
      operationId: requestPaymentTokenizationIntent
      tags: [Tokenization]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateTokenizationIntentRequest"
      responses:
        "409":
          description: Tokenization is not enabled for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "422":
          description: Validation failed. Raw card data is always rejected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
        "501":
          description: Tokenization worker is not configured for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "503":
          description: Payment operation storage is not configured.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/payment-method-tokens/{tokenId}:
    delete:
      summary: Request guarded payment-token revocation
      operationId: requestPaymentTokenRevocation
      tags: [Tokenization]
      parameters:
        - name: tokenId
          in: path
          required: true
          schema:
            type: string
            maxLength: 160
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      responses:
        "409":
          description: Token revocation is not enabled for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "501":
          description: Token revocation worker is not configured for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "503":
          description: Payment operation storage is not configured.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/subscriptions:
    post:
      summary: Request guarded subscription creation
      operationId: requestSubscriptionCreate
      tags: [Subscriptions]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSubscriptionRequest"
      responses:
        "409":
          description: Subscriptions are not enabled for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "422":
          description: Validation failed. Raw card data is always rejected.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidationErrorResponse"
        "501":
          description: Subscription worker is not configured for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "503":
          description: Payment operation storage is not configured.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v1/subscriptions/{subscriptionId}/cancel:
    post:
      summary: Request guarded subscription cancellation
      operationId: requestSubscriptionCancel
      tags: [Subscriptions]
      parameters:
        - name: subscriptionId
          in: path
          required: true
          schema:
            type: string
            maxLength: 160
        - $ref: "#/components/parameters/IdempotencyKeyRequired"
      responses:
        "409":
          description: Subscription cancellation is not enabled for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "501":
          description: Subscription cancellation worker is not configured for the provider route.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperationErrorResponse"
        "503":
          description: Payment operation storage is not configured.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: Stable merchant-side key for retry-safe payment-session creation.
      schema:
        type: string
        maxLength: 160
    IdempotencyKeyRequired:
      name: Idempotency-Key
      in: header
      required: true
      description: Stable merchant-side operation key. Reuse it for retries; do not generate a new key for every retry.
      schema:
        type: string
        maxLength: 160
    PaymentId:
      name: paymentId
      in: path
      required: true
      schema:
        type: string
        maxLength: 120
  securitySchemes:
    merchantApiKey:
      type: http
      scheme: bearer
      bearerFormat: merchant-token
      description: Merchant bearer token issued after onboarding. Keep it server-side only.
  schemas:
    CreatePaymentSessionRequest:
      type: object
      required: [merchantOrderId, amountMinor, currency, returnUrl]
      additionalProperties: false
      properties:
        merchantOrderId:
          type: string
          maxLength: 120
          description: Stable merchant order reference.
        amountMinor:
          type: integer
          minimum: 1
          description: Amount in minor currency units.
        currency:
          $ref: "#/components/schemas/CurrencyCode"
        provider:
          $ref: "#/components/schemas/PaymentProvider"
        returnUrl:
          type: string
          format: uri
          maxLength: 300
          description: Absolute HTTP(S) URL where the buyer returns after checkout.
        customer:
          $ref: "#/components/schemas/Customer"
        metadata:
          type: object
          additionalProperties: true
          description: Merchant metadata for reconciliation. Do not place secrets here.
    CreatePaymentLinkRequest:
      type: object
      required: [amount, currency, description]
      additionalProperties: false
      properties:
        amount:
          oneOf:
            - type: string
            - type: number
          description: Major-unit amount. AMD must be a whole number; USD/EUR allow two decimals.
        amountMinor:
          type: integer
          minimum: 1
          description: Optional amount in minor currency units.
        currency:
          $ref: "#/components/schemas/CurrencyCode"
        description:
          type: string
          minLength: 2
          maxLength: 600
        expiresAt:
          type: string
          format: date-time
          description: Optional future expiry for the link. Expiry is not proof of payment outcome.
        providerRoute:
          $ref: "#/components/schemas/PaymentProvider"
        merchantOrderId:
          type: string
          maxLength: 120
          description: Optional merchant reference. If omitted, VPOS derives a stable reference from Idempotency-Key.
        returnUrl:
          type: string
          format: uri
          maxLength: 300
        customer:
          $ref: "#/components/schemas/Customer"
        metadata:
          type: object
          additionalProperties: true
          description: Merchant metadata for reconciliation. Do not place secrets here.
    CreateRefundRequest:
      type: object
      required: [amountMinor, reason]
      additionalProperties: false
      properties:
        amountMinor:
          type: integer
          minimum: 1
          description: Refund amount in minor currency units. Must not exceed the original payment amount.
        currency:
          $ref: "#/components/schemas/CurrencyCode"
        reason:
          type: string
          minLength: 1
          maxLength: 240
        metadata:
          type: object
          additionalProperties: true
          description: Non-secret merchant audit metadata.
    CreateCaptureRequest:
      type: object
      additionalProperties: false
      properties:
        amountMinor:
          type: integer
          minimum: 1
          description: Optional capture amount. If omitted, VPOS uses the authorized amount.
        currency:
          $ref: "#/components/schemas/CurrencyCode"
        reason:
          type: string
          maxLength: 240
        metadata:
          type: object
          additionalProperties: true
    CreateVoidRequest:
      type: object
      required: [reason]
      additionalProperties: false
      properties:
        reason:
          type: string
          minLength: 1
          maxLength: 240
        metadata:
          type: object
          additionalProperties: true
    CreateTokenizationIntentRequest:
      type: object
      required: [providerRoute, customerConsentId]
      additionalProperties: false
      description: Raw card data is never accepted. Use the provider-hosted tokenization flow when the route is enabled.
      properties:
        providerRoute:
          $ref: "#/components/schemas/PaymentProvider"
        customerConsentId:
          type: string
          maxLength: 160
          description: Merchant-side proof/reference for explicit customer consent.
        returnUrl:
          type: string
          format: uri
          maxLength: 300
        customer:
          $ref: "#/components/schemas/Customer"
        metadata:
          type: object
          additionalProperties: true
    CreateSubscriptionRequest:
      type: object
      required: [providerRoute, merchantSubscriptionId, paymentTokenId, amountMinor, currency, interval, description]
      additionalProperties: false
      description: Requires a provider token created outside WordPress/browser card handling.
      properties:
        providerRoute:
          $ref: "#/components/schemas/PaymentProvider"
        merchantSubscriptionId:
          type: string
          maxLength: 120
        paymentTokenId:
          type: string
          maxLength: 160
        amountMinor:
          type: integer
          minimum: 1
        currency:
          $ref: "#/components/schemas/CurrencyCode"
        interval:
          type: string
          enum: [day, week, month, quarter, year]
        description:
          type: string
          minLength: 1
          maxLength: 600
        metadata:
          type: object
          additionalProperties: true
    CapabilitiesResponse:
      type: object
      required: [ok, mode, defaultProvider, status, generatedAt, providers]
      properties:
        ok:
          type: boolean
          const: true
        mode:
          type: string
        defaultProvider:
          type: string
        status:
          type: string
        generatedAt:
          type: string
          format: date-time
        providers:
          type: array
          items:
            $ref: "#/components/schemas/ProviderCapability"
    ProviderCapability:
      type: object
      required: [id, label, currencies, liveCapable, supportsAutoinit, readiness, capabilities]
      properties:
        id:
          $ref: "#/components/schemas/PaymentProvider"
        label:
          type: string
        currencies:
          type: array
          items:
            $ref: "#/components/schemas/CurrencyCode"
        liveCapable:
          type: boolean
        supportsAutoinit:
          type: boolean
        readiness:
          type: object
          required: [enabledForPaymentCreation, reason, missingEnv]
          properties:
            enabledForPaymentCreation:
              type: boolean
            reason:
              type: string
            missingEnv:
              type: array
              items:
                type: string
        capabilities:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/CapabilityState"
    CapabilityState:
      type: object
      required: [supported, enabled]
      properties:
        supported:
          type: boolean
        enabled:
          type: boolean
        reason:
          type: string
        proofOfPayment:
          type: string
          enum: [webhook_or_reconciliation_only]
    PaymentLinkResponse:
      type: object
      required: [ok, created, linkId, paymentId, paymentUrl, checkoutUrl, status, payment]
      properties:
        ok:
          type: boolean
          const: true
        created:
          type: boolean
        linkId:
          type: string
        paymentId:
          type: string
        paymentUrl:
          type: string
          format: uri
        checkoutUrl:
          type: string
          format: uri
        providerCheckoutUrl:
          type: string
        expiresAt:
          type: string
        status:
          $ref: "#/components/schemas/PaymentStatus"
        qr:
          $ref: "#/components/schemas/QrPresentation"
        payment:
          $ref: "#/components/schemas/PaymentSession"
    QrPresentation:
      type: object
      description: QR presentation payload only; it is not a separate payment status signal.
      required: [type, value, proofOfPayment]
      properties:
        type:
          type: string
          enum: [url]
        value:
          type: string
          format: uri
        proofOfPayment:
          type: string
          enum: [webhook_or_reconciliation_only]
    PaymentSession:
      type: object
      required: [id, merchantOrderId, provider, amountMinor, currency, status, fiscalStatus]
      properties:
        ok:
          type: boolean
        id:
          type: string
          example: pay_01hxxexample
        merchantOrderId:
          type: string
          example: order-1001
        provider:
          $ref: "#/components/schemas/PaymentProvider"
        amountMinor:
          type: integer
          example: 15000
        currency:
          $ref: "#/components/schemas/CurrencyCode"
        status:
          $ref: "#/components/schemas/PaymentStatus"
        fiscalStatus:
          $ref: "#/components/schemas/FiscalStatus"
        checkoutUrl:
          type: string
          format: uri
        providerCheckoutUrl:
          type: string
          format: uri
        customer:
          $ref: "#/components/schemas/Customer"
        metadata:
          type: object
          additionalProperties: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    PaymentOperation:
      type: object
      required: [id, type, provider, status, createdAt]
      properties:
        id:
          type: string
          example: op_01hxxexample
        paymentSessionId:
          type: string
        type:
          type: string
          enum:
            - refund
            - capture
            - void
            - fiscal_retry
            - tokenization_intent
            - token_revoke
            - subscription_create
            - subscription_cancel
        provider:
          $ref: "#/components/schemas/PaymentProvider"
        providerOperationId:
          type: string
        status:
          type: string
          enum:
            - blocked
            - queued
            - processing
            - succeeded
            - failed
            - requires_provider_contract
            - requires_operator_review
        amountMinor:
          type: integer
          nullable: true
        currency:
          $ref: "#/components/schemas/CurrencyCode"
        statusReason:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    Customer:
      type: object
      additionalProperties: false
      properties:
        email:
          type: string
          format: email
          maxLength: 160
        phone:
          type: string
          maxLength: 80
        name:
          type: string
          maxLength: 120
    CurrencyCode:
      type: string
      enum: [AMD, USD, EUR]
    PaymentProvider:
      type: string
      description: Provider availability depends on onboarding and configured merchant access.
      enum: [arca_ipay, ameriabank_vpos, converse_hostx, idram, inecopay, manual]
    PaymentStatus:
      type: string
      enum:
        - created
        - pending_redirect
        - pending_payment
        - authorized
        - paid
        - failed
        - cancelled
        - reversed
        - partially_refunded
        - refunded
        - expired
        - status_unknown
        - disputed
    FiscalStatus:
      type: string
      enum:
        - not_required
        - pending
        - issued
        - failed
        - refunded
        - partially_refunded
        - correction_required
    HealthResponse:
      type: object
      required: [ok, ready, service, version, mode]
      properties:
        ok:
          type: boolean
          const: true
        ready:
          type: boolean
        service:
          type: string
          example: vpos-am
        version:
          type: string
          example: 0.1.0
        mode:
          type: string
          example: api-ready
      additionalProperties: true
    ErrorResponse:
      type: object
      required: [ok, error]
      properties:
        ok:
          type: boolean
          const: false
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
          additionalProperties: true
    ValidationErrorResponse:
      allOf:
        - $ref: "#/components/schemas/ErrorResponse"
        - type: object
          properties:
            errors:
              type: object
              additionalProperties:
                type: string
    OperationErrorResponse:
      allOf:
        - $ref: "#/components/schemas/ErrorResponse"
        - type: object
          properties:
            error:
              type: object
              properties:
                operation:
                  $ref: "#/components/schemas/PaymentOperation"
    PaymentWebhookEvent:
      type: object
      required: [id, type, createdAt, payment]
      properties:
        id:
          type: string
          example: evt_01hxxexample
        type:
          type: string
          enum: [payment.status_changed]
        createdAt:
          type: string
          format: date-time
        payment:
          $ref: "#/components/schemas/PaymentSession"
webhooks:
  paymentStatusChanged:
    post:
      summary: Payment lifecycle event delivered to merchant callback URL
      operationId: handlePaymentStatusChangedWebhook
      description: >
        The callback URL is configured during onboarding. Verify
        X-VPOS-Signature before changing order, CRM, ERP or fulfillment state.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentWebhookEvent"
      responses:
        "200":
          description: Event accepted.
