TransIT Integration
1. Overview
This document specifies the monorepo's TSYS TransIT integration via the shared Kotlin library libs/transit-client.
libs/transit-clientis the canonical API wrapper for TransIT request/response models, wrappers, retries, and exception handling.services/processingintegrates throughTransitPaymentProcessor, which maps service DTOs to transit-client DTOs and delegates toTransitClient.- Other services can also use the same library for non-processing workflows (for example merchant activation and provisioning).
2. TransIT API Details
Environments
| Environment | Base URL | Purpose |
|---|---|---|
| Sandbox | https://stagegw.transnox.com/servlets/TransNox_API_Server | Development and testing |
| Production | https://gateway.transit-pass.com/servlets/TransNox_API_Server | Live transactions |
NOTE: These URLs must be confirmed via the TransIT Developer Portal once we have authenticated access. The sandbox URL is derived from publicly available integration guides and may have changed.
Authentication
Every request to TransIT requires:
Headers:
Content-Type: application/json
Accept: application/json
Body (in every request):
"deviceID": "<assigned by TransIT>",
"transactionKey": "<assigned by TransIT>",
"operatorID": "<our operator identifier>"
- deviceID: Unique identifier for our integration endpoint (assigned during onboarding)
- transactionKey: Secret key for authenticating requests (assigned during onboarding)
- operatorID: Identifies the operator performing the transaction (configurable per-merchant)
BLOCKER: We need these credentials from TSYS. Cannot proceed with integration testing without them.
API Version
- Current: API v10.0 (October 2024 release)
- Our target: Latest stable version at time of integration
- Format: JSON (XML also supported but we will use JSON exclusively)
Integration Architecture (Current Code)
services/* -> libs/transit-client: TransitClient -> TransIT endpoint
TransitClientcentralizes wrapper selection (Sale,Auth, etc.), request body assembly, credentials injection, retries, and response/decline parsing.- Transport concerns stay inside
libs/transit-client(OkHttpClient, timeout/backoff, sensitive field redaction). - Service modules own business mapping:
services/processing: transaction workflows throughTransitPaymentProcessorservices/management: merchant/activation related integration pointsservices/merchant-onboarding: activation and provisioning retry orchestration
- Exception boundary is split by layer:
TransitClientthrowsTransitException.*- callers map those to service-level outcomes/exceptions
3. TransIT Client API Surface (Current)
The table below reflects the current operations in libs/transit-client/src/main/kotlin/com/myriad/gateway/transit/TransitClient.kt.
3.1 Core Transaction and Utility Operations (Existing)
| Operation | Request Wrapper | Key Fields | Kotlin Method |
|---|---|---|---|
| Sale | Sale | transactionAmount, cardNumber/tokenID, cardDataSource, transactionIndustryType, externalReferenceID | sale(credentials, SaleRequest) |
| Auth | Auth | transactionAmount, cardNumber/tokenID, cardDataSource, transactionIndustryType, externalReferenceID | authorize(credentials, AuthRequest) |
| Capture | Capture | transactionID, transactionAmount, externalReferenceID | capture(credentials, CaptureRequest) |
| Void | Void | transactionID, externalReferenceID | void(credentials, VoidRequest) |
| Return / Refund | Return | transactionID or card fields, transactionAmount, externalReferenceID | refund(credentials, ReturnRequest) |
| TipAdjust | TipAdjustment | tip, transactionID, externalReferenceID | tipAdjust(credentials, TipAdjustRequest) |
| L2L3Adjust | TransactionAdjustment | transactionID, level2Data, level3Data, externalReferenceID | l2l3Adjust(credentials, L2L3AdjustRequest) |
| CardVerification | CardVerification | cardNumber, expirationDate, CVV2, externalReferenceID | cardVerification(credentials, CardVerificationRequest) |
| BalanceInquiry | BalanceInquiry | card/account lookup fields + externalReferenceID | balanceInquiry(credentials, BalanceInquiryRequest) |
| BatchClose | BatchClose | optional batch context + externalReferenceID | batchClose(credentials, BatchCloseRequest) |
| MerchantActivation | ActivateMerchant | merchant onboarding fields | merchantActivation(credentials, MerchantActivationRequest) |
| ForcedAuth | ForcedAuth | auth fields + force-auth details | forcedAuth(credentials, ForcedAuthRequest) |
| CardAuthentication | CardAuthentication | 3DS/card-auth fields | cardAuthentication(credentials, CardAuthenticationRequest) |
| DebitSale | DebitSale | debit card/PIN transaction fields | debitSale(credentials, DebitSaleRequest) |
| DebitReturn | DebitReturn | debit refund fields | debitReturn(credentials, DebitReturnRequest) |
| CashSale | CashSale | cash sale amount/order fields | cashSale(credentials, CashSaleRequest) |
| Notification | Notification | callback/event payload fields | notification(credentials, NotificationRequest) |
| GenerateReceipt | GenerateReceipt | transactionID, receipt delivery fields | generateReceipt(credentials, GenerateReceiptRequest) |
| SearchTransaction | SearchTransaction | search criteria (transactionID, refs, date ranges) | searchTransaction(credentials, SearchTransactionRequest) |
| GenerateKey | GenerateKey | key generation payload (no merchant credentials wrapper) | generateKey(GenerateKeyRequest) |
3.2 Newer Operations
Network Tokenization (4)
| Operation | Request Wrapper | Key Fields | Kotlin Method |
|---|---|---|---|
| GenerateToken | GenerateToken | PAN/tokenization input + customer context | generateToken(credentials, GenerateTokenRequest) |
| GenerateTokenCryptogram | GenerateTokenCryptogram | token + cryptogram context (amount, auth context) | generateTokenCryptogram(credentials, GenerateTokenCryptogramRequest) |
| GetTokenStatus | GetTokenStatus | token identifier/status lookup fields | getTokenStatus(credentials, GetTokenStatusRequest) |
| GetTokenCardMetaData | GetTokenCardMetaData | token identifier for metadata retrieval | getTokenCardMetadata(credentials, GetTokenCardMetaDataRequest) |
EBT (5)
| Operation | Request Wrapper | Key Fields | Kotlin Method |
|---|---|---|---|
| EbtCashBenefitPurchase | EbtCashBenefitPurchase | EBT account + purchase amount fields | ebtCashBenefitPurchase(credentials, EbtCashBenefitPurchaseRequest) |
| EbtCashWithdrawal | EbtCashWithdrawal | EBT account + withdrawal amount fields | ebtCashWithdrawal(credentials, EbtCashWithdrawalRequest) |
| EbtElectronicVoucher | EbtElectronicVoucher | voucher ID + voucher redemption fields | ebtElectronicVoucher(credentials, EbtElectronicVoucherRequest) |
| EbtFoodStampPurchase | EbtFoodStampPurchase | food-stamp purchase amount/account fields | ebtFoodStampPurchase(credentials, EbtFoodStampPurchaseRequest) |
| EbtReturn | EbtReturn | original EBT transaction reference + return amount | ebtReturn(credentials, EbtReturnRequest) |
eInvoice (7)
| Operation | Request Wrapper | Key Fields | Kotlin Method |
|---|---|---|---|
| CreateInvoice | CreateInvoice | invoice amount, recipient, due and metadata fields | createInvoice(credentials, CreateInvoiceRequest) |
| CancelInvoice | CancelInvoice | invoice identifier + cancel reason/context | cancelInvoice(credentials, CancelInvoiceRequest) |
| GenerateInvoiceEmail | GenerateInvoiceEmail | invoice identifier + recipient email context | generateInvoiceEmail(credentials, GenerateInvoiceEmailRequest) |
| InvoiceAch | InvoiceAch | invoice identifier + ACH payment details | invoiceAch(credentials, InvoiceAchRequest) |
| InvoiceSale | InvoiceSale | invoice identifier + card payment details | invoiceSale(credentials, InvoiceSaleRequest) |
| SearchInvoice | SearchInvoice | invoice search criteria (ID, merchant/customer/date status) | searchInvoice(credentials, SearchInvoiceRequest) |
| UpdateInvoice | UpdateInvoice | invoice identifier + mutable invoice fields | updateInvoice(credentials, UpdateInvoiceRequest) |
Digital Wallet (4)
| Operation | Request Wrapper | Key Fields | Kotlin Method |
|---|---|---|---|
| DigitalWalletSale | DigitalWalletSale | wallet payload + sale amount/context | digitalWalletSale(credentials, DigitalWalletSaleRequest) |
| DigitalWalletAuth | DigitalWalletAuth | wallet payload + auth amount/context | digitalWalletAuth(credentials, DigitalWalletAuthRequest) |
| SecureAuth | SecureAuth | secure wallet auth payload (network token + cryptogram) | secureAuth(credentials, SecureAuthRequest) |
| SecureSale | SecureSale | secure wallet sale payload (network token + cryptogram) | secureSale(credentials, SecureSaleRequest) |
ACH (4)
| Operation | Request Wrapper | Key Fields | Kotlin Method |
|---|---|---|---|
| AchSale | Ach | routing/account details, amount, SEC context | achSale(credentials, AchSaleRequest) |
| AchReturn | AchReturn | original ACH transaction reference + return reason | achReturn(credentials, AchReturnRequest) |
| AchResubmit | AchResubmit | original ACH reference + resubmission details | achResubmit(credentials, AchResubmitRequest) |
| AchVerification | AchVerification | account verification (routing/account/customer fields) | achVerification(credentials, AchVerificationRequest) |
Signature and EMV Enrichment
| Operation | Request Wrapper | Key Fields | Kotlin Method |
|---|---|---|---|
| UpdateSignature | UpdateTransactionSignature | transactionID, signature payload (signatureData/image fields) | updateSignature(credentials, UpdateSignatureRequest) |
| EMV Enrichment | TransactionAdjustment plus EMV fields on sale/auth requests | L2/L3 line items (level2Data, level3Data) and EMV request fields (emvTags, terminal/card-present capability fields) | l2l3Adjust(credentials, L2L3AdjustRequest) and EMV-capable sale/authorize models |
3.3 Processing Service Delegation
services/processing/src/main/kotlin/com/myriad/gateway/processing/service/TransitPaymentProcessor.kt is the adapter from service DTOs to transit-client models:
- injects
TransitClientas a constructor dependency (@Component) - maps
SaleRequest,AuthRequest, capture/void/refund/tip/enrichment inputs into transit-client request DTOs - delegates to
sale,authorize,capture,void,refund,tipAdjust,l2l3Adjust, andbatchClose - maps transit responses back to
TransactionResult - maps
TransitExceptiontypes to service-layer behavior (declines -> declined result, auth/network/server errors -> service exceptions)
4. Transaction Industry Types
| Code | Type | Use Case |
|---|---|---|
RE | Retail | PeakPOS in-store transactions |
EC | E-Commerce | Online payment website |
RS | Restaurant | Future vertical |
HC | Healthcare | Future vertical |
DM | Direct Marketing | Mail/phone orders, actively used now |
LD | Lodging | Out of scope (Sierra) |
5. Card Data Sources
| Code | Description | Our Usage |
|---|---|---|
MANUAL | Manually keyed card number | Management portal manual entry, online payments |
SWIPE | Magnetic stripe | Pre-certified terminal |
EMV | Chip insertion | Pre-certified terminal |
CONTACTLESS | NFC tap | Pre-certified terminal |
TOKEN | Previously tokenized card | Recurring payments, stored cards |
INTERNET | E-commerce transaction | Online payment website |
6. Response Codes
Approval Codes (Prefix: A)
| Code | Meaning |
|---|---|
A0000 | Approved |
A0001 | Approved (partial) |
Decline Codes (Prefix: D)
| Code | Meaning |
|---|---|
D0092 | Declined, general |
D1218 | Declined, invalid card |
D1219 | Declined, expired card |
D1220 | Declined, insufficient funds |
D1221 | Declined, CVV mismatch |
D1222 | Declined, AVS mismatch |
D1223 | Declined, card restricted |
D1224 | Declined, do not honor |
D1225 | Declined, pickup card |
D1226 | Declined, invalid transaction |
Error Codes (Prefix: E)
| Code | Meaning |
|---|---|
E0913 | Error, invalid device credentials |
E0XXX | Error, communication failure |
NOTE: Full response code table available in TransIT API documentation (requires developer access).
7. Semi-Integrated Device Flow, NexGO SmartConnect API
For card-present transactions, PeakPOS communicates with NexGO's pre-certified Integrator app via the SmartConnect API. SmartConnect uses Android Intents (on-device, preferred) or TCP (port 8765, cross-device) to pass JSON messages. Card data never touches our PeakPOS app or servers.
7.1 SmartConnect Flow
┌──────────────┐ ┌───────────────────────┐ ┌─────────────┐
│ PeakPOS App │ │ NexGO Integrator App │ │ TransIT │
│ (Android) │ │ (SmartConnect API, │ │ Multipass │
│ │ │ Pre-Certified EMV) │ │ API │
└──────┬───────┘ └──────────┬────────────┘ └──────┬──────┘
│ │ │
│ 1. Android Intent │ │
│ w/ JSON request │ │
│───────────────────────>│ │
│ │ │
│ │ 2. Integrator takes │
│ │ screen, prompts │
│ │ insert/tap/swipe │
│ │ │
│ │ 3. Encrypts card data, │
│ │ sends to TransIT │
│ │────────────────────────>│
│ │ │
│ │ 4. TransIT processes, │
│ │ returns auth result │
│ │<────────────────────────│
│ │ │
│ 5. Intent result │ │
│ w/ JSON response │ │
│ (transdata extra) │ │
│<───────────────────────│ │
│ │ │
│ 6. PeakPOS logs result │ │
│ via Processing │ │
│ Microservice │ │
7.2 SmartConnect Request Format
PeakPOS sends an Android Intent with a JSON payload:
val intent = Intent("android.intent.action.Integrator")
intent.putExtra("Input", requestJson.toString())
startActivityForResult(intent, REQUEST_CODE)
Sale Request
{
"action": {
"accountType": 0,
"processor": "<TSYS_TI>",
"receipt": true,
"signature": true,
"manual": true
},
"payment": {
"type": "Sale",
"amount": "10.00",
"tip_amount": "0.00",
"cash_back": "0.00"
}
}
Auth (Pre-Authorization) Request
{
"action": { "processor": "<TSYS_TI>", "receipt": true },
"payment": {
"type": "Auth",
"amount": "10.00",
"tip_amount": "0.00"
}
}
Capture (Post-Auth) Request
{
"action": { "processor": "<TSYS_TI>", "receipt": true },
"payment": {
"type": "Capture",
"amount": "10.00",
"tip_amount": "0.00",
"transaction_id": "<transactionID from Auth>",
"trace_no": "<traceNo from Auth>"
}
}
Either transaction_id or trace_no must be included. If both are present, transaction_id takes precedence.
Void Request
{
"action": { "processor": "<TSYS_TI>", "receipt": true },
"payment": {
"type": "Void",
"transaction_id": "<transactionID>",
"trace_no": "<traceNo>"
}
}
Return (Refund) Request
{
"action": { "processor": "<TSYS_TI>", "receipt": true },
"payment": {
"type": "Return",
"amount": "5.00"
}
}
Tip Adjustment Request
{
"action": { "processor": "<TSYS_TI>", "receipt": true },
"payment": {
"type": "Tip Adjustment",
"amount": "10.00",
"tip_amount": "3.00",
"transaction_id": "<transactionID>",
"trace_no": "<traceNo>"
}
}
Note (EVO-specific): Tip Adjustment on EVO can only modify the tip, not the base amount. Confirm TransIT behavior matches.
Action Field Reference
| Field | Type | Required | Description |
|---|---|---|---|
processor | String | M | Processor identifier. Can be left blank if only one Integrator is installed. |
receipt | boolean | M | Print receipt on terminal's integrated printer |
signature | boolean | O | Prompt for on-screen signature after approval |
manual | boolean | O | Allow manual/keyed card entry (vs. swipe/EMV/tap only) |
accountType | String | O | 0 = Automatic, 1 = Credit, 2 = Debit |
Payment Field Reference
| Field | Type | Required | Description |
|---|---|---|---|
type | String | M | Transaction type: Sale, Auth, Capture, Void, Return, Tip Adjustment, BatchClose, BatchDetail, BatchSummary, Reprint, getLastStatus, TerminalInfo, EBTFSPurchase, EBTCBPurchase, EBTBalance, EBTFSReturn |
amount | String | M | Base transaction amount (e.g., "10.00") |
tip_amount | String | O | Tip amount. If omitted, Integrator prompts user for tip on screen. |
cash_back | String | O | Cash back amount |
transaction_id | String | O/M | Host transaction ID (for Void, Capture, Tip Adjust). Either this or trace_no required. |
trace_no | String | O/M | Local terminal transaction ID (for Void, Capture, Tip Adjust) |
panDataToken | String | O | Payment token for card-on-file transactions (see section 7.5) |
taxRate | String | O | Tax rate percentage (TSYS Sierra only) |
customTaxOneRate | String | O | Custom tax rate (TSYS Sierra only) |
customTaxTwoRate | String | O | Custom tax rate (TSYS Sierra only) |
7.3 SmartConnect Response Format
Response is returned via the Intent result in onActivityResult():
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val response = data?.getStringExtra("transdata") // JSON response
val signature = data?.getByteArrayExtra("signature") // Signature bitmap (if requested)
}
Sale/Auth Response Fields
| Field | Type | Description |
|---|---|---|
batchID | String | Batch number (e.g., "000004") |
transactionID | String | Host transaction ID, use for Void, Capture, Tip Adjust |
traceNo | String | Local terminal transaction ID |
transactionType | String | e.g., "Credit Sale", "Pre-Auth", "Void", "Credit Refund", "Add Tip" |
baseAmount | String | Base amount processed (excluding tip) |
tipAmount | String | Tip amount ("0.00" if none) |
cashbackAmount | String | Cash back amount ("0.00" if none) |
processedAmount | String | Total processed (base + tip + fees) |
resultCode | String | "00" = approved. Non-zero = declined/error. -501 = cancelled. |
cardNumber | String | Last 4 digits only (e.g., "0010") |
cardIssuer | String | Card brand (e.g., "VISA", "MASTERCARD") |
cardDataEntry | String | Entry method: "SWIPED", "CHIP READ (I)", "CONTACTLESS", "KEYED", "TOKEN" |
panDataToken | String | Payment token for future card-on-file charges |
referenceNumber | String | Host reference number for troubleshooting with processor |
authorizationCode | String | Processor authorization code |
merchantId | String | Merchant account ID used for processing |
applicationVersion | String | Integrator app version (e.g., "N08.011.066_49") |
fwVersion | String | Terminal firmware version |
emvTags[] | JSON Array | EMV tag key-value pairs (e.g., "9F06=A0000000031010") |
cardHolder | String | Cardholder name from card (if present) |
accountType | String | "Credit" or "Debit" (TSYS Sierra only) |
cardBIN | String | Card BIN (TSYS Sierra only) |
emvAppLabel | String | EMV application label (e.g., "MasterCard") |
| Full JSON response | JSON Object | Store for debugging |
Sample Sale Response (TSYS TransIT)
{
"packetID": "XCRT",
"packetData": {
"batchID": "000004",
"transactionID": "53ABB10B5BFF49C7A20B456AD7040BBD",
"traceNo": "6",
"transactionType": "Credit Sale",
"baseAmount": "1.00",
"tipAmount": "0.00",
"cashbackAmount": "0.00",
"resultCode": "00",
"cardNumber": "0010",
"cardIssuer": "VISA",
"cardDataEntry": "CHIP READ (I)",
"referenceNumber": "000001560420",
"authorizationCode": "556475",
"merchantId": "000000001168579",
"applicationVersion": "N08.011.066_49",
"fwVersion": "v4.5.5",
"emvTags": [
"9F06=A0000000031010",
"5F2A=0840",
"9F36=0042",
"9F26=6206484E62C59D54",
"9F02=000000000100",
"82=5C00",
"95=42C0008000"
],
"cardHolder": "TEST CARD 01"
}
}
SmartConnect Result Codes
| resultCode | Description |
|---|---|
00 | Success / Approved |
| Non-zero | Declined, check processor response for meaning |
-50X | General application error |
-501 | Transaction cancelled by user |
7.4 SmartConnect Transaction Type Support (TSYS TransIT)
| Transaction Type | TSYS TransIT | Notes |
|---|---|---|
| Sale | ✅ | Credit + Debit |
| Credit/Debit Selection | ❌ | Not supported on TransIT |
| Auth (Pre-Auth) | ✅ | |
| Capture (Post-Auth) | ✅ | Reference by transaction_id or trace_no |
| Void | ✅ | |
| Return (Refund) | ✅ | |
| Tip Adjustment | ✅ | |
| Reprint | ✅ | Reprint last receipt / retrieve last response JSON |
| getLastStatus | ✅ | Get raw JSON from last transaction |
| BatchClose | ✅ | Settle the open batch (see section 9) |
| BatchDetail | ✅ | Per-transaction batch listing |
| BatchSummary | ✅ | Aggregate batch totals |
| EBTFSPurchase | ✅ | EBT Food Stamp |
| EBTCBPurchase | ✅ | EBT Cash Benefit |
| EBTBalance | ❌ | Not supported on TransIT |
| EBTFSReturn | ✅ | |
| EBTFSVoucher | ✅ | |
| EBTFSVoucherReturn | ❌ | Not supported on TransIT |
| Gift (all types) | ❌ | Not supported on TransIT |
| DEVICE_INFO | ❌ | Not supported on TransIT |
7.5 SmartConnect Token Payments (panDataToken)
SmartConnect supports sending a panDataToken in the request to charge a previously tokenized card without requiring the physical card:
{
"action": { "processor": "<TSYS_TI>", "receipt": true },
"payment": {
"type": "Sale",
"panDataToken": "03a0106a-d62a-4a47-9301-6060c1774e26...",
"amount": "10.00",
"tip_amount": "0.00"
}
}
On approved transactions, the response includes a panDataToken field that can be stored for future use.
⚠️ IMPORTANT: TransIT Token Support Unconfirmed: As of SmartConnect API rev. 1.18 (Oct 2024),
panDataTokenis documented as supported only on the EVO Integrator application. Support on the TSYS TransIT Integrator is not explicitly confirmed in the documentation. This must be verified with NexGO before relying on SmartConnect-based token replay for card-present recurring. If unsupported, card-present tokenization still works (tokens are returned in responses), but replaying those tokens must go through the gateway's server-to-server TransIT API path, not through SmartConnect.
7.6 SmartConnect Communication Modes
| Mode | When to Use | Details |
|---|---|---|
| Android Intent (preferred) | PeakPOS runs on same device as Integrator | intent.setAction("android.intent.action.Integrator"), auto-starts Integrator if not running |
| TCP | External device initiating payment | Connect to terminal IP on port 8765. Same JSON format. Must handle socket lifecycle. |
PeakPOS will use Android Intent since our app runs on the same NexGO device as the Integrator.
7.7 PeakPOS to SmartConnect Response Mapping
The PeakPOS app must map SmartConnect response fields to the monorepo's card_authorizations schema:
| SmartConnect Field | Monorepo Schema Field | Notes |
|---|---|---|
transactionID | processor_transaction_id | Host transaction ID |
traceNo | terminal_trace_no (new) | Local terminal ID, needed for Void/Capture/Tip Adjust |
batchID | batch_id | |
baseAmount | amount | Parse string to cents |
tipAmount | tip_amount | |
cashbackAmount | cashback_amount | |
processedAmount | processed_amount | |
resultCode | response_code | "00" = approved |
authorizationCode | approval_code | |
cardNumber | card_last_four | Last 4 only |
cardIssuer | card_brand | |
cardDataEntry | card_data_entry | SWIPED/CHIP/CONTACTLESS/KEYED/TOKEN |
panDataToken | token | Store for card-on-file |
referenceNumber | reference_number | |
merchantId | processor_merchant_id | |
emvTags[] | emv_data (JSONB) | Parse array to structured object |
cardHolder | cardholder_name | |
emvAppLabel | emv_app_label | |
| Full JSON response | raw_response (JSONB) | Store complete response for debugging |
8. Tokenization Strategy, Guardian Tokenization
TransIT provides tokenization through Guardian Tokenization, a TSYS-native token vault that replaces card PANs with non-reversible token values. Guardian tokens are the foundation for recurring billing, card-on-file, and refund-on-original-card flows.
8.1 How Guardian Tokenization Works
- Token generation: On any transaction where
tokenRequired: "Y", TransIT returns atokenIDalongside the authorization response - Token usage: Subsequent transactions send
tokenIDin thecardNumberfield withcardDataSource: "TOKEN", no raw card data needed - Token scope: Tokens are scoped to our
deviceID. A token generated under one deviceID cannot be used under another - Token persistence: Tokens remain valid as long as the underlying card is valid, no separate expiration on the token itself
8.2 Token Lifecycle
┌──────────────────────┐
│ Transaction with │
│ tokenRequired: "Y" │
└──────────┬───────────┘
│
v
┌──────────────────────┐
│ ACTIVE │ <- Token returned in response
│ (usable for charges) │
└──────────┬───────────┘
│
┌───────┼────────┐
│ │ │
v v v
┌──────┐ ┌──────┐ ┌─────────┐
│ Used │ │ Card │ │ Explicit│
│ for │ │ exp │ │ delete │
│ TOKEN│ │ or │ │ request │
│ txns │ │ lost │ │ │
└──────┘ └──┬───┘ └────┬────┘
│ │
v v
┌──────────┐ ┌──────────┐
│ INACTIVE │ │ DELETED │
│ (decline │ └──────────┘
│ on use) │
└────┬─────┘
│
v
┌──────────┐
│ DELETED │ (after cleanup period)
└──────────┘
8.3 Token Generation, Card Present vs. Card Not Present
| Channel | How Token is Generated | Card Data Source |
|---|---|---|
| Card Present (NexGO) | SmartConnect semi-integration: NexGO Integrator handles card capture, sends to TransIT, panDataToken returned in SmartConnect response, PeakPOS stores token | EMV, CONTACTLESS, SWIPE |
| Card Not Present (Server-to-Server) | Merchant sends card data via API, Processing Service sends to TransIT with tokenRequired: "Y", token stored | MANUAL, INTERNET |
| Card Not Present (Hosted Payment) | Customer enters card in hosted page/iFrame, TransIT handles PCI-scoped tokenization, token returned to our backend | INTERNET |
| Existing Token | Subsequent charges (recurring, card-on-file) use stored tokenID | TOKEN |
⚠️ Card-Present Token Replay: SmartConnect returns
panDataTokenin responses for all processors, but replaying apanDataTokenin a SmartConnect request (sending a token instead of requiring a card swipe) is only documented as supported on EVO. TransIT support forpanDataTokenreplay via SmartConnect is unconfirmed, verify with NexGO. If unsupported, card-present token replay must go through the gateway's server-to-server TransIT API using Guardian tokenization, not through SmartConnect.
8.4 Token-to-Customer Mapping
Our gateway maintains the relationship between Guardian tokens and customers in the stored_tokens table (Online Txn Service):
Customer "cust_001" (Jane Doe)
├── tok_visa_1234 → Visa ****1234, exp 12/27 (ACTIVE, primary)
├── tok_mc_5678 → MC ****5678, exp 03/28 (ACTIVE)
└── tok_visa_9012 → Visa ****9012, exp 01/26 (INACTIVE, expired)
Customer "cust_002" (John Smith)
└── tok_amex_3456 → Amex ****3456, exp 09/27 (ACTIVE, primary)
Rules:
- One customer can have multiple stored tokens (multiple cards)
- Each token links to exactly one
customer_idand onemerchant_id - Tokens are merchant-scoped: a token stored by merchant A cannot be used by merchant B (Guardian's deviceID scoping enforces this at the TSYS level)
- Customers can designate a "primary" token for default recurring charges
- On card update (new expiry, replacement card), merchant must create new token. Guardian tokens are immutable
8.5 Token Usage for Recurring Billing
The subscription billing engine (Processing Microservice) uses Guardian tokens to execute recurring charges:
1. Billing job queries due subscriptions
2. For each subscription, retrieves stored tokenId
3. Sends TOKEN sale to TransIT:
{
"SaleTransaction": {
"deviceID": "...",
"transactionKey": "...",
"cardNumber": "<tokenID>", <- Guardian token, NOT a PAN
"cardDataSource": "TOKEN",
"transactionAmount": "49.99",
"transactionIndustryType": "EC",
"tokenRequired": "N" <- Already have token, don't need new one
}
}
4. TransIT resolves token, charges underlying card
5. Response handled same as any sale (approved/declined)
Decline handling for recurring: If a recurring TOKEN charge is declined, the token status is not automatically changed. The subscription enters retry/dunning flow. Only persistent declines (card cancelled, account closed) should trigger token deactivation.
8.6 NMI Token Migration Strategy
The existing PeakPOS monorepo uses NMI as its payment processor. Customers already have NMI tokens stored in the card_authorizations table (field: token). When migrating merchants from NMI to Peak Gateway (TSYS TransIT):
NMI tokens are not compatible with Guardian tokens. Migration requires re-tokenization.
| Migration Approach | Description | Pros | Cons |
|---|---|---|---|
| Gradual re-tokenization | On next customer transaction (any channel), capture new card data, generate Guardian token, store alongside NMI token, future charges use Guardian | No bulk migration, zero downtime | Stale customers may never re-tokenize |
| Card-on-file migration | For active recurring customers, use NMI's card-on-file data to generate a Guardian token via a one-time batch process | Complete migration for active subscribers | Requires NMI to allow one final charge/verification per token; PCI implications of handling card data in transit |
| Customer re-entry | Notify customers to re-enter card data via hosted payment page | Cleanest from PCI perspective | Worst customer experience; guaranteed churn |
Recommended: Gradual re-tokenization as default, with card-on-file migration for merchants with active recurring/subscription billing that must continue uninterrupted. Track migration status per-merchant:
merchant.tokenMigrationStatus:
NOT_STARTED -> IN_PROGRESS -> COMPLETED
Per stored token:
nmi_token: "nmi_abc123" (original)
guardian_token: "tok_xyz789" (new, null until re-tokenized)
migration_status: PENDING | MIGRATED
8.7 Token Security & PCI Considerations
- Guardian tokens are non-reversible, cannot derive card PAN from token
- Tokens are scoped to our deviceID, compromised token is useless without our TransIT credentials
- Our systems never store raw card data (PANs, CVVs), only Guardian token IDs
- Card-present tokens: card data never touches our servers (NexGO semi-integration handles encryption)
- Card-not-present tokens: raw card data is transmitted to TransIT in the initial tokenization call, but our gateway acts as a pass-through, we log the token, not the PAN
- Hosted payment tokens: card data enters TransIT's hosted page directly, never touches our infrastructure
NOTE: Exact Guardian Tokenization API fields and token management endpoints must be verified against TransIT documentation once developer access is obtained. Token deletion/deactivation API may have additional parameters.
9. Settlement & Batch Management (via SmartConnect)
TransIT is a host-capture platform, batch management happens on the TSYS cloud. We manage batches ourselves through SmartConnect's batch operations on the terminal, not through the TSYS Merchant Center portal.
Settlement Modes
| Mode | Description |
|---|---|
| Scheduled | Auto-settle at a configured time daily. Default dev batch closing: 10:30 PM |
| On-Demand | Manual batch close via SmartConnect BatchClose request from PeakPOS or gateway |
SmartConnect Batch Operations
BatchDetail, View All Transactions in Current Batch
// Request
{
"action": { "processor": "<TSYS_TI>", "receipt": true },
"payment": { "type": "BatchDetail" }
}
// Response
{
"packetID": "XCRT",
"packetData": {
"batchID": "000004",
"transactionType": "BatchDetail",
"timestamp": "2026-03-08 13:51",
"transactions": [
{
"traceNo": "1",
"transactionID": "8420679",
"transactionType": "Sale",
"referenceNumber": "123456789",
"authorizationCode": "556475",
"cardDataEntry": "SWIPE",
"cardHolder": "JOHN DOE",
"baseAmount": "10.00",
"tipAmount": "2.00",
"cashbackAmount": "0.00",
"feeAmount": "0.50",
"taxAmount": "0.10",
"processedAmount": "12.60",
"timestamp": "2026-03-08 11:30"
}
]
}
}
Use case: Display batch contents in PeakPOS, identify problematic transactions before settlement, reconcile terminal batch against gateway records.
BatchSummary, Aggregate Batch Totals
// Request
{
"action": { "processor": "<TSYS_TI>", "receipt": true },
"payment": { "type": "BatchSummary" }
}
// Response
{
"packetID": "XCRT",
"packetData": {
"batchID": "000004",
"transactionType": "BatchSummary",
"timestamp": "2026-03-08 13:51",
"summary": [{
"cashSaleCount": "1",
"cashSaleAmount": "5.00",
"cashReturnCount": "0",
"cashReturnAmount": "0.00",
"totalCash": "5.00",
"creditDebitCount": "4",
"creditDebitAmount": "48.00",
"creditDebitReturnCount": "1",
"creditDebitReturnAmount": "5.00",
"totalCreditDebit": "43.00",
"ebtCount": "1",
"ebtAmount": "12.00",
"ebtReturnCount": "0",
"ebtReturnAmount": "0.00",
"totalEbt": "12.00",
"giftRedeemCount": "0",
"giftRedeemAmount": "0.00",
"giftActivateCount": "0",
"giftActivateAmount": "0.00",
"giftReloadCount": "0",
"giftReloadAmount": "0.00",
"totalAmount": "60.00"
}]
}
}
Use case: End-of-day summary display, quick reconciliation check before settling.
BatchClose, Settle the Batch
// Request
{
"action": { "processor": "<TSYS_TI>", "receipt": true },
"payment": { "type": "BatchClose" }
}
// Response (TSYS TransIT-specific)
{
"packetID": "XCRT",
"packetData": {
"batchID": "000004",
"transactionType": "BatchClose",
"resultCode": "00",
"merchantId": "000000001168579",
"applicationVersion": "N08.011.066_49",
"fwVersion": "v4.5.5",
"settlementTime": "2026/03/08 22:30:01",
"creditSaleCount": "6",
"creditSaleAmount": "120.00",
"creditReturnCount": "1",
"creditReturnAmount": "5.00",
"debitSaleCount": "2",
"debitSaleAmount": "30.00",
"debitReturnCount": "0",
"debitReturnAmount": "0.00",
"transITHostResponseObject": {
"BatchCloseResponse": {
"batchInfo": {
"returnAmount": "500",
"returnCount": "1",
"sICCODE": "5999",
"saleAmount": "15000",
"saleCount": "8"
},
"responseCode": "A0000",
"responseMessage": "Success",
"status": "PASS"
}
},
"hostReturnAmount": "5.00",
"hostReturnCount": "1",
"hostSaleAmount": "150.00",
"hostSaleCount": "8"
}
}
TransIT-specific fields in BatchClose response:
| Field | Description |
|---|---|
transITHostResponseObject | Full parsed host response from TransIT (JSON object) |
hostSaleCount / hostSaleAmount | Host-reported sale totals, use for reconciliation against terminal counts |
hostReturnCount / hostReturnAmount | Host-reported return totals |
Reconciliation: Compare terminal-reported counts (creditSaleCount, etc.) against host-reported counts (hostSaleCount, etc.). Discrepancies indicate transactions that were processed locally but not received by the host, or vice versa.
Batch Management Workflow
1. During business day: PeakPOS processes Sales, Auths, Voids, Returns
2. Before settlement: PeakPOS calls BatchDetail to review transactions
- Identify bad/problematic transactions
- Void incorrect transactions (before batch closes)
- Apply tip adjustments
3. At configured time (10:30 PM default): PeakPOS sends BatchClose
- Or: Gateway triggers BatchClose via TCP to terminal
4. After settlement: Gateway compares terminal response vs. host response
- Flag discrepancies for manual review
- Store reconciliation results in CDE Spanner
5. New batch opens automatically after settlement
Void Rules
| Void Type | Availability |
|---|---|
| Full void | Before batch settlement only |
| Partial void | Before batch settlement only |
| After settlement | Must use refund (Return) instead |
10. Recurring & Scheduled Payments via TransIT
How Recurring Works at the TransIT Level
TransIT does not have a native subscription/recurring billing engine. Recurring billing is implemented by our gateway, the Processing Microservice's subscription engine schedules and executes TOKEN-based sale transactions at configured intervals.
TransIT's Role in Recurring
| Responsibility | Owner |
|---|---|
| Subscription creation, lifecycle, scheduling | Peak Gateway (Processing Microservice) |
| Dunning logic (retries on decline) | Peak Gateway (Processing Microservice) |
| Executing the actual charge | TransIT (via TOKEN sale) |
| Token storage & resolution | TransIT (Guardian Tokenization) |
| Token-to-customer mapping | Peak Gateway (Online Txn Service) |
Transaction Flow for a Recurring Charge
Peak Gateway (scheduler)
│
│ 1. Query subscriptions due today
│ 2. For each: look up Guardian tokenId
│
v
Processing Microservice
│
│ 3. POST SaleTransaction with cardDataSource: "TOKEN"
│
v
TransIT Multipass API
│
│ 4. Resolve Guardian token, card PAN
│ 5. Process sale against issuer
│ 6. Return approval/decline
│
v
Processing Microservice
│
│ 7a. Approved, update subscription cycle, publish event
│ 7b. Declined, increment failed_attempts, schedule retry
Industry Type for Recurring
- Recurring charges for online merchants:
transactionIndustryType: "EC"(E-Commerce) - Recurring charges for mail/phone order merchants:
transactionIndustryType: "DM"(Direct Marketing) - The industry type is set per-merchant in their configuration, not per-subscription
Certification Note
Recurring billing uses the same TOKEN-based sale flow as card-on-file payments. No separate certification is needed for recurring billing specifically, it falls under the Card Not Present (server-to-server) certification scope. Once CNP is certified, recurring billing works automatically.
11. Certification Scope
Pre-Certified (No Additional Certification Needed)
| Capability | Status |
|---|---|
| Level 3 EMV (Card Present) | Pre-certified, works out of the box with NexGO semi-integration |
Requires Separate Certification
| Capability | Certification | Notes |
|---|---|---|
| Back Office | Separate cert | NexGO integration, Sales/Refund/Void/Batch Management, Tokenization (if needed) |
| Card Not Present | Separate cert | Server-to-server, not pre-certified |
| Hosted Payment 2.0 | Separate cert | Sale-only, "really simple certification process" |
| Apple Pay | Separate cert | After base CNP certification |
| Google Pay | Separate cert | After base CNP certification |
Key Certification Rule
Adding new capabilities does NOT extend existing certification, only new items need testing.
Each new feature or payment method requires its own certification testing with TSYS. Plan certification batches strategically to minimize re-testing.
12. Error Handling & Retry Strategy
| Scenario | Action |
|---|---|
| Network timeout to TransIT | Retry with exponential backoff (max 3 attempts, 1s -> 2s -> 4s) |
| TransIT returns 5xx | Retry with exponential backoff (max 3 attempts) |
| TransIT returns 4xx | Do NOT retry. Log error, return failure to caller. |
| Duplicate transaction suspected | Use idempotency key (Spanner) to prevent double-charge |
| TransIT unreachable | Queue transaction in Pub/Sub for retry. Alert ops. |
Idempotency
Every transaction request includes a client-generated externalReferenceID (UUID). If a timeout occurs and we retry, TransIT will recognize the duplicate and return the original response.
13. Pre-Requisites (Blockers)
| Item | Status | Impact |
|---|---|---|
| TransIT developer portal access | BLOCKED | Cannot verify API specs |
| Sandbox deviceID + transactionKey | BLOCKED | Cannot test any transactions |
| RESOLVED, NexGO | NexGO selected for card-present semi-integration | |
| RESOLVED | SmartConnect API docs + SmartSDK v3.08.012 AAR obtained. Semi-integration via Android Intents. See section 7. | |
| Second TSYS meeting | NOT SCHEDULED | Need to clarify NexGO-specific scope and back office cert |
14. References
- TransIT Developer Portal: https://developerportal.transit-pass.com/
- TransIT API Revision History: https://developerportal.transit-pass.com/developerportal/resources/dist/#/api-specs/
- TSYS Semi-Integrated EMV: https://tsysacquiring.my.site.com/partner/s/TSYS-Semi-integrated-EMV-Solution
- TSYS Developers: https://developers.tsys.com/api
- NexGO SmartConnect API:
NEXGO SmartConnect 21-0625.pdf(Rev. 1.18, Oct 2024) , local:Nexgo-Android-Public/Semi-Integration/ - NexGO SmartSDK: v3.08.012 AAR , local:
Nexgo-Android-Public/Nexgo-Smart-SDK/Library and Release Documentation/ - NexGO SmartSDK API Reference:
Nexgo_SmartPos_API_3.08_012.pdf, local:Nexgo-Android-Public/Nexgo-Smart-SDK/Library and Release Documentation/ - NexGO Developer Contact: sdk@nexgo.us