Skip to content

Business Central - Multiple GSTINs and Queue-First Posting

Applies to: e-Invoice · e-Way Bill · Which modules do I have?

Purpose

Two changes to a Business Central extension that talks to Complifly, done in this order:

Change The problem it removes
A. One setup row per GSTIN A single setup table with one credential and one set of URLs for the whole BC company. A company with two state registrations cannot send each registration's documents through its own credential or mapping template.
B. Queue-first posting HTTP calls, dialogs and message boxes running inside the posting transaction. A slow or failing Complifly call holds the user's posting screen, and an error can roll the posting back.

The one fact to act on: Complifly already supports many GSTINs on one credential. You do not need one Entra app per GSTIN. The limit is in the BC extension's setup, not in the platform.

Audience

Business Central developers and implementation partners.

Prerequisites

  • Dynamics 365 Business Central — the single-registration integration, working end to end
  • AL development access to the extension. Steps 1-4 change tables and codeunits; a partner without source access cannot do them.
  • Complifly-side, done by Complifly support or your Complifly administrator, not by you: the ERP connection must list every GSTIN you will send for. See What Complifly does for you.

What Complifly does for you

You do not configure these, but you need to know them, because they decide what BC must send.

Behaviour Consequence for your BC code
One connection (one Entra app) covers a set of GSTINs You may use one credential for every registration. Use a separate credential per registration only if your own policy demands it.
A document whose GSTIN is not in the connection's set is refused with HTTP 403 GSTIN_NOT_PERMITTED Adding a registration in BC does nothing until the same GSTIN is added to the connection on the Complifly side. This is the first thing to check when a new registration fails.
The same document number under two different GSTINs is not a duplicate Number series may overlap across registrations. A duplicate is the same number under the same GSTIN (HTTP 409 DUPLICATE).
The write-back feed (GET /events) returns events for every GSTIN the connection covers One feed poll serves all registrations. Events must be matched back to BC documents by GSTIN and document type and document number.

Steps

A. One setup row per GSTIN

1. Add a registration table

Create a table keyed on GSTIN. Keep the existing setup table as the shared defaults.

Field Type Purpose
GSTIN (key) Code[15] The registration this row governs
Registration Name Text[100] Label, for example "UP - Lucknow Plant"
Location Code Code[10] The BC location that carries this GSTIN. Blank means Company Information.
Mapping Template Id Integer The Complifly template this registration posts against
Client ID Text[100] Optional. Blank means use the default.
Client secret stored in IsolatedStorage Optional. Never a table field, never shown on a page.
Single / Batch / Events / Ack URL Text[250] Optional. Blank means use the default. Most customers leave these blank.
Process Invoices / Credit Memos / Transfers / Purchases Boolean Which document types this registration sends
Active Boolean Switch a registration off without deleting its history

2. Add a resolver and use it everywhere

Write one codeunit with one procedure that takes a GSTIN and returns the effective token URL, credential, endpoints and template id: the registration's own value if set, otherwise the default.

Then replace every direct read of the singleton setup row (the literal key 'SETUP') in the single processor, the batch processor and the event puller. A call site you miss still sends that registration's documents with the default credential, with no error.

If a posted document's GSTIN has no active registration row, stop with an error that names the GSTIN. Do not fall back to the default silently: that is how a document reaches Complifly under the wrong registration.

3. Resolve the GSTIN from the document, not from setup

Document GSTIN comes from
Sales invoice, credit memo The posted document's Location GST registration number; Company Information if there is no location
Transfer shipment / receipt The from-location or to-location registration, according to the transaction

Never read the GSTIN from the setup table. It has no GSTIN field and must not gain one.

4. Add GSTIN to the staging and write-back tables

Add a GSTIN field to the staging header and line tables, the event log and the document-state table. Make the key (GSTIN, Document Type, Document No).

When applying a write-back event, match on all three. Matching on document number alone lets one registration's event land on another registration's document.

Upgrade codeunit, in this order:

  1. Ship the registration table first, with no data moves.
  2. For each existing company, create one registration from Company Information's GST number, leaving credential and URL fields blank so it inherits the defaults. A single-GSTIN customer then changes nothing.
  3. Backfill GSTIN on every existing staging, event log and state row from that one registration. This is unambiguous only because a single-GSTIN customer had one value.
  4. Count rows with a blank GSTIN. If any remain, raise an error and stop the upgrade. Apply the new key only after that count is zero. Silent truncation of rows that failed to backfill is the failure to avoid.
  5. A customer with several GSTINs adds the other registration rows by hand afterwards. The extension cannot know a second GSTIN exists until an administrator names it.

B. Queue-first posting

5. Make the posting subscriber thin

The event subscribers on sales posting and on transfer shipment and receipt must only:

  1. Look up the location with if not Location.Get(...) then exit;. An unguarded Get raises an error inside posting.
  2. Insert the staging header and lines with status Pending.
  3. Return.

Remove from the subscribers: every HTTP call, every Message or Confirm, and every Commit. Any of these inside the posting transaction either blocks the user or commits a half-finished posting.

6. Dispatch from a job queue entry

Add a codeunit run by a job queue entry every minute or two:

Step Rule
Select Pending rows whose next-retry time has passed
Group By registration, so each GSTIN uses its own resolved credential
Send One call per document to the mapped ingest endpoint
On success Status Acknowledged
On failure Add 1 to the attempt count and set the next-retry time with growing back-off
Too many failures Status Dead Letter, shown on a page a person reviews

Treat the responses differently. This is what makes a retry safe:

Response Action
5xx, timeout, no network Retry
429 Retry later, with longer back-off
409 DUPLICATE Treat as done. The document is already at Complifly. Do not retry.
403 GSTIN_NOT_PERMITTED Do not retry. Dead Letter at once and tell the administrator: the GSTIN is missing from the Complifly connection.
422 VALIDATION Do not retry. The data is wrong; retrying sends the same data. Show the field errors to a person.

7. Move the secret out of the table

Store each client secret in IsolatedStorage under a key made from the registration (company scope). Cache the access token the same way, per registration. Never render either on a page, and never write either to a log. The token routine must not call Commit; the caller owns the transaction.

Validation

Check Method Pass condition
Posting is not blocked Stop the network and post an invoice Posting completes at once; the row waits as Pending
Retry works Restore the network The row is sent and becomes Acknowledged without user action
Each GSTIN uses its own setup Post one document from each registration Each document arrives under its own GSTIN, template and credential
Unknown GSTIN is stopped in BC Post from a location with no registration row A clear error naming the GSTIN; nothing is sent
Unknown GSTIN is stopped by Complifly Add a registration in BC but not on the Complifly connection 403 GSTIN_NOT_PERMITTED; row goes to Dead Letter, not retried
Same number, two GSTINs Post invoice 100 from each registration Both accepted; no DUPLICATE
Same number, same GSTIN Send the same row twice Second response is 409; row ends Acknowledged
Write-back matches correctly Complete a document in each registration Reference number on the right document only
Upgrade is safe Run the upgrade on a copy with data Zero blank GSTIN rows; if forced to fail, the upgrade stops with an error
No secret visible Open every setup page and query the tables No secret or token in plain text

Troubleshooting

Symptom Cause Action
One registration's documents fail with 403 GSTIN_NOT_PERMITTED, the other's work The GSTIN is not on the Complifly connection Ask Complifly support or your Complifly administrator to add it. BC cannot fix this.
Documents from the second GSTIN carry the first GSTIN's details A call site still reads the default setup row Search the extension for the literal 'SETUP' and route each hit through the resolver
An event updated the wrong document The match uses document number only Match on GSTIN, document type and number
Posting is slow only when Complifly is slow An HTTP call is still in a subscriber Move it to the dispatch codeunit
Rows stay Pending and nothing is sent The job queue entry is not running, or all rows are waiting on a next-retry time Check the entry's status, then the next-retry column
Upgrade stops with a blank-GSTIN error Some rows could not be backfilled Fix those rows, then rerun. Do not bypass the check.
Rows in Dead Letter after a validation error Data is wrong, so retries cannot help Correct the source data, then requeue by hand