{
  "openapi": "3.1.0",
  "info": {
    "title": "Shopify Bridge API",
    "version": "1.0.0",
    "summary": "Multi-tenant bridge that converts landing-page leads into Shopify draft orders (invoice format).",
    "description": "Self-hosted Express service. An operator registers one or more Shopify stores (via the admin UI + OAuth), then any number of landing pages can POST a customer + cart to /api/checkout with an X-Bridge-Key and receive back an invoice_url to show the buyer. Line items are rewritten to 'Invoice #NNNN' before they hit Shopify, so the draft order shows up as an invoice rather than named products.",
    "contact": { "name": "Bridge admin", "url": "https://shopify.anonbusiness.com/admin" },
    "license": { "name": "Private" }
  },
  "servers": [
    { "url": "https://shopify.anonbusiness.com", "description": "Production" }
  ],
  "tags": [
    { "name": "public", "description": "No auth. Safe to call from anywhere." },
    { "name": "checkout", "description": "Requires X-Bridge-Key. Called by landing pages." },
    { "name": "admin", "description": "Requires Bearer JWT from /api/admin/login. Called by the operator dashboard." },
    { "name": "oauth", "description": "Shopify install flow. Run once per store." }
  ],
  "security": [],
  "paths": {
    "/healthz": {
      "get": {
        "tags": ["public"],
        "summary": "Liveness probe",
        "description": "Zero-auth health endpoint. 200 means the service is up and the SQLite DB is reachable.",
        "responses": {
          "200": {
            "description": "Service healthy",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Health" },
                "example": { "ok": true, "service": "shopify-bridge" }
              }
            }
          }
        }
      }
    },
    "/api/admin/login": {
      "post": {
        "tags": ["admin"],
        "summary": "Exchange the admin password for a JWT",
        "description": "Returns a Bearer JWT valid for 24 hours. Include it as `Authorization: Bearer <token>` on every /api/admin/* call.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LoginRequest" },
              "example": { "password": "your-admin-password" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Login OK",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LoginResponse" },
                "example": { "token": "eyJhbGciOiJIUzI1NiIs..." }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/admin/stores": {
      "get": {
        "tags": ["admin"],
        "summary": "List registered stores",
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": {
            "description": "List of stores",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Store" } }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["admin"],
        "summary": "Register a new Shopify store",
        "description": "Status starts as 'pending'. Call /api/auth/shopify/{storeId} next to run OAuth; status flips to 'connected' once the access_token is saved.",
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/StoreCreate" },
              "example": {
                "name": "My Morocco Store",
                "domain": "my-shop.myshopify.com",
                "client_id": "abc123...",
                "client_secret": "shpss_..."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Store created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "integer" },
                    "message": { "type": "string" }
                  },
                  "required": ["id", "message"]
                },
                "example": { "id": 1, "message": "Store added successfully" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/api/admin/stores/{id}": {
      "delete": {
        "tags": ["admin"],
        "summary": "Delete a store",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "integer" }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "example": { "message": "Store deleted" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/auth/shopify/{storeId}": {
      "get": {
        "tags": ["oauth"],
        "summary": "Start Shopify OAuth install for a store",
        "description": "Redirects (302) the browser to Shopify's authorize URL with the correct scopes. After the merchant approves, Shopify sends them to /api/auth/shopify/callback.",
        "parameters": [
          {
            "name": "storeId",
            "in": "path",
            "required": true,
            "schema": { "type": "integer" }
          }
        ],
        "responses": {
          "302": { "description": "Redirect to Shopify authorize URL" },
          "404": { "description": "Store not found" }
        }
      }
    },
    "/api/auth/shopify/callback": {
      "get": {
        "tags": ["oauth"],
        "summary": "OAuth callback (called by Shopify)",
        "description": "Verifies the HMAC signature, cross-checks the shop domain, exchanges the code for an access_token, and marks the store as 'connected'. Not intended to be called manually.",
        "parameters": [
          { "name": "shop", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "code", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "state", "in": "query", "required": true, "schema": { "type": "string" }, "description": "The storeId this install maps to" },
          { "name": "hmac", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "timestamp", "in": "query", "required": false, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Connected — plain HTML confirmation" },
          "400": { "description": "Bad state / invalid HMAC / shop mismatch" },
          "500": { "description": "Token exchange failed" }
        }
      }
    },
    "/api/checkout": {
      "post": {
        "tags": ["checkout"],
        "summary": "Create a stealth Shopify draft order and return its invoice URL",
        "description": "The primary integration endpoint. Given a store_id, a customer, and a line-item cart, it creates a Shopify draft order with line-item titles rewritten to 'Invoice #NNNN' (stealth format) and returns the invoice_url the buyer should be redirected to for payment.\n\nIMPORTANT: prices in the `items` array are taken as-is and sent straight to Shopify. The bridge does not validate them against any catalog. Treat this endpoint as if it were a trusted internal API — never call it from browser JavaScript with the X-Bridge-Key exposed. Call it from a server-side controller on your landing-page backend.",
        "security": [{ "bridgeKey": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CheckoutRequest" },
              "example": {
                "store_id": 1,
                "customer": {
                  "firstName": "Mohamed",
                  "lastName": "Alaoui",
                  "email": "m.alaoui@example.com",
                  "address": "12 Rue des Orangers",
                  "city": "Casablanca",
                  "zip": "20000"
                },
                "items": [
                  { "price": 219, "quantity": 1 }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Draft order created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CheckoutResponse" },
                "example": {
                  "invoice_url": "https://my-shop.myshopify.com/12345/invoices/abcdef123"
                }
              }
            }
          },
          "401": { "description": "Missing or wrong X-Bridge-Key" },
          "404": { "description": "store_id not found or store is not yet connected via OAuth" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "JWT issued by /api/admin/login. Expires in 24h."
      },
      "bridgeKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Bridge-Key",
        "description": "Shared secret for /api/checkout. Value is in the bridge's .env (BRIDGE_API_KEY). Rotate by editing .env and restarting the service."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid credentials",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Unauthorized" }
          }
        }
      },
      "ServerError": {
        "description": "Unexpected error",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      }
    },
    "schemas": {
      "Health": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "service": { "type": "string" }
        },
        "required": ["ok", "service"]
      },
      "LoginRequest": {
        "type": "object",
        "properties": {
          "password": { "type": "string", "writeOnly": true }
        },
        "required": ["password"]
      },
      "LoginResponse": {
        "type": "object",
        "properties": {
          "token": { "type": "string", "description": "JWT, 24h lifetime" }
        },
        "required": ["token"]
      },
      "Store": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" },
          "domain": { "type": "string", "example": "my-shop.myshopify.com" },
          "client_id": { "type": "string" },
          "access_token": { "type": "string", "nullable": true, "description": "Present only after OAuth has completed" },
          "status": { "type": "string", "enum": ["pending", "connected"] },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "StoreCreate": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "domain": { "type": "string", "example": "my-shop.myshopify.com" },
          "client_id": { "type": "string" },
          "client_secret": { "type": "string", "writeOnly": true }
        },
        "required": ["name", "domain", "client_id", "client_secret"]
      },
      "Customer": {
        "type": "object",
        "properties": {
          "firstName": { "type": "string" },
          "lastName":  { "type": "string" },
          "email":     { "type": "string", "format": "email" },
          "address":   { "type": "string" },
          "city":      { "type": "string" },
          "zip":       { "type": "string" }
        },
        "required": ["firstName", "lastName", "email", "address", "city", "zip"]
      },
      "Item": {
        "type": "object",
        "description": "Cart line item. `title` is ignored — the bridge overwrites it with 'Invoice #NNNN'.",
        "properties": {
          "price":    { "type": "number", "description": "Unit price in the store's currency (sent to Shopify as-is)" },
          "quantity": { "type": "integer", "minimum": 1 }
        },
        "required": ["price", "quantity"]
      },
      "CheckoutRequest": {
        "type": "object",
        "properties": {
          "store_id": { "type": "integer", "description": "The bridge's internal store ID (from /api/admin/stores)" },
          "customer": { "$ref": "#/components/schemas/Customer" },
          "items":    { "type": "array", "minItems": 1, "items": { "$ref": "#/components/schemas/Item" } }
        },
        "required": ["store_id", "customer", "items"]
      },
      "CheckoutResponse": {
        "type": "object",
        "properties": {
          "invoice_url": { "type": "string", "format": "uri", "description": "Redirect the buyer here to complete payment" }
        },
        "required": ["invoice_url"]
      },
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string" } },
        "required": ["error"]
      }
    }
  }
}
