---
title: Payee management
summary: Creates, validates, and persists the payees attached to a benefit payment, including automatic payee generation and the support-payee sub-flow.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Business.Claim.Payment
reference_type: embedded
operation_id: OP-CP-04
trigger: A benefit payment is created or retrieved and its payee collection is populated, edited, or saved
---

# Payee management

Every benefit payment distributes money across one or more payees — worker, beneficiary, employer, attorney, medical/other provider, recovery (overpayment offset), and support (child-support withholding). This operation covers the shared payee base class, the factory that resolves the concrete payee type, automatic-payee generation, and the support-payee sub-flow that reads Oregon DHS support-order data.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-CP-04 |
| **Trigger** | `clsPayeeCollection.CreatePayee(payeeType, personID)`; `clsPayeeCollection.GetAutoPayees(dpi, claimData, paymentType, payeeTypes)` followed by `GenerateAutoPayees` |
| **Inputs** | `IDataPortalInfo`, `BenefitPaymentID`, claim data, payee type, person/provider identifiers, payment amount, cash card ID, special handling code |
| **Outputs** | Persisted `CL_BENEFIT_PAYEE` rows (plus `CL_STATE_AWARD_MONTHLY_PAYEE` for award payees); resolved payee name/address; broken-rule/error messages |
| **Primary components** | `clsPayeeBase`, `clsPayeeFactory`, `clsPayeeCollection`, `clsAwardPayeeInfo`, `clsSupportPayee` |
| **Entry point** | `Business\SAIF.Business.Claim.Payment\Payees\clsPayeeBase.vb` |

## Steps

1. **Select the payee implementation** ([BR-CP-016](../business-rules.md#br-cp-016-payee-implementation-is-selected-by-payee-type-code)). `clsPayeeFactory.GetClaimPayee`/`NewClaimPayee` dispatch by payee type code: Attorney/MedicalProvider/Other/Unknown resolve to the provider payee; Beneficiary, Employer, Recovery, Support, and Worker each have their own class; any unmapped type falls back to the provider payee.
2. **Create a new payee.** The base constructor draws a new `BENEFIT_PAYEE_SEQ` value only when the parent `BenefitPaymentID` is non-zero, then each subtype's `OnInstantiation()` sets its payee type and, for Worker/Beneficiary/Employer, applies default special handling.
3. **Apply default special handling** ([BR-CP-017](../business-rules.md#br-cp-017-payment-address-required-for-a-non-zero-non-award-payee)). `SetDefaultSpecialHandling` sets `SpecialHandlingCode = "FGNML"` when the payment address is valid, non-empty, and foreign; clears it otherwise. Re-evaluated whenever a provider payee's location changes.
4. **Determine automatic payees** ([BR-CP-021](../business-rules.md#br-cp-021-auto-payee-eligibility-and-de-duplication)). `GetAutoPayees` evaluates eligibility per type: Worker always; Support only with an active support order (cross-library read of `clsClaimData`, `SAIF.Business.View.Claim` — not yet migrated); Recovery based on non-deferred overpayment balance for TD or any overpayment balance otherwise; Attorney only with a legal-representative provider location; one Medical Provider per non-disputed, non-pharmacy-TPA, non-MCO-plan billing provider (uncatalogued inline read against `CBP_BILL`/`PHARMACY_TPA`/`MCO_PLAN`); one Beneficiary per claim beneficiary (`clsClaimPersonView`, same not-yet-migrated library).
5. **Materialize and de-duplicate automatic payees** ([BR-CP-021](../business-rules.md#br-cp-021-auto-payee-eligibility-and-de-duplication)). `GenerateAutoPayees` creates each eligible candidate only if no existing payee of the same type (Worker/Support), same type plus provider location (Attorney/Medical Provider), or same type plus person (Beneficiary) already exists; a new Recovery payee's award-info balance is seeded from the claim's current overpayment balance.
6. **Resolve payee name and payment address.** Each subtype overrides `PayeeName`/`PaymentAddress`/`ValidatePayeeAddress` polymorphically: Worker and Beneficiary read the person's address through `ClaimServices.getPersonAddress` (`SAIF.Business.View.Claim`); Employer reads the policyholder address through `clsPolicyAddress` (`SAIF.Business.View.Policy`); Provider and Attorney read through `SAIF.Business.View.Provider`'s `clsProviderLocation`/`clsProviderAddress`; Recovery uses a fixed "SAIF Recovery" identity with no address validation; Support reads the DHS provider's active address. None of these three view libraries are in the already-migrated set, so their logic is not embedded further here — see each library's `operations-processes.md` for the address-resolution logic they own.
   - Provider addresses at a parent location have their lines shifted down one position with the parent's name inserted at line 1; the tax ID used for payment is the parent's when the address is a parent location.
7. **Validate the payee** ([BR-CP-017](../business-rules.md#br-cp-017-payment-address-required-for-a-non-zero-non-award-payee), [BR-CP-018](../business-rules.md#br-cp-018-cash-card-identifiers-must-be-numeric-and-within-length-limits), [BR-CP-019](../business-rules.md#br-cp-019-provider-payees-require-a-valid-location-and-tax-id-for-non-zero-payments)). A non-award, non-zero payee must have a foreign or complete payment address; a used cash card ID must contain no decimal point and be ≤ 16 digits; a provider payee with a non-zero payment plus monthly amount must reference a valid provider location, and a non-zero immediate payment additionally requires a non-blank tax ID.
8. **Attach and validate award-info child, if applicable** ([BR-CP-025](../business-rules.md#br-cp-025-overpayment-recoverable-balance-is-enforced-on-settlement-recovery-payees-not-on-refund-payments)). `MonthlyAwardInfo` lazily loads `CL_STATE_AWARD_MONTHLY_PAYEE` ([SQL-045](../sql/SQL-045.md), [CL_STATE_AWARD_MONTHLY_PAYEE](../database-tables.md#cl_state_award_monthly_payee)); its rules require the next monthly payment not to exceed the current balance due and require the retro reserve amount not to exceed the monthly payment, both non-negative.
9. **Support-payee sub-flow.** See below.
10. **Save the payee.** `Save()` returns `False` immediately when `BenefitPayeeID = 0` — a payee without an assigned parent silently fails to save.
11. **Insert/update the payee.** The shared parameter block ([SQL-070](../sql/SQL-070.md) covers the related collection-level candidate read; the payee's own insert/update procedures were traced by name — `pk82394_claim_benefit.insertBenefitPayee`/`updateBenefitPayee` against `CL_BENEFIT_PAYEE` — but have no distinct catalog entry and are described here without a link) is extended per subtype: Worker adds person ID and form flags; Beneficiary adds person ID; Employer sets person ID to the *worker's* ID; Provider adds provider location IDs (zero coerced to NULL); Support adds the support order ID and the DHS provider location ID.
    - On a rows-affected value other than 1: `"Insert Failed"` (insert) or `DBContentionError` (update) ([BR-CP-001](../business-rules.md#br-cp-001-optimistic-concurrency-on-all-stored-procedure-based-operations)); success saves any materialized award-info and remark children.
12. **Persist the award-info child** ([SQL-012](../sql/SQL-012.md) create, [SQL-095](../sql/SQL-095.md) update, [SQL-118](../sql/SQL-118.md) delete, [CL_STATE_AWARD_MONTHLY_PAYEE](../database-tables.md#cl_state_award_monthly_payee)). The update statement enforces optimistic concurrency in its `WHERE` clause; the delete statement carries no update-number guard at all.
13. **Delete the payee.** `BeforeDelete` first cascades a delete of the materialized award-info child, then deletes the `CL_BENEFIT_PAYEE` row itself; the benefit remark child is **not** cascaded, a possible orphan.
    - On a rows-affected value other than 1: `DBContentionError` ([BR-CP-001](../business-rules.md#br-cp-001-optimistic-concurrency-on-all-stored-procedure-based-operations)).

### Support payee sub-flow (`clsSupportPayee`)

14. **Resolve the active support order.** On create, `GetSupportOrderID()` reads the worker's untermed support orders ([SQL-084](../sql/SQL-084.md), [CL_CLAIM_SUPPORT_ORDER](../database-tables.md#cl_claim_support_order)) and takes the first row when one exists.
15. **Revalidate on load** ([BR-CP-020](../business-rules.md#br-cp-020-support-payee-requires-an-active-support-order-for-immediate-payment)). If the stored order is no longer active (validated via [SQL-085](../sql/SQL-085.md)/[SQL-086](../sql/SQL-086.md)), it is silently replaced by the claim's currently active order when one exists.
16. **Look up the SED account number** ([SQL-087](../sql/SQL-087.md), [CL_CLAIM_SUPPORT_ORDER](../database-tables.md#cl_claim_support_order)). Wrapped in a catch-all `Try/Catch` that swallows any lookup error and returns an empty string — a lookup failure is indistinguishable from a genuinely missing SED number.
17. **Format the EDI support-order addendum.** Builds a fixed-format `DED*WT`/`DED*WA` string encoding the multi-order marker, SED account number, issue date, payment amount in cents, worker tax ID digits, and a truncated payee name; any payment type other than TD payment, award, or DCS settlement throws an exception.
18. **Validate the support order requirement** ([BR-CP-020](../business-rules.md#br-cp-020-support-payee-requires-an-active-support-order-for-immediate-payment)). A non-award support payee with a positive payment amount requires an active support order on the claim; an award-mode payment accepts a terminated order as long as it is on file.

> **Cross-library data note:** `clsSupportPayee` reads `CL_CLAIM_SUPPORT_ORDER` directly with its own inline SQL and never calls into `SAIF.Business.Claim.Maintenance`. That library's already-migrated "Support order management" operation (`../../saif-business-claim-maintenance/operations/support-order-management.md`) owns the same table from the maintenance side (creating/updating/deleting orders and checking whether a benefit payee references one before allowing changes). This is a candidate cross-library data coupling through a shared table, not a code delegation, so no `> Origin` callout is used.

## Process flow

```mermaid
flowchart TD
	Start([Payee created or<br>auto-payee pass requested]) --> Type{Payee type?}
	Type -->|Attorney/Medical/Other/Unknown| Provider[Provider payee]
	Type -->|Beneficiary/Employer/Recovery/Support/Worker| Concrete[Concrete payee class]
	Provider --> Addr[Resolve payee name<br>and payment address]
	Concrete --> Addr
	Addr --> Special[Apply default special handling:<br>foreign address to FGNML]
	Special --> Validate{Address, cash card,<br>provider rules pass?}
	Validate -->|No| Blocked([Broken rules returned])
	Validate -->|Yes| Award{Award-info<br>materialized?}
	Award -->|Yes| AwardRules{Monthly payment<br>within balance?}
	AwardRules -->|No| AwardBlocked([Broken rule])
	AwardRules -->|Yes| Save
	Award -->|No| Save[Save payee]
	Save --> HasId{BenefitPayeeID<br>assigned?}
	HasId -->|No| SilentFail([Save returns False,<br>no error added])
	HasId -->|Yes| Persist[Insert/update CL_BENEFIT_PAYEE]
	Persist --> Rows{RowsAffected = 1?}
	Rows -->|No| Contention([Insert Failed / DB contention error])
	Rows -->|Yes| Done([Payee persisted;<br>award-info/remark children saved])
```

## Failure and edge-case behavior

- A payee constructed without a parent payment silently fails to save (`Save()` returns `False`, no error message).
- `clsAwardPayeeInfo`'s delete statement carries no update-number guard, unlike its insert and update, so a stale-object delete cannot be detected as contention.
- Deleting a payee does not cascade to its benefit remark child, only to its award-info child — a remark row can be orphaned.
- `SEDAccountNumber`'s catch-all exception handling makes a database error indistinguishable from a genuinely missing SED number.
- `clsBeneficiaryPayee.BeforeRetrieve` is overridden but unreachable in practice, because the base `Criteria.RetrieveSQL` always throws `InvalidSQLProperty` — payees must be loaded through the parent payment's collection, not individually.
- `Rule_CheckValidPersonID` on the beneficiary payee is registered under the property name `"BeneficiaryID"` even though the actual property is `PersonID` — a likely defect in rule wiring that may cause the broken-rule message to attach to the wrong field.
- Read-only "candidate" payees built by `InstantiateReadonlyPayee` are assigned a process-wide static incrementing ID unrelated to the real `BENEFIT_PAYEE_SEQ` sequence.

## 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.Claim.Payment\Payees\clsPayeeBase.vb`
- `Business\SAIF.Business.Claim.Payment\Payees\clsPayeeCollection.vb`
- `Business\SAIF.Business.Claim.Payment\Payees\clsPayeeFactory.vb`
- `Business\SAIF.Business.Claim.Payment\Payees\clsAwardPayeeInfo.vb`
- `Business\SAIF.Business.Claim.Payment\Payees\clsSupportPayee.vb`
- `Business\SAIF.Business.Claim.Payment\Payees\clsWorkerPayee.vb`
- `Business\SAIF.Business.Claim.Payment\Payees\clsBeneficiaryPayee.vb`
- `Business\SAIF.Business.Claim.Payment\Payees\clsEmployerPayee.vb`
- `Business\SAIF.Business.Claim.Payment\Payees\clsProviderPayee.vb`
- `Business\SAIF.Business.Claim.Payment\Payees\clsRecoveryPayee.vb`
- `Business\SAIF.Business.Claim.Payment\Payees\IPayee.vb`
