Marqeta & Airwallex

The Seller Card was originally issued by Marqeta and is being migrated to Airwallex. Both are live simultaneously, so the client must present a coherent "Your Cards" experience across two very different integrations.

The business rules are in Seller Card; this page covers the integration mechanics.

Marqeta

Module

MarqetaModule.forRoot({ sandbox: environment.marquetaSandbox }), in src/app/core/marqeta/

Modelled as

A payment method — lives in the paymentMethods slice

Status enum

PCardStatus (Active, Suspended, Unactivated, Terminated)

Form factor

PCardTypeIndicator, derived from Marqeta’s instrument_type string

Key

paymentConfigKey on the payment method; card_token on the p-card

Because it is a payment method, Marqeta card actions and payout-method actions share a slice. Actions such as sellerCardRequest, sellerCardSuccessResponse, sellerCardActivate and sellerCardActivateSuccess sit alongside savePaymentMethod and makePaymentMethodAsDefault.

instrument_type is a free-form string, normalized with a substring check:

const marqetaFormFactor = (instrumentType: string | undefined): string =>
    (instrumentType ?? '').toUpperCase().includes('VIRTUAL')
        ? PCardTypeIndicator.Virtual
        : PCardTypeIndicator.Physical;

Values like PHYSICAL_ICC and VIRTUAL_PAN therefore both resolve correctly, and anything unrecognised defaults to physical.

Airwallex

Base URL

environment.airwalexBaseUrl

Service

AirwallexCardService (core/pyb/services/airwallex-card.service.ts)

Modelled as

A card — lives in the airwallexCards slice

Status enum

AirwallexCardStatus (ACTIVE, INACTIVE, CLOSED, PENDING)

Form factor

CardFormFactor (PHYSICAL, VIRTUAL)

Key

cardId

Endpoints

GET /suppliers/{key}/cards

Unified list across both issuers, each entry tagged with provider.

GET /suppliers/{key}/card_providers

Which issuers and form factors the supplier may order.

GET /suppliers/{key}/airwallex

Cardholder consent status (cardholder_terms_accepted).

POST /suppliers/{key}/airwallex/consent

Record consent.

POST /suppliers/{key}/cards/newcard

Order a card.

POST /suppliers/{key}/airwallex/pan-token

Short-lived token for revealing the PAN.

POST /suppliers/{key}/cards/block/{cardId}

Suspend, with { provider: Airwallex } in the body.

POST /suppliers/{key}/cards/activate/{cardId}

Activate or unblock.

/cards, /cards/newcard, /cards/block/ and /cards/activate/ are unified endpoints — the issuer is selected by the provider field, not by the path. Only the consent and PAN-token endpoints are Airwallex-specific.

/**
 * Persist the supplier's acceptance of the Airwallex cardholder terms + privacy policy.
 * Must be recorded before a card is ordered — if this fails, the order must not proceed.
 */
saveConsent(supplierKey: string): Observable<void> {
    return this.http.post<void>(`${this.env.apiUrl}/suppliers/${supplierKey}/airwallex/consent`, {});
}

Consent is required both for new Airwallex cards and for migrating existing Marqeta cardholders. The migration path is delivered as the airwallex_pcard_terms dashboard notification.

Order request shaping

newCard(supplierKey, { provider, formFactor, recipientAddress, expediteCard }) {
    return this.http.post(`${this.env.apiUrl}/suppliers/${supplierKey}/cards/newcard`, {
        provider,
        form_factor: formFactor,
        ...(recipientAddress ? { recipient_address: recipientAddress } : {}),
        ...(expediteCard != null ? { expedite_card: expediteCard } : {}),
    });
}

recipient_address and expedite_card are omitted for Airwallex, which ships to the cardholder address already on file. Only issuers with physical shipping — Marqeta — receive them.

The unification layer

core/pyb/interfaces/unified-supplier-card.interface.ts maps both issuers onto UnifiedSupplierCard with a common status, form factor, label keys and colours, retaining the original model in raw so actions can be routed correctly.

Status normalisation:

UnifiedCardStatus PCardStatus AirwallexCardStatus

Active

Active

ACTIVE

Blocked

Suspended

INACTIVE

Pending

Unactivated

PENDING

Closed

Terminated

CLOSED

When a Marqeta card appears in /cards without a matching payment method, synthesizeMarqetaPaymentMethod() fabricates a minimal stand-in carrying the card token. That is sufficient for block and activate; the view details flow needs a real paymentConfigKey and is only offered when a genuine payment method was matched.

Card data handling

The application never handles a raw PAN, CVV or PIN. Card details are revealed through a provider-issued short-lived token (getPanToken() for Airwallex) rendered inside the provider’s own secure surface, and every reveal is behind a 2FA challenge.

Decline codes

NetworkResponseCodeEnum maps roughly thirty issuer decline codes to explanatory copy — expired card, lost or stolen pick-up, insufficient funds, limit exceeded, CVV failure, account blocked, suspected fraud, closed account, and the Mastercard Send-specific ZN / ZD / ZR codes. See Enum Reference.