Module 4: Key Data Elements Deep Dive
1 / 24
You've parsed the structure. Now understand the meaning.
Field Name What It Tells You
───── ──── ─────────────────
DE2 Primary Account Number Which card
DE3 Processing Code What type of transaction
DE4 Amount How much money
DE7 Transmission DateTime When (UTC)
DE11 STAN Unique transaction ID
DE35 Track 2 Data Swipe data (PAN + expiry + more)
DE37 RRN Network reference number
DE39 Response Code Approved or why not
These fields appear in 90% of authorization messages.
PAN: 4532015112830366
├──────┤├──────────┤├┤
IIN/BIN Account Check
Number Digit
IIN First Digits:
4... = Visa
51-55... = Mastercard
34, 37... = American Express
6011, 65.. = Discover
Key facts:
453201******0366Validate PAN integrity - catches typos and transmission errors.
PAN: 4 5 3 2 0 1 5 1 1 2 8 3 0 3 6 6
Step 1: From right, double every second digit
8 5 6 2 0 1 10 1 2 2 16 3 0 3 12 6
Step 2: If result > 9, subtract 9
8 5 6 2 0 1 1 1 2 2 7 3 0 3 3 6
Step 3: Sum all digits
8+5+6+2+0+1+1+1+2+2+7+3+0+3+3+6 = 50
Step 4: Check: sum % 10 == 0?
50 % 10 = 0 ✓ Valid!
func ValidateLuhn(pan string) bool {
sum := 0
double := false
// Process right-to-left
for i := len(pan) - 1; i >= 0; i-- {
digit := int(pan[i] - '0')
if digit < 0 || digit > 9 {
return false
}
if double {
digit *= 2
if digit > 9 {
digit -= 9
}
}
sum += digit
double = !double
}
return sum%10 == 0
}
DE3: 00 00 00
├┤ ├┤ ├┤
│ │ └─ To Account Type
│ └─ From Account Type
└─ Transaction Type
Transaction Type (first 2 digits):
00 = Purchase
01 = Cash Advance
09 = Purchase with Cash Back
20 = Refund
30 = Balance Inquiry
Account Type (digits 3-6):
00 = Default
10 = Savings
20 = Checking
30 = Credit
Example: 010020 = Cash Advance from Credit to Checking
Field: DE4 (Transaction Amount)
Type: Fixed n 12
Value: "000000010000" = ???
The answer depends on currency!
Currency Exponents (DE49):
| Currency | Code | Exponent | "000000010000" means |
|---|---|---|---|
| USD | 840 | 2 | $100.00 |
| EUR | 978 | 2 | €100.00 |
| JPY | 392 | 0 | ¥10000 |
| KWD | 414 | 3 | 10.000 KWD |
Always pair DE4 with DE49!
Cross-border transaction: US cardholder in Japan
╔═══════════════════════════════════════════════════════╗
║ Merchant (Tokyo) Acquirer Cardholder (US) ║
║ ¥5000 $33.00 $33.00 ║
╚═══════════════════════════════════════════════════════╝
↓ ↓ ↓
DE4 DE5 DE6
"000000005000" "000000003300" "000000003300"
DE49="392" DE50="840" DE51="840"
(JPY) (USD) (USD)
DE7: Transmission Date/Time - n 10 (MMDDhhmmss) - UTC
DE12: Local Time - n 6 (hhmmss) - Terminal timezone
DE13: Local Date - n 4 (MMDD) - Terminal timezone
Example: Transaction in New York at 2:30 PM EST
DE7 = "0315193045" (UTC: 19:30:45)
DE12 = "143045" (Local: 14:30:45)
DE13 = "0315" (Local: March 15)
Critical issues:
┌────────────────────────────────────────────────────────┐
│ │
│ DE11 (STAN) DE37 (RRN) │
│ ────────── ────────── │
│ 6 digits 12 alphanumeric │
│ Terminal-assigned Network-assigned │
│ Unique per terminal/day Unique network-wide │
│ For message matching For customer service lookup │
│ │
│ Example: "123456" Example: "240315123456" │
│ │
└────────────────────────────────────────────────────────┘
STAN links request↔response RRN appears on receipts and statements
Track 2: 4532015112830366D2512101DDDDDDDDDDDDD
├──────PAN──────┤│├──┤├─┤├──────────┤
│ │ │ │
│ │ │ └ Discretionary data
│ │ └ Service code (3 digits)
│ └ Expiry YYMM
└ Separator (= or D)
Components:
- PAN: 4532015112830366
- Separator: D
- Expiry: 2512 (December 2025)
- Service Code: 101
- Discretionary: DDDDDDDDDDDDD
Type: LLVAR z..37 (z = Track 2 code set)
Service Code: 1 0 1
│ │ └─ Digit 3: PIN/Service restrictions
│ └─ Digit 2: Authorization type
└─ Digit 1: Interchange rules
Digit 1 Digit 2 Digit 3
──────── ───────── ────────
1 = Intl+Chip 0 = Normal 0 = No restrictions
2 = Intl+Mag 2 = Call issuer 1 = No cash
5 = Natl+Chip 4 = Except floor 3 = ATM only
6 = Natl+Mag 6 = PIN required
Example: 201 = International magnetic, normal auth, no cash
type Track2Data struct {
PAN string
ExpiryYYMM string
ServiceCode string
Discretionary string
}
func ParseTrack2(track2 string) (Track2Data, error) {
// Find separator (= or D)
sepIdx := strings.IndexAny(track2, "=D")
if sepIdx == -1 {
return Track2Data{}, fmt.Errorf("no separator")
}
pan := track2[:sepIdx]
rest := track2[sepIdx+1:]
// Expiry (4) + Service code (3) + Discretionary
return Track2Data{
PAN: pan,
ExpiryYYMM: rest[0:4],
ServiceCode: rest[4:7],
Discretionary: rest[7:],
}, nil
}
Code Meaning Category
──── ─────── ────────
00 Approved ✓ Success
01 Refer to issuer ⚠ Referral
05 Do not honor ✗ Decline
12 Invalid transaction ✗ Decline
14 Invalid card number ✗ Decline (Luhn fail?)
41 Lost card - pick up ✗ Decline (retain card)
51 Insufficient funds ✗ Decline
54 Expired card ✗ Decline
55 Incorrect PIN ✗ Decline
91 Issuer unavailable ⚠ Retry
96 System malfunction ⚠ Retry
Rule: Only 00 means approved. Everything else needs handling.
type ResponseCategory string
const (
Approved ResponseCategory = "approved"
Declined ResponseCategory = "declined"
Referral ResponseCategory = "referral"
SystemError ResponseCategory = "system_error"
PickUp ResponseCategory = "pick_up"
)
func Categorize(code string) ResponseCategory {
switch code {
case "00":
return Approved
case "01", "02":
return Referral
case "41", "43":
return PickUp
case "91", "96":
return SystemError
default:
return Declined
}
}
For balance inquiries and complex transactions:
DE54: "0001840C000000150000"
├┤├┤├──┤│├────────────┤
00 01 840 C 000000150000
Structure (20 bytes per record):
- Bytes 1-2: Account type (00 = default)
- Bytes 3-4: Amount type (01 = available balance)
- Bytes 5-7: Currency (840 = USD)
- Byte 8: Sign (C = credit, D = debit)
- Bytes 9-20: Amount (000000150000 = $1500.00)
Multiple records can be concatenated.
0100 Message - Typical Fields
═══════════════════════════════════════════════════════
MTI "0100" Auth request
DE2 "4532015112830366" PAN (LLVAR)
DE3 "000000" Purchase
DE4 "000000010000" $100.00
DE7 "0315143045" Mar 15, 14:30:45 UTC
DE11 "123456" STAN
DE12 "143045" Local time
DE13 "0315" Local date
DE14 "2512" Card expiry Dec 2025
DE22 "051" Chip read
DE35 "4532...D2512101..." Track 2
DE37 "240315123456" RRN
DE41 "TERM0001" Terminal ID
DE42 "MERCH00001" Merchant ID
DE49 "840" USD
═══════════════════════════════════════════════════════
1. PAN Length
✗ if len(pan) != 16 // Amex is 15!
✓ if len(pan) < 13 || len(pan) > 19
2. Amount Without Currency
✗ amount := 10000 // $100 or ¥10000?
✓ type Money struct { Amount int64; Currency string }
3. Track 2 Separator
✗ strings.Index(track2, "=") // Misses 'D'
✓ strings.IndexAny(track2, "=D")
4. Response Code as Number
✗ if code == 0 // String "00" vs int 0
✓ if code == "00"
5. Year in Dates
✗ year := time.Now().Year() // December edge case!
✓ Handle Dec→Jan transition explicitly
┌─────────────────────────────────────────────────────┐
│ POS Entry Mode (DE22) determines required fields │
│ │
│ DE22 = "051" (Chip) │
│ → DE55 (EMV data) REQUIRED │
│ → DE35 (Track 2) OPTIONAL │
│ │
│ DE22 = "021" (Magnetic Stripe) │
│ → DE35 (Track 2) REQUIRED │
│ → DE55 ABSENT │
│ │
│ DE22 = "010" (Manual/Keyed) │
│ → DE2, DE14 from keyboard │
│ → No DE35, no DE55 │
└─────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ FIELD │ TYPE │ KEY POINTS │
├─────────┼─────────────┼─────────────────────────────┤
│ DE2 │ LLVAR n..19 │ Luhn, mask in logs │
│ DE3 │ Fixed n 6 │ TxnType+FromAcct+ToAcct │
│ DE4 │ Fixed n 12 │ Pair with DE49 for decimals │
│ DE7 │ Fixed n 10 │ MMDDhhmmss UTC, no year │
│ DE11 │ Fixed n 6 │ STAN, unique per term/day │
│ DE12 │ Fixed n 6 │ hhmmss local time │
│ DE13 │ Fixed n 4 │ MMDD local date │
│ DE35 │ LLVAR z..37 │ PAN=YYMMSSC+disc │
│ DE37 │ Fixed an 12 │ RRN, on receipts │
│ DE39 │ Fixed an 2 │ "00"=approved │
│ DE49 │ Fixed n 3 │ ISO 4217 currency │
│ DE54 │ LLLVAR │ 20-byte balance records │
└──────────────────────────────────────────────────────┘
When debugging: Read fields like a story - who, what, how much, when, and what happened.
IF DE2 + DE35 present -> PAN cores should match
IF DE4 present -> DE49 required for exact value
IF DE22 starts with 0 -> manual mode -> expect terminal-entered DE2/DE14
IF DE22 = 051 -> EMV data (DE55) should be present
IF DE22 = 021 -> Track2 (DE35) should be present
IF DE39 != "00" -> do not map directly to customer messaging
Goal: Parse correctness + semantic correctness = protocol correctness.
1) Field syntax stage
2) Dependency stage
3) Coherence stage
4) Action stage
FIELD_INVALID, DEPENDENCY_MISSING, SEMANTIC_MISMATCH)This ordering prevents one bad field from corrupting every downstream decision.
High-value checks:
- PAN as string, never numeric
- DE4 + DE49 for value
- DE22 dictates DE35/DE55 expectations
- DE11/DE12/DE13 correlation key
- DE54 length must be multiple of 20 bytes