CardPointe Integration
1. Overview
This document specifies the monorepo's Fiserv CardPointe integration (#883 multi-processor), covering the card-not-present (CNP) Gateway API path, the Bolt card-present terminal path, and the interim tokenization stack that sits in front of both.
libs/cardpointe-clientis the canonical CardPointe Gateway API wrapper (CardPointeGatewayApiinterface +CardPointeClientimplementation), plusResilientCardPointeClient, a Resilience4j retry/circuit-breaker decorator.services/processingintegrates throughprocessor/fiserv/cardpointe/FiservCardPointePaymentProcessor, the SPI adapter registered as(FISERV, CARDPOINTE), reachable through the normal registry/router path — this is not a scaffold, it is live.libs/bolt-client+services/card-present's Bolt controller/service cover the card-present terminal side (Bolt devices settle through CardPointe under the hood).- Adapter identity:
ProcessorType.FISERV+ProcessorPlatform.CARDPOINTE. SeeProcessorCapabilities.cardPointeDefault()inservices/processing/src/main/kotlin/com/myriad/gateway/processing/domain/payment/ProcessorCapabilities.ktfor the exact capability flags (GATEWAY integration depth,cpEmvL3Owner = PLATFORM,ownsTokenization = true,ownsSettlement = false— CardPointe auto-batches).
2. Endpoints
| Purpose | Host | Notes |
|---|---|---|
| CardPointe Gateway API (CNP) — sandbox | https://fts-uat.cardconnect.com/cardconnect/rest | CardPointeConfig.Environment.SANDBOX |
| CardPointe Gateway API (CNP) — production | https://fts.cardconnect.com/cardconnect/rest | CardPointeConfig.Environment.PRODUCTION |
| Bolt Integrated Terminal API (card-present) | https://bolt-uat.cardpointe.com/api/ (sandbox) | Session auth; devices are Clover Flex/Pocket/Compact/Mini, Ingenico Lane 3600/7000/8000, Link2500 |
| CardPointe Reporting (transaction verification) | https://cardpointe-uat.cardconnect.com | Use for UAT verification — cross-check respstat/respcode/retref against gateway-side processor_attempts/transactions rows; see websites/support-docs/docs/ops/multi-processor-cert-evidence.md |
Sandbox test MIDs: 800000070034 (PeakPOS IC Plus) and 800000070035 (PeakPOS Surcharge-enrolled).
Authentication is HTTP Basic (username + password) per request, scoped to a merchid. CardPointeConfig rejects cleartext http:// base URLs except for localhost/127.0.0.1 (MockWebServer in tests).
3. Credential Payload Shape
CardPointeProcessorCredentials (services/processing/.../domain/payment/ProcessorCredentials.kt), sourced from the typed credential_payload JSON on credential_profiles (processor type FISERV, platform CARDPOINTE):
data class CardPointeProcessorCredentials(
val merchid: String,
val username: String,
val password: String,
val environment: CardPointeConfig.Environment? = null, // SANDBOX | PRODUCTION
val baseUrl: String? = null,
)
merchid,username,passwordare required —ProcessorCredentialResolverthrowsConflictExceptionif any is blank.environmentandbaseUrlare both optional.ProcessorAdapterFactoryresolves the effective base URL asbaseUrl ?: (environment ?: SANDBOX).baseUrl— an explicitbaseUrlalways wins, and the default without either field is the sandbox host (fail-safe: a profile can never silently point at production without an explicit choice).ProcessorAdapterFactorybuilds and caches oneCardPointeClient/ResilientCardPointeClient/FiservCardPointePaymentProcessorinstance percredentialProfileId— CardPointe is multi-tenant per credential profile, unlike TransIT's single per-service singleton. Only the Resilience4jRetry/CircuitBreakerpolicy beans (CardPointeClientConfig) are shared singletons; they carry no per-profile secrets.
4. Tokenization Stack (Interim, User-Approved)
The tokenization stack in front of CardPointe is an interim design, explicitly approved by the user, with a known migration path to owned/network tokenization later:
- CardSecure tokens back the canonical vault. A sale/auth response that returns a CardSecure
tokentriggers minting of a canonical token (av_tok_…); the CardSecure token is provisioned into the vault, never held outside it.ProcessorResult.tokenIdis always the canonical token, never the raw CardSecure value. - Customer Profiles API creates a profile on store-worthy approvals.
CardPointeClient.createProfile/updateProfile(PUT /profile) persist{profileid, acctid}asprocessor_tokens.provider_token_metadata(see theProfileMetadataholder inFiservCardPointePaymentProcessor.kt— field names match the wire response exactly,@JsonProperty-annotated on both fields; see the Jackson gotcha inCLAUDE.md). - Recurring/MIT funds via profile-or-token. A recurring/merchant-initiated charge uses
/authwithcapture=Y,ecomind=R(CardPointeEcomind.RECURRING),cof=M(CardPointeCofType.MERCHANT), andcofscheduled=Y— no CVV is sent, matching the CardConnect stored-credential contract. It funds from the stored profile/account when one exists, or the raw CardSecure token otherwise. - Expiry sync-back. If an auth response reports an updated expiry (CardPointe's Card Account Updater refreshing a stored credential — see §7 CAU below), the new expiry is synced onto the canonical token record.
- Hosted iFrame Tokenizer ships in
websites/checkout—tokenizationModeis a field on checkout sessions; CSPframe-srcincludes*.cardconnect.com; the flow is SAQ-A-style (raw PAN never transits gateway-owned JS).
Why this matters for later migration: the canonical av_tok_… token is the only reference any other part of the system holds. CardSecure specifics (profile IDs, account IDs, raw CardSecure tokens) live entirely inside processor_tokens.provider_token_metadata, scoped to the (canonical_token_id, processor_type, credential_profile_id) tuple. Migrating off CardPointe onto Rapid Connect or a network-token scheme later is a vault-internal change — no caller-facing token format changes.
5. Bolt Card-Present Flow
Bolt is CardPointe's Integrated Terminal API, providing the card-present companion to the CNP Gateway API path documented above.
Flow: bind → sale → follow-ons
- Bind: a Bolt terminal is registered in the
bolt_terminalsdevice registry (migrationV063), associated with a location/credential profile. - Sale:
libs/bolt-clientopens a session against the Bolt host and callsauthCard. Two properties are load-bearing:authCardis never auto-retried — an ambiguous/timeout outcome from a card-present device must not be blindly resubmitted (double-charge risk).- The client preserves indeterminate-outcome semantics rather than collapsing them into approve/decline — callers must handle "unknown, go check" as a distinct case.
- Record: a completed Bolt sale is recorded into
services/processingvia/internal/transactions/syncwithsource=BOLT(andterminalTransactionId= the CardPointeretref).BoltTerminalController(/api/v1/card-present/bolt) +BoltTerminalServiceown this on thecard-presentside. - Follow-ons: void/refund against a Bolt-recorded transaction route through
(FISERV, CARDPOINTE)using the hydrated/carried route columns on the original transaction — the sync spec carriescredentialProfileIdend-to-end so a follow-on re-selects the exact adapter/profile the original sale used, not just "some CardPointe profile."
ProcessorCapabilities.cardPointeDefault().cardPresent = true reflects that this adapter serves Bolt-recorded follow-ons, even though the adapter itself never originates a card-present authorization (the Bolt device does).
6. Surcharge, Funding, ACH
| Feature | Status | Notes |
|---|---|---|
| Surcharge | Internal-only, fail-closed | BIN-lookup (GET /bin/{merchid}/{account}, CardPointeClient.binLookup) determines card-network surcharge eligibility before any surcharge is applied; an inconclusive/unknown BIN result fails closed (no surcharge applied) rather than guessing. surchargeAmount pass-through math funds amount + surchargeAmount as the total wire amount. Kept internal-only pending vendor verification (see Open Questions). |
| Funding | On-demand only | CardPointeFundingLookupController (/api/v1/internal/reconciliation/cardpointe/funding, internal-service-only) is a synchronous, on-demand lookup against the live CardPointe Funding API for a single credentialProfileId + date. It does not persist funding rows, does not run on a schedule, and is not a durable reconciliation store — every call re-fetches from CardPointe. A durable ingestion pipeline is deferred. |
| ACH | Live, no vault/profile involvement | achSale/achAccountType (ECHK/ESAV)/achRoutingNumber fund via /auth with the CardPointe ACH fields. ProcessorCapabilities.achSale = true only for this adapter. Surcharge is explicitly rejected on ACH transactions (card-network surcharge programs do not apply to ACH/eCheck) — validateAchPreconditions throws if surchargeAmount != null. No canonical-token/profile involvement: ACH does not go through the CardSecure/profile stack in §4. |
7. Open Vendor Questions
These remain unresolved pending vendor (Fiserv/CardConnect) confirmation. Do not treat any of these as settled without a written vendor answer:
- Surcharge auto-apply vs. explicit amount, and the BIN endpoint response shape. Whether CardPointe auto-applies surcharge server-side under certain merchant configurations (which would double-apply against our pass-through math) is unconfirmed; the exact
CardPointeBinResponsefield set for the surcharge-eligibility decision needs a live vendor cross-check, not just the sandbox shape observed so far. - ACH enablement on sandbox MIDs and the field contract. Whether ACH is actually enabled/certified on the sandbox MIDs in use (
800000070034/800000070035) and the exact required/optional field set forAchSale-equivalent CardPointe requests needs vendor confirmation before any ACH UAT evidence is submitted. - CAU (Card Account Updater) enrollment mechanics. See the dedicated CAU section below.
- Recurring network-transaction-id echo. Whether CardPointe echoes back a network transaction identifier on recurring/MIT charges (needed for downstream reconciliation/dispute workflows) is unconfirmed.
- Bolt session TTL/cancel semantics and device shipment. Exact Bolt session lifetime, cancel-in-flight semantics, and the physical device shipment/provisioning process for Clover/Ingenico/Link2500 terminals are not yet documented against a live vendor answer.
- Funding path/shape. The exact
GET /fundingquery-param shape and date format (YYYYMMDDvs.MMDD) are markedTODO(vendor-verify)inCardPointeClient.funding— confirm against the CardPointe Funding API reference. - Per-merchant iframe tokenizer subdomain. Whether the Hosted iFrame Tokenizer origin is a shared CardPointe domain or a per-merchant subdomain (which would affect the checkout CSP
frame-srcallowlist) is unconfirmed.
CAU (P2d) — Card Account Updater
CardPointe's Card Account Updater (CAU) can refresh a stored credential's expiry (and potentially PAN/account reference) behind the scenes; §4 above already syncs an updated expiry back onto the canonical token record when an auth response reports one.
Open question: the exact CAU enrollment mechanics for our sandbox MIDs — whether enrollment is automatic once a Customer Profile exists, or requires an explicit registration call/flag per merchant/profile — is not yet confirmed by the vendor. Similarly unconfirmed: whether CAU operates on profiles only, or also refreshes bare stored CardSecure tokens that are not attached to a Customer Profile. Since our storage model always creates a Customer Profile for store-worthy approvals (§4, step 2), profile-based storage should already satisfy whatever CAU requires — but this is an assumption pending vendor confirmation, not a verified fact.
Current implementation state: no explicit CAU registration/enrollment API call is implemented in libs/cardpointe-client or FiservCardPointePaymentProcessor — this is intentional, pending the vendor answer above. The expiry-sync-back consumer path (§4, step 4) is implemented and ready to receive CAU-driven updates whenever CardPointe starts sending them; nothing further should be built here (e.g., a registration call) until the enrollment mechanics are confirmed.
8. Related Docs
websites/support-docs/docs/ops/multi-processor-cert-evidence.md— certification/UAT evidence handling, including the CardPointe reporting-tool cross-check.- The #883 multi-processor implementation plan (
2026-06-20-883-multi-processor-implementation.md, tracked with the repo's other planning artifacts) — see its CURRENT STATE section for how CardPointe/Bolt/smart-routing fit into the overall program. CLAUDE.md/AGENTS.md— repo-wide dependency table (libs/cardpointe-client,libs/bolt-client) and the bare-JsonMapper/@JsonPropertygotcha referenced in §4 above.