Skip to content

Constructing a Standard Invoice

Khadija Karkhanawala edited this page Dec 14, 2023 · 7 revisions

Constructing the Invoice

For simplified invoices(B2C), you need to sign and create the QR-code yourself. For standard invoices (B2B), you use ZATCA's clearance API which signs and adds the QR Code for you.

Using the clearance API also counts as reporting the invoice, so you don't need to do anything else from ZATCA's side.

They'll return a Base64-encoded XML e-invoice that's signed and also has a QR Code.

In this guide we'll create and upload an invoice and parse the QR code ZATCA gives us back so we can render it.

#===============================================================================
# 1. Setup the Invoice Values
#===============================================================================
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: false,
  third_party: false,
  nominal: false,
  exports: false,
  summary: false,
  self_billed: false
) # => "0100000"

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
)

# NOTE: For credit or debit notes you must also fill out
# invoice.instruction_note with an explanation on why
# this note was issued.

#===============================================================================
# Done! You now have an invoice constructed
#===============================================================================

Clear the Invoice

Make sure to check the compliance first.

ZATCA will be doing the signing (also known as clearance) for B2B/Standard invoices. We can use the clearance API to do this for us.

# Construct a client with the username and password
client = ZATCA::Client.new(username: username, password: password, environment: :simulation)

# Send the request to ZATCA
response = client.clear_invoice(
  uuid: invoice.uuid,
  invoice_hash: invoice.generate_hash,
  invoice: invoice.to_base64,
  cleared: false
)

# This is the Base64 that we will submit via reporting and extract the QR Code out of
# This is technically a signed XML e-invoice encoded as Base64
signed_base64 = response["clearedInvoice"]

response = client.report_invoice(
  uuid: invoice.uuid,
  invoice_hash: invoice.generate_hash,
  invoice: signed_base64,
  cleared: true
)

Extracting the QR Code

From the Base64 that ZATCA signed for us, we can simply do this:

qr_base64 = ZATCA.extract_qr_base64_from_base64_invoice(signed_base64)
# => "ARNBY21lIFdpZGdldOKAmXMgTFREAg8zMTAwOTQwMTAzMDAwMDMDFDIwMjItMDktMDdUMTI6MjE6MjhaBAQ0LjYwBQMwLjYGLFY0ZlovZ1VCNWZVbUw4VVZFR29IMFN1R0hVN1ZSQ0FtV01tdUtmMFdzQ...

# This is a Base64 QR code, we still need to render it.

Rendering the QR Code

See this page