From 3f76792d59783b77a6a6df19e5ea0f80ba09937a Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 14 Apr 2025 12:45:10 +0200 Subject: [PATCH] feat: add token intents --- src/modules/tokenIntents.ts | 62 ++++++++++++++++ src/useBasisTheory.ts | 6 +- tests/modules/tokenIntents.test.ts | 115 +++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/modules/tokenIntents.ts create mode 100644 tests/modules/tokenIntents.test.ts diff --git a/src/modules/tokenIntents.ts b/src/modules/tokenIntents.ts new file mode 100644 index 0000000..f38249e --- /dev/null +++ b/src/modules/tokenIntents.ts @@ -0,0 +1,62 @@ +import type { + CreateTokenIntent, + TokenIntent, +} from '@basis-theory/basis-theory-js/types/models'; +import type { + BasisTheory as BasisTheoryType, + RequestOptions, +} from '@basis-theory/basis-theory-js/types/sdk'; +import type { + BTRef, + InputBTRefWithDatepart, + PrimitiveType, +} from '../BaseElementTypes'; +import { _elementErrors } from '../ElementValues'; +import { replaceElementRefs } from '../utils/dataManipulationUtils'; +import { isNilOrEmpty } from '../utils/shared'; + +export type CreateTokenIntentWithBtRef = Omit & { + data: Record; +}; + +export type TokenIntentData = TokenIntent< + BTRef | InputBTRefWithDatepart | PrimitiveType +>; + +export const TokenIntents = (bt: BasisTheoryType) => { + const create = async ( + tokenIntentWithRef: CreateTokenIntentWithBtRef, + requestOptions?: RequestOptions + ) => { + if (!isNilOrEmpty(_elementErrors)) { + throw new Error( + 'Unable to create token. Payload contains invalid values. Review elements events for more details.' + ); + } + + try { + const _token = replaceElementRefs(tokenIntentWithRef); + + const token = await bt.tokenIntents.create(_token, requestOptions); + + return token; + } catch (error) { + console.error(error); + } + }; + + const deleteTokenIntent = async (id: string) => { + try { + if (id) { + await bt.tokenIntents.delete(id); + } + } catch (error) { + console.error(error); + } + }; + + return { + create, + delete: deleteTokenIntent, + }; +}; diff --git a/src/useBasisTheory.ts b/src/useBasisTheory.ts index 52ec07c..8cf94d9 100644 --- a/src/useBasisTheory.ts +++ b/src/useBasisTheory.ts @@ -9,6 +9,7 @@ import { Proxy } from './modules/proxy'; import { Sessions } from './modules/sessions'; import { Tokens } from './modules/tokens'; import { useBasisTheoryFromContext } from './BasisTheoryProvider'; +import { TokenIntents } from './modules/tokenIntents'; const _BasisTheoryElements = async ({ apiKey, @@ -25,10 +26,13 @@ const _BasisTheoryElements = async ({ const tokens = Tokens(bt); + const tokenIntents = TokenIntents(bt); + return { + proxy, sessions, + tokenIntents, tokens, - proxy, }; }; diff --git a/tests/modules/tokenIntents.test.ts b/tests/modules/tokenIntents.test.ts new file mode 100644 index 0000000..20673e3 --- /dev/null +++ b/tests/modules/tokenIntents.test.ts @@ -0,0 +1,115 @@ +import { _elementErrors, _elementValues } from '../../src/ElementValues'; +import { + CreateTokenIntentWithBtRef, + TokenIntents, + TokenizeData, +} from '../../src/modules/tokenIntents'; +import type { BasisTheory as BasisTheoryType } from '@basis-theory/basis-theory-js/types/sdk'; + +jest.mock('../../src/ElementValues', () => ({ + _elementValues: {}, + _elementErrors: {}, +})); + +describe('tokens', () => { + beforeEach(() => { + Object.assign(_elementValues, { + '123': 'my very sensitive value', + '456': 'my other very sensitive value', + firstArrayElement: 'first sensitive element in array', + secondArrayElement: 'second sensitive element in array', + expirationDate: '12/23', + }); + + Object.assign(_elementErrors, {}); + }); + afterAll(() => { + Object.keys(_elementValues).forEach((key) => delete _elementValues[key]); + Object.keys(_elementErrors).forEach((key) => delete _elementErrors[key]); + }); + + test('calls bt tokens create', async () => { + const mockCreate = jest.fn(); + const tokenIntents = TokenIntents({ + tokenIntents: { create: mockCreate }, + } as unknown as BasisTheoryType); + + const tokenWithRef = { + id: 'tokenID', + type: 'card', + data: { + number: { id: '123', format: jest.fn() }, + nestedObject: { + test: { id: '456', format: jest.fn() }, + }, + myArray: [ + { id: 'firstArrayElement', format: jest.fn() }, + { id: 'secondArrayElement', format: jest.fn() }, + ], + }, + } as unknown as CreateTokenIntentWithBtRef; + + const expectedResult = { + id: 'tokenID', + type: 'card', + data: { + number: 'my very sensitive value', + nestedObject: { + test: 'my other very sensitive value', + }, + myArray: [ + 'first sensitive element in array', + 'second sensitive element in array', + ], + }, + }; + + await tokenIntents.create(tokenWithRef); + + expect(mockCreate).toHaveBeenCalledWith(expectedResult, undefined); + }); + + test('calls bt tokens delete', async () => { + const mockDelete = jest.fn(); + const tokenIntents = TokenIntents({ + tokenIntents: { delete: mockDelete }, + } as unknown as BasisTheoryType); + + await tokenIntents.delete('tokenID'); + + expect(mockDelete).toHaveBeenCalledWith('tokenID'); + }); +}); + +describe('tokens - Validation', () => { + test('throws if there are any validation errors', () => { + Object.assign(_elementErrors, { + secondArrayElement: 'incomplete', + }); + + const tokenIntents = TokenIntents({} as BasisTheoryType); + + const tokenWithRef = { + id: 'tokenID', + type: 'card', + data: { + number: { id: '123', format: jest.fn() }, + nestedObject: { + test: { id: '456', format: jest.fn }, + }, + myArray: [ + { id: 'firstArrayElement', format: jest.fn() }, + { id: 'secondArrayElement', format: jest.fn() }, + ], + }, + } as unknown as CreateTokenIntentWithBtRef; + + const action = async () => { + await tokenIntents.create(tokenWithRef); + }; + + expect(() => action()).rejects.toThrow( + 'Unable to create token. Payload contains invalid values. Review elements events for more details.' + ); + }); +});