Skip to content

Commit aca72cc

Browse files
committed
3DS integration working
1 parent 0d5c87a commit aca72cc

File tree

4 files changed

+100
-64
lines changed

4 files changed

+100
-64
lines changed

README.md

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,42 +17,55 @@
1717
<hr />
1818
</div>
1919

20-
# PayPal JavaScript FullStack Advanced Checkout
21-
This sample app shows how to build and customize a card payment form to accept debit and credit cards. Please make sure to style the card form so that it aligns with your business branding.
20+
# PayPal JavaScript FullStack 3Ds Advanced Checkout
21+
This sample app shows you how to build and customize a card payment form to accept debit and credit cards and using 3Ds for authentification. Style the card form so that it aligns with your business branding.
2222

2323
To create this application from scratch, follow the [Advanced Checkout integration](https://developer.paypal.com/docs/checkout/advanced/integrate) guide from the [PayPal Developer](https://developer.paypal.com/home) docs.
2424

25+
### Run this App
2526

26-
## Run this project
27+
You can run this app locally or on [Stackblitz](https://stackblitz.com/edit/pp-so?file=README.md).
2728

28-
### PayPal Codespaces
29-
[![Open Code In GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/paypaldev/PayPal-JavaScript-FullStack-Advanced-Checkout-Sample?devcontainer_path=.devcontainer%2Fdevcontainer.json)
29+
#### Run it locally
30+
You will need to create a `.env` file with the following environment variables:
3031

31-
- Rename the ``.env.example`` file to `.env`.
32-
- Add your environment variables in the `.env` file.
32+
```shell
33+
CLIENT_ID=
34+
APP_SECRET=
35+
```
3336

34-
### Locally
35-
36-
- Rename the `.env.example` file to `.env`.
37-
- Add your environment variables in the `.env` file.
37+
In the `checkout.ejs` replace the `YOUR_CLIENT_ID` string in the `script` tag with your PayPal Client ID.
3838

3939
Complete the steps in [Get started](https://developer.paypal.com/api/rest/) to get the following sandbox account information from the Developer Dashboard:
4040
- Sandbox client ID and the secret of [a REST app](https://www.paypal.com/signin?returnUri=https%3A%2F%2Fdeveloper.paypal.com%2Fdeveloper%2Fapplications&_ga=1.252581760.841672670.1664266268).
4141
- Access token to use the PayPal REST API server.
4242

4343
![paypal developer credentials](env.png)
4444

45-
Now, run the following commands in your terminal:
45+
Now, run the following command in your terminal:
4646

4747
`npm install`
4848

49-
`npm start` and navigate to [http://localhost:8888/](http://localhost:8888/).
49+
`npm run start`
50+
51+
and navigate in your browser to: `http://localhost:9597/`.
5052

5153
### Sample Card
5254

55+
#### Succesful 3Ds Authentification
56+
Card Type: `Visa`
57+
58+
Card Number: `5458406954745076`
59+
60+
Expiration Date: `01/2025`
61+
62+
CVV: `123`
63+
64+
#### Failure 3Ds Authentification
65+
5366
Card Type: `Visa`
5467

55-
Card Number: `4032039534213337`
68+
Card Number: `4928527426776525`
5669

5770
Expiration Date: `01/2025`
5871

client/app.js

Lines changed: 72 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,29 @@
1+
//Close 3Ds Dialog
2+
function onClose() {
3+
const threedsElement = document.getElementById("threeds");
4+
threedsElement.innerHTML = "";
5+
}
6+
7+
//Handle 3Ds Payload
8+
async function onHandle3Ds(payload, orderId) {
9+
const { liabilityShifted, liabilityShift } = payload;
10+
11+
if (liabilityShift === "POSSIBLE") {
12+
await onApproveCallback(orderId);
13+
} else if (liabilityShifted === false || liabilityShifted === undefined) {
14+
document.getElementById("threeds").innerHTML = `<Dialog open>
15+
<p>You have the option to complete the payment at your own risk,
16+
meaning that the liability of any chargeback has not shifted from
17+
the merchant to the card issuer.</p>
18+
<button onclick=onApproveCallback("${orderId}")>Pay Now</button>
19+
<button onclick=onClose()>Close</button>
20+
</Dialog>
21+
`;
22+
}
23+
}
24+
125
async function createOrderCallback() {
26+
resultMessage("")
227
try {
328
const response = await fetch("/api/orders", {
429
method: "POST",
@@ -35,9 +60,14 @@ async function createOrderCallback() {
3560
}
3661
}
3762

38-
async function onApproveCallback(data, actions) {
63+
async function onApproveCallback(orderId) {
64+
console.log('orderId', orderId);
65+
66+
const threedsElement = document.getElementById("threeds");
67+
threedsElement.innerHTML = "";
68+
3969
try {
40-
const response = await fetch(`/api/orders/${data.orderID}/capture`, {
70+
const response = await fetch(`/api/orders/${orderId}/capture`, {
4171
method: "POST",
4272
headers: {
4373
"Content-Type": "application/json",
@@ -99,7 +129,7 @@ async function createOrderCallback() {
99129
window.paypal
100130
.Buttons({
101131
createOrder: createOrderCallback,
102-
onApprove: onApproveCallback,
132+
onApprove: (data)=> onApproveCallback(data.orderID),
103133
})
104134
.render("#paypal-button-container");
105135

@@ -111,10 +141,14 @@ async function createOrderCallback() {
111141

112142
// If this returns false or the card fields aren't visible, see Step #1.
113143
if (window.paypal.HostedFields.isEligible()) {
144+
let orderId;
114145
// Renders card fields
115146
window.paypal.HostedFields.render({
116147
// Call your server to set up the transaction
117-
createOrder: createOrderCallback,
148+
createOrder: async (data, actions) => {
149+
orderId = await createOrderCallback(data, actions);
150+
return orderId;
151+
},
118152
styles: {
119153
".valid": {
120154
color: "green",
@@ -138,49 +172,37 @@ async function createOrderCallback() {
138172
},
139173
},
140174
}).then((cardFields) => {
141-
document.querySelector("#card-form").addEventListener("submit", (event) => {
142-
event.preventDefault();
143-
cardFields
144-
.submit({
145-
// Cardholder's first and last name
146-
cardholderName: document.getElementById("card-holder-name").value,
147-
// Billing Address
148-
billingAddress: {
149-
// Street address, line 1
150-
streetAddress: document.getElementById(
151-
"card-billing-address-street",
152-
).value,
153-
// Street address, line 2 (Ex: Unit, Apartment, etc.)
154-
extendedAddress: document.getElementById(
155-
"card-billing-address-unit",
156-
).value,
157-
// State
158-
region: document.getElementById("card-billing-address-state").value,
159-
// City
160-
locality: document.getElementById("card-billing-address-city")
161-
.value,
162-
// Postal Code
163-
postalCode: document.getElementById("card-billing-address-zip")
164-
.value,
165-
// Country Code
166-
countryCodeAlpha2: document.getElementById(
167-
"card-billing-address-country",
168-
).value,
169-
},
170-
})
171-
.then((data) => {
172-
return onApproveCallback(data);
173-
})
174-
.catch((orderData) => {
175-
resultMessage(
176-
`Sorry, your transaction could not be processed...<br><br>${JSON.stringify(
177-
orderData,
178-
)}`,
179-
);
180-
});
181-
});
182-
});
183-
} else {
184-
// Hides card fields if the merchant isn't eligible
185-
document.querySelector("#card-form").style = "display: none";
186-
}
175+
document.querySelector("#card-form").addEventListener("submit", async (event) => {
176+
event.preventDefault();
177+
try {
178+
const { value: cardHolderName } = document.getElementById("card-holder-name");
179+
const { value: streetAddress } = document.getElementById("card-billing-address-street");
180+
const { value: extendedAddress } = document.getElementById("card-billing-address-unit");
181+
const { value: region } = document.getElementById("card-billing-address-state");
182+
const { value: locality } = document.getElementById("card-billing-address-city");
183+
const { value: postalCode } = document.getElementById("card-billing-address-zip");
184+
const { value: countryCodeAlpha2 } = document.getElementById("card-billing-address-country");
185+
186+
const payload = await cardFields.submit({
187+
cardHolderName,
188+
contingencies: ["SCA_ALWAYS"],
189+
billingAddress: {
190+
streetAddress,
191+
extendedAddress,
192+
region,
193+
locality,
194+
postalCode,
195+
countryCodeAlpha2,
196+
},
197+
});
198+
199+
await onHandle3Ds(payload, orderId);
200+
} catch (error) {
201+
alert("Payment could not be captured! " + JSON.stringify(error));
202+
}
203+
});
204+
});
205+
} else {
206+
// Hides card fields if the merchant isn't eligible
207+
document.querySelector("#card-form").style = "display: none";
208+
}

env.png

53 KB
Loading

server/views/checkout.ejs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
</form>
100100
<p id="result-message"></p>
101101
</div>
102+
<div id="threeds"></div>
102103
<script src="app.js"></script>
103104
</body>
104105
</html>

0 commit comments

Comments
 (0)