GithubHelp home page GithubHelp logo

mrsool / zatca Goto Github PK

View Code? Open in Web Editor NEW
36.0 18.0 8.0 50.38 MB

An unofficial Ruby library for generating ZATCA e-invoices, QR Codes, and submitting e-invoices to ZATCA's servers.

License: MIT License

Ruby 99.89% Shell 0.11%
zatca taxes saudi-arabia qrcode e-invoicing ruby fatoora zatca-second-phase

zatca's Issues

Httpx dependency needs an update

There's a known problem for windows users that is only solved in later versions of httpx.
For reference: HoneyryderChuck/httpx#19

I'm running into the same issue when running the application. I confirm the issue is solved after I forked the repo and updated the dependency myself.

How to add new field in hash

How to add new field in hash?

I have tried adding new fieds in tag schema by overriding the tag schema behavior by going in the lib folder and overriding the tag.rb file by freezing the added field but it gives Argument error undefined

My hash:

tags = ZATCA::Tags.new({
  seller_name: "Mrsool",
  vat_registration_number: "310228833400003",
  timestamp: "2021-10-20T19:29:32+03:00",
  vat_total: "15",
  invoice_total: "115",
  invoice_number: '212'
})

error:
Image Pasted at 2021-11-19 12-12

Can you help me?

Always fails with invalid OTP

I have set valid OTP with the code below and correct VAT numbers etc

require "zatca"

vat_id = "131231231231312312"

# Four digits, each digit acting as a bool. The order is as follows: Standard Invoice, Simplified, future use, future use
invoice_type = "1100"

options = {
  common_name: "1231231123123123",
  organization_identifier: vat_id,
  organization_name: "testing",
  organization_unit: "IT",
  country: "SA",
  invoice_type: invoice_type,
  address: "Riyadh, Al Olaya D, Al Olaya, 111111",
  business_category: "IT",

  # The solution provider name
  egs_solution_name: "zxvasfsfsfadf",

  # The model of the unit the stamp is being generated for
  egs_model: "10001",

  # If you have multiple devices each should have a unique serial number
  egs_serial_number: "100000211"
}

# ENSURE THAT YOU USE YOUR OWN PRIVATE KEY by passing private_key_path and private_key_password.
# Otherwise the SDK will generate a passwordless one and delete it (this is only for testing purposes)
# Modes:
# :production (Via Fatoora Portal)
# :sandbox (Via Developer Portal)
# :simulation (Via Fatoora Portal - Simulation)
# For the sandbox set production_mode to false, otherwise set it to true. It is true by default.
#generator = ZATCA::Signing::CSR.new(csr_options: options, mode: :production)
generator = ZATCA::Signing::CSR.new(csr_options: options, mode: :simulation, private_key_path: '/home/ubuntu/myzats/ec-secp256k1-priv-key.pem')

# This is the CSR as PEM
csr = generator.generate

# Output the CSR
puts "CSR: #{csr}"

# ZATCA's API expects us to encode the PEM to Base64
csr_base64 = Base64.strict_encode64(csr)

# Output the CSR base64
puts "CSR: #{csr_base64}"

# Construct an unauthenticated API client, this is the only endpoint that is unauthenticated
client = ZATCA::Client.new(username: "", password: "")

# Get this OTP from Fatoora portal
otp = "253253"
response = client.issue_csid(csr: csr_base64, otp: otp)

# Output the response
puts "Response: #{response}"

always shows
Response: Invalid OTP

out of char range when generating tags for qrcode

I'm facing this error here at tag 8 and 9 respectively... any guidance is appreciated

C:/Ruby32-x64/lib/ruby/gems/3.2.0/gems/zatca-1.1.0/lib/zatca/tag.rb:40:in chr': 550 out of char range (RangeError) from C:/Ruby32-x64/lib/ruby/gems/3.2.0/gems/zatca-1.1.0/lib/zatca/tag.rb:40:in to_tlv'
from C:/Ruby32-x64/lib/ruby/gems/3.2.0/gems/zatca-1.1.0/lib/zatca/tags.rb:47:in map' from C:/Ruby32-x64/lib/ruby/gems/3.2.0/gems/zatca-1.1.0/lib/zatca/tags.rb:47:in to_tlv'
from C:/Ruby32-x64/lib/ruby/gems/3.2.0/gems/zatca-1.1.0/lib/zatca/tags.rb:33:in `to_base64'

C:/Ruby32-x64/lib/ruby/gems/3.2.0/gems/zatca-1.1.0/lib/zatca/tag.rb:40:in chr': 512 out of char range (RangeError) from C:/Ruby32-x64/lib/ruby/gems/3.2.0/gems/zatca-1.1.0/lib/zatca/tag.rb:40:in to_tlv'
from C:/Ruby32-x64/lib/ruby/gems/3.2.0/gems/zatca-1.1.0/lib/zatca/tags.rb:47:in map' from C:/Ruby32-x64/lib/ruby/gems/3.2.0/gems/zatca-1.1.0/lib/zatca/tags.rb:47:in to_tlv'
from C:/Ruby32-x64/lib/ruby/gems/3.2.0/gems/zatca-1.1.0/lib/zatca/tags.rb:33:in `to_base64'


tags = ZATCA::Tags.new({
  seller_name: "Acme Widgets LTD",
  vat_registration_number: "311111111101113",
  timestamp: invoice_timestamp,
  vat_total: "30.15",
  invoice_total: "231.15",
  xml_invoice_hash: invoice_hash,

  # These 3 properties on the invoice assume you have signed it before.
  # They are nil unless signed.
  ecdsa_signature: invoice.signed_hash,
  ecdsa_public_key: invoice.public_key_bytes,
  ecdsa_stamp_signature: invoice.certificate_signature
})

# Turn the tags to TLV and then encode that TLV to base64
invoice.qr_code = tags.to_base64

Enhance How Gem is Required

It would be great to have a structure similar to ActiveSupport, something like so:

  • Rename gem to zatca (it already currently needs to be required as zatca zatca anyways).
  • Allow developers to either require "zatca" (all of zatca) or specific modules like require "zatca/qr_code"

Need help with discount fields when reporting invoice

I have a confusion regarding how to handle discounts at the invoice level versus the line item level. Currently, applying the discount solely to the invoice line items without reflecting it at the document level doesn't trigger any warnings or errors. However, when the total discount from the invoice lines is added to the discount at the document level and allowance_total_amount, these warning appears in zatca response:

{
  "type": "WARNING",
   "code": "BR-S-08",
   "category": "EN_16931",
   "message": "لكل رمز من فئات ضريبة القيمة المضافة (BT-119) الذي يكون فيه الرمز الخاصّ بفئة ضريبة القيمة المضافة (BT-118) “خاضع للضريبة بالنسبة الأساسية“، يجب أن يكون المبلغ الخاضع للضريبة ضمن فئة ضريبة القيمة المضافة (BT-116) مساويًا لمجموع المبالغ الصافية على مستوى بند الفاتورة (BT-131) مطروحًا منه مجموع مبالغ الخصم على مستوى المستند(BT-92) بالإضافة إلى مجموع مبالغ الرسوم على مستوى المستند (BT-99) في الحالات التي تكون فيها الرموز الخاصّة بفئة ضريبة القيمة المضافة (BT-151, BT-95, BT-102) “خاضعة للضريبة بالنسبة الأساسية“.، ونسبة ضريبة القيمة المضافة للعناصر (BT-152,BT-96,BT-103) يجب أن تساوي نسبة ضريبة القيمة المضافة للعنصر (BT-119).",
    "status": "WARNING"
},
 {
    "type": "WARNING",
    "code": "BR-CO-13",
     "category": "EN_16931",
     "message": "المبلغ الإجمالي للفاتورة غير شامل ضريبة القيمة المضافة (BT-109) = Σ صافي المبلغ على مستوى بند الفاتورة (BT-131) - مجموع الخصومات على مستوى المستند (BT-107) + مجموع الرسوم على مستوى المستند (BT-108).",
      "status": "WARNING"
 }

Despite there being only one discount amount per invoice line (in my case that I'm testing), this same discount amount is present in both "discount_amount" and "allowance_total_amount" at the document level so as per the documentation it's correct.

Here's the relevant part of the xml for the invoice that gets a pass without errors or warnings (notice discount is only on invoice line)

    <cac:AllowanceCharge>
        <cbc:ChargeIndicator>false</cbc:ChargeIndicator>
        <cbc:AllowanceChargeReason>discount</cbc:AllowanceChargeReason>
        <cbc:Amount currencyID="SAR">0.00</cbc:Amount>
        <cac:TaxCategory>
            <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5305">S</cbc:ID>
            <cbc:Percent>15</cbc:Percent>
            <cac:TaxScheme>
                <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5153">VAT</cbc:ID>
            </cac:TaxScheme>
        </cac:TaxCategory>
        <cac:TaxCategory>
            <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5305">S</cbc:ID>
            <cbc:Percent>15</cbc:Percent>
            <cac:TaxScheme>
                <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5153">VAT</cbc:ID>
            </cac:TaxScheme>
        </cac:TaxCategory>
    </cac:AllowanceCharge>

....

    <cac:LegalMonetaryTotal>
        <cbc:LineExtensionAmount currencyID="SAR">286.67</cbc:LineExtensionAmount>
        <cbc:TaxExclusiveAmount currencyID="SAR">286.67</cbc:TaxExclusiveAmount>
        <cbc:TaxInclusiveAmount currencyID="SAR">329.68</cbc:TaxInclusiveAmount>
        <cbc:AllowanceTotalAmount currencyID="SAR">0.00</cbc:AllowanceTotalAmount>
        <cbc:PrepaidAmount currencyID="SAR">0.00</cbc:PrepaidAmount>
        <cbc:PayableAmount currencyID="SAR">329.68</cbc:PayableAmount>
    </cac:LegalMonetaryTotal>

...

    <cac:InvoiceLine>
        <cbc:ID>1</cbc:ID>
        <cbc:InvoicedQuantity unitCode="PCE">1.000000</cbc:InvoicedQuantity>
        <cbc:LineExtensionAmount currencyID="SAR">234.50</cbc:LineExtensionAmount>
        <cac:TaxTotal>
            <cbc:TaxAmount currencyID="SAR">35.18</cbc:TaxAmount>
            <cbc:RoundingAmount currencyID="SAR">269.68</cbc:RoundingAmount>
        </cac:TaxTotal>
        <cac:Item>
            <cbc:Name>...</cbc:Name>
            <cac:ClassifiedTaxCategory>
                <cbc:ID>S</cbc:ID>
                <cbc:Percent>15.00</cbc:Percent>
                <cac:TaxScheme>
                    <cbc:ID>VAT</cbc:ID>
                </cac:TaxScheme>
            </cac:ClassifiedTaxCategory>
        </cac:Item>
        <cac:Price>
            <cbc:PriceAmount currencyID="SAR">234.50</cbc:PriceAmount>
            <cac:AllowanceCharge>
                <cbc:ChargeIndicator>false</cbc:ChargeIndicator>
                <cbc:AllowanceChargeReason>discount</cbc:AllowanceChargeReason>
                <cbc:Amount currencyID="SAR">100.50</cbc:Amount>
            </cac:AllowanceCharge>
        </cac:Price>
    </cac:InvoiceLine>
    <cac:InvoiceLine>
        <cbc:ID>2</cbc:ID>
        <cbc:InvoicedQuantity unitCode="PCE">1.000000</cbc:InvoicedQuantity>
        <cbc:LineExtensionAmount currencyID="SAR">52.17</cbc:LineExtensionAmount>
        <cac:TaxTotal>
            <cbc:TaxAmount currencyID="SAR">7.83</cbc:TaxAmount>
            <cbc:RoundingAmount currencyID="SAR">60.00</cbc:RoundingAmount>
        </cac:TaxTotal>
        <cac:Item>
            <cbc:Name>...</cbc:Name>
            <cac:ClassifiedTaxCategory>
                <cbc:ID>S</cbc:ID>
                <cbc:Percent>15.00</cbc:Percent>
                <cac:TaxScheme>
                    <cbc:ID>VAT</cbc:ID>
                </cac:TaxScheme>
            </cac:ClassifiedTaxCategory>
        </cac:Item>
        <cac:Price>
            <cbc:PriceAmount currencyID="SAR">52.17</cbc:PriceAmount>
            <cac:AllowanceCharge>
                <cbc:ChargeIndicator>false</cbc:ChargeIndicator>
                <cbc:AllowanceChargeReason>discount</cbc:AllowanceChargeReason>
                <cbc:Amount currencyID="SAR">0.00</cbc:Amount>
            </cac:AllowanceCharge>
        </cac:Price>
    </cac:InvoiceLine>

And this is the xml that gets the warnings mentioned

    <cac:AllowanceCharge>
        <cbc:ChargeIndicator>false</cbc:ChargeIndicator>
        <cbc:AllowanceChargeReason>discount</cbc:AllowanceChargeReason>
        <cbc:Amount currencyID="SAR">100.50</cbc:Amount>
        <cac:TaxCategory>
            <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5305">S</cbc:ID>
            <cbc:Percent>15</cbc:Percent>
            <cac:TaxScheme>
                <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5153">VAT</cbc:ID>
            </cac:TaxScheme>
        </cac:TaxCategory>
        <cac:TaxCategory>
            <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5305">S</cbc:ID>
            <cbc:Percent>15</cbc:Percent>
            <cac:TaxScheme>
                <cbc:ID schemeAgencyID="6" schemeID="UN/ECE 5153">VAT</cbc:ID>
            </cac:TaxScheme>
        </cac:TaxCategory>
    </cac:AllowanceCharge>

...

    <cac:LegalMonetaryTotal>
        <cbc:LineExtensionAmount currencyID="SAR">286.67</cbc:LineExtensionAmount>
        <cbc:TaxExclusiveAmount currencyID="SAR">286.67</cbc:TaxExclusiveAmount>
        <cbc:TaxInclusiveAmount currencyID="SAR">329.68</cbc:TaxInclusiveAmount>
        <cbc:AllowanceTotalAmount currencyID="SAR">100.50</cbc:AllowanceTotalAmount>
        <cbc:PrepaidAmount currencyID="SAR">0.00</cbc:PrepaidAmount>
        <cbc:PayableAmount currencyID="SAR">329.68</cbc:PayableAmount>
    </cac:LegalMonetaryTotal>

...

    <cac:InvoiceLine>
        <cbc:ID>1</cbc:ID>
        <cbc:InvoicedQuantity unitCode="PCE">1.000000</cbc:InvoicedQuantity>
        <cbc:LineExtensionAmount currencyID="SAR">234.50</cbc:LineExtensionAmount>
        <cac:TaxTotal>
            <cbc:TaxAmount currencyID="SAR">35.18</cbc:TaxAmount>
            <cbc:RoundingAmount currencyID="SAR">269.68</cbc:RoundingAmount>
        </cac:TaxTotal>
        <cac:Item>
            <cbc:Name>...</cbc:Name>
            <cac:ClassifiedTaxCategory>
                <cbc:ID>S</cbc:ID>
                <cbc:Percent>15.00</cbc:Percent>
                <cac:TaxScheme>
                    <cbc:ID>VAT</cbc:ID>
                </cac:TaxScheme>
            </cac:ClassifiedTaxCategory>
        </cac:Item>
        <cac:Price>
            <cbc:PriceAmount currencyID="SAR">234.50</cbc:PriceAmount>
            <cac:AllowanceCharge>
                <cbc:ChargeIndicator>false</cbc:ChargeIndicator>
                <cbc:AllowanceChargeReason>discount</cbc:AllowanceChargeReason>
                <cbc:Amount currencyID="SAR">100.50</cbc:Amount>
            </cac:AllowanceCharge>
        </cac:Price>
    </cac:InvoiceLine>

    <cac:InvoiceLine>
        <cbc:ID>2</cbc:ID>
        <cbc:InvoicedQuantity unitCode="PCE">1.000000</cbc:InvoicedQuantity>
        <cbc:LineExtensionAmount currencyID="SAR">52.17</cbc:LineExtensionAmount>
        <cac:TaxTotal>
            <cbc:TaxAmount currencyID="SAR">7.83</cbc:TaxAmount>
            <cbc:RoundingAmount currencyID="SAR">60.00</cbc:RoundingAmount>
        </cac:TaxTotal>
        <cac:Item>
            <cbc:Name>...</cbc:Name>
            <cac:ClassifiedTaxCategory>
                <cbc:ID>S</cbc:ID>
                <cbc:Percent>15.00</cbc:Percent>
                <cac:TaxScheme>
                    <cbc:ID>VAT</cbc:ID>
                </cac:TaxScheme>
            </cac:ClassifiedTaxCategory>
        </cac:Item>
        <cac:Price>
            <cbc:PriceAmount currencyID="SAR">52.17</cbc:PriceAmount>
            <cac:AllowanceCharge>
                <cbc:ChargeIndicator>false</cbc:ChargeIndicator>
                <cbc:AllowanceChargeReason>discount</cbc:AllowanceChargeReason>
                <cbc:Amount currencyID="SAR">0.00</cbc:Amount>
            </cac:AllowanceCharge>
        </cac:Price>
    </cac:InvoiceLine>

API integration

Wrap ZATCA's API and add samples of requests and valid responses.

Trouble with Phase 2 - Generating a CSR

I followed https://github.com/mrsool/zatca/wiki/Generating-a-CSR

  1. Installed gem
    gem install zatca

  2. Created a file called csr.rb as below

vat_id = "XXXXXXXX"

# Four digits, each digit acting as a bool. The order is as follows: Standard Invoice, Simplified, future use, future use
invoice_type = "1100"

options = {
  common_name: "XXXXXX",
  organization_identifier: vat_id,
  organization_name: "XXXXXX",
  organization_unit: "IT",
  country: "SA",
  invoice_type: invoice_type, 
  address: "Riyadh, Al Olaya D, Al Olaya, 12211",
  business_category: "IT",

  # The solution provider name
  egs_solution_name: "XXXXXXXXX",

  # The model of the unit the stamp is being generated for
  egs_model: "1000",

  # If you have multiple devices each should have a unique serial number
  egs_serial_number: "10000021"
}

# ENSURE THAT YOU USE YOUR OWN PRIVATE KEY by passing private_key_path and private_key_password.
# Otherwise the SDK will generate a passwordless one and delete it (this is only for testing purposes)
# Modes:
# :production (Via Fatoora Portal)
# :sandbox (Via Developer Portal)
# :simulation (Via Fatoora Portal - Simulation)
# For the sandbox set production_mode to false, otherwise set it to true. It is true by default.
#generator = ZATCA::Signing::CSR.new(csr_options: options, mode: :production)
generator = ZATCA::Signing::CSR.new(csr_options: options, mode: :sandbox)

# This is the CSR as PEM
csr = generator.generate

# ZATCA's API expects us to encode the PEM to Base64
csr_base64 = Base64.strict_encode64(csr)

# Construct an unauthenticated API client, this is the only endpoint that is unauthenticated
client = ZATCA::Client.new(username: "", password: "")

# Get this OTP from Fatoora portal
otp = "111111" 
response = client.issue_csid(csr: csr_base64, otp: otp)

# Output the response
puts "Response: #{response}"
  1. Error when running rb file
    ruby csr.rb
csr.rb:34:in `<main>': uninitialized constant ZATCA (NameError)

generator = ZATCA::Signing::CSR.new(csr_options: options, mode: :sandbox)
                          ^^^^^
  1. gem list (this shows Zatca)
*** LOCAL GEMS ***

abbrev (default: 0.1.1)
actioncable (7.0.4)
actionmailbox (7.0.4)
actionmailer (7.0.4)
actionpack (7.0.4)
actiontext (7.0.4)
actionview (7.0.4)
activejob (7.0.4)
activemodel (7.0.4)
activerecord (7.0.4)
activestorage (7.0.4)
activesupport (7.0.4)
base64 (default: 0.1.1)
benchmark (default: 0.2.1)
bigdecimal (default: 3.1.3)
builder (3.2.4)
bundler (2.4.22, default: 2.4.10)
cgi (default: 0.3.6)
chunky_png (1.4.0)
concurrent-ruby (1.2.2)
crass (1.0.6)
csv (default: 3.2.6)
date (default: 3.3.3)
debug (1.7.1)
delegate (default: 0.3.0)
did_you_mean (default: 1.6.3)
digest (default: 3.1.1)
drb (default: 2.1.1)
dry-configurable (1.1.0)
dry-core (1.0.1)
dry-inflector (1.0.0)
dry-initializer (3.1.1)
dry-logic (1.5.0)
dry-schema (1.13.3)
dry-types (1.7.1)
english (default: 0.7.2)
erb (default: 4.0.2)
error_highlight (default: 0.5.1)
erubi (1.12.0)
etc (default: 1.4.2)
fcntl (default: 1.0.2)
fiddle (default: 1.1.1)
fileutils (default: 1.7.0)
find (default: 0.1.1)
forwardable (default: 1.3.3)
getoptlong (default: 0.2.0)
globalid (1.2.1)
http-2-next (1.0.1)
httpx (0.21.1)
i18n (1.14.1)
io-console (default: 0.6.0)
io-nonblock (default: 0.2.0)
io-wait (default: 0.3.0)
ipaddr (default: 1.2.5)
irb (default: 1.6.2)
json (default: 2.6.3)
logger (default: 1.5.3)
loofah (2.22.0)
mail (2.8.1)
marcel (1.0.2)
matrix (0.4.2)
method_source (1.0.0)
mini_mime (1.1.5)
minitest (5.20.0, 5.16.3)
mutex_m (default: 0.1.2)
net-ftp (0.2.0)
net-http (default: 0.3.2)
net-imap (0.3.4)
net-pop (0.1.2)
net-protocol (default: 0.2.1)
net-smtp (0.3.3)
nio4r (2.7.0)
nkf (default: 0.1.2)
nokogiri (1.15.5 x86_64-linux)
observer (default: 0.1.1)
open-uri (default: 0.3.0)
open3 (default: 0.1.2)
openssl (default: 3.1.0)
optparse (default: 0.3.1)
ostruct (default: 0.5.5)
pathname (default: 0.2.1)
power_assert (2.0.3)
pp (default: 0.4.0)
prettyprint (default: 0.1.1)
prime (0.1.2)
pstore (default: 0.1.2)
psych (default: 5.0.1)
racc (default: 1.6.2)
rack (2.2.8)
rack-test (2.1.0)
rails (7.0.4)
rails-dom-testing (2.2.0)
rails-html-sanitizer (1.6.0)
railties (7.0.4)
rake (13.0.6)
rbs (2.8.2)
rdoc (default: 6.5.0)
readline (default: 0.0.3)
readline-ext (default: 0.1.5)
reline (default: 0.3.2)
resolv (default: 0.2.2)
resolv-replace (default: 0.1.1)
rexml (3.2.5)
rinda (default: 0.1.1)
rqrcode (2.1.2)
rqrcode_core (1.2.0)
rss (0.2.9)
ruby2_keywords (default: 0.0.5)
securerandom (default: 0.2.2)
set (default: 1.0.3)
shellwords (default: 0.1.0)
singleton (default: 0.1.1)
starkbank-ecdsa (2.0.0)
stringio (default: 3.0.4)
strscan (default: 3.0.5)
syntax_suggest (default: 1.0.2)
syslog (default: 0.1.1)
tempfile (default: 0.1.3)
test-unit (3.5.7)
thor (1.3.0)
time (default: 0.2.2)
timeout (default: 0.3.1)
tmpdir (default: 0.1.3)
tsort (default: 0.1.1)
typeprof (0.21.3)
tzinfo (2.0.6)
un (default: 0.2.1)
uri (default: 0.12.1)
weakref (default: 0.1.2)
websocket-driver (0.7.6)
websocket-extensions (0.1.5)
yaml (default: 0.2.1)
zatca (1.0.1)
zeitwerk (2.6.12)
zlib (default: 3.0.0)

Guidance to check the compliance of invoices - Phase 2

We have the documentation here https://github.com/mrsool/zatca/wiki/Checking-the-Compliance-of-an-Invoice

Please guide me on the optimal solution to pass compliance for invoices. I understand the system needs to send total 6 including standard & simplified. I have managed to generate simulation mode but need help on how to proceed. Any template invoices or samples where i can just change VAT numbers or test information to have this processed.

Issue with Zatca with Phase 2 - Generating a CSR

I followed https://github.com/mrsool/zatca/wiki/Generating-a-CSR

  1. Installed gem
    gem install zatca

  2. Created a file called csr.rb as below

require "zatca"
vat_id = "XXXXXXXX"

# Four digits, each digit acting as a bool. The order is as follows: Standard Invoice, Simplified, future use, future use
invoice_type = "1100"

options = {
  common_name: "XXXXXX",
  organization_identifier: vat_id,
  organization_name: "XXXXXX",
  organization_unit: "IT",
  country: "SA",
  invoice_type: invoice_type, 
  address: "Riyadh, Al Olaya D, Al Olaya, 12211",
  business_category: "IT",

  # The solution provider name
  egs_solution_name: "XXXXXXXXX",

  # The model of the unit the stamp is being generated for
  egs_model: "1000",

  # If you have multiple devices each should have a unique serial number
  egs_serial_number: "10000021"
}

# ENSURE THAT YOU USE YOUR OWN PRIVATE KEY by passing private_key_path and private_key_password.
# Otherwise the SDK will generate a passwordless one and delete it (this is only for testing purposes)
# Modes:
# :production (Via Fatoora Portal)
# :sandbox (Via Developer Portal)
# :simulation (Via Fatoora Portal - Simulation)
# For the sandbox set production_mode to false, otherwise set it to true. It is true by default.
#generator = ZATCA::Signing::CSR.new(csr_options: options, mode: :production)
generator = ZATCA::Signing::CSR.new(csr_options: options, mode: :sandbox)

# This is the CSR as PEM
csr = generator.generate

# ZATCA's API expects us to encode the PEM to Base64
csr_base64 = Base64.strict_encode64(csr)

# Construct an unauthenticated API client, this is the only endpoint that is unauthenticated
client = ZATCA::Client.new(username: "", password: "")

# Get this OTP from Fatoora portal
otp = "111111" 
response = client.issue_csid(csr: csr_base64, otp: otp)

# Output the response
puts "Response: #{response}"
  1. Error when running rb file
    ruby csr.rb
/home/ubuntu/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/zatca-1.0.1/lib/zatca/signing/csr.rb:118:in `generate_key!': pkeys are immutable on OpenSSL 3.0 (OpenSSL::PKey::PKeyError)
	from /home/ubuntu/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/zatca-1.0.1/lib/zatca/signing/csr.rb:118:in `generate_key'
	from /home/ubuntu/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/zatca-1.0.1/lib/zatca/signing/csr.rb:103:in `set_key'
	from /home/ubuntu/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/zatca-1.0.1/lib/zatca/signing/csr.rb:23:in `generate'
	from csr.rb:39:in `<main>'

gsub : invalid byte sequence in US-ASCII

When I try to follow example of implementation, I got an issue on the hashing part. Here is the error

Traceback (most recent call last):
        7: from test.rb:289:in `<main>'
        6: from /var/lib/gems/2.7.0/gems/zatca-1.1.0/lib/zatca/ubl/invoice.rb:188:in `to_base64'
        5: from /var/lib/gems/2.7.0/gems/zatca-1.1.0/lib/zatca/ubl/invoice.rb:214:in `generate_xml'
        4: from /var/lib/gems/2.7.0/gems/zatca-1.1.0/lib/zatca/ubl/base_component.rb:135:in `generate_xml'
        3: from /var/lib/gems/2.7.0/gems/zatca-1.1.0/lib/zatca/ubl/builder.rb:24:in `build'
        2: from /var/lib/gems/2.7.0/gems/zatca-1.1.0/lib/zatca/ubl/builder.rb:37:in `apply_hacks_to_invoice'
        1: from /var/lib/gems/2.7.0/gems/zatca-1.1.0/lib/zatca/ubl/builder.rb:45:in `apply_qualifying_properties_hacks'
/var/lib/gems/2.7.0/gems/zatca-1.1.0/lib/zatca/ubl/builder.rb:45:in `gsub': invalid byte sequence in US-ASCII (ArgumentError)

Here is my code

#encoding: utf-8

require "zatca"

vat_id = "310000000000003"

# Four digits, each digit acting as a bool. The order is as follows: Standard Invoice, Simplified, future use, future use
invoice_type = "1100"

options = {
  common_name: "The common name to be used in the certificate",
  organization_identifier: vat_id,
  organization_name: "The name of your organization",
  organization_unit: "A subunit in your organization",
  country: "SA",
  invoice_type: invoice_type,
  address: "Riyadh 1234 Street",
  business_category: "Your business category",

  # The solution provider name
  egs_solution_name: "mrsoolsdk",

  # The model of the unit the stamp is being generated for
  egs_model: "1",

  # If you have multiple devices each should have a unique serial number
  egs_serial_number: "1-Zatca|2-Hububl|3-fa4e99b8-c9c5-4c37-957a-3926352c34e6"
}

generator = ZATCA::Signing::CSR.new(csr_options: options, mode: :sandbox)


csr = generator.generate

# ZATCA's API expects us to encode the PEM to Base64
csr_base64 = Base64.strict_encode64(csr)

client = ZATCA::Client.new(username: "", password: "", environment: :sandbox)

otp = "123345"
response = client.issue_csid(csr: csr_base64, otp: otp)

request_id = response["requestID"]
binary_security_token = response["binarySecurityToken"]
secret = response["secret"]
#puts response ;

#binarySecurityToken as user
#secret as passwort

client = ZATCA::Client.new(username: binary_security_token, password: secret, environment: :sandbox)



invoice_id = "SME00010"
invoice_uuid = "8e6000cf-1a98-4174-b3e7-b5d5954bc10d"
note = "ABC"
note_language_id = "ar"
issue_date = "2022-08-17"
issue_time = "17:41:08"

invoice_subtype = ZATCA::UBL::InvoiceSubtypeBuilder.build(
  simplified: true,
  third_party: false,
  nominal: false,
  exports: false,
  summary: false,
  self_billed: false
) # => "0200000"

payment_means_code = ZATCA::UBL::Invoice::PAYMENT_MEANS[:bank_card] # => "48"
invoice_type = ZATCA::UBL::Invoice::TYPES[:invoice] # => "388"

invoice_counter_value = "10"
previous_invoice_hash = "NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ=="
currency_code = "SAR"
vat_registration_number = "311111111101113"

#===============================================================================
# 2. Setup the Invoice's Nested Properties
#===============================================================================

# Create the supplier party (the party issuing the invoice, e.g. the seller)
accounting_supplier_party = ZATCA::UBL::CommonAggregateComponents::Party.new(
  party_identification: ZATCA::UBL::CommonAggregateComponents::PartyIdentification.new(
    id: "324223432432432"
  ),
  postal_address: ZATCA::UBL::CommonAggregateComponents::PostalAddress.new(
    street_name: "الامير سلطان",
    additional_street_name: nil,
    building_number: "3242",
    plot_identification: "4323",
    city_subdivision_name: "32423423",
    city_name: "الرياض | Riyadh",
    postal_zone: "32432",
    country_subentity: nil,
    country_identification_code: "SA"
  ),

  party_tax_scheme: ZATCA::UBL::CommonAggregateComponents::PartyTaxScheme.new(
    company_id: vat_registration_number
  ),

  party_legal_entity: ZATCA::UBL::CommonAggregateComponents::PartyLegalEntity.new(
    registration_name: "Acme Widgets LTD"
  )
)

# Create the customer party (the party receiving the invoice, e.g. the buyer)
accounting_customer_party = ZATCA::UBL::CommonAggregateComponents::Party.new(
  # party_identification: ZATCA::UBL::CommonAggregateComponents::PartyIdentification.new(
  #   id: "2345",
  #   scheme_id: "NAT"
  # ),
  party_identification: nil,
  postal_address: ZATCA::UBL::CommonAggregateComponents::PostalAddress.new(
    street_name: nil,
    additional_street_name: nil,
    building_number: nil,
    plot_identification: nil,
    city_subdivision_name: "32423423",
    city_name: nil,
    postal_zone: nil,
    country_subentity: nil,
    country_identification_code: "SA"
  ),

  party_tax_scheme: ZATCA::UBL::CommonAggregateComponents::PartyTaxScheme.new,
  party_legal_entity: nil
)

# Create the (optional) delivery object (detailing the delivery date and time)
# delivery = ZATCA::UBL::CommonAggregateComponents::Delivery.new(
#   actual_delivery_date: "2022-03-13",
#   latest_delivery_date: "2022-03-15"
# )

delivery = nil

# Create the allowance charges (e.g. discounts) for the invoice
allowance_charges = [
  ZATCA::UBL::CommonAggregateComponents::AllowanceCharge.new(
    charge_indicator: false,
    amount: "0.00",
    allowance_charge_reason: "discount",
    currency_id: "SAR",
    tax_categories: [
      # Yes, ZATCA's official valid sample duplicates these, not sure why
      ZATCA::UBL::CommonAggregateComponents::TaxCategory.new(
        tax_percent: "15"
      ),
      ZATCA::UBL::CommonAggregateComponents::TaxCategory.new(
        tax_percent: "15"
      )
    ]
  )
]

# Create the tax totals for the invoice
# ZATCA's official valid sample has two of these, not sure why
tax_totals = [
  ZATCA::UBL::CommonAggregateComponents::TaxTotal.new(
    tax_amount: "30.15"
  ),
  ZATCA::UBL::CommonAggregateComponents::TaxTotal.new(
    tax_amount: "30.15",
    tax_subtotal_amount: "30.15",
    taxable_amount: "201.00",
    tax_category: ZATCA::UBL::CommonAggregateComponents::TaxCategory.new(
      tax_percent: "15.00"
    )
  )
]

# Create the legal monetary total for the invoice
legal_monetary_total = ZATCA::UBL::CommonAggregateComponents::LegalMonetaryTotal.new(
  line_extension_amount: "201.00",
  tax_exclusive_amount: "201.00",
  tax_inclusive_amount: "231.15",
  allowance_total_amount: "0.00",
  prepaid_amount: "0.00",
  payable_amount: "231.15"
)

# Create the invoice lines for the invoice (the list of items that were sold)
invoice_lines = [
  # Book
  ZATCA::UBL::CommonAggregateComponents::InvoiceLine.new(
    invoiced_quantity: "33.000000",
    invoiced_quantity_unit_code: "PCE",
    line_extension_amount: "99.00",
    tax_total: ZATCA::UBL::CommonAggregateComponents::TaxTotal.new(
      tax_amount: "14.85",
      rounding_amount: "113.85"
    ),
    item: ZATCA::UBL::CommonAggregateComponents::Item.new(
      name: "كتاب"
    ),
    price: ZATCA::UBL::CommonAggregateComponents::Price.new(
      price_amount: "3.00",
      allowance_charge: ZATCA::UBL::CommonAggregateComponents::AllowanceCharge.new(
        charge_indicator: false,
        allowance_charge_reason: "discount",
        amount: "0.00",
        add_tax_category: false,

        # ZATCA's samples can sometimes have a nested tax scheme with an ID
        # and sometimes they omit it. Setting this boolean controls whether it is
        # present or not
        add_id: false
      )
    )
  ),

  # Pen
  ZATCA::UBL::CommonAggregateComponents::InvoiceLine.new(
    invoiced_quantity: "3.000000",
    invoiced_quantity_unit_code: "PCE",
    line_extension_amount: "102.00",
    tax_total: ZATCA::UBL::CommonAggregateComponents::TaxTotal.new(
      tax_amount: "15.30",
      rounding_amount: "117.30"
    ),
    item: ZATCA::UBL::CommonAggregateComponents::Item.new(
      name: "قلم"
    ),
    price: ZATCA::UBL::CommonAggregateComponents::Price.new(
      price_amount: "34.00",
      allowance_charge: ZATCA::UBL::CommonAggregateComponents::AllowanceCharge.new(
        charge_indicator: false,
        allowance_charge_reason: "discount",
        amount: "0.00",
        add_tax_category: false,
        add_id: false
      )
    )
  )
]

#===============================================================================
# 3. Construct the Invoice
#===============================================================================
# Construct the invoice using all of the above
invoice = ZATCA::UBL::Invoice.new(
  add_ids_to_allowance_charges: false,
  id: invoice_id,
  uuid: invoice_uuid,
  issue_date: issue_date,
  issue_time: issue_time,
  subtype: invoice_subtype,
  type: invoice_type,
  invoice_counter_value: invoice_counter_value,
  previous_invoice_hash: previous_invoice_hash,
  accounting_supplier_party: accounting_supplier_party,
  accounting_customer_party: accounting_customer_party,
  delivery: delivery,
  payment_means_code: payment_means_code,
  allowance_charges: allowance_charges,
  tax_totals: tax_totals,
  legal_monetary_total: legal_monetary_total,
  invoice_lines: invoice_lines,
  currency_code: currency_code,
  note: note,
  note_language_id: note_language_id
)

private_key_path = "path/to_your/private.key"

# Must be in pem format with header blocks
certificate_path = "path/to_your/certificate.pem"

# You can omit this if you don't need to customize it,
# the default value is the same as you see here.
signing_time = Time.now.utc.strftime("#{invoice.issue_date}T#{invoice.issue_time}")

# Sign the invoice (this adds a couple of new elements to the invoice)

# openssl ecparam -name secp256k1 -genkey -noout -out ec-secp256k1-priv-key.pem
invoice.sign(
  private_key_path: "/var/www/html/public/ec-secp256k1-priv-key.pem",
  certificate_path: "/var/www/html/public/.private/app.pem",
  signing_time: signing_time,
  decode_private_key_from_base64: false # Optional
)

# 2. Send the invoice to ZATCA
# Assuming you have already constructed a ZATCA::UBL::Invoice
response = client.compliance_check(
  invoice: invoice.to_base64,
  invoice_hash: invoice.generate_hash,
  uuid: invoice.uuid
)

puts response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.