Generate QR codes and print them on receipts for Saudi Arabia

Note

The functionality described in this article is meant to support the Phase 1 requirements of the E-invoicing implementation in the Kingdom of Saudi Arabia. For information about the features that are meant to support the Phase 2 requirements of the E-invoicing implementation in the Kingdom of Saudi Arabia, see Generate and submit simplified electronic invoices for Saudi Arabia.

This article provides an overview of the functionality for printing QR codes that is available for Saudi Arabia in Microsoft Dynamics 365 Commerce.

In a store that is linked to a legal entity that has its primary address in Saudi Arabia, users can print a QR code on the receipt for a cash-and-carry or customer order sales transaction. The QR code contains the following information.

Sequence Field Data source
1 Company name The name of the legal entity
2 Company VAT registration number The tax registration number of the legal entity
3 Date and time of transaction The date and time of the retail store transaction
4 Total receipt amount (including value-added tax [VAT]) The total amount of the retail store transaction
5 Total amount of VAT included in receipt The total tax amount of the retail store transaction

Note

When customer order transactions are created, the total receipt amount is calculated by adding the total amounts of all transaction lines that use the carry-out delivery mode.

The QR code is generated by applying the base64 transformation to the transaction information encoded in the Tag-Length-Value (TLV) format. Zakat, Tax and Customs Authority (ZATCA) provides tools that can be used to validate the QR code. For more information about the electronic invoicing requirements and QR code validation capabilities, see E-Invoicing portal by ZATCA.

Set up QR codes

To generate QR codes and print them on receipts for Saudi Arabia, you must complete the following tasks.

  1. Configure custom fields so that they can be used in receipt formats for sales receipts.
  2. Configure receipt formats.
  3. Specify QR code dimensions in Commerce parameters.
  4. Enable Commerce runtime (CRT) extensions.

Configure custom fields so that they can be used in receipt formats for sales receipts

You can configure the language text and custom fields that are used in the point of sale (POS) receipt formats. On the Language text page, add the following records for the labels of the custom fields for receipt layouts. Note that the Language ID, Text ID, and Text values that are shown in the table are just examples. You can change them to meet your requirements. However, the Text ID values that you use must be unique, and they must be equal to or higher than 900001.

Language ID Text ID Text
en-US 900001 QR code (SA)

Note

The default company of the user who creates the receipt setup should be the same legal entity where the language text setup is created. Alternatively, the same language texts should be created in both the user's default company and the legal entity of the store that the setup is created for.

On the Custom fields page, add the following records for the custom fields for receipt layouts. Note that Caption text ID values must correspond to the Text ID values that you specified on the Language text page.

Name Type Caption text ID
INVOICEQRCODE_SA Receipt 900001

Configure receipt formats

For every required receipt format, change the value of the Print behavior field to Always print.

In the receipt format designer, add the following custom field to the Footer section of the receipt. Note that field names correspond to the language texts that you defined in the previous section.

  • QR code (SA) – This field prints the QR code on the receipt.

For more information about how to work with receipt formats, see Set up and design receipt formats.

Specify QR code dimensions in Commerce parameters

On the Configuration parameters tab of the Commerce parameters page, add the following configuration parameters:

  • QrCodeWidth - The width of the QR code image, in pixels. Specify an appropriate value for the parameter.
  • QrCodeHeight - The height of the QR code image, in pixels. Specify an appropriate value for the parameter.

Note

It is mandatory to specify the values of these configuration parameters to print QR codes in receipts. Support for default values of the parameters may be added in future updates.

Enable CRT extensions

Warning

This localization functionality can't be used with the new independent packaging and extension model and Commerce software development kit (SDK). You must use the previous version of the Retail SDK on a developer virtual machine (VM) in Microsoft Dynamics Lifecycle Services (LCS). For information about the localization features for Saudi Arabia that are available in Commerce SDK, see Generate and submit simplified electronic invoices for Saudi Arabia

Development environment

Follow these steps to set up a development environment so that you can test and extend the localization functionality.

  1. Find the extension configuration file for CRT:

    • Retail Server: The file is named commerceruntime.ext.config, and it can be found in the bin\ext folder under the Internet Information Services (IIS) Retail Server site location.
    • Local CRT on Modern POS: The file is named CommerceRuntime.MPOSOffline.Ext.config, and it can be found under the local CRT client broker location.
  2. Register the CRT change in the extension configuration file, as shown in the following example.

    <add source="assembly" value="Microsoft.Dynamics.Commerce.Runtime.ReceiptsSaudiArabia" />
    <add source="assembly" value="Microsoft.Dynamics.Commerce.Runtime.ElectronicReporting" />
    

Production environment

Follow these steps to create deployable packages that contain Commerce components, and to apply those packages in a production environment.

  1. In the commerceruntime.ext.config and CommerceRuntime.MPOSOffline.Ext.config package configuration files under the RetailSdk\Assets folder, add the following lines to the composition section.

    <add source="assembly" value="Microsoft.Dynamics.Commerce.Runtime.ReceiptsSaudiArabia" />
    <add source="assembly" value="Microsoft.Dynamics.Commerce.Runtime.ElectronicReporting" />
    
  2. Open the MSBuild Command Prompt for Visual Studio utility and run msbuild under the Retail SDK folder to create deployable packages.

  3. Apply the packages via LCS or manually. For more information, see Create deployable packages.

When you use an Object Linking and Embedding for Retail POS (OPOS) printer, you might have to implement additional customizations to support printer-specific requirements for the QR code image. For example, you might have to convert the QR code image from the PNG format to the BMP format. This section shows an example of this type of customization.

Note

This customization example was tested by using the EPSON TM-T88V OPOS printer. It might have to be modified to support different printer makes or models.

Follow these steps to create a new extension and add it to your environment.

  1. Install the Retail SDK. For more information, see Retail software development kit (SDK).

  2. In the Retail SDK, use the following code, based on your Commerce version, to create a C# project under the CommerceRuntimeSamples.sln solution in RetailSdk\SampleExtensions\CommerceRuntime.

    <Project Sdk="Microsoft.NET.Sdk">
        <Import Project="..\..\..\BuildTools\Microsoft.Dynamics.RetailSdk.Build.props" />
        <Import Project="..\..\..\BuildTools\Common.props" />
        <Import Project="..\..\..\BuildTools\Microsoft.Dynamics.RetailSdk.Build.settings" />
    
        <PropertyGroup>
            <TargetFramework>netstandard2.0</TargetFramework>
            <AssemblyName>$(AssemblyNamePrefix).Commerce.Runtime.QrCodeExtension</AssemblyName>
            <RootNamespace>Contoso.Commerce.Runtime.QrCodeExtension</RootNamespace>
            <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
        </PropertyGroup>
    
        <Import Project="..\..\..\BuildTools\Microsoft.Dynamics.RetailSdk.Build.targets" />
    
        <ItemGroup>
            <PackageReference Include="Microsoft.Dynamics.Commerce.Runtime.Framework" Version="$(FrameworkRepoPackagesVersion)" />
            <PackageReference Include="Microsoft.Dynamics.Commerce.Runtime.Services.Messages" Version="$(ChannelRepoPackagesVersion)" />
            <PackageReference Include="System.Drawing.Common" Version="4.7.0" />
        </ItemGroup>
    
        <ItemGroup>
            <Reference Include="Microsoft.Dynamics.Commerce.Runtime.ElectronicReporting">
                <HintPath>..\..\..\..\..\nuget packages\microsoft.dynamics.commerce.runtime.electronicreporting.9.35.21321.4\lib\netstandard2.0\Microsoft.Dynamics.Commerce.Runtime.ElectronicReporting.dll</HintPath>
            </Reference>
        </ItemGroup>
    
        <ItemGroup>
            <Folder Include="Properties\" />
        </ItemGroup>
    </Project>
    

    You must also change the value of the HintPath element to reference the Microsoft.Dynamics.Commerce.Runtime.ElectronicReporting.dll library under the IIS Retail Server site location.

  3. Use the following code, based on your Commerce version, to create an extension class.

    /**
     * SAMPLE CODE NOTICE
     * 
     * THIS SAMPLE CODE IS MADE AVAILABLE AS IS. MICROSOFT MAKES NO WARRANTIES, WHETHER EXPRESS OR IMPLIED,
     * OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OR CONDITIONS OF MERCHANTABILITY.
     * THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS SAMPLE CODE REMAINS WITH THE USER.
     * NO TECHNICAL SUPPORT IS PROVIDED. YOU MAY NOT DISTRIBUTE THIS CODE UNLESS YOU HAVE A LICENSE AGREEMENT WITH MICROSOFT THAT ALLOWS YOU TO DO SO.
     */
    
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    
    namespace Contoso
    {
        namespace Commerce.Runtime.QrCodeExtension
        {
            using System;
            using System.Collections.Generic;
            using System.Threading.Tasks;
            using Microsoft.Dynamics.Commerce.Runtime;
            using Microsoft.Dynamics.Commerce.Runtime.Messages;
            using Microsoft.Dynamics.Commerce.Runtime.Services.Messages;
    
            /// <summary>
            /// The extension for QR code printing.
            /// </summary>
            internal class QrCodeServiceExtension : IRequestHandlerAsync
            {
                /// <summary>
                /// Printer horizontal resolution for image.
                /// </summary>
                private const float PrinterXDpi = 60f;
    
                /// <summary>
                /// Printer vertical resolution for image.
                /// </summary>
                private const float PrinterYDpi = 90f;
    
                /// <summary>
                /// Printer pixel format for image.
                /// </summary>
                private const PixelFormat PrinterPixelFormat = PixelFormat.Format8bppIndexed;
    
                /// <summary>
                /// Gets the collection of supported request types by this service.
                /// </summary>
                public IEnumerable<Type> SupportedRequestTypes
                {
                    get => new[] {typeof(EncodeQrCodeServiceRequest)};
                }
    
                /// <summary>
                /// Processes the request.
                /// </summary>
                /// <param name="request">The request.</param>
                /// <returns>The response.</returns>
                public async Task<Response> Execute(Request request)
                {
                    ThrowIf.Null(request, nameof(request));
    
                    switch (request)
                    {
                        case EncodeQrCodeServiceRequest encodeQrCodeServiceRequest:
                        {
                            EncodeQrCodeServiceResponse nextResponse = await this.ExecuteNextAsync<EncodeQrCodeServiceResponse>(encodeQrCodeServiceRequest).ConfigureAwait(false);
    
                            if (nextResponse != null)
                            {
                                var qrCodeBmp = string.IsNullOrWhiteSpace(nextResponse.QRcode) ? nextResponse.QRcode : ConvertToGenericCompatibilityImage(nextResponse.QRcode);
                                return new EncodeQrCodeServiceResponse(qrCodeBmp);
                            }
    
                            return nextResponse;
                        }
                    }
    
                    return new NotHandledResponse();
                }
    
                /// <summary>
                /// Converts QR code image from any format to compatible with printer.
                /// </summary>
                /// <param name="base64data">Base64 image.</param>
                /// <returns>Image that Compatible with printer.</returns>
                private static string ConvertToGenericCompatibilityImage(string base64data)
                {
                    string convertedQrCode = base64data;
                    byte[] imageBytes = Convert.FromBase64String(convertedQrCode);
                    using (MemoryStream msOriginal = new MemoryStream(imageBytes))
                    using (MemoryStream msConverted = new MemoryStream())
                    {
                        var bitmapOriginal = new Bitmap(msOriginal);
                        if (!IsFormatCompatible(bitmapOriginal) || !AreResolutionAndPixelFormatCompatible(bitmapOriginal))
                        {
                            var bitmapConverted = bitmapOriginal;
    
                            if (!AreResolutionAndPixelFormatCompatible(bitmapOriginal))
                            {
                                var rectangle = new Rectangle(0, 0, bitmapOriginal.Width, bitmapOriginal.Height);
                                bitmapConverted = bitmapOriginal.Clone(rectangle, PrinterPixelFormat);
                                bitmapConverted.SetResolution(PrinterXDpi, PrinterYDpi);
                            }
    
                            bitmapConverted.Save(msConverted, ImageFormat.Bmp);
                        }
    
                        convertedQrCode = Convert.ToBase64String(msConverted.ToArray());
                    }
    
                    return convertedQrCode;
                }
    
                /// <summary>
                /// Verifies if the resolution and pixel format of bitmap are compatible with printer requirements.
                /// </summary>
                /// <param name="source">Bitmap.</param>
                /// <returns>True if compatible; otherwise false.</returns>
                private static bool AreResolutionAndPixelFormatCompatible(Bitmap source)
                {
                    return source.VerticalResolution == PrinterYDpi &&
                           source.HorizontalResolution == PrinterXDpi &&
                           source.PixelFormat == PrinterPixelFormat;
                }
    
                /// <summary>
                /// Verifies if the format of bitmap is compatible with printer requirements.
                /// </summary>
                /// <param name="source">Bitmap.</param>
                /// <returns>True if compatible; otherwise false.</returns>
                private static bool IsFormatCompatible(Bitmap source)
                {
                    return source.RawFormat.Equals(ImageFormat.Bmp);
                }
            }
        }
    }
    
  4. In the commerceruntime.ext.config and CommerceRuntime.MPOSOffline.Ext.config configuration files under the RetailSdk\Assets folder, add the following lines to the composition section.

    <add source="assembly" value="Contoso.Commerce.Runtime.QrCodeExtension" />
    <add source="assembly" value="Microsoft.Dynamics.Commerce.Runtime.ReceiptsSaudiArabia" />
    <add source="assembly" value="Microsoft.Dynamics.Commerce.Runtime.ElectronicReporting" />
    
  5. In the Customization.settings package customization configuration file under the BuildTools folder, add the following lines to include the CRT extensions in the deployable packages.

    <ISV_CommerceRuntime_CustomizableFile Include="$(SdkReferencesPath)\Contoso.Commerce.Runtime.QrCodeExtension.dll" />
    
  6. Start the MSBuild Command Prompt for Visual Studio utility, and run msbuild under the Retail SDK folder to create deployable packages.

  7. Apply the packages via LCS or manually. For more information, see Create deployable packages.