@restomenum/plugin-sdk

Restomenum eklenti platformu için resmi TypeScript SDK'sı. Webhook imza doğrulama, OAuth token exchange, tipli Callback API istemcisi, event/scope katalogu ve tipler — tek pakette. Node 20+ (global fetch), sıfır runtime bağımlılığı (yalnız node:crypto).

Kurulum

npm install @restomenum/plugin-sdk

SDK; manuel imza/HTTP yazımını ortadan kaldırır ve OAuth Connect, token exchange, webhook ve Data API sözleşmelerini tipli olarak sunar.

Webhook imza doğrulama

İmza ham gövde üzerinden doğrulanır (JSON.parse edilmiş değil), ±5 dk replay penceresi, timing-safe. Geçersizse 401 dön.

import { verifyWebhookSignature } from '@restomenum/plugin-sdk';

// Express (ham gövdeyi sakla: app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf } })))
const ok = verifyWebhookSignature(req.rawBody.toString('utf8'), req.headers['x-restomenum-signature'], webhookSecret);
if (!ok) return res.status(401).json({ error: 'invalid_signature' });

Hazır Express middleware — imza + şekil doğrulamayı otomatik yapar:

expressWebhook
import { expressWebhook } from '@restomenum/plugin-sdk';

app.post('/webhook', express.json({ verify: (req, _r, buf) => { (req as any).rawBody = buf; } }),
  expressWebhook(
    { getSecret: (tenantId) => store.webhookSecretFor(tenantId) },   // senkron/async
    async (envelope) => {
      // imza + şekil doğrulandı; envelope = { id, type, version, environment, tenantId, occurredAt, data }
      if (envelope.type === 'table.created') await handle(envelope.data);
      // yanıt yazmazsan otomatik 200 { ok:true }
    },
  ),
);

Ayrıntı: İmza Şeması (HMAC) · /webhook Alıcı.

OAuth token exchange

import { exchangeCode } from '@restomenum/plugin-sdk';

const cred = await exchangeCode(
  { code, clientId, clientSecret },
  { environment: 'sandbox' }, // veya { baseUrl }
);
// cred = { tenantId, apiKey, webhookSecret, scopes }  → tenant başına SAKLA

cred tenant başına saklanır (apiKey + webhookSecret). Akış: OAuth Connect → Token Exchange.

Callback API istemcisi (tipli)

import { RestomenumClient } from '@restomenum/plugin-sdk';

const client = new RestomenumClient({ apiKey: cred.apiKey, environment: 'production' });

const packets = await client.packets.open();
const packet  = await client.packets.get('packetId');
const products = await client.products.list();          // { data, truncated?, total? }
const customer = await client.customers.get('custId');  // PII consent kuralı sunucuda uygulanır

// Müşteri listesi — cursor pagination (offset YOK):
for await (const c of client.customers.listAll(200)) {
  // tüm müşteriler, sayfa sayfa otomatik
}

Kaynak grupları: packets, tables, products, categories, paymentMethods, ingredients, users, customers. Tümü: Data API.

Hata yönetimi

REST hata kodları ApiError'a çevrilir; OAuth hataları OAuthError; imza SignatureError.

import { ApiError } from '@restomenum/plugin-sdk';
try {
  await client.packets.get('yok');
} catch (e) {
  if (e instanceof ApiError) {
    console.log(e.status, e.code);          // 404, "plugin.packets.notFound"
    if (e.status === 429) wait(e.retryAfterSec); // Retry-After (sn)
  }
}

Action (buton/hook) yanıtı

import { actionResponse } from '@restomenum/plugin-sdk';
res.json(actionResponse(true, 'İşlem tamam', { level: 'success', display: 'toast' }));

Sözleşme: Action Ucu · Aksiyon Butonları.

Satış sırası (sequence)

Teslim sıralı değildir: zarftaki sequence + sequenceScope ile bayat (geç gelen eski) snapshot'ı ele. Defterini (tenantId, sequenceScope) ile anahtarla ve terminal olaydan sonra ≥ 7 gün sakla.

import { sequenceKey, sequenceVerdict, advanceSequenceCursor, isSaleSequenceEvent } from '@restomenum/plugin-sdk';

if (isSaleSequenceEvent(envelope.type)) {
  const key = sequenceKey(envelope.tenantId, envelope);        // (tenantId, sequenceScope) — TEK SLOT YASAK
  const cursor = key ? await ledger.get(key) : null;
  switch (sequenceVerdict(envelope, cursor)) {
    case 'stale':    return;                                    // daha küçük numara → bayat
    case 'terminal': return;                                    // satış sonlanmış; *.updated onu diriltmez
    default: break;                                             // 'apply' | 'unsequenced' → işle
  }
  if (key) await ledger.set(key, advanceSequenceCursor(cursor, envelope));   // ≥7 gün sakla
}

Beş kuralın tamamı ve hat (scope) değişim tablosu: Olay sırası.

Sürüm: sequence / sequenceScope okumak için ≥ 3.0.0 gerekir. Daha eski sürümlerde parseEnvelope zarfı sabit bir alan listesinden kuruyordu ve bu alanlar (ayrıca actor / origin) düşüyordu. 3.0.0'dan itibaren eşleyici tanımadığı üst-seviye alanları taşır — platform zarfa yeni bir alan eklediğinde yeni SDK sürümü beklemezsin (envelope['yeniAlan'] → unknown). Aynı kural parseCapabilityRequest için de geçerlidir.

Tutarlar (amounts)

amounts = satır tutarının tamsayı minor unit bileşen kırılımı; para birimi belge kökünde (currency + amountExponent). ⚠️ Üs yalnız amounts.* içindir — lineTotalDecimal/total/paid ondalıktır.

import { minorToDecimal, sumLineAmounts, vatBreakdown } from '@restomenum/plugin-sdk';

const { currency, amountExponent, orders } = envelope.data;      // ör. "TRY", 2
const totals = sumLineAmounts(orders);                            // null → ondalık `total`'a geri düş
if (totals && amountExponent !== undefined) {
  console.log(minorToDecimal(totals.gross, amountExponent), currency);   // 129 TRY
}
const buckets = vatBreakdown(orders);   // [{ rate: 19, gross, vat, net }, …] — yüksek oran önce

Garantiler, karışık KDV kovaları (perVat) ve geri düşüş kuralları: Tutarlar (amounts).

Katalog & tipler

import { EVENT_TYPES, SCOPES, PII_SCOPES, isPiiScope, isEventType } from '@restomenum/plugin-sdk';
import type { EventType, Scope, WebhookEnvelope, Customer, Packet, ActionRequest } from '@restomenum/plugin-sdk';

Geçerli değerler: Event Kataloğu · Scope Referansı.

Manifest'i AI ile oluşturmak/düzenlemek mi istersin? Remote MCP ile Claude'a bağlan, eklentini sohbetle yönet.