Case Study 2: Message Queue Integration for Real-Time Notifications

Background

MidWest Community Bank processes over 200,000 debit card transactions daily through its COBOL-based authorization system running on IBM z/OS. Customers have been requesting real-time transaction alerts -- push notifications on their mobile devices within seconds of a purchase. The current system processes transactions in batch overnight, meaning customers discover unauthorized charges only when they check their statements the next morning.

The fraud team also needs real-time visibility. Their cloud-based fraud analytics platform can detect suspicious patterns across multiple cards, but only if it receives transaction data as it happens, not in overnight batch feeds.

The architecture team selects IBM MQ as the integration backbone. When the COBOL authorization program approves a transaction, it publishes an event message to an MQ topic. Downstream subscribers -- the mobile notification service and the fraud analytics platform -- consume these events independently and in real time. The COBOL program's performance is minimally impacted because MQ put operations are asynchronous and take only 1-2 milliseconds.

This case study implements the complete flow: the COBOL transaction publisher, the JSON message format, the MQ configuration, and a COBOL-based fraud monitoring subscriber.


Problem Statement

Implement an MQ-based real-time transaction notification system where:

  1. The COBOL authorization program publishes a JSON-formatted event message after each approved transaction.
  2. Messages are published to an MQ topic so multiple subscribers can receive them independently.
  3. The message includes transaction details: card number (masked), merchant, amount, location, timestamp, and authorization code.
  4. A fraud monitoring COBOL program subscribes to the topic and examines each transaction against configurable rules.
  5. The system handles MQ failures gracefully without disrupting transaction processing.

Message Format Design

The team designs a JSON message format that serves both the notification service and the fraud analytics platform:

{
  "eventType": "TXN_AUTHORIZED",
  "eventTimestamp": "2025-04-01T14:23:45.123",
  "transactionId": "TXN20250401142345001",
  "cardNumber": "****1234",
  "accountNumber": "1000234567",
  "merchantName": "WHOLE FOODS MARKET #10842",
  "merchantCategory": "5411",
  "transactionAmount": "87.43",
  "transactionCurrency": "USD",
  "merchantCity": "CHICAGO",
  "merchantState": "IL",
  "merchantCountry": "US",
  "authorizationCode": "A04521",
  "channelType": "POS",
  "riskScore": 15
}

The Transaction Event Publisher

This COBOL program is called by the main authorization program after a transaction is approved. It builds the JSON message and publishes it to the MQ topic. The design prioritizes minimal impact on transaction processing time.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. TXNEVTPB.
      *================================================================
      * PROGRAM: TXNEVTPB - Transaction Event Publisher
      * PURPOSE: Publishes approved transaction events to IBM MQ
      *          for real-time notification and fraud monitoring
      * CALLED BY: Main authorization program (AUTHPROC)
      * MQ TOPIC: TXN.EVENTS.AUTHORIZED
      *================================================================

       ENVIRONMENT DIVISION.

       DATA DIVISION.
       WORKING-STORAGE SECTION.

       COPY CMQV.

       01  WS-PROGRAM-NAME    PIC X(8) VALUE "TXNEVTPB".

      *--- MQ connection and handle fields ---
       01  WS-QM-NAME         PIC X(48) VALUE "BANKQM01".
       01  WS-TOPIC-STRING    PIC X(48)
                               VALUE "TXN.EVENTS.AUTHORIZED".
       01  WS-HCONN           PIC S9(9) BINARY VALUE 0.
       01  WS-HOBJ            PIC S9(9) BINARY VALUE 0.
       01  WS-COMP-CODE       PIC S9(9) BINARY VALUE 0.
       01  WS-REASON          PIC S9(9) BINARY VALUE 0.
       01  WS-OPEN-OPTIONS    PIC S9(9) BINARY VALUE 0.

       01  WS-OBJDESC.
           COPY CMQODV.

       01  WS-MSGDESC.
           COPY CMQMDV.

       01  WS-PMO.
           COPY CMQPMOV.

      *--- JSON message construction ---
       01  WS-MSG-BUFFER       PIC X(2000) VALUE SPACES.
       01  WS-MSG-LENGTH       PIC S9(9) BINARY VALUE 0.
       01  WS-JSON-LENGTH      PIC 9(5) VALUE 0.

      *--- Transaction event data for JSON generation ---
       01  WS-TXN-EVENT.
           05  EVENTTYPE       PIC X(20)
               JSON NAME "eventType".
           05  EVENTTIMESTAMP   PIC X(23)
               JSON NAME "eventTimestamp".
           05  TRANSACTIONID   PIC X(25)
               JSON NAME "transactionId".
           05  CARDNUMBER      PIC X(16)
               JSON NAME "cardNumber".
           05  ACCOUNTNUMBER   PIC X(10)
               JSON NAME "accountNumber".
           05  MERCHANTNAME    PIC X(40)
               JSON NAME "merchantName".
           05  MERCHANTCATEGORY PIC X(4)
               JSON NAME "merchantCategory".
           05  TRANSACTIONAMOUNT PIC X(12)
               JSON NAME "transactionAmount".
           05  TRANSACTIONCURRENCY PIC X(3)
               JSON NAME "transactionCurrency".
           05  MERCHANTCITY    PIC X(20)
               JSON NAME "merchantCity".
           05  MERCHANTSTATE   PIC X(2)
               JSON NAME "merchantState".
           05  MERCHANTCOUNTRY PIC X(2)
               JSON NAME "merchantCountry".
           05  AUTHORIZATIONCODE PIC X(6)
               JSON NAME "authorizationCode".
           05  CHANNELTYPE     PIC X(6)
               JSON NAME "channelType".
           05  RISKSCORE       PIC 9(3)
               JSON NAME "riskScore".

      *--- Formatting work fields ---
       01  WS-FMT-AMOUNT      PIC Z(8)9.99.
       01  WS-MASKED-CARD     PIC X(16).
       01  WS-CURRENT-TS      PIC X(26).
       01  WS-FORMATTED-TS    PIC X(23).

      *--- Error handling ---
       01  WS-MQ-CONNECTED    PIC 9 VALUE 0.
           88  MQ-IS-CONNECTED             VALUE 1.
           88  MQ-NOT-CONNECTED            VALUE 0.
       01  WS-MQ-OPENED       PIC 9 VALUE 0.
           88  MQ-TOPIC-OPENED             VALUE 1.
           88  MQ-TOPIC-NOT-OPENED         VALUE 0.
       01  WS-PUBLISH-OK      PIC 9 VALUE 0.

       LINKAGE SECTION.
      *--- Input from calling authorization program ---
       01  LS-TXN-DATA.
           05  LS-CARD-NUM     PIC X(16).
           05  LS-ACCT-NUM     PIC X(10).
           05  LS-MERCHANT     PIC X(40).
           05  LS-MERCH-CAT    PIC X(4).
           05  LS-AMOUNT       PIC 9(9)V99.
           05  LS-CURRENCY     PIC X(3).
           05  LS-MERCH-CITY   PIC X(20).
           05  LS-MERCH-STATE  PIC X(2).
           05  LS-MERCH-COUNTRY PIC X(2).
           05  LS-AUTH-CODE    PIC X(6).
           05  LS-CHANNEL      PIC X(6).
           05  LS-RISK-SCORE   PIC 9(3).
           05  LS-TXN-ID       PIC X(25).
       01  LS-RETURN-CODE      PIC 9.

       PROCEDURE DIVISION USING LS-TXN-DATA LS-RETURN-CODE.
       0000-MAIN.
           MOVE 0 TO LS-RETURN-CODE

           PERFORM 1000-BUILD-EVENT-MESSAGE
           PERFORM 2000-CONNECT-TO-QM
           IF MQ-IS-CONNECTED
               PERFORM 3000-OPEN-TOPIC
           END-IF
           IF MQ-TOPIC-OPENED
               PERFORM 4000-PUBLISH-MESSAGE
           END-IF
           PERFORM 5000-CLEANUP

           GOBACK
           .

       1000-BUILD-EVENT-MESSAGE.
      *    Populate the event structure
           MOVE "TXN_AUTHORIZED" TO EVENTTYPE

      *    Format timestamp: YYYY-MM-DDTHH:MM:SS.mmm
           MOVE FUNCTION CURRENT-DATE TO WS-CURRENT-TS
           STRING WS-CURRENT-TS(1:4) "-"
                  WS-CURRENT-TS(5:2) "-"
                  WS-CURRENT-TS(7:2) "T"
                  WS-CURRENT-TS(9:2) ":"
                  WS-CURRENT-TS(11:2) ":"
                  WS-CURRENT-TS(13:2) "."
                  WS-CURRENT-TS(15:3)
                  DELIMITED SIZE
                  INTO WS-FORMATTED-TS
           MOVE WS-FORMATTED-TS TO EVENTTIMESTAMP

           MOVE LS-TXN-ID TO TRANSACTIONID

      *    Mask card number: show only last 4 digits
           STRING "************"
                  LS-CARD-NUM(13:4)
                  DELIMITED SIZE
                  INTO WS-MASKED-CARD
           MOVE WS-MASKED-CARD TO CARDNUMBER

           MOVE LS-ACCT-NUM TO ACCOUNTNUMBER
           MOVE LS-MERCHANT TO MERCHANTNAME
           MOVE LS-MERCH-CAT TO MERCHANTCATEGORY

      *    Format amount as string with decimal
           MOVE LS-AMOUNT TO WS-FMT-AMOUNT
           MOVE FUNCTION TRIM(WS-FMT-AMOUNT)
               TO TRANSACTIONAMOUNT

           MOVE LS-CURRENCY TO TRANSACTIONCURRENCY
           MOVE LS-MERCH-CITY TO MERCHANTCITY
           MOVE LS-MERCH-STATE TO MERCHANTSTATE
           MOVE LS-MERCH-COUNTRY TO MERCHANTCOUNTRY
           MOVE LS-AUTH-CODE TO AUTHORIZATIONCODE
           MOVE LS-CHANNEL TO CHANNELTYPE
           MOVE LS-RISK-SCORE TO RISKSCORE

      *    Generate JSON from the event structure
           JSON GENERATE WS-MSG-BUFFER
               FROM WS-TXN-EVENT
               COUNT WS-JSON-LENGTH
               ON EXCEPTION
                   DISPLAY WS-PROGRAM-NAME
                       " JSON GENERATE failed"
                   MOVE 1 TO LS-RETURN-CODE
           END-JSON

           MOVE WS-JSON-LENGTH TO WS-MSG-LENGTH
           .

       2000-CONNECT-TO-QM.
           CALL 'MQCONN' USING WS-QM-NAME
                                WS-HCONN
                                WS-COMP-CODE
                                WS-REASON

           EVALUATE WS-COMP-CODE
               WHEN MQCC-OK
                   SET MQ-IS-CONNECTED TO TRUE
               WHEN MQCC-WARNING
                   SET MQ-IS-CONNECTED TO TRUE
                   DISPLAY WS-PROGRAM-NAME
                       " MQCONN warning. Reason: " WS-REASON
               WHEN OTHER
                   SET MQ-NOT-CONNECTED TO TRUE
                   DISPLAY WS-PROGRAM-NAME
                       " MQCONN failed. Reason: " WS-REASON
                   MOVE 1 TO LS-RETURN-CODE
           END-EVALUATE
           .

       3000-OPEN-TOPIC.
      *    Configure object descriptor for topic publish
           MOVE MQOT-TOPIC TO MQOD-OBJECTTYPE
           MOVE WS-TOPIC-STRING TO MQOD-OBJECTSTRING
           MOVE FUNCTION LENGTH(
               FUNCTION TRIM(WS-TOPIC-STRING))
               TO MQOD-OBJECTSTRINGLENGTH

           COMPUTE WS-OPEN-OPTIONS =
               MQOO-OUTPUT + MQOO-FAIL-IF-QUIESCING

           CALL 'MQOPEN' USING WS-HCONN
                                WS-OBJDESC
                                WS-OPEN-OPTIONS
                                WS-HOBJ
                                WS-COMP-CODE
                                WS-REASON

           IF WS-COMP-CODE = MQCC-OK
               SET MQ-TOPIC-OPENED TO TRUE
           ELSE
               SET MQ-TOPIC-NOT-OPENED TO TRUE
               DISPLAY WS-PROGRAM-NAME
                   " MQOPEN topic failed. Reason: " WS-REASON
               MOVE 1 TO LS-RETURN-CODE
           END-IF
           .

       4000-PUBLISH-MESSAGE.
      *    Set message descriptor fields
           MOVE MQMT-DATAGRAM TO MQMD-MSGTYPE
           MOVE MQFMT-STRING  TO MQMD-FORMAT
           MOVE MQPER-NOT-PERSISTENT TO MQMD-PERSISTENCE
           MOVE MQEI-UNLIMITED TO MQMD-EXPIRY

      *    Set put-message options
           MOVE MQPMO-NO-SYNCPOINT TO MQPMO-OPTIONS

           CALL 'MQPUT' USING WS-HCONN
                               WS-HOBJ
                               WS-MSGDESC
                               WS-PMO
                               WS-MSG-LENGTH
                               WS-MSG-BUFFER
                               WS-COMP-CODE
                               WS-REASON

           IF WS-COMP-CODE = MQCC-OK
               MOVE 1 TO WS-PUBLISH-OK
           ELSE
               DISPLAY WS-PROGRAM-NAME
                   " MQPUT failed. Reason: " WS-REASON
               MOVE 1 TO LS-RETURN-CODE
           END-IF
           .

       5000-CLEANUP.
           IF MQ-TOPIC-OPENED
               CALL 'MQCLOSE' USING WS-HCONN
                                     WS-HOBJ
                                     MQCO-NONE
                                     WS-COMP-CODE
                                     WS-REASON
               IF WS-COMP-CODE NOT = MQCC-OK
                   DISPLAY WS-PROGRAM-NAME
                       " MQCLOSE warning. Reason: "
                       WS-REASON
               END-IF
           END-IF

           IF MQ-IS-CONNECTED
               CALL 'MQDISC' USING WS-HCONN
                                    WS-COMP-CODE
                                    WS-REASON
               IF WS-COMP-CODE NOT = MQCC-OK
                   DISPLAY WS-PROGRAM-NAME
                       " MQDISC warning. Reason: "
                       WS-REASON
               END-IF
           END-IF
           .

The Fraud Monitoring Subscriber

The fraud monitoring program subscribes to the transaction events topic and evaluates each transaction against configurable rules. When a suspicious transaction is detected, it writes an alert record and can optionally put a message on a separate alert queue.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. FRAUDMON.
      *================================================================
      * PROGRAM: FRAUDMON - Fraud Monitoring Subscriber
      * PURPOSE: Subscribes to transaction events via IBM MQ,
      *          evaluates fraud rules, generates alerts
      * MQ TOPIC: TXN.EVENTS.AUTHORIZED
      * RUNS AS: Long-running batch process or CICS task
      *================================================================

       ENVIRONMENT DIVISION.

       DATA DIVISION.
       WORKING-STORAGE SECTION.

       COPY CMQV.

       01  WS-PROGRAM-NAME    PIC X(8) VALUE "FRAUDMON".

      *--- MQ connection fields ---
       01  WS-QM-NAME         PIC X(48) VALUE "BANKQM01".
       01  WS-TOPIC-STRING    PIC X(48)
                               VALUE "TXN.EVENTS.AUTHORIZED".
       01  WS-SUB-NAME        PIC X(48)
                               VALUE "FRAUD.MONITOR.SUB".
       01  WS-HCONN           PIC S9(9) BINARY VALUE 0.
       01  WS-HSUB            PIC S9(9) BINARY VALUE 0.
       01  WS-HOBJ            PIC S9(9) BINARY VALUE 0.
       01  WS-COMP-CODE       PIC S9(9) BINARY VALUE 0.
       01  WS-REASON          PIC S9(9) BINARY VALUE 0.

       01  WS-SUBDESC.
           COPY CMQSDV.

       01  WS-MSGDESC.
           COPY CMQMDV.

       01  WS-GMO.
           COPY CMQGMOV.

      *--- Message buffer ---
       01  WS-MSG-BUFFER       PIC X(2000) VALUE SPACES.
       01  WS-MSG-LENGTH       PIC S9(9) BINARY VALUE 2000.
       01  WS-DATA-LENGTH      PIC S9(9) BINARY VALUE 0.

      *--- Parsed transaction event ---
       01  WS-TXN-EVENT.
           05  EVENTTYPE       PIC X(20)
               JSON NAME "eventType".
           05  EVENTTIMESTAMP   PIC X(23)
               JSON NAME "eventTimestamp".
           05  TRANSACTIONID   PIC X(25)
               JSON NAME "transactionId".
           05  CARDNUMBER      PIC X(16)
               JSON NAME "cardNumber".
           05  ACCOUNTNUMBER   PIC X(10)
               JSON NAME "accountNumber".
           05  MERCHANTNAME    PIC X(40)
               JSON NAME "merchantName".
           05  MERCHANTCATEGORY PIC X(4)
               JSON NAME "merchantCategory".
           05  TRANSACTIONAMOUNT PIC X(12)
               JSON NAME "transactionAmount".
           05  TRANSACTIONCURRENCY PIC X(3)
               JSON NAME "transactionCurrency".
           05  MERCHANTCITY    PIC X(20)
               JSON NAME "merchantCity".
           05  MERCHANTSTATE   PIC X(2)
               JSON NAME "merchantState".
           05  MERCHANTCOUNTRY PIC X(2)
               JSON NAME "merchantCountry".
           05  AUTHORIZATIONCODE PIC X(6)
               JSON NAME "authorizationCode".
           05  CHANNELTYPE     PIC X(6)
               JSON NAME "channelType".
           05  RISKSCORE       PIC 9(3)
               JSON NAME "riskScore".

      *--- Fraud detection rules and thresholds ---
       01  WS-FRAUD-RULES.
           05  WS-HIGH-VALUE-THRESHOLD
                               PIC 9(9)V99 VALUE 10000.00.
           05  WS-RISK-SCORE-THRESHOLD
                               PIC 9(3) VALUE 70.
           05  WS-HIGH-RISK-CATEGORIES.
               10  WS-HR-CAT  PIC X(4) OCCURS 5 TIMES.
           05  WS-HIGH-RISK-COUNTRIES.
               10  WS-HR-CTRY PIC X(2) OCCURS 10 TIMES.
           05  WS-HR-CAT-COUNT PIC 9 VALUE 3.
           05  WS-HR-CTRY-COUNT PIC 9(2) VALUE 5.

      *--- Alert tracking ---
       01  WS-ALERT-RECORD.
           05  WS-ALERT-TXN-ID    PIC X(25).
           05  WS-ALERT-CARD      PIC X(16).
           05  WS-ALERT-AMOUNT    PIC X(12).
           05  WS-ALERT-RULE      PIC X(30).
           05  WS-ALERT-SEVERITY  PIC X(8).
           05  WS-ALERT-TIMESTAMP PIC X(23).

      *--- Processing counters ---
       01  WS-COUNTERS.
           05  WS-MSGS-RECEIVED   PIC 9(8) VALUE 0.
           05  WS-MSGS-CLEAN      PIC 9(8) VALUE 0.
           05  WS-ALERTS-RAISED   PIC 9(8) VALUE 0.
           05  WS-PARSE-ERRORS    PIC 9(5) VALUE 0.

      *--- Control flags ---
       01  WS-CONTINUE-FLAG   PIC 9 VALUE 1.
           88  CONTINUE-PROCESSING         VALUE 1.
           88  STOP-PROCESSING             VALUE 0.
       01  WS-ALERT-FOUND     PIC 9 VALUE 0.
           88  ALERT-DETECTED              VALUE 1.
           88  NO-ALERT                    VALUE 0.

      *--- Work fields ---
       01  WS-TXN-AMOUNT-NUM  PIC 9(9)V99 VALUE 0.
       01  WS-IDX             PIC 9(2).
       01  WS-FMT-COUNT       PIC ZZ,ZZ9.

       PROCEDURE DIVISION.
       0000-MAIN.
           DISPLAY WS-PROGRAM-NAME " starting..."

           PERFORM 1000-INITIALIZE-RULES
           PERFORM 2000-CONNECT-AND-SUBSCRIBE
           IF WS-COMP-CODE = MQCC-OK
               PERFORM 3000-PROCESS-MESSAGES
                   UNTIL NOT CONTINUE-PROCESSING
           END-IF
           PERFORM 8000-PRINT-SUMMARY
           PERFORM 9000-CLEANUP

           DISPLAY WS-PROGRAM-NAME " ended."
           STOP RUN
           .

       1000-INITIALIZE-RULES.
      *    Load high-risk merchant categories
           MOVE "5912" TO WS-HR-CAT(1)
           MOVE "5944" TO WS-HR-CAT(2)
           MOVE "7995" TO WS-HR-CAT(3)

      *    Load high-risk countries
           MOVE "NG" TO WS-HR-CTRY(1)
           MOVE "RO" TO WS-HR-CTRY(2)
           MOVE "UA" TO WS-HR-CTRY(3)
           MOVE "VN" TO WS-HR-CTRY(4)
           MOVE "PH" TO WS-HR-CTRY(5)

           DISPLAY "  Fraud rules initialized"
           DISPLAY "  High-value threshold: $"
               WS-HIGH-VALUE-THRESHOLD
           DISPLAY "  Risk score threshold: "
               WS-RISK-SCORE-THRESHOLD
           .

       2000-CONNECT-AND-SUBSCRIBE.
      *    Connect to queue manager
           CALL 'MQCONN' USING WS-QM-NAME
                                WS-HCONN
                                WS-COMP-CODE
                                WS-REASON
           IF WS-COMP-CODE NOT = MQCC-OK
               DISPLAY WS-PROGRAM-NAME
                   " MQCONN failed. Reason: " WS-REASON
               SET STOP-PROCESSING TO TRUE
               GOBACK
           END-IF
           DISPLAY "  Connected to " WS-QM-NAME

      *    Create a durable subscription to the topic
           MOVE WS-TOPIC-STRING TO MQSD-OBJECTSTRING
           MOVE FUNCTION LENGTH(
               FUNCTION TRIM(WS-TOPIC-STRING))
               TO MQSD-OBJECTSTRINGLENGTH
           MOVE WS-SUB-NAME TO MQSD-SUBNAME

           COMPUTE MQSD-OPTIONS =
               MQSO-CREATE + MQSO-RESUME
               + MQSO-DURABLE
               + MQSO-FAIL-IF-QUIESCING

           CALL 'MQSUB' USING WS-HCONN
                               WS-SUBDESC
                               WS-HOBJ
                               WS-HSUB
                               WS-COMP-CODE
                               WS-REASON

           IF WS-COMP-CODE = MQCC-OK
               DISPLAY "  Subscribed to " WS-TOPIC-STRING
           ELSE
               DISPLAY WS-PROGRAM-NAME
                   " MQSUB failed. Reason: " WS-REASON
               SET STOP-PROCESSING TO TRUE
           END-IF
           .

       3000-PROCESS-MESSAGES.
      *    Set get-message options
           COMPUTE MQGMO-OPTIONS =
               MQGMO-WAIT + MQGMO-FAIL-IF-QUIESCING
           MOVE 5000 TO MQGMO-WAITINTERVAL

      *    Reset message descriptor for each get
           INITIALIZE WS-MSGDESC
           MOVE MQMI-NONE TO MQMD-MSGID
           MOVE MQCI-NONE TO MQMD-CORRELID

           MOVE 2000 TO WS-MSG-LENGTH
           MOVE SPACES TO WS-MSG-BUFFER

           CALL 'MQGET' USING WS-HCONN
                               WS-HOBJ
                               WS-MSGDESC
                               WS-GMO
                               WS-MSG-LENGTH
                               WS-MSG-BUFFER
                               WS-DATA-LENGTH
                               WS-COMP-CODE
                               WS-REASON

           EVALUATE WS-COMP-CODE
               WHEN MQCC-OK
                   ADD 1 TO WS-MSGS-RECEIVED
                   PERFORM 4000-PARSE-AND-EVALUATE
               WHEN MQCC-FAILED
                   IF WS-REASON = MQRC-NO-MSG-AVAILABLE
                       CONTINUE
                   ELSE
                       DISPLAY WS-PROGRAM-NAME
                           " MQGET failed. Reason: "
                           WS-REASON
                       SET STOP-PROCESSING TO TRUE
                   END-IF
               WHEN OTHER
                   DISPLAY WS-PROGRAM-NAME
                       " MQGET unexpected. CC: "
                       WS-COMP-CODE " Reason: " WS-REASON
           END-EVALUATE
           .

       4000-PARSE-AND-EVALUATE.
           INITIALIZE WS-TXN-EVENT

           JSON PARSE WS-MSG-BUFFER(1:WS-DATA-LENGTH)
               INTO WS-TXN-EVENT
               ON EXCEPTION
                   ADD 1 TO WS-PARSE-ERRORS
                   DISPLAY WS-PROGRAM-NAME
                       " JSON parse error for message "
                       WS-MSGS-RECEIVED
           END-JSON

           SET NO-ALERT TO TRUE
           PERFORM 5000-CHECK-HIGH-VALUE
           PERFORM 5100-CHECK-RISK-SCORE
           PERFORM 5200-CHECK-HIGH-RISK-CATEGORY
           PERFORM 5300-CHECK-HIGH-RISK-COUNTRY

           IF NO-ALERT
               ADD 1 TO WS-MSGS-CLEAN
           END-IF
           .

       5000-CHECK-HIGH-VALUE.
           COMPUTE WS-TXN-AMOUNT-NUM =
               FUNCTION NUMVAL(TRANSACTIONAMOUNT)

           IF WS-TXN-AMOUNT-NUM > WS-HIGH-VALUE-THRESHOLD
               PERFORM 6000-RAISE-ALERT-SETUP
               MOVE "HIGH VALUE TRANSACTION"
                   TO WS-ALERT-RULE
               MOVE "HIGH" TO WS-ALERT-SEVERITY
               PERFORM 7000-LOG-ALERT
           END-IF
           .

       5100-CHECK-RISK-SCORE.
           IF RISKSCORE > WS-RISK-SCORE-THRESHOLD
               PERFORM 6000-RAISE-ALERT-SETUP
               MOVE "ELEVATED RISK SCORE"
                   TO WS-ALERT-RULE
               MOVE "CRITICAL" TO WS-ALERT-SEVERITY
               PERFORM 7000-LOG-ALERT
           END-IF
           .

       5200-CHECK-HIGH-RISK-CATEGORY.
           PERFORM VARYING WS-IDX FROM 1 BY 1
               UNTIL WS-IDX > WS-HR-CAT-COUNT
               IF MERCHANTCATEGORY = WS-HR-CAT(WS-IDX)
                   PERFORM 6000-RAISE-ALERT-SETUP
                   MOVE "HIGH RISK MERCHANT CATEGORY"
                       TO WS-ALERT-RULE
                   MOVE "MEDIUM" TO WS-ALERT-SEVERITY
                   PERFORM 7000-LOG-ALERT
               END-IF
           END-PERFORM
           .

       5300-CHECK-HIGH-RISK-COUNTRY.
           IF MERCHANTCOUNTRY NOT = "US"
               PERFORM VARYING WS-IDX FROM 1 BY 1
                   UNTIL WS-IDX > WS-HR-CTRY-COUNT
                   IF MERCHANTCOUNTRY = WS-HR-CTRY(WS-IDX)
                       PERFORM 6000-RAISE-ALERT-SETUP
                       MOVE "HIGH RISK COUNTRY"
                           TO WS-ALERT-RULE
                       MOVE "CRITICAL" TO WS-ALERT-SEVERITY
                       PERFORM 7000-LOG-ALERT
                   END-IF
               END-PERFORM
           END-IF
           .

       6000-RAISE-ALERT-SETUP.
           SET ALERT-DETECTED TO TRUE
           ADD 1 TO WS-ALERTS-RAISED
           MOVE TRANSACTIONID TO WS-ALERT-TXN-ID
           MOVE CARDNUMBER TO WS-ALERT-CARD
           MOVE TRANSACTIONAMOUNT TO WS-ALERT-AMOUNT
           MOVE EVENTTIMESTAMP TO WS-ALERT-TIMESTAMP
           .

       7000-LOG-ALERT.
           DISPLAY "*** FRAUD ALERT ***"
           DISPLAY "  Transaction: " WS-ALERT-TXN-ID
           DISPLAY "  Card:        " WS-ALERT-CARD
           DISPLAY "  Amount:      " WS-ALERT-AMOUNT
           DISPLAY "  Rule:        " WS-ALERT-RULE
           DISPLAY "  Severity:    " WS-ALERT-SEVERITY
           DISPLAY "  Merchant:    " MERCHANTNAME
           DISPLAY "  Location:    " MERCHANTCITY " "
               MERCHANTSTATE " " MERCHANTCOUNTRY
           DISPLAY "  Timestamp:   " WS-ALERT-TIMESTAMP
           DISPLAY "---"
           .

       8000-PRINT-SUMMARY.
           DISPLAY " "
           DISPLAY "========================================"
           DISPLAY "  FRAUD MONITORING SESSION SUMMARY"
           DISPLAY "========================================"
           MOVE WS-MSGS-RECEIVED TO WS-FMT-COUNT
           DISPLAY "  Messages received:  " WS-FMT-COUNT
           MOVE WS-MSGS-CLEAN TO WS-FMT-COUNT
           DISPLAY "  Clean transactions: " WS-FMT-COUNT
           MOVE WS-ALERTS-RAISED TO WS-FMT-COUNT
           DISPLAY "  Alerts raised:      " WS-FMT-COUNT
           MOVE WS-PARSE-ERRORS TO WS-FMT-COUNT
           DISPLAY "  Parse errors:       " WS-FMT-COUNT
           DISPLAY "========================================"
           .

       9000-CLEANUP.
           IF WS-HSUB NOT = 0
               CALL 'MQCLOSE' USING WS-HCONN
                                     WS-HSUB
                                     MQCO-KEEP-SUB
                                     WS-COMP-CODE
                                     WS-REASON
           END-IF

           IF WS-HOBJ NOT = 0
               CALL 'MQCLOSE' USING WS-HCONN
                                     WS-HOBJ
                                     MQCO-NONE
                                     WS-COMP-CODE
                                     WS-REASON
           END-IF

           IF WS-HCONN NOT = 0
               CALL 'MQDISC' USING WS-HCONN
                                    WS-COMP-CODE
                                    WS-REASON
           END-IF
           .

Solution Walkthrough

Publisher Design Decisions

Non-persistent messages: The publisher uses MQPER-NOT-PERSISTENT for transaction events. This is a deliberate trade-off: non-persistent messages are faster (no disk write) but can be lost if the queue manager restarts. For real-time notifications, a missed alert is acceptable -- the customer will see the transaction on their next statement. If guaranteed delivery is required (e.g., for regulatory audit), persistence should be enabled.

No syncpoint: The publisher uses MQPMO-NO-SYNCPOINT, meaning the message is committed immediately without participating in a CICS unit of work. This ensures that the MQ put cannot cause the transaction authorization to roll back. If the MQ put fails, the transaction is still authorized -- the alert is simply not sent. The alternative (syncpoint integration) would risk rolling back an approved transaction due to an MQ issue, which is unacceptable for an authorization system.

Card masking: The card number is masked before being placed on the message queue. This is a security requirement -- even internal MQ messages should not contain full card numbers. The masking happens in the publisher so that no downstream subscriber ever sees the full number.

Subscriber Design Decisions

Durable subscription: The subscriber uses MQSO-DURABLE combined with MQSO-CREATE + MQSO-RESUME. This means the subscription persists even when the subscriber is not running. Messages published while the subscriber is down are queued and delivered when it reconnects. This prevents message loss during planned maintenance windows.

Wait with timeout: The subscriber uses MQGMO-WAIT with a 5-second interval. This allows the program to periodically check for shutdown signals while waiting for messages. An infinite wait (MQWI-UNLIMITED) would make the program unresponsive to shutdown requests.

Multiple rule checks per message: Each message is evaluated against all fraud rules, not just the first matching rule. A single transaction can trigger multiple alerts (e.g., high value AND high-risk country). This is important for fraud analysis -- the pattern of multiple triggers is more significant than any single trigger.

Message Flow Architecture

                                   +--> Mobile Notification Service
                                   |    (Cloud subscriber)
COBOL Auth Program                 |
  (AUTHPROC)                       |
       |                           |
       v                           |
  TXNEVTPB -----> MQ Topic --------+--> FRAUDMON
  (Publisher)     TXN.EVENTS.      |    (COBOL Fraud Monitor)
                  AUTHORIZED       |
                                   +--> Analytics Platform
                                        (Cloud subscriber)

Each subscriber receives its own copy of every message. Adding a new subscriber (e.g., a regulatory reporting system) requires only creating a new subscription -- no changes to the publisher or existing subscribers.


JCL for the Fraud Monitor Batch Job

//FRAUDMON JOB (ACCT),'FRAUD MONITOR',
//         CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//*
//* Run the fraud monitoring subscriber as a long-running
//* batch job. The program connects to MQ, subscribes to
//* the transaction events topic, and processes messages
//* until manually stopped or the queue manager quiesces.
//*
//STEP01   EXEC PGM=FRAUDMON
//STEPLIB  DD DSN=BANK.PROD.LOADLIB,DISP=SHR
//         DD DSN=IBM.MQ.SCSQCOBC,DISP=SHR
//         DD DSN=IBM.MQ.SCSQLOAD,DISP=SHR
//SYSOUT   DD SYSOUT=*
//SYSPRINT DD SYSOUT=*
//ALERTS   DD DSN=BANK.FRAUD.ALERTS,
//         DISP=MOD,
//         DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)

Discussion Questions

  1. Reliability vs. performance: The publisher uses non-persistent messages without syncpoint. Under what circumstances should you switch to persistent messages with syncpoint? What is the performance cost, and how would you measure it?

  2. Message ordering: If two transactions for the same card are authorized simultaneously on different CICS regions, can the fraud monitor receive them out of order? Does this affect fraud detection accuracy? How would you handle ordering if it matters?

  3. Subscriber scaling: During peak hours (Black Friday), transaction volume increases 5x. How would you scale the fraud monitoring subscriber? Can multiple instances of FRAUDMON subscribe to the same topic? What coordination issues arise?

  4. Dead letter handling: If the fraud monitor cannot parse a JSON message (corrupt data), the message is currently lost after the parse error counter is incremented. Design a dead-letter queue strategy that preserves unprocessable messages for later analysis.

  5. Schema evolution: If a new field (e.g., "deviceFingerprint") is added to the transaction event JSON, what happens to existing subscribers that do not expect this field? How would you manage schema versioning across publishers and subscribers?