> For the complete documentation index, see [llms.txt](https://help.checkoutchamp.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.checkoutchamp.com/crm/plugins/authentication/paay/authenticating-with-paay-3ds-on-landers.md).

# Authenticating with PAAY 3DS on Landers

3D Secure is the behind-the-scenes protocol that verifies it's really you, in real time, during checkout.

{% hint style="info" %}
This article is for a direct API integration with Checkout Champ.  Follow these [instructions](/funnel-builder/creating-and-editing-funnels-pages/funnel-visualizer-and-page-builder/funnel-visualizer/edit-funnel-settings/add-ons/paay-add-on.md) to enable PAAY on Checkout Champ hosted pages.
{% endhint %}

***

### Enable the PAAY Plugin <a href="#enabling-the-paay-plugin" id="enabling-the-paay-plugin"></a>

Follow these steps to enable the [PAAY Plugin](/crm/plugins/authentication/paay.md)

### Checkout Page <a href="#direct-api" id="direct-api"></a>

Call the [https://api.checkoutchamp.com/order/threeds/authenticate/](https://apidocs.checkoutchamp.com/#f97b80fa-8950-4917-ab3b-6fd28e5aafcd) endpoint on the checkout page

<table data-search="false"><thead><tr><th width="193.33331298828125">Field</th><th width="106.3333740234375">Required</th><th>Note</th></tr></thead><tbody><tr><td>firstName</td><td>Y</td><td> </td></tr><tr><td>lastName</td><td>Y</td><td> </td></tr><tr><td>address1</td><td>Y</td><td> </td></tr><tr><td>state</td><td>Y</td><td> </td></tr><tr><td>city</td><td>Y</td><td> </td></tr><tr><td>country</td><td>Y</td><td> </td></tr><tr><td>postalCode</td><td>Y</td><td> </td></tr><tr><td>campaignId</td><td>Y</td><td> </td></tr><tr><td>product1_id</td><td>Y</td><td>campaign product id (also product2_id, product3_id, etc are allowed)</td></tr><tr><td>product1_qty</td><td>N</td><td>campaign product id quantity (also product2_qty, product3_qty, etc are allowed)</td></tr><tr><td>cardNumber</td><td>Y</td><td> </td></tr><tr><td>cardMonth</td><td>Y</td><td> </td></tr><tr><td>cardYear</td><td>Y</td><td> </td></tr><tr><td>salesUrl</td><td>Y</td><td> The full url of the current page</td></tr><tr><td>browserData</td><td>Y</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">const browserData = {
    acceptHeader: 'application/json',
    userAgent: navigator.userAgent,
    language: navigator.language,
    timezone: (new Date()).getTimezoneOffset().toString(),
    colorDepth: screen.colorDepth,
    screen: {
        height: screen.height.toString(),
        width: screen.width.toString()
    },
    javaScriptEnabled: true,
    javaEnabled: navigator.javaEnabled()
};
</code></pre></td></tr><tr><td>paayApiKey</td><td>C</td><td><ol start="1"><li>Required if the PAAY API key was obtained from an external provider. (or)</li><li>Required if you want to use a specific PAAY API key when multiple PAAY plugins are configured</li></ol></td></tr><tr><td>threedsData</td><td>O</td><td>Authentication Configuration (JSON String Format).  See below.</td></tr></tbody></table>

{% hint style="warning" %}
This endpoint enforces fraud plugins and Checkout Champ basic fraud protection prior to calling PAAY.  It may be necessary to pass **ipAddress** or a **sessionId** to properly evaluate fraud.
{% endhint %}

Authentication success...

```json
{
    "result": "SUCCESS",
    "message": {
        "requires3DS": true,
        "script": script to evaluate on page
    }
}
```

{% hint style="info" %}
Evaluating the script will return a token (jwt).  Send this token on the Import Order API call as **threedsAuthToken**.  An example of this is in the code sample below.
{% endhint %}

Authentication failure...

```json
{
    "result": "SUCCESS",
    "message": {
        "requires3DS": false
    }
}
```

Authentication error...

```json
{
    "result": "ERROR",
    "message": {
        "requires3DS": false,
        "action": "error",
        "message": "the error message...",
        "extra": {
           "blockOrder": true/false,
           "validationError": "validation error message..."
        }
    }
}
```

### Upsell Page <a href="#direct-api" id="direct-api"></a>

Call the [https://api.checkoutchamp.com/order/threeds/authenticate/](https://apidocs.checkoutchamp.com/#f97b80fa-8950-4917-ab3b-6fd28e5aafcd) endpoint on each upsell page, to authenticate upsells

<table data-search="false"><thead><tr><th width="229.33331298828125">Field</th><th width="131.3333740234375">Required</th><th>Note</th></tr></thead><tbody><tr><td>orderId</td><td>Y</td><td> The orderId returned by Order Import API call</td></tr><tr><td>product1_id / productId</td><td>Y</td><td>The upsell campaign product id </td></tr><tr><td>salesUrl</td><td>Y</td><td>The full url of the current page </td></tr><tr><td>browserData</td><td>Y</td><td><p></p><pre class="language-javascript"><code class="lang-javascript">const browserData = {
    acceptHeader: 'application/json',
    userAgent: navigator.userAgent,
    language: navigator.language,
    timezone: (new Date()).getTimezoneOffset().toString(),
    colorDepth: screen.colorDepth,
    screen: {
        height: screen.height.toString(),
        width: screen.width.toString()
    },
    javaScriptEnabled: true,
    javaEnabled: navigator.javaEnabled()
};
</code></pre></td></tr><tr><td>paayApiKey</td><td>C</td><td><ol start="1"><li>Required if the PAAY API key was obtained from an external provider. (or)</li><li>Required if you want to use a specific PAAY API key when multiple PAAY plugins are configured</li></ol></td></tr><tr><td>threedsData</td><td>O</td><td>Authentication Configuration (JSON String Format).  See below.</td></tr></tbody></table>

The responses are the same as noted on the Checkout page

### 3DS Data <a href="#direct-api" id="direct-api"></a>

This is a 3DS authentication configuration json string passed as **threedsData** parameter on both checkout and upsells.  The key-value pairs in the json string are detailed in this table.  All fields are optional.

<table data-search="false"><thead><tr><th width="240.66668701171875">Parameter</th><th width="211">Description</th><th>Example</th></tr></thead><tbody><tr><td>minimumAmount</td><td>Minimum transaction amount to trigger 3DS</td><td>100.00</td></tr><tr><td>enabledCardTypes</td><td>Comma-separated list of allowed card types</td><td>VISA, MASTERCARD</td></tr><tr><td>binData</td><td>Comma-separated BINs</td><td>411111,522222</td></tr><tr><td>minimumChallengeAmount</td><td>Minimum amount to trigger challenge flow</td><td>50.00</td></tr><tr><td>challengeWindowSize</td><td>Refer to PAAY API documentation for supported window sizes (<a href="https://docs.3dsintegrator.com/reference/post_authenticate-browser#body-params">challengeWindowSize</a>) </td><td>-</td></tr><tr><td>merchantName</td><td>The merchant name to be used in the 3DS request</td><td>-</td></tr><tr><td>requestorName</td><td>The threeDSRequestor name to be used in the 3DS request</td><td>-</td></tr><tr><td>challengeIndicator</td><td>Refer PAAY API documentation for supported values (<a href="https://docs.3dsintegrator.com/reference/post_authenticate-browser#body-params">challengeIndicator</a>)</td><td>-</td></tr><tr><td>forceTimeOut</td><td>Force the PAAY challenge time-out in a specific duration (seconds)</td><td> </td></tr><tr><td>rejectOrder</td><td>Use this flag to identify validation errors returned by PAAY; if <code>blockOrder</code> is 1 on the response, the order should not be processed.</td><td>1 or 0</td></tr></tbody></table>

### Code Examples <a href="#direct-api" id="direct-api"></a>

Client-side code (sample)

```javascript
//Step 1: Common 3DS SDK script for Checkout and Upsale
<!-- Add 3DS SDK script in head tag of page, ensure integrity and crossorigin attributes are added.  -->
<script
	src="https://resources.checkoutchamp.com/js/sdk/paay-3ds-sdk-1.0.0.min.js"
	integrity="sha384-6f1pKbNNie8uqmu3+5yLJHpiXgh28MbNik3nZ14nWgwOibcw1DtaDAm2wG5iGY40"
	crossorigin="anonymous"
></script>


//Step 2: Common code example to handle 3DS authentication API response for Checkout and Upsale
<!-- Add below script in head or body tag of page needing 3DS authentication -->
<script type="text/javascript">

	// Define 3DS configuration parameters for authentication
	// Paramter can be added from server side
	// For details about each parameter check ThreeDS Data sheet
	function get3DSParameters() {
		const parameters = {
			"minimumAmount": 0,
	        "enabledCardTypes": "VISA,MASTERCARD",
	        "binData": "VISA",
	        "minimumChallengeAmount": 0,
	        "merchantName": "Test",
	        "requestorName": "Test",
	        "challengeIndicator": "02",
	        "forceTimeOut": 600,
	        "rejectOrder": 1,
	        "challengeWindowSize": "05"
		};
		return {
			threedsData: JSON.stringify(parameters)
		};
	}

	// Function to handle 3DS response and return a status object
	function handle3DSResponse(jwt, extra, err) {
		const errorMessage = err?.message || "";
		if (extra?.blockOrder) {
			return {
				blockOrder: true,
				message: errorMessage
			}
		}
		return {
			blockOrder: false,
			cc3DSError: errorMessage,
			threedsAuthToken: jwt 
		}
	}

	// Function to setup 3DS callbacks to be called by SDK
	function set3DSCallback() {
		window.__paayCallbacks = {
	        onComplete: function(jwt, extra) {
	        	const authResponse = handle3DSResponse(jwt, extra);
	        	// If blockOrder is true, block buyer from completing transaction
				if (authResponse.blockOrder) {
					// Display error message (authResponse.message) to buyer
					return;
				}
				
				// Here proceed with completeChekout or completeUpsale call and pass authResponse as argument
	        },
	        onError: function(err, jwt, extra) {
	            const authResponse = handle3DSResponse(jwt, extra, err);
	            // If blockOrder is true, block buyer from completing transaction
				if (authResponse.blockOrder) {
					// Display custom error message (authResponse.message) to buyer
					return;
				}
				
				// proceed with completeChekout or completeUpsale call and pass authResponse as argument
	        }
	    };
	}

	// Function to dispose 3DS callbacks
	function dispose3DSCallback() {
		try {
			delete window.__paayCallbacks;
		} catch(e) {
			window.__paayCallbacks = undefined;
		}
	}

	// Code example to call and handle 3DS authentication
	async function threedsAuthenticate(authDetails) {
		// Status object initialization with blockOrder and message keys
		const status = {
			blockOrder: false,
			message: ""
		};

		try {
			// Check if 3DS SDK object is initialized
			// If not initialized returing true to block buyer from completing transaction
			if (!window.PAAY3DS) {
				status.blockOrder = true;
				// Update below statement with appropriate message
				status.message = "3DS SDK initialization failed";
				return status;
			}

			// Payload object for 3DS auth API, parameters object is not needed if added from server side code
			const parameters = get3DSParameters();
			const payload = {
				...authDetails,
				...parameters
			}

			// Post API call with 3DS auth details invoking "https://api.checkoutchamp.com/order/threeds/authenticate/" API on server
			const response = await fetch("<Base API Url>/threeds/authenticate",
				{
					headers: {
						// Pass API Headers
					},
					method: "POST",
					body: JSON.stringify(payload)
				}
			);

			if (response.result === "SUCCESS") {
				const resp = response.message;
				// Check if 3DS authentication errored
				if (resp?.action?.toLowerCase() === "error" || resp?.result?.toLowerCase() === "error") {
					const errorMessage = resp.message || resp.errorMessage || resp.error || 'Unknown server error';
					return handle3DSResponse(null, resp?.extra, { message: errorMessage });
				}

				// Check determining if 3DS auth is needed or not
				if (!resp.requires3DS || !resp.script) {
					return status;
				}

				// Setting up 3DS callback for SDK to call
				set3DSCallback();

				try {
					status.blockOrder = true;
	                status.message = "";
	                // Evaluating and executing 3DS script returned from authenticate API 
	                eval(resp.script);
	                return status;
	            } catch (err) {
	            	status.blockOrder = true;
	            	// Update below statement with custom message
	                status.message = "";
	                dispose3DSCallback();
	                return status;
	            }
			} else {
				const message = typeof response?.message === "string" ? response?.message : response?.message?.message;
				if (response?.message?.extra?.validationError) {
	                status.blockOrder = true;
					status.message = message;
	                return status;
	            }
	            return handle3DSResponse(response?.message?.jwt, response?.message?.extra, { message });
			}
		} catch (error) {
			status.blockOrder = true;
	        // Update below statement with custom message or use error.message
	        status.message = "";
	        dispose3DSCallback();
	        return status;
		}
	}
</script>


//Step 3: Snippet for Checkout pages
<!-- Code example to complete Checkout -->
<script type="text/javascript">
	async function completeChekout(authResponse) {
		try {
			// Add code logic to collect Checkout email, phone, billing, shipping, card (card number, CVV, card month, card year), product and other necessary details. Update below orderDetails object with details.
			// ...
			const orderDetails = {};

			// Add 3DS JWT and Error message to API call payload if exists
			if (authResponse.threedsAuthToken) {
				orderDetails.threedsAuthToken = authResponse.threedsAuthToken;
			}
			if (authResponse.cc3DSError) {
				orderDetails.cc3DSError = authResponse.cc3DSError;
			}

			// Post API call with order details invoking "https://api.checkoutchamp.com/order/import/" API on server 
			const response = await fetch("<Base API URL>/complete/checkout",
				{
					headers: {
						// Pass CheckoutChamp API headers
					},
					method: "POST",
					body: JSON.stringify(orderDetails)
				}
			);
			const responseData = await response.json();
			if (responseData.result === "SUCCESS") {
				// Route buyer to next upsell or thank you page
				// If required to authroize card for upsell save or collection order id at this setup
				// const orderId = responseData.message.orderId;
			} else if (responseData.result === "MERC_REDIRECT") {
				// Handle merchant redirect
			} else {
				const errorMessage = result.message;
				// Show error on page and allow user to retry
			}
		} catch (error) {
			// Show error on page and allow user to retry
		}
	}

	// Code example of function gets called on submit button click for credit card checkout
	async function submitCardCheckoutButtonClick() {
		// Add logic below to collect buyer email, phone, billing address, shipping address, product and card (card number, card month, card year) details 
		// ...
		const authDetails = {}

		// Check if card number, card month or card year is empty. if yes block transaction and show error message to buyer
		if (!authDetails.cardNumber || !authDetails.cardMonth || !authDetails.cardYear) {
			// Display custom error message to buyer
			return;
		}

		// Calling threedsAuthenticate function
		const authResponse = await threedsAuthenticate(authDetails);

		// If authResponse.blockOrder is true, block buyer from completing transaction
		if (authResponse.blockOrder) {
			// Display custom error message (authResponse.message) to buyer and allow user to retry
			return;
		}
		
		// Proceed with Checkout
		await completeChekout(authResponse);
	}
</script>


//Step 4: Snippet for Upsale pages
<!-- Code snippet example to complete Upsale using /upsale/import -->
<script type="text/javascript">
	async function completeUpsale(authResponse) {
		try {
			// Add logic to collect Upsell product details and other necessary details. Update below orderDetails object with details.
			// ...
			const orderDetails = {};

			// Add 3DS JWT and Error message to API call payload if exists
			if (authResponse.threedsAuthToken) {
				orderDetails.threedsAuthToken = authResponse.threedsAuthToken;
			}
			if (authResponse.cc3DSError) {
				orderDetails.cc3DSError = authResponse.cc3DSError;
			}

			// Post API call with order details invoking "https://api.checkoutchamp.com/upsale/import/" API on server 
			const response = await fetch("<Base API URL>/complete/upsale",
				{
					headers: {
						// Pass CheckoutChamp API headers
					},
					method: "POST",
					body: JSON.stringify(authDetails)
				}
			);
			const responseData = await response.json();
			if (responseData.result === "SUCCESS") {
				// Route buyer to next upsell or thank you page
				// If required to authorize card for upsell save or collection order id at this setup
				// const orderId = responseData.message.orderId;
			} else if (responseData.result === "MERC_REDIRECT") {
				// Handle merchant redirect
			} else {
				const errorMessage = result.message;
				// Show error on page and allow user to retry
			}
		} catch (error) {
			// Show error on page and allow user to retry
		}
	}
</script>

<!-- Code snippet example to call and handle submit for upsale  -->
<script type="text/javascript">
	async function submitUpsaleOnButtonClick() {
		// Add logic to collect product details and other necessary details. Update below authDetails object with details.
		// Note 3DS auth on upsell does not require passing buyer email, phone, address, card number, card month and card year details. Inplace of card details pass orderId on threedsAuthenticate request.
		// ...
		const authDetails = {}

		if (!authDetails.orderId) {
			// Display custom error message to buyer and allow user to retry
			return;
		}
		
		const authResponse = await threedsAuthenticate(authDetails);

		// If authResponse.blockOrder is true, block buyer from completing transaction
		if (authResponse.blockOrder) {
			// Display custom error message (authResponse.message) to buyer and allow user to retry
			return;
		}
		
		// Proceed with Upsale
		await completeUpsale(authResponse);
	}
</script>

```

Server-side code (sample)

```javascript
//Step 5: Server Side Code
<!-- Node Express service example -->
const express = require('express');
const axios = require('axios');
const app = express();
const PORT = 3000;

// Middleware to parse JSON request bodies
app.use(express.json());

// Example of API service calling CheckoutChamp 3DS Auth API
// Ensure "/threeds/authenticate" API always responds in below format
// {
//		result: "SUCCESS", // or "ERROR"
//		message: "<Error Message>" // or 3DS object returned from API
// }
app.post("/threeds/authenticate", (req, res) => {
	try {
		const payload = req.body;
		// Parse and validate rquest body
		// Add necessary guard or check before calling 3DS auth API

		if (!payload.threedsData) {
			return res.status(400).json({ 
	            result: "Error", 
	            message: "Invalid request received" 
	        });
		}

		// For Checkout use below check
		if (!payload.cardNumber || !payload.cardYear || !payload.cardMonth) {
			return res.status(400).json({ 
	            result: "Error", 
	            message: "Missing card details" 
	        });
		}

		// For Upsell use below check
		if (!payload.orderId) {
			return res.status(400).json({ 
	            result: "Error", 
	            message: "OrderId is required"
	        });
		}

		// Add PAAY 3DS API key to payload
		payload["paayApiKey"] = "";

		const authAPIBaseUrl = new URL("https://api.checkoutchamp.com/order/threeds/authenticate");

        // Append each key-value pair from the request object
        Object.entries(payload).forEach(([key, value]) => {
            authAPIBaseUrl.searchParams.append(key, value);
        });

        // Converting URL object to URL string with query params
        // ALso any other required URL params to below API url
        const updatedAuthAPIUrl = authAPIBaseUrl.toString();

        // Also add other necessary URL params, auth token or (username or password) 
		const response = await axios.get(updatedAuthAPIUrl);

	    res.status(200).json({
	        result: response.result,
	        message: response.message
	    });

	} catch (error) {
		const statusCode = error.response ? error.response.status : 500;
        res.status(statusCode).json({ 
            result: "ERROR",
            message: error.message 
        });
	}
});

// Start the server
app.listen(PORT, () => {
    console.log("Server Started");
});
```

&#x20;

***

### Testing <a href="#testing" id="testing"></a>

If the 3DS parameters are passed in properly on the Import call and the transaction goes to a gateway that accepts 3rd Party 3DS you will see a tag that says \*3DSecure underneath the result of the transaction.

&#x20;

<figure><img src="https://3790748257-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FT43PzcNjyZtWby9yrGd3%2Fuploads%2FlhL7uOqKSiKOfSnKXmD9%2Fimage.png?alt=media&amp;token=fec22bf5-e9b1-4646-b6ec-216ddb411eb8" alt=""><figcaption></figcaption></figure>

***

### Suggested PAAY Practices <a href="#suggested-paay-practices" id="suggested-paay-practices"></a>

**Rebills**

The recommendation is to use [PAAY 3RI](/crm/plugins/authentication/paay-3ri.md) for rebill authentication

&#x20;

**Trial Authentications**

For trials, we recommend following these practices:

* **Hold Trial Charge** - Authenticate the cycle 2 transaction amount and send as the initial authentication amount. Do not attempt a PAAY rebill authentication.
* **Full Authorize & Void** and **Validate Card** Trials or any trial with either no cycle 1 price or only shipping for cycle 1 - Run the PAAY authentication using the cycle 2 transaction amount and then send to Checkout Champ as rebill 3DS parameters.
