Saturday, December 19, 2020

GST E-Invoice API Integration Explained

Dear Reader, GSPs/ASPs & Dynamics NAV Service Provider/Consultants are too busy to integrate E-Invoice solutions. Taxpayers with Turnover between Rs 100 Cr- Rs 500 Cr can contact the GSP / ERPs for the willingness to integrate the e-Invoice APIs through them.

Lots of solutions being offered. Customers are in rush to get it done ASAP as dead line is approaching.

Mostly solution are opted by customer:

  1. API : Plain Text  
  2. API : With Encryption & Decryption
  3. SFTP file transfer (Excel/CSV/JSON)
Further solutions are offered with two type of schema:
  1. GSP's own schema
  2. Government Compatible
Below I am explaining solution step by step, how to integrate E-Invoice APIs.

Hope all of us are aware about Microsoft has already given solution (can be checked in recent Cumulative updates & can be found on Posted Sales Invoice / Credit Memo):
  1. Generate IRN
  2. Export Json File
  3. Import Json File
There is a codeunit ID; 16511 with Name: e-Invoice, for export & import JSON request & response based on Government Schema (based on NIC portal & can be checked here). Which is provided by Microsoft to export & import Json files.

So, not need to create external solution (eg. DLLs for calling API & parsing received response). API request & response can be done in NAV only using NAV Codeunits & standard .net variables of Windows .net framework & NAV add-ins.

Creating outside solution will require extra maintenance. You need to update your DLL, place DLL in add-in folder & update your NAV codes. Making such solution, you are making customer highly dependent. So, its better to write code natively in NAV for API call & response parsing which can be easily updated & deployed. 

Below are major steps to reuse NAV existing solutions:

Step 1: We have already solution available to prepare JSON structure with required data. We need to reuse this solution to fit into GSP's own schema or Govt. compatible schema. It's easy & quick to modify given Codeunit 16511 or can save in available Codeunit's ID of customer's license with different name. For example Codeunit ID: 50005 Name: e-Invoice Management.

Step 2: Create a Setup table to store GSP's API URLs & Credentials, etc. For example:


Note: You may add/modify filed as per GSP details. Create a corresponding page for this table.

Step 3: Create a log/entry table to store API request & response status & details. For Example: e-Invoice Entry


Note: Above fields are for example only. You may add/modify as per your need. Create a corresponding page for this table.

Step 4: Modify e-Invoice Management codeunit's methos "ExportAsJson(DocumentNo);" to call API & process response. Get the e-Invoice Setup and check if Integration is enabled then Call API otherwise leave the original code to save the JSON file. 

Define the below variables in function "ExportAsJson":

 Update "ExportAsJson" function as below:

 LOCAL PROCEDURE ExportAsJson@1500012(DocumentNo@1500000 : Code[20]);

    VAR

      NewStream@1055602 : InStream;
      HttpWebRequestMgt@1170000000 : Codeunit 1297;
      TempBlob@1170000003 : Record 99008535;
      HTTPStatusCode@1170000002 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Net.HttpStatusCode";
      ResponseHeader@1170000001 : DotNet "'System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.System.Collections.Specialized.NameObjectCollectionBase";
      InStr@1170000004 : InStream;
      Json@1170000005 : Text;
      JSONManagement@1170000006 : Codeunit 5459;
      WasSuccess@1170000007 : Boolean;
      ErrorText@1170000008 : Text;
      TrnsIdentifier@1170000009 : Text[250];
      AckNumber@1170000014 : Text[30];
      AckDateText@1170000015 : Text[30];
      IRNText@1170000010 : Text[250];
      SignedQR@1170000011 : Text;
      SignedInv@1170000012 : Text;     
      eInvoiceEntry@1170000016 : Record 50051;
      OStream@1170000017 : OutStream;
      RequestText@1170000018 : Text;
      QRPayload@1000000001 : Text;
      IRNStatus@1000000002 : Text;
      eInvoiceSetup@1000 : Record 50050;
      AuthToken@1004 : Text[250];
      SuccessText@1000000004 : Text[10];
      QRCodeFileName@1000000005 : Text;
      FileMgmt@1000000006 : Codeunit 419;
                  ArrayString@1000000009 : Text; 
      JObject@1000000008 : DotNet "'Newtonsoft.Json'.Newtonsoft.Json.Linq.JObject";
      JSONArray@1000000007 : DotNet "'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'.Newtonsoft.Json.Linq.JArray";
      RequestResponse@1000000010 : BigText;
    BEGIN
      eInvoiceSetup.GET();
               IF eInvoiceSetup."Integration Enabled" THEN BEGIN 
                               
      HttpWebRequestMgt.Initialize(eInvoiceSetup."Base URL" + eInvoiceSetup."
Generate E-Invoice URL");
      HttpWebRequestMgt.DisableUI();
      HttpWebRequestMgt.SetMethod('POST');
                         //Write similar function to get the Auth Token
      HttpWebRequestMgt.AddHeader('Bearer', AuthToken);
      HttpWebRequestMgt.SetContentType('application/json');
      HttpWebRequestMgt.SetReturnType('application/json');
                         RequestText := FORMAT(StringBuilder.ToString); 
      HttpWebRequestMgt.AddBodyAsText(RequestText);
      
      WasSuccess := FALSE;
      ErrorText := '';
      IRNText := '';
      SignedQR := '';
      SignedInv := '';
       AckNumber := '';
      QRPayload := '';
      IRNStatus := '';
      TempBlob.INIT;
      TempBlob.Blob.CREATEINSTREAM(InStr);
      IF HttpWebRequestMgt.GetResponse(InStr,HTTPStatusCode,ResponseHeader) THEN BEGIN
        IF FORMAT(HTTPStatusCode.ToString) IN ['OK','Ok','ok'] THEN
          WasSuccess := TRUE;
          Json := TempBlob.ReadAsText('',TEXTENCODING::UTF8);
          
          JSONManagement.InitializeFromString(Json);
          SuccessText := JSONManagement.GetValue('Success');
          IF UPPERCASE(SuccessText) = 'Y' THEN BEGIN
            AckNumber := JSONManagement.GetValue('AckNo');
            AckDateText := JSONManagement.GetValue('AckDt');
            IRNText := JSONManagement.GetValue('Irn');
            SignedInv := JSONManagement.GetValue('SignedInvoice');
            SignedQR := JSONManagement.GetValue('SignedQRCode');
            IRNStatus := JSONManagement.GetValue('Status');
          END ELSE BEGIN
            JObject := JObject.JObject();
            JSONManagement.GetJSONObject(JObject);
            ArrayString := JObject.SelectToken('ErrorDetails').ToString;
            CLEAR(JSONManagement);
            CLEAR(JObject);
            JObject := JObject.JObject();
            JSONArray := JSONArray.JArray();
            JSONManagement.InitializeCollection(ArrayString);
            JSONManagement.GetJsonArray(JSONArray);
            FOREACH JObject IN  JSONArray DO BEGIN
              ErrorText := JObject.GetValue('error_message').ToString;
            END
          END;
      END ELSE
        SuccessText := 'N';
 
      //Create Entry
      eInvoiceEntry.INIT;
      IF IsInvoice THEN
        eInvoiceEntry."Document Type" := eInvoiceEntry."Document Type"::Invoice
      ELSE
        eInvoiceEntry."Document Type" := eInvoiceEntry."Document Type"::CrMemo;
      eInvoiceEntry."Document No." := DocumentNo;
      
      CLEAR(RequestResponse);
      RequestResponse.ADDTEXT(RequestText);
      eInvoiceEntry."Request JSON".CREATEOUTSTREAM(OStream);
      RequestResponse.WRITE(OStream);
      
      CLEAR(RequestResponse);
      RequestResponse.ADDTEXT(Json);
      eInvoiceEntry."Response JSON".CREATEOUTSTREAM(OStream);
      RequestResponse.WRITE(OStream);
     
      IF NOT eInvoiceEntry.INSERT THEN
        eInvoiceEntry.MODIFY;
     
      IF UPPERCASE(SuccessText) = 'Y' THEN BEGIN
        QRCodeFileName := GetQRCode(SignedQR);
        QRCodeFileName := MoveToMagicPath(QRCodeFileName); // To avoid confirmation dialogue on RTC
        CLEAR(TempBlob);
        FileMgmt.BLOBImport(TempBlob,QRCodeFileName);
        IF TempBlob.Blob.HASVALUE THEN BEGIN
          eInvoiceEntry."QR Code Image" := TempBlob.Blob;
        END;
        eInvoiceEntry.Status := eInvoiceEntry.Status::Generated;
        eInvoiceEntry."Acknowledgment No." := AckNumber;
        eInvoiceEntry."Acknowledgment Date" := AckDateText;
        eInvoiceEntry.IRN := IRNText;
        eInvoiceEntry."IRN Status" := IRNStatus;
        eInvoiceEntry.SignedQRWriteAsText(SignedQR,TEXTENCODING::UTF8);
        eInvoiceEntry.SignedInvWriteAsText(SignedInv,TEXTENCODING::UTF8);
        eInvoiceEntry."QR Code".CREATEOUTSTREAM(OStream,TEXTENCODING::UTF8);
        OStream.WRITETEXT(QRPayload,STRLEN(QRPayload));
      END ELSE BEGIN
        eInvoiceEntry.Status := eInvoiceEntry.Status::Fail;
        eInvoiceEntry."Error Message" := COPYSTR(ErrorText,1,250);
      END;
      eInvoiceEntry.RequestWriteAsText(RequestText,TEXTENCODING::UTF8);
      eInvoiceEntry.ResponseWriteAsText(Json,TEXTENCODING::UTF8);
      eInvoiceEntry."Created By" := USERID;
      eInvoiceEntry."Created Date Time" := CURRENTDATETIME;
      eInvoiceEntryMODIFY;
    END ELSE BEGIN 
                   FileName := DELCHR(FileName,'=','/');
                     ServerFile := FileManagement.ServerTempFileName('.json');
                     IF FILE.EXISTS(ServerFile) THEN
                       ERASE(ServerFile);
                    TempFile.CREATE(ServerFile);
                    TempFile.CREATEOUTSTREAM(OutStrm);
                    OutStrm.WRITETEXT(DN_JSonConvert.Parse(StringBuilder.ToString).ToString);
                    TempFile.CLOSE;
                    LocalFile := FileManagement.SaveFileDialog('Select Folder to Save Json',FileName + '.Json','*.JSON|*.json');
                    IF LocalFile <> '' THEN
FileManagement.DownloadToFile(ServerFile,LocalFile); 
    END;
END;

Note: This code might not run directly, you need to change/update accordingly. Codeunit: Http Web Request Mgt., JSON Management, & File Management are sufficient to call API & easily parse response using few supporting .net variables.

Step 5: Similarly need to write code for cancel IRN. Give a button at e-Invoice Entries page.

Step 6: Finalize your solution by putting required validations to avoid errors. Check the link for validation. 

Happy to help you, pleas comment & give feedback. Or, you may contact us for further detailed information. 

Thanks for reading...


1 comment:

  1. Home-Based Medical Billing Businesses vary significantly from one to the next in experience Small Practices Medical Billing Services, ability and services offered. Many are small start up businesses with only one or two employees. Some offer extensive experience from previous employment in a doctor's office, others may have only one or two clients.

    ReplyDelete