@sfpy/atoms
v0.3.7
Published
Atomic building blocks to build custom payment interfaces
Readme
Safepay Atoms
Safepay Atoms is a modular library that provides secure payment components for web applications. It includes both Web Components and React components for card capture and payer authentication, enabling seamless integration of Safepay's payment functionality into your applications.
Installation
Using npm
npm install @sfpy/atomsUsing yarn
yarn add @sfpy/atomsUsing CDN
<script src="https://cdn.jsdelivr.net/npm/@sfpy/atoms@latest/dist/components/index.global.js"></script>Web Components
CardCaptureAtom
The <safepay-card-atom> component provides card capture functionality. Set its configuration through the element's properties:
<safepay-card-atom id="card-atom"></safepay-card-atom>
<script type="module">
const cardAtom = document.getElementById('card-atom');
cardAtom.environment = 'sandbox';
cardAtom.authToken = 'your-auth-token';
cardAtom.tracker = 'your-tracker';
cardAtom.inputStyle = {
fontFamily: 'Inter, system-ui, sans-serif',
fontSize: '16px',
color: '#111827',
};
</script>Available Attributes (HTML)
Use attributes for string values only. Attributes are always strings and are mapped to properties internally.
| Attribute | Type | Description |
|-------------------------------------|-----------|------------------------------------------|
| environment | 'development' \| 'production' \| 'sandbox' \| 'local' (string) | Environment setting |
| authToken | string | Authentication token for the session |
| tracker | string | Tracking identifier |
| validationEvent | 'submit' | 'change' | 'keydown' | 'none' (string) | Determines when card inputs are validated (defaults to 'submit') |
Available Properties (JS)
Set properties directly on the element for functions, objects, and non-string values.
| Property | Type | Description |
|-------------------------------------|-----------|------------------------------------------|
| environment | 'development' \| 'production' \| 'sandbox' \| 'local' (string) | Environment setting |
| authToken | string | Authentication token for the session |
| tracker | string | Tracking identifier |
| validationEvent | 'submit' | 'change' | 'keydown' | 'none' (string) | Determines when card inputs are validated (defaults to 'submit') |
| inputStyle | InputStyle | Inline styles forwarded to the secure iframe inputs |
| promoCode | string | Optional promo code to auto-apply on load. Applied once discount offers are fetched from the API. |
| forcePromoCode | boolean | When true and a promoCode is set, BIN-based card scheme discounts are ignored and the promo code is always used. |
| onReady | function | Callback when the embedded iframe signals it is ready |
| onError | function | Error callback handler |
| onValidated | (data: { bin: string; lastFour: string; cardType?: string }) => void | Called when the card passes client-side validation. Receives the first 6 digits (bin), last 4 digits (lastFour), and the detected card type (cardType, e.g. "visa"). |
| onDiscountApplied | function | Discount applied callback (includes discountBody) |
| onProceedToAuthentication | function | Authentication proceed callback |
| imperativeRef | CardCaptureImperativeRef | Ref object for imperative methods (optional) |
Setting Properties vs Attributes
Attributes are string-only. For callbacks and objects, set properties directly:
<safepay-card-atom id="card-atom" environment="sandbox"></safepay-card-atom>
<script type="module">
const cardAtom = document.getElementById('card-atom');
cardAtom.onReady = () => console.log('ready');
cardAtom.onValidated = ({ bin, lastFour, cardType }) => console.log('validated', bin, lastFour, cardType);
cardAtom.onProceedToAuthentication = (data) => console.log('auth', data);
</script>
inputStyleis exposed as a JavaScript property on the custom element. It is not intended to be passed as an HTML attribute string.
CardCaptureAtom Methods
The <safepay-card-atom> component exposes several methods that can be called imperatively:
// Get reference to the element
const cardAtom = document.querySelector('safepay-card-atom');| Method | Description | Returns | Example |
|----------------|---------------------------------------------|-----------|---------------------------------------------|
| submit() | Triggers the submission of the card data | void | cardAtom.submit() |
| validate() | Performs validation of the current card input| void | cardAtom.validate() |
| fetchValidity()| Checks if the current card input is valid | boolean | const isValid = cardAtom.fetchValidity() |
| clear() | Clears all input fields | void | cardAtom.clear() |
Example Usage
// Example using Web Component methods
const cardAtom = document.querySelector('safepay-card-atom');
// Submit the form
cardAtom.submit();
// Validate the input
cardAtom.validate();
// Check if input is valid
const isValid = cardAtom.fetchValidity();
isValid.then((isValid) => {
console.log(isValid);
cardAtom.submit();
});
// Clear the form
cardAtom.clear();Using Web Components
For a full plain HTML/JavaScript integration (no bundler required), see examples/card-links-demo.html. The snippet below mirrors that example so you can copy it into your own demo page:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Safepay Card Capture Demo</title>
<script src="https://unpkg.com/@sfpy/atoms@latest/dist/components/index.global.js"></script>
<style>
.card-frame {
width: 22.5rem;
height: 2.6rem;
}
.modal-backdrop {
position: fixed;
inset: 0;
display: none;
background-color: rgba(0, 0, 0, 0.5);
cursor: pointer;
}
.modal-backdrop.show {
display: block;
}
.popup {
position: absolute;
top: 12.5%;
left: 25%;
width: 40%;
height: 75%;
border: 1px solid black;
background-color: white;
}
</style>
</head>
<body>
<h1>Card Capture Atom</h1>
<div class="card-frame">
<safepay-card-atom></safepay-card-atom>
</div>
<button type="button" onclick="handleSubmit()">Submit</button>
<div id="threeds-modal" class="modal-backdrop">
<div class="popup">
<safepay-payer-auth-atom></safepay-payer-auth-atom>
</div>
</div>
<script type="text/javascript">
const ENVIRONMENT = 'sandbox';
const TRACKER = 'track_your_tracker_id';
const CLIENT_SECRET = 'your_auth_token';
const cardAtom = document.querySelector('safepay-card-atom');
const payerAuthAtom = document.querySelector('safepay-payer-auth-atom');
const modal = document.getElementById('threeds-modal');
function closeModal() {
modal.classList.remove('show');
}
function openModal() {
modal.classList.add('show');
}
Object.assign(payerAuthAtom, {
environment: ENVIRONMENT,
tracker: TRACKER,
authToken: CLIENT_SECRET,
authorizationOptions: {
do_capture: true,
do_card_on_file: true,
},
onPayerAuthenticationFailure: closeModal,
onPayerAuthenticationSuccess: closeModal,
onPayerAuthenticationFrictionless: closeModal,
onPayerAuthenticationUnavailable: closeModal,
onSafepayError: (error) => {
console.error('Safepay error', error);
closeModal();
},
});
Object.assign(cardAtom, {
environment: ENVIRONMENT,
tracker: TRACKER,
authToken: CLIENT_SECRET,
validationEvent: 'submit',
onError: (error) => console.error(error),
onValidated: () => console.log('validated'),
onDiscountApplied: (data) => {
if (data && data.discountBody) {
payerAuthAtom.discountBody = data.discountBody;
}
},
onProceedToAuthentication: (data) => {
payerAuthAtom.deviceDataCollectionJWT = data.accessToken;
payerAuthAtom.deviceDataCollectionURL = data.deviceDataCollectionURL;
openModal();
},
});
function handleSubmit() {
cardAtom.fetchValidity().then((isValid) => {
if (isValid) {
cardAtom.submit();
}
});
}
window.handleSubmit = handleSubmit;
</script>
</body>
</html>PayerAuthenticationAtom
The <safepay-payer-authentication> component handles payer authentication flows.
<safepay-payer-authentication
environment="sandbox"
auth-token="your-auth-token"
tracker="your-tracker"
></safepay-payer-authentication>
<script>
// Set object properties directly on the element.
const el = document.querySelector('safepay-payer-authentication');
el.billing = {
street_1: '123 Main Street',
street_2: 'Suite 500',
city: 'Berlin',
state: 'BE',
country: 'DE',
postal_code: '10115',
};
el.discountBody = {
dry_run: true,
bin_discount: { cardscheme_id: 'visa', bin: '411111' },
};
</script>Available Attributes (HTML)
Use attributes for string values only. Attributes are always strings and are mapped to properties internally.
| Attribute | Type | Description |
|-------------------------------------|-----------|------------------------------------------|
| environment | 'development' \| 'production' \| 'sandbox' \| 'local' (string) | Environment setting |
| tracker | string | Tracking identifier |
| authToken | string | Authentication token |
| user | string | User identifier forwarded with authentication requests |
| deviceDataCollectionJWT | string | Device data collection JWT |
| deviceDataCollectionURL | string | Device data collection URL |
Available Properties (JS)
Set properties directly on the element for functions, objects, and non-string values.
| Property | Type | Description |
|-------------------------------------|-----------|------------------------------------------|
| environment | 'development' \| 'production' \| 'sandbox' \| 'local' (string) | Environment setting |
| tracker | string | Tracking identifier |
| authToken | string | Authentication token |
| user | string | User identifier forwarded with authentication requests |
| billing | Billing | Billing information |
| deviceDataCollectionJWT | string | Device data collection JWT |
| deviceDataCollectionURL | string | Device data collection URL |
| discountBody | DiscountBody | Optional discount context object sent to Safepay to evaluate/apply discounts during authentication. Set via property (not attribute). |
| authorizationOptions | AuthorizationOptions | Authorization configuration options |
| onPayerAuthenticationFailure | function | Recommended. Called after a challenge-based 3DS flow fails. The user may already have seen the challenge UI. |
| onPayerAuthenticationSuccess | function | Recommended. Called after a challenge-based 3DS flow succeeds. This does not run for frictionless auth. |
| onPayerAuthenticationRequired | function | Optional. Called when enrollment determines that a step-up 3DS challenge is required. Informational only; the challenge UI may start rendering immediately after this event. |
| onPayerAuthenticationFrictionless | function | Recommended. Called when authentication completes without a 3DS challenge and authorization succeeds. This is the success callback for the frictionless path. |
| onPayerAuthenticationUnavailable | function | Recommended. Called when the payer-auth flow cannot continue before a successful challenge flow is reached, such as enrollment/setup failures or other pre-challenge terminal errors. |
| onSafepayError | function | Recommended. Called for general Safepay iframe/application errors outside the normal authentication lifecycle. |
| imperativeRef | PayerAuthenticationImperativeRef | Ref object for imperative methods (optional) |
Callback Semantics
The payer authentication callbacks represent different stages of the flow. They are not interchangeable:
onPayerAuthenticationRequiredmeans Safepay has finished enrollment and determined that a 3DS challenge is needed. Use it for analytics or UI state if you want to know that a challenge is about to happen. It is not a completion callback, and the internal challenge iframe may begin rendering right away.onPayerAuthenticationFrictionlessmeans authentication succeeded without showing a challenge to the user. This is the terminal success callback for the frictionless path.onPayerAuthenticationSuccessmeans the challenge-based 3DS flow completed successfully after the user entered the challenge flow. It does not run for frictionless auth.onPayerAuthenticationFailuremeans the challenge-based 3DS flow failed after the user had already entered the challenge flow.onPayerAuthenticationUnavailablemeans the flow failed before Safepay could complete a successful challenge flow. In practice, this covers enrollment/setup failures and other pre-challenge terminal states.onSafepayErroris the catch-all error callback for general iframe/application errors that do not fit the normal authentication lifecycle.
In most integrations, the recommended callback set is:
onPayerAuthenticationFrictionlessonPayerAuthenticationSuccessonPayerAuthenticationFailureonPayerAuthenticationUnavailableonSafepayError
These cover the terminal success and failure states you usually need to close modals, update UI, or continue checkout. onPayerAuthenticationRequired is usually optional and best treated as an informational/analytics hook.
Setting Properties vs Attributes
Attributes are string-only. For callbacks and objects, set properties directly:
<safepay-payer-authentication id="payer-auth" environment="sandbox"></safepay-payer-authentication>
<script type="module">
const payerAuth = document.getElementById('payer-auth');
payerAuth.billing = {
street_1: '123 Main Street',
street_2: 'Suite 500',
city: 'Berlin',
state: 'BE',
country: 'DE',
postal_code: '10115',
};
payerAuth.authorizationOptions = { do_capture: true };
payerAuth.onPayerAuthenticationSuccess = (data) => console.log('success', data);
</script>React Components
Using Styles
The Safepay Atoms library includes pre-built CSS styles that you can import into your project. These styles are bundled into a single file for convenience.
Importing Styles
To use the styles in your project, import the bundled CSS file:
// Import the styles in your JavaScript/TypeScript project
import '@sfpy/atoms/styles'CardCapture
import { Suspense, useRef } from 'react';
import { CardCapture, Environment } from '@sfpy/atoms';
function PaymentForm() {
const cardRef = useRef(null);
return (
<Suspense fallback={<div>Loading card capture...</div>}>
<CardCapture
environment={Environment.Sandbox}
authToken="your-auth-token"
tracker="your-tracker"
validationEvent="change" // Use `submit` | `change` | `keydown` | `none`
inputStyle={{
fontFamily: '"Courier New", ui-monospace, monospace',
fontSize: '18px',
color: '#111827',
}}
imperativeRef={cardRef}
// Optional callbacks:
// onReady={() => console.log('Card iframe ready')}
// onValidated={(data) => console.log('Card validated', data.bin, data.lastFour, data.cardType)}
// onDiscountApplied={(data) => console.log('Discount applied', data)}
// onProceedToAuthentication={(data) => console.log('Proceed to auth', data)}
// onError={(error) => console.error('Error', error)}
/>
</Suspense>
);
}
Available Props
| Prop | Type | Description | Required |
|-------------------------------|-------------------------------|---------------------------------------------------------|:--------:|
| environment | Environment or case-insensitive string ('development', 'production', 'sandbox', 'local') | Environment setting | ✅ |
| authToken | string | Authentication token | ✅ |
| tracker | string | Tracking identifier | ✅ |
| validationEvent | 'submit' | 'change' | 'keydown' | 'none' | Choose when validation runs (defaults to submit) | ✅ |
| inputStyle | React.CSSProperties | Inline styles forwarded to the secure iframe inputs | |
| promoCode | string | Optional promo code to auto-apply on load. Applied once discount offers are fetched from the API. | |
| forcePromoCode | boolean | When true and a promoCode is set, BIN-based card scheme discounts are ignored and the promo code is always used. | |
| onReady | () => void | Callback when the embedded iframe signals it is ready | |
| onProceedToAuthentication | (data: any) => void | Callback when ready to proceed to authentication | |
| onValidated | (data: CardValidatedData) => void | Called when the card passes client-side validation. Receives { bin, lastFour, cardType? }. | |
| onDiscountApplied | (data: any) => void | Callback when a discount is applied (includes discountBody) | |
| onError | (error: string) => void | Error handling callback | |
| imperativeRef | React.MutableRefObject | Ref to control the component imperatively | ✅ |
CardCapture Imperative Methods
The CardCapture component can be controlled using a ref. These methods are accessed through the imperativeRef prop:
// Define the ref
const cardRef = useRef<{
submit: () => void;
validate: () => void;
fetchValidity: () => Promise<boolean>;
clear: () => void;
}>(null);| Method | Description | Returns | Example |
|------------------|--------------------------------------------|-----------------|-------------------------------------------------|
| submit() | Submits the card data for processing | void | cardRef.current?.submit() |
| validate() | Triggers validation of the current input | void | cardRef.current?.validate() |
| fetchValidity()| Asynchronously checks input validity | Promise<boolean> | const isValid = await cardRef.current?.fetchValidity() |
| clear() | Resets all input fields | void | cardRef.current?.clear() |
Example Usage
import React, { useRef } from 'react';
import { CardCapture } from '@sfpy/atoms';
function PaymentForm() {
const cardRef = useRef(null);
const handleSubmit = async () => {
// Submit the form
cardRef.current?.submit();
};
const validateCard = async () => {
// Validate the input
cardRef.current?.validate();
// Check validity
const isValid = await cardRef.current?.fetchValidity();
console.log('Is card valid:', isValid);
};
const resetForm = () => {
// Clear all inputs
cardRef.current?.clear();
};
return (
<CardCapture
imperativeRef={cardRef}
environment="sandbox"
authToken="your-auth-token"
// ... other props
/>
);
}PayerAuthentication
import { Suspense, useRef } from 'react';
import { PayerAuthentication, Environment } from '@sfpy/atoms';
const billing = {
street_1: '123 Main Street',
street_2: 'Suite 500',
city: 'Berlin',
state: 'BE',
country: 'DE',
postal_code: '10115',
};
function AuthenticationForm() {
const authRef = useRef(null);
return (
<Suspense fallback={<div>Loading authentication...</div>}>
<PayerAuthentication
environment={Environment.Sandbox}
tracker="your-tracker"
authToken="your-auth-token"
deviceDataCollectionJWT="your-device-jwt"
deviceDataCollectionURL="https://your-collection-url"
billing={billing}
discountBody={{ dry_run: true, bin_discount: { cardscheme_id: 'visa', bin: '411111' } }}
imperativeRef={authRef}
// Optional callbacks you can pass if needed:
// onPayerAuthenticationSuccess={(data) => console.log('Success', data)}
// onPayerAuthenticationFailure={(error) => console.log('Failure', error)}
// onSafepayError={(error) => console.error('Safepay Error', error)}
/>
</Suspense>
);
}Available Props
| Prop | Type | Description | Required |
|----------------------------------|---------------------------------|---------------------------------------------------|:--------:|
| environment | Environment or case-insensitive string ('development', 'production', 'sandbox', 'local') | Environment setting | ✅ |
| tracker | string | Tracking identifier | ✅ |
| authToken | string | Authentication token | ✅ |
| deviceDataCollectionJWT | string | Device data collection JWT | ✅ |
| deviceDataCollectionURL | string | Device data collection endpoint URL | ✅ |
| discountBody | DiscountBody | Optional discount context object sent to Safepay to evaluate/apply discounts during authentication | |
| user | string | User identifier forwarded with authentication requests | |
| billing | Billing | Billing information (optional) | |
| authorizationOptions | AuthorizationOptions | Authorization configuration options | |
| onPayerAuthenticationFailure | (data: PayerAuthErrorData) => void | Recommended. Called after a challenge-based 3DS flow fails. The user may already have seen the challenge UI. | |
| onPayerAuthenticationSuccess | (data: PayerAuthSuccessData) => void | Recommended. Called after a challenge-based 3DS flow succeeds. This does not run for frictionless auth. | |
| onPayerAuthenticationRequired | (data: PayerAuthData) => void | Optional. Called when enrollment determines that a step-up 3DS challenge is required. Informational only; the challenge UI may start rendering immediately after this event. | |
| onPayerAuthenticationFrictionless| (data: PayerAuthData) => void | Recommended. Called when authentication completes without a challenge and authorization succeeds. This is the success callback for the frictionless path. | |
| onPayerAuthenticationUnavailable | (data: PayerAuthData) => void | Recommended. Called when the payer-auth flow cannot continue before a successful challenge flow is reached, such as enrollment/setup failures or other pre-challenge terminal errors. | |
| onSafepayError | (data: SafepayError) => void | Recommended. Called for general Safepay iframe/application errors outside the normal authentication lifecycle. | |
| imperativeRef | React.MutableRefObject | Ref to control the component imperatively | ✅ |
Combined Card + Authentication Flow
The example below mirrors the stripped-down integration used in app/routes/combinedDemoStripped.tsx from the test app. It shows how to:
- render
CardCaptureandPayerAuthenticationtogether - pass
inputStyleinto the secure card iframe - wait for card validation before submitting
- open the authentication modal only when Safepay requests it
- forward
discountBodyfrom card capture into payer authentication
import { CardCapture, Environment, PayerAuthentication } from '@sfpy/atoms';
import '@sfpy/atoms/styles';
import * as React from 'react';
const DEMO_ENVIRONMENT = Environment.Development;
const DEMO_AUTH_TOKEN = 'your-auth-token';
const DEMO_TRACKER = 'your-tracker';
const CARD_INPUT_STYLE = {
fontFamily: '"Courier New", ui-monospace, monospace',
fontSize: '18px',
color: '#111827',
};
const CARD_FRAME_STYLE = {
width: '22.5rem',
height: '2.6rem',
};
const MODAL_BACKDROP_STYLE = {
position: 'fixed',
width: '100%',
height: '100%',
inset: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
zIndex: 2,
cursor: 'pointer',
};
const POPUP_STYLE = {
position: 'absolute',
top: '12.5%',
left: '25%',
width: '40%',
height: '75%',
border: '1px solid black',
backgroundColor: 'white',
zIndex: 2,
};
const MODAL_CONTENT_STYLE = {
width: '100%',
height: '100%',
};
type PayerAuthSession = {
accessToken: string;
deviceDataCollectionURL: string;
};
type DiscountBody = {
dry_run: boolean;
bin_discount?: { cardscheme_id: string; bin: string };
promo_discount?: { code: string };
};
const BILLING = {
street_1: '123 Main Street',
street_2: 'Suite 500',
city: 'Berlin',
state: 'BE',
country: 'DE',
postal_code: '10115',
};
export default function CombinedDemo() {
const cardRef = React.useRef(null);
const payerAuthRef = React.useRef(null);
// Keep payer-auth session details only after Safepay asks us to continue into 3DS.
const [payerAuthSession, setPayerAuthSession] = React.useState<PayerAuthSession | null>(null);
// If card capture returns discount context, forward it into payer authentication.
const [discountBody, setDiscountBody] = React.useState<DiscountBody | undefined>();
const closeModal = React.useCallback(() => {
setPayerAuthSession(null);
}, []);
const handleSubmit = React.useCallback(async () => {
// Trigger validation first so the iframe can surface any field errors.
cardRef.current?.validate();
// Then ask the iframe whether the current input is valid.
const isValid = await cardRef.current?.fetchValidity();
if (isValid) {
// Only submit once the secure iframe confirms the input is valid.
await cardRef.current?.submit();
}
}, []);
return (
<main style={{ padding: '24px' }}>
<div style={CARD_FRAME_STYLE}>
<CardCapture
environment={DEMO_ENVIRONMENT}
authToken={DEMO_AUTH_TOKEN}
tracker={DEMO_TRACKER}
validationEvent="submit"
inputStyle={CARD_INPUT_STYLE}
imperativeRef={cardRef}
// promoCode="SAVE10" — optional: auto-applies this promo code once offers load
onReady={() => console.log('card iframe ready')}
onError={(error) => console.log(error)}
onValidated={(data) => console.log('validated', data.bin, data.lastFour, data.cardType)}
onDiscountApplied={(data) => {
if (data?.discountBody) {
setDiscountBody(data.discountBody);
}
}}
onProceedToAuthentication={(data) => {
// Safepay returns the values needed to initialize payer authentication.
setPayerAuthSession({
accessToken: data.accessToken,
deviceDataCollectionURL: data.deviceDataCollectionURL,
});
}}
/>
</div>
<button style={{ marginTop: '16px' }} onClick={handleSubmit}>
Submit
</button>
{payerAuthSession ? (
<div style={MODAL_BACKDROP_STYLE} onClick={closeModal}>
<div style={POPUP_STYLE} onClick={(event) => event.stopPropagation()}>
<div style={MODAL_CONTENT_STYLE}>
<PayerAuthentication
environment={DEMO_ENVIRONMENT}
authToken={DEMO_AUTH_TOKEN}
tracker={DEMO_TRACKER}
imperativeRef={payerAuthRef}
deviceDataCollectionJWT={payerAuthSession.accessToken}
deviceDataCollectionURL={payerAuthSession.deviceDataCollectionURL}
billing={BILLING}
discountBody={discountBody}
authorizationOptions={{
do_capture: true,
do_card_on_file: true,
}}
onPayerAuthenticationFailure={(data) => {
console.log('onPayerAuthenticationFailure', data);
closeModal();
}}
onPayerAuthenticationSuccess={(data) => {
console.log('onPayerAuthenticationSuccess', data);
closeModal();
}}
onPayerAuthenticationFrictionless={(data) => {
console.log('onPayerAuthenticationFrictionless', data);
closeModal();
}}
onPayerAuthenticationUnavailable={(data) => {
console.log('onPayerAuthenticationUnavailable', data);
closeModal();
}}
onSafepayError={(error) => {
console.log('onSafepayError', error.error.message);
closeModal();
}}
/>
</div>
</div>
</div>
) : null}
</main>
);
}Object Shapes
The following sections show the shapes for the structured values you pass into the atoms.
InputStyle
Used by CardCapture and <safepay-card-atom>.
type InputStyle = Record<string, string | number>;Example:
const inputStyle = {
fontFamily: '"Courier New", ui-monospace, monospace',
fontSize: '18px',
color: '#111827',
};Billing
Used by PayerAuthentication and <safepay-payer-authentication>.
street_2, state, and postal_code are optional.
type Billing = {
street_1: string;
street_2?: string;
city: string;
state?: string;
country: string;
postal_code?: string;
};Example:
const billing = {
street_1: '123 Main Street',
street_2: 'Suite 500',
city: 'Berlin',
state: 'BE',
country: 'DE',
postal_code: '10115',
};AuthorizationOptions
Used by PayerAuthentication and <safepay-payer-authentication>.
type AuthorizationOptions = {
do_capture?: boolean;
do_card_on_file?: boolean;
};Example:
const authorizationOptions = {
do_capture: true,
do_card_on_file: true,
};DiscountBody
Used by PayerAuthentication and <safepay-payer-authentication>. Pass this as an object (do not stringify it).
type DiscountBody = {
dry_run: boolean;
bin_discount?: {
cardscheme_id: string;
bin: string;
};
promo_discount?: {
code: string;
};
};Examples:
// BIN-based discount (applied automatically when the user enters their card number)
const binDiscountBody = {
dry_run: true,
bin_discount: {
cardscheme_id: 'visa',
bin: '411111',
},
};
// Promo code discount (applied automatically when a promoCode is passed to CardCapture)
const promoDiscountBody = {
dry_run: true,
promo_discount: {
code: 'SAVE10',
},
};Notes:
binis the first 6 digits of the card number, with no spaces.dry_runevaluates the discount without committing application when your flow supports it.bin_discountandpromo_discountare mutually exclusive in a single request.
CardCaptureImperativeRef
type CardCaptureImperativeRef = {
current: null | {
submit: () => void;
validate: () => void;
fetchValidity: () => Promise<boolean>;
clear: () => void;
};
};PayerAuthenticationImperativeRef
type PayerAuthenticationImperativeRef = {
current: null | Record<string, never>;
};Note: In React usage, you can pass either the Environment enum (recommended) or a string value such as "SANDBOX" or "sandbox". String values are mapped case-insensitively to the corresponding enum value. If the value is invalid, an exception is thrown to surface the misconfiguration.
Development
This repository uses pnpm for dependency management.
Install dependencies:
pnpm installBuild the package:
pnpm run buildProject Structure
.
├── README.md
├── examples
│ ├── card-links-demo.html
│ └── device-metrics-demo.html
├── pnpm-lock.yaml
├── package.json
├── postcss.config.cjs
├── src
│ ├── atoms
│ │ ├── CardCaptureIframe
│ │ │ ├── iframe.tsx
│ │ │ └── index.tsx
│ │ ├── PayerAuthenticationIframe
│ │ │ ├── iframe.tsx
│ │ │ ├── index.tsx
│ │ │ └── types.ts
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ └── useFunctionQueue.ts
│ │ └── index.ts
│ ├── bridge
│ │ ├── CardAtom.tsx
│ │ ├── PayerAuthentication.tsx
│ │ └── index.tsx
│ ├── elements
│ │ ├── CardCaptureAtom
│ │ │ └── index.ts
│ │ ├── PayerAuthenticationAtom
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── errors.ts
│ ├── index.ts
│ ├── styles
│ │ ├── css
│ │ │ ├── card-link.css
│ │ │ ├── index.css
│ │ │ ├── payer-auth.css
│ │ │ └── seamless-iframe.css
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ └── useStyles.ts
│ │ ├── index.css
│ │ ├── index.ts
│ │ └── loaders
│ │ ├── index.ts
│ │ ├── load-all.ts
│ │ ├── load-card-link.ts
│ │ ├── load-index.ts
│ │ ├── load-payer-auth.ts
│ │ └── load-seamless-iframe.ts
│ ├── types
│ │ ├── atoms.ts
│ │ ├── index.ts
│ │ └── safepay.ts
│ ├── utils
│ │ ├── funcs
│ │ │ ├── base64.ts
│ │ │ ├── defineReactiveProperties.ts
│ │ │ ├── generateUUID.ts
│ │ │ ├── isObject.ts
│ │ │ ├── isString.ts
│ │ │ ├── resolveBaseUrl.ts
│ │ │ └── toCamelCase.ts
│ │ └── index.ts
│ └── utils.ts
├── tsconfig.json
├── tsconfig.react.json
└── types
├── atoms
│ ├── index.d.ts
│ └── models.d.ts
├── index.d.ts
└── models.d.ts