CARDCOR/i is a bank card-issuing back office: cardholder and account masters, card
issuance, real-time authorization against open-to-buy, settlement of authorized purchases into a
posted ledger, average-daily-balance finance charges, delinquency and credit-limit management, a
statement/minimum-payment cycle with a printed spool, payments, a balanced general-ledger feed, and a
full dispute/chargeback lifecycle. It is a mixed-language IBM i application — fixed- and
free-form RPG, ILE COBOL, CL, DDS (PF/LF, display, printer) and embedded/DDL SQL — running in
library CARDCOR. This manual is the reference for the operator who runs the online screens
and the night-batch chain, and for the developer maintaining it. It is grounded entirely in the
committed source (creditcard-app/src/sources.mjs, src/seed.mjs, the
test/cc_*.mjs drivers and PLAN.md), and it is honest where the application
deliberately simplifies or where a documented gap exists.
CARDCOR/i services the life of a revolving credit-card account:
CCACCMNT) and issue plastics onto an open account (CCISSUE).AUTHTRN (CCAUTH).CCPOST).CCFIN).CCDELQ).CCSTMT).CCPAY).CCGL), cross-checked by a COBOL portfolio report
(CCACCRPT).CCDISP) and resolve it won or lost (CCCHGBK).Unlike the SQL-PL-centred sibling apps, CARDCOR/i keeps its business logic in the RPG/COBOL programs and uses DB2 for i primarily as a keyed record store, reached through native record I/O (CHAIN / READ / WRITE / UPDATE / SETLL over DDS PFs and LFs). SQL appears in two deliberate places:
exec sql select coalesce(max(id),0) into :w from cardcor.<file>
and bumps a local counter. This is a documented design-around for a platform quirk (a
SETGT *HIVAL / READP reposition over an output-opened file does not see
rows written earlier in the same job); embedded-SQL MAX always sees committed rows.
See F.2.CCGL writes balanced DR/CR pairs into
the SQL table GLENTRY and finalises the batch with a real EXEC SQL COMMIT
under the job's commitment definition; the COBOL CCACCRPT cross-checks with an
EXEC SQL SUM over GLENTRY.The application follows the proven POLARIS/i house style (the column-exact RPG C()/D()
helpers in sources.mjs) and is coded around several documented emulator quirks —
nested if/else only (free-form elseif mis-scopes its END-IF),
packed id parameters to match packed callers, UF A opens where a program both CHAINs and
WRITEs the same file, and PRTF field columns whose position digits end at DDS column 44. Everything runs
in library CARDCOR.
ONLINE (5250) FRONT-OFFICE NIGHT BATCH (CCNIGHT -> CCNITEQ *JOBQ)
------------- ------------ -------------------------------------
CCMENUP --CALL--> CCISSUE (issue card) CCPOST --settle AUTHTRN 'A' -> POSTTRN 'P'
opt 1 -> CCACCINQ CCAUTH (authorize) (+ACCBAL, release HELDAUTH, A->S)
opt 2 -> CCTRNINQ OTB check -> AUTHTRN CCFIN --ADB finance charge -> POSTTRN 'I'
(SFL over 'A'/'D' CCDELQ --late/over-limit fee -> POSTTRN 'F'
POSTLF) CCPAY (payment) CCGL --POSTTRN -> balanced GLENTRY DR/CR
-> POSTTRN 'Y' (EXEC SQL, COMMIT)
CCDISP (open disp.) CCSTMT --cut cycle -> STMTHDR + PRTF spool
-> DISPTRN 'O'
+ POSTTRN 'C' CCACCRPT (COBOL) -- portfolio report,
CCCHGBK (resolve) GLENTRY cross-check "IN BALANCE"
'W' stand / 'L' rebill
-> POSTTRN 'B'
\ /
\ /
v v
ACCTMST (account master: ACCBAL, HELDAUTH, DELQBKT, ASTAT, MINPAY, LASTBAL)
| \ \ journaled ledgers: AUTHTRN, POSTTRN, DISPTRN
| +--> AUTHTRN (auth log) (CCJRN, IMAGES(*BOTH))
| +--> POSTTRN (posted ledger) --POSTLF--> CCTRNINQ subfile
| +--> DISPTRN (dispute ledger) --DISPLF--> per-account dispute history
+------> STMTHDR (statement header) + STMTP spool + GLENTRY (GL feed)
A single event — say a purchase — flows: CCAUTH approves it and writes an
AUTHTRN 'A' row plus a HELDAUTH hold → the night CCPOST
settles it into a POSTTRN 'P' row, adds the amount to ACCBAL, releases the
hold and flips the auth marker 'A'→'S' so a re-run skips it → CCGL
later mirrors that posted row into a balanced GLENTRY DR/CR pair.
| Object | Type | Role |
|---|---|---|
| CARDHLDR | PF | Cardholder master (party data). |
| ACCTMST | PF | Account master (the heart of the app). |
| CARDMST | PF | Issued plastics (one+ per account). |
| CARDLF | LF | Cards keyed by account then card. |
| AUTHTRN | PF (journaled) | Authorization log (arrival AUTHID). |
| POSTTRN | PF (journaled) | Posted-transaction ledger (arrival POSTID). |
| POSTLF | LF | Posted txns by account (subfile source). |
| FEESCHED | PF | Fee/rate schedule (MIN/LTE/OVL). |
| STMTHDR | PF | Statement/cycle header. |
| DELQLF | LF | Delinquency-bucket management view over ACCTMST. |
| DISPTRN | PF (journaled) | Dispute/chargeback ledger (arrival DISPID). |
| DISPLF | LF | Disputes by account then dispute id. |
| GLENTRY | SQL table | Balanced GL DR/CR feed. |
| ACCTDSPF | DSPF | Account-inquiry display file. |
| TRNDSPF | DSPF (SFL) | Transaction-inquiry subfile display file. |
| CCMENUD | DSPF | Operator-menu display file. |
| STMTP | PRTF | Statement printer file (spool). |
| CCISSUE / CCAUTH / CCPOST / CCPAY | RPGLE | Issue, authorize, settle, pay. |
| CCFIN / CCDELQ / CCSTMT / CCGL | RPGLE | Finance charge, delinquency, statement, GL. |
| CCDISP / CCCHGBK | RPGLE | Open / resolve a dispute. |
| CCACCMNT | RPGLE | Account-master maintenance (A/C/I/X). |
| CCACCINQ / CCTRNINQ / CCMENUP | RPGLE | 5250 inquiries + menu. |
| CCACCRPT | CBLLE | COBOL portfolio report + GL cross-check. |
| CCSETUP | CLP | Single build entry point (objects + journal + seed). |
| CCNIGHT | CLP | Night-batch SBMJOB chain onto CCNITEQ. |
The full catalogue is 8 PFs + 3 LFs + 1 SQL table, 3 DSPF + 1 PRTF, 15 RPG programs, 1 COBOL program
and 2 CL programs, with three ledger tables journaled to CCJRN. Sections D and F expand each.
CARDCOR/i has no CICS transaction identifiers and no transid switch. On IBM i, each program is
reached by name from a 5250 command-entry line (or through the operator menu, or a JOBQ/scheduler
for the batch chain). The operator equivalent of “type a transid and Enter” is “type a
CALL command and Enter”. Before invoking anything, the job's library list must include
CARDCOR — the tested jobs run with LIBL = QSYS QGPL CARDCOR QTEMP and
CURLIB = CARDCOR.
| To do this | Type on the command line |
|---|---|
| Open the operator menu | CALL CARDCOR/CCMENUP |
| Account inquiry (direct) | CALL CARDCOR/CCACCINQ |
| Transaction inquiry (subfile, direct) | CALL CARDCOR/CCTRNINQ |
| Issue a card onto an open account | CALL CARDCOR/CCISSUE (PARM acct, card, expiry) |
| Authorize a purchase | CALL CARDCOR/CCAUTH (PARM acct, card, date, amt, MCC, rsp) |
| Apply a payment | CALL CARDCOR/CCPAY (PARM acct, amt) |
| Account-master maintenance | CALL CARDCOR/CCACCMNT (PARM action + fields) |
| Open / resolve a dispute | CALL CARDCOR/CCDISP / CALL CARDCOR/CCCHGBK |
| Run the whole night batch | CALL CARDCOR/CCNIGHT (or SBMJOB it) |
The front-office programs (CCISSUE, CCAUTH, CCPAY,
CCACCMNT, CCDISP, CCCHGBK) are callable with an
*ENTRY PLIST — they take parameters and return a response code, so in practice they
are invoked program-to-program (an acquiring feed, a teller front-end, or a test driver) rather than typed
by hand. The three interactive programs (CCMENUP, CCACCINQ, CCTRNINQ)
are 5250 screens. The batch programs are described in section C.
CARDCOR/i has three real 5250 programs. CCMENUP (over DDS CCMENUD) is the
operator menu; it routes option 1 to CCACCINQ and option 2 to
CCTRNINQ with a classic indicator-conditioned dynamic CALL, rejects any other
option with “Invalid option”, and exits on F3.
A plain (non-subfile) EXFMT loop over record format ACCINQF. The operator keys
an account number into IACCTNO and presses Enter; the program CHAINs
ACCTMST, then CHAINs CARDHLDR for the holder name, and renders status, credit
limit, balance, open-to-buy (CRLIMIT − ACCBAL − HELDAUTH), APR, minimum payment and
delinquency bucket. A not-found account clears the output fields and shows
“Account not found: <acct>”. F3 exits.
A header record plus a real SFL/SFLCTL subfile (TRNSFL under TRNCTL,
SFLPAG(0010) per page, SFLSIZ(0020)). The operator keys an account number; the
program clears the subfile (SFLCLR), then loads it from POSTLF (the posted-ledger
logical keyed by account) with SETLL/READ, writing one TRNSFL row per
posted transaction until the account changes, and displays it. The SFL indicators mirror the DDS numbering:
31=SFLDSP, 32=SFLDSPCTL, 33=SFLCLR, 34=SFLEND(*MORE);
SFLDSPCTL must be on before the first EXFMT or nothing renders.
PostID / Date / Ty / Amount / Description) sits on DDS
row 6, one line above the subfile body on row 7. An earlier revision authored the headers on the same row 7
as the first data fields, colliding the 1-char STRNTYP under the 2-char 'Ty'
literal and rendering Yy/Py/Iy in the Ty column; that DDS
row/column defect was found and corrected (SIM-2026-Q6). The Ty column now shows a clean single
Y/P/I. Negative amounts (payments) render with a correct leading
minus, because the amount is a plain 14A alphanumeric field populated by RPG
%char() (which carries the sign), not a numeric EDTCDE field.Honest statement: CARDCOR/i does not model a four-eyes maker–checker /
separate-authorization workflow. There is no “one user requests, a second user approves” step:
a CCAUTH approves-or-declines in one call, CCPAY and CCDISP act
immediately, and account maintenance through CCACCMNT applies at once. The manual documents the
control model the application does have:
AUTHTRN), every posted money
movement (POSTTRN) and every dispute (DISPTRN) is written as an
arrival-sequence row and journaled to CCJRN with IMAGES(*BOTH). This is the
after-the-fact reconstructable trail — the account balance can always be re-derived from the
posted ledger.CCISSUE rejects a card onto a non-open account (ASTAT≠'O');
CCAUTH declines any account not open; CCPOST settles only auths still
marked 'A' (approved-unsettled) and flips them to 'S' so a re-run is a
no-op; CCDISP rejects a dispute unless the posted txn exists and is a purchase
('P'); CCCHGBK rejects a dispute that is not still 'O'
(open).CCACCMNT add ('A') does a guarding
CHAIN(E) first and rejects if the account already exists — it never relies on the
WRITE itself to reject a duplicate (a design-around for the documented PG-049
unmonitored-dup-WRITE-absorption behaviour). Every maintenance UPDATE re-CHAINs the
record immediately before it (never a double-UPDATE of one read record).HELDAUTH hold that reduces
open-to-buy until settlement releases it (floored at 0), so a card cannot be over-authorized between
auth and posting.In sum, the control posture is immutable journaled ledgers + status/state gating + duplicate-add guarding, enforced in the application programs, rather than a segregation-of-duties approval workflow.
CARDCOR/i's servicing runs as a night-batch chain packaged by the CL program CCNIGHT,
which creates the CCNITEQ job queue and submits each step as its own SBMJOB in
sequence: CCPOST → CCFIN → CCDELQ → CCGL → CCSTMT. Each step is a
separate job on purpose — a documented platform quirk means rows written to an output-opened
file may not be visible to a program CALLed inline in the same job until the writer closes, so the steps are
not chained as one CL calling all five inline. (The test harness drives the individual steps directly where
it must assert between them; CCNIGHT is the packaged operator entry point that proves the chain
exists as one command.)
-- run the whole night chain (creates CCNITEQ, submits the five steps) CALL PGM(CARDCOR/CCNIGHT) -- or submit an individual step SBMJOB CMD(CALL PGM(CARDCOR/CCPOST)) JOB(CCPOSTJ) JOBQ(CARDCOR/CCNITEQ) SBMJOB CMD(CALL PGM(CARDCOR/CCSTMT) PARM('001')) JOB(CCSTMTJ) JOBQ(CARDCOR/CCNITEQ)
Most batch programs take no CALL parameters and read the whole account or ledger file forward;
only CCSTMT takes a parameter (the 3-digit cycle number). None of these programs reads a
control-date row — the posting date on the ledger rows they write is a fixed literal
(20260201) in the current source (see F.5), so the “processing date” is compiled-in
rather than operator-supplied.
| Program | Purpose | Reads / writes | Inputs | Outputs (DSPLY) | Frequency |
|---|---|---|---|---|---|
| CCPOST | Settle each approved-unsettled authorization into the ledger + balance. | Reads AUTHTRN (arrival); writes POSTTRN 'P'; UPDATEs ACCTMST (+ACCBAL, −HELDAUTH floored 0); flips AUTHRSP 'A'→'S'. |
None. | CCPOST SETTLED=n SKIP=n. |
Nightly. |
| CCFIN | Assess an average-daily-balance finance charge on every open, non-zero account. | Reads/UPDATEs ACCTMST; writes POSTTRN 'I'. |
None (ADB proxy = (LASTBAL+ACCBAL)/2; rate = APR/1200). | CCFIN CHARGED=n SKIP=n TOTFC=amt. |
Monthly (per cycle). |
| CCDELQ | Over-limit + late-fee assessment over the delinquency LF, with a freeze path. | Reads DELQLF; reads FEESCHED; writes POSTTRN 'F'; UPDATEs ACCTMST (+fee; ASTAT→'F' if DELQBKT≥3). |
None (fees from FEESCHED OVL/LTE, defaults 35.00/29.00). | CCDELQ LATE=n OVL=n FROZEN=n. |
Monthly (per cycle). |
| CCGL | Consolidate the posted ledger into balanced GL DR/CR pairs (embedded SQL, commitment control). | Reads POSTTRN (arrival); inserts two GLENTRY rows per posted row; EXEC SQL COMMIT. |
None (BATCHID literal 1). | CCGL BATCH=1 ROWS=n. |
Nightly / per cycle. |
| CCSTMT | Cut the statement cycle: new balance, minimum payment, STMTHDR row + PRTF spool. | Reads/UPDATEs ACCTMST (MINPAY, LASTBAL=NEWBAL); reads FEESCHED; writes STMTHDR; prints STMTP. |
Cycle number (3-digit *ENTRY PLIST). |
CCSTMT CUT=n CYCLE=nnn; spooled statement. |
Monthly (cycle cut). |
| CCACCRPT | COBOL portfolio report: total open-account balance, cross-check vs GLENTRY receivable DR. | Reads ACCTMST (COMP-3 FD); EXEC SQL SUM over GLENTRY. |
None. | CCACCRPT OPEN-ACCTS n BAL amt / NONOPEN-ACCTS n / GLENTRY-RECV-DR n AMT amt. |
After CCGL (reconcile). |
Front-office CCISSUE, CCAUTH, CCPAY, CCACCMNT,
CCDISP and CCCHGBK are on-demand callables, not part of the night chain; they are
driven as transactions arrive.
Computes open-to-buy = CRLIMIT − ACCBAL − HELDAUTH with packed-decimal math.
A blocked/non-open account (ASTAT≠'O') is declined. Otherwise if
AUTHAMT ≤ OTB the auth is approved ('A'): HELDAUTH rises by
the amount (the hold) and an AUTHTRN 'A' row is written; else declined ('D'),
no hold, but an AUTHTRN 'D' row is still logged. The response code is returned in the last PARM.
Seeded example (from cc_daycycle):
AC00000001 bal 200.00, limit 5000.00, held 0 -> OTB 4800.00
CCAUTH 300.00 -> A (300 <= 4800) HELDAUTH now 300.00
AC00000002 bal 4800.00, limit 5000.00 -> OTB 200.00
CCAUTH 200.00 -> A (fits exactly)
CCAUTH 500.00 -> D (OTB now 0, declined)
One forward pass over AUTHTRN in arrival sequence. For each auth still marked
'A' (approved-unsettled) it writes a POSTTRN 'P' (PURCHASE SETTLEMENT)
row, adds AUTHAMT to ACCTMST.ACCBAL, releases the hold
(HELDAUTH −= AUTHAMT, floored at 0), and flips the auth marker 'A'→'S'
in place so a re-run is idempotent (already-settled 'S'/declined 'D' auths are
skipped). It seeds the next POSTID once from COALESCE(MAX(POSTID),0) via embedded
SQL, then bumps a local counter per WRITE.
Expected DSPLY: CCPOST SETTLED=2 SKIP=1 2 approved auths settled, 1 declined skipped
Forward pass over ACCTMST. For every account with ASTAT='O' and
ACCBAL > 0: ADB = (LASTBAL + ACCBAL) / 2 (a two-point average-daily-balance
proxy — a real system tracks per-day balances; documented in PLAN.md), monthly periodic rate
= APR / 1200 (APR is an annual percent, so /100 for a fraction and /12 for the month), finance
charge = %DECH(ADB × rate). A positive charge is written as a POSTTRN 'I'
(FINANCE CHARGE) row and added to ACCBAL; zero-balance accounts are skipped.
Worked example (SIM-2026-M3, AC1 at month-accumulated 730.00):
ADB 730.00 x (18.00/1200) = 730.00 x 0.015 = 10.95 -> POSTTRN 'I' 10.95
Forward pass over DELQLF (keyed DELQBKT then ACCTNO, so it reads
worst-first within a bucket). Per account: if ACCBAL > CRLIMIT it assesses an over-limit fee
(FEESCHED 'OVL', default 35.00); else if DELQBKT ≥ 1 (past due) it assesses a
late fee (FEESCHED 'LTE', default 29.00). Either fee is written as a POSTTRN 'F'
(DELINQUENCY FEE) row and added to ACCBAL; if DELQBKT ≥ 3 the
account is frozen (ASTAT→'F').
CCDELQ's header comment says it “bumps
DELQBKT”, but the code never actually increments DELQBKT anywhere — it
writes the fee row and grows the balance only. Because the freeze path requires DELQBKT ≥ 3
and nothing in CCDELQ advances the bucket, an account can be frozen only if it is seeded
at bucket 3 (as the day-cycle test's AC00000003 is) or its balance exceeds the limit; a
chronically past-due account at bucket 1 will be late-fee'd every cycle but is structurally never
auto-frozen by CCDELQ alone. This is a confirmed comment/implementation mismatch, not a
platform bug.Given a cycle number, a forward pass over ACCTMST cuts a statement for every open, non-zero
account: NEWBAL = ACCBAL; minimum payment = the greater of a floor
(FEESCHED 'MIN' FEEAMT, default 25.00) or a percentage of the balance
(FEESCHED 'MIN' FEEFCTR, default 0.020 = 2%), never more than the whole balance. It writes a
STMTHDR row (keyed SACCTNO+CYCLENO), records MINPAY on the account,
snapshots LASTBAL = NEWBAL (setting up the next cycle's ADB start point), and prints a real
STMTP statement line to the spool.
Example (cc_daycycle, AC2 at 5125.00): 2% x 5125.00 = 102.50 > 25.00 floor
MINPAY = 102.50 STMTHDR written LASTBAL <- 5125.00
CCSTMT CUT=n CYCLE=001
One pass over POSTTRN in arrival sequence, mapping each posted row to a balanced DR/CR pair
(chart-of-accounts-lite, documented, not a real GL): P DR 1400-RECV / CR 2200-STLMT;
Y DR 1000-CASH / CR 1400-RECV; F DR 1400-RECV / CR 4200-FEEIN; I
DR 1400-RECV / CR 4100-FININ. TRNAMT is signed; each posting uses %ABS so DR and
CR magnitudes match, keeping SUM(DR) = SUM(CR) by construction. Both inserts run as embedded
EXEC SQL under the job's commitment definition, finalised with a real EXEC SQL
COMMIT.
CCGL carries no posting watermark, so it
re-posts the whole POSTTRN table every run. After a single run the row count is exactly
2 × the posted-row count; run across multiple cycles the GLENTRY count
inflates past that (same non-idempotent-batch family as the sibling apps' GL posters). It stays balanced
(SUM(DR)=SUM(CR)) every time; operators run it once per cycle by discipline rather than relying
on a re-run guard.POSTTRN, so it
runs after CCPOST/CCFIN/CCDELQ have written their rows. This is
the exact order CCNIGHT submits: CCPOST → CCFIN → CCDELQ → CCGL
→ CCSTMT.GLENTRY receivable DR total, so it runs after CCGL.All files are in library CARDCOR, grounded in src/sources.mjs. Money is packed
decimal 11P2 (nine integer digits) or 9P2; dates are stored as zoned integers in
YYYYMMDD (or YYYYMM forms as noted); APR is packed 5P2 as an annual
percent (18.00 = 18%); the fee factor is packed 5P3 (0.020 = 2%).
| Field | Type | Meaning |
|---|---|---|
| CHNO | 7A | Cardholder number (key). |
| HNAME | 25A | Holder name. |
| ADDR / CITY / ST / ZIP | 25A / 15A / 2A / 5A | Mailing address. |
| DOB | 8S 0 | Date of birth (YYYYMMDD). |
| Field | Type | Meaning |
|---|---|---|
| ACCTNO | 10A | Account number (key), e.g. AC00000001. |
| CHNO | 7A | Owning cardholder. |
| CRLIMIT | 11P 2 | Credit limit. |
| CASHLIM | 11P 2 | Cash-advance limit. |
| ACCBAL | 11P 2 | Current balance (the running revolving balance). |
| HELDAUTH | 11P 2 | Sum of open (unsettled) authorization holds. |
| APR | 5P 2 | Annual percentage rate (percent; 18.00 = 18%). |
| CYCDAY | 2S 0 | Statement cycle day-of-month. |
| ASTAT | 1A | Status: O open, F frozen, C closed. |
| DELQBKT | 1S 0 | Delinquency bucket (0–n; drives fees/freeze). See F.5 note. |
| LASTBAL | 11P 2 | Last statement balance (ADB start point). |
| MINPAY | 9P 2 | Minimum payment due (set by CCSTMT). |
| Field | Type | Meaning |
|---|---|---|
| CARDNO | 16A | 16-digit card number (key). |
| ACCTNO | 10A | Owning account. |
| EXPDT | 6S 0 | Expiry (YYYYMM). |
| CSTAT | 1A | Card status: A active (written by CCISSUE); B blocked. |
CARDLF is a logical over CARDMST keyed ACCTNO then
CARDNO — an account's cards in order.
| Field | Type | Meaning |
|---|---|---|
| AUTHID | 8S 0 | Arrival-sequence auth id (key). |
| ACCTNO / CARDNO | 10A / 16A | Account and card authorized. |
| AUTHDT | 8S 0 | Authorization date (YYYYMMDD). |
| AUTHAMT | 11P 2 | Authorized amount. |
| AUTHRSP | 1A | Response: A approved, D declined; CCPOST flips A→S when settled. |
| MCC | 4A | Merchant category code. |
| Field | Type | Meaning |
|---|---|---|
| POSTID | 8S 0 | Arrival-sequence posted id (key). |
| ACCTNO | 10A | Account. |
| POSTDT | 8S 0 | Post date (YYYYMMDD; literal 20260201 in current source — see F.5). |
| TRNTYPE | 1A | P purchase, Y payment, F fee, I interest, C dispute provisional credit, B rebill. See F.4. |
| TRNAMT | 11P 2 | Signed amount (payments/credits negative). |
| DESCR | 20A | Free-text detail (e.g. PURCHASE SETTLEMENT). |
POSTLF is a logical over POSTTRN keyed ACCTNO then
POSTID — the source the CCTRNINQ subfile loads from.
| Field | Type | Meaning |
|---|---|---|
| FEECD | 3A | Fee code (key): MIN, LTE, OVL. |
| FEEAMT | 9P 2 | Flat amount / floor. |
| FEEFCTR | 5P 3 | Rate factor (e.g. 0.020 = 2% for MIN). |
| FDESC | 20A | Description. |
Seeded rows (from the day-cycle driver): MIN 25.00 / 0.020 (min-payment
floor/pct), LTE 29.00 (late fee), OVL 35.00 (over-limit fee). Programs fall back
to hard-coded defaults (25.00/0.020, 29.00, 35.00) if a code is missing.
| Field | Type | Meaning |
|---|---|---|
| SACCTNO / CYCLENO | 10A / 3S 0 | Account + cycle number (key). |
| STMTDT | 8S 0 | Statement date. |
| NEWBAL | 11P 2 | New balance billed. |
| MINDUE | 9P 2 | Minimum payment due. |
| SDUEDT | 8S 0 | Payment due date. |
| FINCHG | 9P 2 | Finance charge shown on the statement (0 in current CCSTMT). |
A logical over ACCTMST exposing ACCTNO, CHNO, CRLIMIT, ACCBAL, APR, ASTAT, DELQBKT,
MINPAY, keyed DELQBKT then ACCTNO so a delinquency pass reads
worst-first-in-bucket order. CCDELQ scans this LF but CHAINs the base ACCTMST for
its UPDATE.
| Field | Type | Meaning |
|---|---|---|
| DISPID | 8S 0 | Arrival-sequence dispute id (key). |
| ACCTNO | 10A | Disputing account. |
| DPOSTID | 8S 0 | The disputed POSTTRN.POSTID (a purchase). |
| DISPDT | 8S 0 | Dispute date. |
| DISPAMT | 11P 2 | Disputed amount. |
| DREASON | 4A | Dispute reason code. |
| DSTAT | 1A | Lifecycle: O open (provisional credit issued), W won (credit permanent), L lost (credit reversed/rebilled). |
DISPLF is a logical over DISPTRN keyed ACCTNO then
DISPID — an account's dispute history in order.
| Column | Type | Meaning |
|---|---|---|
| BATCHID | INTEGER | GL batch id (literal 1 from CCGL). |
| ACCT | CHAR(10) | GL account, e.g. 1400-RECV, 2200-STLMT. |
| DRCR | CHAR(1) | D debit / C credit. |
| AMT | DECIMAL(11,2) | Posting magnitude (always positive; %ABS applied). |
| SRCPGM | CHAR(10) | Source program (CCGL). |
CARDCOR, load the
source with the seeder, compile CCSETUP and run it —
CALL PGM(CARDCOR/CCSETUP) creates every object, journals the three ledger tables to
CCJRN, and is the single build entry point.CALL CARDCOR/CCACCMNT (add / change / close accounts) and
CALL CARDCOR/CCISSUE (issue a card onto an open account).CALL CARDCOR/CCAUTH approves or declines
against open-to-buy and logs an AUTHTRN row; approvals place a HELDAUTH hold.CALL CARDCOR/CCPAY (account + amount) reduces the
balance and writes a POSTTRN 'Y' row.CALL CARDCOR/CCDISP opens a dispute (provisional credit),
CALL CARDCOR/CCCHGBK resolves it won/lost.CALL CARDCOR/CCNIGHT queues
CCPOST → CCFIN → CCDELQ → CCGL → CCSTMT onto CCNITEQ.CALL CARDCOR/CCMENUP (account / transaction
inquiry).Pre-checks: confirm the job's library list includes CARDCOR; confirm the three
ledger tables are journaled to CCJRN (they are, after CCSETUP).
Post-checks after posting (CCPOST):
CCPOST SETTLED=n SKIP=n — SETTLED equals the count of auths that were
'A'; those auths are now 'S', each has a POSTTRN 'P' row, and
each account's ACCBAL rose by its amount while HELDAUTH fell (floored 0).CCPOST should report SETTLED=0 (all now 'S') —
the idempotency check.CCPOST) so balances are current.CCFIN then CCDELQ
(order matters — both feed the balance the statement bills).CCGL (once per cycle — no re-run guard; see C.2 note).CALL CARDCOR/CCSTMT PARM('001') (the cycle number). Confirm
CCSTMT CUT=n CYCLE=001 and inspect the spooled statement.CALL CARDCOR/CCACCRPT.Reconciling figures (the same the day-cycle and SIM drivers assert):
CCACCRPT OPEN-ACCTS n BAL amt = count and SUM(ACCBAL)
over ASTAT='O' accounts; non-open accounts are counted separately.SUM(AMT) WHERE DRCR='D' equals SUM(AMT) WHERE
DRCR='C' in GLENTRY (e.g. the monthly SIM closed at
DR=CR=5017.49). CCGL writes exactly two rows per POSTTRN row,
so after one run COUNT(GLENTRY) = 2 × COUNT(POSTTRN).CCACCRPT GLENTRY-RECV-DR n AMT amt is the SUM of
1400-RECV debit rows written by CCGL — the “IN BALANCE”
idiom comparing the ledger's receivable side to the account portfolio.CCFIN CHARGED=n TOTFC=amt; per account
%DECH((LASTBAL+ACCBAL)/2 × APR/1200) (e.g. ADB 730.00 @ 18% = 10.95).ACCTMST.MINPAY and STMTHDR.MINDUE, the
greater of 25.00 or 2% of the new balance (e.g. 5125.00 → 102.50).Each program DSPLYs a one-line summary. The safe-to-rerun posture differs per program:
| Situation | Behaviour | Action |
|---|---|---|
| Re-run CCPOST | Already-settled auths are 'S' and skipped. | Idempotent — safe to re-submit; only genuinely-approved-unsettled auths settle. |
| Re-run CCISSUE for an existing card | Idempotent CHAIN check skips the write. | Safe no-op (CCISSUE SKIP: CARD EXISTS). |
| Re-run CCGL | Not idempotent — re-posts the whole ledger; GLENTRY count inflates. | Run once per cycle by discipline. GL stays balanced but rows duplicate; do not re-run to “top up”. |
| Re-run CCFIN / CCDELQ / CCSTMT | No period watermark — each re-run re-charges / re-fees / re-cuts. | Run each exactly once per cycle. CCSTMT is keyed SACCTNO+CYCLENO, so re-cutting the same cycle number is blocked by the unique key; a new cycle number cuts again. |
| CCAUTH against a frozen/closed account | Declined (ASTAT≠'O'). | Expected; an AUTHTRN 'D' row is still logged for the trail. |
| CCDISP rejected | Account missing, POSTID missing, or the posted txn is not a purchase ('P'). | POUTRSP='R', no rows written; correct the input and retry. |
| CCCHGBK rejected | Dispute missing, not still open, or bad outcome (not W/L). | POUTRSP='R'; only an open dispute can be resolved once. |
| Duplicate account add (CCACCMNT 'A') | Guarding CHAIN detects it; the existing row is not overwritten. | RSP=R; the balance/limit are unchanged (verified by cc_build). |
POSTTRN (and auths to
AUTHTRN, disputes to DISPTRN) with IMAGES(*BOTH) on CCJRN,
an account's balance is fully reconstructable from the posted ledger for reconciliation and recovery. The
CCGL non-watermark and CCDELQ non-increment behaviours (F.5) are the two items an
operator must manage by discipline rather than rely on the code to guard.The complete program surface, from src/sources.mjs. All objects are in library
CARDCOR; source is held as JS string constants and loaded into the library's source physical
files (QDDSSRC / QRPGLESRC / QCBLLESRC / QCLSRC /
QSQLSRC) by src/seed.mjs.
ASTAT='O');
idempotent (CHAIN(E) CARDR first, skip if the card already exists); writes a
CARDMST row with CSTAT='A' for the caller-supplied card number.WOTB = CRLIMIT − ACCBAL − HELDAUTH (packed).
Non-open account or amount > OTB → 'D'; else 'A' with
HELDAUTH += AUTHAMT. Always writes an AUTHTRN row (A or D). Next
AUTHID via committed MAX.AUTHTRN; for each AUTHRSP='A': write POSTTRN 'P',
ACCBAL += AUTHAMT, HELDAUTH −= AUTHAMT (floored 0), flip
'A'→'S'. DSPLYs SETTLED/SKIP. Seeds POSTID once from
committed MAX.ACCBAL by the payment, floored at 0 (an overpayment zeroes the balance; the excess
is not carried as a credit — documented simplification), writes a POSTTRN 'Y'
row with a negative TRNAMT. Guards the account exists.ACCTMST; for open, non-zero accounts:
WADB=(LASTBAL+ACCBAL)/2, WRATE=APR/1200,
WFC=%DECH(WADB×WRATE); if >0, write POSTTRN 'I' and add to balance.
Nested if/else only.DELQLF: ACCBAL>CRLIMIT → over-limit fee (OVL);
else DELQBKT≥1 → late fee (LTE). Writes POSTTRN 'F', adds to balance,
freezes (ASTAT='F') if DELQBKT≥3. Note: never increments
DELQBKT (F.5).STMTHDR, sets MINPAY and
LASTBAL=NEWBAL, prints STMTP spool.POSTTRN; maps each row to a balanced DR/CR pair by TRNTYPE
(P/Y/F/I), inserts two GLENTRY rows using %ABS(TRNAMT), ends with
EXEC SQL COMMIT. No period watermark (F.5).POSTID exists and
is 'P'; writes DISPTRN 'O', a POSTTRN 'C' provisional credit
(negative amount), and reduces ACCBAL by the dispute amount (Reg-Z provisional credit).
PPOSTID is packed to match the packed caller. POSTTRN opened
UF A (both CHAIN and WRITE).'W' won → DISPTRN 'O'→'W', balance
unchanged; 'L' lost → write POSTTRN 'B' rebill (+amount), restore
ACCBAL, DISPTRN 'O'→'L'. Guards the dispute exists and is still open.
Nested if/else only.'A' add (guarding CHAIN, reject if exists),
'C' change (limit/APR/status), 'I' inquire (balance/status out),
'X' close (ASTAT='C'). Every UPDATE re-CHAINs first.ACCTMST byte-for-byte; totals ACCBAL for 'O'
accounts; EXEC SQL SUM over GLENTRY receivable DR rows; “IN
BALANCE” cross-check. Zoned nS0 map to PIC S9(n) DISPLAY, packed
nP2 to COMP-3.CCSETUP: single build entry point — DLTF/CRTPF/CRTLF/CRTDSPF/CRTPRTF/RUNSQLSTM,
CRTBNDRPG/CRTBNDCBL/CRTCLPGM for every program, then CRTJRNRCV/CRTJRN/STRJRNPF
(AUTHTRN, POSTTRN, DISPTRN to CCJRN).
CCNIGHT: creates CCNITEQ and SBMJOBs the five night steps.Every arrival-sequence id (AUTHID, POSTID, DISPID) is generated
with embedded SQL, not a native reposition:
-- once at entry for a multi-write batch, then bump a local counter per WRITE
exec sql select coalesce(max(postid),0) into :wnewid from cardcor.posttrn;
wnewid += 1;
This is a documented design-around (PLAN.md §5): a SETGT *HIVAL / READP
next-id generator over an output-opened file does not see rows written earlier in the same job, so it
returned 1 on every call after the first and the resulting duplicate-key WRITEs were silently
dropped. Embedded-SQL MAX always sees committed rows. Multi-write batch programs
(CCPOST/CCFIN/CCDELQ) query MAX once then bump locally
within the run. Other embedded-SQL idioms: CCGL's two INSERTs + COMMIT,
and CCACCRPT's SELECT COUNT/SUM ... INTO.
packed(8:0) CALL argument passed into a zoned
8S0 callee parameter is corrupted (read as the wrong representation). Because
CCDISP/CCCHGBK use the id as a CHAIN key, those id parameters are declared
packed (PPOSTID, PDISPID) to match the natural packed caller — a
host-language-type-match discipline, not just a width match.CCSETUP creates journal receiver CCRCV and journal
CCJRN, then STRJRNPF ... IMAGES(*BOTH) on the three audit-critical ledgers:
AUTHTRN, POSTTRN, DISPTRN. Both before- and after-images are
captured, so every row insert on those tables is journaled and reconstructable (the
cc_journal suite verifies this including a receiver roll).STRCMTCTL) and finalises with a real
EXEC SQL COMMIT — a documented quirk is that commitment control / ROLLBACK are
honored on the SQL path but not the native-record path, so the GL posting deliberately uses the SQL
path for real commit semantics.RVKOBJAUT *ALL +
GRTOBJAUT *USE on a batch-written file can throw SFF9802 even under QSECOFR;
the app's authority proof grants *CHANGE (not *USE) to a writer on a
batch-written file.| Code | Field | Meaning |
|---|---|---|
| P | POSTTRN.TRNTYPE | Purchase settlement (CCPOST). |
| Y | POSTTRN.TRNTYPE | Payment received (CCPAY; negative amount). |
| F | POSTTRN.TRNTYPE | Fee — late or over-limit (CCDELQ). |
| I | POSTTRN.TRNTYPE | Interest / finance charge (CCFIN). |
| C | POSTTRN.TRNTYPE | Dispute provisional credit (CCDISP; negative amount). |
| B | POSTTRN.TRNTYPE | Dispute rebill (CCCHGBK lost; +amount). |
| A / D / S | AUTHTRN.AUTHRSP | Approved / declined / settled (A flipped to S by CCPOST). |
| O / W / L | DISPTRN.DSTAT | Dispute open / won / lost. |
| O / F / C | ACCTMST.ASTAT | Account open / frozen / closed. |
| A / B | CARDMST.CSTAT | Card active / blocked. |
| A / R / C / I / X | CCACCMNT POUTRSP | Add / reject / change / inquire / close response. |
Grounded in PLAN.md §6 and the source; all confirmed correct-as-designed or documented, none a platform bug hidden from the operator:
CCDELQ alone (freeze needs DELQBKT≥3); freeze is reachable
only via an over-limit balance or a pre-seeded bucket. Comment/implementation mismatch.SUM(DR)=SUM(CR)), but the row count inflates on re-runs.POSTDT is a compiled-in literal (20260201) in
CCPAY/CCFIN/CCDELQ/CCDISP/CCCHGBK;
there is no operator-set processing-date control row.CCPAY floors the balance at 0; an overpayment leaves a
zero balance and the excess is not carried as a credit balance.CCFIN uses (LASTBAL+ACCBAL)/2, not true
per-day balances.Platform quirks the app is coded around (not fixed in the engine): nested if/else only
(free-form elseif mis-scopes its END-IF); free-form CHAIN can't reference a
fixed-form KLIST; output-opened-file read invisibility (hence embedded-SQL MAX); a native
WRITE against an input-only (IF) F-spec is silently lost (hence UF A opens);
PRTF field-position digits end at DDS col 44; packed/zoned parameter-type match for CHAIN keys.
(LASTBAL+ACCBAL)/2 (not true per-day tracking), × the monthly periodic rate.18.00 = 18%). The monthly periodic rate is
APR/1200.open-to-buy = CRLIMIT − ACCBAL −
HELDAUTH. An approval places a HELDAUTH hold until settlement releases it.'W' won) — the provisional credit
becomes permanent. The opposite ('L' lost) rebills the amount.COMMIT (or undone by ROLLBACK). CARDCOR/i uses it on the GL path
(CCGL), where the emulator honors it.≥3) the
freeze path. Note: CCDELQ does not itself advance this value (F.5).CCDISP opens it (DISPTRN 'O'),
posts a provisional credit (POSTTRN 'C') and drops the balance while it is open (Reg-Z).CCFIN), written as a POSTTRN 'I' row and added
to the balance.CCPOST releases the matching amount).CCPOST (auth marker 'A'→'S') and
CCISSUE (dup CHAIN) are idempotent; CCGL/CCFIN/CCDELQ
are not (run once per cycle).AUTHTRN,
POSTTRN, DISPTRN) are journaled with IMAGES(*BOTH).CCSTMT).CRLIMIT − ACCBAL − HELDAUTH — the amount still available to authorize.CCPOST writes
POSTTRN 'P', raises ACCBAL and releases the hold.STMTP) and the spooled output it produces — here the printed
statement cut by CCSTMT.CCNIGHT submits the night chain onto CCNITEQ.CCTRNINQ loads a subfile of an
account's posted transactions from POSTLF.POSTTRN: P/Y/F/I/C/B (F.4).