Skip to main content

Token Fields (HPF) with 3DS

When you capture the card with Token Fields (HPF), your server never receives the PAN, expiry or CVV. You only hold the paymentToken returned by the SDK. This page explains how to run the 3D Secure v2 flow in that case.

Short version

You use the same standalone 3DS v2 flow as for raw cards. The only difference is that, everywhere the 3DS guide asks for cardNumber / cardExpireMonth / cardExpireYear, you send the paymentToken instead. The gateway resolves the token to the underlying card internally before contacting the 3DS provider, so no card data leaves the iframe.

The flow

  1. InitialisePOST /3ds/v2/initialization with the paymentToken (instead of card fields) plus the browser data.
  2. Handle the response — frictionless (Flow A) or challenge required (Flow B), exactly as documented in Flows.
  3. SalePOST /sale with the same paymentToken plus the resulting 3DS values (eci, cavv, dsTransactionId, version). See Add 3DS fields in Sale API.
// 1. Initialise 3DS using the Token Fields token (no card data)
const init = await initialize({ paymentToken, /* browser + order fields */ });
if (init.responseCode !== '3DS_1000') return handleError(init);

// 2a. Flow A — frictionless: eci + cavv already present
if (init.threeDSProviderResponse.eci && init.threeDSProviderResponse.cavv)
return sale({ paymentToken, ...mapThreeDsVals(init) });

// 2b. Flow B — challenge required: hand control to the provider, then read the result
if (init.threeDSProviderResponse.status === 'ACS_REQUIRED')
redirectToChallengePage(init.threeDSProviderResponse.redirectUrl);

Initialisation request with a token

Send the Initialisation API request as usual, but omit the raw card fields and provide paymentToken:

Example Request (Token Fields)
{
"netvalveMidId": "{{netvalveMidId}}",
"amount": 43.10,
"currency": "USD",
"cardHolderName": "John Doe",
"paymentToken": "e9d74bfb-12d6-422c-a230-fdcc351afc52",
"merchantRedirectUrl": "https://example.com/redirect",
"customerIp": "203.0.113.1",
"customerEmail": "docs@netvalve.com",
"customerPhone": "+12025551234",
"userAgent": "{{USER_AGENT}}",
"browserHeader": "{{BROWSER_HEADER}}",
"browserJavaEnabled": true,
"browserLanguage": "en-US",
"browserColorDepth": 24,
"browserScreenHeight": 864,
"browserScreenWidth": 1536,
"browserTimeZone": 300
}

What changes versus the raw-card request

FieldRaw cardToken Fields
paymentTokennot usedRequired (replaces the card fields)
cardNumberRequiredOmit
cardExpireMonthRequiredOmit
cardExpireYearRequiredOmit
cardHolderNameRequiredStill required
currencyRequiredRequired
amount, netvalveMidId/midId/siteId, merchantRedirectUrl, browser fields, customerEmail/customerPhone (for Visa)as documentedas documented
note

cardHolderName is still validated on the initialisation request (you receive GTW_2021 if it is missing), even when paying with a paymentToken. Collect the cardholder name alongside the Token Fields form so you can supply it here.

caution

The paymentToken must still be valid and unused at the time of the initialisation call. A token is only retired after a successful sale, so the same token is used for both the initialisation and the sale. Do not request a fresh token between the two calls.

Challenge flow (Flow B) and finalisation

If the initialisation response indicates a challenge is required, handle it exactly as in the standard flow described in Flows and ACS Challenge:

  • Initialisation response: responseCode = "3DS_1000", threeDSProviderResponse.status = "ACS_REQUIRED" (equivalently challengeRequired: true), with the challenge location in threeDSProviderResponse.redirectUrl.
  • After the cardholder completes the challenge, they return to the merchantRedirectUrl you supplied, carrying a transID.
  • Call the 3DS Result API (POST /3ds/result with { netvalveMidId, transID }) to retrieve eci, cavv, threeDs2TransactionId and threeDsVersion.
  • Call /sale with the paymentToken and the 3DS values mapped into the request.

See also