ZATCA FLUTTER
zatca_flutter
is a Flutter plugin that provides seamless integration with the ZATCA (Zakat, Tax, and Customs Authority) e-invoicing system. It supports Windows and Linux environments, allowing developers to generate compliant electronic invoices efficiently.
The only flutter plugin that fully and perfectly implements with Zatca/Fatoora E-Invoicing Phase 1 & 2 requirements. It is the complete solution for creating a flutter e-invoicing desktop app (Windows & Linux) for use in Saudi Arabia.
Current Status
Features
- Setting up Business Info.
- Generating and saving CSR related files.
- Generating and saving XML files for invoices, debit notes, and credit notes.
- Generating and Displaying QR codes for invoice/note.
- Validating XML invoice/note.
- Signing and saving the signed invoices and notes.
- Generating Invoice/Note Hash Value (Typically used as PIH for next invoice)
- Generating ApiRequest for Compliance Check, Reporting and Clearance
- Generating and saving Compliance CSID and Production CSID.
- Checking invoice/note/system compliance.
- Clearing Invoice/Note.
- Reporting Invoice/Note.
- Converting and Saving Keys/Certs.
- Invoice, debit note, and credit note reporting and clearance.
Installation
Add zatca_flutter
to your pubspec.yaml
:
Run the command below
flutter pub add zatca_flutter
Usage
Initialization
You only have to
The three modes acceptable are sandbox
, simulation
, and production
.
Sandbox uses the developer-portal zatca server endpoint.
void main() {
WidgetsFlutterBinding.ensureInitialized();
// This all that is required. Switch mode as per needed.
ZatcaFlutter.init(mode: Mode.sandbox);
runApp(const App());
}
Business Setup
// Just assign MyBusinessInfo model to the LocalStore `myBusinessInfo`. That automatically saves it for future reference.
LocalStore.instance.myBusinessInfo = MyBusinessInfo(
name: _nameContr.text,
address: _addressContr.text,
taxId: _taxIdContr.text,
buildingNumber: _buildingNumberContr.text,
citySubdivision: _citySubdivisionContr.text,
city: _cityContr.text,
postalZone: _postalZoneContr.text,
countryCode: _countryCodeContr.text,
schemeID: _schemeIDContr.text,
companyID: _companyIdContr.text,
businessID: _businessIdContr.text
);
Actually, by just creating an instance of MyBusinessInfo, the business info is automatically saved and assigned to the myBusinessInfo of the LocalStore. If you are in doubt, you can still explicitly save the myBusinessInfo by calling its save()
method which is actually called behind the scene when you just create an instance.
CSR Config File Generation
CsrConfig(
commonName: 'Your Common Name',
serialNumber: 'SerialN',
organizationIdentifier: '...',
organizationUnitName:'Organization unit name',
organizationName: 'Organization Name',
countryName: 'Country name (SA)',
invoiceType: 'Typically 1100',
locationAddress: 'Business Address',
industryBusinessCategory: 'Business Category');
Once an instance of CsrConfig
is created, the csrConfig.properties
file will automatically be saved.
After creating an instance of CsrConfig file, you can await the value of the filePath
where the file is created if you need to by just doing the following:
CsrConfig csrConfig = CsrConfig(
commonName: 'Your Common Name',
serialNumber: 'SerialN',
organizationIdentifier: '...',
organizationUnitName:'Organization unit name',
organizationName: 'Organization Name',
countryName: 'Country name (SA)',
invoiceType: 'Typically 1100',
locationAddress: 'Business Address',
industryBusinessCategory: 'Business Category');
// Await the file path as shown below
String csrFilePath = await csrConfig.filePath;
Also, note that you could also load an already created file from the filesystem after it has been created.
CsrConfig csrConfig = await CsrConfig.load();
Generate CSR file
The simplest way is to call FatooraService.generateCsr() method with the only required argument csrConfigFile
passed in. The outputCsrFile
and the privateKeyFile
are optional.
FatooraServiceCsrResponse csrResponse = FatooraService.generateCsr(csrConfigFile: csrConfigFile);
Note: csrConfigFile
is the file name from previous step above. See the example app for illustration or read the documentation.
The FatooraServiceCsrResponse
has the following properties csrOutputFileName
and keyOutputFileName
that you can make reference to if you want to read the csr value and private key value respectively.
Reading the Generated CSR Value and Private Key Value
Just use the utility function getFileContentAsString(fileName)
;
Example of reading the csr value and key value from above.
String csr = getFileContentAsString(response.csrOutputFileName);
String key = getFileContentAsString(response.keyOutputFileName);
Requesting CCSID from Zatca
CCSIDRequestProp prop = CCSIDRequestProp(csr: csr, otp: otp);
ComplianceCSIDResponse ccsidResponse = await ZatcaFlutter.request.requestComplianceCSID(request: prop);
See the documentation for properties of the ComplianceCSIDResponse
Requesting PCSID from Zatca
For you to request PCSID you must have first done the CCSID request. Then do the following:
ProductionCSIDResponse response = await ZatcaFlutter.request.requestProductionCSIDOnboarding();
See the documentation for properties of the ProductionCSIDResponse
Save the Returned Token and Key
After requesting for CCSID, if the request was successful, a binary token and a key will be returned. You can save it by doing the following:
request.convertTokenAndKeyToPemAndSaveToSDKForSigning(certAndKey: CertAndKey(cert: ccsidResponse.successData, key: key))
After the above steps have been completed, you can then follow the following steps for regular processes.
Generating An Invoice/Note
There are two types of Invoices. These are SimplifiedInvoice
and StandardInvoice
.
There are two types of Notes. These are Credit Note
and Debit Note
. Each of these types of notes could either be Simplified
or Standard
. So that makes it a total of 6 major types of xml files you can generate with zatca_flutter
package.
Example:
// Create the invoice object
SimplifiedInvoice simplifiedInvoice = SimplifiedInvoice(
icv: 1,
pih:'', // Pass in an empty string like this if this is your first invoice. Otherwise, pass in the actual PIH (Previous Invoice Hash)
id: "INV-001",
uuid: generateUuid(),
issueDate: DateTime.now(),
issueTime: DateTime.now(),
currency: 'SAR',
customer: IndividualParty(
name: "Elizabeth",
taxId: "310298993344553",
address: "address 3az",
),
lines: [
InvoiceLine(
quantity: "1",
price: "64",
total: "64",
tax: TaxDetails(
amount: "9.60", // 64 * 15% = 9.60
percent: "15",
currency: 'SAR',
taxableAmount:'64',
code: TaxCategoryCode.standard, // Ensure taxable amount matches total
),
name: 'Garri',
)
],
tax: TaxDetails(
amount: "9.60", // Sum of line-level tax amounts
percent: "15",
currency: 'SAR',
taxableAmount: '64', code: TaxCategoryCode.standard, // Total taxable amount
),
monetaryTotal: LegalMonetaryTotal(
lineExtensionAmount: "64", // Sum of line totals
taxExclusiveAmount: "64", // Same as lineExtensionAmount
taxInclusiveAmount: "73.60", // taxExclusiveAmount + total VAT
payableAmount: "73.60", // Final payable amount
allowanceTotalAmount: 0.0,
prepaidAmount: 0.0
),
);
simplifiedInvoice.generateAndSaveXml('simple_invoice.xml'); // Generate and save the invoice xml data from the invoice object
Signing An Invoice/Note
Signing an invoice requires using the necessary certificate. But you don't need to worry about that since the plugin handles that for you. However, you need to specify if the signing you are doing is for checks or not by passing in isForComplianceCheck:true
if you want to use it for local (fatoora) validation check or server (zatca) compliance check.
If you want to use the signed document for reporting (as required for Simplified Invoice/Note reporting) then you might omit the isForComplianceCheck
or explicitly indicate it's for reporting by passing in isForComplianceCheck:false
to the FatooraService.signInvoice()
method.
Example:
// Sign the invoice if it's Simplified (B2C)
FatooraServiceResponse signRes = await FatooraService.signInvoice(invoiceFileName: 'simple_invoice.xml', isForComplianceCheck: true);
// The signed invoice for the above will be simple_invoice_signed.xml
Note: if you do not specify the outputSignedInvoiceFileName
, the signed invoice will have the name of the original invoice above with _signed.xml
added as the last part.
Generating Invoice RequestAPI FatooraService.generateInvoiceRequestAPI
Invoice/Note request API is the JSON data format that consist of uuid
, invoiceHash
and invoice
data and is required for invoice/note compliance check, reporting, and clearance.
If you want to generate the request API for clearance, you should pass in forClearance:true
to the FatooraService.generateInvoiceRequestAPI()
method. Otherwise, the plugin assumes you are generating the request API for reporting.
// Generate the Request API for compliance check, reporting or clearance
FatooraInvoiceRequestApiResponse invoiceApi =
await FatooraService.generateInvoiceRequestAPI(
invoiceFileName: 'simple_invoice_signed.xml');
Example of Reporting An Invoice B2C
// Create the invoice object
SimplifiedInvoice simplifiedInvoice = SimplifiedInvoice(
icv: 1,
pih:
'', // Pass in an empty string like this if this is your first invoice. Otherwise, pass in the actual PIH (Previous Invoice Hash)
id: "INV-001",
uuid: generateUuid(),
issueDate: DateTime.now(),
issueTime: DateTime.now(),
currency: 'SAR',
customer: IndividualParty(
name: "Elizabeth",
taxId: "310298993344553",
address: "address 3az",
),
lines: [
InvoiceLine(
quantity: "1",
price: "64",
total: "64",
tax: TaxDetails(
amount: "9.60", // 64 * 15% = 9.60
percent: "15",
currency: 'SAR',
taxableAmount: '64',
code: TaxCategoryCode
.standard, // Ensure taxable amount matches total
),
name: 'Garri',
)
],
tax: TaxDetails(
amount: "9.60", // Sum of line-level tax amounts
percent: "15",
currency: 'SAR',
taxableAmount: '64',
code: TaxCategoryCode
.standard, // Total taxable amount
),
monetaryTotal: LegalMonetaryTotal(
lineExtensionAmount: "64", // Sum of line totals
taxExclusiveAmount:
"64", // Same as lineExtensionAmount
taxInclusiveAmount:
"73.60", // taxExclusiveAmount + total VAT
payableAmount: "73.60", // Final payable amount
allowanceTotalAmount: 0.0,
prepaidAmount: 0.0),
);
// XML creation
simplifiedInvoice.generateAndSaveXml(
'simple_invoice.xml'); // Generate and save the invoice xml data from the invoice object
// Sign the invoice if it's Simplified (B2C)
FatooraServiceResponse signRes =
await FatooraService.signInvoice(
invoiceFileName: 'simple_invoice.xml',
isForComplianceCheck: true);
// Get the XML of the signed invoice
xml = await getFileContentAsString(
'simple_invoice_signed.xml');
// Get the QR Code from the signed invoice
FatooraQrCodeResponse qrCodeResponse =
await FatooraService.generateInvoiceQRCode(
'simple_invoice_signed.xml');
// Get the qrCode String like this
String base64EncodedQrCode = qrCodeResponse.qrCode;
debugPrint(
"SIGNATURE (${signRes.status.name.toUpperCase()}): ${signRes.infos?.map((d) => d.message)}");
// Validate signed invoice locally before doing compliance check, reporting or clearance
FatooraServiceResponse validationRes =
await FatooraService.validateInvoice(
invoiceFileName: 'simple_invoice_signed.xml',
ignoreWarningForResponseStatus: true);
debugPrint(
"VALIDATION (${validationRes.status.name.toUpperCase()}): SUCCESS (${validationRes.infos?.length}); FAILURE (${validationRes.errors?.length}) WARNING (${validationRes.warnings?.length}) ${validationRes.infos?.map((d) => d.message)}");
// Generate the Request API for compliance check, reporting or clearance
FatooraInvoiceRequestApiResponse invoiceApi =
await FatooraService.generateInvoiceRequestAPI(
invoiceFileName: 'simple_invoice_signed.xml');
// Check compliance on zatca server.
ComplianceInvoiceCheckResponse? res = await Controller
.instance.request
.requestComplianceCheck(
prop: invoiceApi.invoiceRequest!);
if (res?.status != null) {
setState(() {
responseStatus =
"${res?.clearanceStatus ?? res?.reportingStatus ?? res?.status.name.toUpperCase()}\n\n${res?.validationResults?.toJson()}";
});
}
Example of Executing Invoice Clearance
StandardInvoice standardInvoice = StandardInvoice(
icv: 1,
pih: '',
id: "INV-001",
uuid: generateUuid(),
issueDate: DateTime.now(),
issueTime: DateTime.now(),
currency: 'SAR',
customer: BusinessParty(
businessID: '1010010000',
address: "RRRD2929",
name: "Info Tech Supply LTD",
taxId: "399999999955553",
buildingNumber: "3233",
citySubdivision: "Al-Murabba",
city: "Riyadh",
postalZone: "23333",
countryCode: "SA",
schemeID: 'CRN'),
lines: [
InvoiceLine(
quantity: "1",
price: "64",
total: "64",
tax: TaxDetails(
amount: "9.60", // 64 * 15% = 9.60
percent: "15",
currency: 'SAR',
taxableAmount: '64',
code: TaxCategoryCode
.standard, // Ensure taxable amount matches total
),
name: 'Garri',
)
],
tax: TaxDetails(
amount: "9.60", // Sum of line-level tax amounts
percent: "15",
currency: 'SAR',
taxableAmount: '64',
code:
TaxCategoryCode.standard, // Total taxable amount
),
monetaryTotal: LegalMonetaryTotal(
lineExtensionAmount: "64", // Sum of line totals
taxExclusiveAmount:
"64", // Same as lineExtensionAmount
taxInclusiveAmount:
"73.60", // taxExclusiveAmount + total VAT
payableAmount: "73.60", // Final payable amount
allowanceTotalAmount: 0.0,
prepaidAmount: 0.0),
delivery: Delivery(
actualDate: DateTime.now(),
latestDate: DateTime.now()),
);
// Create the XML
standardInvoice
.generateAndSaveXml('standard_invoice.xml');
// Request API generation
FatooraInvoiceRequestApiResponse invoiceApi =
await FatooraService.generateInvoiceRequestAPI(
invoiceFileName: 'standard_invoice.xml',
forClearance: true);
debugPrint(
"INVOICE API: ${invoiceApi.invoiceRequest?.toMap()}");
ZatcaFlutter zatca = ZatcaFlutter.instance;
InvoiceClearanceResponse? res = await zatca.request
.requestClearance(invoiceApi.invoiceRequest!);
debugPrint(
"RESULT FROM CLEARANCE ${res?.statusCode} ${res?.clearanceStatus} ${res?.validationResults?.toJson()}");
debugPrint(
"CLEARED INVOICE: \n${res?.clearedInvoice}\n\nCLEARANCE DATA: \n${res?.fileName}");
String? qrCode = await ClearedInvoiceService.getQrCode(res.fileName!); // To get the qrCode of the cleared invoice
String? invoiceHash = await ClearedInvoiceService.getInvoiceHash(res.fileName!); // To get the qrCode of the cleared invoice
Displaying the QR CODE
QrCodeImage(base64EncodedQrCode: qrCode);
See the documentation or example app for more usage.
Sponsorship
Please support us to keep maintaining this package if you find it useful and want to help us.
Thanks
Contributing
If you are interested in contributing to this package reach-out to me via GitHub.
Connect With the Author
Engineer Isaiah Pius E. I am pleased to have you use this package. You can reach-out to me via my GitHub, Email and Linked. See my GitHub page for relevant connection links. Thanks once again.
Libraries
- enums
- local_store
- model/api/compliance_csid_response
- model/api/compliance_invoice_response
- model/api/csr_request
- model/api/invoice_clearance_response
- model/api/invoice_reporting_response
- model/api/message
- model/api/pcsid_request_prop
- model/api/production_csid_response
- model/api/server_error_response
- model/api/util
- model/api/validation_results
- model/cert_and_key
- model/certificates_errors_response
- model/cleared_invoice_result_model
- model/csr_config
- model/error_model
- model/fatoora_invoice_hash_response
- model/fatoora_invoice_request_api_response
- model/fatoora_qr_code_response
- model/fatoora_service_response
- model/info_model
- model/invoice_request
- model/invoice_result_base_model
- model/invoice_result_model
- model/my_business_info
- model/warning_model
- model/xml/allowance_charge
- model/xml/billing_reference
- model/xml/buyer
- model/xml/delivery
- model/xml/invoice_item
- model/xml/invoice_line
- model/xml/invoice_total
- model/xml/legal_monetary_total
- model/xml/party
- model/xml/payment_means
- model/xml/seller
- model/xml/simplified_credit_note
- model/xml/simplified_debit_note
- model/xml/simplified_invoice
- model/xml/standard_credit_note
- model/xml/standard_debit_note
- model/xml/standard_invoice
- model/xml/tax_details
- qr_code/widget/qr_code_image
- request
- service/cleared_invoice_service
- service/fatoora_service
- service/fatoora_service_finder
- service/fatoora_service_response_parser
- service/util
- zatca_flutter