GENERATE AC 9F 26 · ARQC A0 00 00 00 03 9F 36 · ATC 0x9000 95 · TVR
Paying.co · Foundry · The Canonical Model · field-by-field draft

The mold. One object every
casting is built against.

The canonical transaction — what the universal lifecycle produces at the seam and every processor adapter consumes. Normalizes the SDK's raw IDTEMVData tag-bags into one clean, processor-agnostic shape. In the native C engine this is an owned-pointer struct; money is handled by a shared, exponent-aware money.h so multi-currency math stays correct across the ISO boundary.

status shipped source IDTEMVData exponent-aware money.h USD · MXN · JPY · BHD EN · ES
What it normalizes

The SDK returns a structured bag of TLV — separate maps for unencrypted, encrypted, and masked tags, plus card type, encryption mode, and result/flags. The canonical model lifts only what matters into named, typed fields, so nothing downstream parses raw TLV again.

▼ what the SDK hands back · IDTEMVData

unencryptedTags : Map<tag,bytes> encryptedTags : Map<tag,bytes> maskedTags : Map<tag,bytes> cardType · encryptionMode · result emv_hasReversal · emv_hasAdvise

◆ what the canonical model exposes

A single typed object with named groups — identity, money, EMV outcome, crypto, verification, lifecycle. Adapters read fields by name, never by tag. Currency & language are first-class. Sensitive data stays referenced, never copied in clear.

core field — every transaction processor-read — the adapter needs it for auth crypto — behind the distilled interface required to assemble canonical
1 · Transaction Identity who/what/when — our own envelope core
FieldTypeSourceNotes
transactionIdreqUUIDFoundry-issuedOur own correlation id, minted at engage. Threads logs, adapter calls, and result.
terminalIdreqstring9F1C / configTerminal identifier from provisioning. Also the link back to which config-group / limits applied.
timestampreqISO-8601device + hostTransaction date/time. Carries 9A date + 9F21 time, normalized to UTC with local offset retained.
entryModereqenumcardType / POScontactless · contact · swipe. v1 is contactless-first; field exists so contact reuses the same model.
transactionTypereqenum9Cpurchase · refund · void · auth-only. Drives both kernel start and adapter message type.
2 · Money amount + currency as first-class data, never per-processor branches processor-read
FieldTypeSourceNotes
amountAuthorizedreqminor-units int9F02Integer minor units (cents/centavos) — never float. Avoids rounding drift across the ISO boundary.
amountOtherminor-units int9F03Cashback / secondary amount. Optional; present for cashback flows.
currencyreqISO 4217 num5F2A840 USD · 484 MXN. A transaction attribute, not a processor one — the adapter reads it; no currency-specific code paths.
currencyExponentint5F36Minor-unit exponent, carried per transaction rather than assumed. The shared money.h formatter is exponent-aware across 0-decimal (JPY), 2-decimal (USD/MXN/EUR), and 3-decimal (BHD/KWD) currencies — fixing an earlier engine bug that hardcoded 2 regardless of currency.
3 · Card & Application what was tapped — masked/tokenized, never clear PAN processor-read
FieldTypeSourceNotes
panTokenreqref / tokenencryptedTagsReference to the encrypted PAN — never clear PAN in canonical. The clear value lives only inside the P2PE/decrypt boundary.
panMaskedstringmaskedTagsDisplay-safe masked pan (first6/last4 policy). For receipts & UI only.
cardholderNamemasked string5F20Often absent on contactless; masked when present.
expiryYYMM5F24Application expiry. Adapter may need it for the auth message.
aidreqhex4F / 84Application Identifier — which app ran (Visa/MC/etc.). Drives brand routing in the adapter.
appLabelstring50Human label of the application. Display + diagnostics.
panSequenceint5F34PAN sequence number — disambiguates multiple cards on one account.
4 · EMV Outcome & Cryptogram the kernel's decision + the crypto the host must verify crypto
FieldTypeSourceNotes
cryptogramreqhex9F26The ARQC the kernel generated. The adapter forwards it to the host for online verification.
cryptogramTypereqenum9F27ARQC · TC · AAC — online-requested vs approved-offline vs declined. Decides whether the adapter is even called.
atchex9F36Application Transaction Counter — replay/clone signal. Host-side fraud relevant.
aiphex82Application Interchange Profile — what the card supports (SDA/DDA/CDA, CVM).
issuerAppDatahex9F10Issuer Application Data — opaque to us, meaningful to the issuer; passed through verbatim.
unpredictableNumberhex9F37Terminal nonce bound into the cryptogram. Part of the auth payload.
crypto boundary This group is carried, not computed. Foundry never generates or validates a cryptogram — the kernel makes it, the issuer verifies it. Canonical simply transports 9F26 + its supporting tags to the adapter, and the adapter relays them to the host. That's the distilled common interface in practice: move the crypto, don't touch the crypto.
5 · Verification & Risk how the cardholder was verified + kernel risk flags core
FieldTypeSourceNotes
cvmPerformedenum9F34no-CVM · CDCVM · signature · online-PIN. Unattended (Kiosk V) vs attended (VP3350) differ here — but it's a read field, set by policy in config.
tvrhex95Terminal Verification Results — the kernel's per-check bitfield. Host risk input.
tsihex9BTransaction Status Information — which functions were performed.
tacDefault / tacOnline / tacDenialhexconfigTerminal Action Codes that shaped the decision. Useful for audit & dispute trail.
6 · Locale & Context language + deployment context — data-driven, two locales now core
FieldTypeSourceNotes
languagereqISO 6395F2Den · es. Distinct from currency-840 so the two never conflate. Drives receipts/prompts; more locales are just more data.
countryCodeISO 3166 num9F1ATerminal country. 840 US · 484 MX. Separate axis from currency & language.
merchantContextobjectconfigMID, MCC, deployment profile (transit kiosk vs retail). Pulled from the provisioning manifest, not the card.
7 · Lifecycle & Result state machine + the round-trip back from the host core
FieldTypeSourceNotes
statereqenumFoundry SMengaged · captured · normalized · online-pending · completed · declined · cancelled · timed-out. The state machine's view.
hasReversal / hasAdviceboolemv_hasReversal / emv_hasAdviseKernel flags the SDK surfaces directly — signals the adapter may owe a reversal or advice message.
authResponseobjectadapter ← hostFilled by the adapter on return: response code, auth code, ARPC, network refs. The one group written after the processor boundary.
rawTagsMap<tag,bytes>IDTEMVDataEscape hatch — the full original tag-bag retained, so an adapter needing a tag we didn't surface isn't blocked. Read-only.
escape hatch rawTags is deliberate: the named fields cover the 95% case, but rather than risk the model being closed too early, the full tag-bag rides along read-only. If a future processor needs an exotic tag, the adapter reads it from rawTags — no core change, no re-cut of the mold. It keeps the canonical model extensible without versioning churn.
8 · TLV Filtering & Views derived per-processor — filter, never mutate processor-read
ConstructTypeSourceNotes
tlvFilterPolicyallow/deny + orderadapter-declaredEach processor casting declares which tags it wants, in what order. Santander's policy ≠ EverTec's. Bundled in the adapter, never in core.
getTlvView(policy)method → TLV setFoundry seamPure function: canonical in → filtered/ordered TLV out. Returns the processor-specific view without mutating the canonical object. The method the adapter calls.
buildDOL(dolPolicy)method → byte stringFoundry seamSame mechanism for Data Object Lists — assembles tags in a processor's required order/format. DOL construction is just a filter policy with layout.
filter, don't mutate Some processors require a specific TLV subset — only certain tags, in a certain order. That's a per-adapter concern, so it sits on the adapter side of the boundary. Canonical stays the complete neutral superset; the adapter supplies a tlvFilterPolicy and Foundry exposes getTlvView() to derive that processor's tailored set on demand. Two adapters can ask for two different views of the same transaction, and audit/logging still sees the full canonical. Same engine handles buildDOL() for required tag-list ordering.
Design rules the mold holds to

These are what keep it reusable when adapter #2 and Kiosk V arrive.

Money is integer minor-units
Never float. amount + currency + exponent travel together, and money.h formats per the carried exponent so the ISO boundary can't introduce rounding drift on JPY or BHD.
No clear PAN, ever
Canonical carries a panToken reference + a display mask. Clear data stays inside the decrypt/P2PE boundary — keeps canonical PCI-friendly to log.
Carry crypto, don't compute it
Cryptogram & supporting tags are transported verbatim. Foundry moves them; the kernel and issuer own the math.
Currency · language · country are 3 axes
Three separate fields that happen to share the number 840 in places. Kept distinct so they never conflate downstream.
Processor-neutral by construction
Nothing Santander-specific appears. The adapter reads named fields; the next acquirer reads the same ones. That's Checkpoint A.
Extensible via rawTags
The named set is the contract; rawTags is the safety valve so exotic needs don't force a model re-cut.
Filter at the seam, not in the model
Per-processor TLV subsets are derived via getTlvView(policy) — canonical stays the whole superset; the adapter declares what it wants. Same engine builds DOLs.