Fejlesztői dokumentáció

Vezoryx Connect fejlesztői útmutató

Pontos bekötési útmutató API kulcsokhoz, HMAC aláíráshoz, outbox mintához, event payloadokhoz, idempotenciához és hibakereséshez.

Ez az útmutató azt írja le, hogyan lehet külső webshopot, egyedi Flask webshopot, WooCommerce/Shopify hidat vagy POS/kasszarendszer wrappert csatlakoztatni a Vezoryx rendszerhez.

Ezt a dokumentumot akkor használd, ha rendeléseket, fizetéseket, refundokat, számlákat, készletváltozásokat vagy POS eladásokat szeretnél beküldeni a Vezoryxba találgatás nélkül.

1. Mit csinál a Vezoryx Connect?

A Vezoryx Connect külső üzleti eseményeket fogad, és az integrációs kulcs alapján a megfelelő tenant/cég alá írja be őket.

Támogatott integrációs irányok:

  • Egyedi Flask webshop
  • WooCommerce adapter wrapper
  • Shopify adapter wrapper
  • Generic POS/kasszarendszer wrapper
  • Egyedi külső üzleti rendszer

Fontos szabály: a webshop payloadjából érkező `tenant_id` vagy szervezeti azonosító nem lehet jogosultsági forrás. A Vezoryx mindig az integrációs kulcs alapján dönti el, melyik céghez tartozik az event.

2. Beállítás a Vezoryx adminban

  1. Lépj be tenant adminként.
  2. Nyisd meg az `/admin/integrations` oldalt.
  3. Hozz létre egy új integrációt.
  4. Válassz provider nevet:
  • `custom_flask` egyedi webshophoz
  • `woocommerce` WooCommerce wrapperhez
  • `shopify` Shopify wrapperhez
  • `pos_generic` POS/kasszarendszer wrapperhez
  1. Másold ki az egyszer megjelenő secretet.

A létrehozás után ezt kapod:

  • `integration_key`: publikus integrációs azonosító, például `vx_int_...`
  • `integration_secret`: aláíráshoz használt titkos kulcs, például `vx_sec_...`

A secretet csak a külső rendszer secret store-jában vagy környezeti változóban tárold. Ne commitold, és ne írd logba.

Ajánlott környezeti változók a webshopban:

VEZORYX_API_BASE_URL=https://www.vezoryx.com
VEZORYX_INTEGRATION_KEY=vx_int_...
VEZORYX_INTEGRATION_SECRET=vx_sec_...

Webshopos termékadatoknál az `sku` egy konkrét eladható méret/variáns egyedi technikai kódja (például `TB-01-GREEN-38`), míg a `model_code` az azonos modell összes színét és méretét összefogó rövid közös kód (például `TB-01`). A `product_type` és `gender` kanonikus értékei például `shoe`/`clothing`, illetve `women`/`men`/`unisex`/`kids`.

3. Endpointok

Alap API endpointok:

POST /api/integrations/events
GET  /api/integrations/products
POST /api/integrations/stock/reserve
POST /api/integrations/stock/release
POST /api/integrations/stock/decrease
GET  /api/integrations/sync/status

Fő event endpoint:

POST https://www.vezoryx.com/api/integrations/events

Minden event requestet alá kell írni.

4. Authentikációs headerek

Kötelező headerek:

Content-Type: application/json
X-Vezoryx-Integration: vx_int_...
X-Vezoryx-Timestamp: 2026-06-01T10:00:00Z
X-Vezoryx-Signature: sha256=...
Idempotency-Key: order-10042-created

Az aláírandó üzenet:

timestamp + "." + raw_json_body

Aláírási algoritmus:

HMAC-SHA256(integration_secret, message)

Az óraeltérés korlátozva van. Az `X-Vezoryx-Timestamp` értékét mindig request küldéskor, UTC idővel generáld.

5. Ajánlott webshop flow

Ne hívd a Vezoryxot közvetlenül a checkout tranzakció közepén. Használj outbox mintát.

Ajánlott folyamat:

  1. A vásárló leadja a rendelést.
  2. A webshop elmenti a rendelést a saját adatbázisába.
  3. Ugyanabban az adatbázis tranzakcióban létrehoz egy pending outbox eventet.
  4. Egy háttér worker elküldi a pending outbox eventeket a Vezoryxnak.
  5. Ha a Vezoryx átmenetileg nem elérhető, az outbox újrapróbálkozik.
  6. Retry esetén ugyanazt az `event_id` értéket kell használni.

Ez megakadályozza az elveszett rendeléseket, és nem lassítja vagy kockáztatja a checkoutot.

6. Python SDK használat

Client inicializálása:

import os

from vezoryx_connect import VezoryxClient

vezoryx = VezoryxClient(
    api_base_url=os.environ["VEZORYX_API_BASE_URL"],
    integration_key=os.environ["VEZORYX_INTEGRATION_KEY"],
    integration_secret=os.environ["VEZORYX_INTEGRATION_SECRET"],
)

Klasszikus webshop rendelés küldése:

vezoryx.send_order_created(
    event_id=f"order-{order.id}-created",
    external_order_id=order.order_number,
    currency="HUF",
    customer={
        "external_customer_id": str(order.customer_id),
        "email": order.customer_email,
        "name": order.customer_name,
    },
    items=[
        {
            "sku": item.sku,
            "name": item.name,
            "quantity": item.quantity,
            "gross_unit_price": item.gross_price,
        }
        for item in order.items
    ],
)

POS-style sale event küldése egyedi webshopból:

from vezoryx_connect.adapters import VezoryxCustomWebshopAdapter

adapter = VezoryxCustomWebshopAdapter(
    provider_location_id="webshop",
    provider_terminal_id="checkout",
)

event = adapter.order_created(
    {
        "external_order_id": order.order_number,
        "currency": "HUF",
        "gross_total": order.total_gross,
        "customer": {
            "email": order.customer_email,
            "name": order.customer_name,
        },
        "items": [
            {
                "sku": item.sku,
                "name": item.name,
                "quantity": item.quantity,
                "gross_unit_price": item.gross_price,
            }
            for item in order.items
        ],
    }
)

vezoryx.send_raw_event(event)

7. Outbox minta

Hozz létre egy táblát a webshop adatbázisában:

CREATE TABLE integration_outbox (
    id INTEGER PRIMARY KEY,
    event_id TEXT UNIQUE NOT NULL,
    event_type TEXT NOT NULL,
    payload TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    retry_count INTEGER NOT NULL DEFAULT 0,
    last_error TEXT,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    sent_at TIMESTAMP
);

SQLAlchemy jellegű model példa:

class IntegrationOutbox(db.Model):
    __tablename__ = "integration_outbox"

    id = db.Column(db.Integer, primary_key=True)
    event_id = db.Column(db.String(255), unique=True, nullable=False)
    event_type = db.Column(db.String(120), nullable=False)
    payload = db.Column(db.JSON, nullable=False)
    status = db.Column(db.String(30), nullable=False, default="pending")
    retry_count = db.Column(db.Integer, nullable=False, default=0)
    last_error = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    sent_at = db.Column(db.DateTime)

Event sorba állítása rendelés létrehozása után:

from vezoryx_connect.outbox import enqueue_event

enqueue_event(
    db.session,
    IntegrationOutbox,
    "order.created",
    f"order-{order.id}-created",
    {
        "external_order_id": order.order_number,
        "currency": "HUF",
        "customer": {
            "email": order.customer_email,
            "name": order.customer_name,
        },
        "items": [
            {
                "sku": item.sku,
                "name": item.name,
                "quantity": item.quantity,
                "gross_unit_price": item.gross_price,
            }
            for item in order.items
        ],
    },
)
db.session.commit()

Pending eventek feldolgozása ütemezett workerből:

from vezoryx_connect.outbox import process_pending_outbox

result = process_pending_outbox(
    db.session,
    IntegrationOutbox,
    vezoryx,
    limit=50,
    max_attempts=5,
)

print(result.sent, result.failed)

8. Event típusok

Általános webshop eventek:

  • `product.created`
  • `product.updated`
  • `product.deleted`
  • `customer.created`
  • `customer.updated`
  • `order.created`
  • `order.paid`
  • `order.cancelled`
  • `order.refunded`
  • `order.fulfilled`
  • `inventory.reserved`
  • `inventory.released`
  • `inventory.decreased`
  • `inventory.adjusted`

POS/canonical commerce eventek:

  • `sale.created`
  • `payment.created`
  • `refund.created`
  • `daily_closing.created`
  • `invoice.created`

Új integrációknál akkor érdemes a POS/canonical commerce eventeket használni, ha a forrásrendszer külön kezeli a rendelés/eladás, fizetés, refund és számla eseményeket.

9. `order.created` payload

Kötelező:

  • `event_id`
  • `event_type`
  • `external_order_id`
  • `currency`
  • `items[]`

Ajánlott:

  • `occurred_at`
  • `source`
  • `customer`

Példa:

{
  "event_type": "order.created",
  "event_id": "order-10042-created",
  "occurred_at": "2026-06-01T10:00:00Z",
  "source": "custom_flask_webshop",
  "external_order_id": "ORDER-10042",
  "currency": "HUF",
  "customer": {
    "external_customer_id": "CUST-582",
    "email": "customer@example.com",
    "name": "Teszt Vasarlo"
  },
  "items": [
    {
      "sku": "SKU-1",
      "name": "Termek",
      "quantity": 2,
      "gross_unit_price": 4500
    }
  ]
}

Eredmény a Vezoryxban:

  • létrehozza vagy frissíti a partnert a `customer` alapján
  • létrehoz egy belső rendelést
  • összeköti az `external_order_id` értéket a belső rendeléssel
  • létrehozza a rendelési tételeket
  • kiszámolja a rendelés összegét
  • elmenti az eventet az `integration_events` táblába

10. Canonical POS payload

A POS/canonical eventek szigorúbb sémát használnak.

Kötelező közös mezők:

  • `event_id`
  • `event_type`
  • `provider`
  • `provider_location_id`
  • `provider_terminal_id`
  • `occurred_at`
  • `currency`
  • `gross_total`

Ajánlott közös mezők:

  • `net_total`
  • `vat_total`
  • `metadata`

`sale.created` esetén kötelező:

  • `external_sale_id`
  • `items[]`

`payment.created` esetén kötelező:

  • `external_sale_id`
  • `external_payment_id`

`refund.created` esetén kötelező:

  • `external_sale_id`
  • `external_refund_id`

`invoice.created` esetén kötelező:

  • `external_sale_id`
  • `external_invoice_id`

`daily_closing.created` esetén kötelező:

  • `external_closing_id`
  • `business_date`

Példa `sale.created`:

{
  "event_type": "sale.created",
  "event_id": "pos-sale-SALE-100",
  "occurred_at": "2026-06-01T10:00:00Z",
  "source": "pos_generic",
  "provider": "pos_generic",
  "provider_location_id": "STORE-1",
  "provider_terminal_id": "TERM-1",
  "external_sale_id": "SALE-100",
  "external_order_id": "SALE-100",
  "receipt_number": "R-100",
  "currency": "HUF",
  "gross_total": 9000,
  "net_total": 7086.61,
  "vat_total": 1913.39,
  "customer": {
    "email": "buyer@example.com",
    "name": "Buyer Name"
  },
  "items": [
    {
      "sku": "SKU-1",
      "name": "Termek",
      "quantity": 2,
      "gross_unit_price": 4500,
      "vat_rate": 27
    }
  ],
  "payments": [
    {
      "external_payment_id": "PAY-100",
      "method": "card",
      "amount": 9000,
      "currency": "HUF"
    }
  ],
  "metadata": {
    "cashier_id": "CASHIER-1"
  }
}

Példa `payment.created`:

{
  "event_type": "payment.created",
  "event_id": "pos-payment-PAY-100",
  "occurred_at": "2026-06-01T10:01:00Z",
  "provider": "pos_generic",
  "provider_location_id": "STORE-1",
  "provider_terminal_id": "TERM-1",
  "external_sale_id": "SALE-100",
  "external_payment_id": "PAY-100",
  "currency": "HUF",
  "gross_total": 9000,
  "method": "card"
}

Példa `refund.created`:

{
  "event_type": "refund.created",
  "event_id": "pos-refund-REF-100",
  "occurred_at": "2026-06-01T10:10:00Z",
  "provider": "pos_generic",
  "provider_location_id": "STORE-1",
  "provider_terminal_id": "TERM-1",
  "external_sale_id": "SALE-100",
  "external_refund_id": "REF-100",
  "currency": "HUF",
  "gross_total": 9000,
  "reason": "customer_return"
}

Példa `daily_closing.created`:

{
  "event_type": "daily_closing.created",
  "event_id": "pos-closing-Z-20260601-1",
  "occurred_at": "2026-06-01T22:00:00Z",
  "provider": "pos_generic",
  "provider_location_id": "STORE-1",
  "provider_terminal_id": "TERM-1",
  "external_closing_id": "Z-20260601-1",
  "business_date": "2026-06-01",
  "currency": "HUF",
  "gross_total": 30000,
  "payment_totals": {
    "card": 22000,
    "cash": 8000
  },
  "tax_totals": {
    "27": 6377.95
  },
  "sales": [
    {"external_sale_id": "SALE-100", "gross_total": 9000},
    {"external_sale_id": "SALE-101", "gross_total": 21000}
  ]
}

Ha a `sales[]` meg van adva, a Vezoryx ellenőrzi, hogy az eladások bruttó összege megegyezik-e a `gross_total` értékkel.

11. Idempotencia szabályok

Minden eventhez kell:

  • stabil `event_id`
  • `Idempotency-Key` header

Az SDK automatikusan az `event_id` értékét teszi az `Idempotency-Key` headerbe.

POS/canonical eventeknél a Vezoryx duplikáció ellen véd:

  • azonos `event_id`
  • azonos `event_type` + külső referencia alapján

Példák:

  • ugyanaz a `sale.created` + ugyanaz az `external_sale_id` nem hoz létre második rendelést
  • ugyanaz a `refund.created` + ugyanaz az `external_refund_id` nem futtatja újra a refund logikát
  • ugyanannak a requestnek az újraküldése feldolgozás után `duplicate: true` választ ad

Használj determinisztikus event ID-kat:

order-{order_id}-created
payment-{payment_id}-created
refund-{refund_id}-created
invoice-{invoice_id}-created
closing-{business_date}-{terminal_id}

Retry esetén ne generálj új random UUID-t ugyanarra az üzleti eseményre.

12. Manuális HMAC request SDK nélkül

Ezt akkor használd, ha a külső rendszer nem Python alapú.

Python referencia implementáció:

import hashlib
import hmac
import json
from datetime import datetime, timezone
from urllib.request import Request, urlopen

api_base_url = "https://www.vezoryx.com"
integration_key = "vx_int_..."
integration_secret = "vx_sec_..."

event = {
    "event_type": "order.created",
    "event_id": "order-10042-created",
    "external_order_id": "ORDER-10042",
    "currency": "HUF",
    "items": [{"sku": "SKU-1", "quantity": 1, "gross_unit_price": 4500}],
}

body = json.dumps(event, separators=(",", ":"), sort_keys=True).encode("utf-8")
timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
message = timestamp.encode("utf-8") + b"." + body
signature = hmac.new(integration_secret.encode("utf-8"), message, hashlib.sha256).hexdigest()

request = Request(
    f"{api_base_url}/api/integrations/events",
    data=body,
    headers={
        "Content-Type": "application/json",
        "X-Vezoryx-Integration": integration_key,
        "X-Vezoryx-Timestamp": timestamp,
        "X-Vezoryx-Signature": f"sha256={signature}",
        "Idempotency-Key": event["event_id"],
    },
    method="POST",
)

with urlopen(request, timeout=10) as response:
    print(response.read().decode("utf-8"))

Sikeres válasz:

{
  "received": true,
  "duplicate": false,
  "event_id": "order-10042-created",
  "result": {
    "order_id": 123
  }
}

Duplikált event válasz:

{
  "received": true,
  "duplicate": true,
  "event_id": "order-10042-created"
}

13. Termék és készlet szinkron

Vezoryx termékek lekérése:

GET /api/integrations/products?webshop_only=1&limit=200

A lapozáshoz a válasz `next_cursor` értékét add vissza `after_id` query paraméterként. A webshopos termékek egy eladható SKU-t jelentenek; a `model_code`, `product_type`, `gender`, `color_name`, `color_code`, `color_hex`, `size_label`, `size_system` és `barcode` mezők alapján a webshop modell–szín–méret hierarchiát építhet.

Készlet foglalása checkoutkor:

POST /api/integrations/stock/reserve
{
  "event_id": "stock-order-42-reserve",
  "reservation_id": "order-42",
  "items": [
    {
      "reservation_id": "order-42-item-101",
      "sku": "MODEL-1-BLACK-38",
      "quantity": 1
    }
  ]
}

A foglalás a teljes `items` listát egy tranzakcióban ellenőrzi és csökkenti. Elégtelen készletnél `422` választ ad, és egyik tételt sem módosítja. Megszakított fizetésnél ugyanazokat a tételeket a `/api/integrations/stock/release` végponton kell visszaengedni, külön stabil event ID-val.

Készlet csökkentése:

vezoryx.decrease_inventory(
    event_id="stock-order-10042-item-1",
    sku="SKU-1",
    quantity=1,
)

Nyers event megfelelője:

{
  "event_type": "inventory.decreased",
  "event_id": "stock-order-10042-item-1",
  "sku": "SKU-1",
  "quantity": 1
}

Készlet eventet csak akkor küldj, ha eldöntötted, melyik rendszer a készlet forrása. Ha nem a webshop a készlet source of truth, küldj inkább rendelés/eladás eventeket, és hagyd, hogy a Vezoryx kezelje a belső készletmozgást.

14. WooCommerce wrapper

A WooCommerce adapter a WooCommerce REST/webhook payloadokat Vezoryx eventekké alakítja.

from vezoryx_connect import VezoryxClient
from vezoryx_connect.adapters import woocommerce_order_to_event

event = woocommerce_order_to_event(woocommerce_order, event_type="order.created")
vezoryx.send_raw_event(event)

Ajánlott WooCommerce wrapper flow:

  1. Fogadd a WooCommerce webhookot.
  2. Ellenőrizd a WooCommerce webhook hitelességét a wrapperben.
  3. Alakítsd át a payloadot `woocommerce_order_to_event` segítségével.
  4. Küldd be a Vezoryxba `send_raw_event` hívással.
  5. Csak enqueue vagy sikeres küldés után válaszolj `200` státusszal a WooCommerce felé.

15. Shopify wrapper

Shopify esetén előbb ellenőrizd a Shopify HMAC aláírást, és csak utána továbbítsd az eventet.

from vezoryx_connect.adapters import shopify_order_to_event, verify_shopify_hmac

if not verify_shopify_hmac(raw_body, request.headers["X-Shopify-Hmac-Sha256"], shopify_shared_secret):
    abort(401)

event = shopify_order_to_event(shopify_order, event_type="order.created")
vezoryx.send_raw_event(event)

16. Mock POS adapter

A `MockPOSAdapter` helyi demókhoz, automatizált tesztekhez és UI fejlesztéshez használható, mielőtt valódi POS provider elérhető lenne.

from vezoryx_connect.adapters import MockPOSAdapter

mock_pos = MockPOSAdapter(
    provider_location_id="STORE-1",
    provider_terminal_id="TERM-1",
)

event = mock_pos.create_sale(
    id="SALE-100",
    currency="HUF",
    items=[
        {"sku": "SKU-1", "quantity": 1, "gross_unit_price": 4500},
    ],
)

vezoryx.send_raw_event(event)

A mock adapter ugyanazt az event contractot használja, mint a későbbi Laurel, Micra, HioPOS vagy custom POS adapterek.

17. Státusz és hibakeresés

Integrációs státusz lekérése:

GET /api/integrations/sync/status

Debug táblák a Vezoryxban:

  • `integration_events`: minden Connect API event
  • `pos_events`: canonical POS eventek raw és normalized payloadokkal

Ha egy ügyfél azt jelzi, hogy valami nem érkezett be, ezt nézd meg:

  1. Beérkezett az event?
  2. Érvényes volt a HMAC?
  3. A payload elfogadásra vagy elutasításra került?
  4. Az event státusza `processed`, `failed` vagy duplicate?
  5. Mi van a `raw_payload` mezőben?
  6. Mi van a `normalized_payload` mezőben?
  7. Össze lett kötve belső rendeléssel?

18. Hiba válaszok

Gyakori válaszok:

400 unsupported event_type
400 event_id is required
400 Idempotency-Key header is required
400 invalid json payload
400 currency must be a 3-letter ISO code
400 items[0].quantity must not be negative
401 missing authentication headers
401 integration not found or inactive
401 invalid timestamp
401 stale timestamp
401 invalid signature
413 payload too large
422 refund references an unknown external sale
500 event processing failed

Retry szabály:

  • Network hibát retry-olj.
  • `500` hibát retry-olj.
  • `400` hibát ne retry-olj addig, amíg a payload nincs javítva.
  • `401` hibát ne retry-olj addig, amíg a credential/aláírás nincs javítva.
  • `422` hibát kézzel vizsgálj, mert általában hiányzó vagy rossz sorrendben érkező üzleti adatot jelez.

19. Fejlesztői go-live checklist

Élesítés előtt:

  • Integráció létrehozva az `/admin/integrations` oldalon
  • Secret nem kódban van tárolva
  • A webshop determinisztikus `event_id` értéket használ
  • `Idempotency-Key` header küldve van
  • Outbox tábla létezik
  • Checkout egy tranzakcióban menti a rendelést és az outbox eventet
  • Background worker feldolgozza a pending outbox eventeket
  • Failed outbox eventek láthatók az üzemeltetőknek
  • Termék SKU-k egyeznek a webshop és Vezoryx között
  • Egy teszt rendelés pontosan egy Vezoryx rendelést hoz létre
  • Ugyanannak az eventnek az újraküldése nem hoz létre duplikált rendelést
  • Payment event meglévő sale/order mellé mapelődik
  • Refund event meglévő sale/order mellé mapelődik
  • Hibás payload elutasításra és naplózásra kerül
  • Az `integration_events` és `pos_events` debughoz használható adatokat tartalmaz

20. Minimális end-to-end teszt

  1. Hozz létre egy teszt integrációt a Vezoryxban.
  2. Add hozzá a credentialöket a webshop `.env` fájljához.
  3. Hozz létre egy teszt terméket a Vezoryxban `SKU-1` SKU-val.
  4. Küldj be egy `order.created` vagy `sale.created` eventet.
  5. Ellenőrizd, hogy létrejött-e a Vezoryx rendelés.
  6. Küldd be pontosan ugyanazt az eventet újra.
  7. Ellenőrizd, hogy a válasz `duplicate: true`.
  8. Ellenőrizd, hogy továbbra is csak egy belső rendelés létezik.
  9. Küldj payment eventet ugyanarra a külső order/sale azonosítóra.
  10. Ellenőrizd, hogy az event `processed` státuszba került.

21. Biztonsági megjegyzések

  • Csak HTTPS-t használj.
  • Secret kitettség esetén azonnal rotáld az integrációs secretet.
  • Soha ne logold az `integration_secret` értékét.
  • Ne bízz külső payloadból érkező tenant ID-ban.
  • Tartsd a payload méretét a beállított API limit alatt.
  • Synchronous checkout függőség helyett outbox retry-t használj.
  • A POS provider credentialöket tartsd külön a Vezoryx credentialöktől.