Server Integration

Submit a wallet token to the CrissCross API yourself

Overview

If you want full control of the payment flow — your own retry logic, your own transaction bookkeeping, your own 3-D Secure presentation — you can submit the wallet token to the CrissCross API yourself instead of letting the SDK do it.

You still use the wallets SDK to render the button and obtain the token. Only the wallet running on the customer’s device can produce one, and it cannot be produced server-side. What changes is that the SDK hands the token to you rather than submitting it.

This is the most involved wallet integration and it carries the most ways to get a payment stuck. Unless you need control that React Integration does not give you, use that instead — it makes the same calls described here.

Pre-requisites

  • Apple Pay and Google Pay enabled on your account.
  • Your payment domains registered and verified. See Wallet Configuration.
  • A checkout session created with integrationType: "direct".

The flow

1

Render the button and capture the token

Initialise the SDK in manual mode, which suppresses submission and hands you the token — and, for Apple Pay, makes the merchant validation yours to route:

<CrissCrossWallets sessionId={sessionId} mode="manual">
<ApplePayButton
// Apple Pay only. Return the merchantSession from step 2, unaltered.
onValidateMerchant={({ validationUrl }) =>
fetch('/api/wallet-merchant-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ validationUrl }),
}).then((response) => response.json())
}
onToken={({ provider, token, deviceFingerprint }) =>
postToYourServer({ provider, token, deviceFingerprint })
}
/>
<GooglePayButton
// No onValidateMerchant — Google Pay has no merchant validation step.
onToken={({ provider, token, deviceFingerprint }) =>
postToYourServer({ provider, token, deviceFingerprint })
}
/>
</CrissCrossWallets>

onValidateMerchant must resolve with the merchantSession object exactly as CrissCross returned it — the wallet sheet will not release a token until it does. Step 2 covers the endpoint behind /api/wallet-merchant-session.

token is already base64-encoded and safe to transport as a string. deviceFingerprint is required for fraud screening — pass it through unaltered.

Validate the merchant session (Apple Pay only)

Apple requires a server-side merchant validation before the wallet sheet will release a token. In manual mode this is yours to make.

onValidateMerchant gives you a validationUrl. Forward it to CrissCross from your server — never from the browser, since this call uses your OAuth credentials:

curl -X POST https://api.crisscross.money/v1/payment/wallet/merchant-session \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "01951c8a-7c3d-7e1f-9d4a-2b3c4d5e6f70",
"provider": "apple-pay",
"validationUrl": "https://apple-pay-gateway.apple.com/paymentservices/paymentSession"
}'

Response:

{
"merchantSession": { }
}

Return merchantSession to the browser unaltered, as JSON. Do not re-serialise its fields, reorder them, or log it — Apple validates it as an opaque whole and it is short-lived.

Note what you do not send: no merchant identifier, no domain, no certificate. CrissCross resolves all of it from sessionId. Google Pay has no equivalent step.

Pass validationUrl through exactly as the wallet issued it. CrissCross accepts it only if it is an HTTPS URL on an Apple Pay validation host and rejects anything else with 422, so a rewritten or constructed URL will fail rather than be requested.

Submit the payment

curl -X POST https://api.crisscross.money/v1/payment \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"paymentMethodId": "digitalwallet",
"sessionId": "01951c8a-7c3d-7e1f-9d4a-2b3c4d5e6f70",
"paymentDetails": {
"type": "digitalwallet",
"provider": "apple-pay",
"encryptedWalletToken": "eyJ2ZXJzaW9uIjoiRUNfdjEiLCJkYXRhIjoi...",
"deviceFingerprint": "eyJmcCI6IjhkM2YxZTJhOWM0YjdkNmU...",
"payerEmail": "[email protected]"
}
}'
FieldRequiredNotes
paymentMethodIdyesAlways the literal string "digitalwallet". The wallet goes in paymentDetails.provider, not here
paymentDetails.provideryesapple-pay or google-pay
paymentDetails.encryptedWalletTokenyesThe base64 token from the SDK, passed through verbatim
paymentDetails.deviceFingerprintyesFrom the SDK. Omitting it will cause fraud screening to reject the payment
paymentDetails.payerEmailyesThe payer’s email address

Handle the response

{
"transactionId": "9f3e4b2c-1a6d-4e88-9d3a-ff1234567890",
"status": "SUCCESSFUL",
"message": "Payment authorised"
}

A payment needing 3-D Secure returns PENDING with an authState:

{
"transactionId": "9f3e4b2c-1a6d-4e88-9d3a-ff1234567890",
"status": "PENDING",
"message": "Authorization required",
"authState": {
"type": "redirect",
"redirectUrl": "https://secure.crisscross.money/3ds/9f3e4b2c"
}
}

Send the customer to redirectUrl. They return to the session’s redirectUrl when the challenge resolves. Most wallet payments are exempt from 3-D Secure, because the device authentication already satisfies strong customer authentication — so treat this as a path you must support, not the common case.

Confirm on the webhook

The webhook is the authoritative outcome. Match it on sessionId or merchantReference, verify its signature, and only then fulfil. See Webhook Events.

Things that will bite you

  • A wallet token is single-use and short-lived. You cannot store it, retry with it after a decline, or submit it twice. A retry means a new tap on the wallet button and a new token.
  • The merchant session is also single-use. Fetch a fresh one per wallet sheet; do not cache it.
  • Never log tokens or merchant sessions. They are payment credentials for as long as they are valid.
  • Do not transform the token. It arrives base64-encoded from the SDK and must reach CrissCross exactly as it left the wallet.

Additional resources