├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── Bee.ZatcaHelper.IntegrationTests ├── Bee.ZatcaHelper.IntegrationTests.csproj ├── CreditNote_E2ETest.cs ├── DebitNote_E2ETest.cs ├── Program.cs ├── StandardInvoice_E2ETest.cs ├── StandardInvoice_ZeroTax_E2ETest.cs └── Usings.cs ├── Bee.ZatcaHelper.UnitTests ├── Bee.ZatcaHelper.UnitTests.csproj ├── ComplianceCsrApiClientTests.cs ├── ProdCsidApiClientTests.cs ├── StandardInvoiceXmlGeneratorTests.cs ├── Usings.cs └── UtilTests │ └── InvoiceHashHelperTests.cs ├── Bee.ZatcaHelper ├── Bee.ZatcaHelper.csproj ├── ComplianceCsrAPIClient.cs ├── Contracts │ ├── ComplianceCsrRequest.cs │ ├── ComplianceCsrResponse.cs │ ├── InvoiceClearanceRequest.cs │ ├── InvoiceClearanceResponse.cs │ ├── ProdCsidOnboardingRequest.cs │ └── ProdCsidOnboardingResponse.cs ├── CreditNote.xml ├── DebitNote.xml ├── GlobalVariables.cs ├── IsExternalInit.cs ├── Model │ ├── CustomerInfo.cs │ ├── InvoiceLineItem.cs │ ├── LegalMonetaryTotal.cs │ ├── Money.cs │ ├── PartyPostalAddress.cs │ ├── StandardInvoice.cs │ ├── StandardInvoiceAdjustment.cs │ ├── SupplierInfo.cs │ └── TaxTotal.cs ├── ProdCsidApiClient.cs ├── StanadardInvoiceXpaths.cs ├── StandardInvoiceClearanceAPIClient.cs ├── StandardInvoiceTemplate.xml ├── StandardInvoiceXmlGenerator.cs └── Util │ ├── EmbeddedResourceHelper.cs │ ├── InvoiceHashHelper.cs │ ├── StandardInvoiceHashingTemplate.xsl │ ├── WebClient.cs │ └── XmlHelper.cs ├── Logo └── Devbee-Logo.png ├── README.md ├── Zatca-Standard-Invoice-Integration-Client.sln └── pro-version-features.md /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: Publish Packages 2 | on: 3 | push: 4 | branches: [ main ] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Setup .NET 13 | uses: actions/setup-dotnet@v1 14 | with: 15 | dotnet-version: 6.0.x 16 | - name: Restore dependencies 17 | run: dotnet restore 18 | - name: Build 19 | run: dotnet build --configuration Release --no-restore 20 | - name: Publish package 21 | uses: brandedoutcast/publish-nuget@v2.5.2 22 | with: 23 | PROJECT_FILE_PATH: Bee.ZatcaHelper/Bee.ZatcaHelper.csproj 24 | NUGET_KEY: ${{secrets.NUGET_API_KEY}} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin/ 3 | obj/ 4 | .vs/ 5 | .idea/ 6 | Zatca-Standard-Invoice-Integration-Client.sln.DotSettings.user 7 | -------------------------------------------------------------------------------- /Bee.ZatcaHelper.IntegrationTests/Bee.ZatcaHelper.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Bee.ZatcaHelper.IntegrationTests/CreditNote_E2ETest.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.IntegrationTests; 2 | 3 | public static class CreditNote_E2ETest 4 | { 5 | private static readonly GlobalVariables GlobalVariables = new GlobalVariables() 6 | { 7 | BaseUrl = "https://gw-apic-gov.gazt.gov.sa", 8 | ComplianceCsidEndpoint = "/e-invoicing/developer-portal/compliance", 9 | ProdCsidEndpoint = "/e-invoicing/developer-portal/production/csids", 10 | InvoiceClearanceEndPoint = "/e-invoicing/developer-portal/invoices/clearance/single" 11 | }; 12 | 13 | private const string Otp = "123456"; 14 | 15 | private const string Csr = 16 | "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0NIE1JSUI3akNDQVpNQ0FRQXdZREVMTUFrR0ExVUVCaE1DVTBFeEZUQVRCZ05WQkFzTURGSnBlV0ZrSUVKeVlXNWoNIGFERXRNQ3NHQTFVRUNnd2tWVzVwZEdWa0lFZDFiR1lnUVdseVkzSmhablFnUm5WbGJHbHVaeUJEYjIxd1lXNTUNIE1Rc3dDUVlEVlFRRERBSlRRVEJXTUJBR0J5cUdTTTQ5QWdFR0JTdUJCQUFLQTBJQUJQWHFkK1Q1ZkZhK1ZlYmcNIHlzL1d4bkJPNHBwOHNVeWtlM1BMSmVKYjZLbkk1Z0ZEcHVsWWY1K1NvUFJZVGFmK1o3Y0g1RzdJSmExdVNYSXENIHkwWVdoVytnZ2RNd2dkQUdDU3FHU0liM0RRRUpEakdCd2pDQnZ6QWhCZ2tyQmdFRUFZSTNGQUlFRkJNU1drRlUNIFEwRXRRMjlrWlMxVGFXZHVhVzVuTUlHWkJnTlZIUkVFZ1pFd2dZNmtnWXN3Z1lneEp6QWxCZ05WQkFRTUhqRXQNIFZXZGhabU52ZkRJdFJWTkhWVTVKVkh3ekxURXlNelExTmpjNE9URWZNQjBHQ2dtU0pvbVQ4aXhrQVFFTUR6TXgNIE1ERTNOVE01TnpRd01EQXdNekVOTUFzR0ExVUVEQXdFTVRFd01ERVNNQkFHQTFVRUdnd0pUWGxCWkdSeVpYTnoNIE1Sa3dGd1lEVlFRUERCQkdkV1ZzYVc1bklFbHVaSFZ6ZEhKNU1Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQ0lRQ1ANIFlFVGJuazFSblFYTDdvSUxJek1qMFJGQWg3QmErUEFPenJIQnMyYzdLZ0loQUxDaUVKNnRjVzdGcHdyVEZ0RGMNIFA1QjVoRE5nYjFGUTJXSVZjMzc1VS9PdA0gLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0t"; 17 | 18 | public static void Run() 19 | { 20 | var csrResponse = GetComplianceCsr(GlobalVariables, Otp, Csr); 21 | 22 | var prodOnboard = GetProdCsid(GlobalVariables, csrResponse); 23 | 24 | ClearInvoice(GlobalVariables, prodOnboard); 25 | 26 | Console.WriteLine("-----------------------------------------------------------------------------------------"); 27 | Console.WriteLine("----------------------------------Test Run Completed-------------------------------------"); 28 | Console.WriteLine("-----------------------------------------------------------------------------------------"); 29 | } 30 | 31 | private static void ClearInvoice(GlobalVariables globalVariables, ProdCsidOnboardingResponse prodOnboard) 32 | { 33 | var invoice = new StandardInvoiceAdjustment() 34 | { 35 | AdjustmentReason = "test adjustment", 36 | AdjsutmentInvoiceNumber = "11221222", 37 | Id = "100012", 38 | UUID = Guid.NewGuid().ToString(), 39 | ActualDeliveryDate = "2022-09-13", 40 | DocumentCurrencyCode = "SAR", 41 | TaxCurrencyCode = "SAR", 42 | InvoiceTypeCode = "388", 43 | IssuedDate = "2022-09-13", 44 | IssuedTime = "14:40:40", 45 | LatestDeliveryDate = "2022-09-13", 46 | PreviousInvoiceHash = 47 | "NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ==", 48 | SupplierInfo = new SupplierInfo() 49 | { 50 | CRN = "310175397400003", 51 | PartyPostalAddress = new PartyPostalAddress() 52 | { 53 | StreetName = "testA", 54 | BuildingNumber = "3454", 55 | CityName = "RiyadhA", 56 | CitySubdivisionName = "fgffA", 57 | Country = "SA", 58 | CountrySubentity = "test", 59 | PlotIdentification = "1234", 60 | PostalZone = "12345", 61 | }, 62 | RegistrationName = "United Gulf Aircraft Fueling Company", 63 | VAT = "310175397400003" 64 | }, 65 | CustomerInfo = new CustomerInfo() 66 | { 67 | NAT = "2345", 68 | PartyPostalAddress = new PartyPostalAddress() 69 | { 70 | BuildingNumber = "3353", 71 | CityName = "DhurmaA", 72 | CitySubdivisionName = "fgffA", 73 | Country = "SA", 74 | CountrySubentity = "ulhkA", 75 | PlotIdentification = "3434", 76 | PostalZone = "34341", 77 | StreetName = "baaounA", 78 | }, 79 | RegistrationName = "testA", 80 | VAT = "300075588700003", 81 | }, 82 | InvoiceLineItem = new InvoiceLineItem() 83 | { 84 | ID = "1", 85 | InvoicedQuantity = "100", 86 | ItemName = "item", 87 | LineExtensionAmount = new Money("SAR", 1000.00), 88 | PriceAmount = new Money("SAR", 10.00), 89 | RoundingAmount = new Money("SAR", 1150.00), 90 | TaxAmount = new Money("SAR", 150.00), 91 | TaxPercent = "15.00", 92 | TaxScheme = "VAT", 93 | TaxSchemeId = "S" 94 | }, 95 | LegalMonetaryTotal = new LegalMonetaryTotal() 96 | { 97 | AllowanceTotalAmount = new Money("SAR", 0.00), 98 | LineExtensionAmount = new Money("SAR", 1000.00), 99 | PayableAmount = new Money("SAR", 1150.00), 100 | PrepaidAmount = new Money("SAR", 0.00), 101 | TaxExclusiveAmount = new Money("SAR", 1000.00), 102 | TaxInclusiveAmount = new Money("SAR", 1150.00), 103 | }, 104 | TaxTotal = new TaxTotal() 105 | { 106 | Percent = "15.00", 107 | TaxableAmount = new Money("SAR", 1000.00), 108 | TaxAmount = new Money("SAR", 150.00), 109 | TaxCategory = "SA" 110 | } 111 | }; 112 | 113 | 114 | var generatedClearanceRequest = StandardInvoiceXmlGenerator.GenerateForCreditNote(invoice); 115 | Console.WriteLine("----------------------Standard Invoice Generation Request---------------------------------"); 116 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(generatedClearanceRequest)); 117 | var clearanceResponse = new StandardInvoiceClearanceApiClient(globalVariables).ClearInvoice( 118 | new InvoiceClearanceRequest(generatedClearanceRequest, prodOnboard.BinarySecurityToken, 119 | prodOnboard.Secret)); 120 | if (clearanceResponse.ClearanceStatus == "NOT_CLEARED") 121 | { 122 | Console.WriteLine("----------------------Standard Invoice Clearance Request - Failure reason---------------------------------"); 123 | Console.WriteLine(clearanceResponse.validationResults); 124 | } 125 | else 126 | { 127 | Console.WriteLine("----------------------Standard Invoice Clearance Request---------------------------------"); 128 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(clearanceResponse)); 129 | } 130 | Assert.IsNotNull(clearanceResponse); 131 | Assert.IsNotEmpty(clearanceResponse.GeneratedQR); 132 | } 133 | 134 | private static ProdCsidOnboardingResponse GetProdCsid(GlobalVariables globalVariables, 135 | ComplianceCsrResponse csrResponse) 136 | { 137 | var prodOnboard = new ProdCsidApiClient(globalVariables).GetToken( 138 | new ProdCsidOnboardingRequest(csrResponse.RequestId, csrResponse.BinarySecurityToken, csrResponse.Secret)); 139 | Console.WriteLine("--------------------------------Prod Csid Request-----------------------------------"); 140 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(prodOnboard)); 141 | Assert.IsNotNull(prodOnboard); 142 | Assert.IsTrue(prodOnboard.Errors is null); 143 | return prodOnboard; 144 | } 145 | 146 | private static ComplianceCsrResponse GetComplianceCsr(GlobalVariables globalVariables, string otp, string csr) 147 | { 148 | var csrResponse = new ComplianceCsrApiClient(globalVariables).GetToken(new ComplianceCsrRequest(otp, csr)); 149 | Console.WriteLine("--------------------------------Compliance Csr Request-----------------------------------"); 150 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(csrResponse)); 151 | Assert.IsNotNull(csrResponse); 152 | Assert.IsTrue(csrResponse.Errors is null); 153 | return csrResponse; 154 | } 155 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper.IntegrationTests/DebitNote_E2ETest.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.IntegrationTests; 2 | 3 | public static class DebitNote_E2ETest 4 | { 5 | private static readonly GlobalVariables GlobalVariables = new GlobalVariables() 6 | { 7 | BaseUrl = "https://gw-apic-gov.gazt.gov.sa", 8 | ComplianceCsidEndpoint = "/e-invoicing/developer-portal/compliance", 9 | ProdCsidEndpoint = "/e-invoicing/developer-portal/production/csids", 10 | InvoiceClearanceEndPoint = "/e-invoicing/developer-portal/invoices/clearance/single" 11 | }; 12 | 13 | private const string Otp = "123456"; 14 | 15 | private const string Csr = 16 | "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0NIE1JSUI3akNDQVpNQ0FRQXdZREVMTUFrR0ExVUVCaE1DVTBFeEZUQVRCZ05WQkFzTURGSnBlV0ZrSUVKeVlXNWoNIGFERXRNQ3NHQTFVRUNnd2tWVzVwZEdWa0lFZDFiR1lnUVdseVkzSmhablFnUm5WbGJHbHVaeUJEYjIxd1lXNTUNIE1Rc3dDUVlEVlFRRERBSlRRVEJXTUJBR0J5cUdTTTQ5QWdFR0JTdUJCQUFLQTBJQUJQWHFkK1Q1ZkZhK1ZlYmcNIHlzL1d4bkJPNHBwOHNVeWtlM1BMSmVKYjZLbkk1Z0ZEcHVsWWY1K1NvUFJZVGFmK1o3Y0g1RzdJSmExdVNYSXENIHkwWVdoVytnZ2RNd2dkQUdDU3FHU0liM0RRRUpEakdCd2pDQnZ6QWhCZ2tyQmdFRUFZSTNGQUlFRkJNU1drRlUNIFEwRXRRMjlrWlMxVGFXZHVhVzVuTUlHWkJnTlZIUkVFZ1pFd2dZNmtnWXN3Z1lneEp6QWxCZ05WQkFRTUhqRXQNIFZXZGhabU52ZkRJdFJWTkhWVTVKVkh3ekxURXlNelExTmpjNE9URWZNQjBHQ2dtU0pvbVQ4aXhrQVFFTUR6TXgNIE1ERTNOVE01TnpRd01EQXdNekVOTUFzR0ExVUVEQXdFTVRFd01ERVNNQkFHQTFVRUdnd0pUWGxCWkdSeVpYTnoNIE1Sa3dGd1lEVlFRUERCQkdkV1ZzYVc1bklFbHVaSFZ6ZEhKNU1Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQ0lRQ1ANIFlFVGJuazFSblFYTDdvSUxJek1qMFJGQWg3QmErUEFPenJIQnMyYzdLZ0loQUxDaUVKNnRjVzdGcHdyVEZ0RGMNIFA1QjVoRE5nYjFGUTJXSVZjMzc1VS9PdA0gLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0t"; 17 | 18 | public static void Run() 19 | { 20 | var csrResponse = GetComplianceCsr(GlobalVariables, Otp, Csr); 21 | 22 | var prodOnboard = GetProdCsid(GlobalVariables, csrResponse); 23 | 24 | ClearInvoice(GlobalVariables, prodOnboard); 25 | 26 | Console.WriteLine("-----------------------------------------------------------------------------------------"); 27 | Console.WriteLine("----------------------------------Test Run Completed-------------------------------------"); 28 | Console.WriteLine("-----------------------------------------------------------------------------------------"); 29 | } 30 | 31 | private static void ClearInvoice(GlobalVariables globalVariables, ProdCsidOnboardingResponse prodOnboard) 32 | { 33 | var invoice = new StandardInvoiceAdjustment() 34 | { 35 | AdjustmentReason = "test adjustment", 36 | AdjsutmentInvoiceNumber = "11221222", 37 | Id = "100012", 38 | UUID = Guid.NewGuid().ToString(), 39 | ActualDeliveryDate = "2022-09-13", 40 | DocumentCurrencyCode = "SAR", 41 | TaxCurrencyCode = "SAR", 42 | InvoiceTypeCode = "388", 43 | IssuedDate = "2022-09-13", 44 | IssuedTime = "14:40:40", 45 | LatestDeliveryDate = "2022-09-13", 46 | PreviousInvoiceHash = 47 | "NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ==", 48 | SupplierInfo = new SupplierInfo() 49 | { 50 | CRN = "310175397400003", 51 | PartyPostalAddress = new PartyPostalAddress() 52 | { 53 | StreetName = "testA", 54 | BuildingNumber = "3454", 55 | CityName = "RiyadhA", 56 | CitySubdivisionName = "fgffA", 57 | Country = "SA", 58 | CountrySubentity = "test", 59 | PlotIdentification = "1234", 60 | PostalZone = "12345", 61 | }, 62 | RegistrationName = "United Gulf Aircraft Fueling Company", 63 | VAT = "310175397400003" 64 | }, 65 | CustomerInfo = new CustomerInfo() 66 | { 67 | NAT = "2345", 68 | PartyPostalAddress = new PartyPostalAddress() 69 | { 70 | BuildingNumber = "3353", 71 | CityName = "DhurmaA", 72 | CitySubdivisionName = "fgffA", 73 | Country = "SA", 74 | CountrySubentity = "ulhkA", 75 | PlotIdentification = "3434", 76 | PostalZone = "34341", 77 | StreetName = "baaounA", 78 | }, 79 | RegistrationName = "testA", 80 | VAT = "300075588700003", 81 | }, 82 | InvoiceLineItem = new InvoiceLineItem() 83 | { 84 | ID = "1", 85 | InvoicedQuantity = "100", 86 | ItemName = "item", 87 | LineExtensionAmount = new Money("SAR", 1000.00), 88 | PriceAmount = new Money("SAR", 10.00), 89 | RoundingAmount = new Money("SAR", 1150.00), 90 | TaxAmount = new Money("SAR", 150.00), 91 | TaxPercent = "15.00", 92 | TaxScheme = "VAT", 93 | TaxSchemeId = "S" 94 | }, 95 | LegalMonetaryTotal = new LegalMonetaryTotal() 96 | { 97 | AllowanceTotalAmount = new Money("SAR", 0.00), 98 | LineExtensionAmount = new Money("SAR", 1000.00), 99 | PayableAmount = new Money("SAR", 1150.00), 100 | PrepaidAmount = new Money("SAR", 0.00), 101 | TaxExclusiveAmount = new Money("SAR", 1000.00), 102 | TaxInclusiveAmount = new Money("SAR", 1150.00), 103 | }, 104 | TaxTotal = new TaxTotal() 105 | { 106 | Percent = "15.00", 107 | TaxableAmount = new Money("SAR", 1000.00), 108 | TaxAmount = new Money("SAR", 150.00), 109 | TaxCategory = "SA" 110 | } 111 | }; 112 | 113 | 114 | var generatedClearanceRequest = StandardInvoiceXmlGenerator.GenerateForDebitNote(invoice); 115 | Console.WriteLine("----------------------Standard Invoice Generation Request---------------------------------"); 116 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(generatedClearanceRequest)); 117 | var clearanceResponse = new StandardInvoiceClearanceApiClient(globalVariables).ClearInvoice( 118 | new InvoiceClearanceRequest(generatedClearanceRequest, prodOnboard.BinarySecurityToken, 119 | prodOnboard.Secret)); 120 | if (clearanceResponse.ClearanceStatus == "NOT_CLEARED") 121 | { 122 | Console.WriteLine("----------------------Standard Invoice Clearance Request - Failure reason---------------------------------"); 123 | Console.WriteLine(clearanceResponse.validationResults); 124 | } 125 | else 126 | { 127 | Console.WriteLine("----------------------Standard Invoice Clearance Request---------------------------------"); 128 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(clearanceResponse)); 129 | } 130 | Assert.IsNotNull(clearanceResponse); 131 | Assert.IsNotEmpty(clearanceResponse.GeneratedQR); 132 | } 133 | 134 | private static ProdCsidOnboardingResponse GetProdCsid(GlobalVariables globalVariables, 135 | ComplianceCsrResponse csrResponse) 136 | { 137 | var prodOnboard = new ProdCsidApiClient(globalVariables).GetToken( 138 | new ProdCsidOnboardingRequest(csrResponse.RequestId, csrResponse.BinarySecurityToken, csrResponse.Secret)); 139 | Console.WriteLine("--------------------------------Prod Csid Request-----------------------------------"); 140 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(prodOnboard)); 141 | Assert.IsNotNull(prodOnboard); 142 | Assert.IsTrue(prodOnboard.Errors is null); 143 | return prodOnboard; 144 | } 145 | 146 | private static ComplianceCsrResponse GetComplianceCsr(GlobalVariables globalVariables, string otp, string csr) 147 | { 148 | var csrResponse = new ComplianceCsrApiClient(globalVariables).GetToken(new ComplianceCsrRequest(otp, csr)); 149 | Console.WriteLine("--------------------------------Compliance Csr Request-----------------------------------"); 150 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(csrResponse)); 151 | Assert.IsNotNull(csrResponse); 152 | Assert.IsTrue(csrResponse.Errors is null); 153 | return csrResponse; 154 | } 155 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper.IntegrationTests/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | 3 | using Bee.ZatcaHelper.IntegrationTests; 4 | 5 | StandardInvoice_E2ETest.Run(); 6 | StandardInvoiceZeroTaxE2ETest.Run(); 7 | DebitNote_E2ETest.Run(); 8 | CreditNote_E2ETest.Run(); -------------------------------------------------------------------------------- /Bee.ZatcaHelper.IntegrationTests/StandardInvoice_E2ETest.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.IntegrationTests; 2 | 3 | public static class StandardInvoice_E2ETest 4 | { 5 | private static readonly GlobalVariables GlobalVariables = new GlobalVariables() 6 | { 7 | BaseUrl = "https://gw-apic-gov.gazt.gov.sa", 8 | ComplianceCsidEndpoint = "/e-invoicing/developer-portal/compliance", 9 | ProdCsidEndpoint = "/e-invoicing/developer-portal/production/csids", 10 | InvoiceClearanceEndPoint = "/e-invoicing/developer-portal/invoices/clearance/single" 11 | }; 12 | 13 | private const string Otp = "123456"; 14 | 15 | private const string Csr = 16 | "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0NIE1JSUI3akNDQVpNQ0FRQXdZREVMTUFrR0ExVUVCaE1DVTBFeEZUQVRCZ05WQkFzTURGSnBlV0ZrSUVKeVlXNWoNIGFERXRNQ3NHQTFVRUNnd2tWVzVwZEdWa0lFZDFiR1lnUVdseVkzSmhablFnUm5WbGJHbHVaeUJEYjIxd1lXNTUNIE1Rc3dDUVlEVlFRRERBSlRRVEJXTUJBR0J5cUdTTTQ5QWdFR0JTdUJCQUFLQTBJQUJQWHFkK1Q1ZkZhK1ZlYmcNIHlzL1d4bkJPNHBwOHNVeWtlM1BMSmVKYjZLbkk1Z0ZEcHVsWWY1K1NvUFJZVGFmK1o3Y0g1RzdJSmExdVNYSXENIHkwWVdoVytnZ2RNd2dkQUdDU3FHU0liM0RRRUpEakdCd2pDQnZ6QWhCZ2tyQmdFRUFZSTNGQUlFRkJNU1drRlUNIFEwRXRRMjlrWlMxVGFXZHVhVzVuTUlHWkJnTlZIUkVFZ1pFd2dZNmtnWXN3Z1lneEp6QWxCZ05WQkFRTUhqRXQNIFZXZGhabU52ZkRJdFJWTkhWVTVKVkh3ekxURXlNelExTmpjNE9URWZNQjBHQ2dtU0pvbVQ4aXhrQVFFTUR6TXgNIE1ERTNOVE01TnpRd01EQXdNekVOTUFzR0ExVUVEQXdFTVRFd01ERVNNQkFHQTFVRUdnd0pUWGxCWkdSeVpYTnoNIE1Sa3dGd1lEVlFRUERCQkdkV1ZzYVc1bklFbHVaSFZ6ZEhKNU1Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQ0lRQ1ANIFlFVGJuazFSblFYTDdvSUxJek1qMFJGQWg3QmErUEFPenJIQnMyYzdLZ0loQUxDaUVKNnRjVzdGcHdyVEZ0RGMNIFA1QjVoRE5nYjFGUTJXSVZjMzc1VS9PdA0gLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0t"; 17 | 18 | public static void Run() 19 | { 20 | var csrResponse = GetComplianceCsr(GlobalVariables, Otp, Csr); 21 | 22 | var prodOnboard = GetProdCsid(GlobalVariables, csrResponse); 23 | 24 | ClearInvoice(GlobalVariables, prodOnboard); 25 | 26 | Console.WriteLine("-----------------------------------------------------------------------------------------"); 27 | Console.WriteLine("----------------------------------Test Run Completed-------------------------------------"); 28 | Console.WriteLine("-----------------------------------------------------------------------------------------"); 29 | } 30 | 31 | private static void ClearInvoice(GlobalVariables globalVariables, ProdCsidOnboardingResponse prodOnboard) 32 | { 33 | var invoice = new StandardInvoice() 34 | { 35 | Id = "100012", 36 | UUID = Guid.NewGuid().ToString(), 37 | ActualDeliveryDate = "2022-09-13", 38 | DocumentCurrencyCode = "SAR", 39 | TaxCurrencyCode = "SAR", 40 | InvoiceTypeCode = "388", 41 | IssuedDate = "2022-09-13", 42 | IssuedTime = "14:40:40", 43 | LatestDeliveryDate = "2022-09-13", 44 | PreviousInvoiceHash = 45 | "NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ==", 46 | SupplierInfo = new SupplierInfo() 47 | { 48 | CRN = "310175397400003", 49 | PartyPostalAddress = new PartyPostalAddress() 50 | { 51 | StreetName = "testA", 52 | BuildingNumber = "3454", 53 | CityName = "RiyadhA", 54 | CitySubdivisionName = "fgffA", 55 | Country = "SA", 56 | CountrySubentity = "test", 57 | PlotIdentification = "1234", 58 | PostalZone = "12345", 59 | }, 60 | RegistrationName = "United Gulf Aircraft Fueling Company", 61 | VAT = "310175397400003" 62 | }, 63 | CustomerInfo = new CustomerInfo() 64 | { 65 | NAT = "2345", 66 | PartyPostalAddress = new PartyPostalAddress() 67 | { 68 | BuildingNumber = "3353", 69 | CityName = "DhurmaA", 70 | CitySubdivisionName = "fgffA", 71 | Country = "SA", 72 | CountrySubentity = "ulhkA", 73 | PlotIdentification = "3434", 74 | PostalZone = "34341", 75 | StreetName = "baaounA", 76 | }, 77 | RegistrationName = "testA", 78 | VAT = "300075588700003", 79 | }, 80 | InvoiceLineItem = new InvoiceLineItem() 81 | { 82 | ID = "1", 83 | InvoicedQuantity = "100", 84 | ItemName = "item", 85 | LineExtensionAmount = new Money("SAR", 1000.00), 86 | PriceAmount = new Money("SAR", 10.00), 87 | RoundingAmount = new Money("SAR", 1150.00), 88 | TaxAmount = new Money("SAR", 150.00), 89 | TaxPercent = "15.00", 90 | TaxScheme = "VAT", 91 | TaxSchemeId = "S" 92 | }, 93 | LegalMonetaryTotal = new LegalMonetaryTotal() 94 | { 95 | AllowanceTotalAmount = new Money("SAR", 0.00), 96 | LineExtensionAmount = new Money("SAR", 1000.00), 97 | PayableAmount = new Money("SAR", 1150.00), 98 | PrepaidAmount = new Money("SAR", 0.00), 99 | TaxExclusiveAmount = new Money("SAR", 1000.00), 100 | TaxInclusiveAmount = new Money("SAR", 1150.00), 101 | }, 102 | TaxTotal = new TaxTotal() 103 | { 104 | Percent = "15.00", 105 | TaxableAmount = new Money("SAR", 1000.00), 106 | TaxAmount = new Money("SAR", 150.00), 107 | TaxCategory = "SA" 108 | } 109 | }; 110 | 111 | 112 | var generatedClearanceRequest = StandardInvoiceXmlGenerator.Generate(invoice); 113 | Console.WriteLine("----------------------Standard Invoice Generation Request---------------------------------"); 114 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(generatedClearanceRequest)); 115 | var clearanceResponse = new StandardInvoiceClearanceApiClient(globalVariables).ClearInvoice( 116 | new InvoiceClearanceRequest(generatedClearanceRequest, prodOnboard.BinarySecurityToken, 117 | prodOnboard.Secret)); 118 | if (clearanceResponse.ClearanceStatus == "NOT_CLEARED") 119 | { 120 | Console.WriteLine("----------------------Standard Invoice Clearance Request - Failure reason---------------------------------"); 121 | Console.WriteLine(clearanceResponse.validationResults); 122 | } 123 | else 124 | { 125 | Console.WriteLine("----------------------Standard Invoice Clearance Request---------------------------------"); 126 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(clearanceResponse)); 127 | } 128 | Assert.IsNotNull(clearanceResponse); 129 | Assert.IsNotEmpty(clearanceResponse.GeneratedQR); 130 | } 131 | 132 | private static ProdCsidOnboardingResponse GetProdCsid(GlobalVariables globalVariables, 133 | ComplianceCsrResponse csrResponse) 134 | { 135 | var prodOnboard = new ProdCsidApiClient(globalVariables).GetToken( 136 | new ProdCsidOnboardingRequest(csrResponse.RequestId, csrResponse.BinarySecurityToken, csrResponse.Secret)); 137 | Console.WriteLine("--------------------------------Prod Csid Request-----------------------------------"); 138 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(prodOnboard)); 139 | Assert.IsNotNull(prodOnboard); 140 | Assert.IsTrue(prodOnboard.Errors is null); 141 | return prodOnboard; 142 | } 143 | 144 | private static ComplianceCsrResponse GetComplianceCsr(GlobalVariables globalVariables, string otp, string csr) 145 | { 146 | var csrResponse = new ComplianceCsrApiClient(globalVariables).GetToken(new ComplianceCsrRequest(otp, csr)); 147 | Console.WriteLine("--------------------------------Compliance Csr Request-----------------------------------"); 148 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(csrResponse)); 149 | Assert.IsNotNull(csrResponse); 150 | Assert.IsTrue(csrResponse.Errors is null); 151 | return csrResponse; 152 | } 153 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper.IntegrationTests/StandardInvoice_ZeroTax_E2ETest.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.IntegrationTests; 2 | 3 | public static class StandardInvoiceZeroTaxE2ETest 4 | { 5 | private static readonly GlobalVariables GlobalVariables = new GlobalVariables() 6 | { 7 | BaseUrl = "https://gw-apic-gov.gazt.gov.sa", 8 | ComplianceCsidEndpoint = "/e-invoicing/developer-portal/compliance", 9 | ProdCsidEndpoint = "/e-invoicing/developer-portal/production/csids", 10 | InvoiceClearanceEndPoint = "/e-invoicing/developer-portal/invoices/clearance/single" 11 | }; 12 | 13 | private const string Otp = "123456"; 14 | 15 | private const string Csr = 16 | "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0NIE1JSUI3akNDQVpNQ0FRQXdZREVMTUFrR0ExVUVCaE1DVTBFeEZUQVRCZ05WQkFzTURGSnBlV0ZrSUVKeVlXNWoNIGFERXRNQ3NHQTFVRUNnd2tWVzVwZEdWa0lFZDFiR1lnUVdseVkzSmhablFnUm5WbGJHbHVaeUJEYjIxd1lXNTUNIE1Rc3dDUVlEVlFRRERBSlRRVEJXTUJBR0J5cUdTTTQ5QWdFR0JTdUJCQUFLQTBJQUJQWHFkK1Q1ZkZhK1ZlYmcNIHlzL1d4bkJPNHBwOHNVeWtlM1BMSmVKYjZLbkk1Z0ZEcHVsWWY1K1NvUFJZVGFmK1o3Y0g1RzdJSmExdVNYSXENIHkwWVdoVytnZ2RNd2dkQUdDU3FHU0liM0RRRUpEakdCd2pDQnZ6QWhCZ2tyQmdFRUFZSTNGQUlFRkJNU1drRlUNIFEwRXRRMjlrWlMxVGFXZHVhVzVuTUlHWkJnTlZIUkVFZ1pFd2dZNmtnWXN3Z1lneEp6QWxCZ05WQkFRTUhqRXQNIFZXZGhabU52ZkRJdFJWTkhWVTVKVkh3ekxURXlNelExTmpjNE9URWZNQjBHQ2dtU0pvbVQ4aXhrQVFFTUR6TXgNIE1ERTNOVE01TnpRd01EQXdNekVOTUFzR0ExVUVEQXdFTVRFd01ERVNNQkFHQTFVRUdnd0pUWGxCWkdSeVpYTnoNIE1Sa3dGd1lEVlFRUERCQkdkV1ZzYVc1bklFbHVaSFZ6ZEhKNU1Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQ0lRQ1ANIFlFVGJuazFSblFYTDdvSUxJek1qMFJGQWg3QmErUEFPenJIQnMyYzdLZ0loQUxDaUVKNnRjVzdGcHdyVEZ0RGMNIFA1QjVoRE5nYjFGUTJXSVZjMzc1VS9PdA0gLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0t"; 17 | 18 | public static void Run() 19 | { 20 | var csrResponse = GetComplianceCsr(GlobalVariables, Otp, Csr); 21 | 22 | var prodOnboard = GetProdCsid(GlobalVariables, csrResponse); 23 | 24 | ClearInvoice(GlobalVariables, prodOnboard); 25 | 26 | Console.WriteLine("-----------------------------------------------------------------------------------------"); 27 | Console.WriteLine("----------------------------------Test Run Completed-------------------------------------"); 28 | Console.WriteLine("-----------------------------------------------------------------------------------------"); 29 | } 30 | 31 | private static void ClearInvoice(GlobalVariables globalVariables, ProdCsidOnboardingResponse prodOnboard) 32 | { 33 | var invoice = new StandardInvoice() 34 | { 35 | Id = "100012", 36 | UUID = Guid.NewGuid().ToString(), 37 | ActualDeliveryDate = "2022-09-13", 38 | DocumentCurrencyCode = "SAR", 39 | TaxCurrencyCode = "SAR", 40 | InvoiceTypeCode = "388", 41 | IssuedDate = "2022-09-13", 42 | IssuedTime = "14:40:40", 43 | LatestDeliveryDate = "2022-09-13", 44 | PreviousInvoiceHash = 45 | "NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ==", 46 | SupplierInfo = new SupplierInfo() 47 | { 48 | CRN = "310175397400003", 49 | PartyPostalAddress = new PartyPostalAddress() 50 | { 51 | StreetName = "testA", 52 | BuildingNumber = "3454", 53 | CityName = "RiyadhA", 54 | CitySubdivisionName = "fgffA", 55 | Country = "SA", 56 | CountrySubentity = "test", 57 | PlotIdentification = "1234", 58 | PostalZone = "12345", 59 | }, 60 | RegistrationName = "United Gulf Aircraft Fueling Company", 61 | VAT = "310175397400003" 62 | }, 63 | CustomerInfo = new CustomerInfo() 64 | { 65 | NAT = "2345", 66 | PartyPostalAddress = new PartyPostalAddress() 67 | { 68 | BuildingNumber = "3353", 69 | CityName = "DhurmaA", 70 | CitySubdivisionName = "fgffA", 71 | Country = "SA", 72 | CountrySubentity = "ulhkA", 73 | PlotIdentification = "3434", 74 | PostalZone = "34341", 75 | StreetName = "baaounA", 76 | }, 77 | RegistrationName = "testA", 78 | VAT = "300075588700003", 79 | }, 80 | InvoiceLineItem = new InvoiceLineItem() 81 | { 82 | ID = "1", 83 | InvoicedQuantity = "100", 84 | ItemName = "item", 85 | LineExtensionAmount = new Money("SAR", 1000.00), 86 | PriceAmount = new Money("SAR", 10.00), 87 | RoundingAmount = new Money("SAR", 1000.00), 88 | TaxAmount = new Money("SAR", 0.00), 89 | TaxPercent = "0.00", 90 | TaxScheme = "VAT", 91 | TaxSchemeId = "S" 92 | }, 93 | LegalMonetaryTotal = new LegalMonetaryTotal() 94 | { 95 | AllowanceTotalAmount = new Money("SAR", 0.00), 96 | LineExtensionAmount = new Money("SAR", 1000.00), 97 | PayableAmount = new Money("SAR", 1000.00), 98 | PrepaidAmount = new Money("SAR", 0.00), 99 | TaxExclusiveAmount = new Money("SAR", 1000.00), 100 | TaxInclusiveAmount = new Money("SAR", 1000.00), 101 | }, 102 | TaxTotal = new TaxTotal() 103 | { 104 | Percent = "0.00", 105 | TaxableAmount = new Money("SAR", 1000.00), 106 | TaxAmount = new Money("SAR", 0.00), 107 | TaxCategory = "SA" 108 | } 109 | }; 110 | 111 | 112 | var generatedClearanceRequest = StandardInvoiceXmlGenerator.Generate(invoice); 113 | Console.WriteLine("----------------------Standard Invoice Generation Request---------------------------------"); 114 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(generatedClearanceRequest)); 115 | var clearanceResponse = new StandardInvoiceClearanceApiClient(globalVariables).ClearInvoice( 116 | new InvoiceClearanceRequest(generatedClearanceRequest, prodOnboard.BinarySecurityToken, 117 | prodOnboard.Secret)); 118 | if (clearanceResponse.ClearanceStatus == "NOT_CLEARED") 119 | { 120 | Console.WriteLine("----------------------Standard Invoice Clearance Request - Failure reason---------------------------------"); 121 | Console.WriteLine(clearanceResponse.validationResults); 122 | } 123 | else 124 | { 125 | Console.WriteLine("----------------------Standard Invoice Clearance Request---------------------------------"); 126 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(clearanceResponse)); 127 | } 128 | Assert.IsNotNull(clearanceResponse); 129 | Assert.IsNotEmpty(clearanceResponse.GeneratedQR); 130 | } 131 | 132 | private static ProdCsidOnboardingResponse GetProdCsid(GlobalVariables globalVariables, 133 | ComplianceCsrResponse csrResponse) 134 | { 135 | var prodOnboard = new ProdCsidApiClient(globalVariables).GetToken( 136 | new ProdCsidOnboardingRequest(csrResponse.RequestId, csrResponse.BinarySecurityToken, csrResponse.Secret)); 137 | Console.WriteLine("--------------------------------Prod Csid Request-----------------------------------"); 138 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(prodOnboard)); 139 | Assert.IsNotNull(prodOnboard); 140 | Assert.IsTrue(prodOnboard.Errors is null); 141 | return prodOnboard; 142 | } 143 | 144 | private static ComplianceCsrResponse GetComplianceCsr(GlobalVariables globalVariables, string otp, string csr) 145 | { 146 | var csrResponse = new ComplianceCsrApiClient(globalVariables).GetToken(new ComplianceCsrRequest(otp, csr)); 147 | Console.WriteLine("--------------------------------Compliance Csr Request-----------------------------------"); 148 | Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(csrResponse)); 149 | Assert.IsNotNull(csrResponse); 150 | Assert.IsTrue(csrResponse.Errors is null); 151 | return csrResponse; 152 | } 153 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper.IntegrationTests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using NUnit.Framework; 2 | global using Bee.ZatcaHelper.Contracts; 3 | global using Bee.ZatcaHelper.Model; -------------------------------------------------------------------------------- /Bee.ZatcaHelper.UnitTests/Bee.ZatcaHelper.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | Bee.ZatcaHelper.UnitTests 11 | 12 | Bee.ZatcaHelper.UnitTests 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Bee.ZatcaHelper.UnitTests/ComplianceCsrApiClientTests.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.UnitTests; 2 | 3 | public class ComplianceCsrApiClientTests 4 | { 5 | [Test] 6 | public void ShouldGetTokenFromAfterComplianceCheck() 7 | { 8 | var globalVariables = new GlobalVariables() 9 | { 10 | BaseUrl = "https://www.google.com/", 11 | ComplianceCsidEndpoint = "/endpoint" 12 | }; 13 | 14 | var complianceCsrApiClient = new ComplianceCsrApiClient(globalVariables); 15 | var complianceCsrRequest = new ComplianceCsrRequest("1234", "csr"); 16 | var response = complianceCsrApiClient.GetToken(complianceCsrRequest); 17 | 18 | Assert.Pass(); 19 | } 20 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper.UnitTests/ProdCsidApiClientTests.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.UnitTests; 2 | 3 | public class ProdCsidApiClientTests 4 | { 5 | [Test] 6 | public void ShouldGetProdCsidTokenWithComplianceCheckBinaryToken() 7 | { 8 | var globalVariables = new GlobalVariables() 9 | { 10 | BaseUrl = "sample base", 11 | ProdCsidEndpoint = "end point" 12 | }; 13 | 14 | var prodCsidApiClient = new ProdCsidApiClient(globalVariables); 15 | var prodCsidRequest = new ProdCsidOnboardingRequest(1234, "binary token", "secret"); 16 | var response = prodCsidApiClient.GetToken(prodCsidRequest); 17 | 18 | Assert.Pass(); 19 | } 20 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper.UnitTests/StandardInvoiceXmlGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.UnitTests; 2 | 3 | public class StandardInvoiceXmlGeneratorTests 4 | { 5 | [Test] 6 | public void ShouldBeAbleToGenerateXml() 7 | { 8 | var invoice = new StandardInvoice() 9 | { 10 | Id = "100012", 11 | UUID = Guid.NewGuid().ToString(), 12 | ActualDeliveryDate = "2022-09-13", 13 | DocumentCurrencyCode = "SAR", 14 | TaxCurrencyCode = "SAR", 15 | InvoiceTypeCode = "388", 16 | IssuedDate = "2022-09-13", 17 | IssuedTime = "14:40:40", 18 | LatestDeliveryDate = "2022-09-13", 19 | PreviousInvoiceHash = 20 | "NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ==", 21 | SupplierInfo = new SupplierInfo() 22 | { 23 | CRN = "310175397400003", 24 | PartyPostalAddress = new PartyPostalAddress() 25 | { 26 | StreetName = "testA", 27 | BuildingNumber = "3454", 28 | CityName = "RiyadhA", 29 | CitySubdivisionName = "fgffA", 30 | Country = "SA", 31 | CountrySubentity = "test", 32 | PlotIdentification = "1234", 33 | PostalZone = "12345", 34 | }, 35 | RegistrationName = "United Gulf Aircraft Fueling Company", 36 | VAT = "310175397400003" 37 | }, 38 | CustomerInfo = new CustomerInfo() 39 | { 40 | NAT = "2345", 41 | PartyPostalAddress = new PartyPostalAddress() 42 | { 43 | BuildingNumber = "3353A", 44 | CityName = "DhurmaA", 45 | CitySubdivisionName = "fgffA", 46 | Country = "SA", 47 | CountrySubentity = "ulhkA", 48 | PlotIdentification = "3434", 49 | PostalZone = "34341", 50 | StreetName = "baaounA", 51 | }, 52 | RegistrationName = "testA", 53 | VAT = "300075588700003", 54 | }, 55 | InvoiceLineItem = new InvoiceLineItem() 56 | { 57 | ID = "1", 58 | InvoicedQuantity = "100", 59 | ItemName = "item", 60 | LineExtensionAmount = new Money("SAR", 1000.00), 61 | PriceAmount = new Money("SAR", 10.00), 62 | RoundingAmount = new Money("SAR", 1150.00), 63 | TaxAmount = new Money("SAR", 150.00), 64 | TaxPercent = "15.00", 65 | TaxScheme = "VAT", 66 | TaxSchemeId = "S" 67 | }, 68 | LegalMonetaryTotal = new LegalMonetaryTotal() 69 | { 70 | AllowanceTotalAmount = new Money("SAR", 0.00), 71 | LineExtensionAmount = new Money("SAR", 1000.00), 72 | PayableAmount = new Money("SAR", 1150.00), 73 | PrepaidAmount = new Money("SAR", 0.00), 74 | TaxExclusiveAmount = new Money("SAR", 1000.00), 75 | TaxInclusiveAmount = new Money("SAR", 1150.00), 76 | }, 77 | TaxTotal = new TaxTotal() 78 | { 79 | Percent = "15.00", 80 | TaxableAmount = new Money("SAR", 1000.00), 81 | TaxAmount = new Money("SAR", 150.00), 82 | TaxCategory = "SA" 83 | } 84 | }; 85 | 86 | var request = StandardInvoiceXmlGenerator.Generate(invoice); 87 | 88 | var actualUuid = request?.GetType().GetProperty("uuid")?.GetValue(request, null); 89 | var actualInvoiceHash = request?.GetType().GetProperty("invoiceHash")?.GetValue(request, null); 90 | var actualInvoice = request?.GetType().GetProperty("invoice")?.GetValue(request, null); 91 | Assert.Multiple(() => 92 | { 93 | Assert.That(actualInvoiceHash, Is.Not.Null); 94 | Assert.That(actualInvoice, Is.Not.Null); 95 | }); 96 | Assert.That(actualUuid, Is.EqualTo(invoice.UUID)); 97 | Assert.Pass(); 98 | } 99 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper.UnitTests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using NUnit.Framework; 2 | global using Bee.ZatcaHelper.Contracts; 3 | global using Bee.ZatcaHelper.Model; -------------------------------------------------------------------------------- /Bee.ZatcaHelper.UnitTests/UtilTests/InvoiceHashHelperTests.cs: -------------------------------------------------------------------------------- 1 | using Bee.ZatcaHelper.Util; 2 | 3 | namespace Bee.ZatcaHelper.UnitTests.UtilTests; 4 | 5 | public class InvoiceHashHelperTests 6 | { 7 | [SetUp] 8 | public void Setup() 9 | { 10 | } 11 | 12 | [Test] 13 | public void ShouldGenerateValidHashForProvidedInvoice() 14 | { 15 | const string expectedHash = "UVySRXxjE3r+aWN93hfnhc2w15aoaZO0v9pQtOKpupA="; 16 | var xmlString = typeof(StandardInvoiceXmlGenerator).GetFileContent("StandardInvoiceTemplate.xml"); 17 | var actualHash = InvoiceHashHelper.GenerateEInvoiceHashing(xmlString); 18 | Assert.That(actualHash, Is.EqualTo(expectedHash)); 19 | Assert.Pass(); 20 | } 21 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Bee.ZatcaHelper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net462;net6.0; 5 | Bee.ZatcaHelper 6 | 10.0 7 | enable 8 | enable 9 | Bee.ZatcaHelper 10 | Bee.ZatcaHelper 11 | 1.0.6 12 | DevBee 13 | This nuget helps in integertation of systems to Zatca systems. 14 | MIT 15 | https://github.com/fasilmarshooq/Bee.ZatcaHelper 16 | ../Logo/Devbee-Logo.png 17 | https://github.com/fasilmarshooq/Bee.ZatcaHelper 18 | Zatca, ZatcaHelper, ZatcaIntegration, Fatoora, Tax 19 | Bee.ZatcaHelper 20 | Bee.ZatcaHelper 21 | True 22 | MIT 23 | 24 | 25 | 26 | 27 | True 28 | \ 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | PreserveNewest 46 | 47 | 48 | PreserveNewest 49 | 50 | 51 | 52 | PreserveNewest 53 | 54 | 55 | 56 | PreserveNewest 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Bee.ZatcaHelper/ComplianceCsrAPIClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | using Bee.ZatcaHelper.Contracts; 3 | using Bee.ZatcaHelper.Util; 4 | using Serilog; 5 | 6 | namespace Bee.ZatcaHelper; 7 | 8 | public class ComplianceCsrApiClient 9 | { 10 | private readonly string _baseUrl; 11 | private readonly string _complianceEndPoint; 12 | 13 | public ComplianceCsrApiClient(GlobalVariables globalVariables) 14 | { 15 | _baseUrl = globalVariables.BaseUrl; 16 | _complianceEndPoint = globalVariables.ComplianceCsidEndpoint; 17 | if (!string.IsNullOrEmpty(_baseUrl) && !string.IsNullOrEmpty(_complianceEndPoint)) return; 18 | Log.Error("Base Url or Compliance Csid Endpoint cant be empty"); 19 | throw new Exception("Base Url or Compliance Csid Endpoint cant be empty"); 20 | } 21 | 22 | public ComplianceCsrResponse? GetToken(ComplianceCsrRequest complianceCsrRequest) 23 | { 24 | var customHeaders = new Dictionary {{"OTP", complianceCsrRequest.Otp}}; 25 | var result = new WebClient(_baseUrl, customHeaders).PostAsJsonAsync( 26 | _complianceEndPoint, new 27 | { 28 | csr = complianceCsrRequest.Csr 29 | }); 30 | return result.Result.Content.ReadFromJsonAsync().Result; 31 | } 32 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Contracts/ComplianceCsrRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Contracts; 2 | 3 | public class ComplianceCsrRequest 4 | { 5 | public ComplianceCsrRequest(string otp, string csr) 6 | { 7 | Otp = otp; 8 | Csr = csr; 9 | } 10 | 11 | public string Otp { get; init; } 12 | public string Csr { get; init; } 13 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Contracts/ComplianceCsrResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Contracts; 2 | 3 | public class ComplianceCsrResponse 4 | { 5 | public long RequestId { get; set; } 6 | public string? DispositionMessage { get; set; } 7 | public string? BinarySecurityToken { get; set; } 8 | public string? Secret { get; set; } 9 | public string[]? Errors { get; set; } 10 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Contracts/InvoiceClearanceRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Contracts; 2 | 3 | public class InvoiceClearanceRequest 4 | { 5 | public InvoiceClearanceRequest(object body, string binaryToken, string secret) 6 | { 7 | Body = body; 8 | BinaryToken = binaryToken; 9 | Secret = secret; 10 | } 11 | 12 | public object Body { get; } 13 | public string BinaryToken { get; } 14 | public string Secret { get; } 15 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Contracts/InvoiceClearanceResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Contracts; 2 | 3 | public class InvoiceClearanceResponse 4 | { 5 | public string ClearanceStatus { get; set; } 6 | public string ClearedInvoice { get; set; } 7 | public object validationResults { get; set; } 8 | public string? Hash { get; set; } 9 | public string? GeneratedQR { get; set; } 10 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Contracts/ProdCsidOnboardingRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Contracts; 2 | 3 | public class ProdCsidOnboardingRequest 4 | { 5 | public ProdCsidOnboardingRequest(long complianceRequestId, string csrBinaryToken, string csrSecret) 6 | { 7 | ComplianceRequestId = complianceRequestId; 8 | CsrBinaryToken = csrBinaryToken; 9 | CsrSecret = csrSecret; 10 | } 11 | 12 | public long ComplianceRequestId { get; } 13 | public string CsrBinaryToken { get; } 14 | public string CsrSecret { get; } 15 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Contracts/ProdCsidOnboardingResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Contracts; 2 | 3 | public class ProdCsidOnboardingResponse 4 | { 5 | public long RequestId { get; set; } 6 | public string? DispositionMessage { get; set; } 7 | public string? BinarySecurityToken { get; set; } 8 | public string? Secret { get; set; } 9 | public string[]? Errors { get; set; } 10 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/CreditNote.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | urn:oasis:names:specification:ubl:dsig:enveloped:xades 10 | 11 | 12 | 16 | 17 | urn:oasis:names:specification:ubl:signature:1 18 | urn:oasis:names:specification:ubl:signature:Invoice 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | not(//ancestor-or-self::ext:UBLExtensions) 29 | 30 | 31 | not(//ancestor-or-self::cac:Signature) 32 | 33 | 34 | 35 | not(//ancestor-or-self::cac:AdditionalDocumentReference[cbc:ID='QR']) 36 | 37 | 38 | 39 | 40 | 41 | BK6cj9p18ffIJHe/pWkkIT0myGMrFXYiczWq3se+nm8= 42 | 43 | 45 | 46 | 47 | ZDZjMzQyZTBjNGVjY2VlODgxYTBiMzhiNDFlM2QxZTlkZDkyMDQ1NTEwNzUwN2RhYjUwNGNmZmNmNTFmY2I2OA== 48 | 49 | 50 | 51 | 52 | MEYCIQCYh5e3irIqWl5YtaCnKiujMITmokXYOPMpIDCIoWW79wIhAMXmc+MFHGtaeBseOuEGTlwD6g09gNQPRPuY89s65OWS 53 | 54 | 55 | 56 | 57 | MIID1DCCA3mgAwIBAgITbwAAe3UAYVU34I/+5QABAAB7dTAKBggqhkjOPQQDAjBjMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxEzARBgoJkiaJk/IsZAEZFgNnb3YxFzAVBgoJkiaJk/IsZAEZFgdleHRnYXp0MRwwGgYDVQQDExNUU1pFSU5WT0lDRS1TdWJDQS0xMB4XDTIyMDYxMjE3NDA1MloXDTI0MDYxMTE3NDA1MlowSTELMAkGA1UEBhMCU0ExDjAMBgNVBAoTBWFnaWxlMRYwFAYDVQQLEw1oYXlhIHlhZ2htb3VyMRIwEAYDVQQDEwkxMjcuMC4wLjEwVjAQBgcqhkjOPQIBBgUrgQQACgNCAATTAK9lrTVko9rkq6ZYcc9HDRZP4b9S4zA4Km7YXJ+snTVhLkzU0HsmSX9Un8jDhRTOHDKaft8C/uuUY934vuMNo4ICJzCCAiMwgYgGA1UdEQSBgDB+pHwwejEbMBkGA1UEBAwSMS1oYXlhfDItMjM0fDMtMTEyMR8wHQYKCZImiZPyLGQBAQwPMzAwMDc1NTg4NzAwMDAzMQ0wCwYDVQQMDAQxMTAwMREwDwYDVQQaDAhaYXRjYSAxMjEYMBYGA1UEDwwPRm9vZCBCdXNzaW5lc3MzMB0GA1UdDgQWBBSgmIWD6bPfbbKkmTwOJRXvIbH9HjAfBgNVHSMEGDAWgBR2YIz7BqCsZ1c1nc+arKcrmTW1LzBOBgNVHR8ERzBFMEOgQaA/hj1odHRwOi8vdHN0Y3JsLnphdGNhLmdvdi5zYS9DZXJ0RW5yb2xsL1RTWkVJTlZPSUNFLVN1YkNBLTEuY3JsMIGtBggrBgEFBQcBAQSBoDCBnTBuBggrBgEFBQcwAYZiaHR0cDovL3RzdGNybC56YXRjYS5nb3Yuc2EvQ2VydEVucm9sbC9UU1pFaW52b2ljZVNDQTEuZXh0Z2F6dC5nb3YubG9jYWxfVFNaRUlOVk9JQ0UtU3ViQ0EtMSgxKS5jcnQwKwYIKwYBBQUHMAGGH2h0dHA6Ly90c3RjcmwuemF0Y2EuZ292LnNhL29jc3AwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDAzAnBgkrBgEEAYI3FQoEGjAYMAoGCCsGAQUFBwMCMAoGCCsGAQUFBwMDMAoGCCqGSM49BAMCA0kAMEYCIQCVwDMcq6PO+McmsBXUz/v1GdhGp7rqSa2AxTKSv838IAIhAOBNDBt9+3DSlijoVfxzrdDh528WC37smEdoGWVrSpG1 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 2022-06-13T11:06:14Z 67 | 68 | 69 | 70 | 72 | 73 | NDliYWY0YmVmNzAyZmE4OWIwZDMzYWIzZjFkOTY5YzFhZjRhZDE4ZTFkODlkNDY2Y2UwYzI2N2QyNDg0MWVlYQ== 74 | 75 | 76 | 77 | CN=TSZEINVOICE-SubCA-1, DC=extgazt, DC=gov, 78 | DC=local 79 | 80 | 81 | 2475382881139449094209233032847838632732556149 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | reporting:1.0 97 | SME00062 98 | 6f4d20e0-6bfe-4a80-9389-7dabe6620f12 99 | 2022-03-13 100 | 14:40:40 101 | 381 102 | SAR 103 | SAR 104 | 105 | 106 | TOSL108 107 | 108 | 109 | 110 | ICV 111 | 62 112 | 113 | 114 | PIH 115 | 116 | 117 | NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ== 118 | 119 | 120 | 121 | 122 | urn:oasis:names:specification:ubl:signature:Invoice 123 | urn:oasis:names:specification:ubl:dsig:enveloped:xades 124 | 125 | 126 | 127 | 128 | 310175397400003 129 | 130 | 131 | test 132 | 3454 133 | 1234 134 | fgff 135 | Riyadh 136 | 12345 137 | test 138 | 139 | SA 140 | 141 | 142 | 143 | 310175397400003 144 | 145 | VAT 146 | 147 | 148 | 149 | United Gulf Aircraft Fueling Company 150 | 151 | 152 | 153 | 154 | 155 | 156 | 2345 157 | 158 | 159 | baaoun 160 | sdsd 161 | 3353 162 | 3434 163 | fgff 164 | Dhurma 165 | 34534 166 | ulhk 167 | 168 | SA 169 | 170 | 171 | 172 | 173 | VAT 174 | 175 | 176 | 177 | sdsa 178 | 179 | 180 | 181 | 182 | 2022-03-13 183 | 2022-03-13 184 | 185 | 186 | 187 | 42 188 | string 189 | 190 | 191 | 192 | 1 193 | false 194 | discount 195 | 2 196 | 197 | S 198 | 15 199 | 200 | VAT 201 | 202 | 203 | 204 | 205 | 144.9 206 | 207 | 966.00 208 | 144.90 209 | 210 | S 211 | 15.00 212 | 213 | VAT 214 | 215 | 216 | 217 | 218 | 219 | 151.00 220 | 221 | 222 | 966.00 223 | 964.00 224 | 1108.90 225 | 2.00 226 | 0.00 227 | 1108.90 228 | 229 | 230 | 1 231 | 44.000000 232 | 966.00 233 | 234 | 144.90 235 | 1110.90 236 | 237 | 238 | dsd 239 | 240 | S 241 | 15.00 242 | 243 | VAT 244 | 245 | 246 | 247 | 248 | 22.00 249 | 250 | false 251 | discount 252 | 2.00 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /Bee.ZatcaHelper/DebitNote.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | urn:oasis:names:specification:ubl:dsig:enveloped:xades 10 | 11 | 12 | 16 | 17 | urn:oasis:names:specification:ubl:signature:1 18 | urn:oasis:names:specification:ubl:signature:Invoice 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | not(//ancestor-or-self::ext:UBLExtensions) 29 | 30 | 31 | not(//ancestor-or-self::cac:Signature) 32 | 33 | 34 | 35 | not(//ancestor-or-self::cac:AdditionalDocumentReference[cbc:ID='QR']) 36 | 37 | 38 | 39 | 40 | 41 | BK6cj9p18ffIJHe/pWkkIT0myGMrFXYiczWq3se+nm8= 42 | 43 | 45 | 46 | 47 | ZDZjMzQyZTBjNGVjY2VlODgxYTBiMzhiNDFlM2QxZTlkZDkyMDQ1NTEwNzUwN2RhYjUwNGNmZmNmNTFmY2I2OA== 48 | 49 | 50 | 51 | 52 | MEYCIQCYh5e3irIqWl5YtaCnKiujMITmokXYOPMpIDCIoWW79wIhAMXmc+MFHGtaeBseOuEGTlwD6g09gNQPRPuY89s65OWS 53 | 54 | 55 | 56 | 57 | MIID1DCCA3mgAwIBAgITbwAAe3UAYVU34I/+5QABAAB7dTAKBggqhkjOPQQDAjBjMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxEzARBgoJkiaJk/IsZAEZFgNnb3YxFzAVBgoJkiaJk/IsZAEZFgdleHRnYXp0MRwwGgYDVQQDExNUU1pFSU5WT0lDRS1TdWJDQS0xMB4XDTIyMDYxMjE3NDA1MloXDTI0MDYxMTE3NDA1MlowSTELMAkGA1UEBhMCU0ExDjAMBgNVBAoTBWFnaWxlMRYwFAYDVQQLEw1oYXlhIHlhZ2htb3VyMRIwEAYDVQQDEwkxMjcuMC4wLjEwVjAQBgcqhkjOPQIBBgUrgQQACgNCAATTAK9lrTVko9rkq6ZYcc9HDRZP4b9S4zA4Km7YXJ+snTVhLkzU0HsmSX9Un8jDhRTOHDKaft8C/uuUY934vuMNo4ICJzCCAiMwgYgGA1UdEQSBgDB+pHwwejEbMBkGA1UEBAwSMS1oYXlhfDItMjM0fDMtMTEyMR8wHQYKCZImiZPyLGQBAQwPMzAwMDc1NTg4NzAwMDAzMQ0wCwYDVQQMDAQxMTAwMREwDwYDVQQaDAhaYXRjYSAxMjEYMBYGA1UEDwwPRm9vZCBCdXNzaW5lc3MzMB0GA1UdDgQWBBSgmIWD6bPfbbKkmTwOJRXvIbH9HjAfBgNVHSMEGDAWgBR2YIz7BqCsZ1c1nc+arKcrmTW1LzBOBgNVHR8ERzBFMEOgQaA/hj1odHRwOi8vdHN0Y3JsLnphdGNhLmdvdi5zYS9DZXJ0RW5yb2xsL1RTWkVJTlZPSUNFLVN1YkNBLTEuY3JsMIGtBggrBgEFBQcBAQSBoDCBnTBuBggrBgEFBQcwAYZiaHR0cDovL3RzdGNybC56YXRjYS5nb3Yuc2EvQ2VydEVucm9sbC9UU1pFaW52b2ljZVNDQTEuZXh0Z2F6dC5nb3YubG9jYWxfVFNaRUlOVk9JQ0UtU3ViQ0EtMSgxKS5jcnQwKwYIKwYBBQUHMAGGH2h0dHA6Ly90c3RjcmwuemF0Y2EuZ292LnNhL29jc3AwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDAzAnBgkrBgEEAYI3FQoEGjAYMAoGCCsGAQUFBwMCMAoGCCsGAQUFBwMDMAoGCCqGSM49BAMCA0kAMEYCIQCVwDMcq6PO+McmsBXUz/v1GdhGp7rqSa2AxTKSv838IAIhAOBNDBt9+3DSlijoVfxzrdDh528WC37smEdoGWVrSpG1 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 2022-06-13T11:06:14Z 67 | 68 | 69 | 70 | 72 | 73 | NDliYWY0YmVmNzAyZmE4OWIwZDMzYWIzZjFkOTY5YzFhZjRhZDE4ZTFkODlkNDY2Y2UwYzI2N2QyNDg0MWVlYQ== 74 | 75 | 76 | 77 | CN=TSZEINVOICE-SubCA-1, DC=extgazt, DC=gov, 78 | DC=local 79 | 80 | 81 | 2475382881139449094209233032847838632732556149 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | reporting:1.0 97 | SME00062 98 | 6f4d20e0-6bfe-4a80-9389-7dabe6620f12 99 | 2022-03-13 100 | 14:40:40 101 | 383 102 | SAR 103 | SAR 104 | 105 | 106 | TOSL108 107 | 108 | 109 | 110 | ICV 111 | 62 112 | 113 | 114 | PIH 115 | 116 | 117 | NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ== 118 | 119 | 120 | 121 | 122 | urn:oasis:names:specification:ubl:signature:Invoice 123 | urn:oasis:names:specification:ubl:dsig:enveloped:xades 124 | 125 | 126 | 127 | 128 | 310175397400003 129 | 130 | 131 | test 132 | 3454 133 | 1234 134 | fgff 135 | Riyadh 136 | 12345 137 | test 138 | 139 | SA 140 | 141 | 142 | 143 | 310175397400003 144 | 145 | VAT 146 | 147 | 148 | 149 | United Gulf Aircraft Fueling Company 150 | 151 | 152 | 153 | 154 | 155 | 156 | 2345 157 | 158 | 159 | baaoun 160 | sdsd 161 | 3353 162 | 3434 163 | fgff 164 | Dhurma 165 | 34534 166 | ulhk 167 | 168 | SA 169 | 170 | 171 | 172 | 173 | VAT 174 | 175 | 176 | 177 | sdsa 178 | 179 | 180 | 181 | 182 | 2022-03-13 183 | 2022-03-13 184 | 185 | 186 | 187 | 42 188 | string 189 | 190 | 191 | 192 | 1 193 | false 194 | discount 195 | 2 196 | 197 | S 198 | 15 199 | 200 | VAT 201 | 202 | 203 | 204 | 205 | 144.9 206 | 207 | 966.00 208 | 144.90 209 | 210 | S 211 | 15.00 212 | 213 | VAT 214 | 215 | 216 | 217 | 218 | 219 | 151.00 220 | 221 | 222 | 966.00 223 | 964.00 224 | 1108.90 225 | 2.00 226 | 0.00 227 | 1108.90 228 | 229 | 230 | 1 231 | 44.000000 232 | 966.00 233 | 234 | 144.90 235 | 1110.90 236 | 237 | 238 | dsd 239 | 240 | S 241 | 15.00 242 | 243 | VAT 244 | 245 | 246 | 247 | 248 | 22.00 249 | 250 | false 251 | discount 252 | 2.00 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /Bee.ZatcaHelper/GlobalVariables.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper; 2 | 3 | public class GlobalVariables 4 | { 5 | //"https://gw-apic-gov.gazt.gov.sa" 6 | public string BaseUrl { get; set; } 7 | 8 | //"/e-invoicing/developer-portal/compliance" 9 | public string ComplianceCsidEndpoint { get; set; } 10 | 11 | //"/e-invoicing/developer-portal/production/csids" 12 | public string ProdCsidEndpoint { get; set; } 13 | //"/e-invoicing/developer-portal/invoices/clearance/single" 14 | public string InvoiceClearanceEndPoint { get; set; } 15 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.CompilerServices 2 | { 3 | public static class IsExternalInit { } 4 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Model/CustomerInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Model; 2 | 3 | public class CustomerInfo 4 | { 5 | public string NAT { get; set; } 6 | public string VAT { get; set; } 7 | public string RegistrationName { get; set; } 8 | public PartyPostalAddress PartyPostalAddress { get; set; } 9 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Model/InvoiceLineItem.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Model; 2 | 3 | public class InvoiceLineItem 4 | { 5 | public string ID { get; set; } 6 | public string InvoicedQuantity { get; set; } 7 | public Money LineExtensionAmount { get; set; } 8 | public Money TaxAmount { get; set; } 9 | public Money RoundingAmount { get; set; } 10 | public string TaxSchemeId { get; set; } 11 | public string TaxScheme { get; set; } 12 | public string TaxPercent { get; set; } 13 | public string ItemName { get; set; } 14 | 15 | //rate of item 16 | public Money PriceAmount { get; set; } 17 | 18 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Model/LegalMonetaryTotal.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Model; 2 | 3 | public class LegalMonetaryTotal 4 | { 5 | public Money LineExtensionAmount { get; set; } 6 | public Money TaxExclusiveAmount { get; set; } 7 | public Money TaxInclusiveAmount { get; set; } 8 | public Money AllowanceTotalAmount { get; set; } 9 | public Money PrepaidAmount { get; set; } 10 | public Money PayableAmount { get; set; } 11 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Model/Money.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Model; 2 | 3 | public class Money 4 | { 5 | public Money(string currencyCode, double amount) 6 | { 7 | CurrencyCode = currencyCode; 8 | Amount = amount; 9 | } 10 | 11 | public string CurrencyCode { get; } 12 | public double Amount { get; } 13 | 14 | public string GetAmountString() => $"{Amount:0.00}"; 15 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Model/PartyPostalAddress.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Model; 2 | 3 | public class PartyPostalAddress 4 | { 5 | public string StreetName { get; set; } 6 | public string BuildingNumber { get; set; } 7 | public string PlotIdentification { get; set; } 8 | public string CitySubdivisionName { get; set; } 9 | public string CityName { get; set; } 10 | public string PostalZone { get; set; } 11 | public string CountrySubentity { get; set; } 12 | public string Country { get; set; } 13 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Model/StandardInvoice.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Model; 2 | 3 | public class StandardInvoice 4 | { 5 | public string Id { get; set; } 6 | public string UUID { get; set; } 7 | public string IssuedDate { get; set; } 8 | public string IssuedTime { get; set; } 9 | public string InvoiceTypeCode { get; set; } 10 | public string DocumentCurrencyCode { get; set; } 11 | public string TaxCurrencyCode { get; set; } 12 | public string PreviousInvoiceHash { get; set; } 13 | public string ActualDeliveryDate { get; set; } 14 | public string LatestDeliveryDate { get; set; } 15 | 16 | 17 | 18 | public SupplierInfo SupplierInfo { get; set; } 19 | public CustomerInfo CustomerInfo { get; set; } 20 | public InvoiceLineItem InvoiceLineItem { get; set; } 21 | public LegalMonetaryTotal LegalMonetaryTotal { get; set; } 22 | public TaxTotal TaxTotal { get; set; } 23 | 24 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Model/StandardInvoiceAdjustment.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Model; 2 | 3 | public class StandardInvoiceAdjustment : StandardInvoice 4 | { 5 | public string AdjustmentReason { get; set; } 6 | public string AdjsutmentInvoiceNumber { get; set; } 7 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Model/SupplierInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Model; 2 | 3 | public class SupplierInfo 4 | { 5 | public string CRN { get; set; } 6 | public string VAT { get; set; } 7 | public string RegistrationName { get; set; } 8 | public PartyPostalAddress PartyPostalAddress { get; set; } 9 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Model/TaxTotal.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Model; 2 | 3 | public class TaxTotal 4 | { 5 | public Money TaxAmount { get; set; } 6 | public Money TaxableAmount { get; set; } 7 | public string TaxCategory { get; set; } 8 | public string Percent { get; set; } 9 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/ProdCsidApiClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | using Bee.ZatcaHelper.Contracts; 3 | using Bee.ZatcaHelper.Util; 4 | 5 | namespace Bee.ZatcaHelper; 6 | 7 | public class ProdCsidApiClient 8 | { 9 | private readonly string _baseUrl; 10 | private readonly string _prodCsidEndPoint; 11 | 12 | public ProdCsidApiClient(GlobalVariables globalVariables) 13 | { 14 | _baseUrl = globalVariables.BaseUrl; 15 | _prodCsidEndPoint = globalVariables.ProdCsidEndpoint; 16 | } 17 | 18 | public ProdCsidOnboardingResponse? GetToken(ProdCsidOnboardingRequest prodCsidOnboardingRequest) 19 | { 20 | var result = new WebClient(_baseUrl, new Dictionary(), 21 | prodCsidOnboardingRequest.CsrBinaryToken, prodCsidOnboardingRequest.CsrSecret).PostAsJsonAsync( 22 | _prodCsidEndPoint, new 23 | { 24 | compliance_request_id = prodCsidOnboardingRequest.ComplianceRequestId 25 | }); 26 | return result.Result.Content.ReadFromJsonAsync().Result; 27 | } 28 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/StanadardInvoiceXpaths.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper; 2 | 3 | public static partial class StandardInvoiceXmlGenerator 4 | { 5 | public const string QrCodeXpath = "/*[local-name() = 'Invoice']/*[local-name() = 'AdditionalDocumentReference' and *[local-name()='ID' and .='QR']]/*[local-name() = 'Attachment']/*[local-name() = 'EmbeddedDocumentBinaryObject']"; 6 | 7 | private const string InvoicePreviousInvoiceHashXpath = "/*[local-name() = 'Invoice']/*[local-name() = 'AdditionalDocumentReference' and *[local-name()='ID' and .='PIH']]/*[local-name() = 'Attachment']/*[local-name() = 'EmbeddedDocumentBinaryObject']"; 8 | 9 | private const string HashXpath = "/*[local-name() = 'Invoice']/*[local-name() = 'UBLExtensions']/*[local-name() = 'UBLExtension']/*[local-name() = 'ExtensionContent']/*[local-name() = 'UBLDocumentSignatures']/*[local-name() = 'SignatureInformation']/*[local-name() = 'Signature']/*[local-name() = 'SignedInfo']/*[local-name() = 'Reference' and @Id='invoiceSignedData']/*[local-name() = 'DigestValue']"; 10 | 11 | private const string InvoiceIdXpath = "/*[local-name() = 'Invoice']/*[local-name() = 'ID']"; 12 | private const string InvoiceUuidXpath = "/*[local-name() = 'Invoice']/*[local-name() = 'UUID']"; 13 | 14 | private const string InvoiceActualDeliveryDateXpath = 15 | "/*[local-name() = 'Invoice']/*[local-name() = 'ActualDeliveryDate']"; 16 | 17 | private const string InvoiceDocumentCurrencyCodeXpath = 18 | "/*[local-name() = 'Invoice']/*[local-name() = 'DocumentCurrencyCode']"; 19 | 20 | private const string InvoiceTaxCurrencyCodeXpath = 21 | "/*[local-name() = 'Invoice']/*[local-name() = 'TaxCurrencyCode']"; 22 | 23 | private const string InvoiceIssueDateXpath = "/*[local-name() = 'Invoice']/*[local-name() = 'IssueDate']"; 24 | private const string InvoiceIssueTimeXpath = "/*[local-name() = 'Invoice']/*[local-name() = 'IssueTime']"; 25 | 26 | private const string InvoiceLatestDeliveryDateXpath = 27 | "/*[local-name() = 'Invoice']/*[local-name() = 'Delivery']/*[local-name() = 'LatestDeliveryDate']"; 28 | 29 | private const string SupplierStreetNameXpath = 30 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'StreetName']"; 31 | 32 | private const string SupplierBuildingNumberXpath = 33 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'BuildingNumber']"; 34 | 35 | private const string SupplierPlotIdentificationXpath = 36 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'PlotIdentification']"; 37 | 38 | private const string SupplierCitySubdivisionNameXpath = 39 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'CitySubdivisionName']"; 40 | 41 | private const string SupplierCityNameXpath = 42 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'CityName']"; 43 | 44 | private const string SupplierPostalZoneXpath = 45 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'PostalZone']"; 46 | 47 | private const string SupplierCountrySubEntityXpath = 48 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'CountrySubentity']"; 49 | 50 | private const string SupplierCountryXpath = 51 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'Country']/*[local-name() = 'Country']"; 52 | 53 | private const string SupplierRegistrationNameXpath = 54 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PartyLegalEntity']/*[local-name() = 'RegistrationName']"; 55 | 56 | private const string SupplierCrnXpath = 57 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PartyIdentification']/*[local-name() = 'ID']"; 58 | 59 | private const string SupplierTaxSchemeXpath = 60 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PartyTaxScheme']/*[local-name() = 'TaxScheme']/*[local-name() = 'ID']"; 61 | 62 | private const string CustomerStreetNameXpath = 63 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingCustomerParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'StreetName']"; 64 | 65 | private const string CustomerBuildingNumberXpath = 66 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingCustomerParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'BuildingNumber']"; 67 | 68 | private const string CustomerPlotIdentificationXpath = 69 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingCustomerParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'PlotIdentification']"; 70 | 71 | private const string CustomerCitySubdivisionNameXpath = 72 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingCustomerParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'CitySubdivisionName']"; 73 | 74 | private const string CustomerCityNameXpath = 75 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingCustomerParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'CityName']"; 76 | 77 | private const string CustomerPostalZoneXpath = 78 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingCustomerParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'PostalZone']"; 79 | 80 | private const string CustomerCountrySubEntityXpath = 81 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingCustomerParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'CountrySubentity']"; 82 | 83 | private const string CustomerCountryXpath = 84 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingCustomerParty']/*[local-name() = 'Party']/*[local-name() = 'PostalAddress']/*[local-name() = 'Country']/*[local-name() = 'Country']"; 85 | 86 | private const string CustomerRegistrationNameXpath = 87 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingCustomerParty']/*[local-name() = 'Party']/*[local-name() = 'PartyLegalEntity']/*[local-name() = 'RegistrationName']"; 88 | 89 | private const string CustomerNatXpath = 90 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingCustomerParty']/*[local-name() = 'Party']/*[local-name() = 'PartyIdentification']/*[local-name() = 'ID']"; 91 | 92 | private const string CustomerTaxSchemeXpath = 93 | "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingCustomerParty']/*[local-name() = 'Party']/*[local-name() = 'PartyTaxScheme']/*[local-name() = 'TaxScheme']/*[local-name() = 'ID']"; 94 | 95 | private const string AllowanceXpath = "/*[local-name() = 'Invoice']/*[local-name() = 'AllowanceCharge']"; 96 | 97 | private const string TaxTotalTaxAmountXpath = 98 | "/*[local-name() = 'Invoice']/*[local-name() = 'TaxTotal']/*[local-name() = 'TaxAmount']"; 99 | 100 | private const string TaxTotalTaxAmountSecondTagXpath = 101 | "(/*[local-name() = 'Invoice']/*[local-name() = 'TaxTotal']/*[local-name() = 'TaxAmount'])[2]"; 102 | 103 | private const string TaxTotalSubTotalTaxableAmountXpath = 104 | "/*[local-name() = 'Invoice']/*[local-name() = 'TaxTotal']/*[local-name() = 'TaxSubtotal']/*[local-name() = 'TaxableAmount']"; 105 | 106 | private const string TaxTotalSubTotalTaxAmountXpath = 107 | "/*[local-name() = 'Invoice']/*[local-name() = 'TaxTotal']/*[local-name() = 'TaxSubtotal']/*[local-name() = 'TaxAmount']"; 108 | 109 | private const string TaxTotalSubTotalTaxCategoryIdTaxAmountXpath = 110 | "/*[local-name() = 'Invoice']/*[local-name() = 'TaxTotal']/*[local-name() = 'TaxSubtotal']/*[local-name() = 'TaxCategory']/*[local-name() = 'ID']"; 111 | 112 | private const string TaxTotalSubTotalTaxPercentTaxAmountXpath = 113 | "/*[local-name() = 'Invoice']/*[local-name() = 'TaxTotal']/*[local-name() = 'TaxSubtotal']/*[local-name() = 'TaxCategory']/*[local-name() = 'Percent']"; 114 | 115 | private const string CurrencyIdAttributeName = "currencyID"; 116 | 117 | private const string LineItemIdXpath = 118 | "/*[local-name() = 'Invoice']/*[local-name() = 'InvoiceLine']/*[local-name() = 'ID']"; 119 | 120 | private const string LineItemInvoicedQuantityXpath = 121 | "/*[local-name() = 'Invoice']/*[local-name() = 'InvoiceLine']/*[local-name() = 'InvoicedQuantity']"; 122 | 123 | private const string LineItemLineExtensionAmountXpath = 124 | "/*[local-name() = 'Invoice']/*[local-name() = 'InvoiceLine']/*[local-name() = 'LineExtensionAmount']"; 125 | 126 | private const string LineItemTaxAmountXpath = 127 | "/*[local-name() = 'Invoice']/*[local-name() = 'InvoiceLine']/*[local-name() = 'TaxTotal']/*[local-name() = 'TaxAmount']"; 128 | 129 | private const string LineItemRoundingAmountXpath = 130 | "/*[local-name() = 'Invoice']/*[local-name() = 'InvoiceLine']/*[local-name() = 'TaxTotal']/*[local-name() = 'RoundingAmount']"; 131 | 132 | private const string LineItemNameXpath = 133 | "/*[local-name() = 'Invoice']/*[local-name() = 'InvoiceLine']/*[local-name() = 'Item']/*[local-name() = 'Name']"; 134 | 135 | private const string LineItemTaxClassificationIdXpath = 136 | "/*[local-name() = 'Invoice']/*[local-name() = 'InvoiceLine']/*[local-name() = 'Item']/*[local-name() = 'ClassifiedTaxCategory']/*[local-name() = 'ID']"; 137 | 138 | private const string LineItemTaxPercentXpath = 139 | "/*[local-name() = 'Invoice']/*[local-name() = 'InvoiceLine']/*[local-name() = 'Item']/*[local-name() = 'ClassifiedTaxCategory']/*[local-name() = 'Percent']"; 140 | 141 | private const string LineItemTaxSchemeXpath = 142 | "/*[local-name() = 'Invoice']/*[local-name() = 'InvoiceLine']/*[local-name() = 'Item']/*[local-name() = 'ClassifiedTaxCategory']/*[local-name() = 'TaxScheme']/*[local-name() = 'ID']"; 143 | 144 | private const string LineItemPriceAmountXpath = 145 | "/*[local-name() = 'Invoice']/*[local-name() = 'InvoiceLine']/*[local-name() = 'Price']/*[local-name() = 'PriceAmount']"; 146 | 147 | private const string LineItemAllowanceXpath = 148 | "/*[local-name() = 'Invoice']/*[local-name() = 'InvoiceLine']/*[local-name() = 'Price']/*[local-name() = 'AllowanceCharge']"; 149 | 150 | private const string LegalMonetaryTotalLineExtensionAmountXpath = 151 | "/*[local-name() = 'Invoice']/*[local-name() = 'LegalMonetaryTotal']/*[local-name() = 'LineExtensionAmount']"; 152 | 153 | private const string LegalMonetaryTotalTaxExclusiveAmountXpath = 154 | "/*[local-name() = 'Invoice']/*[local-name() = 'LegalMonetaryTotal']/*[local-name() = 'TaxExclusiveAmount']"; 155 | 156 | private const string LegalMonetaryTotalTaxInclusiveAmountXpath = 157 | "/*[local-name() = 'Invoice']/*[local-name() = 'LegalMonetaryTotal']/*[local-name() = 'TaxInclusiveAmount']"; 158 | 159 | private const string LegalMonetaryTotalAllowanceTotalAmountXpath = 160 | "/*[local-name() = 'Invoice']/*[local-name() = 'LegalMonetaryTotal']/*[local-name() = 'AllowanceTotalAmount']"; 161 | 162 | private const string LegalMonetaryTotalPrepaidAmountXpath = 163 | "/*[local-name() = 'Invoice']/*[local-name() = 'LegalMonetaryTotal']/*[local-name() = 'PrepaidAmount']"; 164 | 165 | private const string LegalMonetaryTotalPayableAmountXpath = 166 | "/*[local-name() = 'Invoice']/*[local-name() = 'LegalMonetaryTotal']/*[local-name() = 'PayableAmount']"; 167 | 168 | private const string InvoiceReferenceXpath = 169 | "/*[local-name() = 'Invoice']/*[local-name() = 'BillingReference']/*[local-name() = 'InvoiceDocumentReference']/*[local-name() = 'ID']"; 170 | 171 | private const string AdjustmentReasonXpath = 172 | "/*[local-name() = 'Invoice']/*[local-name() = 'PaymentMeans']/*[local-name() = 'InstructionNote']"; 173 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/StandardInvoiceClearanceAPIClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Http.Json; 3 | using System.Text; 4 | using System.Xml; 5 | using Bee.ZatcaHelper.Contracts; 6 | using Bee.ZatcaHelper.Util; 7 | using WebClient = Bee.ZatcaHelper.Util.WebClient; 8 | 9 | namespace Bee.ZatcaHelper; 10 | 11 | public class StandardInvoiceClearanceApiClient 12 | { 13 | private readonly string _baseUrl; 14 | private readonly string _invoiceClearanceEndPoint; 15 | 16 | public StandardInvoiceClearanceApiClient(GlobalVariables globalVariables) 17 | { 18 | _baseUrl = globalVariables.BaseUrl; 19 | _invoiceClearanceEndPoint = globalVariables.InvoiceClearanceEndPoint; 20 | } 21 | 22 | public InvoiceClearanceResponse ClearInvoice(InvoiceClearanceRequest invoiceClearanceRequest) 23 | { 24 | var customHeaders = new Dictionary {{"Clearance-Status", "1"},{"accept-language","en"}}; 25 | var result = new WebClient(_baseUrl, customHeaders, 26 | invoiceClearanceRequest.BinaryToken, invoiceClearanceRequest.Secret).PostAsJsonAsync( 27 | _invoiceClearanceEndPoint, invoiceClearanceRequest.Body); 28 | var response = result.Result.Content.ReadFromJsonAsync().Result; 29 | 30 | if (result.Result.StatusCode == HttpStatusCode.InternalServerError) 31 | { 32 | throw new Exception("Something went wrong " + result.Result.Content); 33 | } 34 | 35 | if (response?.ClearanceStatus != "CLEARED") return response; 36 | response.Hash = ((dynamic) invoiceClearanceRequest.Body).invoiceHash; 37 | response.GeneratedQR = GetGeneratedQrCode(response); 38 | return response; 39 | } 40 | 41 | private static string? GetGeneratedQrCode(InvoiceClearanceResponse response) 42 | { 43 | var data = Convert.FromBase64String(response.ClearedInvoice); 44 | var decodedString = Encoding.UTF8.GetString(data); 45 | 46 | var xmlDoc = new XmlDocument 47 | { 48 | PreserveWhitespace = true 49 | }; 50 | 51 | xmlDoc.LoadXml(decodedString); 52 | 53 | var value = xmlDoc.GetNodeValue(Bee.ZatcaHelper.StandardInvoiceXmlGenerator.QrCodeXpath); 54 | 55 | return value; 56 | } 57 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/StandardInvoiceTemplate.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | urn:oasis:names:specification:ubl:dsig:enveloped:xades 10 | 11 | 12 | 16 | 17 | urn:oasis:names:specification:ubl:signature:1 18 | urn:oasis:names:specification:ubl:signature:Invoice 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | not(//ancestor-or-self::ext:UBLExtensions) 29 | 30 | 31 | not(//ancestor-or-self::cac:Signature) 32 | 33 | 34 | 35 | not(//ancestor-or-self::cac:AdditionalDocumentReference[cbc:ID='QR']) 36 | 37 | 38 | 39 | 40 | 41 | BK6cj9p18ffIJHe/pWkkIT0myGMrFXYiczWq3se+nm8= 42 | 43 | 45 | 46 | 47 | ZDZjMzQyZTBjNGVjY2VlODgxYTBiMzhiNDFlM2QxZTlkZDkyMDQ1NTEwNzUwN2RhYjUwNGNmZmNmNTFmY2I2OA== 48 | 49 | 50 | 51 | 52 | MEYCIQCYh5e3irIqWl5YtaCnKiujMITmokXYOPMpIDCIoWW79wIhAMXmc+MFHGtaeBseOuEGTlwD6g09gNQPRPuY89s65OWS 53 | 54 | 55 | 56 | 57 | MIID1DCCA3mgAwIBAgITbwAAe3UAYVU34I/+5QABAAB7dTAKBggqhkjOPQQDAjBjMRUwEwYKCZImiZPyLGQBGRYFbG9jYWwxEzARBgoJkiaJk/IsZAEZFgNnb3YxFzAVBgoJkiaJk/IsZAEZFgdleHRnYXp0MRwwGgYDVQQDExNUU1pFSU5WT0lDRS1TdWJDQS0xMB4XDTIyMDYxMjE3NDA1MloXDTI0MDYxMTE3NDA1MlowSTELMAkGA1UEBhMCU0ExDjAMBgNVBAoTBWFnaWxlMRYwFAYDVQQLEw1oYXlhIHlhZ2htb3VyMRIwEAYDVQQDEwkxMjcuMC4wLjEwVjAQBgcqhkjOPQIBBgUrgQQACgNCAATTAK9lrTVko9rkq6ZYcc9HDRZP4b9S4zA4Km7YXJ+snTVhLkzU0HsmSX9Un8jDhRTOHDKaft8C/uuUY934vuMNo4ICJzCCAiMwgYgGA1UdEQSBgDB+pHwwejEbMBkGA1UEBAwSMS1oYXlhfDItMjM0fDMtMTEyMR8wHQYKCZImiZPyLGQBAQwPMzAwMDc1NTg4NzAwMDAzMQ0wCwYDVQQMDAQxMTAwMREwDwYDVQQaDAhaYXRjYSAxMjEYMBYGA1UEDwwPRm9vZCBCdXNzaW5lc3MzMB0GA1UdDgQWBBSgmIWD6bPfbbKkmTwOJRXvIbH9HjAfBgNVHSMEGDAWgBR2YIz7BqCsZ1c1nc+arKcrmTW1LzBOBgNVHR8ERzBFMEOgQaA/hj1odHRwOi8vdHN0Y3JsLnphdGNhLmdvdi5zYS9DZXJ0RW5yb2xsL1RTWkVJTlZPSUNFLVN1YkNBLTEuY3JsMIGtBggrBgEFBQcBAQSBoDCBnTBuBggrBgEFBQcwAYZiaHR0cDovL3RzdGNybC56YXRjYS5nb3Yuc2EvQ2VydEVucm9sbC9UU1pFaW52b2ljZVNDQTEuZXh0Z2F6dC5nb3YubG9jYWxfVFNaRUlOVk9JQ0UtU3ViQ0EtMSgxKS5jcnQwKwYIKwYBBQUHMAGGH2h0dHA6Ly90c3RjcmwuemF0Y2EuZ292LnNhL29jc3AwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDAzAnBgkrBgEEAYI3FQoEGjAYMAoGCCsGAQUFBwMCMAoGCCsGAQUFBwMDMAoGCCqGSM49BAMCA0kAMEYCIQCVwDMcq6PO+McmsBXUz/v1GdhGp7rqSa2AxTKSv838IAIhAOBNDBt9+3DSlijoVfxzrdDh528WC37smEdoGWVrSpG1 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 2022-06-13T11:06:14Z 67 | 68 | 69 | 70 | 72 | 73 | NDliYWY0YmVmNzAyZmE4OWIwZDMzYWIzZjFkOTY5YzFhZjRhZDE4ZTFkODlkNDY2Y2UwYzI2N2QyNDg0MWVlYQ== 74 | 75 | 76 | 77 | CN=TSZEINVOICE-SubCA-1, DC=extgazt, DC=gov, 78 | DC=local 79 | 80 | 81 | 2475382881139449094209233032847838632732556149 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | reporting:1.0 97 | SME00062 98 | 6f4d20e0-6bfe-4a80-9389-7dabe6620f12 99 | 2022-03-13 100 | 14:40:40 101 | 388 102 | SAR 103 | SAR 104 | 105 | ICV 106 | 63 107 | 108 | 109 | PIH 110 | 111 | 112 | NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ== 113 | 114 | 115 | 116 | 117 | urn:oasis:names:specification:ubl:signature:Invoice 118 | urn:oasis:names:specification:ubl:dsig:enveloped:xades 119 | 120 | 121 | 122 | 123 | 310175397400003 124 | 125 | 126 | test 127 | 3454 128 | 1234 129 | fgff 130 | Riyadh 131 | 12345 132 | test 133 | 134 | SA 135 | 136 | 137 | 138 | 310175397400003 139 | 140 | VAT 141 | 142 | 143 | 144 | United Gulf Aircraft Fueling Company 145 | 146 | 147 | 148 | 149 | 150 | 151 | 2345 152 | 153 | 154 | baaoun 155 | sdsd 156 | 3353 157 | 3434 158 | fgff 159 | Dhurma 160 | 34534 161 | ulhk 162 | 163 | SA 164 | 165 | 166 | 167 | 168 | VAT 169 | 170 | 171 | 172 | sdsa 173 | 174 | 175 | 176 | 177 | 2022-03-13 178 | 2022-03-13 179 | 180 | 181 | 1 182 | false 183 | discount 184 | 2 185 | 186 | S 187 | 15 188 | 189 | VAT 190 | 191 | 192 | 193 | 194 | 144.9 195 | 196 | 966.00 197 | 144.90 198 | 199 | S 200 | 15.00 201 | 202 | VAT 203 | 204 | 205 | 206 | 207 | 208 | 151.00 209 | 210 | 211 | 966.00 212 | 964.00 213 | 1108.90 214 | 2.00 215 | 0.00 216 | 1108.90 217 | 218 | 219 | 1 220 | 44.000000 221 | 966.00 222 | 223 | 144.90 224 | 1110.90 225 | 226 | 227 | dsd 228 | 229 | S 230 | 15.00 231 | 232 | VAT 233 | 234 | 235 | 236 | 237 | 22.00 238 | 239 | false 240 | discount 241 | 2.00 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /Bee.ZatcaHelper/StandardInvoiceXmlGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Xml; 2 | using Bee.ZatcaHelper.Model; 3 | using Bee.ZatcaHelper.Util; 4 | 5 | namespace Bee.ZatcaHelper; 6 | 7 | public static partial class StandardInvoiceXmlGenerator 8 | { 9 | private const string StandardInvoiceXmlFormat = "StandardInvoiceTemplate.xml"; 10 | private static string? _currentHash; 11 | 12 | public static object GenerateForDebitNote(StandardInvoiceAdjustment standardInvoiceAdjustment) 13 | { 14 | const string debitNoteXmlFormat = "DebitNote.xml"; 15 | return GenerateForAdjustment(standardInvoiceAdjustment, debitNoteXmlFormat); 16 | } 17 | 18 | public static object GenerateForCreditNote(StandardInvoiceAdjustment standardInvoiceAdjustment) 19 | { 20 | const string debitNoteXmlFormat = "CreditNote.xml"; 21 | return GenerateForAdjustment(standardInvoiceAdjustment, debitNoteXmlFormat); 22 | } 23 | 24 | private static object GenerateForAdjustment(StandardInvoiceAdjustment standardInvoiceAdjustment, 25 | string debitNoteXmlFormat) 26 | { 27 | var xmlDoc = new XmlDocument 28 | { 29 | PreserveWhitespace = true 30 | }; 31 | var xmlString = typeof(StandardInvoiceXmlGenerator).GetFileContent(debitNoteXmlFormat); 32 | 33 | 34 | var standardInvoice = (StandardInvoice) standardInvoiceAdjustment; 35 | xmlDoc.LoadXml(xmlString); 36 | PopulateInvoiceBasicInfo(xmlDoc, standardInvoice); 37 | PopulateSupplierInfo(xmlDoc, standardInvoice); 38 | PopulateCustomerInfo(xmlDoc, standardInvoice); 39 | PopulateDiscountInfo(xmlDoc, standardInvoice); 40 | PopulateTaxTotals(xmlDoc, standardInvoice); 41 | PopulateInvoiceLineItems(xmlDoc, standardInvoice); 42 | PopulateTotals(xmlDoc, standardInvoice); 43 | 44 | xmlDoc.SetNodeValue(InvoiceReferenceXpath, standardInvoiceAdjustment.AdjsutmentInvoiceNumber); 45 | xmlDoc.SetNodeValue(AdjustmentReasonXpath, standardInvoiceAdjustment.AdjustmentReason); 46 | 47 | PopulateInvoiceHash(xmlDoc); 48 | 49 | 50 | var xmlBytes = System.Text.Encoding.UTF8.GetBytes(xmlDoc.OuterXml); 51 | var sample = xmlDoc.OuterXml; 52 | 53 | var resultAPiCall = new 54 | { 55 | invoiceHash = _currentHash, 56 | uuid = standardInvoice.UUID, 57 | invoice = Convert.ToBase64String(xmlBytes) 58 | }; 59 | return resultAPiCall; 60 | } 61 | 62 | public static object Generate(StandardInvoice standardInvoice) 63 | { 64 | 65 | var xmlDoc = new XmlDocument 66 | { 67 | PreserveWhitespace = true 68 | }; 69 | var xmlString = typeof(StandardInvoiceXmlGenerator).GetFileContent(StandardInvoiceXmlFormat); 70 | 71 | 72 | xmlDoc.LoadXml(xmlString); 73 | PopulateInvoiceBasicInfo(xmlDoc, standardInvoice); 74 | PopulateSupplierInfo(xmlDoc, standardInvoice); 75 | PopulateCustomerInfo(xmlDoc, standardInvoice); 76 | PopulateDiscountInfo(xmlDoc, standardInvoice); 77 | PopulateTaxTotals(xmlDoc, standardInvoice); 78 | PopulateInvoiceLineItems(xmlDoc, standardInvoice); 79 | PopulateTotals(xmlDoc, standardInvoice); 80 | PopulateInvoiceHash(xmlDoc); 81 | 82 | 83 | 84 | var xmlBytes = System.Text.Encoding.UTF8.GetBytes(xmlDoc.OuterXml); 85 | var sample = xmlDoc.OuterXml; 86 | 87 | var resultAPiCall = new 88 | { 89 | invoiceHash = _currentHash, 90 | uuid = standardInvoice.UUID, 91 | invoice = Convert.ToBase64String(xmlBytes) 92 | }; 93 | 94 | // Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(resultAPiCall)); 95 | // Console.WriteLine(xmlDoc.OuterXml); 96 | 97 | return resultAPiCall; 98 | } 99 | 100 | private static void PopulateInvoiceHash(XmlDocument xmlDoc) 101 | { 102 | _currentHash = InvoiceHashHelper.GenerateEInvoiceHashing(xmlDoc.OuterXml); 103 | xmlDoc.SetNodeValue(HashXpath, _currentHash); 104 | } 105 | 106 | private static void PopulateTotals(XmlDocument xmlDoc, StandardInvoice standardInvoice) 107 | { 108 | xmlDoc.SetCurrencyNodeValue(LegalMonetaryTotalLineExtensionAmountXpath, standardInvoice.LegalMonetaryTotal.LineExtensionAmount); 109 | xmlDoc.SetCurrencyNodeValue(LegalMonetaryTotalTaxExclusiveAmountXpath, standardInvoice.LegalMonetaryTotal.TaxExclusiveAmount); 110 | xmlDoc.SetCurrencyNodeValue(LegalMonetaryTotalTaxInclusiveAmountXpath, standardInvoice.LegalMonetaryTotal.TaxInclusiveAmount); 111 | xmlDoc.SetCurrencyNodeValue(LegalMonetaryTotalAllowanceTotalAmountXpath, standardInvoice.LegalMonetaryTotal.AllowanceTotalAmount); 112 | xmlDoc.SetCurrencyNodeValue(LegalMonetaryTotalAllowanceTotalAmountXpath, standardInvoice.LegalMonetaryTotal.AllowanceTotalAmount); 113 | xmlDoc.SetCurrencyNodeValue(LegalMonetaryTotalPrepaidAmountXpath, standardInvoice.LegalMonetaryTotal.PrepaidAmount); 114 | xmlDoc.SetCurrencyNodeValue(LegalMonetaryTotalPayableAmountXpath, standardInvoice.LegalMonetaryTotal.PayableAmount); 115 | } 116 | 117 | private static void PopulateInvoiceLineItems(XmlDocument xmlDoc, StandardInvoice standardInvoice) 118 | { 119 | xmlDoc.SetNodeValue(LineItemIdXpath, standardInvoice.InvoiceLineItem.ID); 120 | xmlDoc.SetNodeValue(LineItemInvoicedQuantityXpath, standardInvoice.InvoiceLineItem.InvoicedQuantity); 121 | xmlDoc.SetCurrencyNodeValue(LineItemLineExtensionAmountXpath, standardInvoice.InvoiceLineItem.LineExtensionAmount); 122 | xmlDoc.SetCurrencyNodeValue(LineItemTaxAmountXpath, standardInvoice.InvoiceLineItem.TaxAmount); 123 | xmlDoc.SetCurrencyNodeValue(LineItemRoundingAmountXpath, standardInvoice.InvoiceLineItem.RoundingAmount); 124 | xmlDoc.SetNodeValue(LineItemNameXpath, standardInvoice.InvoiceLineItem.ItemName); 125 | xmlDoc.SetNodeValue(LineItemTaxPercentXpath, standardInvoice.InvoiceLineItem.TaxPercent); 126 | xmlDoc.SetNodeValue(LineItemTaxClassificationIdXpath, standardInvoice.InvoiceLineItem.TaxSchemeId); 127 | xmlDoc.SetNodeValue(LineItemTaxSchemeXpath, standardInvoice.InvoiceLineItem.TaxScheme); 128 | xmlDoc.SetCurrencyNodeValue(LineItemPriceAmountXpath, standardInvoice.InvoiceLineItem.PriceAmount); 129 | 130 | // TODO : implement Allowance charge 131 | 132 | xmlDoc.RemoveNode(LineItemAllowanceXpath); 133 | } 134 | 135 | private static void PopulateTaxTotals(XmlDocument xmlDoc, StandardInvoice standardInvoice) 136 | { 137 | //TODO: Need to handle tax exemption 138 | 139 | xmlDoc.SetCurrencyNodeValue(TaxTotalTaxAmountXpath, standardInvoice.TaxTotal.TaxAmount); 140 | xmlDoc.SetCurrencyNodeValue(TaxTotalTaxAmountSecondTagXpath, standardInvoice.TaxTotal.TaxAmount); 141 | xmlDoc.SetCurrencyNodeValue(TaxTotalSubTotalTaxableAmountXpath,standardInvoice.TaxTotal.TaxableAmount); 142 | xmlDoc.SetCurrencyNodeValue(TaxTotalSubTotalTaxAmountXpath,standardInvoice.TaxTotal.TaxAmount); 143 | xmlDoc.SetNodeValue(TaxTotalSubTotalTaxCategoryIdTaxAmountXpath,standardInvoice.TaxTotal.TaxCategory); 144 | xmlDoc.SetNodeValue(TaxTotalSubTotalTaxPercentTaxAmountXpath,standardInvoice.TaxTotal.Percent); 145 | } 146 | 147 | private static void PopulateDiscountInfo(XmlDocument xmlDoc, StandardInvoice standardInvoice) 148 | { 149 | // TODO : implement Allowance charge 150 | 151 | xmlDoc.RemoveNode(AllowanceXpath); 152 | } 153 | 154 | private static void PopulateCustomerInfo(XmlDocument xmlDoc, StandardInvoice standardInvoice) 155 | { 156 | xmlDoc.SetNodeValue(CustomerStreetNameXpath, standardInvoice.CustomerInfo.PartyPostalAddress.StreetName); 157 | xmlDoc.SetNodeValue(CustomerBuildingNumberXpath, standardInvoice.CustomerInfo.PartyPostalAddress.BuildingNumber); 158 | xmlDoc.SetNodeValue(CustomerPlotIdentificationXpath, standardInvoice.CustomerInfo.PartyPostalAddress.PlotIdentification); 159 | xmlDoc.SetNodeValue(CustomerCitySubdivisionNameXpath, standardInvoice.CustomerInfo.PartyPostalAddress.CitySubdivisionName); 160 | xmlDoc.SetNodeValue(CustomerCityNameXpath, standardInvoice.CustomerInfo.PartyPostalAddress.CityName); 161 | xmlDoc.SetNodeValue(CustomerPostalZoneXpath, standardInvoice.CustomerInfo.PartyPostalAddress.PostalZone); 162 | xmlDoc.SetNodeValue(CustomerCountrySubEntityXpath, standardInvoice.CustomerInfo.PartyPostalAddress.CountrySubentity); 163 | xmlDoc.SetNodeValue(CustomerCountryXpath, standardInvoice.CustomerInfo.PartyPostalAddress.Country); 164 | xmlDoc.SetNodeValue(CustomerRegistrationNameXpath, standardInvoice.CustomerInfo.RegistrationName); 165 | xmlDoc.SetNodeValue(CustomerNatXpath, standardInvoice.CustomerInfo.NAT); 166 | xmlDoc.SetNodeValue(CustomerTaxSchemeXpath, "VAT"); 167 | } 168 | 169 | private static void PopulateSupplierInfo(XmlDocument xmlDoc, StandardInvoice standardInvoice) 170 | { 171 | xmlDoc.SetNodeValue(SupplierStreetNameXpath, standardInvoice.SupplierInfo.PartyPostalAddress.StreetName); 172 | xmlDoc.SetNodeValue(SupplierBuildingNumberXpath, standardInvoice.SupplierInfo.PartyPostalAddress.BuildingNumber); 173 | xmlDoc.SetNodeValue(SupplierPlotIdentificationXpath, standardInvoice.SupplierInfo.PartyPostalAddress.PlotIdentification); 174 | xmlDoc.SetNodeValue(SupplierCitySubdivisionNameXpath, standardInvoice.SupplierInfo.PartyPostalAddress.CitySubdivisionName); 175 | xmlDoc.SetNodeValue(SupplierCityNameXpath, standardInvoice.SupplierInfo.PartyPostalAddress.CityName); 176 | xmlDoc.SetNodeValue(SupplierPostalZoneXpath, standardInvoice.SupplierInfo.PartyPostalAddress.PostalZone); 177 | xmlDoc.SetNodeValue(SupplierCountrySubEntityXpath, standardInvoice.SupplierInfo.PartyPostalAddress.CountrySubentity); 178 | xmlDoc.SetNodeValue(SupplierCountryXpath, standardInvoice.SupplierInfo.PartyPostalAddress.Country); 179 | xmlDoc.SetNodeValue(SupplierRegistrationNameXpath, standardInvoice.SupplierInfo.RegistrationName); 180 | xmlDoc.SetNodeValue(SupplierTaxSchemeXpath, "VAT"); 181 | xmlDoc.SetNodeValue(SupplierCrnXpath, standardInvoice.SupplierInfo.CRN); 182 | } 183 | 184 | private static void PopulateInvoiceBasicInfo(XmlDocument xmlDoc, StandardInvoice standardInvoice) 185 | { 186 | xmlDoc.SetNodeValue(InvoiceIdXpath, standardInvoice.Id); 187 | xmlDoc.SetNodeValue(InvoiceUuidXpath, standardInvoice.UUID); 188 | xmlDoc.SetNodeValue(InvoiceActualDeliveryDateXpath, standardInvoice.ActualDeliveryDate); 189 | xmlDoc.SetNodeValue(InvoiceDocumentCurrencyCodeXpath, standardInvoice.DocumentCurrencyCode); 190 | xmlDoc.SetNodeValue(InvoiceTaxCurrencyCodeXpath, standardInvoice.TaxCurrencyCode); 191 | xmlDoc.SetNodeValue(InvoiceIssueDateXpath, standardInvoice.IssuedDate); 192 | xmlDoc.SetNodeValue(InvoiceIssueTimeXpath, standardInvoice.IssuedTime); 193 | xmlDoc.SetNodeValue(InvoiceLatestDeliveryDateXpath, standardInvoice.LatestDeliveryDate); 194 | xmlDoc.SetNodeValue(InvoicePreviousInvoiceHashXpath, standardInvoice.PreviousInvoiceHash); 195 | } 196 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Util/EmbeddedResourceHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Bee.ZatcaHelper.Util; 2 | 3 | public static class EmbeddedResourceHelper 4 | { 5 | public static string? GetFileContent(this Type type,string fileName) 6 | { 7 | var fullyQualifiedName = type.Namespace + "." + fileName; 8 | using var stream = typeof(Bee.ZatcaHelper.StandardInvoiceXmlGenerator).Assembly.GetManifestResourceStream(fullyQualifiedName); 9 | if (stream == null) return null; 10 | using var reader = new StreamReader(stream); 11 | var fileContent = reader.ReadToEnd(); 12 | return fileContent; 13 | } 14 | 15 | public static StreamReader? GetFileContentAsStream(this Type type,string fileName) 16 | { 17 | var fullyQualifiedName = type.Namespace + "." + fileName; 18 | var stream = typeof(Bee.ZatcaHelper.StandardInvoiceXmlGenerator).Assembly.GetManifestResourceStream(fullyQualifiedName); 19 | return stream == null ? null : new StreamReader(stream); 20 | } 21 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Util/InvoiceHashHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Security.Cryptography.Xml; 3 | using System.Text; 4 | using System.Xml; 5 | using System.Xml.Xsl; 6 | using Serilog; 7 | 8 | namespace Bee.ZatcaHelper.Util; 9 | 10 | public static class InvoiceHashHelper 11 | { 12 | private const string StandardInvoiceHashingXslTemplate = "StandardInvoiceHashingTemplate.xsl"; 13 | 14 | public static string? GenerateEInvoiceHashing(string xmlString) 15 | { 16 | try 17 | { 18 | var xslStream = typeof(InvoiceHashHelper).GetFileContentAsStream(StandardInvoiceHashingXslTemplate); 19 | string transformedXml; 20 | try 21 | { 22 | transformedXml = ApplyXslt(xmlString, xslStream); 23 | } 24 | catch 25 | { 26 | Log.Error("Can not apply XSL file"); 27 | return null; 28 | } 29 | 30 | if (string.IsNullOrEmpty(transformedXml)) 31 | { 32 | Log.Error("Error In applying XSL file"); 33 | return null; 34 | } 35 | 36 | var canonicalizedXml = CanonicalizeXml(transformedXml); 37 | var hashValue = GetSha256Hash(Encoding.UTF8.GetString(canonicalizedXml?.ToArray() ?? Array.Empty())); 38 | if (hashValue.Any()) return Convert.ToBase64String(hashValue); 39 | Log.Error("Hashing Error"); 40 | return null; 41 | } 42 | catch (Exception ex) 43 | { 44 | Log.Error(ex,ex.Message); 45 | return null; 46 | } 47 | } 48 | 49 | private static MemoryStream? CanonicalizeXml(string transformedXml) 50 | { 51 | using var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(transformedXml)); 52 | //converting xml to standard format 53 | //https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.xml.xmldsigc14ntransform?view=dotnet-plat-ext-6.0 54 | var canonicalizer = new XmlDsigC14NTransform(false); 55 | canonicalizer.LoadInput(memoryStream); 56 | var canonicalizedXml = canonicalizer.GetOutput() as MemoryStream; 57 | return canonicalizedXml; 58 | } 59 | 60 | 61 | private static byte[] GetSha256Hash(string value) 62 | { 63 | using var shA256 = SHA256.Create(); 64 | var utF8 = Encoding.UTF8; 65 | return shA256.ComputeHash(utF8.GetBytes(value)); 66 | } 67 | 68 | private static string ApplyXslt(string xmlContent, StreamReader? xslFile) 69 | { 70 | var xmlReader = XmlReader.Create(new StringReader(xmlContent)); 71 | var output = new StringBuilder(); 72 | using (var results = XmlWriter.Create(output, new XmlWriterSettings() 73 | { 74 | OmitXmlDeclaration = true, 75 | Encoding = Encoding.UTF8, 76 | Indent = false 77 | })) 78 | { 79 | if (xslFile != null) 80 | { 81 | var stylesheet = XmlReader.Create(xslFile); 82 | 83 | var compiledTransform = new XslCompiledTransform(); 84 | compiledTransform.Load(stylesheet); 85 | compiledTransform.Transform(xmlReader, results); 86 | } 87 | } 88 | 89 | xslFile?.Close(); 90 | return output.ToString(); 91 | } 92 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Util/StandardInvoiceHashingTemplate.xsl: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Util/WebClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Net.Http.Headers; 3 | using System.Net.Http.Json; 4 | using System.Text; 5 | using Serilog; 6 | 7 | namespace Bee.ZatcaHelper.Util; 8 | 9 | public class WebClient 10 | { 11 | private readonly HttpClient _client = new(); 12 | 13 | public WebClient(string baseUrl, Dictionary customHeaders) 14 | { 15 | _client.BaseAddress = new Uri(baseUrl); 16 | _client.DefaultRequestHeaders.Accept.Clear(); 17 | _client.DefaultRequestHeaders.Accept.Add( 18 | new MediaTypeWithQualityHeaderValue("application/json")); 19 | _client.DefaultRequestHeaders.Add("Accept-Version", "V2"); 20 | 21 | foreach (var (header, value) in customHeaders.Select( x => (x.Key, x.Value) )) 22 | { 23 | _client.DefaultRequestHeaders.Add(header, value); 24 | } 25 | } 26 | 27 | public WebClient(string baseUrl, Dictionary customHeaders, string userName, string password) 28 | : this(baseUrl, customHeaders) 29 | { 30 | var credentials = Convert.ToBase64String( 31 | Encoding.ASCII.GetBytes(userName + ":" + password)); 32 | _client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", $"Basic {credentials}"); 33 | } 34 | 35 | public async Task PostAsJsonAsync(string controller, object data) 36 | { 37 | HttpResponseMessage response = new(); 38 | 39 | try 40 | { 41 | response = await _client.PostAsJsonAsync(controller, data).ConfigureAwait(false); 42 | 43 | response.EnsureSuccessStatusCode(); 44 | } 45 | catch (Exception ex) 46 | { 47 | Log.Error(ex, "Exception occured - {0}", ex.Message); 48 | } 49 | 50 | return response; 51 | } 52 | } -------------------------------------------------------------------------------- /Bee.ZatcaHelper/Util/XmlHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Xml; 2 | using Bee.ZatcaHelper.Model; 3 | 4 | namespace Bee.ZatcaHelper.Util; 5 | 6 | public static class XmlHelper 7 | { 8 | public static void SetNodeValue(this XmlDocument xmlDocument, string xPath, string? value) 9 | { 10 | var xmlNode = xmlDocument.SelectSingleNode(xPath); 11 | if (xmlNode == null) 12 | return; 13 | if (value != null) xmlNode.InnerText = value; 14 | } 15 | 16 | public static void RemoveNode(this XmlDocument xmlDocument, string xPath) 17 | { 18 | var xmlNode = xmlDocument.SelectSingleNode(xPath); 19 | xmlNode?.ParentNode?.RemoveChild(xmlNode); 20 | } 21 | 22 | public static void SetAttributeValue(this XmlDocument xmlDocument, string xPath, string attributeName, 23 | string? value) 24 | { 25 | var xmlNode = xmlDocument.SelectSingleNode(xPath); 26 | if (xmlNode?.Attributes == null) return; 27 | foreach (XmlAttribute attribute in xmlNode.Attributes) 28 | { 29 | if (attribute.Name == attributeName) 30 | attribute.Value = value; 31 | } 32 | } 33 | 34 | public static void SetCurrencyNodeValue(this XmlDocument xmlDocument, string xPath, string? value, string? attributeValue) 35 | { 36 | const string currencyIdAttributeName = "currencyID"; 37 | 38 | var xmlNode = xmlDocument.SelectSingleNode(xPath); 39 | if (xmlNode?.Attributes == null) return; 40 | foreach (XmlAttribute attribute in xmlNode.Attributes) 41 | { 42 | if (attribute.Name == currencyIdAttributeName) 43 | attribute.Value = attributeValue; 44 | } 45 | 46 | if (value != null) xmlNode.InnerText = value; 47 | } 48 | 49 | public static void SetCurrencyNodeValue(this XmlDocument xmlDocument, string xPath, Money money) 50 | { 51 | const string currencyIdAttributeName = "currencyID"; 52 | 53 | var xmlNode = xmlDocument.SelectSingleNode(xPath); 54 | if (xmlNode?.Attributes == null) return; 55 | foreach (XmlAttribute attribute in xmlNode.Attributes) 56 | { 57 | if (attribute.Name == currencyIdAttributeName) 58 | attribute.Value = money.CurrencyCode; 59 | } 60 | 61 | xmlNode.InnerText = money.GetAmountString(); 62 | } 63 | 64 | public static string? GetNodeValue(this XmlDocument xmlDocument, string xPath) 65 | { 66 | var xmlNode = xmlDocument.SelectSingleNode(xPath); 67 | return xmlNode?.InnerText; 68 | } 69 | } -------------------------------------------------------------------------------- /Logo/Devbee-Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devbee-engineering/ZatcaHelper/1ef567cd6f1775a7ba98f4e7d18ac4d3198bb7a7/Logo/Devbee-Logo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Zatca | Fatoora - Integeration Helper 3 | 4 | This project helps in integertation of systems to Zatca systems. 5 | Currently tested using their sandbox APIs. 6 | 7 | > 🚀 Check [Pro feature list](https://github.com/devbee-engineering/ZatcaHelper/blob/main/pro-version-features.md) for the Pro version, we already have more than 10 clients using our pro version. 8 | 9 | 10 | > **_NOTE:_** We have implemented similar solutions for many of our B2B clients in KSA, if you have similar requirements feel free to reach me at +91 - 9003095635 11 | 12 | ## 🔗 Contact 13 | [![linkedin](https://img.shields.io/badge/linkedin-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/fasilmarshooq) 14 | 15 | Write to me at **fasil@dev-bee.com** 16 | 17 | ## 🔗 NuGet Package 18 | [![Nuget](https://img.shields.io/nuget/dt/Bee.ZatcaHelper?color=Blue&label=Nuget&logo=Nuget&style=flat-square)](https://www.nuget.org/packages/Bee.ZatcaHelper/) 19 | 20 | 21 | ## Follow below shell commands to generate CSR 22 | 23 | we need a csr token to initiate compliance API request , more about what is CSR here -> https://en.wikipedia.org/wiki/Certificate_signing_request 24 | 25 | ```bash 26 | #generate private key using below command 27 | openssl ecparam -name secp256k1 -genkey -noout -out privatekey.pem 28 | 29 | #generate csr using below command 30 | openssl req -new -sha256 -key privatekey.pem -extensions v3_req -config cert.cnf -out generatedCSR.csr 31 | 32 | #convert to base64 33 | echo -n `cat generatedCSR.csr` | openssl enc -base64 -a -A >> csr_Output.txt 34 | ``` 35 | > **_NOTE:_** Above script is bash/shell script, if you are running from windows cmd setup openssl and execute the commands or simply dowload the git bash (https://git-scm.com/download/win) which can help in execution 36 | ## API Reference 37 | 38 | **Note:** Global variables is a dependecy for all the classes , its parameters are described below 39 | 40 | | Parameter | Type | Description | 41 | | :-------- | :------- | :-------------------------------- | 42 | | `BaseUrl` | `string` | **Required**. Base url of zatca [Eg: https://gw-apic-gov.gazt.gov.sa] | 43 | | `ComplianceCsidEndpoint` | `string` | **Required**. Compliance Csid endpoint [Eg: /e-invoicing/developer-portal/compliance] | 44 | | `ProdCsidEndpoint` | `string` | **Required**. Prod csid endpoint [Eg : /e-invoicing/developer-portal/production/csids] | 45 | | `InvoiceClearanceEndPoint` | `string` | **Required**. clearance endpoint [Eg: /e-invoicing/developer-portal/invoices/clearance/single] | 46 | 47 | #### Get Compliance CSR token 48 | 49 | ```http 50 | ComplianceCsrApiClient(GlobalVariables).GetToken(ComplianceCsrRequest) 51 | ``` 52 | 53 | | ComplianceCsrRequest Parameter | Type | Description | 54 | | :-------- | :------- | :------------------------- | 55 | | `Otp` | `string` | **Required**. OTP obatined from tax portal | 56 | | `Csr` | `string` | **Required**. CSR token generated using above mentioned step | 57 | 58 | #### Get Prod CSID 59 | 60 | ```http 61 | ProdCsidApiClient(GlobalVariables).GetToken(ProdCsidOnboardingRequest) 62 | ``` 63 | 64 | | ProdCsidOnboardingRequest Parameter | Type | Description | 65 | | :-------- | :------- | :-------------------------------- | 66 | | `ComplianceRequestId` | `string` | **Required**. RequestId from the response of above endpoint | 67 | | `CsrBinaryToken` | `string` | **Required**. Binary token from the response of above endpoint | 68 | | `CsrSecret` | `string` | **Required**. Secret from the response of above endpoint | 69 | 70 | #### Generate xml 71 | 72 | ```http 73 | StandardInvoiceXmlGenerator.Generate(StandardInvoice) 74 | ``` 75 | 76 | For parameters refer -> https://github.com/fasilmarshooq/Zatca_Integration_Helper/blob/main/Zatca-Standard-Invoice-Integration-Client/Model/StandardInvoice.cs 77 | 78 | #### Clear Invoice 79 | 80 | ```http 81 | StandardInvoiceClearanceApiClient(GlobalVariables).ClearInvoice(InvoiceClearanceRequest) 82 | ``` 83 | 84 | | InvoiceClearanceRequest Parameter | Type | Description | 85 | | :-------- | :------- | :-------------------------------- | 86 | | `Body` | `string` | **Required**. response of above endpoint | 87 | | `BinaryToken` | `string` | **Required**. Binary token from the response of Get Prod CSID endpoint | 88 | | `Secret` | `string` | **Required**. Secret from the response of Get Prod CSID endpoint | 89 | 90 | #### Generate xml for Debit Note 91 | 92 | ```http 93 | StandardInvoiceXmlGenerator.GenerateForDebitNote(StandardInvoiceAdjustment) 94 | ``` 95 | 96 | For parameters refer -> https://github.com/fasilmarshooq/Zatca_Integration_Helper/blob/main/Zatca-Standard-Invoice-Integration-Client/Model/StandardInvoiceAdjustment.cs 97 | 98 | > **_NOTE:_** use above clear Invoice endpoint to clear Debit Note 99 | 100 | #### Generate xml for Credit Note 101 | 102 | ```http 103 | StandardInvoiceXmlGenerator.GenerateForCreditNote(StandardInvoiceAdjustment) 104 | ``` 105 | 106 | For parameters refer -> https://github.com/fasilmarshooq/Zatca_Integration_Helper/blob/main/Zatca-Standard-Invoice-Integration-Client/Model/StandardInvoiceAdjustment.cs 107 | 108 | > **_NOTE:_** use above clear Invoice endpoint to clear Credit Note 109 | ## Roadmap 110 | 111 | - implement Allowance charge / Discount 112 | 113 | - implement reporting api for simplified tax invoices 114 | 115 | ## License 116 | 117 | [MIT](https://choosealicense.com/licenses/mit/) 118 | 119 | ## Trouble Shooting 120 | 121 | - If you facing Schema validation execption without any error message please refer -> https://stackoverflow.com/questions/73388407/schema-validation-failed-xml-does-not-comply-with-ubl-2-1-standards-in-line-wit 122 | 123 | - UBL Schema repo -> http://www.datypic.com/sc/ubl21/e-ns39_Invoice.html 124 | 125 | -------------------------------------------------------------------------------- /Zatca-Standard-Invoice-Integration-Client.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bee.ZatcaHelper", "Bee.ZatcaHelper\Bee.ZatcaHelper.csproj", "{DE9A3E6E-9E5E-41EF-ABCC-AB8D81BE323D}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bee.ZatcaHelper.IntegrationTests", "Bee.ZatcaHelper.IntegrationTests\Bee.ZatcaHelper.IntegrationTests.csproj", "{9DC352F7-577D-4180-97C5-36320D0794B4}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bee.ZatcaHelper.UnitTests", "Bee.ZatcaHelper.UnitTests\Bee.ZatcaHelper.UnitTests.csproj", "{61240BFD-01B7-4EA0-B48F-206A658C1732}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {DE9A3E6E-9E5E-41EF-ABCC-AB8D81BE323D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {DE9A3E6E-9E5E-41EF-ABCC-AB8D81BE323D}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {DE9A3E6E-9E5E-41EF-ABCC-AB8D81BE323D}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {DE9A3E6E-9E5E-41EF-ABCC-AB8D81BE323D}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {9DC352F7-577D-4180-97C5-36320D0794B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {9DC352F7-577D-4180-97C5-36320D0794B4}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {9DC352F7-577D-4180-97C5-36320D0794B4}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {9DC352F7-577D-4180-97C5-36320D0794B4}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {61240BFD-01B7-4EA0-B48F-206A658C1732}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {61240BFD-01B7-4EA0-B48F-206A658C1732}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {61240BFD-01B7-4EA0-B48F-206A658C1732}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {61240BFD-01B7-4EA0-B48F-206A658C1732}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /pro-version-features.md: -------------------------------------------------------------------------------- 1 | 2 | # Zatca | Fatoora - Integeration Helper 3 | 4 | This project helps in integertation of systems to Zatca systems. 5 | Currently tested using their sandbox APIs. 6 | 7 | --- 8 |
9 | 10 |

CSR generation

11 |

B2C Simplified Invoice Generation is available Now

12 |

Compliance API

13 |

Reporting API

14 |
15 | 16 | --- 17 | 18 | ## Follow [This guide](CSR_Generation.md) to generate csr token. 19 | 20 | ## API Reference 21 | 22 | **Note:** Global variables is a dependecy for all the classes , its parameters are described below 23 | 24 | | Parameter | Type | Description | 25 | | :-------- | :------- | :-------------------------------- | 26 | | `BaseUrl` | `string` | **Required**. Base url of zatca [Eg: https://gw-apic-gov.gazt.gov.sa] | 27 | | `ComplianceCsidEndpoint` | `string` | **Required**. Compliance Csid endpoint [Eg: /e-invoicing/developer-portal/compliance] | 28 | | `ProdCsidEndpoint` | `string` | **Required**. Prod csid endpoint [Eg : /e-invoicing/developer-portal/production/csids] | 29 | | `InvoiceClearanceEndPoint` | `string` | **Required**. clearance endpoint [Eg: /e-invoicing/developer-portal/invoices/clearance/single] | 30 | 31 | #### Get Compliance CSR token 32 | 33 | ```http 34 | ComplianceCsrApiClient(GlobalVariables).GetToken(ComplianceCsrRequest) 35 | ``` 36 | 37 | | ComplianceCsrRequest Parameter | Type | Description | 38 | | :-------- | :------- | :------------------------- | 39 | | `Otp` | `string` | **Required**. OTP obatined from tax portal | 40 | | `Csr` | `string` | **Required**. CSR token generated using above mentioned step | 41 | 42 | #### Get Prod CSID 43 | 44 | ```http 45 | ProdCsidApiClient(GlobalVariables).GetToken(ProdCsidOnboardingRequest) 46 | ``` 47 | 48 | | ProdCsidOnboardingRequest Parameter | Type | Description | 49 | | :-------- | :------- | :-------------------------------- | 50 | | `ComplianceRequestId` | `string` | **Required**. RequestId from the response of above endpoint | 51 | | `CsrBinaryToken` | `string` | **Required**. Binary token from the response of above endpoint | 52 | | `CsrSecret` | `string` | **Required**. Secret from the response of above endpoint | 53 | 54 | #### Generate xml 55 | 56 | ```http 57 | StandardInvoiceXmlGenerator.Generate(StandardInvoice) 58 | ``` 59 | 60 | For parameters refer -> https://github.com/fasilmarshooq/Zatca_Integration_Helper/blob/main/Zatca-Standard-Invoice-Integration-Client/Model/StandardInvoice.cs 61 | 62 | #### Clear Invoice 63 | 64 | ```http 65 | StandardInvoiceClearanceApiClient(GlobalVariables).ClearInvoice(InvoiceClearanceRequest) 66 | ``` 67 | 68 | | InvoiceClearanceRequest Parameter | Type | Description | 69 | | :-------- | :------- | :-------------------------------- | 70 | | `Body` | `string` | **Required**. response of above endpoint | 71 | | `BinaryToken` | `string` | **Required**. Binary token from the response of Get Prod CSID endpoint | 72 | | `Secret` | `string` | **Required**. Secret from the response of Get Prod CSID endpoint | 73 | 74 | #### Generate xml for Debit Note 75 | 76 | ```http 77 | StandardInvoiceXmlGenerator.GenerateForDebitNote(StandardInvoiceAdjustment) 78 | ``` 79 | 80 | For parameters refer -> https://github.com/fasilmarshooq/Zatca_Integration_Helper/blob/main/Zatca-Standard-Invoice-Integration-Client/Model/StandardInvoiceAdjustment.cs 81 | 82 | > **_NOTE:_** use above clear Invoice endpoint to clear Debit Note 83 | 84 | #### Generate xml for Credit Note 85 | 86 | ```http 87 | StandardInvoiceXmlGenerator.GenerateForCreditNote(StandardInvoiceAdjustment) 88 | ``` 89 | 90 | For parameters refer -> https://github.com/fasilmarshooq/Zatca_Integration_Helper/blob/main/Zatca-Standard-Invoice-Integration-Client/Model/StandardInvoiceAdjustment.cs 91 | 92 | > **_NOTE:_** use above clear Invoice endpoint to clear Credit Note 93 | 94 | #### Generate xml for Simplified Invoice 95 | 96 | ```http 97 | StandardInvoiceXmlGenerator.GenerateForSimplifiedInvoice(StandardInvoiceAdjustment,Certificate,PrivateKey) 98 | ``` 99 | 100 | For parameters refer -> https://github.com/fasilmarshooq/Zatca_Integration_Helper/blob/main/Zatca-Standard-Invoice-Integration-Client/Model/StandardInvoice.cs 101 | 102 | - Certificate : Binary token received using Get Prod CSID API 103 | - PrivateKey : your private key generated using above openSsl commands 104 | - feel free to use certifcate and private key from test project for testing purpose 105 | - [Certificate for test](Bee.ZatcaHelper.UnitTests/TestXmls/SimpleInvoice_Cert.pem) 106 | - [Private Key for test](Bee.ZatcaHelper.UnitTests/TestXmls/SimpleInvoice_privateKey.pem) 107 | 108 | > **_NOTE:_** reporting api is still under developement it has a bug in zatca sandbox till then feel free to use compliance API 109 | 110 | 111 | --- 112 |
113 | 114 |

Rest API supprt for non .net apps

115 |

Zatca Reporting API bug fixes

116 |
117 | 118 | --- 119 | ## License 120 | 121 | [MIT](https://choosealicense.com/licenses/mit/) 122 | 123 | ## Trouble Shooting 124 | 125 | - If you facing Schema validation execption without any error message please refer -> https://stackoverflow.com/questions/73388407/schema-validation-failed-xml-does-not-comply-with-ubl-2-1-standards-in-line-wit 126 | 127 | - UBL Schema repo -> http://www.datypic.com/sc/ubl21/e-ns39_Invoice.html 128 | 129 | --------------------------------------------------------------------------------