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
createIsbasiInvoicegibi yardımcılarla soyutlanmıştır. Ayar alanları örnektir; uygulamanın son sürümünde farklılık gösterebilir.
Ne Yapar#
| Özellik | Platform 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 saklar | Yerel ayar formu + Vault |
| Fatura numarasını siparişe yazar; sipariş detayındaki "Uygulamalar" kartında görünür | Metafield'lar |
| Sipariş detayına "İşbaşı'nda fatura kes", sipariş listesine "Seçilenleri faturala" ekler | Aksiyonlar |
| Panelde "Faturalar" ekranı | App Bridge, navigation |
Fatura kesilince app.isbasi.invoice.created olayını yayınlar | Uygulamalar Arası İletişim |
| Aylık plan ve 14 günlük deneme | Ücretlendirme |
Yapılandırma#
{
"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: 204Fatura Oluşturma#
Webhook işleyicisi, iframe aksiyonu ve toplu API aksiyonu aynı fonksiyonu çağırır:
// 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:
// 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#
| Yer | Ne görünür |
|---|---|
| Uygulamalar → İşbaşı → Ayarlar | Yerel ayar formu; sır alanları yalnız "ayarlı / ayarlı değil" gösterir |
| Uygulamalar → İşbaşı → Faturalar | Gö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)#
| Konu | Durum |
|---|---|
| Para birimi | Yalnız TRY siparişler faturalanır; diğer para birimleri atlanır |
| İptal / iade | order.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şler | Kalemde taxRate: null ise uygulama ayarındaki varsayılan oran kullanılır |
| Abonelik | Otomatik yenileme yok; mağaza dönem sonunda planı yeniler |