Complete Checkout with 3DS Challenge - React

JavaScript
1import React, { useCallback, useEffect, useState } from 'react';
2import axios from 'axios';
3
4type ChallengeData = {
5 transactionId: string;
6 url: string;
7 creq: string;
8} | null;
9
10// gets details about user's session to pass onto 3ds network
11function get3DSParams() {
12 return {
13 colorDepth: window.screen.colorDepth,
14 screenHeight: window.screen.height,
15 screenWidth: window.screen.width,
16 timeZone: -new Date().getTimezoneOffset(),
17 };
18}
19
20// displays 3ds challenge modal
21function ChallengeModal({
22 url,
23 creq,
24 transactionId,
25 onChallengeComplete
26}){
27 const handleMessage = useCallback(
28 (event: MessageEvent<string>) => {
29 if (event.data !== 'challenge_success') return;
30 onChallengeComplete(transactionId);
31 },
32 [transactionId, onChallengeComplete]
33 );
34
35 useEffect(() => {
36 window.addEventListener('message', handleMessage);
37 return () => window.removeEventListener('message', handleMessage);
38 }, [handleMessage]);
39
40 return (
41 <div style={{ height: '100vh', margin: 0 }}>
42 <iframe
43 style={{ width: '100%',
44 maxWidth: '100%',
45 minWidth: '391px',
46 height: '100vh',
47 border: 'none'}}
48 srcDoc={`<html><body onload="document.challenge.submit()">
49 <form method="post" name="challenge" action="${url}">
50 <input type="hidden" name="creq" value="${creq}" />
51 </form>
52 </body></html>`}`
53 />
54 </div>
55 );
56
57}
58
59// This initializes the 3DS challenge
60async function initiateCheckout() {
61 const data = {
62 subtotal: { cents: 198 }, // Subtotal ending in 98 cents will force a 3ds challenge in sandbox
63 authentication3DS: get3DSParams(),
64 card: {
65 cardToken: 'YOUR_CARD_TOKEN', // Use any valid tokenized card
66 expYear: '29',
67 expMonth: '10',
68 email: 'dwaynejohnson@therock.com',
69 firstName: 'Dwayne',
70 lastName: 'Johnson',
71 address1: '201 E Randolph St',
72 city: 'Chicago',
73 zip: '60601',
74 state: 'IL',
75 country: 'US',
76 },
77 saveCard: true,
78 };
79
80 try {
81 const response = await axios.post(
82 'https://api-sandbox.coinflow.cash/api/checkout/card/YOUR_MERCHANT_ID',
83 data,
84 {
85 headers: {
86 accept: 'application/json',
87 'content-type': 'application/json',
88 'x-coinflow-auth-session-key': 'PAYER_SESSION_KEY',
89 },
90 }
91 );
92
93 return { response };
94 } catch (error) {
95 if (error.response && error.response.status === 412) {
96 const { transactionId, creq, url } = error.response.data;
97 return { error: { transactionId, creq, url } };
98 }
99 throw error;
100 }
101}
102
103// After challenge is completed, user can proceed with card checkout
104async function completeCheckout(transactionId: string) {
105 const data = {
106 subtotal: { cents: 198 }, // Must match the subtotal from the initial request
107 authentication3DS: { transactionId },
108 card: {
109 cardToken: 'YOUR_CARD_TOKEN', // Use any valid tokenized card
110 expYear: '29',
111 expMonth: '10',
112 email: 'dwaynejohnson@therock.com',
113 firstName: 'Dwayne',
114 lastName: 'Johnson',
115 address1: '201 E Randolph St',
116 city: 'Chicago',
117 zip: '60601',
118 state: 'IL',
119 country: 'US',
120 },
121 saveCard: true,
122 };
123
124 try {
125 const response = await axios.post(
126 'https://api-sandbox.coinflow.cash/api/checkout/card/YOUR_MERCHANT_ID',
127 data,
128 {
129 headers: {
130 accept: 'application/json',
131 'content-type': 'application/json',
132 'x-coinflow-auth-session-key': 'PAYER_SESSION_KEY',
133 },
134 }
135 );
136 console.log('Payment id:', response.data.paymentId);
137 } catch (error) {
138 console.error('Error:', error);
139 }
140}
141
142function App() {
143 const [challengeData, setChallengeData] = useState<ChallengeData>(null);
144
145 // Initiates a checkout w/ 3DS challenge
146 useEffect(() => {
147 const handle3DSChallenge = async () => {
148 const result = await initiateCheckout();
149 if (result.error) {
150 setChallengeData(result.error);
151 }
152 };
153
154 handle3DSChallenge();
155 }, []);
156
157 const handleChallengeComplete = (transactionId: string) => {
158 completeCheckout(transactionId);
159 setChallengeData(null); // This resets the challenge data after its been completed
160 };
161
162 return (
163 <div>
164 {challengeData ? (
165 <ChallengeModal
166 url={challengeData.url}
167 creq={challengeData.creq}
168 transactionId={challengeData.transactionId}
169 onChallengeComplete={handleChallengeComplete}
170 />
171 ) : null}
172 </div>
173 );
174}
175
176export default App;
Response Example
1{"success":true}

Get User Session Details

3DS requires you to pass details about the user’s session, which will be used to analyze likelihood of fraud.

Create a function that gets the required session data.

Initiate Card Checkout using 3DS Params

Make a POST request to the card checkout endpoint and pass the 3ds session parameters.

If the end-user needs to go through a frictioned challenge, this will return a 412, and you will need to retrieve transactionId, creq, and url from the response.

For saved card checkout, change request url to: https://api-sandbox.coinflow.cash/api/checkout/token/YOUR_MERCHANT_ID

If challenge is frictionless, this request will return a paymentId and displaying a challenge is not required

Display the 3DS Challenge Modal

Display a challenge modal to the end-user and pass url creq transactionId and a callback function to handle when the challenge gets completed.

While testing, you may use any random key to complete the challenge.

Make a Subsequent Request to Card Checkout

After the challenge is complete, send a subsequent POST request to Card Checkout, and pass in the transactionId. Challenges that are successful will return a paymentId.

For saved card checkout, change request url to: https://api-sandbox.coinflow.cash/api/checkout/token/YOUR_MERCHANT_ID

Complete the 3DS Challenge and Finalize Checkout

Now it’s time to put everything together!

Initiate a card checkout and watch for any 3DS challenges. If a challenge is required, display it in a modal, and when the challenge is completed, finalize the checkout by making a follow-up call using the transaction ID associated with the challenge.