# Quickpay API — OpenAPI 3.0.3 (generated from app/Support/ApiReference.php)
openapi: "3.0.3"
info:
  title: "Quickpay API"
  version: "1.0"
  description: "The Quickpay REST API lets you create payments, manage products, issue refunds,\nissue invoices, and create shareable checkout links from any platform.\n\n**Base URL:** `https://api.quickpay.ge/v1`\n\n**Authentication:** Bearer token — use your live or test API key.\n`Authorization: Bearer qpk_live_...`\n\n**Idempotency:** Include `Idempotency-Key: {key}` on all POST requests to safely\nretry without double-charging.\n\n**Amounts:** Decimal numbers. Default currency GEL. Supported: GEL, USD, EUR, GBP.\n\n**Rate limit:** 100 requests/minute per API key (`X-RateLimit-Limit` /\n`X-RateLimit-Remaining` headers)."
  contact:
    name: "Quickpay Support"
    url: "https://quickpay.ge/contact"
  license:
    name: "Proprietary"
servers:
  - url: "https://api.quickpay.ge/v1"
    description: "Production"
security:
  - bearerAuth: []
tags:
  - name: "Payments"
  - name: "Products"
  - name: "Checkout Links"
  - name: "Invoices"
  - name: "Gateways"
  - name: "Checkout Templates"
  - name: "Exchange Rates"
  - name: "Recurring"
paths:
  "/payments":
    post:
      tags:
        - "Payments"
      operationId: "postPayments"
      summary: "Create a payment"
      description: "Creates a payment and returns a `payment_url` — redirect your customer there to complete checkout. Omit `gateway_slug` to let the customer choose their preferred gateway on the hosted payment page."
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                amount:
                  type: "number"
                  format: "float"
                  description: "Payment amount as a decimal (e.g. `49.99`). Min `0.01`, up to 2 decimal places."
                currency:
                  type: "string"
                  description: "One of `GEL`, `USD`, `EUR`, `GBP`. Defaults to `GEL`."
                description:
                  type: "string"
                  description: "Order description shown to the customer (max 500 chars)."
                gateway_slug:
                  type: "string"
                  description: "Pre-select a gateway. Omit to show all available gateways. See [List gateways](#gateways) and [What you can build](#capabilities) for valid slugs."
                merchant_order_id:
                  type: "string"
                  description: "Your own order reference — stored and returned on all responses (max 255 chars)."
                bank_transfer_reference:
                  type: "string"
                  description: "Custom reference shown to the customer on the Bank Transfer instructions (e.g. your own invoice number). Only applies to `bank_transfer` payments — falls back to a Quickpay-generated code when omitted (max 140 chars)."
                customer_name:
                  type: "string"
                  description: "Customer full name — pre-fills the checkout form (max 255 chars)."
                customer_email:
                  type: "string"
                  description: "Customer email — pre-fills the form and used for the receipt."
                customer_phone:
                  type: "string"
                  description: "Customer phone — pre-fills the checkout form."
                return_url:
                  type: "string"
                  description: "Redirect URL after checkout completes."
                webhook_url:
                  type: "string"
                  description: "Webhook endpoint for this payment only. Overrides the brand-level webhook URL configured in the dashboard. Useful for multi-tenant setups where each payment routes to a different endpoint."
                expires_at:
                  type: "string"
                  format: "date-time"
                  description: "ISO 8601 datetime after which the payment link expires. Must be in the future."
                coupon_code:
                  type: "string"
                  description: "Coupon code to apply — the discount is deducted from `amount` before charging (max 50 chars). **Only applied when `gateway_slug` is set**; silently ignored on gateway-less payments."
                product_id:
                  type: "integer"
                  description: "Internal product ID to attach to the payment (used for coupon product-scoping)."
                product_image_url:
                  type: "string"
                  description: "Product image URL shown on the checkout page."
                line_items:
                  type: "array"
                  items:
                    type: "object"
                  description: "Itemised breakdown. The sum of `line_items[].total` must equal `amount`."
                checkout_template_id:
                  type: "integer"
                  description: "Override the checkout page template for this payment. Use an `id` from [List checkout templates](#checkout-templates)."
                custom_fields_schema:
                  type: "array"
                  items:
                    type: "object"
                  description: "Extra form fields shown to the customer — see the schema below."
                prefill_only:
                  type: "boolean"
                  description: "When `true`, pre-fill customer info but still show the form for confirmation."
                source:
                  type: "string"
                  description: "Integration source tag: `woocommerce`, `whmcs`, `opencart`, `cscart`, `prestashop`, `edd`, or `api`."
                idempotency_key:
                  type: "string"
                  description: "Alternative to the `Idempotency-Key` header (max 255 chars)."
              required:
                - "amount"
      responses:
        "201":
          description: "201 Created (200 on idempotency replay)"
          content:
            "application/json":
              example:
                uuid: "018f2a3b-..."
                merchant_order_id: "ORD-1234"
                payment_url: "https://qpy.ge/pay/018f2a3b-..."
                status: "pending"
                amount: 149.99
                currency: "GEL"
                discount_gel: null
                coupon_code: null
                redirect_url: null
        default:
          "$ref": "#/components/responses/Error"
    get:
      tags:
        - "Payments"
      operationId: "getPayments"
      summary: "List payments"
      description: "Returns a paginated list (20 per page) sorted newest first."
      parameters:
        - name: "status"
          in: "query"
          required: false
          description: "Filter by status — see the status list above."
          schema:
            type: "string"
        - name: "gateway"
          in: "query"
          required: false
          description: "Filter by gateway slug (e.g. `bog_card`, `tbc_card`)."
          schema:
            type: "string"
        - name: "from"
          in: "query"
          required: false
          description: "Start date — `YYYY-MM-DD`."
          schema:
            type: "string"
            format: "date"
        - name: "to"
          in: "query"
          required: false
          description: "End date — `YYYY-MM-DD`. Must be ≥ `from`."
          schema:
            type: "string"
            format: "date"
        - name: "page"
          in: "query"
          required: false
          description: "Page number (default 1)."
          schema:
            type: "integer"
      responses:
        "200":
          description: "200 OK"
        default:
          "$ref": "#/components/responses/Error"
  "/payments/{uuid}":
    get:
      tags:
        - "Payments"
      operationId: "getPaymentsUuid"
      summary: "Get a payment"
      description: "Returns the full payment object."
      parameters:
        - name: "uuid"
          in: "path"
          required: true
          description: "Payment UUID."
          schema:
            type: "string"
      responses:
        "200":
          description: "200 OK"
          content:
            "application/json":
              example:
                uuid: "018f2a3b-..."
                merchant_order_id: "ORD-1234"
                status: "paid"
                amount: 149.99
                currency: "GEL"
                description: "Order #1234"
                customer_name: "Giorgi Beridze"
                customer_email: "giorgi@example.ge"
                gateway_order_id: "BOG-XYZ789"
                surcharge: null
                coupon: null
                form_data: []
                line_items: null
                refunded_amount: 0
                refunds: []
                paid_at: "2026-05-16T10:23:00Z"
                expires_at: null
                created_at: "2026-05-16T10:20:00Z"
        default:
          "$ref": "#/components/responses/Error"
  "/payments/{uuid}/refund":
    post:
      tags:
        - "Payments"
      operationId: "postPaymentsUuidRefund"
      summary: "Refund a payment"
      description: "Initiates a full or partial refund. Omit `amount` to refund the full remaining amount. Returns the updated payment object."
      parameters:
        - name: "uuid"
          in: "path"
          required: true
          description: "Payment UUID."
          schema:
            type: "string"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                amount:
                  type: "number"
                  format: "float"
                  description: "Amount to refund. Defaults to the full remaining refundable amount."
                reason:
                  type: "string"
                  description: "Internal reason for the refund — stored in the audit log (max 500 chars)."
      responses:
        "200":
          description: "200 OK"
        default:
          "$ref": "#/components/responses/Error"
  "/products":
    get:
      tags:
        - "Products"
      operationId: "getProducts"
      summary: "List products"
      description: "Returns a paginated list (20 per page) sorted newest first."
      parameters:
        - name: "active"
          in: "query"
          required: false
          description: "`true` or `false` — filter by active status."
          schema:
            type: "boolean"
        - name: "page"
          in: "query"
          required: false
          description: "Page number (default 1)."
          schema:
            type: "integer"
      responses:
        "200":
          description: "200 OK"
          content:
            "application/json":
              example:
                data:
                  - uuid: "018f..."
                    name_en: "Product Name"
                    name_ka: "პროდუქტის სახელი"
                    short_description_en: "..."
                    short_description_ka: "..."
                    price: 99.99
                    compare_price: 129.99
                    base_currency: "GEL"
                    accepted_currencies:
                      - "GEL"
                      - "USD"
                    sku: "SKU-001"
                    track_stock: false
                    stock_quantity: null
                    image_url: "https://..."
                    categories:
                      - "clothing"
                    custom_fields_schema: []
                    is_active: true
                    created_at: "2026-01-01T10:00:00Z"
                    updated_at: "2026-01-01T10:00:00Z"
                meta:
                  current_page: 1
                  last_page: 1
                  total: 3
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags:
        - "Products"
      operationId: "postProducts"
      summary: "Create a product"
      description: "At least one of `name_en` or `name_ka` is required."
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                name_en:
                  type: "string"
                  description: "Product name in English (required if `name_ka` is absent)."
                name_ka:
                  type: "string"
                  description: "Product name in Georgian (required if `name_en` is absent)."
                short_description_en:
                  type: "string"
                  description: "Short description in English."
                short_description_ka:
                  type: "string"
                  description: "Short description in Georgian."
                price:
                  type: "number"
                  format: "float"
                  description: "Sale price (min 0.01)."
                compare_price:
                  type: "number"
                  format: "float"
                  description: "Original/crossed-out price shown to highlight a discount."
                base_currency:
                  type: "string"
                  description: "Base pricing currency. One of `GEL`, `USD`, `EUR`, `GBP`. Defaults to `GEL`."
                accepted_currencies:
                  type: "array"
                  items:
                    type: "object"
                  description: "Currencies the customer may pay in (e.g. `[\"GEL\",\"USD\"]`)."
                sku:
                  type: "string"
                  description: "Stock keeping unit identifier (max 100 chars)."
                track_stock:
                  type: "boolean"
                  description: "Enable stock tracking. Defaults to `false`."
                stock_quantity:
                  type: "integer"
                  description: "Current stock level. Only used when `track_stock` is `true`."
                categories:
                  type: "array"
                  items:
                    type: "object"
                  description: "Array of category strings (e.g. `[\"clothing\",\"sale\"]`)."
                custom_fields_schema:
                  type: "array"
                  items:
                    type: "object"
                  description: "Extra checkout form fields — same structure as the payment `custom_fields_schema`."
                is_active:
                  type: "boolean"
                  description: "Whether the product is available for purchase. Defaults to `true`."
              required:
                - "price"
      responses:
        "201":
          description: "201 Created"
        default:
          "$ref": "#/components/responses/Error"
  "/products/{uuid}":
    get:
      tags:
        - "Products"
      operationId: "getProductsUuid"
      summary: "Get a product"
      description: "Returns the full product object."
      parameters:
        - name: "uuid"
          in: "path"
          required: true
          description: "Product UUID."
          schema:
            type: "string"
      responses:
        "200":
          description: "200 OK"
        default:
          "$ref": "#/components/responses/Error"
    put:
      tags:
        - "Products"
      operationId: "putProductsUuid"
      summary: "Update a product"
      description: "Partial update — include only the fields you want to change. Same field rules as create. Returns the full product object."
      parameters:
        - name: "uuid"
          in: "path"
          required: true
          description: "Product UUID."
          schema:
            type: "string"
      responses:
        "200":
          description: "200 OK"
        default:
          "$ref": "#/components/responses/Error"
    delete:
      tags:
        - "Products"
      operationId: "deleteProductsUuid"
      summary: "Delete a product"
      description: "Deletes the product."
      parameters:
        - name: "uuid"
          in: "path"
          required: true
          description: "Product UUID."
          schema:
            type: "string"
      responses:
        "200":
          description: "200 OK"
          content:
            "application/json":
              example:
                deleted: true
        default:
          "$ref": "#/components/responses/Error"
  "/checkout-links":
    post:
      tags:
        - "Checkout Links"
      operationId: "postCheckoutLinks"
      summary: "Create a checkout link"
      description: "Creates a reusable payment link. Provide either `product_uuid` (price comes from the product) or a fixed `amount`."
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                product_uuid:
                  type: "string"
                  description: "Attach an existing product — its price is used automatically. Required if `amount` is absent."
                amount:
                  type: "number"
                  format: "float"
                  description: "Fixed amount in the chosen currency. Required if `product_uuid` is absent."
                currency_override:
                  type: "string"
                  description: "Override the default currency: `GEL`, `USD`, `EUR`, or `GBP`."
                hide_currency_selector:
                  type: "boolean"
                  description: "Hide the currency selector on checkout. Defaults to `false`."
                expires_at:
                  type: "string"
                  format: "date-time"
                  description: "ISO 8601 datetime after which the link is no longer usable."
                max_uses:
                  type: "integer"
                  description: "Maximum number of times the link can be used (min 1)."
                allowed_gateways:
                  type: "array"
                  items:
                    type: "object"
                  description: "Restrict which gateway slugs are shown. Defaults to all active gateways."
                custom_fields_schema:
                  type: "array"
                  items:
                    type: "object"
                  description: "Extra checkout form fields (see note below)."
                checkout_settings:
                  type: "object"
                  description: "Checkout page behaviour overrides (same structure as Brand Checkout Settings)."
      responses:
        "201":
          description: "201 Created"
          content:
            "application/json":
              example:
                uuid: "018f..."
                url: "https://qpy.ge/p/abc123"
                slug: "abc123"
                product_uuid: null
                amount: 50
                currency: "GEL"
                expires_at: "2026-12-31T23:59:59+00:00"
                max_uses: 100
                is_active: true
                created_at: "2026-06-01T09:00:00+00:00"
        default:
          "$ref": "#/components/responses/Error"
  "/invoices":
    get:
      tags:
        - "Invoices"
      operationId: "getInvoices"
      summary: "List invoices"
      description: "Returns a paginated list (20 per page) sorted newest first."
      parameters:
        - name: "status"
          in: "query"
          required: false
          description: "One of `draft`, `sent`, `viewed`, `paid`, `cancelled`, `overdue`."
          schema:
            type: "string"
        - name: "customer_email"
          in: "query"
          required: false
          description: "Filter by exact customer email."
          schema:
            type: "string"
        - name: "from"
          in: "query"
          required: false
          description: "Start date — `YYYY-MM-DD`."
          schema:
            type: "string"
            format: "date"
        - name: "to"
          in: "query"
          required: false
          description: "End date — `YYYY-MM-DD`. Must be ≥ `from`."
          schema:
            type: "string"
            format: "date"
        - name: "page"
          in: "query"
          required: false
          description: "Page number (default 1)."
          schema:
            type: "integer"
      responses:
        "200":
          description: "200 OK"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags:
        - "Invoices"
      operationId: "postInvoices"
      summary: "Create an invoice"
      description: "Creates an invoice in `draft` status. Use [Send invoice](#send-invoice) to email it. The response includes a `payment_url` the customer can use to pay online."
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                customer_name:
                  type: "string"
                  description: "Customer full name (max 255 chars)."
                customer_email:
                  type: "string"
                  description: "Customer email address."
                customer_phone:
                  type: "string"
                  description: "Customer phone number."
                currency:
                  type: "string"
                  description: "One of `GEL`, `USD`, `EUR`, `GBP`. Defaults to `GEL`."
                due_at:
                  type: "string"
                  format: "date"
                  description: "Payment due date — `YYYY-MM-DD`. Must be after today."
                notes:
                  type: "string"
                  description: "Notes shown to the customer on the invoice (max 2000 chars)."
                terms:
                  type: "string"
                  description: "Terms & conditions shown on the invoice (max 2000 chars)."
                discount_type:
                  type: "string"
                  description: "`percent` or `fixed`."
                discount_value:
                  type: "number"
                  format: "float"
                  description: "Discount amount. Percent: 0–100. Fixed: deducted from subtotal."
                items:
                  type: "array"
                  items:
                    type: "object"
                  description: "Line items array — at least one item required."
              required:
                - "customer_name"
                - "customer_email"
                - "items"
      responses:
        "201":
          description: "201 Created"
          content:
            "application/json":
              example:
                uuid: "018f..."
                invoice_number: "INV-0001"
                status: "draft"
                currency: "GEL"
                subtotal: 515
                discount_type: "percent"
                discount_value: 10
                discount_amount: 51.5
                total: 463.5
                due_at: "2026-07-01"
                payment_url: "https://qpy.ge/invoice/018f..."
                items:
                  - description: "Web design — 10 hours"
                    quantity: 10
                    unit_price: 50
                    line_total: 500
                  - description: "Hosting (monthly)"
                    quantity: 1
                    unit_price: 15
                    line_total: 15
        default:
          "$ref": "#/components/responses/Error"
  "/invoices/{uuid}":
    get:
      tags:
        - "Invoices"
      operationId: "getInvoicesUuid"
      summary: "Get an invoice"
      description: "Returns the full invoice object including `payment_url`, `sent_at`, `viewed_at`, and `paid_at` timestamps."
      parameters:
        - name: "uuid"
          in: "path"
          required: true
          description: "Invoice UUID."
          schema:
            type: "string"
      responses:
        "200":
          description: "200 OK"
        default:
          "$ref": "#/components/responses/Error"
  "/invoices/{uuid}/send":
    post:
      tags:
        - "Invoices"
      operationId: "postInvoicesUuidSend"
      summary: "Send an invoice"
      description: "Emails the invoice to the customer and transitions status to `sent`. Cannot be called when status is `paid` or `cancelled`."
      parameters:
        - name: "uuid"
          in: "path"
          required: true
          description: "Invoice UUID."
          schema:
            type: "string"
      responses:
        "200":
          description: "200 OK"
          content:
            "application/json":
              example:
                status: "sent"
                sent_at: "2026-06-01T10:05:00+00:00"
                email_delivered: true
        default:
          "$ref": "#/components/responses/Error"
  "/gateways":
    get:
      tags:
        - "Gateways"
      operationId: "getGateways"
      summary: "List gateways"
      description: "Returns the active gateways for your brand — gateways with an active module subscription. Useful for populating a gateway selector in your own UI. Requires authentication but does **not** require a gateway license."
      responses:
        "200":
          description: "200 OK"
          content:
            "application/json":
              example:
                gateways:
                  - slug: "bog_card"
                    name: "BOG Card"
                    min_amount_gel: 0.5
                    max_amount_gel: 50000
                    brand_color: "#FF6600"
                    bg_color: "#FFF3E0"
                    icon_url: "https://cdn.quickpay.ge/gateways/bog.svg"
                    dark_icon_url: "https://cdn.quickpay.ge/gateways/bog-dark.svg"
        default:
          "$ref": "#/components/responses/Error"
  "/checkout-templates":
    get:
      tags:
        - "Checkout Templates"
      operationId: "getCheckoutTemplates"
      summary: "List checkout templates"
      description: "Returns the active checkout templates for your brand. Use the returned `id` as `checkout_template_id` when creating a payment. Requires authentication but does **not** require a gateway license."
      responses:
        "200":
          description: "200 OK"
          content:
            "application/json":
              example:
                templates:
                  - id: 1
                    uuid: "018f..."
                    name: "Default"
                    is_default: true
                    gateway_count: 3
                    fields_count: 2
        default:
          "$ref": "#/components/responses/Error"
  "/exchange-rates":
    get:
      tags:
        - "Exchange Rates"
      operationId: "getExchangeRates"
      summary: "Exchange rates"
      description: "**Public endpoint — no authentication required.** Converts an amount between two currencies using live rates."
      security: []
      parameters:
        - name: "from"
          in: "query"
          required: true
          description: "3-letter source currency code (e.g. `GEL`)."
          schema:
            type: "string"
        - name: "to"
          in: "query"
          required: true
          description: "3-letter target currency code (e.g. `USD`)."
          schema:
            type: "string"
        - name: "amount"
          in: "query"
          required: true
          description: "Amount to convert."
          schema:
            type: "number"
            format: "float"
      responses:
        "200":
          description: "200 OK"
          content:
            "application/json":
              example:
                from: "GEL"
                to: "USD"
                rate: 0.3676
                amount: 100
                converted: 36.76
        default:
          "$ref": "#/components/responses/Error"
  "/payments/charge":
    post:
      tags:
        - "Recurring"
      operationId: "postPaymentsCharge"
      summary: "Charge a subscription token"
      description: "Charges a stored subscription token server-to-server. The customer completed the first payment on the hosted page using a `*_subscriptions` slug, which tokenised their card with the bank."
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                subscription_token:
                  type: "string"
                  description: "Token returned in the `payment.paid` webhook after the first payment (UUID)."
                amount:
                  type: "number"
                  format: "float"
                  description: "Override amount for this charge. Defaults to the original subscription amount."
                description:
                  type: "string"
                  description: "Description for this charge (max 500 chars)."
              required:
                - "subscription_token"
      responses:
        "202":
          description: "202 Accepted"
          content:
            "application/json":
              example:
                uuid: "018f..."
                status: "pending"
                amount: 29.99
                currency: "GEL"
                subscription_token: "a1b2c3d4-..."
                message: "Charge initiated. Final result will be delivered via webhook."
        default:
          "$ref": "#/components/responses/Error"
components:
  securitySchemes:
    bearerAuth:
      type: "http"
      scheme: "bearer"
      description: "Brand-scoped API key: `qpk_live_...` (production) or `qpk_test_...` (test mode)."
  schemas:
    Error:
      type: "object"
      properties:
        error_code:
          type: "string"
          description: "Machine-readable error code — switch on this, not the message."
        message:
          type: "string"
          description: "Human-readable error message."
        errors:
          type: "object"
          description: "Field-keyed validation errors (422 only)."
          additionalProperties:
            type: "array"
            items:
              type: "string"
      required:
        - "error_code"
        - "message"
  responses:
    Error:
      description: "Error — see error_code"
      content:
        "application/json":
          schema:
            "$ref": "#/components/schemas/Error"
