لماذا يحدث هذا: «فاتورة PDF جميلة» ليست فاتورة إلكترونية
حين تطلب من أداة ذكاء اصطناعي «أضف فاتورة ضريبية»، تحصل عادة على صفحة HTML أو ملف PDF فيه شعارك والمبلغ وعبارة «شامل الضريبة»، ومعه أحيانًا رمز QR يحمل رابط الفاتورة. هذا شكل فاتورة، لكنه لا يحقق متطلبات هيئة الزكاة والضريبة والجمارك. رمز QR في الفاتورة المبسطة ليس رابطًا، بل بيانات مرمّزة بصيغة محددة اسمها TLV ثم Base64، وتطبيق الهيئة للتحقق يقرأ هذه البيانات لا صفحة موقعك.
والخلط الثاني بين المرحلتين: المرحلة الأولى (الإصدار والحفظ) بدأت في 4 ديسمبر 2021 وتطلب إصدار الفاتورة وحفظها إلكترونيًا بحقولها الإلزامية. المرحلة الثانية (الربط والتكامل) تُطبَّق على المنشآت على موجات تحددها الهيئة وتُبلغ بها المنشأة مسبقًا، وفيها تصبح الفاتورة ملف XML موقّعًا يُرسَل إلى منصة «فاتورة». أداة الذكاء الاصطناعي لا تعرف في أي موجة أنت، فاعرف ذلك أولًا.
ما الذي تحتاجه
- رقم التسجيل الضريبي المكوّن من 15 رقمًا والاسم المسجّل للمنشأة كما في شهادة التسجيل.
- تحديد نوع الفاتورة: فاتورة ضريبية مبسطة للبيع للأفراد (B2C)، وفاتورة ضريبية (قياسية) للبيع لمنشأة مسجّلة (B2B) وتتضمن بيانات المشتري ورقمه الضريبي.
- ترقيم تسلسلي فريد لا يُعاد استخدامه ولا تُحذف منه فاتورة؛ التصحيح يكون بإشعار دائن أو مدين.
- ختم زمني بتوقيت ISO 8601 لكل فاتورة.
- معرفة موجتك في المرحلة الثانية من إشعار الهيئة، وحل فوترة معتمد إن كنت مشمولًا.
| فاتورة ضريبية مبسطة | فاتورة ضريبية (قياسية) | |
|---|---|---|
| لمن | مستهلك فرد | منشأة، غالبًا مسجّلة ضريبيًا |
| بيانات المشتري | غير إلزامية في الغالب | الاسم والعنوان والرقم الضريبي |
| رمز QR | إلزامي | إلزامي في المرحلة الثانية |
| المرحلة الثانية | تُرسل للهيئة خلال مدة محددة بعد الإصدار (Reporting) | تُعتمد من الهيئة قبل تسليمها للمشتري (Clearance) |
الشرح التنظيمي الكامل للمرحلتين والغرامات والجدول الزمني موجود في دليل الفوترة الإلكترونية وفي دليل الفواتير والضريبة. هنا نركّز على ما يكتبه تطبيقك فعليًا.
الحل خطوة بخطوة
احسب الضريبة في الخادم وخزّن الأرقام
احسب الإجمالي قبل الضريبة والضريبة 15% والإجمالي بعدها، بتقريب إلى منزلتين، واحفظها في جدول الفواتير مع رقم تسلسلي. لا تعِد حسابها عند كل عرض حتى لا تختلف الهللات.
ابنِ حقول TLV الخمسة
كل حقل يُكتب كبايت للرقم (Tag)، ثم بايت للطول بالبايتات، ثم القيمة بترميز UTF-8. انتبه: الطول بالبايتات لا بعدد الحروف، والحرف العربي يأخذ بايتين.
رمّز النتيجة Base64 وحوّلها إلى QR
النص الناتج هو ما يُوضع في رمز QR، لا رابط ولا JSON.
اختبر الرمز بتطبيق الهيئة
امسح الرمز بتطبيق التحقق الذي تنشره الهيئة، وتأكد أن الاسم العربي يظهر صحيحًا وأن المبالغ مطابقة.
خطّط للمرحلة الثانية مبكرًا
اجعل جدول الفواتير يحفظ كل ما يحتاجه ملف UBL لاحقًا: بنود الفاتورة، والكميات، ورمز الفئة الضريبية، ورقم الفاتورة السابقة وبصمتها (Hash).
// Phase-1 QR: tags 1..5, each as [tag][length][UTF-8 value]
function tlv(tag, value) {
const bytes = new TextEncoder().encode(String(value));
if (bytes.length > 255) throw new Error(`Tag ${tag} is too long`);
return Uint8Array.from([tag, bytes.length, ...bytes]);
}
export function zatcaQrBase64({ sellerName, vatNumber, timestamp, totalWithVat, vatAmount }) {
const parts = [
tlv(1, sellerName),
tlv(2, vatNumber),
tlv(3, timestamp), // ISO 8601, e.g. 2026-11-05T14:30:00Z
tlv(4, Number(totalWithVat).toFixed(2)),
tlv(5, Number(vatAmount).toFixed(2)),
];
const all = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
let i = 0;
for (const p of parts) { all.set(p, i); i += p.length; }
let bin = "";
for (const b of all) bin += String.fromCharCode(b);
return btoa(bin);
}
// Example: a coffee roastery in Riyadh
console.log(zatcaQrBase64({
sellerName: "محمصة الريان",
vatNumber: "300000000000003",
timestamp: "2026-11-05T14:30:00Z",
totalWithVat: 115,
vatAmount: 15,
}));// npm i qrcode
import QRCode from "qrcode";
import { zatcaQrBase64 } from "./zatca-qr.js";
export async function invoiceQrDataUrl(invoice) {
const payload = zatcaQrBase64({
sellerName: invoice.sellerName,
vatNumber: invoice.sellerVat,
timestamp: invoice.issuedAt,
totalWithVat: invoice.totalWithVat,
vatAmount: invoice.vatAmount,
});
return QRCode.toDataURL(payload, { errorCorrectionLevel: "M", margin: 1 });
}الرقم الضريبي في المثال للتوضيح فقط. وفي المرحلة الثانية يحتوي رمز الفاتورة المبسطة حقولًا إضافية (بصمة الفاتورة والتوقيع الرقمي والمفتاح العام وتوقيع الشهادة) تُولَّد من عملية التوقيع نفسها، فلا تحاول تقليدها يدويًا.
المرحلة الثانية: XML بمعيار UBL 2.1 وحل معتمد
في الربط والتكامل تصبح الفاتورة ملف XML وفق معيار UBL 2.1 بالمواصفات التي نشرتها الهيئة، يُوقَّع بشهادة تشفير تصدرها الهيئة لجهازك أو حلّك بعد التسجيل عبر بوابة المطورين (Onboarding)، ويُربط بسلسلة بصمات مع الفاتورة السابقة. ثم تُرسل الفاتورة إلى منصة «فاتورة»: القياسية للاعتماد قبل التسليم، والمبسطة للإبلاغ بعد الإصدار.
بناء هذا كله داخل تطبيق صغير ممكن تقنيًا، لكنه يعني إدارة الشهادات والتوقيع والتعامل مع رفض الفواتير وإعادة الإرسال. لمعظم المتاجر والمنشآت الصغيرة، مثل صالون في جدة أو متجر إلكتروني ناشئ، الأوفر وقتًا أن يتكامل تطبيقك مع حل فوترة إلكترونية معتمد عبر واجهته البرمجية: تطبيقك يرسل بيانات الفاتورة، والحل يولّد XML ويوقّعه ويرسله ويعيد لك الرمز والحالة.
أنشئ جدول فواتير يحفظ لكل فاتورة: رقمًا تسلسليًا لا يتكرر، ونوعها (مبسطة أو ضريبية)، وبنودها بالكمية والسعر ورمز الفئة الضريبية، والإجمالي قبل الضريبة والضريبة 15% والإجمالي بعدها، وتاريخ الإصدار ISO 8601. أضف دالة تولّد رمز QR للمرحلة الأولى بصيغة TLV و Base64، واجعل إرسال الفاتورة لحل فوترة خارجي في وحدة مستقلة يمكن تفعيلها لاحقًا.
الحل الأسرع: أوترا
في أوترا تطلب صفحة الفاتورة والجدول ودالة رمز QR بالمحادثة، وترى الفواتير المحفوظة في معاينة «قاعدة البيانات» داخل المنصة. وإذا اخترت حل فوترة معتمدًا للمرحلة الثانية، تضع مفتاح واجهته في «البيانات السرية» وتطلب الربط به.
أوترا ليست حل فوترة إلكترونية معتمدًا من الهيئة، ولا تُصدر فواتير مشروعك الضريبية نيابة عنك؛ هي تبني لك الكود الذي يحسب ويخزن ويولّد الرمز، أو يرسل البيانات إلى الحل المعتمد الذي تختاره. والتحقق النهائي من الامتثال مسؤولية المنشأة ومحاسبها.
أضف إلى متجري صفحة فاتورة ضريبية مبسطة بالعربية فيها اسم المتجر ورقمه الضريبي ورقم الفاتورة والبنود والضريبة 15%، مع رمز QR بصيغة TLV للمرحلة الأولى، وزر تنزيل PDF للعميل.