Bağlayıcı Örnek Kodları▾
Anlık Bağlantı

Bağlayıcı Örnek Kodları

Node.js, C# ve Python ile bağlayıcı servis iskeleti: imza doğrulaması ve dört uç.

Son güncelleme: 21 Eylül 2026 · Sözleşme sürümü 1.0.0

Üç dilde aynı servis: imza doğrulaması ve dört uç. ERP sorgunuzu işaretli yerlere koyun. Sır MUTABAKATAI_CONNECTOR_SECRET ortam değişkeninden okunur; kodda tutmayın. Servisi yayınladıktan sonra panelde "Bağlantıyı test et" ile doğrulayın.

Node.js (Express) — imza doğrulama + 4 uç▾
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const SECRET = process.env.MUTABAKATAI_CONNECTOR_SECRET; // panelde girilen sır
const app = express();

app.use((req, res, next) => {
  const ts = String(req.header("x-mutabakatai-timestamp") ?? "");
  const sig = String(req.header("x-mutabakatai-signature") ?? "");
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.status(401).json({ error: "stale" });
  const expected = createHmac("sha256", SECRET).update(`${ts}\n${req.method}\n${req.originalUrl}`).digest("hex");
  const ok = expected.length === sig.length && timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
  if (!ok) return res.status(401).json({ error: "invalid_signature" });
  next();
});

app.get("/mutabakatai/ping", (_req, res) => res.json({ ok: true, erp: "SAP S/4HANA", company: "1000 OneWorks Digital", version: "1.0" }));

app.get("/mutabakatai/counterparties", async (req, res) => {
  const rows = await erp.searchVendors(String(req.query.q ?? ""), Number(req.query.limit ?? 50)); // LFA1/KNA1
  res.json({ data: rows.map((r) => ({ external_id: r.lifnr, name: r.name1, tax_number: r.stcd2, email: r.smtp_addr })) });
});

app.get("/mutabakatai/statements", async (req, res) => {
  const { counterparty_external_id, period_start, period_end, currency = "TRY" } = req.query; // FBL1N/FBL5N eşdeğeri; currency = personelin seçimi
  // Sayfalama: page_size kadar satır + kaldığı yeri kodlayan next_cursor (burada satır konumu; son sayfada null).
  const pageSize = Math.min(Number(req.query.page_size ?? 20000), 20000);
  const offset = req.query.cursor ? Number(Buffer.from(String(req.query.cursor), "base64url")) : 0;
  const items = await erp.lineItems(String(counterparty_external_id), String(period_start), String(period_end), String(currency), { offset, limit: pageSize + 1 });
  const page = items.slice(0, pageSize);
  const nextCursor = items.length > pageSize ? Buffer.from(String(offset + pageSize)).toString("base64url") : null;
  res.json({ data: { currency: String(currency), rows: page.map((i) => ({
    reference_no: i.belnr, date: i.budat, description: i.sgtxt,
    amount: i.shkzg === "S" ? i.wrbtr : -i.wrbtr,
  })), next_cursor: nextCursor } });
});

app.get("/mutabakatai/balances", async (req, res) => {
  const currency = String(req.query.currency ?? "TRY"); // personelin seçimi
  const items = await erp.openBalances(String(req.query.period_date), currency);
  res.json({ data: { currency, items: items.map((b) => ({
    counterparty: { external_id: b.lifnr, name: b.name1, tax_number: b.stcd2, email: b.smtp_addr },
    amount: Math.abs(b.balance).toFixed(2), direction: b.balance >= 0 ? "debit" : "credit",
  })) } });
});

app.listen(8443);
C# (ASP.NET Core minimal API) — imza doğrulama▾
var secret = Environment.GetEnvironmentVariable("MUTABAKATAI_CONNECTOR_SECRET")!;
var app = WebApplication.CreateBuilder(args).Build();

app.Use(async (ctx, next) =>
{
    var ts = ctx.Request.Headers["X-MutabakatAI-Timestamp"].ToString();
    var sig = ctx.Request.Headers["X-MutabakatAI-Signature"].ToString();
    if (!long.TryParse(ts, out var t) || Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > 300)
    { ctx.Response.StatusCode = 401; return; }
    var payload = $"{ts}\n{ctx.Request.Method}\n{ctx.Request.Path}{ctx.Request.QueryString}";
    using var hmac = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(secret));
    var expected = Convert.ToHexString(hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payload))).ToLowerInvariant();
    if (!System.Security.Cryptography.CryptographicOperations.FixedTimeEquals(
            System.Text.Encoding.UTF8.GetBytes(expected), System.Text.Encoding.UTF8.GetBytes(sig)))
    { ctx.Response.StatusCode = 401; return; }
    await next();
});

app.MapGet("/mutabakatai/ping", () => new { ok = true, erp = "SAP ECC", company = "1000 OneWorks Digital", version = "1.0" });
app.MapGet("/mutabakatai/counterparties", (string? q, int? limit) => new { data = Erp.SearchVendors(q, limit ?? 50) });
app.MapGet("/mutabakatai/statements", (string counterparty_external_id, string period_start, string period_end, string? currency, int? page_size, string? cursor) =>
{
    // Sayfalama: page_size kadar satır + kaldığı yeri kodlayan next_cursor (son sayfada null).
    var size = Math.Min(page_size ?? 20000, 20000);
    var offset = string.IsNullOrEmpty(cursor) ? 0 : int.Parse(cursor);
    var items = Erp.LineItems(counterparty_external_id, period_start, period_end, currency ?? "TRY", offset, size + 1);
    var page = items.Take(size).ToList();
    string? next = items.Count > size ? (offset + size).ToString() : null;
    return new { data = new { currency = currency ?? "TRY", rows = page, next_cursor = next } };
});
app.MapGet("/mutabakatai/balances", (string period_date, string? currency) =>
    new { data = new { currency = currency ?? "TRY", items = Erp.OpenBalances(period_date, currency ?? "TRY") } });
app.Run();
Python (FastAPI) — imza doğrulama▾
import hmac, hashlib, os, time
from fastapi import FastAPI, Request, HTTPException

SECRET = os.environ["MUTABAKATAI_CONNECTOR_SECRET"].encode()
app = FastAPI()

@app.middleware("http")
async def verify_signature(request: Request, call_next):
    ts = request.headers.get("x-mutabakatai-timestamp", "")
    sig = request.headers.get("x-mutabakatai-signature", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        raise HTTPException(401, "stale")
    path = request.url.path + ("?" + request.url.query if request.url.query else "")
    expected = hmac.new(SECRET, f"{ts}\n{request.method}\n{path}".encode(), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig):
        raise HTTPException(401, "invalid_signature")
    return await call_next(request)

@app.get("/mutabakatai/ping")
def ping(): return {"ok": True, "erp": "Logo Tiger", "company": "OneWorks Digital", "version": "1.0"}

@app.get("/mutabakatai/statements")
def statements(counterparty_external_id: str, period_start: str, period_end: str, currency: str = "TRY", page_size: int = 20000, cursor: str | None = None):
    # Sayfalama: page_size kadar satır + kaldığı yeri kodlayan next_cursor (burada satır konumu; son sayfada None).
    size = min(page_size, 20000)
    offset = int(cursor) if cursor else 0
    rows = logo.cari_hareketleri(counterparty_external_id, period_start, period_end, currency, offset=offset, limit=size + 1)  # LG_XXX_01_CLFLINE; currency = personelin seçimi
    page = rows[:size]
    return {"data": {"currency": currency, "rows": [
        {"reference_no": r.tranno, "date": r.date_, "description": r.lineexp,
         "amount": r.amount if r.sign == 0 else -r.amount} for r in page],
        "next_cursor": str(offset + size) if len(rows) > size else None}}