İçeriğe geç
Docs

Örnek Uygulama: Logo İşbaşı

Uçtan uca örnek uygulama: Logo İşbaşı ile otomatik fatura — Vault, metafield, aksiyon ve olay yayını.

Son güncelleme:

Logo İşbaşı uygulaması, platformun yeteneklerini uçtan uca gösteren örnek bir fatura uygulamasıdır: ödenen siparişler için İşbaşı'nda otomatik fatura oluşturur, İşbaşı kimlik bilgilerini Vault'ta saklar, fatura numarasını siparişe metafield olarak yazıp panelde gösterir, sipariş ekranlarına aksiyon ekler ve başka uygulamaların dinleyebileceği invoice.created olayını yayınlar.

Bu sayfa uygulamanın mimarisini ve platformla sözleşmesini anlatır. İşbaşı API'sinin kendi ayrıntıları (uç adları, alanlar) uygulamaya özgüdür ve burada createIsbasiInvoice gibi yardımcılarla soyutlanmıştır. Ayar alanları örnektir; uygulamanın son sürümünde farklılık gösterebilir.


Ne Yapar#

ÖzellikPlatform yeteneği
order.paid geldiğinde faturayı taslak ya da kesilmiş olarak oluşturur (mağaza ayarı)Webhook'lar, API
İşbaşı API anahtarı ve parolasını panelin ayar formundan alır, şifreli saklarYerel ayar formu + Vault
Fatura numarasını siparişe yazar; sipariş detayındaki "Uygulamalar" kartında görünürMetafield'lar
Sipariş detayına "İşbaşı'nda fatura kes", sipariş listesine "Seçilenleri faturala" eklerAksiyonlar
Panelde "Faturalar" ekranıApp Bridge, navigation
Fatura kesilince app.isbasi.invoice.created olayını yayınlarUygulamalar Arası İletişim
Aylık plan ve 14 günlük denemeÜcretlendirme

Yapılandırma#

JSON
{
  "name": "Logo İşbaşı",
  "slug": "isbasi",
  "hosting": "tecof",
  "embedded": true,
  "scopes": ["orders:read", "customers:read", "products:read"],
  "webhooks": {
    "path": "/api/webhooks",
    "events": ["order.paid", "order.cancelled", "order.refunded"]
  },
  "actions": [
    {
      "key": "create-invoice", "type": "iframe", "placement": "order.detail",
      "title": { "tr": "İşbaşı'nda fatura kes", "en": "Create İşbaşı invoice" },
      "path": "/actions/create-invoice", "presentation": "modal"
    },
    {
      "key": "bulk-invoice", "type": "api", "placement": "order.list",
      "title": { "tr": "Seçilenleri faturala", "en": "Invoice selected" },
      "path": "/api/actions/bulk-invoice",
      "confirm": { "tr": "Seçili siparişler için fatura kesilsin mi?", "en": "Create invoices for selected orders?" }
    }
  ],
  "navigation": [
    { "label": { "tr": "Faturalar", "en": "Invoices" }, "path": "/invoices" }
  ],
  "settings": [
    { "key": "username", "label": { "tr": "İşbaşı kullanıcı adı", "en": "İşbaşı username" }, "type": "text", "required": true },
    { "key": "password", "label": { "tr": "İşbaşı parolası", "en": "İşbaşı password" }, "type": "secret", "required": true },
    { "key": "apiKey", "label": { "tr": "İşbaşı API anahtarı", "en": "İşbaşı API key" }, "type": "secret", "required": true },
    { "key": "autoInvoice", "label": { "tr": "Ödenen siparişleri otomatik faturala", "en": "Auto-invoice paid orders" }, "type": "boolean" },
    { "key": "issueInvoices", "label": { "tr": "Faturayı taslak değil kesilmiş olarak oluştur", "en": "Issue invoices instead of drafts" }, "type": "boolean" },
    { "key": "defaultVatRate", "label": { "tr": "Varsayılan KDV oranı (%)", "en": "Default VAT rate (%)" }, "type": "number" }
  ],
  "appEvents": {
    "publishes": [
      { "name": "invoice.created", "description": { "tr": "Fatura oluşturuldu", "en": "Invoice created" } }
    ],
    "subscribes": ["app.kargo.shipment.created"]
  },
  "plans": [
    {
      "key": "standart", "name": { "tr": "Standart", "en": "Standard" },
      "description": { "tr": "Sınırsız otomatik fatura", "en": "Unlimited automatic invoices" },
      "price": 199, "currency": "TRY", "interval": "month", "trialDays": 14,
      "features": [{ "tr": "Sınırsız fatura", "en": "Unlimited invoices" }]
    }
  ],
  "listing": {
    "shortDescription": { "tr": "Siparişlerinizi Logo İşbaşı'nda otomatik faturalayın", "en": "Automatically invoice orders in Logo İşbaşı" },
    "description": { "tr": "…", "en": "…" },
    "category": "accounting",
    "supportEmail": "destek@ornek.com",
    "privacyUrl": "https://ornek.com/gizlilik"
  }
}

Kapsam gerekçeleri: orders:read sipariş ve order.* olayları, sipariş aksiyonları ve sipariş metafield'ları için; customers:read fatura alıcısı için; products:read ürün bilgisi için. Uygulama Tecof'a ürün ya da sipariş yazmaz; tek yazdığı şey kendi metafield'ları, ayarları ve olaylarıdır.


Mimari#

sequenceDiagram
    participant M as Mağaza paneli
    participant T as Tecof API
    participant A as İşbaşı uygulaması
    participant L as Logo İşbaşı API

    Note over M,A: Kurulum ve ayar
    M->>T: Kurulum (kapsam onayı, deneme planı)
    T->>A: webhook app.installed
    M->>T: Ayarlar sekmesi: kullanıcı adı, parola, API anahtarı
    T->>T: Sır alanları Vault'a (AES-256-GCM)
    T->>A: webhook app.settings.updated { keys, secretKeys }

    Note over T,L: Otomatik fatura
    T->>A: webhook order.paid (imzalı)
    A->>T: POST /api/apps/oauth/token (client_credentials)
    A->>T: GET /api/v1/metafields?ownerType=order&namespace=isbasi
    A->>T: GET /api/v1/orders/:id
    A->>T: GET /api/v1/app/settings + /api/v1/app/secrets/:key
    A->>L: Fatura oluştur (taslak / kesilmiş)
    L-->>A: Fatura numarası
    A->>T: PUT /api/v1/metafields (invoiceNumber, shared, panelde göster)
    A->>T: POST /api/v1/app/events { name: "invoice.created" }
    A-->>T: 204
Diyagram

Fatura Oluşturma#

Webhook işleyicisi, iframe aksiyonu ve toplu API aksiyonu aynı fonksiyonu çağırır:

TypeScript
// lib/invoice.ts
import { tecof } from "@/lib/tecof";
import { createIsbasiInvoice } from "@/lib/isbasi"; // uygulamaya özgü İşbaşı istemcisi

export async function invoiceOrder(installationId: string, orderId: string) {
  const api = tecof.client(installationId);

  // 1) Idempotency: siparişte fatura numarası varsa tekrar kesme
  const existing = await api.metafields.list({ ownerType: "order", ownerId: orderId, namespace: "isbasi" });
  if (existing.some((m: { key: string }) => m.key === "invoiceNumber")) {
    return { status: "skipped" as const };
  }

  // 2) Siparişin güncel hâli
  const order = await api.orders.get(orderId);
  if (order.currency !== "TRY") {
    return { status: "unsupported-currency" as const }; // v1: yalnız TRY
  }

  // 3) Ayarlar ve Vault
  const { settings } = await api.settings.get();
  const [{ value: password }, { value: apiKey }] = await Promise.all([
    api.secrets.get("password"),
    api.secrets.get("apiKey"),
  ]);
  const defaultVatRate = Number(settings.defaultVatRate ?? 20);

  // 4) İşbaşı'nda fatura
  const invoice = await createIsbasiInvoice(
    { username: String(settings.username), password, apiKey },
    {
      issue: settings.issueInvoices === true, // false → taslak
      buyer: order.billingAddress,            // identityNumber: TCKN/VKN
      lines: order.lineItems.map((li) => ({
        name: li.variantTitle ? `${li.name} (${li.variantTitle})` : li.name,
        sku: li.sku,
        quantity: li.quantity,
        unitPrice: li.unitPrice,
        vatRate: li.taxRate ?? defaultVatRate, // null = eski sipariş → mağaza varsayılanı
        taxIncluded: li.taxIncluded,
      })),
      shipping: order.shippingLines,
      discounts: order.discountLines,
    },
  );

  // 5) Fatura numarasını siparişe yaz (panelde görünür, diğer uygulamalarla paylaşılır)
  await api.metafields.set({
    ownerType: "order",
    ownerId: order.id,
    key: "invoiceNumber",
    type: "string",
    value: invoice.number,
    visibility: "shared",
    panel: { label: { tr: "İşbaşı fatura no", en: "İşbaşı invoice no" }, show: true },
  });

  // 6) Olayı yayınla
  await api.events.publish("invoice.created", {
    orderId: order.id,
    orderNumber: order.orderNumber,
    invoiceNumber: invoice.number,
    issued: settings.issueInvoices === true,
  });

  return { status: "created" as const, invoiceNumber: invoice.number };
}

Webhook işleyicisi:

TypeScript
// app/api/webhooks/route.ts
import { createWebhookHandler } from "@tecof/app-sdk";
import { tecof } from "@/lib/tecof";
import { invoiceOrder } from "@/lib/invoice";

export const POST = createWebhookHandler({
  app: tecof,
  handlers: {
    "order.paid": async (event) => {
      const { settings } = await tecof.client(event.installationId).settings.get();
      if (settings.autoInvoice === true) {
        await invoiceOrder(event.installationId, String(event.data.orderId));
      }
    },
    // v1: fatura otomatik iptal edilmez — iptal/iade belgesi İşbaşı'nda yönetilir
    "order.cancelled": async () => {},
    "order.refunded": async () => {},
  },
});

invoiceOrder kalıcı hatalarda (eksik ayar, desteklenmeyen para birimi) hata fırlatmaz, bir durum döner; böylece işleyici 2xx yanıt verir ve Tecof aynı olayı boşuna yeniden denemez. Geçici hatalarda (İşbaşı'na ulaşılamadı) hata fırlatılır; createWebhookHandler 2xx dışı yanıt döner ve teslimat +2 dk ile +15 dk sonra yeniden denenir. Yeniden denemede invoiceNumber metafield kontrolü çift faturayı önler.


Panelde Görünüm#

YerNe görünür
Uygulamalar → İşbaşı → AyarlarYerel ayar formu; sır alanları yalnız "ayarlı / ayarlı değil" gösterir
Uygulamalar → İşbaşı → FaturalarGömülü /invoices ekranı (App Bridge)
Sipariş detayı → "Uygulamalar" kartıİşbaşı fatura no: ABC2026000000123 (metafield)
Sipariş detayı → "Uygulamalar" menüsü"İşbaşı'nda fatura kes" (iframe aksiyonu, modal)
Sipariş listesi → toplu işlemler"Seçilenleri faturala" (API aksiyonu, onaylı)

Sınırlamalar (v1)#

KonuDurum
Para birimiYalnız TRY siparişler faturalanır; diğer para birimleri atlanır
İptal / iadeorder.cancelled ve order.refunded alınır ancak fatura otomatik iptal edilmez; iptal ve iade belgeleri İşbaşı'nda elle yönetilir
KDV oranı bilinmeyen eski siparişlerKalemde taxRate: null ise uygulama ayarındaki varsayılan oran kullanılır
AbonelikOtomatik yenileme yok; mağaza dönem sonunda planı yeniler