> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polygon.technology/llms.txt
> Use this file to discover all available pages before exploring further.

# Debit cards

> Use a debit card as an on-ramp funding source or an off-ramp payout destination in OMS.

A debit card connects to OMS in two directions. As a **funding source**, OMS pulls US dollars from the card and delivers crypto to a wallet (a card buy). As a **payout destination**, OMS sells crypto and pushes the dollars to the card (a card sell). Both use the standard quote-and-transaction flow.

You register a card once per customer. It becomes an [external account](/payments/external-accounts) with an `ext_` ID, and every later quote references that ID. Cards must be enabled on your project, and the customer needs an active `usd` endorsement.

## Choose an integration path

Your PCI DSS status decides how you capture card details:

* **Not PCI DSS compliant:** you must use the OMS browser SDK. The card number, expiry, and CVC are typed into secure fields hosted by Very Good Security (VGS), which replaces the card data with aliases before it reaches OMS. Your systems only see the resulting `ext_` ID.
* **PCI DSS compliant:** you may call the API directly from your certified environment, or use the SDK to keep card data out of your PCI scope.

Both paths produce the same external account. After registration, quotes and transactions always run server to server with your secret key.

## Registering a card

### With the browser SDK

<Note>
  The OMS browser SDKs are in early access. [Contact us](https://polygon.technology/get-access?utm_source=docs\&utm_medium=card\&utm_campaign=oms_access) to request access.
</Note>

The browser SDK renders the secure card fields and attaches the risk session for you. Install the card SDK and its companion risk SDK:

```bash theme={null}
npm install @polygonlabs/oms-sdk @polygonlabs/oms-risk-sdk
```

You need a publishable key (`pk_...`) from the OMS Dashboard, and your project must be provisioned for risk collection.

Your server exposes one authenticated endpoint that issues a customer token. It checks your own session, looks up the user's OMS customer ID, calls `POST /customers/{customerId}/token` with your secret key, and returns the `{ token, expiresAt }` response unchanged. Tokens expire after one minute, so issue a fresh one on every request.

In the browser, wrap your app in `OMSProvider` and render `DebitCardForm` on the card page:

```tsx theme={null}
import { createRiskClient } from '@polygonlabs/oms-risk-sdk'
import { OMSProvider, DebitCardForm } from '@polygonlabs/oms-sdk/react'

const riskConfig = createRiskClient()

async function getToken() {
  const response = await fetch('/api/oms/customer-token', { method: 'POST' })
  if (!response.ok) throw new Error('Could not authorize the card submission')
  return response.json()
}

export function AddCard({
  customerId,
  onSaved,
}: {
  customerId: string
  onSaved: (externalAccountId?: string) => void
}) {
  return (
    <OMSProvider
      publishableKey="pk_..."
      riskConfig={riskConfig}
      customerId={customerId}
      getToken={getToken}
    >
      <DebitCardForm onSuccess={({ externalAccountId }) => onSaved(externalAccountId)} />
    </OMSProvider>
  )
}
```

`DebitCardForm` includes the card fields, billing address inputs, and inline validation. `onSuccess` fires when OMS accepts the card and returns its `ext_` ID. If a submission times out, the card may or may not have been saved: list the customer's external accounts with `GET /external-accounts?customerId=...` and match the card's last four digits and expiry before letting them try again.

### With the API

PCI DSS compliant partners register a card with `POST /external-accounts/cards`. Cards have their own endpoint: the generic `POST /external-accounts` rejects `type: card` with `CardMustUseCardEndpoint`.

```json theme={null}
{
  "customerId": "cst_...",
  "details": {
    "cardNumber": "4111111111111111",
    "cvv": "123",
    "expiryMonth": 4,
    "expiryYear": 2028
  },
  "billingAddress": {
    "firstName": "Jane",
    "lastName": "Smith",
    "line1": "123 Main St",
    "city": "Austin",
    "state": "TX",
    "zipCode": "78701",
    "country": "US"
  },
  "webSessionId": "...",
  "deviceFingerprint": "..."
}
```

`webSessionId` and `deviceFingerprint` are required. They are a matched risk-session pair that the OMS risk SDK collects in the customer's browser; pass them through unchanged and do not log or store them.

### After registration

The billing address belongs to the card, not the customer's KYC record, and the card processor uses it for address verification. If you omit it, OMS uses the customer's residential address.

The card number and CVV are write-only. The response returns only `cardNumberLast4`, `cardProvider`, `cardType`, `expiryMonth`, `expiryYear`, and `billingAddressSource`. Only debit cards are accepted. In sandbox, use the test card `4111 1111 1111 1111` with any future expiry and any three-digit CVC.

A new card starts `pending`. Once the processor verifies it, the card moves to `active` and you can use it on quotes. Subscribe to `externalAccount.statusChanged` or poll `GET /external-accounts/{id}` to see the change. See [External accounts](/payments/external-accounts) for the full lifecycle.

## Use a card on a quote

Reference the card as the quote `source` for a buy, or as the `destination` for a sell. The card side always uses `asset: usd`, and OMS infers the network.

<CodeGroup>
  ```json Card buy theme={null}
  {
    "customerId": "cst_...",
    "source": {
      "type": "card",
      "details": { "id": "ext_...", "asset": "usd" },
      "amount": "100.00"
    },
    "destination": {
      "type": "walletExternal",
      "details": { "id": "ext_...", "asset": "usdc", "network": "polygon" }
    }
  }
  ```

  ```json Card sell theme={null}
  {
    "customerId": "cst_...",
    "source": {
      "type": "walletCrypto",
      "details": { "id": "wlt_...", "asset": "usdc", "network": "polygon" },
      "amount": "100.00"
    },
    "destination": {
      "type": "card",
      "details": { "id": "ext_...", "asset": "usd" }
    }
  }
  ```
</CodeGroup>

## Execute the transaction

Create the transaction with `POST /transactions`, passing the quote ID and the risk-session pair from the customer's browser:

```json theme={null}
{
  "quoteId": "qt_...",
  "webSessionId": "...",
  "deviceFingerprint": "..."
}
```

A card transaction without `webSessionId` is rejected with `RiskSessionRequired`. See [Transactions](/payments/transactions) for statuses and webhooks.

<Note>
  Card registration, quote, and transaction requests accept an `Idempotency-Key` header. Use one key per logical attempt and reuse it when you retry after a timeout.
</Note>

## Related

* [External accounts](/payments/external-accounts): the shared model for cards, banks, and external wallets
* [Transactions](/payments/transactions): the quote-and-execute flow
* [Currencies and rails](/payments/core-concepts/currencies-and-rails): supported assets and rails
