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: 100 },
63 authentication3DS: get3DSParams(),
64 card: {
65 cardToken: '230377LB2YJJ0408', //This is test card will force a 3ds challenge
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-blockchain': 'ENTER_BLOCKCHAIN',
89 'x-coinflow-auth-wallet': 'ENTER_USER_WALLET_ADDRES',
90 },
91 }
92 );
93
94 return { response };
95 } catch (error) {
96 if (error.response && error.response.status === 412) {
97 const { transactionId, creq, url } = error.response.data;
98 return { error: { transactionId, creq, url } };
99 }
100 throw error;
101 }
102}
103
104// After challenge is completed, user can proceed with card checkout
105async function completeCheckout(transactionId: string) {
106 const data = {
107 subtotal: { cents: 100 },
108 authentication3DS: { transactionId },
109 card: {
110 cardToken: '230377LB2YJJ0408', //This is test card will force a 3ds challenge
111 expYear: '29',
112 expMonth: '10',
113 email: 'dwaynejohnson@therock.com',
114 firstName: 'Dwayne',
115 lastName: 'Johnson',
116 address1: '201 E Randolph St',
117 city: 'Chicago',
118 zip: '60601',
119 state: 'IL',
120 country: 'US',
121 },
122 saveCard: true,
123 };
124
125 try {
126 const response = await axios.post(
127 'https://api-sandbox.coinflow.cash/api/checkout/card/YOUR_MERCHANT_ID',
128 data,
129 {
130 headers: {
131 accept: 'application/json',
132 'content-type': 'application/json',
133 'x-coinflow-auth-blockchain': 'ENTER_BLOCKCHAIN',
134 'x-coinflow-auth-wallet': 'ENTER_USER_WALLET_ADDRESS',
135 },
136 }
137 );
138 console.log('Payment id:', response.data.paymentId);
139 } catch (error) {
140 console.error('Error:', error);
141 }
142}
143
144function App() {
145 const [challengeData, setChallengeData] = useState<ChallengeData>(null);
146
147 // Initiates a checkout w/ 3DS challenge
148 useEffect(() => {
149 const handle3DSChallenge = async () => {
150 const result = await initiateCheckout();
151 if (result.error) {
152 setChallengeData(result.error);
153 }
154 };
155
156 handle3DSChallenge();
157 }, []);
158
159 const handleChallengeComplete = (transactionId: string) => {
160 completeCheckout(transactionId);
161 setChallengeData(null); // This resets the challenge data after its been completed
162 };
163
164 return (
165 <div>
166 {challengeData ? (
167 <ChallengeModal
168 url={challengeData.url}
169 creq={challengeData.creq}
170 transactionId={challengeData.transactionId}
171 onChallengeComplete={handleChallengeComplete}
172 />
173 ) : null}
174 </div>
175 );
176}
177
178export 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.