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:
- Ship the registration table first, with no data moves.
- 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.
- 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.
- 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.
- 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:
- Look up the location with
if not Location.Get(...) then exit;. An unguardedGetraises an error inside posting. - Insert the staging header and lines with status Pending.
- 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 |
Related Articles¶
- Dynamics 365 Business Central — the base integration
- The Integration Pattern
- Write-back API — events and acknowledgement
- Machine-to-Machine Authentication