Case Study 1: Credit Card Authorization at Global Payments Inc.

Background

Global Payments Inc. processes over 2 million credit card transactions per day across a network of retail partners, online merchants, and point-of-sale terminals. Their authorization engine -- the system that decides in real time whether to approve or decline each transaction -- is a mission-critical COBOL application running on an IBM z/OS mainframe.

Every transaction must be evaluated against multiple business rules within 200 milliseconds. A false decline loses revenue and frustrates the cardholder. A false approval can result in fraud losses and regulatory penalties. The authorization engine must balance these competing pressures with precision.

This case study examines the conditional logic at the heart of Global Payments' authorization system and demonstrates how COBOL's IF, EVALUATE, and condition name features combine to implement complex, maintainable business rules.


The Business Requirements

The authorization engine must evaluate each incoming transaction against eight sequential checks:

  1. Card Status Validation -- Is the card active, suspended, lost, or stolen?
  2. Expiration Date Check -- Has the card's validity period passed?
  3. Currency Support -- Is the transaction currency one that the system supports?
  4. Credit Limit Verification -- Does the cardholder have sufficient available credit?
  5. Transaction Velocity Check -- Has the card been used an abnormal number of times today?
  6. Merchant Category Restrictions -- Is the merchant type allowed for this card?
  7. Fraud Risk Assessment -- Does the fraud scoring model flag this transaction?
  8. Geographic Risk Evaluation -- Is this a cross-border transaction to a high-risk country?

Each check can produce one of three outcomes: - PASS -- The transaction clears this check and proceeds to the next - WARN -- The transaction is flagged for post-authorization review but continues - FAIL -- The transaction is immediately declined with a specific reason code


Data Design: Condition Names as Business Language

The first design decision is how to represent the various statuses, categories, and risk levels. Rather than using raw codes throughout the program, the team defines comprehensive condition names:

       01  WS-CARD-STATUS         PIC X(1).
           88  CARD-ACTIVE                    VALUE "A".
           88  CARD-BLOCKED                   VALUE "B".
           88  CARD-EXPIRED                   VALUE "E".
           88  CARD-STOLEN                    VALUE "S".
           88  CARD-LOST                      VALUE "L".
           88  CARD-RESTRICTED                VALUE "R".
           88  CARD-USABLE                    VALUE "A" "R".
           88  CARD-COMPROMISED               VALUE "S" "L".

Notice how condition names are organized at multiple levels of abstraction: - Specific states: CARD-ACTIVE, CARD-BLOCKED, CARD-STOLEN, etc. - Grouped states: CARD-USABLE (active or restricted), CARD-COMPROMISED (stolen or lost)

This layered approach means a business rule that says "the card must be usable" does not need to enumerate every acceptable status -- it simply tests CARD-USABLE.

The fraud scoring system produces a numeric score from 0 to 100:

       05  WS-FRAUD-SCORE         PIC 9(3).
           88  FRAUD-LOW-RISK                 VALUE 0 THRU 30.
           88  FRAUD-MEDIUM-RISK              VALUE 31 THRU 60.
           88  FRAUD-HIGH-RISK                VALUE 61 THRU 80.
           88  FRAUD-CRITICAL                 VALUE 81 THRU 100.
           88  FRAUD-ACCEPTABLE               VALUE 0 THRU 60.

The THRU ranges with overlapping group conditions (FRAUD-ACCEPTABLE encompasses both LOW and MEDIUM) allow the authorization logic to operate at whatever granularity is appropriate for each specific rule.


Authorization Flow: The Sequential Check Pattern

The authorization engine uses a common COBOL pattern: sequential rule evaluation with a "continue processing" flag. This pattern is both efficient and easy to extend:

       3000-AUTHORIZE-TRANSACTION.
           SET CONTINUE-PROCESSING TO TRUE
           SET NO-REVIEW-NEEDED TO TRUE

           IF CONTINUE-PROCESSING
               PERFORM 3100-CHECK-CARD-STATUS
           END-IF
           IF CONTINUE-PROCESSING
               PERFORM 3200-CHECK-EXPIRATION
           END-IF
           IF CONTINUE-PROCESSING
               PERFORM 3300-CHECK-CURRENCY
           END-IF
           ...
           IF CONTINUE-PROCESSING
               PERFORM 3900-FINAL-APPROVAL
           END-IF

When any check sets STOP-PROCESSING to TRUE, all subsequent checks are skipped. This is both a performance optimization (no unnecessary processing) and a logical necessity (some checks depend on earlier checks passing).

Design insight: This pattern is the COBOL equivalent of an "early return" pattern in other languages. Since COBOL paragraphs do not have a return mechanism (PERFORM always runs to the end), the continue-flag pattern serves the same purpose.


EVALUATE for Multi-Way Decisions

The card status check uses EVALUATE TRUE to handle the multiple possible statuses elegantly:

       3100-CHECK-CARD-STATUS.
           EVALUATE TRUE
               WHEN CARD-ACTIVE
                   DISPLAY "PASS: Card is active"
               WHEN CARD-RESTRICTED
                   DISPLAY "WARN: Card is restricted"
                   SET NEEDS-REVIEW TO TRUE
               WHEN CARD-COMPROMISED
                   SET AUTH-FRAUD-HOLD TO TRUE
                   MOVE "Card reported compromised"
                       TO WS-AUTH-MESSAGE
                   SET STOP-PROCESSING TO TRUE
               WHEN CARD-BLOCKED
                   SET AUTH-DECLINED TO TRUE
                   MOVE "Card is blocked"
                       TO WS-AUTH-MESSAGE
                   SET STOP-PROCESSING TO TRUE
               WHEN CARD-EXPIRED
                   SET AUTH-EXPIRED-CARD TO TRUE
                   SET STOP-PROCESSING TO TRUE
               WHEN OTHER
                   SET AUTH-SYSTEM-ERROR TO TRUE
                   SET STOP-PROCESSING TO TRUE
           END-EVALUATE

Compare how readable this is versus a nested IF chain. Each card status is a clearly delineated case with its own handling logic. The WHEN OTHER clause provides a safety net for unexpected data -- in a system processing millions of transactions, defensive coding is essential.


EVALUATE TRUE ALSO TRUE: The Geographic Risk Matrix

The most sophisticated check combines geographic and fraud data using EVALUATE TRUE ALSO TRUE:

       EVALUATE TRUE ALSO TRUE
           WHEN HIGH-RISK-COUNTRY
               ALSO ANY
               SET AUTH-DECLINED TO TRUE
               SET STOP-PROCESSING TO TRUE

           WHEN IS-CROSS-BORDER
               ALSO FRAUD-HIGH-RISK
               SET AUTH-CALL-CENTER TO TRUE
               SET STOP-PROCESSING TO TRUE

           WHEN IS-CROSS-BORDER
               ALSO WS-TRANS-AMOUNT > WS-HIGH-VALUE-THRESH
               SET NEEDS-REVIEW TO TRUE

           WHEN IS-CROSS-BORDER
               ALSO OTHER
               CONTINUE

           WHEN IS-DOMESTIC
               ALSO ANY
               CONTINUE
       END-EVALUATE

This two-dimensional decision matrix evaluates the geographic dimension (domestic vs. cross-border vs. high-risk origin) against the risk dimension (fraud level, transaction amount) simultaneously. In a traditional IF/ELSE structure, this same logic would require deeply nested conditions that are much harder to understand and maintain.

The ANY keyword acts as a wildcard -- "regardless of the fraud level, if the origin is high-risk, decline." This makes the business intent crystal clear.


The Final Decision: Compound Conditions

The final approval paragraph demonstrates compound conditions with multiple factors:

       3900-FINAL-APPROVAL.
           IF NEEDS-REVIEW
               IF WS-TRANS-AMOUNT > WS-HIGH-VALUE-THRESH
                   AND (IS-CROSS-BORDER
                       OR NOT FRAUD-LOW-RISK)
                   SET AUTH-CALL-CENTER TO TRUE
                   MOVE "Manual review recommended"
                       TO WS-AUTH-MESSAGE
               ELSE
                   SET AUTH-APPROVED TO TRUE
                   MOVE "Approved with monitoring"
                       TO WS-AUTH-MESSAGE
               END-IF
           ELSE
               SET AUTH-APPROVED TO TRUE
               MOVE "Approved" TO WS-AUTH-MESSAGE
           END-IF

This logic says: "If the transaction has accumulated warning flags, and it is both high-value and either cross-border or not low-risk on the fraud scale, then route it to the call center for manual review. Otherwise, approve it with a monitoring flag."

Note the use of parentheses to group IS-CROSS-BORDER OR NOT FRAUD-LOW-RISK. Without parentheses, AND's higher precedence would change the meaning.


Results Display: The Power of Condition Names

The results display uses EVALUATE with condition names to produce human-readable output:

       EVALUATE TRUE
           WHEN AUTH-APPROVED
               DISPLAY "Status: *** APPROVED ***"
           WHEN AUTH-CALL-CENTER
               DISPLAY "Status: REFER TO CALL CENTER"
           WHEN AUTH-DECLINED
               DISPLAY "Status: *** DECLINED ***"
           WHEN AUTH-FRAUD-HOLD
               DISPLAY "Status: *** FRAUD HOLD ***"
           WHEN AUTH-EXPIRED-CARD
               DISPLAY "Status: EXPIRED CARD"
           WHEN AUTH-OVER-LIMIT
               DISPLAY "Status: OVER CREDIT LIMIT"
           WHEN AUTH-RESTRICTED
               DISPLAY "Status: RESTRICTED"
           WHEN AUTH-SYSTEM-ERROR
               DISPLAY "Status: SYSTEM ERROR"
       END-EVALUATE

Each authorization code (00, 01, 05, 43, 54, 51, 57, 96) has a condition name that describes what it means. The code that generates the output does not contain a single literal code value -- it reads entirely in business language.


Lessons Learned

1. Condition Names Are Not Optional -- They Are Essential

In a system that processes millions of transactions, every code value must have a clear business meaning. The team learned that condition names should be defined at the time the data structure is created, not added later. They are part of the data design, not an afterthought.

2. The Sequential Check Pattern Scales

When Global Payments needed to add a new check (merchant country sanctions screening), the pattern made it trivial: define the new paragraph, add one IF CONTINUE-PROCESSING/PERFORM block, and the new rule was integrated. No existing logic needed to change.

3. EVALUATE TRUE ALSO TRUE Replaces Decision Tables

The geographic risk matrix was originally implemented as a 47-line nested IF structure. When converted to EVALUATE TRUE ALSO TRUE, it became 25 lines and was immediately understandable to the business analysts reviewing the logic.

4. Defensive Coding Matters at Scale

The WHEN OTHER clause in every EVALUATE catches data anomalies that should "never happen." In production, these clauses fire dozens of times per day due to upstream data quality issues. Without them, transactions would either be incorrectly approved or cause program abends.

5. COBOL's Lack of Short-Circuit Evaluation Requires Discipline

The credit limit check must verify that available credit is positive before comparing the transaction amount against it. The team maintains a strict coding standard: any check that could fail on invalid data must be performed in a separate, guarded IF block.


Code Reference

The complete implementation of this authorization system is in code/example-06-complex-logic.cob. The program can be compiled and run with GnuCOBOL:

cobc -x example-06-complex-logic.cob
./example-06-complex-logic

Modify the test data in paragraph 1000-INITIALIZE-TEST-DATA to explore different authorization scenarios: change the card status, fraud score, merchant category, or transaction amount to see how the authorization engine responds.


Discussion Questions

  1. What would happen if the checks were evaluated in a different order? Are there dependencies between the checks that require a specific sequence?

  2. The system uses a single CONTINUE-PROCESSING flag. What are the advantages and disadvantages compared to using a numeric "severity level" that accumulates across checks?

  3. How would you modify the geographic risk check to handle a new business requirement: "Transactions from allied countries (US, CA, GB, AU) should be treated as domestic for risk purposes"?

  4. The current design processes one transaction at a time. If you needed to batch-process thousands of transactions, what changes to the conditional logic structure would improve throughput?

  5. How would you add audit logging so that every PASS, WARN, and FAIL decision is recorded with the rule that produced it? How would condition names help make those log entries meaningful?