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:

  1. 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.

  2. 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:

  1. Request Validation: Verify all required fields are present and properly formatted.
  2. Originator Verification: Read the customer's wire agreement and verify limits.
  3. Balance Verification: Confirm sufficient available funds in the originator's account.
  4. OFAC Screening: Screen beneficiary name, beneficiary bank, and intermediary bank against the SDN list.
  5. Routing Number Validation: Verify the beneficiary bank's ABA routing number using the weighted modulus 10 algorithm.
  6. Fedwire Message Formatting: Format the authorized wire into a Fedwire-compatible message.
  7. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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?

  5. 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?

  6. 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?