{
  "openapi": "3.1.0",
  "info": {
    "title": "Payment Hub Project API",
    "version": "1.0.0",
    "description": "Server-to-server API for project-scoped payment orders. Payment Hub does not own project users or entitlements.",
    "license": {
      "name": "Private service contract"
    }
  },
  "servers": [
    {
      "url": "https://payments.null2null.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Orders",
      "description": "Create and reconcile project payment orders"
    },
    {
      "name": "Operations",
      "description": "Unauthenticated service health"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": ["Operations"],
        "operationId": "getPaymentHubHealth",
        "summary": "Check service and database health",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                },
                "example": {
                  "ok": true,
                  "service": "payment-service"
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders": {
      "post": {
        "tags": ["Orders"],
        "operationId": "createPaymentOrder",
        "summary": "Create an idempotent payment order",
        "description": "Call only from a project backend. Product, price, currency, payment methods, provider account, return URL, and allowed channels are resolved from server-side configuration.",
        "security": [
          {
            "ProjectApiKey": []
          }
        ],
        "x-required-scope": "orders:write",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrderRequest"
              },
              "example": {
                "productId": "premium_monthly",
                "merchantOrderId": "project-order-0001",
                "customerRef": "opaque-customer-0001",
                "distributionChannel": "web"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Order created or the matching idempotent order returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/v1/orders/{orderId}": {
      "get": {
        "tags": ["Orders"],
        "operationId": "getPaymentOrder",
        "summary": "Get an order by Payment Hub ID",
        "security": [
          {
            "ProjectApiKey": []
          }
        ],
        "x-required-scope": "orders:read",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "description": "Payment Hub order UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/Refresh"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/OrderResponse"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/orders/by-merchant-order/{merchantOrderId}": {
      "get": {
        "tags": ["Orders"],
        "operationId": "getPaymentOrderByMerchantOrderId",
        "summary": "Get an order by the project merchant order ID",
        "security": [
          {
            "ProjectApiKey": []
          }
        ],
        "x-required-scope": "orders:read",
        "parameters": [
          {
            "name": "merchantOrderId",
            "in": "path",
            "required": true,
            "description": "Project-generated merchant order reference",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
            }
          },
          {
            "$ref": "#/components/parameters/Refresh"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/OrderResponse"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "webhooks": {
    "paymentEvent": {
      "post": {
        "operationId": "receivePaymentEvent",
        "summary": "Receive a signed Payment Hub event",
        "description": "This request is sent by Payment Hub to the HTTPS webhook URL configured for a project. Verify the signature over the exact raw body before parsing JSON.",
        "parameters": [
          {
            "$ref": "#/components/parameters/PaymentEventId"
          },
          {
            "$ref": "#/components/parameters/PaymentTimestamp"
          },
          {
            "$ref": "#/components/parameters/PaymentSignature"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event durably accepted and processed, or durably queued for processing"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ProjectApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "pay_live_...",
        "description": "Project-scoped server-only API key"
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Stable key for this logical create attempt. Reuse it for network retries of the same request.",
        "schema": {
          "type": "string",
          "minLength": 16,
          "maxLength": 80,
          "pattern": "^[A-Za-z0-9_-]{16,80}$"
        }
      },
      "Refresh": {
        "name": "refresh",
        "in": "query",
        "required": false,
        "description": "Refresh eligible orders from the authenticated provider API when needed",
        "schema": {
          "type": "boolean",
          "default": true
        }
      },
      "PaymentEventId": {
        "name": "X-Payment-Event-Id",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "PaymentTimestamp": {
        "name": "X-Payment-Timestamp",
        "in": "header",
        "required": true,
        "description": "Unix timestamp in seconds",
        "schema": {
          "type": "integer",
          "format": "int64"
        }
      },
      "PaymentSignature": {
        "name": "X-Payment-Signature",
        "in": "header",
        "required": true,
        "description": "v1= followed by lowercase hexadecimal HMAC-SHA256 of timestamp.eventId.rawBody",
        "schema": {
          "type": "string",
          "pattern": "^v1=[a-f0-9]{64}$"
        }
      }
    },
    "responses": {
      "OrderResponse": {
        "description": "Payment order",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OrderEnvelope"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Request validation failed",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Bearer authentication is missing or invalid",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "Forbidden": {
        "description": "API key scope or checkout channel is not allowed",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "NotFound": {
        "description": "The project-scoped resource was not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "Conflict": {
        "description": "Idempotency key or merchant order conflicts with an existing order",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Project request limit exceeded",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "BadGateway": {
        "description": "Provider request or response could not be authenticated",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "Unavailable": {
        "description": "The configured provider route is unavailable",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      }
    },
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "required": ["ok", "service"],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "service": {
            "type": "string",
            "const": "payment-service"
          }
        }
      },
      "CreateOrderRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": ["productId", "merchantOrderId", "customerRef", "distributionChannel"],
        "properties": {
          "productId": {
            "type": "string",
            "minLength": 2,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]{1,63}$"
          },
          "merchantOrderId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
          },
          "customerRef": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"
          },
          "distributionChannel": {
            "type": "string",
            "minLength": 2,
            "maxLength": 40,
            "pattern": "^[a-z][a-z0-9_]{1,39}$"
          }
        }
      },
      "OrderEnvelope": {
        "type": "object",
        "additionalProperties": false,
        "required": ["order"],
        "properties": {
          "order": {
            "$ref": "#/components/schemas/PaymentOrder"
          }
        }
      },
      "PaymentOrder": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "merchantOrderId",
          "customerRef",
          "productId",
          "provider",
          "state",
          "stateVersion",
          "priceAmount",
          "priceCurrency",
          "paymentMethodIdsDigest",
          "paymentExpiresAt",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "merchantOrderId": {
            "type": "string"
          },
          "customerRef": {
            "type": "string"
          },
          "productId": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "providerPaymentId": {
            "type": ["string", "null"]
          },
          "state": {
            "$ref": "#/components/schemas/OrderState"
          },
          "stateVersion": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          },
          "priceAmount": {
            "type": "number"
          },
          "priceCurrency": {
            "type": "string"
          },
          "paymentMethodIdsDigest": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$"
          },
          "payAmount": {
            "type": ["number", "null"]
          },
          "payCurrency": {
            "type": ["string", "null"]
          },
          "checkoutUrl": {
            "type": ["string", "null"],
            "format": "uri"
          },
          "paymentExpiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "paidAt": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "refundedAt": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "OrderState": {
        "type": "string",
        "enum": [
          "creating",
          "waiting",
          "partially_paid",
          "processing",
          "succeeded",
          "failed",
          "expired",
          "refunded"
        ]
      },
      "PaymentEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": ["eventId", "type", "createdAt", "projectId", "data"],
        "properties": {
          "eventId": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "enum": [
              "payment.succeeded",
              "payment.refunded",
              "payment.failed",
              "payment.expired"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "projectId": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": ["order"],
            "properties": {
              "order": {
                "$ref": "#/components/schemas/PaymentOrder"
              }
            }
          }
        }
      },
      "ApiError": {
        "type": "object",
        "additionalProperties": false,
        "required": ["timestamp", "status", "code", "message", "path"],
        "properties": {
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "path": {
            "type": "string"
          }
        }
      }
    }
  },
  "x-integration-rules": [
    "Project API keys are server-only",
    "Create a local merchant order first",
    "Use a stable Idempotency-Key for retries",
    "Verify signed webhooks from the exact raw body",
    "Grant entitlement only for succeeded",
    "Make success and refund processing idempotent",
    "Reconcile non-terminal orders through the authenticated API",
    "Do not expose external checkout in App Store or Google Play builds"
  ]
}
