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).

FlowServiceWhat Happens
Full transfer core-service (CardsFacade) Unload src -> dest, block src
KYC-lock clone core-service (CardsFacade) Clone lock, block src, no money moved
P2P transfer axis-service (CardAccountTransferTask) Unload src -> dest, both stay active
1

Full Card Transfer (Replacement)

Service: core-service · POST /core/api/v1/{partnerExternalId}/cards/transferCardsFacade.transfer()

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.

Transfer Steps

StepTagDescription
1. Validate cards core-service Schemes must match. Destination must be NOT_ACTIVATED. Source must be ACTIVATED (checked inside TribeService).
2. Check for KYC lock database resolveRegistrationLock(sourceCard) -- if source is SUSPENDED or BLOCKED and has an unfunded KycLockEntity, branch to the KYC-lock clone path (section 02). Otherwise continue here.
3. Link destination database Finds the source card's PersonPartnerPaymentCardEntity link and creates the same link for the destination card.
4. Validate source status Tribe getCardStatus(sourceCard) -- source must be ACTIVATED. Any other status is rejected with WrongCardStatusException.
5. Activate destination Tribe getCardStatus(destinationCard) -- if NOT_ACTIVATED, calls activateCard(destinationCard). If any other non-active status, rejected.
6. Check balance + unload Tribe Validates source has sufficient balance for request.getAmount(). Then tribeHttpClient.unloadCard() -- single Tribe call debits source and credits destination atomically.
7. Block source card Tribe TribeService.blockedCard(sourceCard) -- status -> BLOCKED.
8. Unlink source card database Deletes PersonPartnerPaymentCardEntity for the source card without suspending it (already blocked).
9. Record transfer database CardTransferService.recordSuccessfulTransfer() -- persists CardTransferEntity with status COMPLETED and the transferred amount.

Card Status Changes

CardBeforeAfterTribe Call
source ACTIVATED BLOCKED changeCardStatus -> B
destination NOT_ACTIVATED ACTIVATED activateCard (inside unloadCard)
2

KYC-Locked Card Transfer (Clone Lock)

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

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.

Clone Steps

StepTagDescription
1. Block source card Tribe TribeService.blockedCard(sourceCard) -- block first to prevent concurrent clone races. A second request hitting this step sees the card already BLOCKED and gets WrongCardStatusException.
2. Clone KYC lock database 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.
3. Record transfer database 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
source SUSPENDED BLOCKED changeCardStatus -> B
destination NOT_ACTIVATED NOT_ACTIVATED none (stays unactivated until KYC passes)
3

P2P Fund Transfer

Service: axis-service · POST /api/cpm/v1/partners/{partnerId}/cards/actions/prepareCardAccountTransferTask

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.

Transfer Steps

StepTagDescription
1. Resolve source and dest database Both cards resolved from the action's AccessScope. Validates they belong to the same program and that source != destination account.
2. Get Tribe IDs database Looks up SPI card IDs and account IDs for both source and destination via EntityExtRef.
3. Unload source -> dest Tribe Transfers.unload(srcCard, destCard, srcAccount, destAccount, amount, ref, "p2pFundTransfer") -- single Tribe call moves funds between card accounts.
4. Complete action database Action status -> COMPLETE. No card status changes -- both cards remain ACTIVATED.

Card Status Changes

CardBeforeAfterTribe Call
source ACTIVATED ACTIVATED none
destination ACTIVATED ACTIVATED none
4

Where Things End Up

KycLockEntity Lifecycle

StateWhenDetails
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) 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 transfer core.card_transfer COMPLETED transferred amount core-service
KYC-lock clone core.card_transfer COMPLETED BigDecimal.ZERO core-service
KYC verification cpm.tran_reference -- load amount axis-service
P2P transfer cpm.action_request COMPLETE in params_json axis-service