---
title: Maintain provider tax ID and sync TIN contact
summary: Persists provider tax IDs (TINs), validates format and uniqueness, normalizes the associated address, and pushes primary-TIN changes to ContactManager.
status: draft
owner: SAIF
last_reviewed: 2026-07-30
library: SAIF.Business.Provider
reference_type: package
operation_id: OP-PRV-07
trigger: Caller creates or retrieves a clsProviderTaxID, edits properties, and calls Save() or Delete()
---

# Maintain provider tax ID and sync TIN contact

`clsProviderTaxID` maintains a provider's tax identification records, keyed by the raw 9-digit `TaxID`. Once saved, the TIN value itself is immutable — only its display formatting may change — and only the record flagged primary is synchronized to Contact Manager through `clsTaxApi.UpdateTinContact`.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-PRV-07 |
| **Trigger** | Caller calls `NewProviderTaxID`/`GetProviderTaxID`, edits properties, and calls `Save()` or `Delete()` |
| **Inputs** | `ProviderSeqNum`, `TaxID`/`TaxIDDisplayDescr`, `RegisteredName`, `EffDate`/`ExpDate`, `W9SentDate`/`W9ReceivedDate`, `Report1099Flag`, address fields, `ForeignAddressFlag`, `PrimaryTaxIDFlag` |
| **Outputs** | Persisted [PROVIDER_TAX_ID](../database-tables.md#provider_tax_id) row; ContactManager `TaxDto` sync for the primary TIN |
| **Primary components** | `clsProviderTaxID.Create`, `BeforeInsert/BeforeUpdate/BeforeDelete`, `FillTinContact`, `clsTaxApi.UpdateTinContact` |
| **Entry point** | `Business\SAIF.Business.Provider\clsProviderTaxID.vb` |

## Steps

1. **Create.** `Create()` sets `ProviderSeqNum` from the criteria and defaults `StateCode = "OR"`, `CountryCode = "US"`.
2. **Retrieve.** `BeforeRetrieve` joins [PROVIDER_TAX_ID](../database-tables.md#provider_tax_id) to [PROVIDER](../database-tables.md#provider) on `PROVIDER_SEQ_NUM`, keyed by `:TaxID` ([SQL-060](../sql/SQL-060-retrieve-provider-tax-i-d.md)). `PrimaryTaxIDFlag` is derived in SQL with `DECODE(PROVIDER.PRIMARY_TAX_ID, PROVIDER_TAX_ID.TAX_ID, 'Y', 'N')` rather than stored directly on the row. `Fill()` also stores `m_RetrievedTaxID` for the immutability check.
3. **Validate before save.**
   - `MissingTIN`/[BR-011](../business-rules.md#br-011-tin-values-must-be-nine-numeric-digits-and-unique): `ValidTIN` requires a numeric value at least 9 digits long; `ValidTINFormat` requires the formatted display to match `00-0000000` (EIN) or `000-00-0000` (SSN) exactly.
   - [BR-025](../business-rules.md#br-025-an-existing-provider-tax-id-value-is-immutable-once-saved): `AlteredTIN` blocks changing the digits of an existing TIN (`m_RetrievedTaxID` differs from the current value); only the display format may change.
   - `IsDuplicateTIN`: hard error querying [PROVIDER_TAX_ID](../database-tables.md#provider_tax_id) for an existing row with the same `TAX_ID` ([BR-011](../business-rules.md#br-011-tin-values-must-be-nine-numeric-digits-and-unique)).
   - `MissingRegisteredName`: hard error requiring `RegisteredName`.
   - `ValidSentReceiptDate`: hard error if `W9ReceivedDate` precedes `W9SentDate`; `UnreasonableW9SentDate`: hard error if the sent date is before 01/01/1850.
   - [BR-012](../business-rules.md#br-012-only-one-primary-tin-is-allowed-per-provider): `HasDuplicatePrimaryTaxID` blocks a save when `PrimaryTaxIDFlag = "Y"` and another primary TIN already exists for the provider.
   - `ValidStatecode`: hard error validating `StateCode` against the `STATE` reference table; `ValidZipCodeExt`: hard error requiring a numeric ZIP+4 when present.
   - `ValidateCleanAddress` (warning only): runs the same USPS cleansing service directly, ignoring USPS error code `"10"`; any remaining error surfaces `"Warning: Address may not be valid."` without blocking the save.
4. **Foreign/domestic address side effects.** Setting `ForeignAddressFlag = "Y"` clears city/state/country/ZIP/ZIP+4; switching back to domestic clears `AddressLine4` and resets `StateCode = "OR"`, `CountryCode = "US"`. The `StateCode` setter itself derives `CountryCode`: blank state clears the country, a nonblank state sets `"US"`.
5. **Insert.** `BeforeInsert` writes `TAX_ID`, `TAX_ID_DISPLAY_DESCR`, `PROVIDER_SEQ_NUM`, `REGISTERED_NAME`/`REGISTERED_NAME_UPPER_CASE`, `EFF_DATE`/`EXP_DATE`, `W9_SENT_DATE`/`W9_RECEIVED_DATE`, `REPORT_1099_FLAG`, address fields, `FOREIGN_ADDRESS_FLAG`, and audit fields to [PROVIDER_TAX_ID](../database-tables.md#provider_tax_id) ([SQL-036](../sql/SQL-036-insert-provider-tax-i-d.md)).
   - `FillTinContact()` builds `TaxDto { providerNumber, tinId = TaxID, tinNumber = TaxIDDisplayDescr }`.
   - On a rows-affected value other than `1`: add `"Insert Failed"`.
   - On success, **only if `PrimaryTaxIDFlag = "Y"`**: call `clsTaxApi.UpdateTinContact(...)` against route `provider/{taxDto.ProviderNumber}/tin`; on exception, serialize the DTO, log it, and add `ApiInsertError`.
6. **Update.** `BeforeUpdate` writes the same fields to [PROVIDER_TAX_ID](../database-tables.md#provider_tax_id) ([SQL-084](../sql/SQL-084-update-provider-tax-i-d.md)) with concurrency check on `UPDATE_NUM`.
   - On success, if primary: call `clsTaxApi.UpdateTinContact(...)`; on API exception, log and add `ApiUpdateError`.
7. **Delete.** Deletes the [PROVIDER_TAX_ID](../database-tables.md#provider_tax_id) row by `TAX_ID` ([SQL-106](../sql/SQL-106-delete-provider-tax-i-d.md)) — **no ContactManager sync call is made on delete**, unlike insert/update for the primary TIN.*
8. **Payment-history helper (not gated by a rule).** `HasPayment` calls `clsPaymentApi.GetPayments` twice — once with an EIN-formatted TIN (`00-0000000`) and once with an SSN-formatted TIN (`000-00-0000`) — but no business rule in this file consumes the result, so it functions as an on-demand check available to callers rather than an enforced save-time gate.*

## Process flow

```mermaid
flowchart TD
	Start([Caller edits tax ID]) --> Altered{Existing TIN digits changed?}
	Altered -->|Yes| Fail([Immutable TIN change<br>blocks save])
	Altered -->|No| FormatCheck{Numeric, 9+ digits,<br>matches EIN/SSN mask?}
	FormatCheck -->|No| Fail2([Invalid format or<br>duplicate blocks save])
	FormatCheck -->|Yes| DupCheck{Duplicate TIN on file?}
	DupCheck -->|Yes| Fail2
	DupCheck -->|No| PrimaryCheck{Marked primary and<br>another primary exists?}
	PrimaryCheck -->|Yes| Fail3([Second primary<br>TIN blocks save])
	PrimaryCheck -->|No| Clean[Normalize address;<br>warning only]
	Clean --> Persist[Insert/update/delete PROVIDER_TAX_ID]
	Persist --> Rows{RowsAffected = 1?}
	Rows -->|No| Contention([Insert Failed / DB contention error])
	Rows -->|Yes| PrimaryFlag{PrimaryTaxIDFlag = Y?}
	PrimaryFlag -->|Yes, insert/update| Api[Call clsTaxApi.UpdateTinContact]
	PrimaryFlag -->|No, or delete| NoApi([No ContactManager call])
	Api --> Done([Save complete])
	NoApi --> Done
```

## Failure and edge-case behavior

- Deleting a tax ID never calls ContactManager, so a removed TIN can remain visible there until a subsequent primary-TIN save elsewhere triggers a sync.*
- Only the primary TIN synchronizes to ContactManager; non-primary TIN inserts/updates persist to the database with no external side effect.
- `HasPayment` exists as a read-only helper but is not wired into any enforced business rule in this file, so its use is caller-driven.*
- USPS address cleansing on a tax ID's mailing address is warning-only and never blocks the save, mirroring the pattern used for provider addresses.

## Related documentation

- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [SQL catalog](../sql/index.md)
- [Database tables](../database-tables.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts.

## Sources

- `Business\SAIF.Business.Provider\clsProviderTaxID.vb`
- `Business\SAIF.Business.Provider\Api\clsTaxApi.vb`
- `Business\SAIF.Business.Provider\Api\clsPaymentApi.vb`
