From 276e54daecc4a1529fe790598a5379bd4c526390 Mon Sep 17 00:00:00 2001
From: Steve Domin <steve@stevedomin.com>
Date: Wed, 8 Jan 2025 08:52:11 +0000
Subject: [PATCH] Add support for 3DS Session exception (#365)

Our 3DS Session API supports an `exception` field to specify the
exception used to opt out of authenticating the payment with the card
issuer. This PR adds support for that field when the session is
initialised by the Card Component.
---
 package.json                                        |  2 +-
 src/functions/createThreeDSecureSession/client.ts   |  1 +
 .../createThreeDSecureSession.ts                    |  4 ++++
 .../functions/createThreeDSecureSession.test.ts     | 13 +++++++++++++
 4 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f180be03..cf0c5768 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@duffel/components",
-  "version": "3.7.26",
+  "version": "3.7.27",
   "description": "Component library to build your travel product with Duffel.",
   "keywords": [
     "Duffel",
diff --git a/src/functions/createThreeDSecureSession/client.ts b/src/functions/createThreeDSecureSession/client.ts
index 7df567dd..e1e8bc73 100644
--- a/src/functions/createThreeDSecureSession/client.ts
+++ b/src/functions/createThreeDSecureSession/client.ts
@@ -52,6 +52,7 @@ interface create3DSSessionPayload {
   resource_id: string;
   services?: Array<Service>;
   cardholder_present: boolean;
+  exception: string;
 }
 
 export const createClient = (duffelUrl: string, clientKey: string) => {
diff --git a/src/functions/createThreeDSecureSession/createThreeDSecureSession.ts b/src/functions/createThreeDSecureSession/createThreeDSecureSession.ts
index 6b214b6e..74f8814a 100644
--- a/src/functions/createThreeDSecureSession/createThreeDSecureSession.ts
+++ b/src/functions/createThreeDSecureSession/createThreeDSecureSession.ts
@@ -16,6 +16,7 @@ const DEFAULT_ENVIRONMENT_CONFIGURATION = {
  * @param resourceId - The resource (offer, order, order change) ID that the 3DS session is for.
  * @param services - Include all services that are being added, empty if no services are being added. This is required when services are also being purchased to ensure an accurate total amount to be authorised. If no services, it should be an empty array.
  * @param cardholderPresent - Whether the cardholder was present when the 3DS session was created. If you are collecting card details offline, for example an agent interface for entering card details received from the traveller over the phone, then you must specify the cardholder as not present
+ * @param exception - The name of the exception used to opt out of authenticating the payment with the card issuer
  */
 type CreateThreeDSecureSessionFn = (
   clientKey: string,
@@ -23,6 +24,7 @@ type CreateThreeDSecureSessionFn = (
   resourceId: string,
   services: Array<{ id: string; quantity: number }>,
   cardholderPresent: boolean,
+  exception?: string,
   environmentConfiguration?: Partial<typeof DEFAULT_ENVIRONMENT_CONFIGURATION>,
 ) => Promise<ThreeDSecureSession>;
 
@@ -40,6 +42,7 @@ export const createThreeDSecureSession: CreateThreeDSecureSessionFn = async (
   resourceId,
   services,
   cardholderPresent,
+  exception = "",
   environmentConfiguration = {},
 ) => {
   const env: typeof DEFAULT_ENVIRONMENT_CONFIGURATION = {
@@ -60,6 +63,7 @@ export const createThreeDSecureSession: CreateThreeDSecureSessionFn = async (
         resource_id: resourceId,
         services: services,
         cardholder_present: cardholderPresent,
+        exception: exception,
       })
       .then((threeDSSession) => {
         if (!threeDSSession) {
diff --git a/src/tests/functions/createThreeDSecureSession.test.ts b/src/tests/functions/createThreeDSecureSession.test.ts
index 4e13e3b8..770070d6 100644
--- a/src/tests/functions/createThreeDSecureSession.test.ts
+++ b/src/tests/functions/createThreeDSecureSession.test.ts
@@ -164,6 +164,19 @@ describe("createThreeDSecureSession", () => {
     expect(result.status).toEqual("ready_for_payment");
   });
 
+  it("successfully returns 3DS session without challenge when passing exception", async () => {
+    const result = await createThreeDSecureSession(
+      clientKey,
+      tokenisedCardId,
+      "off_readyforpayment",
+      services,
+      cardholderPresent,
+      "secure_corporate_payment",
+    );
+
+    expect(result.status).toEqual("ready_for_payment");
+  });
+
   it("successfully returns 3DS session with challenge", async () => {
     setTimeout(() => {
       const callback = mockOn.mock.calls.find(