From 9ff22e7dc4991125851ec7eb76271fa87ddacbcb Mon Sep 17 00:00:00 2001 From: Alan Kennedy Date: Tue, 12 Nov 2024 12:22:12 +0000 Subject: [PATCH] feat: add 3DS session support and update to cards endpoint Includes card changes to use `multi_use` and new endpoint with /payments prefix. Add support for Create a 3DS session. BREAKING CHANGE: requireda`multi_use` field when creating a card --- src/{Vault => Payments}/Cards/Cards.spec.ts | 14 +++- src/{Vault => Payments}/Cards/Cards.ts | 10 ++- src/{Vault => Payments}/Cards/index.ts | 0 .../ThreeDSecureSessions.spec.ts | 34 ++++++++++ .../ThreeDSecureSessions.ts | 65 +++++++++++++++++++ src/Payments/ThreeDSecureSessions/index.ts | 1 + src/Payments/index.ts | 2 + src/Vault/index.ts | 1 - src/index.ts | 6 +- 9 files changed, 128 insertions(+), 5 deletions(-) rename src/{Vault => Payments}/Cards/Cards.spec.ts (72%) rename src/{Vault => Payments}/Cards/Cards.ts (87%) rename src/{Vault => Payments}/Cards/index.ts (100%) create mode 100644 src/Payments/ThreeDSecureSessions/ThreeDSecureSessions.spec.ts create mode 100644 src/Payments/ThreeDSecureSessions/ThreeDSecureSessions.ts create mode 100644 src/Payments/ThreeDSecureSessions/index.ts create mode 100644 src/Payments/index.ts delete mode 100644 src/Vault/index.ts diff --git a/src/Vault/Cards/Cards.spec.ts b/src/Payments/Cards/Cards.spec.ts similarity index 72% rename from src/Vault/Cards/Cards.spec.ts rename to src/Payments/Cards/Cards.spec.ts index e5318801..ccd0cb55 100644 --- a/src/Vault/Cards/Cards.spec.ts +++ b/src/Payments/Cards/Cards.spec.ts @@ -10,8 +10,17 @@ describe('Cards', () => { it('should create a card record when `create` is called', async () => { const MOCK_ID = 'tcd_00009hthhsUZ8W4LxQgkjb' nock(/(.*)/) - .post('/vault/cards') - .reply(200, { data: { id: MOCK_ID, liveMode: false } }) + .post('/payments/cards') + .reply(200, { + data: { + id: MOCK_ID, + liveMode: false, + unavailableAt: '2024-01-01T00:00:00', + brand: 'visa', + multiUse: false, + last4Digits: '4242', + }, + }) const response = await duffel.cards.create({ address_city: 'London', @@ -25,6 +34,7 @@ describe('Cards', () => { name: 'Neil Armstrong', number: '4242424242424242', cvc: '123', + multi_use: false, }) expect(response.data.id).toBe(MOCK_ID) }) diff --git a/src/Vault/Cards/Cards.ts b/src/Payments/Cards/Cards.ts similarity index 87% rename from src/Vault/Cards/Cards.ts rename to src/Payments/Cards/Cards.ts index 07f02b5a..5c231a8a 100644 --- a/src/Vault/Cards/Cards.ts +++ b/src/Payments/Cards/Cards.ts @@ -56,11 +56,19 @@ interface CardParameters { * The card verification code */ cvc: string + /** + * Set to true to indicate that this card can be used multiple times + */ + multi_use: boolean } interface CardRecord { id: string live_mode: boolean + multi_use: boolean + unavailble_at: string + brand: string + last_4_digits: string } export class Cards extends Resource { @@ -72,7 +80,7 @@ export class Cards extends Resource { // basePath must be 'https://api.duffel.cards' constructor(client: Client) { super(client) - this.path = 'vault/cards' + this.path = 'payments/cards' } /** diff --git a/src/Vault/Cards/index.ts b/src/Payments/Cards/index.ts similarity index 100% rename from src/Vault/Cards/index.ts rename to src/Payments/Cards/index.ts diff --git a/src/Payments/ThreeDSecureSessions/ThreeDSecureSessions.spec.ts b/src/Payments/ThreeDSecureSessions/ThreeDSecureSessions.spec.ts new file mode 100644 index 00000000..a0eb377c --- /dev/null +++ b/src/Payments/ThreeDSecureSessions/ThreeDSecureSessions.spec.ts @@ -0,0 +1,34 @@ +import nock from 'nock' +import { Duffel } from '../../index' + +const duffel = new Duffel({ token: 'mockToken' }) +describe('ThreeDSecureSessions', () => { + afterEach(() => { + nock.cleanAll() + }) + + it('should create a 3DS session record when `create` is called', async () => { + const MOCK_ID = '3ds_00009hthhsUZ8W4LxQgkjb' + nock(/(.*)/) + .post('/payments/three_d_secure_sessions') + .reply(200, { + data: { + id: MOCK_ID, + liveMode: false, + expiresAt: '2024-01-01T00:00:00', + status: 'ready_for_payment', + resourceId: 'off_00009hthhsUZ8W4LxQgkjb', + clientId: 'tds_57aa862f8bf7', + cardId: 'tcd_00009hthhsUZ8W4LxQgkjb', + }, + }) + + const response = await duffel.three_d_secure_sessions.create({ + resource_id: 'off_00009hthhsUZ8W4LxQgkjb', + card_id: 'tcd_00009hthhsUZ8W4LxQgkjb', + services: [{ quantity: 1, id: 'ser_00009UhD4ongolulWd9123' }], + exception: 'secure_corporate_payment', + }) + expect(response.data.id).toBe(MOCK_ID) + }) +}) diff --git a/src/Payments/ThreeDSecureSessions/ThreeDSecureSessions.ts b/src/Payments/ThreeDSecureSessions/ThreeDSecureSessions.ts new file mode 100644 index 00000000..bd2530a3 --- /dev/null +++ b/src/Payments/ThreeDSecureSessions/ThreeDSecureSessions.ts @@ -0,0 +1,65 @@ +import { Resource } from '../../Resource' +import { DuffelResponse } from '../../types' + +interface Service { + /** + * The quantity of the service ID to pay for + */ + quantity: number + /** + * The ID of the service to pay for + */ + id: string +} + +interface ThreeDSecureSessionParameters { + /** + * The offer ID, order ID, order change ID or quote ID intended to pay + */ + resource_id: string + + /** + * The services inteded to pay + */ + services: Service[] + + /** + * The card ID + */ + card_id: string + + /** + * The exception name for the 3DS session + */ + exception: string +} + +interface ThreeDSecureSessionRecord { + id: string + resource_id: string + card_id: string + live_mode: boolean + expires_at: string + status: string + client_id: string +} + +export class ThreeDSecureSessions extends Resource { + /** + * Endpoint path + */ + path: string + + constructor(args: any) { + super(args) + this.path = 'payments/three_d_secure_sessions' + } + + /** + * Create a Duffel ThreeDSecureSession record + */ + public create = async ( + data: ThreeDSecureSessionParameters, + ): Promise> => + this.request({ method: 'POST', path: this.path, data }) +} diff --git a/src/Payments/ThreeDSecureSessions/index.ts b/src/Payments/ThreeDSecureSessions/index.ts new file mode 100644 index 00000000..28ba9018 --- /dev/null +++ b/src/Payments/ThreeDSecureSessions/index.ts @@ -0,0 +1 @@ +export * from './ThreeDSecureSessions' diff --git a/src/Payments/index.ts b/src/Payments/index.ts new file mode 100644 index 00000000..59c836e0 --- /dev/null +++ b/src/Payments/index.ts @@ -0,0 +1,2 @@ +export * from './Cards' +export * from './ThreeDSecureSessions' diff --git a/src/Vault/index.ts b/src/Vault/index.ts deleted file mode 100644 index 90016471..00000000 --- a/src/Vault/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Cards' diff --git a/src/index.ts b/src/index.ts index 85859804..41bbab31 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,8 @@ import { Refunds } from './DuffelPayments/Refunds' import { Sessions } from './Links' import { Webhooks } from './notifications' import { Stays } from './Stays/Stays' -import { Cards } from './Vault/Cards' +import { Cards } from './Payments/Cards' +import { ThreeDSecureSessions } from './Payments/ThreeDSecureSessions' export interface DuffelAPIClient { aircraft: Aircraft @@ -34,6 +35,7 @@ export interface DuffelAPIClient { orderCancellations: OrderCancellations payments: Payments seatMaps: SeatMaps + threeDSecureSessions: ThreeDSecureSessions } export class Duffel { @@ -58,6 +60,7 @@ export class Duffel { public refunds: Refunds public webhooks: Webhooks public stays: Stays + public three_d_secure_sessions: ThreeDSecureSessions private cardsClient: Client public cards: Cards @@ -85,6 +88,7 @@ export class Duffel { this.refunds = new Refunds(this.client) this.webhooks = new Webhooks(this.client) this.stays = new Stays(this.client) + this.three_d_secure_sessions = new ThreeDSecureSessions(this.client) this.cardsClient = new Client({ ...config,