Card Transfers

liveThree card transfer flows across core-service and axis-service: full card transfer, KYC-lock clone, and P2P fund transfer.· 2026-05-30
0

The Three Flows

Money moves between cards through three flows, split across core-service and axis-service. Full card transfer replaces one card with another (unload + block). P2P fund transfer moves funds between active cards. When a card is KYC-locked (never funded), the transfer clones the lock to the destination instead of moving money. The KycLockEntity is created during activation and consumed when KYC passes (axis-service) or cloned during card replacement (core-service).

all fund movements go through Tribe (RSA-encrypted HTTP) — unload, load, activate, block
card-to-card transfer KYC lock clone Tribe (external) cpm database
1

Full Card Transfer (Replacement)

core-service POST /core/api/v1/{partnerExternalId}/cards/transfer → CardsFacade.transfer()

card replacement — move all funds to a new card

The caller provides a source card (being replaced) and a destination card (the replacement). All funds are moved from source to destination via a single Tribe unloadCard call that debits one card and credits the other atomically. The source card is then BLOCKED.

core-service validate cards

Schemes must match. Destination must be NOT_ACTIVATED. Source must be ACTIVATED (checked inside TribeService).

database check for KYC registration lock

resolveRegistrationLock(sourceCard) — if source is SUSPENDED or BLOCKED and has an unfunded KycLockEntity, branch to the KYC-lock clone path (§2). Otherwise continue here.

database link destination to source owner

Finds the source card's PersonPartnerPaymentCardEntity link and creates the same link for the destination card.

tribe validate source status

getCardStatus(sourceCard) — source must be ACTIVATED. Any other status is rejected with WrongCardStatusException.

tribe activate destination if needed

getCardStatus(destinationCard) — if NOT_ACTIVATED, calls activateCard(destinationCard) to activate it at Tribe. If any other non-active status, rejected.

tribe check balance + unload

Validates source has sufficient balance for request.getAmount(). Then tribeHttpClient.unloadCard() — single Tribe call debits source and credits destination atomically.

tribe block source card

TribeService.blockedCard(sourceCard) — status → BLOCKED. Currently called inside unloadCard but should move to the facade (see note below).

database unlink source card

Deletes PersonPartnerPaymentCardEntity for the source card without suspending it (already blocked).

database record transfer

CardTransferService.recordSuccessfulTransfer() — persists CardTransferEntity with status COMPLETED and the transferred amount.

card status changes
cardbeforeaftertribe call
sourceactivatedblockedchangeCardStatus → B
destinationnot activatedactivatedactivateCard (inside unloadCard)
2

KYC-Locked Card Transfer (Clone Lock)

core-service Same endpoint as §1 — branched inside CardsFacade.transfer() when resolveRegistrationLock returns a lock

replacing a card that was never funded

When the source card is SUSPENDED (or BLOCKED from a previous failed attempt) and has an unfunded KycLockEntity, no money exists on the card to transfer. Instead, the lock is cloned to the destination card so it inherits the pending load instruction. The source card is blocked.

tribe block source card

TribeService.blockedCard(sourceCard) — block first to prevent concurrent clone races. A second request hitting this step sees the card already BLOCKED and gets WrongCardStatusException.

database clone KYC lock to destination

KycLockService.cloneKycLock(destCardId, existingLock) — creates a new KycLockEntity with the destination card's ID and SPI card ID, copying amount, currency, channel, and reference from the source lock.

database record transfer

CardTransferService.recordSuccessfulTransfer() — records with BigDecimal.ZERO amount (no funds moved). Person ID is null (KYC-locked cards are not yet linked to a person).

card status changes
cardbeforeaftertribe call
sourcesuspendedblockedchangeCardStatus → B
destinationnot activatednot activatednone (stays unactivated until KYC passes)
3

P2P Fund Transfer

axis-service POST /api/cpm/v1/partners/{partnerId}/cards/actions/prepare → CardAccountTransferTask

move funds between active cards in the same program

A peer-to-peer fund transfer between two cards in the same PartnerProgram. Neither card is blocked or suspended after — both stay active. Uses the same Tribe unload API as the full transfer but without any post-transfer status change.

database resolve source and destination

Both cards resolved from the action's AccessScope. Validates they belong to the same program and that source ≠ destination account.

database get Tribe IDs

Looks up SPI card IDs and account IDs for both source and destination via EntityExtRef.

tribe unload source → destination

Transfers.unload(srcCard, destCard, srcAccount, destAccount, amount, ref, "p2pFundTransfer") — single Tribe call moves funds between card accounts.

database complete action

Action status → COMPLETE. No card status changes — both cards remain ACTIVATED.

card status changes
cardbeforeaftertribe call
sourceactivatedactivatednone
destinationactivatedactivatednone
4

Where Things End Up

KycLockEntity lifecycle
created
during activateWithLoad (axis-service)
  • PK = cardId (one lock per card)
  • Stores: spiCardId, amount, currency, channel, reference
  • initial_funded_date = NULL (unfunded)
consumed (funded)
during KYC verification (axis-service)
  • initial_funded_date stamped atomically via UPDATE
  • Funds transferred from partner account → card
  • Card status: SUSPENDED → ACTIVATED
cloned (transferred)
during card replacement (core-service, §2)
  • New lock created for destination card
  • Copies amount, channel, reference from source
  • Source card: SUSPENDED → BLOCKED
never consumed
card expired or KYC never completed
  • initial_funded_date stays NULL
  • Card remains SUSPENDED indefinitely
  • Funds never loaded — still in partner account
transaction records
flowtablestatusamountservice
full transfercore.card_transferCOMPLETEDtransferred amountcore-service
KYC-lock clonecore.card_transferCOMPLETEDBigDecimal.ZEROcore-service
KYC verificationcpm.tran_referenceload amountaxis-service
P2P transfercpm.action_requestCOMPLETEin params_jsonaxis-service