Skip to main content

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-client is the canonical CardPointe Gateway API wrapper (CardPointeGatewayApi interface + CardPointeClient implementation), plus ResilientCardPointeClient, a Resilience4j retry/circuit-breaker decorator.
  • services/processing integrates through processor/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. See ProcessorCapabilities.cardPointeDefault() in services/processing/src/main/kotlin/com/myriad/gateway/processing/domain/payment/ProcessorCapabilities.kt for the exact capability flags (GATEWAY integration depth, cpEmvL3Owner = PLATFORM, ownsTokenization = true, ownsSettlement = false — CardPointe auto-batches).

2. Endpoints

PurposeHostNotes
CardPointe Gateway API (CNP) — sandboxhttps://fts-uat.cardconnect.com/cardconnect/restCardPointeConfig.Environment.SANDBOX
CardPointe Gateway API (CNP) — productionhttps://fts.cardconnect.com/cardconnect/restCardPointeConfig.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.comUse 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, password are required — ProcessorCredentialResolver throws ConflictException if any is blank.
  • environment and baseUrl are both optional. ProcessorAdapterFactory resolves the effective base URL as baseUrl ?: (environment ?: SANDBOX).baseUrl — an explicit baseUrl always 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).
  • ProcessorAdapterFactory builds and caches one CardPointeClient/ResilientCardPointeClient/FiservCardPointePaymentProcessor instance per credentialProfileId — CardPointe is multi-tenant per credential profile, unlike TransIT's single per-service singleton. Only the Resilience4j Retry/CircuitBreaker policy 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:

  1. CardSecure tokens back the canonical vault. A sale/auth response that returns a CardSecure token triggers minting of a canonical token (av_tok_…); the CardSecure token is provisioned into the vault, never held outside it. ProcessorResult.tokenId is always the canonical token, never the raw CardSecure value.
  2. Customer Profiles API creates a profile on store-worthy approvals. CardPointeClient.createProfile/updateProfile (PUT /profile) persist {profileid, acctid} as processor_tokens.provider_token_metadata (see the ProfileMetadata holder in FiservCardPointePaymentProcessor.kt — field names match the wire response exactly, @JsonProperty-annotated on both fields; see the Jackson gotcha in CLAUDE.md).
  3. Recurring/MIT funds via profile-or-token. A recurring/merchant-initiated charge uses /auth with capture=Y, ecomind=R (CardPointeEcomind.RECURRING), cof=M (CardPointeCofType.MERCHANT), and cofscheduled=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.
  4. 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.
  5. Hosted iFrame Tokenizer ships in websites/checkouttokenizationMode is a field on checkout sessions; CSP frame-src includes *.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

  1. Bind: a Bolt terminal is registered in the bolt_terminals device registry (migration V063), associated with a location/credential profile.
  2. Sale: libs/bolt-client opens a session against the Bolt host and calls authCard. Two properties are load-bearing:
    • authCard is 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.
  3. Record: a completed Bolt sale is recorded into services/processing via /internal/transactions/sync with source=BOLT (and terminalTransactionId = the CardPointe retref). BoltTerminalController (/api/v1/card-present/bolt) + BoltTerminalService own this on the card-present side.
  4. 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 carries credentialProfileId end-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

FeatureStatusNotes
SurchargeInternal-only, fail-closedBIN-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).
FundingOn-demand onlyCardPointeFundingLookupController (/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.
ACHLive, no vault/profile involvementachSale/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:

  1. 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 CardPointeBinResponse field set for the surcharge-eligibility decision needs a live vendor cross-check, not just the sandbox shape observed so far.
  2. 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 for AchSale-equivalent CardPointe requests needs vendor confirmation before any ACH UAT evidence is submitted.
  3. CAU (Card Account Updater) enrollment mechanics. See the dedicated CAU section below.
  4. 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.
  5. 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.
  6. Funding path/shape. The exact GET /funding query-param shape and date format (YYYYMMDD vs. MMDD) are marked TODO(vendor-verify) in CardPointeClient.funding — confirm against the CardPointe Funding API reference.
  7. 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-src allowlist) 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.


  • 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/@JsonProperty gotcha referenced in §4 above.