---
title: Bank account management
summary: Creates, retrieves, updates, and deletes (soft or hard) a payee's BANK_ACCOUNT record, and links it to a US Bank online-banking session.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Business.Payment.EFT
reference_type: package
operation_id: OP-EFT-01
trigger: Caller creates a clsBankAccount via NewBankAccount, edits its properties, and calls Save(); or retrieves an existing account by BankAccountID or CreateUser
---

# Bank account management

A `clsBankAccount` represents one payee's bank-account record — routing number, account number, account type, and status — and implements the `IBankAccount` contract consumed by `SAIF.Business.Payment`. Every EFT authorization and payment preference in this library ultimately points at one `clsBankAccount`. A related, smaller object, `clsBankAccountTransfer`, links a bank account to a US Bank online-banking session (`US_BANK_USER_SEQ_NUM`) once the account has been transferred/enrolled at US Bank.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-EFT-01 |
| **Trigger** | Caller calls `clsBankAccount.NewBankAccount` to start a new account, or `GetBankAccount`/`GetBankAccounts` to retrieve one or more existing accounts by ID or create user; edits properties and calls `Save()` |
| **Inputs** | Bank routing number, account number, account type/status/data-source codes, business/consumer code, nickname, holder/owner name; for transfer, a `US_BANK_USER_SEQ_NUM` |
| **Outputs** | Persisted/updated `BANK_ACCOUNT` row with encrypted routing and account numbers; soft-deleted (status = DELETED) or, if `PurgeRecord` is set, hard-deleted row; persisted `BANK_ACCOUNT_TRANSFER` link row |
| **Primary components** | `clsBankAccount.BeforeCreate/AfterCreate`, `BeforeInsert/AfterInsert`, `BeforeRetrieve/AfterRetrieve`, `BeforeUpdate/AfterUpdate`, `BeforeDelete/AfterDelete`, `AddBusinessRules`; `clsBankAccountCollection`; `clsBankAccountTransfer` |
| **Entry point** | `Business\SAIF.Business.Payment.EFT\clsBankAccount.vb` |

## Steps

1. **Obtain the next bank-account sequence number.** `NewBankAccount` triggers `BeforeCreate`/`AfterCreate`, which executes `SELECT BANK_ACCOUNT_seq.NEXTVAL FROM DUAL` as a scalar request ([SQL-001](../sql/SQL-001-saif-business-payment-eft.md), [DUAL](../database-tables.md#dual)) and stores the result as `BankAccountID`; the create user is stamped from the data-portal info.
2. **Caller populates account fields.** Routing number and account number setters encrypt the incoming value via `SAIF.Security.clsSecureEncrypt.EncryptString` before storing it, and only re-encrypt/mark dirty if the decrypted stored value differs from the new value; account type, status, data-source, business/consumer code, nickname, and owner name are set directly.
3. **Validate before save.** `IsValid`/`CheckRules` runs the registered business rules:
   - Required-field rules: non-zero `BankAccountID`, non-blank account number, bank name, holder name, business/consumer code, data-source code, status code, owner name, create IP address, create user, and a required create date ([BR-EFT-05](../business-rules.md#br-eft-05-insertupdate-operations-require-exactly-one-affected-row) governs the write side of these fields).
   - `HasBadCharacters`: rejects `<`/`>` characters in the nickname or holder name.
   - `ValidRoutingNumber`: decrypts the routing number and requires exactly 9 numeric digits with no separators.
     - On new accounts only (Level Two): `IsNewRoutingNumVerifed` additionally requires `NewRoutingNumVerified = True` before the account can be saved — this flag is normally set upstream by [Payment preference selection](payment-preference-selection.md) after a successful bank-validator check, or automatically by [EFT authorization / consent](eft-authorization-consent.md) when a default routing number is applied from the authorization type.
   - `ValidAccountNumber` (only when `ValidateBankAccountNumberForOldBankAccount` applies): decrypts the account number and requires a length of 5–17 characters, numeric unless the account is a debit card, with no separators.
   - Level Two code-validity rules: `ValidAccountDataSouceCode`, `ValidAccountStatusCode`, `ValidPaymentReturnReasonCode` (only if not blank), `ValidAccountTypeCode`, `ValidConsummerBusnessCode` — each defers to the corresponding lookup object's own `IsValid` (see [Code table lookups](code-table-lookups.md)).
   - On any failure: the broken-rule collection accumulates messages and `Save()` proceeds to fail validation; no database call is attempted.
4. **Insert a new account.** `BeforeInsert`/`AfterInsert` executes `INSERT INTO BANK_ACCOUNT (...)` with the encrypted routing/account numbers and all descriptive fields ([SQL-009](../sql/SQL-009-saif-business-payment-eft.md), [BANK_ACCOUNT](../database-tables.md#bank_account)).
   - On failure (`RowsAffected <> 1`): adds an `InsertError` message ([BR-EFT-05](../business-rules.md#br-eft-05-insertupdate-operations-require-exactly-one-affected-row)).
5. **Retrieve an existing account.** `BeforeRetrieve`/`AfterRetrieve` selects the full column set by `BankAccountID` ([SQL-030](../sql/SQL-030-saif-business-payment-eft.md), [BANK_ACCOUNT](../database-tables.md#bank_account)); a companion collection retrieval selects by `CREATE_USER` (case-insensitive) ([SQL-031](../sql/SQL-031-saif-business-payment-eft.md)).
   - A commented-out alternate retrieval via stored procedure `PK72970_EPAY.retrieve_BankAccount` exists in source but is not wired into `BeforeRetrieve` — dead code.*
6. **Update an existing account.** `BeforeUpdate`/`AfterUpdate` stamps `UpdateDate`/`UpdateUser` and executes a full-column `UPDATE BANK_ACCOUNT` keyed on `BankAccountID` ([SQL-077](../sql/SQL-077-saif-business-payment-eft.md), [BANK_ACCOUNT](../database-tables.md#bank_account)); `CREATE_DATE`/`CREATE_IP_ADDR`/`CREATE_USER` are intentionally excluded from the SET clause so audit-creation fields never change.
   - On failure (`RowsAffected <> 1`): adds an `UpdateError` message ([BR-EFT-05](../business-rules.md#br-eft-05-insertupdate-operations-require-exactly-one-affected-row)).
7. **Delete an account.** `BeforeDelete` branches on the `PurgeRecord` flag ([BR-EFT-11](../business-rules.md#br-eft-11-bank-account-deletion-supports-an-explicit-hard-purge-path)):
   - Default (`PurgeRecord = False`): issues a soft-delete `UPDATE BANK_ACCOUNT SET BANK_ACCOUNT_STATUS_CODE = 'DELETED', UPDATE_DATE = ..., UPDATE_USER = ...` ([SQL-088](../sql/SQL-088-saif-business-payment-eft.md), [BANK_ACCOUNT](../database-tables.md#bank_account)) — the row remains for historical linkage to authorizations and payment details ([BR-EFT-04](../business-rules.md#br-eft-04-bank-account-soft-delete-preserves-record-integrity)).
   - If `PurgeRecord = True`: issues a physical `DELETE BANK_ACCOUNT WHERE BANK_ACCOUNT_SEQ_NUM = :BankAccountID`, permanently removing the row.
   - On failure (`RowsAffected <> 1`): adds a `DeleteError` message.
8. **Link the account to a US Bank session (optional).** `clsBankAccountTransfer.Create()` copies `BankAccountSeqNum`/`USBankUserSeqNum` from the criteria (no sequence round-trip — the composite key is supplied by the caller) and inserts into `BANK_ACCOUNT_TRANSFER` ([SQL-010](../sql/SQL-010-saif-business-payment-eft.md), [BANK_ACCOUNT_TRANSFER](../database-tables.md#bank_account_transfer)); retrieval selects by the same composite key ([SQL-036](../sql/SQL-036-saif-business-payment-eft.md)), and updates/deletes follow the same pattern ([SQL-078](../sql/SQL-078-saif-business-payment-eft.md), [SQL-089](../sql/SQL-089-saif-business-payment-eft.md)).
9. **Return the outcome.** `Save()` calls `ApplyEdit()`, dispatches to `SaveChild()` or the base `Save()` depending on whether the account is a child object, and calls `MarkOld()` on success; the boolean result reflects `ErrorCount = 0`.

## Process flow

```mermaid
flowchart TD
	Start([Caller creates or retrieves<br>a bank account]) --> New{New account?}
	New -->|Yes| Seq[Fetch BANK_ACCOUNT_SEQ.NEXTVAL]
	New -->|No| Retrieve[Retrieve by BankAccountID<br>or CreateUser]
	Seq --> Edit[Caller sets routing/account<br>numbers, type, status]
	Edit --> Validate{IsValid?}
	Validate -->|No| Fail([Broken rules returned;<br>no database call])
	Validate -->|Yes| Insert[INSERT BANK_ACCOUNT]
	Retrieve --> EditExisting[Caller edits fields]
	EditExisting --> ValidateU{IsValid?}
	ValidateU -->|No| Fail
	ValidateU -->|Yes| Update[UPDATE BANK_ACCOUNT]
	Insert --> Rows{RowsAffected = 1?}
	Update --> Rows
	Rows -->|No| Contention([InsertError / UpdateError added])
	Rows -->|Yes| Done([Save returns ErrorCount = 0])
	Retrieve --> Delete{Delete requested?}
	Delete -->|PurgeRecord = True| Purge[DELETE BANK_ACCOUNT row]
	Delete -->|PurgeRecord = False| Soft[UPDATE status = DELETED]
	Purge --> RowsD{RowsAffected = 1?}
	Soft --> RowsD
	RowsD -->|No| DeleteErr([DeleteError added])
	RowsD -->|Yes| Done
```

## Failure and edge-case behavior

- A `RowsAffected` value other than `1` on insert, update, or delete adds an error message but does not raise an exception; the caller must check `ErrorCount`/`IsValid` after `Save()` ([BR-EFT-05](../business-rules.md#br-eft-05-insertupdate-operations-require-exactly-one-affected-row)).
- Soft-delete is the default and preserves the row (and its historical linkage to authorizations/payment details) with `BANK_ACCOUNT_STATUS_CODE = 'DELETED'`; only an explicit `PurgeRecord = True` performs a hard delete ([BR-EFT-11](../business-rules.md#br-eft-11-bank-account-deletion-supports-an-explicit-hard-purge-path)).
- New accounts cannot be saved until `NewRoutingNumVerified = True`, which is normally set by an upstream bank-validator check (see [Payment preference selection](payment-preference-selection.md)) or by the authorization-type cascade in [EFT authorization / consent](eft-authorization-consent.md) ([BR-EFT-09](../business-rules.md#br-eft-09-selecting-an-authorization-type-cascades-default-bank-account-attributes)).
- `IsValid`/`IsDirty` overrides on `clsBankAccount` contain only placeholder comments for child-object validation that was never implemented — no child business objects are actually validated from this class today.*
- A commented-out alternate retrieval SQL referencing stored procedure `PK72970_EPAY.retrieve_BankAccount` exists in source but is not reachable from any code path — dead code.*
- Several duplicate/no-op parameter-binding calls exist in the Insert and Update SQL builders (e.g., `UpdateDate`/`UpdateIPAddr`/`UpdateUser`/`BankAccountID` added twice); these are redundant but harmless.*

## 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.Payment.EFT\clsBankAccount.vb`
- `Business\SAIF.Business.Payment.EFT\clsBankAccountCollection.vb`
- `Business\SAIF.Business.Payment.EFT\clsBankAccountTransfer.vb`
