Skip to content

Commit

Permalink
FIX ZATCA Encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
eldy committed Mar 19, 2022
1 parent 79d34bd commit cb93ab4
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 5 deletions.
8 changes: 6 additions & 2 deletions dev/resources/iso-normes/QR code for invoices.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ https://en.wikipedia.org/wiki/EPC_QR_code#Generators



* For ZATCA QR Code format (Saudi Arabia)
-----------------------------------------
* For ZATCA QR Code format (Saudi Arabia). Used when INVOICE_ADD_ZATCA_QR_CODE is set
-------------------------------------------------------------------------------------
https://www.pwc.com/m1/en/services/tax/me-tax-legal-news/2021/saudi-arabia-guide-to-develop-compliant-qr-code-for-simplified-einvoices.html

https://www.tecklenborgh.com/post/ksa-zatca-publishes-guide-on-how-to-develop-a-fatoora-compliant-qr-code

Method to encode/decode ZATCA string is available in test/phpunit/BarcodeTest.php
8 changes: 5 additions & 3 deletions htdocs/core/class/commoninvoice.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -827,8 +827,10 @@ public function buildZATCAQRString()
$tmplang->load("main");

$datestring = dol_print_date($this->date, 'dayhourrfc');
$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
//$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
//$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
$pricewithtaxstring = price2num($this->total_ttc, 2, 1);
$pricetaxstring = price2num($this->total_tva, 2, 1);

/*
$name = implode(unpack("H*", $this->thirdparty->name));
Expand Down Expand Up @@ -857,7 +859,7 @@ public function buildZATCAQRString()
// Using TLV format
$s = pack('C1', 1).pack('C1', strlen($this->thirdparty->name)).$this->thirdparty->name;
$s .= pack('C1', 2).pack('C1', strlen($this->thirdparty->tva_intra)).$this->thirdparty->tva_intra;
$s .= pack('C1', 3).pack('C1', strlen($datestring)).$this->date;
$s .= pack('C1', 3).pack('C1', strlen($datestring)).$datestring;
$s .= pack('C1', 4).pack('C1', strlen($pricewithtaxstring)).$pricewithtaxstring;
$s .= pack('C1', 5).pack('C1', strlen($pricetaxstring)).$pricetaxstring;
$s .= ''; // Hash of xml invoice
Expand Down
203 changes: 203 additions & 0 deletions test/phpunit/BarcodeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<?php
/* Copyright (C) 2010 Laurent Destailleur <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* or see https://www.gnu.org/
*/

/**
* \file test/phpunit/BarcodeTest.php
* \ingroup test
* \brief PHPUnit test
* \remarks To run this script as CLI: phpunit filename.php
*/

global $conf,$user,$langs,$db;
//define('TEST_DB_FORCE_TYPE','mysql'); // This is to force using mysql driver
//require_once 'PHPUnit/Autoload.php';
require_once dirname(__FILE__).'/../../htdocs/master.inc.php';
require_once dirname(__FILE__).'/../../htdocs/compta/facture/class/facture.class.php';


if (empty($user->id)) {
print "Load permissions for admin user nb 1\n";
$user->fetch(1);
$user->getrights();
}
$conf->global->MAIN_DISABLE_ALL_MAILS=1;

$langs->load("main");


/**
* Class for PHPUnit tests
*
* @backupGlobals disabled
* @backupStaticAttributes enabled
* @remarks backupGlobals must be disabled to have db,conf,user and lang not erased.
*/
class BarcodeTest extends PHPUnit\Framework\TestCase
{
protected $savconf;
protected $savuser;
protected $savlangs;
protected $savdb;

/**
* Constructor
* We save global variables into local variables
*
* @return BarcodeTest
*/
public function __construct()
{
parent::__construct();

//$this->sharedFixture
global $conf,$user,$langs,$db;
$this->savconf=$conf;
$this->savuser=$user;
$this->savlangs=$langs;
$this->savdb=$db;

print __METHOD__." db->type=".$db->type." user->id=".$user->id;
//print " - db ".$db->db;
print "\n";
}

/**
* setUpBeforeClass
*
* @return void
*/
public static function setUpBeforeClass()
{
global $conf,$user,$langs,$db;
$db->begin(); // This is to have all actions inside a transaction even if test launched without suite.

print __METHOD__."\n";
}

/**
* tearDownAfterClass
*
* @return void
*/
public static function tearDownAfterClass()
{
global $conf,$user,$langs,$db;
$db->rollback();

print __METHOD__."\n";
}

/**
* Init phpunit tests
*
* @return void
*/
protected function setUp()
{
global $conf,$user,$langs,$db;
$conf=$this->savconf;
$user=$this->savuser;
$langs=$this->savlangs;
$db=$this->savdb;

print __METHOD__."\n";
}

/**
* End phpunit tests
*
* @return void
*/
protected function tearDown()
{
print __METHOD__."\n";
}


/**
* testBarcodeZATCAEncode
*
* @return int
*/
public function testBarcodeZATCAEncode()
{
global $conf,$user,$langs,$db;
$conf=$this->savconf;
$user=$this->savuser;
$langs=$this->savlangs;
$db=$this->savdb;

$company = new Societe($db);
$company->name = 'Specimen company';
$company->tva_intra = '123456789';

$tmpinvoice = new Facture($db);

$tmpinvoice->thirdparty = $company;
$tmpinvoice->total_ht = 100;
$tmpinvoice->total_tva = 20;
$tmpinvoice->total_ttc = $tmpinvoice->total_ht + $tmpinvoice->total_tva;
$tmpinvoice->date = dol_mktime(12, 34, 56, 1, 1, 2020, 'gmt');

$string_zatca = $tmpinvoice->buildZATCAQRString();

$this->assertEquals($string_zatca, "ARBTcGVjaW1lbiBjb21wYW55AgkxMjM0NTY3ODkDFDIwMjAtMDEtMDFUMDk6MzQ6NTZaBAMxMjAFAjIw");

return 1;
}



/**
* testBarcodeZATCADecode
*
* @return int
*/
public function testBarcodeZATCADecode()
{
global $conf,$user,$langs,$db;
$conf=$this->savconf;
$user=$this->savuser;
$langs=$this->savlangs;
$db=$this->savdb;

//$string_zatca_base64 = "AQZSYWZlZXECDTEyMzQ1Njc4OVQxMjUDFDIwMjEtMDctMTJUMTQ6MjU6MDlaBAM3ODYFAjI1";
$string_zatca_base64 = "ARBTcGVjaW1lbiBjb21wYW55AgkxMjM0NTY3ODkDFDIwMjAtMDEtMDFUMDk6MzQ6NTZaBAMxMjAFAjIw";

$decoded = base64_decode($string_zatca_base64);

//print_r($decoded)
//raw data
//\u0001\u0006Rafeeq\u0002\t123456789\u0003\u00142021-07-12T14:25:09Z\u0004\u0003786\u0005\u000225

$result_data = preg_replace('/[\x00-\x1F\x80-\xFF]/', ',', $decoded);

$arrayOfData = explode(',,', $result_data);


print __METHOD__." result=".var_export($arrayOfData, true)."\n";
$this->assertEquals("", $arrayOfData[0]);
$this->assertEquals("Specimen company", $arrayOfData[1]);
$this->assertEquals("123456789", $arrayOfData[2]);
$this->assertEquals("2020-01-01T09:34:56Z", $arrayOfData[3]);
$this->assertEquals("120", $arrayOfData[4]);
$this->assertEquals("20", $arrayOfData[5]);

return 1;
}
}

0 comments on commit cb93ab4

Please sign in to comment.