Card Provisioning
The Whole Lifecycle
Provisioning a card is two stages. bulkCardGen reads a template and creates the cards on Tribe, logging the new ids to a file. Those ids then feed bulkCardImport, which reads each card back from Tribe and mirrors it into the cpm database. Gen writes only to Tribe; import is what populates our DB.
Stage 1: bulkCardGen — Create on Tribe
docker exec ... acli.sh bulkCardGen -input pc-gen.csv -output pc-out.csv -threads 1 -tribeCredential DEFAULT -rundry falseThe Input File — A 3-Section Template
| Section | Purpose |
|---|---|
| [bulk_card_create] | One row per kind of card. ~70 columns mapping to Tribe's create-card params; a card_count column says how many to mint from that row. |
| [create_card_defaults] | request_param,default_value pairs. Any column a row leaves blank falls back here. Unknown param names are rejected. |
| [address_templates] | Named, reusable address blocks. A row references one via holder_address / delivery_address / bulk_address instead of repeating fields. |
Sample Template
# comments start with # [bulk_card_create] card_program_id,card_design_id,holder_address,card_count 380107,55,uk_office,500 380107,55,uk_office,250 [create_card_defaults] request_param,default_value card_country_ison,826 card_virtual,0 default_locale,en_GB [address_templates] template_name,city,zipcode,country_ison,address_line_1 uk_office,London,EC1A 1BB,826,1 High StHow Each Field Is Resolved
Fields are resolved in this priority order: row value → if blank, defaults section → if blank and an address field, named address template → else null / omitted.
card_count fans out: the first row above mints 500 identical cards, the second 250 — so the file's batch size is the sum of every row's count. Country/currency codes are normalised to numeric-3. A field that stays null is simply left out of the Tribe request.
Flow — What Happens Per Card
| Step | Tag | Description |
|---|---|---|
| 1. Build | resolve | Pull each column for this card (row -> defaults -> address template), normalise ISO codes, and assemble the Tribe CreateCardAction request. |
| 2. Gate | pool | Card #1 runs synchronously (fail-fast -- bad creds/program surface before the whole batch fires); the rest fan out across the pool, capped at 6 concurrent for Tribe. |
| 3. Tribe | RSA | new Cards(api).createCard(req) creates the card on Tribe and returns its ids. -rundry true fabricates a response and skips Tribe -- for validating a template safely. |
| 4. Check | error | A Tribe rejection is counted; with -maxErrorCount at its default 0, the first error stops the batch. Pass a higher value to push through. |
| 5. Record | output | A single notifier thread appends the result -- cardId,cardExtId,programId,designId,... on success, or seq,rootCause on failure. |
The Hand-off
Gen's output rows are exactly the ids import needs — Tribe's card id becomes import's spiCardId; the external ref carries over.
| bulkCardGen Output | bulkCardImport Input |
|---|---|
| cardId (e.g. 1499713) | spiCardId |
| cardExtId (e.g. 9295121628797499) | externalId |
| (not used) | PAN (optional) |
Field mapping:cardId → spiCardId · cardExtId → externalId · PAN optional (else import fetches it from Tribe).
Stage 2: bulkCardImport — Mirror into cpm
docker exec ... acli.sh bulkCardImport -input cards.csv -threads 6 -progressEvery 10 -tribeCredential DEFAULTThe Input File — A Flat Card List
| Field | Description |
|---|---|
| spiCardId | Tribe card id (required, first numeric field) |
| externalId | Your external reference (required, second numeric field) |
| PAN | Optional. If >= 16 digits, masked locally; if omitted, import fetches from Tribe via getCardNumber. |
| Extra columns | Ignored silently. |
Sample cards.csv
# spiCardId, externalId, optional PAN (extra columns ignored) 1499713,9295121628797499, 1499714,9295121628797500,5480391234568598 # header / blank lines that don't match the pattern are silently skippedInput Rules
- Row pattern
^[0-9]{6,20}\s*,\s*[0-9]{6,20}.*— the first two numeric fields (spiCardId, externalId) are required; non-matching rows are silently skipped. - PAN is optional. Supplied (≥16 digits) → masked locally; omitted → import makes an extra Tribe
getCardNumbercall to fetch + mask + hash it. - Duplicate spiCardId → hard error — the whole run aborts before importing anything.
- On launch it counts the rows and prompts
"...importing N cards? [y/n]"— onlyyproceeds. - This list is usually gen's
out.csvreshaped (see the hand-off above):cardId→spiCardId,cardExtId→externalId.
Flow — Per Card (Its Own Committed Transaction)
| Step | Tag | Description |
|---|---|---|
| 1. Context | credential | Set Tribe credential. DEFAULT / VISA selects the RSA keys for the calls below. |
| 2. Tribe | getCardDetails | Reads the card from Tribe. Not on Tribe -> VendorException (card fails). Returns program, holder, account, currency, status... |
| 3. Tribe | getCardNumber | Only if no PAN supplied. Fetches + masks + hashes the PAN. |
| 4. Resolve | database | Find PartnerProgram by Tribe program id; look up existing card by SpiId / ExternalId. |
| 5. Idempotent | check | Already imported? Skip. Re-running the same list is safe. |
| 6. Commit | database | Write the card graph and commit this card's own transaction. One bad card rolls back alone. |
What Lands in cpm
| Entity | Table | Key Details |
|---|---|---|
| PaymentCardEntity | PAYMENT_CARD | maskedPan, panHash, homeCurrency, isVirtual, validUntil, designId, kyc_locked = false, requires_kyc = null |
| CardOrderEntity | CARD_ORDER | new, status COMPLETE |
| CustomerAccount | CUSTOMER_ACCOUNT | get-or-create by SpiId |
| AccountOwner | ACCOUNT_OWNER | KycStatus Unverified, get-or-create |
| status | ACTION_REQUEST | spiImport + Tribe status |
| EntityExtRef (x N) | external refs | Links every entity (SpiId, ExternalId, panHash) |
External references: SpiId (Tribe ids) · ExternalId (your ref) · panHash (lookup).
Concurrency — The Tribe Limit
Tribe throttles / flags us above 6 concurrent calls. Both commands cap their worker pool at 6 — and for these commands, pool size == concurrent Tribe calls.
| Worker | Operation | Notes |
|---|---|---|
| 1-6 | createCard / getCardDetails | Active Tribe calls via the worker pool |
| 7+ | waits | Blocked until a slot opens |
| output thread | write results | Never calls Tribe |
Where Everything Ends Up
Gen's out.csv is the bridge — its card ids become import's input. Skip import and the cards exist on Tribe but never appear in cpm.
Source Classes
BulkCardGenCommand · BulkCardOrderProcessor · BulkCardGenRequest · BulkCardImport · CardImportTask