sa: add Saudi Arabia tax regime#740
sa: add Saudi Arabia tax regime#740enrique-fernandez-polo wants to merge 28 commits intoinvopop:mainfrom
Conversation
TIN: 15 digits starting and ending with 3 per ZATCA BR-KSA-40. Includes a minimal regime definition (SA, SAR, Asia/Riyadh) to expose the Validate/Normalize entry points used by tests. Sources: - https://zatca.gov.sa - https://lookuptax.com/docs/tax-identification-number/saudi-arabia-tax-id-guide Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VAT rates: 15% since Jul 2020 (Royal Decree A/638), 5% since Jan 2018 (GCC Unified VAT Agreement). Zero/exempt handled via tax.GlobalVATKeys(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Seller IDs (BT-29-1): CRN, MOM, MLS, 700, SAG. Buyer IDs (BT-46-1): NAT, IQA, PAS, GCC, OTH. Format validation for numeric types per ZATCA E-Invoice XML Implementation Standard v1.2. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scenarios auto-generate legal notes for reverse charge and simplified invoices, with Arabic translations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Invoice validation per ZATCA rules: - BR-KSA-39: seller name required - BR-KSA-40: seller TIN required - BR-KSA-42: buyer name required on standard invoices Simplified invoices (B2C) skip customer requirement. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5 YAML examples exercising the full regime: - invoice-sa-standard: B2B with standard + zero-rated lines - invoice-sa-simplified: B2C POS, no customer - invoice-sa-credit-note: credit note referencing preceding invoice - invoice-sa-reverse-charge: B2B with reverse charge tag - invoice-sa-exempt: invoice with VAT-exempt lines Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
README covers VAT rates, identity types, validation rules, and ZATCA references. Generated sa.json regime data and updated regime-code schema. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Correct ministry names based on fact-check against ZATCA documentation: - MOMRA → MOMRAH (Ministry of Municipalities and Housing, post-2024 rename) - MHRSD Arabic: add full name "والتنمية الاجتماعية" (and Social Development) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add missing TIN (Tax Identification Number) identity type for buyer identification per ZATCA BR-KSA-14. TIN is a valid buyer-only scheme ID in the ZATCA e-invoicing specification. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement BR-KSA-81: standard invoice buyers must have either a VAT registration number (TaxID) or an alternative buyer identification (org.Identity). Also correct validation rule comments to reference the right ZATCA rules (BR-KSA-39, BR-06, BR-KSA-42). Document BR-KSA-25 (education/healthcare simplified invoice exception) as handled by the gobl.zatca addon. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Royal Decree → Royal Order No. A/638 - GCC Unified VAT Agreement → GCC VAT Framework Agreement - MOMRA → MOMRAH in seller identity table - Add TIN to buyer identity table - Add note that seller codes are also valid for buyers (BR-KSA-14) - Fix rule references: BR-KSA-39, BR-06, BR-KSA-42, BR-KSA-81 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix gofmt formatting in sa.go and remove unnecessary string conversion in sa_test.go. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the standalone README.md with a comprehensive Description field in the regime definition, following the project convention. Covers tax identification, VAT rates, identity types, invoice validation rules, and ZATCA Phase 2 addon reference. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9419ee1 to
d19fa7f
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace custom validation functions with inline validation.Match calls for identity code validation, and remove unnecessary Retained zero value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace ValidateStruct wrapper with direct validation.Match().Validate() calls for consistency with tax_identity.go pattern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #740 +/- ##
==========================================
+ Coverage 92.86% 92.89% +0.02%
==========================================
Files 331 335 +4
Lines 17260 17413 +153
==========================================
+ Hits 16029 16176 +147
- Misses 867 870 +3
- Partials 364 367 +3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds a new Saudi Arabia (SA) tax regime to GOBL, including VAT category definitions, identity types, invoice validation rules, and example documents/data artifacts to support regime expansion.
Changes:
- Introduces
regimes/sa/with regime registration, VAT category/rates, identity definitions/normalization, scenario notes, and invoice validation rules. - Adds SA to the regimes registry and to the tax regime code schema; generates
data/regimes/sa.json. - Adds SA invoice examples (YAML inputs + generated JSON envelopes) and updates
CHANGELOG.md.
Reviewed changes
Copilot reviewed 21 out of 26 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
regimes/sa/sa.go |
Defines and registers the SA regime (currency, timezone, validators/normalizers, scenarios, corrections). |
regimes/sa/tax_categories.go |
Adds SA VAT category using global VAT keys and historical standard rates (15%/5%). |
regimes/sa/tax_categories_test.go |
Tests SA VAT rate definitions and effective dates. |
regimes/sa/tax_identity.go |
Adds SA TIN format validation (currently regex-only). |
regimes/sa/tax_identity_test.go |
Tests SA TIN validation and normalization behaviors. |
regimes/sa/identities.go |
Defines seller/buyer identity type codes, normalization, and type-specific validation. |
regimes/sa/identities_test.go |
Tests identity type constants, validation, and normalization. |
regimes/sa/invoices.go |
Implements SA invoice validation rules for supplier/customer requirements and simplified behavior. |
regimes/sa/invoices_test.go |
Tests invoice validation scenarios (supplier/customer requirements; simplified vs standard; zero/exempt lines). |
regimes/sa/scenarios.go |
Adds scenario-driven legal notes for reverse-charge and simplified invoices. |
regimes/sa/scenarios_test.go |
Tests scenario notes are applied as expected. |
regimes/regimes.go |
Registers the SA regime via side-effect import. |
data/schemas/tax/regime-code.json |
Adds "SA" to the allowed regime codes schema. |
data/regimes/sa.json |
Generated SA regime definition data. |
examples/sa/invoice-sa-*.yaml |
Adds SA example invoice inputs (standard, simplified, reverse-charge, exempt, credit-note). |
examples/sa/out/invoice-sa-*.json |
Adds generated envelopes for the SA example invoices. |
CHANGELOG.md |
Notes addition of the new SA regime. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
samlown
left a comment
There was a problem hiding this comment.
Thanks for this! Looks good, although I think the sources of data need to be double checked. For example, where can the latest tax rates be found? What is the source for the list of identity types? Copilot also came up with a few suggestions.
| Code: tax.CategoryVAT, | ||
| Name: i18n.String{ | ||
| i18n.EN: "VAT", | ||
| i18n.AR: "ضريبة القيمة المضافة", |
There was a problem hiding this comment.
There is no short version of the name?
There was a problem hiding this comment.
It looks like there is one "ض.ق.م" Updated
|
|
||
| // validateTaxIdentity checks to ensure the SA TIN format is correct. | ||
| func validateTaxIdentity(tID *tax.Identity) error { | ||
| return validation.Match(tinRegex).Error("must be a 15-digit number starting and ending with 3").Validate(&tID.Code) |
There was a problem hiding this comment.
This breaks away from the standard pattern of using ValidateStruct and looses the field attribution. Also, as stated by Copilot, the it appears to be missing a Luhn check.
There was a problem hiding this comment.
I misunderstood a comment in another PR about how to use validation.Match My mistake
| // BR-KSA-39: Supplier VAT registration number required on all invoices. | ||
| validation.Field(&p.TaxID, | ||
| validation.Required, | ||
| tax.RequireIdentityCode, |
There was a problem hiding this comment.
Are you completely sure about this? Some countries often apply thresholds before registering for a VAT number.
There was a problem hiding this comment.
I struggled with this one. There is a mandatory VAT registration threshold of SAR 375,000 (and SAR 187,500 for voluntary), so not all businesses issuing invoices have a TIN.
At the same time, BR-KSA-39 requires the supplier's VAT registration number on all e-invoices.
The required TaxID should reside in the ZATCA add-on that verifies its presence when creating the e-invoice, not in the base regime. I think I mixed law and e-invoicing in some other requirements
| // IdentityTypeTIN is a Tax Identification Number (buyer-only). | ||
| IdentityTypeTIN cbc.Code = "TIN" |
There was a problem hiding this comment.
Not sure I understand this... this code is not the same as the Tax ID?
| i18n.EN: "Zakat, Tax and Customs Authority - VAT", | ||
| i18n.AR: "هيئة الزكاة والضريبة والجمارك - ضريبة القيمة المضافة", | ||
| }, | ||
| URL: "https://zatca.gov.sa/en/E-Invoicing/Introduction/Pages/What-is-e-invoicing.aspx", |
There was a problem hiding this comment.
I couldn't see any details in this page about VAT tax rates specifically. The objective here is to know where to look exactly when a tax rate change is expected.
There was a problem hiding this comment.
You're right, that page was about e-invoicing and didn't have VAT rate details. Updated the source URL to the ZATCA VAT Rules & Regulations page
| "github.com/invopop/validation" | ||
| ) | ||
|
|
||
| // Party identification scheme codes for seller (BT-29-1) and buyer (BT-46-1). |
There was a problem hiding this comment.
Just in general here, this codes feel very specific for an implementation as opposed to a tax Regime which is mostly focussed on tax law. Are these all standard Type codes? I don't see a source document to be able to verify. Passport for example already has a specific key (passport) inside the org.Identity definition.
There was a problem hiding this comment.
Ok now I get it. These codes (TIN, NAT, IQA, PAS, GCC, OTH) are ZATCA e-invoicing scheme IDs for BT-29-1/BT-46-1 They're implementation-specific, not tax law
Remove the supplier TaxID validation (BR-KSA-39) from the base regime. VAT registration is only mandatory for businesses with annual taxable supplies exceeding SAR 375,000, so requiring a TaxID is an e-invoicing concern that belongs in the ZATCA addon, not the base tax regime. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Point the VAT category source to the ZATCA VAT rules and regulations page where tax rate changes are published, instead of the e-invoicing introduction page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactor validateTaxIdentity to use validation.ValidateStruct with field attribution, matching the pattern used by other regimes (DE, BR, ES). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove buyer-only identity types (TIN, NAT, IQA, PAS, GCC, OTH) that are ZATCA e-invoicing scheme codes (BT-29-1/BT-46-1), not tax law concepts. Several also duplicate GOBL built-ins (org.IdentityKeyPassport, org.IdentityKeyNational, etc.). Keep only seller business registration types (CRN, MOM, MLS, 700, SAG) which represent real Saudi government-issued registrations rooted in tax law. The ZATCA scheme ID mappings belong in the e-invoicing addon. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Identity scheme codes (CRN, MOM, MLS, 700, SAG) are ZATCA e-invoicing concepts (BT-29-1/BT-46-1), not tax law requirements. These belong in a future ZATCA addon, not the base tax regime. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use ض.ق.م (short form) in the Name field, keeping the full ضريبة القيمة المضافة in the Title field, consistent with how other regimes use abbreviated names (e.g., IVA for Spanish). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There is no public evidence that ZATCA specifies a check digit algorithm for position 10 of the TIN. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Invoice-level validations (supplier name, customer name/identification, simplified invoice rules) belong in the ZATCA e-invoicing addon scope, not in the base regime definition. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Why Saudi Arabia
Saudi Arabia is the strongest next candidate for GOBL regime expansion, scoring highest across commercial urgency, technical fit, documentation quality, and strategic leverage.
Market opportunity. The ZATCA e-invoicing market is valued at $143M (2024), projected to reach $594M by 2033 at 15.3% CAGR. Phase 2 integration has reached Wave 24 (SAR 375K threshold, June 2026), approaching universal coverage of all VAT-registered businesses.
Technical fit. ZATCA uses UBL 2.1 XML based on EN 16931 — GOBL's existing
gobl.ublconverter handles the bulk of XML generation. The regime sits cleanly in GOBL's architecture: 15% VAT, 15-digit TIN validation, ZATCA-specific identity types, and well-documented business rules (BR-KSA series).Strategic multiplier. Every GCC country is following Saudi Arabia's lead: UAE mandates e-invoicing from January 2027, Oman launches its Fawtara pilot in August 2026, Bahrain has active consultations, and Qatar has issued platform tenders. Building the SA regime directly accelerates five additional country implementations.
Documentation quality. All specifications are published in English at zatca.gov.sa, including the XML Implementation Standard v1.2, Data Dictionary, and Developer Portal Manual. The sandbox environment provides full API testing.
Changes
This PR adds the foundational Saudi Arabia tax regime (
regimes/sa/) covering:Out of Scope
The following ZATCA Phase 2 compliance areas are intentionally not covered by this regime PR:
Sources
Test coverage
regimes/sa/: 94.0% statement coveragego test ./regimes/sa/...go test ./...passes (no regressions)Pre-Review Checklist
go generate .to ensure that the Schemas and Regime data are up to date.Only after checking off all the previous items: