@duffel/react-native-components-card-form
v1.0.0
Published
PCI-compliant React Native component to support card input and 3DS with the Duffel API
Readme
@duffel/react-native-components-card-form
PCI-compliant React Native components for collecting card details, saving cards, using saved cards, and completing 3DS authentication with the Duffel API.
This package is intended for apps integrating card passthrough for taking payments on the Duffel API. It handles the mobile UI and client-side calls that need to happen from the card form. You still need a backend that talks to the Duffel API and returns a clientKey to your app.
Installation
yarn add @duffel/react-native-components-card-form \
@duffel/api \
@evervault/react-native \
@react-native-picker/picker \
react-native-mask-input \
react-native-picker-select \
react-native-svg \
react-native-webviewFor iOS, install pods after adding the package:
cd ios && pod installBefore You Start
Create a Duffel client key on your backend and pass it to your app. Do not ship your Duffel API access token in your React Native app.
Your backend should:
- Authenticate the app user.
- Create or retrieve the Duffel resource the user is paying for.
- Generate a Duffel client key for the mobile session.
- Return the client key to the app.
Collect Card Details
Use DuffelCardForm to render the card form, then trigger the relevant action from your own button. The form calls onValidateSuccess whenever the current details are valid, and onValidateFailure if they become invalid again.
import React from 'react';
import { Button, View } from 'react-native';
import {
DuffelCardForm,
useDuffelCardFormActions,
} from '@duffel/react-native-components-card-form';
export function CheckoutCardForm({ clientKey }: { clientKey: string }) {
const { ref, createCardForTemporaryUse } = useDuffelCardFormActions();
const [canPay, setCanPay] = React.useState(false);
return (
<View>
<DuffelCardForm
ref={ref}
clientKey={clientKey}
intent="to-create-card-for-temporary-use"
onValidateSuccess={() => setCanPay(true)}
onValidateFailure={() => setCanPay(false)}
onCreateCardForTemporaryUseSuccess={(card) => {
// Send card.id to your backend to create the payment or order.
console.log('Created card', card.id);
}}
onCreateCardForTemporaryUseFailure={(error) => {
console.error('Could not create card', error);
}}
/>
<Button
title="Pay"
disabled={!canPay}
onPress={createCardForTemporaryUse}
/>
</View>
);
}Card Form Intents
Choose the intent that matches your checkout flow:
to-create-card-for-temporary-use: renders the full card form including CVC. Use this when the customer is paying with a new card. The returned card can be used for the current payment.to-use-saved-card: renders only the CVC field for an existing saved card. ProvidesavedCardData={{ id, brand }}and callcreateCardForTemporaryUse.to-save-card: renders the full card form without CVC. Use this when the customer is saving a card for later. CallsaveCard.
Save a Card for Later
const { ref, saveCard } = useDuffelCardFormActions();
<DuffelCardForm
ref={ref}
clientKey={clientKey}
intent="to-save-card"
onValidateSuccess={() => setCanSave(true)}
onValidateFailure={() => setCanSave(false)}
onSaveCardSuccess={(card) => {
console.log('Saved card', card.id);
}}
onSaveCardFailure={console.error}
/>;
<Button title="Save card" disabled={!canSave} onPress={saveCard} />;Use a Saved Card
const { ref, createCardForTemporaryUse } = useDuffelCardFormActions();
<DuffelCardForm
ref={ref}
clientKey={clientKey}
intent="to-use-saved-card"
savedCardData={{ id: 'card_0000', brand: 'visa' }}
onValidateSuccess={() => setCanPay(true)}
onValidateFailure={() => setCanPay(false)}
onCreateCardForTemporaryUseSuccess={(card) => {
console.log('Card ready for payment', card.id);
}}
onCreateCardForTemporaryUseFailure={console.error}
/>;3DS Authentication
Wrap your checkout screen with DuffelThreeDSecureProvider, then call create3DSecureSession before completing the payment on your backend.
Learn more in Duffel's card form component with 3DSecure guide.
import {
DuffelThreeDSecureProvider,
useCreate3DSecureSession,
} from '@duffel/react-native-components-card-form';
function CheckoutScreen({ clientKey }: { clientKey: string }) {
return (
<DuffelThreeDSecureProvider clientKey={clientKey}>
<CheckoutContent />
</DuffelThreeDSecureProvider>
);
}
function CheckoutContent() {
const { create3DSecureSession } = useCreate3DSecureSession();
async function authenticateCard(cardId: string, offerId: string) {
await create3DSecureSession(
{
card_id: cardId,
resource_id: offerId,
exception: null,
},
{
onSuccess: (session) => {
// Send session.id to your backend and complete the payment/order.
console.log('3DS ready for payment', session.id);
},
onFailure: (error) => {
// Authentication failed or was cancelled by the user.
console.error(error);
},
onError: (error) => {
// The session could not be created or the challenge could not render.
console.error(error);
},
}
);
}
}If Duffel returns a 3DS session that is already ready_for_payment, the success callback is called without showing a challenge.
Styling
Pass styles to customise the rendered fields:
<DuffelCardForm
clientKey={clientKey}
intent="to-create-card-for-temporary-use"
styles={{
input: { color: '#111827', fontSize: 16 },
label: { color: '#374151', fontWeight: '600' },
errorMessage: { color: '#B42318' },
formField: { marginBottom: 16 },
formContainer: { gap: 12 },
sectionTitle: { fontSize: 18, fontWeight: '700' },
}}
onValidateSuccess={() => {}}
onValidateFailure={() => {}}
onCreateCardForTemporaryUseSuccess={() => {}}
onCreateCardForTemporaryUseFailure={() => {}}
/>The countryPicker style uses react-native-picker-select's PickerStyle type.
Custom Strings
Use customStrings to replace labels, placeholders, section titles, and validation messages:
<DuffelCardForm
clientKey={clientKey}
intent="to-create-card-for-temporary-use"
customStrings={{
cardNumberLabel: 'Card number',
expirationDatePlaceholder: 'MM/YY',
billingAddressSectionTitle: 'Billing address',
cvvRequiredMessage: 'Enter the card security code',
}}
onValidateSuccess={() => {}}
onValidateFailure={() => {}}
onCreateCardForTemporaryUseSuccess={() => {}}
onCreateCardForTemporaryUseFailure={() => {}}
/>See DuffelCardFormStrings in the package types for the full list of strings.
API Reference
DuffelCardForm
Required props:
clientKey: Duffel client key returned by your backend.intent: one ofto-create-card-for-temporary-use,to-use-saved-card, orto-save-card.onValidateSuccess: called when the current form data is valid.onValidateFailure: called when the current form data is invalid.
Intent-specific props:
onCreateCardForTemporaryUseSuccessandonCreateCardForTemporaryUseFailureare required forto-create-card-for-temporary-useandto-use-saved-card.savedCardDatais required forto-use-saved-card.onSaveCardSuccessandonSaveCardFailureare required forto-save-card.
Optional props:
styles: custom styles for inputs, labels, errors, and layout.customStrings: replacement text for labels, placeholders, and validation messages.
useDuffelCardFormActions
Returns:
ref: pass this toDuffelCardForm.createCardForTemporaryUse(): creates a card for the current payment. Use withto-create-card-for-temporary-useorto-use-saved-card.saveCard(): saves a card for later. Use withto-save-card.
DuffelThreeDSecureProvider
Props:
clientKey: Duffel client key returned by your backend.children: checkout UI that needs access to 3DS.
useCreate3DSecureSession
Returns create3DSecureSession(payload, callbacks).
Payload:
card_id: card ID returned by the card form.resource_id: Duffel resource ID for the payment, such as an offer or order.exception:secure_corporate_paymentornull.services: optional service IDs and quantities for ancillary payments.
Callbacks:
onSuccess(session): 3DS is ready for payment. Complete the payment on your backend.onFailure(error): authentication failed or was cancelled.onError(error): the session could not be created or the challenge could not render.
