lubos
August 30, 2025, 1:03am
1
A simple extension that creates QR codes for Saudi Arabian tax invoices.
Installation
Go to Settings → Extensions in Manager.
Create a new extension with:
Extension Name : ZATCA Phase 1 QR Code Generator
Source : Url
Endpoint https:// www.luboshasko.com/extensions/sa/zatca-phase-1-qr-generator/
Placement : sales-invoice-view
Click Create button.
Quick Start
Click View button on any invoice
Click ZATCA Phase 1 QR Code Generator button.
Follow the prompts
3 Likes
hI @lubos @Mabaega
Iam trying to set up QR Code for Saudi Entity. the extension is appear in the view screen.
I tried to copy from other company I got below error
will you please guide me how to add or how I could solve below error
below is extension set up
Hello How is Jordan added?
lubos
November 11, 2025, 3:51am
4
@abdulbari this has been fixed in the latest version now
Thank you @lubos
I just would like to know if you can do something regard the previous invoices before update
QR Code now removed from All invoices except the one has been done with extension
This will put them us in risk if audit from ZATCA is come and find out these invoices without QR code
will you be please able to do something or advise. even if it is agreed to do it again it will capture the date and time of today.
2 Likes
You’re right — we hope for assistance with that, especially given the constant updates that cause unexpected issues.
lubos
November 11, 2025, 10:57am
8
What is QR extension solving is that QR code is generated once and then stored alongside with the invoice so it won’t change.
Previous approach was to generate QR code on demand which was incorrect because programming code could affect QR code generation in the future. New approach is to store QR code as data which will guarantee it will never change.
I can provide custom theme which will replicate the old behavior for these historical invoices.
3 Likes
<table style="padding: 30px; width: 100%">
<thead>
<tr>
<td colspan="99">
<table style="margin-bottom: 20px; width: 100%">
<tr>
<td style="font-weight: bold; font-size: 32px; vertical-align: top">{{ title }}</td>
{% if business.logo != null %}
<td style="text-align: end">
<img src="{{ business.logo }}" style="max-height: 150px; max-width: 300px; display: inline" />
</td>
{% endif %}
</tr>
</table>
<table style="margin-bottom: 20px; width: 100%">
<tr>
<td style="vertical-align: top">
<div style="font-weight: bold">
<p>{{ recipient.name }}</p>
</div>
<div>{{ recipient.address | newline_to_br }}</div>
<div>{{ recipient.identifier }}</div>
</td>
<td style="text-align: end; vertical-align: top">
{% for field in fields %}
<div style="font-weight: bold">{{ field.label }}</div>
<div id="{{ field.key }}" style="margin-bottom: 10px">{{ field.text }}</div>
{% endfor %}
</td>
{% if business.address != null %}
<td style="width: 20px"></td>
<td style="width: 1px; border-left-width: 1px; border-left-color: #000; border-left-style: solid"></td>
<td style="width: 20px"></td>
<td style="width: 1px; white-space: nowrap; vertical-align: top">
<div id="BusinessName" style="font-weight: bold">
<p>{{ business.name }}</p>
</div>
<div>{{ business.address | newline_to_br }}</div>
{% for field in business.custom_fields %}
<div>{{ field.label }} <span id="{{ field.key }}">{{ field.text }}</span></div>
{% endfor %}
</td>
{% endif %}
</tr>
</table>
<div style="font-size: 14px; font-weight: bold; margin-bottom: 20px">{{ description }}</div>
</td>
</tr>
<tr>
{% for column in table.columns %}
<td style="writing-mode: horizontal-tb; border-inline-start-width: 1px; border-inline-start-style: solid; border-inline-start-color: #000; {% if forloop.last == true %} border-inline-end-width: 1px; border-inline-end-style: solid; border-inline-end-color: #000{% endif %}; text-align: {{ column.align }}; font-weight: bold; padding: 5px 10px; border-bottom-width: 1px; border-bottom-color: #000; border-top-width: 1px; border-top-color: #000; border-top-style: solid; border-bottom-style: solid{% if column.nowrap %}; width: 80px{% endif %}">{{ column.label }}</td>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in table.rows %}
<tr>
{% for cell in row.cells %}
<td style="writing-mode: horizontal-tb; border-inline-start-width: 1px; border-inline-start-style: solid; border-inline-start-color: #000; {% if forloop.last == true %} border-inline-end-width: 1px; border-inline-end-style: solid; border-inline-end-color: #000{% endif %}; padding: 5px 10px; text-align: {{ table.columns[forloop.index0].align }}; {% if table.columns[forloop.index0].nowrap %}; white-space: nowrap; width: 80px{% endif %}">{{ cell.text | newline_to_br }}</td>
{% endfor %}
</tr>
{% endfor %}
<tr>
{% for column in table.columns %}
<td style="writing-mode: horizontal-tb; border-inline-start-width: 1px; border-inline-start-style: solid; border-inline-start-color: #000; {% if forloop.last == true %} border-inline-end-width: 1px; border-inline-end-style: solid; border-inline-end-color: #000{% endif %}; border-bottom-width: 1px; border-bottom-color: #000000; border-bottom-style: solid"> </td>
{% endfor %}
</tr>
{% for total in table.totals %}
<tr>
<td colspan="{{ table.columns | size | minus:1 }}" style="text-align: end; padding: 5px 10px{% if total.emphasis == true %}; font-weight: bold{% endif %}">{{ total.label }}</td>
<td id="{{ total.key }}" class="{{ total.class }}" data-value="{{ total.number }}" style="border-left-width: 1px; border-left-style: solid; border-left-color: #000; border-right-width: 1px; border-right-style: solid; border-right-color: #000; text-align: right; white-space: nowrap; border-bottom-width: 1px; border-bottom-color: #000000; border-bottom-style: solid; padding: 5px 10px{% if total.emphasis == true %}; font-weight: bold{% endif %}">{{ total.text }}</td>
</tr>
{% endfor %}
{% for field in custom_fields %}
<tr>
<td colspan="99">
<div style="font-weight: bold; padding-top: 20px">{{ field.label }}</div>
<div>{{ field.text | newline_to_br }}</div>
</td>
</tr>
{% endfor %}
<tr>
<td colspan="99">
<div id="qrcode" style="margin-top: 20px; margin-bottom: 30px"></div>
{% if emphasis.text != null and emphasis.positive %}
<div style="text-align: center; margin-top: 40px"><span style="color: #006400; border-width: 5px; border-color: #006400; border-style: solid; padding: 10px; font-size: 20px">{{ emphasis.text | upcase }}</span></div>
{% endif %}
{% if emphasis.text != null and emphasis.negative %}
<div style="text-align: center; margin-top: 40px"><span style="color: #FF0000; border-width: 5px; border-color: #FF0000; border-style: solid; padding: 10px; font-size: 20px">{{ emphasis.text | upcase }}</span></div>
{% endif %}
</td>
</tr>
<input type="hidden" name="Timestamp" id="TimestampField" value="">
</tbody>
</table>
<!-- QR CODE SCRIPT UPDATED -->
<script src="resources/qrcode/qrcode.js"></script>
<script type="text/javascript">
// قراءة تاريخ الفاتورة
var invoiceDateElement = document.getElementById('InvoiceDate');
var realDate = null;
var timestampValue = NaN;
if (invoiceDateElement) {
var invoiceDateString = invoiceDateElement.innerText.trim();
var parts = invoiceDateString.split(/[- /.]/);
if (parts.length === 3) {
var day = parseInt(parts[0], 10);
var month = parseInt(parts[1], 10);
var year = parseInt(parts[2], 10);
realDate = new Date(year, month - 1, day);
if (isNaN(realDate.getTime())) realDate = new Date(year, day - 1, month);
}
}
if (!isNaN(realDate?.getTime())) {
var now = new Date();
realDate.setHours(now.getHours(), now.getMinutes(), now.getSeconds());
timestampValue = realDate.getTime();
}
document.getElementById("TimestampField").value = timestampValue;
// توليد كود QR بنظام TLV
function appendTLV(tag, text, byteList) {
const encoded = new TextEncoder().encode(text);
byteList.push(tag);
byteList.push(encoded.length);
for (let b of encoded) byteList.push(b);
}
try {
var businessName = document.getElementById("BusinessName")?.innerText?.trim() || "No name";
var vatNumber = "0000000000000";
var vatField = document.querySelector('[id^="d96d97e8-c857-42c6-8360-443c06a13de9"]');
if (vatField && vatField.innerText.trim() !== "") vatNumber = vatField.innerText.trim();
var isoDate = new Date(timestampValue).toISOString();
var totalElement = document.getElementById('Total');
var total = 0;
if (totalElement) total = parseFloat(totalElement.getAttribute('data-value')) || 0;
var vat = 0;
var taxElements = document.getElementsByClassName('taxAmount');
for (let i = 0; i < taxElements.length; i++) {
vat += parseFloat(taxElements[i].getAttribute('data-value')) || 0;
}
const byteList = [];
appendTLV(1, businessName, byteList);
appendTLV(2, vatNumber, byteList);
appendTLV(3, isoDate, byteList);
appendTLV(4, total.toFixed(2), byteList);
appendTLV(5, vat.toFixed(2), byteList);
const tlvBytes = Uint8Array.from(byteList);
const qrData = btoa(String.fromCharCode(...tlvBytes));
new QRCode(document.getElementById("qrcode"), {
text: qrData,
width: 128,
height: 128,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.L
});
} catch (e) {
console.error("QR Code generation error:", e);
}
</script>
I did that and I think it works.
@lubos
1 Like
Please, I need a solution
Thank you, brother, it has worked
Still the issue with colours, everything is black and white
I think if you deactivate the night theme, the colours will return