# Paying vendors safely: tax checks when the money moves

Most companies check a vendor once, on the day it is added. Ritwik One Finance checks the GST registration again before every payment, never lets a failed check count as a pass, and verifies the PAN–Aadhaar link while keeping only four digits.

By Nikhil Kumar, AI/ML Engineer, Digital Transformation and Alliances Department · 14 November 2026 · Ritwik Finance Enterprises Private Limited
Source: https://blogs.ritwikfinance.com/vendor-tax-checks-at-payment/

*I designed and built Ritwik One Finance. This post explains how it decides whether a vendor can be paid, and why the most important rules live in the database.*

Ritwik One Finance is where our branches raise vendor bills and the Accounts team pays them: the electrician who fixes a branch's wiring, the landlord, the printer. The money itself moves through the company's bank; Finance decides whether a bill may be paid, and records the payment. Every one of those payments carries two tax risks that don't show up on the day you pay.

- **GST.** If the vendor's GST registration has been cancelled or suspended, the vendor shouldn't be charging GST at all, and the company may not be able to claim credit for the GST on that bill.
- **TDS.** If an individual vendor's PAN is inoperative because it isn't linked to Aadhaar, TDS has to be deducted at a higher rate: at least 20% under section 397(2) of the Income-tax Act 2025 (section 206AA under the old Act). Deduct at the normal rate, and the shortfall can come back later as a demand on the company.

The usual approach is to check a vendor once, when it is added, and find any problem at the next return or reconciliation, months after the money has gone. A registration that was active in June can be cancelled by October. So Finance checks at each step where money is decided, under four rules:

- **Check when the money moves**, not only on the first day.
- **A check that can't run is not a pass.**
- **Keep proof that you checked, not the data you checked with.**
- **Put the rules where no screen can skip them**: in the database.

1. **Adding a vendor**: GSTIN checked on our servers, then looked up live. PAN–Aadhaar link checked for individuals.
2. **Approving a bill**: Warnings from the last stored answers: inactive GST, risky vendor, PAN not linked to Aadhaar.
3. **Paying**: GSTIN checked again. Payment blocked if it isn't active. Higher TDS rate ticked for an inoperative PAN.

## Part 1Adding a vendor: fast for clean vendors, careful for the rest

Checks that slow down every vendor get ignored, so Finance is quick for clean vendors and careful for the rest.

**Offline first.** When staff type a GSTIN, Finance checks it on our own servers before anything goes out: the 15-character format, the state code, the type of PAN inside it, and the check character at the end. The check character catches every single-character typo, so a mistyped GSTIN never costs a lookup.

**Then live.** A GST verification service fills in the legal name, trade name, status, business type, state and address. Staff add only what the vendor supplies and how to reach them. What happens next depends on the answer:

| The GSTIN is… | What happens |
| --- | --- |
| Active | Approved straight away, with no call-back. Accounts is told and adds the bank account. |
| Cancelled or suspended | Can be added, but waits for Accounts, who see a red warning and must tick "I checked this with the vendor". |
| Not found | Refused. |
| Can't be checked right now | Staff type the legal name, and Accounts checks the vendor by hand. |

Even an active GSTIN goes to Accounts when something looks wrong: its email or mobile belongs to an employee or to another live vendor, or it shares a GSTIN, PAN, email or mobile with a vendor Accounts rejected before, in which case Accounts sees the earlier reason. Vendors without GST register with their PAN and a photo of the PAN card or letterhead; if that PAN turns out to have an active GST registration, Finance gives the GSTIN and insists on a GST vendor instead.

When a person has to decide, the rules are strict. A **different** Accounts user approves (maker-checker). They first call the vendor on a number from a trusted source (the GST certificate, a letterhead, a cancelled cheque or an earlier invoice) and record which one. And a vendor can't be an employee: a match with any current or former employee's email or mobile, or an address on the company's own domain, blocks approval.

## Part 2The last known answer

Outside services go down, time out and run out of credits. The tempting shortcut is to treat "couldn't check" as "fine" and let the work continue. That shortcut is exactly how a cancelled vendor gets paid on the one afternoon the GST service was slow.

So every answer is kept, and the rule is simple: **a check that can't run never clears a warning**. Finance uses the newest real answer it has, and a "couldn't check" falls back to it.

[Diagram: Five checks over five days. Day 1 the service says active, Finance uses active, payment allowed. Day 2 there is no answer, Finance keeps the last answer, active, payment allowed. Day 3 the service says cancelled, payment blocked. Day 4 there is no answer, Finance keeps the last answer, cancelled, payment still blocked. Day 5 the service says active again and payment is allowed.]

*Figure 1. When the service can't answer (days 2 and 4), the last real answer stands. A vendor last seen cancelled stays blocked until a real answer says otherwise.*

Three details make the answers trustworthy:

- **Every answer is stored in an append-only log**, including answers for adds that were later refused. Database triggers refuse any update or truncation, so history can't be rewritten.
- **Answers we can't trust count as "couldn't check"**: sample answers from a test key, an answer about a different GSTIN or PAN than the one asked, and anything that can't be read. "Not found" counts only when the service's own error reply says so, never because a request failed.
- **The date of the GST records is shown** wherever the status is, because the service may answer from a saved copy up to 30 days old.

Lookups cost credits, so Finance spends them carefully. At payment, a very recent check is reused instead of asking again, and budgets cap the total, so a bug can never burn the account. Approvers never trigger a lookup at all: their warnings ("GST registration not active", or "Risky vendor" when the service rates the vendor as high risk) are worked out from answers already stored.

## Part 3Paying: check again, right before the money leaves

When Accounts opens the pay screen, Finance checks the GSTIN again and shows the result in one line: active on the GST portal, when it was checked, and the date of the records it came from. The decision is deliberately boring:

```
answer = newest GST answer for this vendor (refreshed unless it is very recent)
if answer is CANCELLED or NOT FOUND:
    block the payment: "put the bill on hold and speak to the vendor"
if the check couldn't run:
    the last known answer decides
```

A blocked payment isn't a dead end. The bill goes on hold, Accounts speaks to the vendor, and the payment goes through once a real answer says the registration is active again.

## Part 4Rules the database enforces

Application code has bugs, screens get rewritten, and someday someone will write a quick script against the database. So the rules that protect money are written twice: once in the application, for good error messages, and once in PostgreSQL, where nothing can go around them.

```
-- simplified: whoever added a vendor can never be the one who approves it
CHECK (status <> 'APPROVED' OR approved_by <> added_by)

-- simplified: one live vendor per GSTIN; adding it again after a rejection still works
UNIQUE (gstin) WHERE status <> 'REJECTED'
```

Beyond these, CHECK constraints require every decision to be complete (who decided, when, and either the call-back details or the reason for rejection), and only allow an automatic approval when the stored GST status is active and there was no call-back. A guard trigger freezes a vendor once it is decided, so approved contact details can't be swapped quietly later, and other triggers refuse to delete or truncate vendors at all. Each vendor gets its own reference number, and every change goes to the audit log.

## Part 5PAN–Aadhaar: check the link, keep four digits

Only individuals' PANs are linked to Aadhaar; you can tell from the fourth character of the PAN, which is P. Firms, companies and trusts are asked nothing extra. For an individual, the add-vendor form asks for the vendor's Aadhaar number and a tick confirming the vendor agreed to the check. Then:

1. **The number is checked on our servers first**: 12 digits and the Verhoeff check digit, which catches every single-digit typo and every swap of two neighbouring digits.
2. **It is sent once** to a licensed KYC service's PAN–Aadhaar link check.
3. **A wrong number, someone else's number or an unknown PAN** stops the add and says what to check. Linked or not linked, the vendor is added and waits for Accounts. If the service can't answer, the vendor still waits for Accounts, with the check shown as not done.

[Diagram: The typed Aadhaar number is checked on our servers, sent once to the KYC service, and never stored. A database check refuses any run of 12 digits, so only the last four digits, shown as XXXX XXXX 9012, are kept with the result and the vendor's consent.]

*Figure 2. The full number travels as far as the KYC check and no further. Only the last four digits reach the database, and the database itself refuses anything longer.*

Keeping only four digits isn't left to good intentions. It is enforced at four levels:

```
-- simplified: a PAN–Aadhaar check result can never hold a full Aadhaar number
CHECK (kind <> 'PAN_AADHAAR' OR result IS NULL OR result::text !~ '[0-9]{12}')
```

- the database refuses any run of 12 digits in a check result;
- the tests search every stored row, audit entry, notification and email for the number;
- Aadhaar and PAN fields are blanked out in logs, and error reports drop request bodies;
- the vendor's consent is recorded in the audit log.

When the PAN turns out to be inoperative, Finance **warns rather than blocks**. Every bill from that vendor carries "PAN not linked to Aadhaar" for approvers, and on the pay screen the higher TDS rate starts ticked. Accounts can still change it; the final call stays with a person.

Not keeping the number has a cost, and it's worth being honest about it. Finance can't re-check the link by itself at payment time, so it shows the last answer, and Accounts can check again by typing the number. A failed re-check never clears "not linked". We chose privacy over convenience.

## Part 6TDS on the Income-tax Act 2025

The TDS options follow the 2025 Act: each one is the payment code the new Act reports, with the old 1961 section next to it so Accounts recognise it. The higher rate for an inoperative PAN is a tick on any code rather than a separate option, because the new Act reports the payment under its own code. Part payments stay consistent: a balance locks to the same code and rate as its advance, and an advance whose TDS can't be matched now blocks the balance instead of letting it through without TDS.

## Part 7What leaves our servers

Only a GSTIN or a PAN goes to the GST service, and only the PAN and the typed Aadhaar number go to the KYC service, once per check. Finance's outside calls are listed in one place: email, the company's own file storage, the GST service, the KYC service, and a bill-reading (OCR) service in Mumbai that keeps no copy. A test walks every import Finance makes, however indirect, using the TypeScript compiler, and fails if any path reaches another outside service or an AI language model.

## What's next

The vendor risk score is refreshed when a vendor is added or when Accounts taps "check again", not on a schedule yet. And the "last known answer" rule is ready for the next outside check we add: every new check gets the same append-only log, the same budgets and the same rule that "couldn't check" never clears a warning.

## What I'd tell anyone building vendor payments

> A check that can't run is not a pass.

- **Check when the money moves**, not only on the day the vendor is added.
- **Keep every answer.** An append-only log turns "what did we know when we paid?" into a query.
- **Let clean cases through automatically.** When an active GSTIN needs no call-back, people take the warnings seriously when they do appear.
- **Keep proof that you checked, not the data you checked with.** Four digits and an audit entry are enough.
- **Write the money rules in the database too**, so no screen and no script can skip them.

---

Published 14 November 2026.

About the author: Nikhil Kumar, AI/ML Engineer, Digital Transformation and Alliances Department, Ritwik Finance Enterprises Private Limited. Nikhil designed and built these parts of Ritwik One, the staff super app of Ritwik Finance: Travel: field trips, GPS tracking, customer visits and travel claims; Finance: vendors, bills, petty cash and vendor payments; AI document reading: bill OCR and odometer photo checks; The Ritwik One Android app and its background GPS; The release pipeline and production setup on Google Cloud.
