---
title: Refund adjustment processing
summary: Reverses previously paid medical, HWR, non-disabling, worker-travel, third-party, or SAIF-check amounts by creating negative financial transactions or routing recovery requests to the external claim-data API.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Business.Claim.Payment
reference_type: embedded
operation_id: OP-CP-05
trigger: An adjuster selects a refund type and calls Save() after reviewing the payments/services eligible to be refunded
---

# Refund adjustment processing

A refund reverses money already paid on a claim. `clsRefundAdjustment` is a thin, shared base (total-refund-amount rule, remark-length rule) that seven concrete refund classes extend — one class per refund mode, selected by the caller at construction rather than by a runtime type switch ([BR-CP-022](../business-rules.md#br-cp-022-refund-and-transfer-adjustments-are-mutually-exclusive-by-concrete-type)). Most refund types build negative financial transactions directly; two (non-disabling and third-party) instead post recovery/activity requests to the external claim-data API.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-CP-05 |
| **Trigger** | Caller constructs the concrete refund class (`clsMedicalRefund`, `clsHwrRefund`, `clsOtherRefund`, `clsSaifCheckRefund`, `clsWorkerTravelRefund`, `clsThirdPartyRefund`, `clsNdRefund`) with claim data, populates candidate refund amounts, and calls `Save()` |
| **Inputs** | `IDataPortalInfo`, `clsClaimData`, issue date, remark text, per-payment/per-service-line refund amounts, optional issue-date filters |
| **Outputs** | Negative-amount `CL_FINANCIAL_TRANS`/`CL_FINANCIAL_TRANS_DTL` rows and `CL_FINANCIAL_TRANS_BILL_ADJUST` rows (most types); external recovery/activity API requests (ND and third-party refunds); boolean save result |
| **Primary components** | `clsAdjustmentBase`, `clsRefundAdjustment`, `clsMedicalRefund`, `clsHwrRefund`, `clsOtherRefund`, `clsSaifCheckRefund`, `clsWorkerTravelRefund`, `clsThirdPartyRefund`, `clsNdRefund` |
| **Entry point** | `Business\SAIF.Business.Claim.Payment\Adjustments\Refunds\clsRefundAdjustment.vb` |

## Steps

1. **Select the concrete refund type** ([BR-CP-022](../business-rules.md#br-cp-022-refund-and-transfer-adjustments-are-mutually-exclusive-by-concrete-type)). The caller instantiates the class matching the refund mode; only that type's logic and rule set run for the request.
2. **Establish the default issue date.** `clsAdjustmentBase`'s constructor seeds `IssueDate` from `clsPaymentBase.GetNextIssueDate(dpi, Adjustment)`; `MaxIssueDate` is capped 28 calendar days ahead, rolled to a work day.
3. **Load candidate payments to refund.** `SetMedicalPayments` (representative of the pattern shared by the other refund types) reads posted financial transactions for the claim, optionally filtered by issue-date range, and builds one read-only line item per prior payment; medical refunds further break a payment into its individual service lines via `clsBillPaymentAdjustments.GetBillPaymentAdjustments`.
4. **Attach a per-line amount rule.** Each candidate line gets a `Rule_ValidPaymentRefundAmount` rule so a caller cannot request a refund amount exceeding what was actually paid on that line.
5. **Validate the aggregate request.** `Rule_RefundAmountNotZero` (base) requires a non-zero total; `Rule_ValidRemarkLength` requires the remark to be 200 characters or fewer; `Rule_CheckPaidCosts` is a **warning**, surfaced when posting the adjustment would drive a cost category negative — the user may override it and proceed ([BR-CP-023](../business-rules.md#br-cp-023-paid-cost-and-own-motion-checks-on-refunds-are-overridable-warnings)).
6. **Build the reversing financial transaction.** For a full-payment refund, `GetRefundTransaction` clones the original transaction's claim/policy/payee snapshot fields, negates `TotalTransactionAmt` and every detail line's `TransactionAmt`, and copies the original detail's category/reimbursement/cost codes; a line refund instead builds fresh detail rows sized to only the refunded services, re-deriving cost category and reimbursement program through `clsMedicalBillPayment.GetClaimCostInfo`.
   - This delegates persistence to [Financial transaction and remark management](financial-transaction-and-remark-management.md) — the refund transaction is created and saved exactly like any other financial transaction (`clsFinancialTrans.NewFinancialTrans` then `Save()`), with `IgnoreBusinessRules = True` set on the new transaction before its own fields are populated.
7. **Record a bill payment adjustment per refunded service line, if applicable.** `GetBillAdjustment` builds a `clsBillPaymentAdjustment` referencing the refund transaction, the original bill ID, and the audited service sequence number ([BR-CP-026](../business-rules.md#br-cp-026-bill-payment-adjustments-require-an-eligible-financial-transaction)); full-payment refunds intentionally skip this per a code comment ("don't create adjustments for full refunds").
8. **Save the refund.** `Save()` re-checks `IsValid`, then saves every built refund transaction in turn, stopping immediately (`Return False`) if any transaction fails to save; only after all transactions succeed does it save the accompanying bill payment adjustments.
9. **Route non-disabling refunds to the external API instead** ([BR-CP-024](../business-rules.md#br-cp-024-non-disabling-and-third-party-refunds-route-through-the-external-claim-data-api)). `clsNdRefund` does not write `CL_FINANCIAL_TRANS` directly; it builds a `RecoveryRequest` and posts it through `ApiRoute(..., ApiRouteType.RetrieveSingleWithBody, ..., "{claimId}/recovery")` against the claim-financial API client, and separately calls `GetNDROverMax` (see [Payment list queries and NDR claim data retrieval](payment-list-queries-and-ndr-claim-data-retrieval.md)) to flag claims already at their statutory reimbursement maximum ([BR-CP-073](../business-rules.md#br-cp-073-non-disabling-reimbursement-is-capped-against-the-cumulative-recovered-amount)).
10. **Route third-party refunds to the activity API as well** ([BR-CP-024](../business-rules.md#br-cp-024-non-disabling-and-third-party-refunds-route-through-the-external-claim-data-api)). `clsThirdPartyRefund` builds an `ActivityRequest` posted through the activity API client in addition to any recovery request, and reads negative-paid-cost information via `GetNegativePaidCostInfos`, which itself calls the claim-financials API (`GetApiRouteClaimFinancials`) to compare proposed refund amounts against previously paid costs per cost category.

## Process flow

```mermaid
flowchart TD
	Start([Adjuster selects a refund type<br>and reviews candidate payments]) --> Which{Refund type?}
	Which -->|Medical/HWR/Other/<br>SAIF check/Worker travel| Load[Load posted transactions;<br>build refund line items]
	Load --> Rule1{Amount, remark,<br>paid-cost rules pass?}
	Rule1 -->|Error| Blocked([Broken rule; save blocked])
	Rule1 -->|Warning only| Confirm{User confirms<br>override?}
	Confirm -->|No| Blocked
	Rule1 -->|Pass| BuildTx[Build negated<br>financial transaction]
	Confirm -->|Yes| BuildTx
	BuildTx --> SaveTx[Save financial transaction<br>via Financial transaction management]
	SaveTx --> TxOk{Save succeeded?}
	TxOk -->|No| Fail([Save returns False])
	TxOk -->|Yes| Adj[Save bill payment adjustments]
	Adj --> Done([Refund complete])
	Which -->|Non-disabling| NdrCall[Build RecoveryRequest;<br>call claim-financial API]
	NdrCall --> NdrCap{Over statutory<br>reimbursement cap?}
	NdrCap -->|Yes| Flagged([Claim flagged for review])
	NdrCap -->|No| NdrDone([Recovery request submitted])
	Which -->|Third-party| TpCall[Build ActivityRequest;<br>check negative paid costs via API]
	TpCall --> TpDone([Activity/recovery requests submitted])
```

## Failure and edge-case behavior

- `clsRefundAdjustment.Save` stops at the first failed transaction save and returns `False` without rolling back transactions already committed earlier in the loop — a multi-line refund can partially post.
- The paid-costs and own-motion warnings can be overridden by the user; only the zero-amount and remark-length rules are hard errors ([BR-CP-023](../business-rules.md#br-cp-023-paid-cost-and-own-motion-checks-on-refunds-are-overridable-warnings)).
- Non-disabling and third-party refunds never create a `CL_FINANCIAL_TRANS` row; a report or query that only looks at that table will miss these two refund types entirely.
- `GetPostedTransactions` on the base class caches its last claim ID/issue date result in `Static` locals, so refund construction against a different claim or issue date in the same process correctly triggers a re-fetch, but the cache is process-wide, not per-instance.
- The `ND` and third-party refund API integrations depend on external claim-data/financial/activity services outside this repository; response-failure handling for those calls was not traced in this pass and should be treated as unconfirmed.*

## 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\Adjustments\clsAdjustmentBase.vb`
- `Business\SAIF.Business.Claim.Payment\Adjustments\Refunds\clsRefundAdjustment.vb`
- `Business\SAIF.Business.Claim.Payment\Adjustments\Refunds\clsMedicalRefund.vb`
- `Business\SAIF.Business.Claim.Payment\Adjustments\Refunds\clsHwrRefund.vb`
- `Business\SAIF.Business.Claim.Payment\Adjustments\Refunds\clsOtherRefund.vb`
- `Business\SAIF.Business.Claim.Payment\Adjustments\Refunds\clsSaifCheckRefund.vb`
- `Business\SAIF.Business.Claim.Payment\Adjustments\Refunds\clsWorkerTravelRefund.vb`
- `Business\SAIF.Business.Claim.Payment\Adjustments\Refunds\clsThirdPartyRefund.vb`
- `Business\SAIF.Business.Claim.Payment\Adjustments\Refunds\clsNdRefund.vb`
