fiscal.de ⏳ YakındaAlmanya KassenSichV / TSE fiş imzalama yeteneği. Tüketici eklenti 'bu fişi imzala' der, platform tenant'ın bağladığı TSE sağlayıcı eklentiye relay eder; sağlayıcı fişi imzalar ve mali bloğu (QR, Beleg-Nr, Sig-Zähler, TSE zamanları) döndürür. invoice.issue'dan AYRI bir yetenektir ve SENKRONDUR — asenkron durum event'i yoktur.
← Eklentiler-Arası Yetenekler · Mali bloğun fişe taşınması: receiptExtras
provides/consumes: [{ capability: "fiscal.de" }] beyan edersiniz, capability:fiscal.de:consume|provide scope'ları otomatik türetilir.invoice.issue | fiscal.de | |
|---|---|---|
| Ne yapar | E-fatura/e-arşiv keser (GİB) | Fişi TSE ile imzalar (KassenSichV) |
| Çerçeve | TR vergi mevzuatı | Alman KassenSichV / DSFinV-K |
| Sonuç | Fatura numarası/URL, asenkron durum | Mali blok: QR + Beleg-Nr + Sig-Zähler |
| Akış | Asenkron (invoice.status event'i) | Senkron (yanıtta biter) |
İkisi ayrı capability'dir; bir eklenti ikisini birden sağlayabilir ama scope'ları ve sözleşmeleri ayrıdır. Detay: Fatura Kesme (invoice.issue).
| Scope | Kim alır | Ne sağlar |
|---|---|---|
| capability:fiscal.de:consume | Kasa/POS tarafı eklenti | POST /plugin-api/capabilities/fiscal.de/invoke çağırabilir |
| capability:fiscal.de:provide | TSE sağlayıcı eklenti | Platformdan imzalı type:"capability" isteği alır + receiptExtras'ta tse.* yazabilir |
providerIsPii: false): taşınan veri tutar, KDV oranı ve ödeme tipidir — müşteri referansı bile yoktur. Bu yüzden kurulumda dataConsent gerektirmez.POST {RESTOMENUM_BASE}/plugin-api/capabilities/fiscal.de/invoke
Authorization: Bearer <apiKey>
Content-Type: application/json
{
"payload": {
"receiptType": "RECEIPT",
"amountsPerVatRate": [
{ "vatRate": 19, "amount": 1990 },
{ "vatRate": 7, "amount": 700 }
],
"amountsPerPaymentType": [
{ "paymentType": "CASH", "amount": 1690 },
{ "paymentType": "NON_CASH", "amount": 1000 }
],
"reference": "masa-5",
"idempotencyKey": "close-masa5-1785438065"
}
}| Alan | Tip | Zorunlu | Kural |
|---|---|---|---|
| receiptType | string | hayır | RECEIPT (varsayılan). DSFinV-K Belegtyp; yeni tip eklemek geriye-uyumlu. |
| amountsPerVatRate | array | evet | 1–50 satır; { vatRate, amount }. |
| …[].vatRate | number | evet | 0–100; ondalık izinli (DE: 19 / 7 / 0; tarihsel 10.7 / 5.5). |
| …[].amount | integer | evet | CENT (ondalık DEĞİL). Negatif izinli. |
| amountsPerPaymentType | array | evet | 1–50 satır; { paymentType, amount }. |
| …[].paymentType | string | evet | CASH | NON_CASH. |
| …[].amount | integer | evet | CENT, negatif izinli. |
| reference | string | hayır | ≤100 — adisyon referansı (masa/paket id). |
| idempotencyKey | string | evet | ≤64. Aşağı bkz. |
19.90 değil 1990 — ondalık gönderilirse istek reddedilir.idempotencyKey zorunludur çünkü aynı fişin iki kez imzalanması TSE'de mükerrer işlem = mali/yasal sorundur. Aynı key + farklı içerik → 409 (keys_reused deseni).import { RestomenumClient } from '@restomenum/plugin-sdk';
// Tüketici (scope: capability:fiscal.de:consume) — tutarlar CENT (integer):
const r = await client.capabilities.invoke('fiscal.de', {
amountsPerVatRate: [{ vatRate: 19, amount: 1990 }], // 19,90 € → 1990
amountsPerPaymentType: [{ paymentType: 'CASH', amount: 1990 }],
reference: tableId,
idempotencyKey: `close-${tableId}-${docNo}`, // ZORUNLU
});
if (r.status === 'signed') { /* mali blok hazır */ }
if (r.status === 'ausfall') { /* HATA DEĞİL — fişe "Ausfall" basılır */ }Platform, tenant'ın bağladığı sağlayıcının actionUrl'üne (yoksa webhookUrl) imzalı type:"capability" POST atar. Ortak model ve binding kuralları: Sağlayıcı olma rehberi.
import { verifyAndParseCapability, capabilityResponse } from '@restomenum/plugin-sdk';
import type { FiscalDePayload, FiscalDeStatus } from '@restomenum/plugin-sdk';
// 1) İMZA + şekil doğrula (webhook ile AYNI HMAC şeması). null → 401.
const req = await verifyAndParseCapability<FiscalDePayload>(rawBody, sigHeader, {
getSecret: (tenantId) => installStore.find(tenantId)?.webhookSecret,
});
if (!req) return res.status(401).json({ error: 'invalid_signature' });
// 2) requestId DEDUPE (ZORUNLU): aynı fişi İKİ KEZ imzalamak TSE'de mükerrer işlem = mali/yasal sorun.
const prior = await store.find(req.requestId);
if (prior) return res.json(capabilityResponse(prior.status, { providerMessageId: prior.providerMessageId }));
// 3) Dengeyi SEN doğrula — platform KDV/ödeme toplamını kontrol etmez.
const sum = (rows) => rows.reduce((t, r) => t + r.amount, 0);
if (sum(req.payload.amountsPerVatRate) !== sum(req.payload.amountsPerPaymentType))
return res.json(capabilityResponse<FiscalDeStatus>('failed', { error: { code: 'unbalanced_receipt' } }));
// 4) TSE'ye ulaşılamıyorsa 'ausfall' — 'failed' DÖNME (işlem geçerli, fişe "Ausfall" basılır).
if (!(await tseReachable())) return res.json(capabilityResponse<FiscalDeStatus>('ausfall'));
const providerMessageId = await signAtTse(req.payload); // CENT tutarlar, negatif olabilir (storno)
await store.save(req.requestId, { status: 'signed', providerMessageId });
res.json(capabilityResponse<FiscalDeStatus>('signed', { providerMessageId }));requestId dedupe pazarlık konusu değildir (inceleme kriteri): aynı fişin iki kez imzalanması TSE'de mükerrer işlem demektir. Aynı requestId tekrar gelirse işi yeniden yapma, önceki providerMessageId ile aynı yanıtı dön.examples/sample-plugin/src/routes/capabilityRoute.mjs.| Durum | Anlamı |
|---|---|
| accepted | İstek alındı, işleniyor. |
| signed | TSE imzaladı — mali blok hazır. |
| ausfall | TSE arızası/erişilemez. HATA DEĞİLDİR: işlem geçerli şekilde arıza modunda tamamlandı; KassenSichV fişe "Ausfall" basılmasını ister. |
| failed | İmza alınamadı (iş reddi). |
invoice.issue'daki invoice.status gibi bir event beklemeyin; POST /plugin-api/capabilities/fiscal.de/status ucu bu yetenek için 404 döner. İmza senkron tamamlanır, arıza durumu da senkron yanıtta bildirilir.ausfall bir hata değildir. TSE'ye ulaşılamadığında işlem geçerli şekilde arıza modunda tamamlanır ve KassenSichV fişe "Ausfall" basılmasını ister. Bunu failed gibi ele alıp işlemi geri sarmayın — fişi arıza işaretiyle bastırın.fiscal.de imzayı üretir; fişe taşıma kapanış gate'inde receiptExtras ile yapılır. Sağlayıcı eklenti table.close / packet.close gate'inde allow + receiptExtras döner:
{ "decision":"allow",
"receiptExtras":[
{ "key":"tse.qr", "type":"qr", "value":"V0;…" },
{ "key":"tse.txNumber", "type":"text", "label":"Beleg-Nr", "value":"366" },
{ "key":"tse.signatureCounter", "type":"text", "label":"Sig-Zähler", "value":"774" },
{ "key":"tse.timeStart", "type":"text", "label":"TSE-Start", "value":"1785438065" },
{ "key":"tse.status", "type":"text", "value":"signed" }
] }tse.* namespace'inin sahibi bu yetenektir: yalnız capability:fiscal.de:provide scope'lu eklenti bu key'leri yazabilir. invoice.issue sağlayıcısı dahil, başka hiçbir eklenti yazamaz — aksi halde fişe sahte mali blok bastırılabilirdi. Yazılan öğeler verified: true işareti alır ve fiş şablonu mali bloğu yalnız bu öğelere basar.| message | HTTP | Ne zaman |
|---|---|---|
| plugin.fiscal.missingParams | 400 | payload yok / obje değil. |
| plugin.fiscal.invalidReceiptType | 400 | receiptType whitelist dışı. |
| plugin.fiscal.invalidVatRates | 400 | KDV satırları boş/bozuk, oran 0–100 dışı, tutar cent-integer değil. |
| plugin.fiscal.invalidPaymentTypes | 400 | Ödeme satırları boş/bozuk, tip CASH/NON_CASH dışı. |
| plugin.fiscal.invalidReference | 400 | reference > 100 karakter. |
| plugin.fiscal.idempotencyKeyRequired | 400 | idempotencyKey yok/boş/uzun (≤64). |
Ortak capability hataları (scope reddi, sağlayıcı bağlı değil, sağlayıcı erişilemez, timeout, idempotency yarışı) mevcut sözleşmedeki kodlarla aynıdır — hata ailesi plugin.fiscal.*: Yetenek hata kodları.