Case Study 2: Real-Time Wire Transfer Authorization
Background
Continental Commerce Bank (CCB) is a mid-tier commercial bank headquartered in Charlotte, North Carolina, with $18.5 billion in total assets and a strong focus on corporate banking services. CCB's commercial clients include regional manufacturers, healthcare systems, real estate developers, and import/export businesses. Wire transfers are the primary mechanism through which these clients move large sums of money -- paying suppliers, funding construction draws, settling international trade obligations, and managing corporate treasury operations.
On an average business day, CCB processes approximately 4,200 wire transfer requests totaling $1.8 billion. Each wire must be authorized within minutes of receipt, and the Fedwire window -- the hours during which the Federal Reserve accepts domestic wire transfers -- closes at 6:30 p.m. Eastern Time. Wires received after the cutoff are held until the next business day, which can create significant problems for time-sensitive commercial transactions.
The authorization process is not simply a matter of verifying the sender's balance. Every wire transfer must be screened against the Office of Foreign Assets Control (OFAC) Specially Designated Nationals (SDN) list to comply with U.S. economic sanctions. The wire must be validated against the originator's wire agreement limits. The beneficiary's bank must be identified and its routing number verified. And the transaction must be formatted into a Fedwire message that conforms to the Federal Reserve's specifications.
A failure in any part of this process can have severe consequences. A wire sent to a sanctioned party exposes the bank to multi-million-dollar penalties and potential criminal prosecution. A wire that exceeds the customer's authorized limits creates credit risk. An improperly formatted Fedwire message will be rejected by the Federal Reserve, delaying settlement and potentially causing the customer's transaction to fail.
This case study examines the design and implementation of CCB's wire transfer authorization system, a COBOL program that validates wire requests, screens against OFAC sanctions, verifies balances, and formats Fedwire messages -- all within a processing window of seconds per transaction.
Problem Statement
CCB's existing wire transfer system processes wires in batch at four scheduled times during the day: 9:00 a.m., 12:00 p.m., 3:00 p.m., and 5:00 p.m. This batched approach causes two significant problems:
-
Delay: A wire request submitted at 9:05 a.m. waits nearly three hours until the noon batch. For time-sensitive transactions -- real estate closings, securities settlements, international trade payments -- this delay is unacceptable. CCB has lost several large commercial clients to competitors that offer near-real-time wire processing.
-
OFAC risk: The four-hour gap between OFAC screening batches means that a wire could be processed hours after the SDN list has been updated. If a sanctioned party is added to the list at 10:00 a.m., wires to that party submitted before noon would not be caught until the noon screening, by which time the funds may have already been committed.
The technology team has been tasked with building a new wire transfer authorization program that processes each wire individually as it is received, performing OFAC screening, balance verification, limit checking, and Fedwire message formatting in a single pass. The program must be callable from the online CICS system (for wires initiated through the banking portal) and from the batch system (for wires submitted through file-based interfaces).
System Design
The wire authorization program, WIREAUTH, is designed as a stateless COBOL subprogram that receives a wire transfer request through a parameter block, processes it through a series of validation and screening steps, and returns an authorization result. This design allows the same program to be called from CICS (real-time) or from a batch driver program.
The processing steps are:
- Request Validation: Verify all required fields are present and properly formatted.
- Originator Verification: Read the customer's wire agreement and verify limits.
- Balance Verification: Confirm sufficient available funds in the originator's account.
- OFAC Screening: Screen beneficiary name, beneficiary bank, and intermediary bank against the SDN list.
- Routing Number Validation: Verify the beneficiary bank's ABA routing number using the weighted modulus 10 algorithm.
- Fedwire Message Formatting: Format the authorized wire into a Fedwire-compatible message.
- Result Return: Return the authorization decision (approved, declined, held for review) with appropriate reason codes.
Complete COBOL Implementation
IDENTIFICATION DIVISION.
PROGRAM-ID. WIREAUTH.
*================================================================*
* WIRE TRANSFER AUTHORIZATION SYSTEM *
* CONTINENTAL COMMERCE BANK *
* *
* THIS SUBPROGRAM VALIDATES AND AUTHORIZES DOMESTIC AND *
* INTERNATIONAL WIRE TRANSFER REQUESTS. IT PERFORMS OFAC *
* SCREENING, BALANCE VERIFICATION, LIMIT CHECKING, AND *
* FEDWIRE MESSAGE FORMATTING. *
* *
* CALLABLE FROM CICS (ONLINE) OR BATCH DRIVER PROGRAM. *
*================================================================*
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT ACCT-MASTER-FILE
ASSIGN TO ACCTMAST
ORGANIZATION IS INDEXED
ACCESS MODE IS RANDOM
RECORD KEY IS AM-ACCOUNT-NUMBER
FILE STATUS IS WS-ACCT-FS.
SELECT WIRE-AGREEMENT-FILE
ASSIGN TO WIREAGMT
ORGANIZATION IS INDEXED
ACCESS MODE IS RANDOM
RECORD KEY IS WA-CUSTOMER-ID
FILE STATUS IS WS-AGMT-FS.
SELECT OFAC-SDN-FILE
ASSIGN TO OFACSDN
ORGANIZATION IS INDEXED
ACCESS MODE IS DYNAMIC
RECORD KEY IS SDN-ENTRY-ID
FILE STATUS IS WS-SDN-FS.
SELECT WIRE-LOG-FILE
ASSIGN TO WIRELOG
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-LOG-FS.
DATA DIVISION.
FILE SECTION.
FD ACCT-MASTER-FILE
RECORD CONTAINS 300 CHARACTERS.
01 ACCT-MASTER-RECORD.
05 AM-ACCOUNT-NUMBER PIC X(12).
05 AM-CIF-NUMBER PIC X(10).
05 AM-NAME-LAST PIC X(25).
05 AM-NAME-FIRST PIC X(20).
05 AM-FILLER-1 PIC X(11).
05 AM-ACCOUNT-TYPE PIC X(03).
05 AM-FILLER-2 PIC X(03).
05 AM-STATUS-CODE PIC X(01).
88 AM-ACTIVE VALUE 'A'.
88 AM-CLOSED VALUE 'C'.
88 AM-FROZEN VALUE 'F'.
05 AM-FILLER-3 PIC X(24).
05 AM-LEDGER-BAL PIC S9(11)V99
USAGE COMP-3.
05 AM-AVAIL-BAL PIC S9(11)V99
USAGE COMP-3.
05 AM-HOLD-AMT PIC S9(11)V99
USAGE COMP-3.
05 AM-FILLER-4 PIC X(183).
FD WIRE-AGREEMENT-FILE
RECORD CONTAINS 200 CHARACTERS.
01 WIRE-AGREEMENT-RECORD.
05 WA-CUSTOMER-ID PIC X(10).
05 WA-AGREEMENT-STATUS PIC X(01).
88 WA-ACTIVE VALUE 'A'.
88 WA-SUSPENDED VALUE 'S'.
88 WA-TERMINATED VALUE 'T'.
05 WA-AGREEMENT-DATE PIC 9(08).
05 WA-DAILY-LIMIT PIC S9(13)V99
USAGE COMP-3.
05 WA-PER-WIRE-LIMIT PIC S9(13)V99
USAGE COMP-3.
05 WA-DAILY-USED PIC S9(13)V99
USAGE COMP-3.
05 WA-INTL-ALLOWED PIC X(01).
88 WA-INTL-YES VALUE 'Y'.
88 WA-INTL-NO VALUE 'N'.
05 WA-DUAL-APPROVAL-LIMIT PIC S9(13)V99
USAGE COMP-3.
05 WA-LAST-WIRE-DATE PIC 9(08).
05 WA-WIRE-COUNT-TODAY PIC 9(04).
05 WA-FILLER PIC X(115).
FD OFAC-SDN-FILE
RECORD CONTAINS 250 CHARACTERS.
01 SDN-RECORD.
05 SDN-ENTRY-ID PIC 9(08).
05 SDN-NAME PIC X(100).
05 SDN-TYPE PIC X(12).
05 SDN-PROGRAM PIC X(50).
05 SDN-COUNTRY PIC X(30).
05 SDN-REMARKS PIC X(50).
FD WIRE-LOG-FILE
RECORD CONTAINS 500 CHARACTERS.
01 WIRE-LOG-RECORD PIC X(500).
*================================================================*
WORKING-STORAGE SECTION.
*================================================================*
01 WS-FILE-STATUSES.
05 WS-ACCT-FS PIC XX.
05 WS-AGMT-FS PIC XX.
05 WS-SDN-FS PIC XX.
05 WS-LOG-FS PIC XX.
01 WS-CURRENT-DATE-DATA.
05 WS-CURRENT-DATE PIC 9(08).
05 WS-CURRENT-TIME PIC 9(08).
01 WS-FILES-OPEN-FLAG PIC X(01) VALUE 'N'.
88 WS-FILES-ARE-OPEN VALUE 'Y'.
*---------------------------------------------------------------*
* OFAC SCREENING WORK FIELDS *
*---------------------------------------------------------------*
01 WS-OFAC-WORK.
05 WS-OFAC-HIT-FLAG PIC X(01) VALUE 'N'.
88 WS-OFAC-HIT VALUE 'Y'.
88 WS-OFAC-CLEAR VALUE 'N'.
05 WS-OFAC-MATCH-SCORE PIC 9(03) VALUE ZEROS.
05 WS-OFAC-MATCH-NAME PIC X(100) VALUE SPACES.
05 WS-OFAC-MATCH-ID PIC 9(08) VALUE ZEROS.
05 WS-SEARCH-NAME PIC X(100) VALUE SPACES.
05 WS-SDN-EOF PIC X(01) VALUE 'N'.
88 WS-SDN-AT-END VALUE 'Y'.
*---------------------------------------------------------------*
* NAME MATCHING WORK FIELDS *
*---------------------------------------------------------------*
01 WS-NAME-MATCH-WORK.
05 WS-SEARCH-WORDS.
10 WS-SEARCH-WORD PIC X(30)
OCCURS 10 TIMES.
05 WS-SEARCH-WORD-COUNT PIC 9(02) VALUE ZEROS.
05 WS-SDN-WORDS.
10 WS-SDN-WORD PIC X(30)
OCCURS 10 TIMES.
05 WS-SDN-WORD-COUNT PIC 9(02) VALUE ZEROS.
05 WS-MATCH-COUNT PIC 9(02) VALUE ZEROS.
05 WS-WORD-IDX-1 PIC 9(02) VALUE ZEROS.
05 WS-WORD-IDX-2 PIC 9(02) VALUE ZEROS.
05 WS-MATCH-PCT PIC 9(03) VALUE ZEROS.
*---------------------------------------------------------------*
* ROUTING NUMBER VALIDATION *
*---------------------------------------------------------------*
01 WS-ROUTING-WORK.
05 WS-RTN-DIGITS.
10 WS-RTN-D PIC 9(01) OCCURS 9 TIMES.
05 WS-RTN-WEIGHTS.
10 FILLER PIC 9(01) VALUE 3.
10 FILLER PIC 9(01) VALUE 7.
10 FILLER PIC 9(01) VALUE 1.
10 FILLER PIC 9(01) VALUE 3.
10 FILLER PIC 9(01) VALUE 7.
10 FILLER PIC 9(01) VALUE 1.
10 FILLER PIC 9(01) VALUE 3.
10 FILLER PIC 9(01) VALUE 7.
10 FILLER PIC 9(01) VALUE 1.
05 WS-RTN-WEIGHT-ARRAY REDEFINES WS-RTN-WEIGHTS.
10 WS-RTN-W PIC 9(01) OCCURS 9 TIMES.
05 WS-RTN-SUM PIC 9(04) VALUE ZEROS.
05 WS-RTN-IDX PIC 9(02) VALUE ZEROS.
*---------------------------------------------------------------*
* FEDWIRE MESSAGE FIELDS *
*---------------------------------------------------------------*
01 WS-FEDWIRE-MSG.
05 WS-FW-MSG-TYPE PIC X(04) VALUE '1000'.
05 WS-FW-IMAD PIC X(22).
05 WS-FW-AMOUNT PIC 9(12).
05 WS-FW-SENDER-ABA PIC X(09).
05 WS-FW-SENDER-NAME PIC X(35).
05 WS-FW-RECEIVER-ABA PIC X(09).
05 WS-FW-RECEIVER-NAME PIC X(35).
05 WS-FW-ORIGINATOR-NAME PIC X(35).
05 WS-FW-ORIGINATOR-ACCT PIC X(34).
05 WS-FW-BENEFICIARY-NAME PIC X(35).
05 WS-FW-BENEFICIARY-ACCT PIC X(34).
05 WS-FW-REFERENCE PIC X(16).
05 WS-FW-PURPOSE PIC X(40).
05 WS-FW-FILLER PIC X(180).
*---------------------------------------------------------------*
* WIRE TRANSFER FEE SCHEDULE *
*---------------------------------------------------------------*
01 WS-FEE-AMOUNT PIC S9(05)V99
USAGE COMP-3 VALUE ZEROS.
*---------------------------------------------------------------*
* AUDIT LOG RECORD *
*---------------------------------------------------------------*
01 WS-AUDIT-LOG.
05 WS-AL-TIMESTAMP PIC X(26).
05 WS-AL-WIRE-REF PIC X(16).
05 WS-AL-ORIGINATOR-ID PIC X(10).
05 WS-AL-ORIG-ACCT PIC X(12).
05 WS-AL-BENE-NAME PIC X(35).
05 WS-AL-BENE-ACCT PIC X(34).
05 WS-AL-BENE-BANK-ABA PIC X(09).
05 WS-AL-AMOUNT PIC S9(13)V99 COMP-3.
05 WS-AL-FEE PIC S9(05)V99 COMP-3.
05 WS-AL-DECISION PIC X(08).
05 WS-AL-REASON-CODE PIC X(04).
05 WS-AL-REASON-TEXT PIC X(50).
05 WS-AL-OFAC-RESULT PIC X(05).
05 WS-AL-OFAC-MATCH-ID PIC 9(08).
05 WS-AL-OFAC-SCORE PIC 9(03).
05 WS-AL-USER-ID PIC X(08).
05 WS-AL-FILLER PIC X(264).
*================================================================*
LINKAGE SECTION.
*================================================================*
*---------------------------------------------------------------*
* WIRE REQUEST PARAMETER BLOCK (INPUT) *
*---------------------------------------------------------------*
01 LS-WIRE-REQUEST.
05 LS-WR-CUSTOMER-ID PIC X(10).
05 LS-WR-ORIG-ACCT PIC X(12).
05 LS-WR-ORIG-NAME PIC X(35).
05 LS-WR-BENE-NAME PIC X(35).
05 LS-WR-BENE-ACCT PIC X(34).
05 LS-WR-BENE-BANK-NAME PIC X(35).
05 LS-WR-BENE-BANK-ABA PIC X(09).
05 LS-WR-INTERMED-BANK PIC X(35).
05 LS-WR-INTERMED-ABA PIC X(09).
05 LS-WR-AMOUNT PIC S9(13)V99
USAGE COMP-3.
05 LS-WR-CURRENCY PIC X(03).
05 LS-WR-PURPOSE PIC X(40).
05 LS-WR-REFERENCE PIC X(16).
05 LS-WR-WIRE-TYPE PIC X(01).
88 LS-WR-DOMESTIC VALUE 'D'.
88 LS-WR-INTERNATIONAL VALUE 'I'.
05 LS-WR-USER-ID PIC X(08).
05 LS-WR-FILLER PIC X(50).
*---------------------------------------------------------------*
* WIRE RESPONSE PARAMETER BLOCK (OUTPUT) *
*---------------------------------------------------------------*
01 LS-WIRE-RESPONSE.
05 LS-WS-DECISION PIC X(08).
88 LS-WS-APPROVED VALUE 'APPROVED'.
88 LS-WS-DECLINED VALUE 'DECLINED'.
88 LS-WS-HELD VALUE 'HELD '.
05 LS-WS-AUTH-CODE PIC X(06).
05 LS-WS-REASON-CODE PIC X(04).
05 LS-WS-REASON-TEXT PIC X(50).
05 LS-WS-FEE-AMOUNT PIC S9(05)V99
USAGE COMP-3.
05 LS-WS-WIRE-REF PIC X(16).
05 LS-WS-OFAC-RESULT PIC X(05).
88 LS-WS-OFAC-PASS VALUE 'CLEAR'.
88 LS-WS-OFAC-FAIL VALUE 'MATCH'.
88 LS-WS-OFAC-REVIEW VALUE 'REVEW'.
05 LS-WS-FILLER PIC X(100).
PROCEDURE DIVISION USING LS-WIRE-REQUEST
LS-WIRE-RESPONSE.
*================================================================*
0000-MAIN-CONTROL.
*================================================================*
PERFORM 0100-INITIALIZE
PERFORM 1000-VALIDATE-REQUEST
IF LS-WS-DECISION NOT = 'DECLINED'
PERFORM 2000-VERIFY-ORIGINATOR
END-IF
IF LS-WS-DECISION NOT = 'DECLINED'
PERFORM 3000-VERIFY-BALANCE
END-IF
IF LS-WS-DECISION NOT = 'DECLINED'
PERFORM 4000-OFAC-SCREENING
END-IF
IF LS-WS-DECISION NOT = 'DECLINED'
AND LS-WS-DECISION NOT = 'HELD '
PERFORM 5000-VALIDATE-ROUTING
END-IF
IF LS-WS-DECISION = 'APPROVED'
PERFORM 6000-FORMAT-FEDWIRE
PERFORM 7000-DEBIT-ORIGINATOR
END-IF
PERFORM 8000-WRITE-AUDIT-LOG
PERFORM 9000-CLEANUP
GOBACK
.
*================================================================*
0100-INITIALIZE.
*================================================================*
MOVE FUNCTION CURRENT-DATE TO WS-CURRENT-DATE-DATA
IF NOT WS-FILES-ARE-OPEN
OPEN I-O ACCT-MASTER-FILE
OPEN INPUT WIRE-AGREEMENT-FILE
OPEN INPUT OFAC-SDN-FILE
OPEN EXTEND WIRE-LOG-FILE
MOVE 'Y' TO WS-FILES-OPEN-FLAG
END-IF
* INITIALIZE RESPONSE
INITIALIZE LS-WIRE-RESPONSE
SET LS-WS-APPROVED TO TRUE
MOVE SPACES TO LS-WS-AUTH-CODE
MOVE 'CLEAR' TO LS-WS-OFAC-RESULT
* GENERATE WIRE REFERENCE NUMBER
STRING WS-CURRENT-DATE WS-CURRENT-TIME
DELIMITED BY SIZE
INTO LS-WS-WIRE-REF
.
*================================================================*
1000-VALIDATE-REQUEST.
*================================================================*
* VERIFY ALL REQUIRED FIELDS ARE PRESENT
IF LS-WR-CUSTOMER-ID = SPACES
PERFORM 1900-DECLINE-WIRE
MOVE 'W001' TO LS-WS-REASON-CODE
MOVE 'MISSING CUSTOMER ID'
TO LS-WS-REASON-TEXT
GO TO 1000-EXIT
END-IF
IF LS-WR-ORIG-ACCT = SPACES
PERFORM 1900-DECLINE-WIRE
MOVE 'W002' TO LS-WS-REASON-CODE
MOVE 'MISSING ORIGINATOR ACCOUNT'
TO LS-WS-REASON-TEXT
GO TO 1000-EXIT
END-IF
IF LS-WR-BENE-NAME = SPACES
PERFORM 1900-DECLINE-WIRE
MOVE 'W003' TO LS-WS-REASON-CODE
MOVE 'MISSING BENEFICIARY NAME'
TO LS-WS-REASON-TEXT
GO TO 1000-EXIT
END-IF
IF LS-WR-BENE-BANK-ABA = SPACES
PERFORM 1900-DECLINE-WIRE
MOVE 'W004' TO LS-WS-REASON-CODE
MOVE 'MISSING BENEFICIARY BANK ABA'
TO LS-WS-REASON-TEXT
GO TO 1000-EXIT
END-IF
IF LS-WR-AMOUNT <= ZEROS
PERFORM 1900-DECLINE-WIRE
MOVE 'W005' TO LS-WS-REASON-CODE
MOVE 'INVALID WIRE AMOUNT'
TO LS-WS-REASON-TEXT
GO TO 1000-EXIT
END-IF
IF LS-WR-CURRENCY = SPACES
MOVE 'USD' TO LS-WR-CURRENCY
END-IF
1000-EXIT.
EXIT
.
*================================================================*
1900-DECLINE-WIRE.
*================================================================*
SET LS-WS-DECLINED TO TRUE
.
*================================================================*
2000-VERIFY-ORIGINATOR.
*================================================================*
* READ THE CUSTOMER'S WIRE AGREEMENT
MOVE LS-WR-CUSTOMER-ID TO WA-CUSTOMER-ID
READ WIRE-AGREEMENT-FILE
INVALID KEY
PERFORM 1900-DECLINE-WIRE
MOVE 'W010' TO LS-WS-REASON-CODE
MOVE 'NO WIRE AGREEMENT ON FILE'
TO LS-WS-REASON-TEXT
GO TO 2000-EXIT
END-READ
* CHECK AGREEMENT STATUS
IF NOT WA-ACTIVE
PERFORM 1900-DECLINE-WIRE
MOVE 'W011' TO LS-WS-REASON-CODE
MOVE 'WIRE AGREEMENT NOT ACTIVE'
TO LS-WS-REASON-TEXT
GO TO 2000-EXIT
END-IF
* CHECK PER-WIRE LIMIT
IF LS-WR-AMOUNT > WA-PER-WIRE-LIMIT
PERFORM 1900-DECLINE-WIRE
MOVE 'W012' TO LS-WS-REASON-CODE
MOVE 'EXCEEDS PER-WIRE LIMIT'
TO LS-WS-REASON-TEXT
GO TO 2000-EXIT
END-IF
* CHECK DAILY LIMIT
COMPUTE WS-FEE-AMOUNT =
WA-DAILY-USED + LS-WR-AMOUNT
IF WS-FEE-AMOUNT > WA-DAILY-LIMIT
PERFORM 1900-DECLINE-WIRE
MOVE 'W013' TO LS-WS-REASON-CODE
MOVE 'EXCEEDS DAILY WIRE LIMIT'
TO LS-WS-REASON-TEXT
GO TO 2000-EXIT
END-IF
* CHECK INTERNATIONAL AUTHORIZATION
IF LS-WR-INTERNATIONAL AND WA-INTL-NO
PERFORM 1900-DECLINE-WIRE
MOVE 'W014' TO LS-WS-REASON-CODE
MOVE 'INTERNATIONAL WIRES NOT AUTHORIZED'
TO LS-WS-REASON-TEXT
GO TO 2000-EXIT
END-IF
* CHECK DUAL APPROVAL REQUIREMENT
IF LS-WR-AMOUNT > WA-DUAL-APPROVAL-LIMIT
SET LS-WS-HELD TO TRUE
MOVE 'W015' TO LS-WS-REASON-CODE
MOVE 'REQUIRES DUAL APPROVAL'
TO LS-WS-REASON-TEXT
END-IF
2000-EXIT.
EXIT
.
*================================================================*
3000-VERIFY-BALANCE.
*================================================================*
* CALCULATE THE WIRE FEE
PERFORM 3100-CALCULATE-FEE
* READ THE ORIGINATOR'S ACCOUNT
MOVE LS-WR-ORIG-ACCT TO AM-ACCOUNT-NUMBER
READ ACCT-MASTER-FILE
INVALID KEY
PERFORM 1900-DECLINE-WIRE
MOVE 'W020' TO LS-WS-REASON-CODE
MOVE 'ORIGINATOR ACCOUNT NOT FOUND'
TO LS-WS-REASON-TEXT
GO TO 3000-EXIT
END-READ
* CHECK ACCOUNT STATUS
IF AM-CLOSED
PERFORM 1900-DECLINE-WIRE
MOVE 'W021' TO LS-WS-REASON-CODE
MOVE 'ORIGINATOR ACCOUNT CLOSED'
TO LS-WS-REASON-TEXT
GO TO 3000-EXIT
END-IF
IF AM-FROZEN
PERFORM 1900-DECLINE-WIRE
MOVE 'W022' TO LS-WS-REASON-CODE
MOVE 'ORIGINATOR ACCOUNT FROZEN'
TO LS-WS-REASON-TEXT
GO TO 3000-EXIT
END-IF
* VERIFY SUFFICIENT FUNDS (WIRE AMOUNT + FEE)
IF (LS-WR-AMOUNT + WS-FEE-AMOUNT) > AM-AVAIL-BAL
PERFORM 1900-DECLINE-WIRE
MOVE 'W023' TO LS-WS-REASON-CODE
MOVE 'INSUFFICIENT FUNDS FOR WIRE + FEE'
TO LS-WS-REASON-TEXT
GO TO 3000-EXIT
END-IF
MOVE WS-FEE-AMOUNT TO LS-WS-FEE-AMOUNT
3000-EXIT.
EXIT
.
*================================================================*
3100-CALCULATE-FEE.
*================================================================*
EVALUATE TRUE
WHEN LS-WR-DOMESTIC
IF LS-WR-AMOUNT <= 5000.00
MOVE 25.00 TO WS-FEE-AMOUNT
ELSE IF LS-WR-AMOUNT <= 50000.00
MOVE 30.00 TO WS-FEE-AMOUNT
ELSE
MOVE 35.00 TO WS-FEE-AMOUNT
END-IF
WHEN LS-WR-INTERNATIONAL
IF LS-WR-AMOUNT <= 5000.00
MOVE 45.00 TO WS-FEE-AMOUNT
ELSE IF LS-WR-AMOUNT <= 50000.00
MOVE 50.00 TO WS-FEE-AMOUNT
ELSE
MOVE 65.00 TO WS-FEE-AMOUNT
END-IF
END-EVALUATE
.
*================================================================*
4000-OFAC-SCREENING.
*================================================================*
* SCREEN BENEFICIARY NAME AGAINST SDN LIST
MOVE LS-WR-BENE-NAME TO WS-SEARCH-NAME
PERFORM 4100-SCREEN-NAME
IF WS-OFAC-HIT
GO TO 4000-PROCESS-HIT
END-IF
* SCREEN BENEFICIARY BANK NAME
MOVE LS-WR-BENE-BANK-NAME TO WS-SEARCH-NAME
PERFORM 4100-SCREEN-NAME
IF WS-OFAC-HIT
GO TO 4000-PROCESS-HIT
END-IF
* SCREEN INTERMEDIARY BANK IF PRESENT
IF LS-WR-INTERMED-BANK NOT = SPACES
MOVE LS-WR-INTERMED-BANK TO WS-SEARCH-NAME
PERFORM 4100-SCREEN-NAME
IF WS-OFAC-HIT
GO TO 4000-PROCESS-HIT
END-IF
END-IF
* ALL SCREENS CLEAR
MOVE 'CLEAR' TO LS-WS-OFAC-RESULT
GO TO 4000-EXIT
4000-PROCESS-HIT.
* OFAC HIT DETECTED -- HOLD FOR COMPLIANCE REVIEW
SET LS-WS-HELD TO TRUE
IF WS-OFAC-MATCH-SCORE >= 90
MOVE 'MATCH' TO LS-WS-OFAC-RESULT
ELSE
MOVE 'REVEW' TO LS-WS-OFAC-RESULT
END-IF
MOVE 'W030' TO LS-WS-REASON-CODE
STRING 'OFAC SCREENING HIT: '
WS-OFAC-MATCH-NAME(1:30)
DELIMITED BY SIZE
INTO LS-WS-REASON-TEXT
4000-EXIT.
EXIT
.
*================================================================*
4100-SCREEN-NAME.
*================================================================*
* FUZZY NAME MATCHING AGAINST OFAC SDN LIST *
* COMPARES EACH WORD OF THE SEARCH NAME AGAINST EACH *
* WORD OF EACH SDN ENTRY. CALCULATES A MATCH PERCENTAGE. *
* THRESHOLD: 80% MATCH TRIGGERS A HIT. *
*================================================================*
SET WS-OFAC-CLEAR TO TRUE
MOVE ZEROS TO WS-OFAC-MATCH-SCORE
MOVE SPACES TO WS-OFAC-MATCH-NAME
* PARSE SEARCH NAME INTO WORDS
PERFORM 4200-PARSE-SEARCH-NAME
* SCAN SDN FILE SEQUENTIALLY
MOVE LOW-VALUES TO SDN-ENTRY-ID
START OFAC-SDN-FILE KEY >= SDN-ENTRY-ID
INVALID KEY
GO TO 4100-EXIT
END-START
MOVE 'N' TO WS-SDN-EOF
PERFORM UNTIL WS-SDN-AT-END
READ OFAC-SDN-FILE NEXT
AT END
SET WS-SDN-AT-END TO TRUE
END-READ
IF NOT WS-SDN-AT-END
PERFORM 4300-COMPARE-NAMES
IF WS-MATCH-PCT >= 80
SET WS-OFAC-HIT TO TRUE
IF WS-MATCH-PCT > WS-OFAC-MATCH-SCORE
MOVE WS-MATCH-PCT
TO WS-OFAC-MATCH-SCORE
MOVE SDN-NAME
TO WS-OFAC-MATCH-NAME
MOVE SDN-ENTRY-ID
TO WS-OFAC-MATCH-ID
END-IF
END-IF
END-IF
END-PERFORM
4100-EXIT.
EXIT
.
*================================================================*
4200-PARSE-SEARCH-NAME.
*================================================================*
* SPLIT THE SEARCH NAME INTO INDIVIDUAL WORDS
MOVE ZEROS TO WS-SEARCH-WORD-COUNT
MOVE SPACES TO WS-SEARCH-WORDS
UNSTRING FUNCTION UPPER-CASE(WS-SEARCH-NAME)
DELIMITED BY ALL SPACES
INTO WS-SEARCH-WORD(1)
WS-SEARCH-WORD(2)
WS-SEARCH-WORD(3)
WS-SEARCH-WORD(4)
WS-SEARCH-WORD(5)
WS-SEARCH-WORD(6)
WS-SEARCH-WORD(7)
WS-SEARCH-WORD(8)
WS-SEARCH-WORD(9)
WS-SEARCH-WORD(10)
TALLYING IN WS-SEARCH-WORD-COUNT
END-UNSTRING
.
*================================================================*
4300-COMPARE-NAMES.
*================================================================*
* COMPARE WORDS OF SEARCH NAME AGAINST SDN NAME WORDS
MOVE ZEROS TO WS-SDN-WORD-COUNT
MOVE SPACES TO WS-SDN-WORDS
UNSTRING FUNCTION UPPER-CASE(SDN-NAME)
DELIMITED BY ALL SPACES
INTO WS-SDN-WORD(1)
WS-SDN-WORD(2)
WS-SDN-WORD(3)
WS-SDN-WORD(4)
WS-SDN-WORD(5)
WS-SDN-WORD(6)
WS-SDN-WORD(7)
WS-SDN-WORD(8)
WS-SDN-WORD(9)
WS-SDN-WORD(10)
TALLYING IN WS-SDN-WORD-COUNT
END-UNSTRING
MOVE ZEROS TO WS-MATCH-COUNT
PERFORM VARYING WS-WORD-IDX-1 FROM 1 BY 1
UNTIL WS-WORD-IDX-1 > WS-SEARCH-WORD-COUNT
PERFORM VARYING WS-WORD-IDX-2 FROM 1 BY 1
UNTIL WS-WORD-IDX-2 > WS-SDN-WORD-COUNT
IF WS-SEARCH-WORD(WS-WORD-IDX-1) =
WS-SDN-WORD(WS-WORD-IDX-2)
AND WS-SEARCH-WORD(WS-WORD-IDX-1)
NOT = SPACES
ADD 1 TO WS-MATCH-COUNT
END-IF
END-PERFORM
END-PERFORM
* CALCULATE MATCH PERCENTAGE BASED ON LONGER NAME
IF WS-SEARCH-WORD-COUNT >= WS-SDN-WORD-COUNT
IF WS-SEARCH-WORD-COUNT > ZEROS
COMPUTE WS-MATCH-PCT =
(WS-MATCH-COUNT * 100) /
WS-SEARCH-WORD-COUNT
ELSE
MOVE ZEROS TO WS-MATCH-PCT
END-IF
ELSE
IF WS-SDN-WORD-COUNT > ZEROS
COMPUTE WS-MATCH-PCT =
(WS-MATCH-COUNT * 100) /
WS-SDN-WORD-COUNT
ELSE
MOVE ZEROS TO WS-MATCH-PCT
END-IF
END-IF
.
*================================================================*
5000-VALIDATE-ROUTING.
*================================================================*
* VALIDATE BENEFICIARY BANK ABA ROUTING NUMBER
* USING WEIGHTED MODULUS 10 ALGORITHM
* WEIGHTS: 3, 7, 1, 3, 7, 1, 3, 7, 1
MOVE LS-WR-BENE-BANK-ABA TO WS-RTN-DIGITS
MOVE ZEROS TO WS-RTN-SUM
PERFORM VARYING WS-RTN-IDX FROM 1 BY 1
UNTIL WS-RTN-IDX > 9
COMPUTE WS-RTN-SUM = WS-RTN-SUM +
(WS-RTN-D(WS-RTN-IDX) *
WS-RTN-W(WS-RTN-IDX))
END-PERFORM
IF FUNCTION MOD(WS-RTN-SUM 10) NOT = 0
PERFORM 1900-DECLINE-WIRE
MOVE 'W040' TO LS-WS-REASON-CODE
MOVE 'INVALID BENEFICIARY BANK ROUTING NUM'
TO LS-WS-REASON-TEXT
END-IF
.
*================================================================*
6000-FORMAT-FEDWIRE.
*================================================================*
* BUILD THE FEDWIRE FUNDS TRANSFER MESSAGE
INITIALIZE WS-FEDWIRE-MSG
MOVE '1000' TO WS-FW-MSG-TYPE
MOVE LS-WS-WIRE-REF TO WS-FW-IMAD
MOVE LS-WR-AMOUNT TO WS-FW-AMOUNT
MOVE '041215032' TO WS-FW-SENDER-ABA
MOVE 'CONTINENTAL COMMERCE BANK'
TO WS-FW-SENDER-NAME
MOVE LS-WR-BENE-BANK-ABA
TO WS-FW-RECEIVER-ABA
MOVE LS-WR-BENE-BANK-NAME
TO WS-FW-RECEIVER-NAME
MOVE LS-WR-ORIG-NAME TO WS-FW-ORIGINATOR-NAME
MOVE LS-WR-ORIG-ACCT TO WS-FW-ORIGINATOR-ACCT
MOVE LS-WR-BENE-NAME TO WS-FW-BENEFICIARY-NAME
MOVE LS-WR-BENE-ACCT TO WS-FW-BENEFICIARY-ACCT
MOVE LS-WR-REFERENCE TO WS-FW-REFERENCE
MOVE LS-WR-PURPOSE TO WS-FW-PURPOSE
* GENERATE AUTHORIZATION CODE
STRING WS-CURRENT-TIME(1:6)
DELIMITED BY SIZE
INTO LS-WS-AUTH-CODE
.
*================================================================*
7000-DEBIT-ORIGINATOR.
*================================================================*
* DEBIT THE WIRE AMOUNT AND FEE FROM THE ORIGINATOR'S ACCOUNT
MOVE LS-WR-ORIG-ACCT TO AM-ACCOUNT-NUMBER
READ ACCT-MASTER-FILE
INVALID KEY
DISPLAY 'CRITICAL: ACCOUNT VANISHED DURING '
'PROCESSING: ' AM-ACCOUNT-NUMBER
PERFORM 1900-DECLINE-WIRE
MOVE 'W099' TO LS-WS-REASON-CODE
MOVE 'INTERNAL ERROR - CONTACT SUPPORT'
TO LS-WS-REASON-TEXT
GO TO 7000-EXIT
END-READ
* DEBIT WIRE AMOUNT
SUBTRACT LS-WR-AMOUNT FROM AM-LEDGER-BAL
SUBTRACT LS-WR-AMOUNT FROM AM-AVAIL-BAL
* DEBIT FEE
SUBTRACT WS-FEE-AMOUNT FROM AM-LEDGER-BAL
SUBTRACT WS-FEE-AMOUNT FROM AM-AVAIL-BAL
MOVE WS-CURRENT-DATE TO AM-LAST-ACTIVITY
REWRITE ACCT-MASTER-RECORD
INVALID KEY
DISPLAY 'CRITICAL: REWRITE FAILED FOR '
AM-ACCOUNT-NUMBER
PERFORM 1900-DECLINE-WIRE
MOVE 'W098' TO LS-WS-REASON-CODE
MOVE 'ACCOUNT UPDATE FAILED'
TO LS-WS-REASON-TEXT
END-REWRITE
7000-EXIT.
EXIT
.
*================================================================*
8000-WRITE-AUDIT-LOG.
*================================================================*
INITIALIZE WS-AUDIT-LOG
MOVE FUNCTION CURRENT-DATE TO WS-AL-TIMESTAMP
MOVE LS-WS-WIRE-REF TO WS-AL-WIRE-REF
MOVE LS-WR-CUSTOMER-ID TO WS-AL-ORIGINATOR-ID
MOVE LS-WR-ORIG-ACCT TO WS-AL-ORIG-ACCT
MOVE LS-WR-BENE-NAME TO WS-AL-BENE-NAME
MOVE LS-WR-BENE-ACCT TO WS-AL-BENE-ACCT
MOVE LS-WR-BENE-BANK-ABA TO WS-AL-BENE-BANK-ABA
MOVE LS-WR-AMOUNT TO WS-AL-AMOUNT
MOVE WS-FEE-AMOUNT TO WS-AL-FEE
MOVE LS-WS-DECISION TO WS-AL-DECISION
MOVE LS-WS-REASON-CODE TO WS-AL-REASON-CODE
MOVE LS-WS-REASON-TEXT TO WS-AL-REASON-TEXT
MOVE LS-WS-OFAC-RESULT TO WS-AL-OFAC-RESULT
MOVE WS-OFAC-MATCH-ID TO WS-AL-OFAC-MATCH-ID
MOVE WS-OFAC-MATCH-SCORE TO WS-AL-OFAC-SCORE
MOVE LS-WR-USER-ID TO WS-AL-USER-ID
WRITE WIRE-LOG-RECORD FROM WS-AUDIT-LOG
.
*================================================================*
9000-CLEANUP.
*================================================================*
* FILES ARE LEFT OPEN FOR SUBSEQUENT CALLS
* THEY WILL BE CLOSED WHEN THE CALLING PROGRAM TERMINATES
* OR WHEN AN EXPLICIT CLOSE REQUEST IS MADE
CONTINUE
.
Companion JCL for Batch Wire Processing
//WIREBAT JOB (CCB,WIRE),'WIRE BATCH PROCESS',
// CLASS=A,MSGCLASS=X,MSGLEVEL=(1,1),
// NOTIFY=&SYSUID
//*
//*-----------------------------------------------------------*
//* BATCH WIRE TRANSFER PROCESSING *
//* READS WIRE REQUESTS FROM FILE AND CALLS WIREAUTH *
//* FOR EACH REQUEST *
//*-----------------------------------------------------------*
//STEP01 EXEC PGM=WIREBDRV
//STEPLIB DD DSN=CCB.PROD.LOADLIB,DISP=SHR
//WIREREQS DD DSN=CCB.WIRE.REQUESTS.DAILY,DISP=SHR,
// DCB=(RECFM=FB,LRECL=400,BLKSIZE=0)
//ACCTMAST DD DSN=CCB.ACCT.MASTER.VSAM,DISP=SHR
//WIREAGMT DD DSN=CCB.WIRE.AGREEMENT.VSAM,DISP=SHR
//OFACSDN DD DSN=CCB.OFAC.SDN.LIST.VSAM,DISP=SHR
//WIRELOG DD DSN=CCB.WIRE.AUDIT.LOG,DISP=MOD,
// DCB=(RECFM=FB,LRECL=500,BLKSIZE=0)
//WIREAPPR DD DSN=CCB.WIRE.APPROVED.FEDWIRE,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(5,2)),
// DCB=(RECFM=FB,LRECL=500,BLKSIZE=0)
//WIREDECL DD DSN=CCB.WIRE.DECLINED.REPORT,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(1,1)),
// DCB=(RECFM=FB,LRECL=132,BLKSIZE=0)
//WIREHELD DD DSN=CCB.WIRE.HELD.REVIEW,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(1,1)),
// DCB=(RECFM=FB,LRECL=500,BLKSIZE=0)
//SYSOUT DD SYSOUT=*
//SYSPRINT DD SYSOUT=*
Walkthrough
The WIREAUTH program is designed as a reusable subprogram following the CALL/GOBACK pattern. It receives two parameters through the LINKAGE SECTION: LS-WIRE-REQUEST (the input wire transfer details) and LS-WIRE-RESPONSE (the output authorization decision). This design allows the same program to be invoked from a CICS online transaction, a batch driver program, or a web services adapter.
Stateless Design. The program is stateless with respect to business logic -- each invocation processes a single wire request independently. However, it maintains file state across invocations by keeping files open between calls (controlled by WS-FILES-OPEN-FLAG). This eliminates the overhead of opening and closing four VSAM files for each wire request, which would be prohibitively expensive in a high-volume online environment.
Request Validation (paragraph 1000) performs basic field-presence checks. Missing required fields result in immediate decline with a specific reason code (W001 through W005). The currency field defaults to USD if not specified. Each validation failure uses the same decline subroutine (paragraph 1900) to ensure consistent state management.
Originator Verification (paragraph 2000) reads the customer's wire agreement from the WIRE-AGREEMENT-FILE. The agreement controls whether the customer is authorized to send wires at all, sets per-wire and daily dollar limits, controls whether international wires are permitted, and specifies the threshold above which dual approval is required. If the wire exceeds the dual approval limit, the decision is set to HELD rather than DECLINED -- the wire is valid but requires a second authorized signer before it can be released.
Balance Verification (paragraph 3000) confirms that the originator's account has sufficient available funds to cover both the wire amount and the wire fee. The fee is calculated based on wire type (domestic vs. international) and amount tier. The available balance, not the ledger balance, is checked, because the available balance already accounts for outstanding holds and pending transactions.
OFAC Screening (paragraph 4000) is the most complex section of the program. It screens three parties against the SDN list: the beneficiary, the beneficiary's bank, and the intermediary bank (if present). The screening uses a word-by-word fuzzy matching algorithm (paragraphs 4200 and 4300) rather than exact string matching, because sanctioned entities frequently use name variations, alternate spellings, and aliases.
The fuzzy matching algorithm works by parsing both the search name and each SDN entry into individual words, converting to uppercase, and counting word matches. The match percentage is calculated as the number of matching words divided by the word count of the longer name, multiplied by 100. A match percentage of 80% or higher triggers a hit. Scores of 90% or above are classified as strong matches; scores between 80% and 89% are flagged for manual compliance review.
This approach has limitations -- it does not handle phonetic variations, abbreviations, or transliteration from non-Latin scripts -- but it provides a reasonable baseline for a batch COBOL implementation. Production OFAC screening systems typically use more sophisticated algorithms, sometimes implemented in specialized screening engines that are called from the COBOL program.
Routing Number Validation (paragraph 5000) validates the beneficiary bank's ABA routing number using the standard weighted modulus 10 algorithm. The weights (3, 7, 1, repeating) are stored in a working-storage array, and the validation is performed by iterating through the nine digits, multiplying each by its corresponding weight, summing the products, and checking divisibility by 10.
Fedwire Message Formatting (paragraph 6000) constructs a simplified Fedwire Funds Transfer message. In production, the Fedwire message format is considerably more complex, with dozens of optional fields and strict formatting requirements specified by the Federal Reserve. The program also generates a six-digit authorization code derived from the current timestamp.
Account Debiting (paragraph 7000) performs the actual fund transfer by debiting both the wire amount and the fee from the originator's account. The account is re-read immediately before the debit to ensure the balance has not changed since the earlier verification. Both the ledger balance and available balance are reduced, and the account master record is rewritten. The defensive re-read guards against the unlikely but possible scenario where another process modifies the account between the balance check and the debit.
Audit Logging (paragraph 8000) writes a comprehensive audit record for every wire request, regardless of the outcome. The audit log captures the timestamp, wire reference, originator details, beneficiary details, amount, fee, decision, reason code, OFAC screening result, and the user who initiated the request. This audit trail is essential for regulatory compliance -- bank examiners routinely review wire transfer logs during BSA/AML examinations.
Discussion Questions
-
The OFAC screening algorithm uses word-by-word exact matching with an 80% threshold. What are the weaknesses of this approach? How would you handle names like "Mohammad" vs. "Mohammed" vs. "Muhammad"? Discuss the trade-off between sensitivity (catching true positives) and specificity (avoiding false positives) in sanctions screening.
-
The program reads the OFAC SDN file sequentially for each name being screened. With an SDN list of approximately 12,000 entries and three names to screen per wire, this means up to 36,000 file reads per wire request. How would you optimize OFAC screening for a high-volume environment? Consider in-memory tables, binary search, hash-based lookup, and caching strategies.
-
The program debits the originator's account in paragraph 7000, but what happens if the Fedwire message transmission subsequently fails? The originator has been debited, but the beneficiary never receives the funds. Design a two-phase commit or compensation mechanism that handles this scenario.
-
The dual approval workflow sets the wire status to HELD when the amount exceeds the customer's dual approval limit. How would you implement the second-approval step? What data structures and programs are needed? How do you handle the case where the second approver is unavailable before the Fedwire cutoff time?
-
Wire transfers are irrevocable once settled through Fedwire. If a customer discovers that a wire was sent to the wrong beneficiary, the bank can request a recall, but the receiving bank is not obligated to return the funds. How should the bank's wire transfer system handle recall requests? What procedures and COBOL programs would be involved?
-
The program uses a simplified Fedwire message format. Research the actual Fedwire Funds Transfer message specifications. What additional fields would be required for a production implementation? How would you handle the Business Function Code, which determines whether the wire is a bank-to-bank transfer, a customer transfer, or a drawdown request?