{
  "openapi": "3.1.0",
  "info": {
    "title": "Tranzak Payment Gateway API",
    "version": "1.0.0",
    "description": "Accept MonCash, NatCash and card payments. Read the integration guide at https://tranzak.co/ai before coding: it explains confirmation rules, webhooks and the sandbox. Amounts of transactions are returned as strings."
  },
  "servers": [
    {
      "url": "https://tranzak.co/api/gateway/v1"
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/payments": {
      "post": {
        "operationId": "createPayment",
        "summary": "Create a payment",
        "description": "Response fields are at the root (NOT wrapped in `data`). Redirect the customer to `payment_url` (not for `card`). Store `transaction_id` before redirecting. There is no idempotency key.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentCreated"
                }
              }
            }
          },
          "400": {
            "description": "amount_too_low | amount_too_high | payment_method_not_available | payment_processing_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "validation_error (see `errors`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing or invalid (`API key is required` / `Invalid API key`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key inactive, website inactive, or action not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests/minute per IP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listPayments",
        "summary": "List payments",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "processing",
                "completed",
                "failed"
              ]
            }
          },
          {
            "name": "payment_method",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentList"
                }
              }
            }
          },
          "401": {
            "description": "API key missing or invalid (`API key is required` / `Invalid API key`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key inactive, website inactive, or action not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests/minute per IP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/payments/reference/{reference}": {
      "get": {
        "operationId": "getPaymentByReference",
        "summary": "Find a payment by your own reference",
        "parameters": [
          {
            "name": "reference",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "transaction_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing or invalid (`API key is required` / `Invalid API key`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key inactive, website inactive, or action not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests/minute per IP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/payments/{transaction_id}": {
      "get": {
        "operationId": "getPayment",
        "summary": "Read a payment",
        "parameters": [
          {
            "name": "transaction_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Numeric string returned by POST /payments"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "transaction_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing or invalid (`API key is required` / `Invalid API key`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key inactive, website inactive, or action not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests/minute per IP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/payments/{transaction_id}/verify": {
      "post": {
        "operationId": "verifyPayment",
        "summary": "Ask the provider for the latest status",
        "description": "Use `data.status` only. `provider_response` uses provider-specific vocabulary — ignore its inner status.",
        "parameters": [
          {
            "name": "transaction_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Numeric string returned by POST /payments"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyResponse"
                }
              }
            }
          },
          "404": {
            "description": "transaction_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing or invalid (`API key is required` / `Invalid API key`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key inactive, website inactive, or action not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests/minute per IP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/events": {
      "get": {
        "operationId": "listSandboxEvents",
        "summary": "Sandbox events feed (tk_test_ keys only)",
        "description": "Finished test payments as payment.success / payment.failed events, oldest first. Poll with `after=<next_cursor>`. Live keys receive 403 events_test_only.",
        "parameters": [
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor returned as next_cursor by the previous call"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventList"
                }
              }
            }
          },
          "422": {
            "description": "invalid_cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing or invalid (`API key is required` / `Invalid API key`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key inactive, website inactive, or action not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests/minute per IP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/exchange-rate": {
      "get": {
        "operationId": "getExchangeRate",
        "summary": "HTG→USD rate used for card payments, and the card minimum",
        "parameters": [
          {
            "name": "amount",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0
            },
            "description": "Optional HTG amount to convert"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExchangeRate"
                }
              }
            }
          },
          "422": {
            "description": "validation_error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing or invalid (`API key is required` / `Invalid API key`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key inactive, website inactive, or action not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests/minute per IP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "health",
        "summary": "Service health",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "service": {
                      "type": "string"
                    },
                    "version": {
                      "type": "string"
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "API key missing or invalid (`API key is required` / `Invalid API key`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API key inactive, website inactive, or action not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (60 requests/minute per IP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "paymentEvent": {
      "post": {
        "summary": "Signed payment event sent to your website's callback_url",
        "description": "Verify `X-Tranzak-Signature` = hex(HMAC-SHA256(webhook secret, RAW request body)) in constant time before parsing. Respond 2xx quickly. Deduplicate on data.transaction_id + event. Retries reuse the original timestamp — do not reject old timestamps. The callback_url must be a public HTTPS URL; non-public destinations are refused and never retried.",
        "parameters": [
          {
            "name": "X-Tranzak-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Tranzak-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "payment.success",
                "payment.failed"
              ]
            }
          },
          {
            "name": "X-Tranzak-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "tk_test_… (sandbox) or tk_live_… (production). Keep it in an environment variable; never in code or the frontend."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "retry_after": {
            "type": "integer"
          }
        }
      },
      "CreatePaymentRequest": {
        "type": "object",
        "required": [
          "amount",
          "currency",
          "payment_method",
          "customer_name"
        ],
        "properties": {
          "amount": {
            "type": "number",
            "minimum": 1,
            "description": "Real currency unit (500 = 500 HTG). natcash: min 20 HTG. card: min 0.50 USD (see /exchange-rate card_minimum)."
          },
          "currency": {
            "type": "string",
            "enum": [
              "HTG",
              "USD"
            ],
            "description": "moncash and natcash are paid in HTG: a USD amount is converted to HTG at the Tranzak rate and the payment is created in HTG (see the conversion object in the response)."
          },
          "payment_method": {
            "type": "string",
            "enum": [
              "moncash",
              "natcash",
              "lakaypay_card",
              "card"
            ]
          },
          "customer_name": {
            "type": "string",
            "maxLength": 255
          },
          "customer_email": {
            "type": "string",
            "format": "email"
          },
          "customer_phone": {
            "type": "string",
            "maxLength": 20
          },
          "reference": {
            "type": "string",
            "maxLength": 255,
            "description": "Your order id. Not enforced unique."
          },
          "description": {
            "type": "string",
            "maxLength": 500
          },
          "metadata": {
            "type": "object"
          },
          "recurring": {
            "type": "boolean",
            "description": "lakaypay_card only"
          },
          "recurring_interval": {
            "type": "string",
            "enum": [
              "daily",
              "weekly",
              "monthly",
              "yearly"
            ]
          },
          "recurring_interval_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12
          }
        }
      },
      "PaymentCreated": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "transaction_id": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "description": "decimal string"
          },
          "currency": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "payment_url": {
            "type": "string",
            "format": "uri",
            "description": "Redirect the customer here (absent for card)"
          },
          "conversion": {
            "type": "object",
            "description": "Present only when a USD amount was converted to HTG (moncash, natcash). amount/currency above are then the converted HTG values.",
            "properties": {
              "original_amount": { "type": "number" },
              "original_currency": { "type": "string", "enum": ["USD"] },
              "exchange_rate": { "type": "number" },
              "charged_amount": { "type": "number" },
              "charged_currency": { "type": "string", "enum": ["HTG"] }
            }
          }
        },
        "additionalProperties": true
      },
      "Payment": {
        "type": "object",
        "properties": {
          "transaction_id": {
            "type": "string"
          },
          "amount": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "completed",
              "failed"
            ]
          },
          "payment_method": {
            "type": "string"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "failed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "failure_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "type": [
              "object",
              "null"
            ]
          }
        }
      },
      "PaymentEnvelope": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "data": {
            "$ref": "#/components/schemas/Payment"
          }
        }
      },
      "PaymentList": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Payment"
            }
          },
          "pagination": {
            "type": "object",
            "properties": {
              "current_page": {
                "type": "integer"
              },
              "per_page": {
                "type": "integer"
              },
              "total": {
                "type": "integer"
              },
              "last_page": {
                "type": "integer"
              }
            }
          }
        }
      },
      "VerifyResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "data": {
            "type": "object",
            "properties": {
              "transaction_id": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "verified_at": {
                "type": "string",
                "format": "date-time"
              },
              "provider_response": {
                "type": "object"
              }
            }
          }
        }
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "event": {
            "type": "string",
            "enum": [
              "payment.success",
              "payment.failed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "$ref": "#/components/schemas/Payment"
          }
        }
      },
      "EventList": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Event"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "has_more": {
            "type": "boolean"
          }
        }
      },
      "ExchangeRate": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "data": {
            "type": "object",
            "properties": {
              "base_currency": {
                "type": "string"
              },
              "quote_currency": {
                "type": "string"
              },
              "rate": {
                "type": "number"
              },
              "example": {
                "type": "string"
              },
              "conversion": {
                "type": [
                  "object",
                  "null"
                ]
              },
              "card_minimum": {
                "type": "object",
                "properties": {
                  "HTG": {
                    "type": "number"
                  },
                  "USD": {
                    "type": "number"
                  }
                }
              },
              "note": {
                "type": "string"
              },
              "updated_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "WebhookBody": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "payment.success",
              "payment.failed"
            ]
          },
          "timestamp": {
            "type": "integer"
          },
          "data": {
            "$ref": "#/components/schemas/Payment"
          }
        }
      }
    }
  }
}
