> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.crisscross.money/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.crisscross.money/_mcp/server.

# React Integration

> Use the CrissCross wallets SDK to render Apple Pay and Google Pay buttons inside your own React checkout, initialised with nothing but a session ID.

### Overview

The CrissCross wallets SDK renders Apple Pay and Google Pay buttons inside your own checkout, on your own domain, while CrissCross keeps the wallet plumbing: merchant validation, token decryption, 3-D Secure, and the transaction itself.

The SDK is initialised with a **`sessionId`** and nothing else. CrissCross resolves the wallet identifiers, the accepted card networks and the merchant display name for that session on the server, exactly as it does for [Secure Fields](/secure-fields).

> **Note**
>
> There is no API key in your front-end bundle. Your OAuth `client_id` and `client_secret` are server-side credentials and must never reach the browser — the SDK does not accept them and does not need them. A `sessionId` authorises exactly one checkout and nothing else.

### Pre-requisites

* Apple Pay and Google Pay enabled on your account.
* Your payment domains registered and verified. See [Wallet Configuration](/wallets-configuration).
* A checkout session created server-side. Create it with `integrationType: "direct"`, since you are rendering the checkout yourself.

### Installation

```bash
npm install @crisscross/wallets
```

### Creating the session

Create the session from your server, as you would for any CrissCross payment, and pass the `sessionId` to your front-end. Never create sessions from the browser — session creation uses your OAuth credentials.

```bash
curl -X POST https://api.crisscross.money/v1/checkout/session \
     -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
  "merchantId": "YOUR_MERCHANT_ID",
  "merchantReference": "ORDER67890",
  "amount": 20000,
  "currency": "ZAR",
  "integrationType": "direct",
  "redirectUrl": "https://merchant.com/redirect",
  "payerDetails": {
    "emailAddress": "siyanda.mthembu@example.com",
    "location": "ZAF",
    "fullName": "Siyanda Mthembu",
    "phoneNumber": "0723456789"
  }
}'
```

### Rendering the buttons

Wrap your checkout in `<CrissCrossWallets>` and drop in the buttons you want to offer. The provider resolves the session's wallet configuration; each button renders **only** if the customer's device and browser can present that wallet and a card is provisioned.

```jsx
import {
  CrissCrossWallets,
  ApplePayButton,
  GooglePayButton,
} from '@crisscross/wallets';

function Checkout({ sessionId }) {
  return (
    <CrissCrossWallets sessionId={sessionId}>
      <ApplePayButton
        onSuccess={({ transactionId }) => confirmOrder(transactionId)}
        onError={(error) => showRetry(error)}
      />
      <GooglePayButton
        onSuccess={({ transactionId }) => confirmOrder(transactionId)}
        onError={(error) => showRetry(error)}
      />
    </CrissCrossWallets>
  );
}
```

A button that cannot be presented renders nothing at all — it does not render disabled. Lay out your checkout so that an absent wallet button leaves no gap, and always offer at least one other payment method.

### Knowing what is available before you render

To branch on availability yourself — to choose a heading, or to reorder your payment options — use the `useWallets` hook:

```jsx
import { useWallets } from '@crisscross/wallets';

function PaymentOptions() {
  const { available, loading } = useWallets();

  if (loading) return <Spinner />;

  return (
    <>
      {available.length > 0 && <h2>Express checkout</h2>}
      {available.includes('apple-pay') && <ApplePayButton onSuccess={...} />}
      {available.includes('google-pay') && <GooglePayButton onSuccess={...} />}
    </>
  );
}
```

`available` combines two things: what the session permits, from [`GET /v1/payment/available-methods`](/payment-method-digital-wallets#retrieving-available-payment-methods), and what the customer's device can actually present. Only wallets satisfying both appear.

### What happens when the customer pays

1. The customer taps the button and the wallet sheet opens on their device.
2. They authorise with Face ID, Touch ID, a passcode, or a screen lock.
3. The device releases an encrypted, single-use payment token, which the SDK submits to CrissCross. For Apple Pay the SDK also completes the merchant validation round trip; you do not implement it.
4. If 3-D Secure is required, the SDK presents the challenge and resolves it before returning. Most wallet payments are exempt, because the device authentication already satisfies strong customer authentication.
5. `onSuccess` fires with the `transactionId`.

A customer who dismisses the wallet sheet without authorising produces no transaction and no callback. Leave your checkout as it was.

### Confirm on the webhook, not on the callback

`onSuccess` tells you the wallet authorised and CrissCross accepted the payment. It is a **display** signal, exactly like the hosted checkout's `?status=completed` redirect. Show a confirmation from it — but fulfil the order from the webhook, which is the authoritative outcome and arrives even if the customer closes the tab. See [Webhook Events](/webhook-events).

Match the webhook to your order on `sessionId` or `merchantReference`.

### Handling errors

`onError` receives a failure that has already been resolved as far as it can be:

| Situation                          | What to do                                                                             |
| ---------------------------------- | -------------------------------------------------------------------------------------- |
| Payment declined                   | Show a retry and keep the other payment methods available. The session is still usable |
| 3-D Secure failed or was abandoned | Same as a decline — the customer can try again                                         |
| Session expired                    | Create a new session and re-render                                                     |
| Wallet unavailable                 | Never surfaced as an error. The button simply does not render                          |

Do not retry automatically. A declined wallet payment usually needs the customer to pick a different card in the wallet sheet.

### Vanilla JavaScript

The same functionality is available without React for other frameworks:

```js
import { createWalletSession } from '@crisscross/wallets';

const wallets = await createWalletSession({ sessionId });

if (wallets.available.includes('apple-pay')) {
  wallets.mount('apple-pay', '#apple-pay-container', {
    onSuccess: ({ transactionId }) => confirmOrder(transactionId),
    onError: (error) => showRetry(error),
  });
}
```

### Additional resources

* **[Wallet Configuration](/wallets-configuration)** — registering the domains this SDK renders on.
* **[Server Integration](/wallets-server-integration)** — submitting the wallet token from your own server instead.
* **[Digital Wallets](/payment-method-digital-wallets)** — supported workflows, refunds and reconciliation.
* **[Webhook Events](/webhook-events)** — the authoritative payment outcome.