import type { CreateCardForTemporaryUsePayload, DuffelCardFormActions, DuffelCardFormProps, SaveCardPayload, UseSavedCardPayload, } from '../../index'; import React from 'react'; import { CreateCardForTemporaryUseForm } from './forms/CreateCardForTemporaryUseForm'; import { SaveCardForm } from './forms/SaveCardForm'; import { UseSavedCardForm } from './forms/UseSavedCardForm'; import { DEFAULT_STRINGS } from './strings'; import { getDuffelCardsAPIClient } from './utils/getDuffelCardsAPIClient'; import type { VirtualFields } from './utils/validateFieldFn'; export const DuffelCardForm = React.forwardRef< DuffelCardFormActions, DuffelCardFormProps >( ( { clientKey, intent, savedCardData, tokenProxyEnvironment = 'production', styles, onValidateSuccess, onValidateFailure, onCreateCardForTemporaryUseSuccess, onCreateCardForTemporaryUseFailure, onSaveCardSuccess, onSaveCardFailure, customStrings = {}, }, ref ) => { // ======= // Validate required prop if (!clientKey) { throw new Error( 'Attempted to render `DuffelCardForm` without a clientKey.' ); } if ( !intent || ![ 'to-create-card-for-temporary-use', 'to-use-saved-card', 'to-save-card', ].includes(intent) ) { throw new Error( 'Attempted to render `DuffelCardForm` without an `intent`. Make sure your provide one of the following: `create-card-for-temporary-use`, `save-card`, `use-saved-card`.' ); } if ( intent === 'to-create-card-for-temporary-use' && !onCreateCardForTemporaryUseSuccess ) { throw new Error( 'Attempted to render `DuffelCardForm` with intent `to-create-card-for-temporary-use` but `onCreateCardForTemporaryUseSuccess` prop is missing. Make sure you provide a function to handle the success case.' ); } if ( intent === 'to-create-card-for-temporary-use' && !onCreateCardForTemporaryUseFailure ) { throw new Error( 'Attempted to render `DuffelCardForm` with intent `to-create-card-for-temporary-use` but `onCreateCardForTemporaryUseFailure` prop is missing. Make sure you provide a function to handle the failure case.' ); } if (intent === 'to-save-card' && !onSaveCardSuccess) { throw new Error( 'Attempted to render `DuffelCardForm` with intent `to-save-card` but `onSaveCardSuccess` prop is missing. Make sure you provide a function to handle the success case.' ); } if (intent === 'to-save-card' && !onSaveCardFailure) { throw new Error( 'Attempted to render `DuffelCardForm` with intent `to-save-card` but `onSaveCardFailure` prop is missing. Make sure you provide a function to handle the failure case.' ); } if (intent === 'to-use-saved-card' && !savedCardData) { throw new Error( "Attempted to render `DuffelCardForm`to use a saved card but the `savedCardData` prop is missing. Make sure you provide the id of the saved card you'd like to use." ); } if (intent === 'to-use-saved-card' && !onValidateSuccess) { throw new Error( 'Attempted to render `DuffelCardForm` with intent `to-use-saved-card` but `onValidateSuccess` prop is missing. Make sure you provide a function to handle the success case.' ); } if (intent === 'to-use-saved-card' && !onValidateFailure) { throw new Error( 'Attempted to render `DuffelCardForm` with intent `to-use-saved-card` but `onValidateFailure` prop is missing. Make sure you provide a function to handle the failure case.' ); } // ======= // Validate custom strings prop const nonStringFields = Object.entries(customStrings).filter( ([_customStringsKeys, value]) => typeof value !== 'string' ); if (nonStringFields.length >= 1) { throw new Error( `The following 'customStrings' prop values are not strings: ${nonStringFields.map(([key]) => key).join(', ')}` ); } const unknownFields = Object.keys(customStrings).filter( (customStringsKeys) => !Object.keys(DEFAULT_STRINGS).includes(customStringsKeys) ); if (unknownFields.length >= 1) { console.warn( `The following custom strings are not recognized: ${unknownFields.join(', ')}` ); } const strings = { ...DEFAULT_STRINGS, ...customStrings, }; // ======= // Sets up form state const [ createCardForTemporaryUseFormData, onChangeCreateCardForTemporaryUseFormData, ] = React.useState({ number: '', expiry_month: '', expiry_year: '', expiry_date: null, cvc: '', name: '', address_line_1: '', address_line_2: '', address_city: '', address_region: '', address_postal_code: '', address_country_code: '', brand: '', multi_use: false, }); const [saveCardFormData, onChangeSaveCardFormData] = React.useState< SaveCardPayload & VirtualFields >({ number: '', expiry_month: '', expiry_year: '', expiry_date: null, name: '', address_line_1: '', address_line_2: '', address_city: '', address_region: '', address_postal_code: '', address_country_code: '', brand: '', multi_use: true, }); const [useSavedCardFormData, onChangeUseSavedCardFormData] = React.useState({ card_id: savedCardData?.id ?? '', brand: savedCardData?.brand ?? '', cvc: '', }); // ======= // Sets up messages that should be pushed into the iframe React.useImperativeHandle(ref, () => { const client = getDuffelCardsAPIClient(tokenProxyEnvironment, clientKey); return { saveCard: async () => { if (typeof onSaveCardSuccess !== 'function') { throw new Error( 'Attempted to call `saveCard` but prop `onSaveCardSuccess` is not a function.' ); } if (typeof onSaveCardFailure !== 'function') { throw new Error( 'Attempted to call `saveCard` but prop `onSaveCardFailure` is not a function.' ); } if (intent !== 'to-save-card') { throw new Error( 'Attempted to call `saveCard`. But this function is only supported for intent: `to-save-card`' ); } try { const response = await client.saveCard(saveCardFormData); if ('errors' in response) { onSaveCardFailure(response); } else { onSaveCardSuccess(response.data); } } catch (error) { onSaveCardFailure(error as Error); } }, createCardForTemporaryUse: async () => { if (typeof onCreateCardForTemporaryUseSuccess !== 'function') { throw new Error( 'Attempted to call `createCardForTemporaryUse` but prop `onCreateCardForTemporaryUseSuccess` is not a function.' ); } if (typeof onCreateCardForTemporaryUseFailure !== 'function') { throw new Error( 'Attempted to call `createCardForTemporaryUse` but prop `onCreateCardForTemporaryUseFailure` is not a function.' ); } if ( intent !== 'to-use-saved-card' && intent !== 'to-create-card-for-temporary-use' ) { throw new Error( 'Attempted to call `createCardForTemporaryUse`. But this function is only supported for intents: `to-create-card-for-temporary-use` or `to-use-saved-card`' ); } try { const response = intent === 'to-use-saved-card' ? await client.useSavedCard(useSavedCardFormData) : await client.createCardForTemporaryUse( createCardForTemporaryUseFormData ); if ('errors' in response) { onCreateCardForTemporaryUseFailure(response); } else { onCreateCardForTemporaryUseSuccess(response.data); } } catch (error) { onCreateCardForTemporaryUseFailure(error as Error); } }, }; }, [ intent, tokenProxyEnvironment, clientKey, saveCardFormData, useSavedCardFormData, createCardForTemporaryUseFormData, onCreateCardForTemporaryUseSuccess, onCreateCardForTemporaryUseFailure, onSaveCardSuccess, onSaveCardFailure, ]); if (intent === 'to-create-card-for-temporary-use') { return ( ); } if (intent === 'to-save-card') { return ( ); } if (intent === 'to-use-saved-card') { return ( ); } // Should never reach this case return null; } );