Skip to main content

Collect card details (Web SDK)

The SecureFields Web SDK renders card input fields as isolated iframes. Sensitive data (PAN, CVV) never touches your page — the SDK returns a vault_form_token you pass to your backend to create the payment.

Native apps

Building a native mobile checkout? Use the iOS SDK or Android SDK instead — they return the same vault_form_token and feed the same Payment API steps.

Prefer a ready-made form?

This guide covers hosted fields mode, where you place and style each field yourself. See Hosted form for hosted form mode, where the SDK renders the whole form — layout and all — and you only supply texts and a theme.

Install the SDK​

<!-- Sandbox -->
<script src="https://cdn.purse-sandbox.com/secure-fields/latest/purse.js"></script>

<!-- Production -->
<script src="https://cdn.purse-secure.com/secure-fields/stable/purse.js"></script>

The CDN script exports a global PurseSecureFields object. Replace purse.js with purse.esm.js for the ESM version.

Initialize SecureFields​

import { loadSecureFields } from '@purse-eu/web-sdk';

const sf = await loadSecureFields('sandbox'); // or 'production'

const secureForm = await sf.initSecureFields({
tenantId: '${VAULT_TENANT_ID}',
config: {
brands: ['VISA', 'MASTERCARD', 'CARTE_BANCAIRE', 'AMERICAN_EXPRESS'],
brandSelector: true,
fields: {
cardNumber: {
target: 'pan-placeholder',
placeholder: '1234 5678 9012 3456',
ariaLabel: 'Card number',
iframeTitle: 'Card number',
errorMessages: {
empty: 'The card number is required',
length: 'The card number is incomplete',
format: 'The card number is invalid',
},
},
cvv: {
target: 'cvv-placeholder',
placeholder: '123',
ariaLabel: 'CVV',
iframeTitle: 'CVV',
},
expDate: {
target: 'exp-placeholder',
placeholder: 'MM/YY',
ariaLabel: 'Expiry date',
iframeTitle: 'Expiry date',
},
holderName: {
target: 'holder-placeholder',
placeholder: 'John Doe',
ariaLabel: 'Cardholder name',
iframeTitle: 'Cardholder name',
},
},
styles: {
color: '#181818',
},
},
});
Build brands from the eligibility response

Instead of hardcoding brands, use the supported_brands of the card solution returned by the eligibility call. The form then matches the brands configured for your entity — see Retrieve payment methods.

Add matching id attributes to your HTML:

<div id="pan-placeholder"></div>
<div id="exp-placeholder"></div>
<div id="cvv-placeholder"></div>
<div id="holder-placeholder"></div>

Render the fields​

await secureForm.render();

Call render() after initSecureFields resolves. The SDK injects an iframe into each target element.

Card brand selection​

Set brandSelector: true in the config. The SDK displays a built-in dropdown when a co-branded card is detected (e.g. Visa/Carte Bancaire). No extra code needed — the selected brand is passed automatically on submit.

note

Brands appear in the dropdown in the same order as specified in the brands array.

Manual​

For custom brand UI, subscribe to the brandDetected event and call setSelectedBrand() as soon as the cardholder picks a brand.

Do not wait until submit. On a co-badged card the two networks may group the digits differently or accept a different CVV length, and the card number field only follows the brand it has been told about. setSelectedBrand() is also what the brand is tokenized under, so you no longer need to pass selectedNetwork to submit().

<select id="brand-select">
<option value="VISA">Visa</option>
<option value="MASTERCARD">Mastercard</option>
<option value="CARTE_BANCAIRE">Carte Bancaire</option>
</select>
const brandSelect = document.getElementById('brand-select');

secureForm.on('brandDetected', ({ brands }) => {
// brands is an array of brand names, e.g. ['CARTE_BANCAIRE', 'VISA']
if (brands?.length) {
brandSelect.value = brands[0];
}
});

brandSelect.addEventListener('change', () => {
// Pushes the choice into the card number field, so digit grouping and CVV length follow it
secureForm.setSelectedBrand(brandSelect.value);
});

const result = await secureForm.submit();
note

A brand the BIN lookup did not detect is ignored. Call setSelectedBrand() only after render().

Listen for events​

secureForm.on('ready', () => {
// Fields are rendered and ready for input
});

secureForm.on('change', (event) => {
// Runs on each field change — use for inline validation UI
console.log(event.fieldName, event.isValid);
});

See Events for the full list of available events and their payloads.

Submit and get vault_form_token​

try {
const result = await secureForm.submit({
saveToken: false, // set to true to save the card for future payments
});

// Send result.vault_form_token to your backend
console.log('vault_form_token:', result.vault_form_token);
} catch (err) {
console.error('Submit failed:', err.code, err.reason);
}

Submit parameters:

ParameterTypeRequiredDescription
saveTokenbooleanNoSave the card to the customer's wallet after payment
selectedNetworkstringNoCard brand (e.g. VISA). Overrides the brand set with setSelectedBrand() for this submit only — it does not reformat the fields. Prefer setSelectedBrand().

The vault_form_token is short-lived. Pass it to your backend immediately — do not store it.

Retrieve card details (optional)​

After submit, your backend can fetch the card metadata (BIN, last 4 digits, brand, issuer) from the Vault API for fraud detection or display purposes.

curl -X GET \
'https://api.purse-sandbox.com/vault/v1/tenants/${VAULT_TENANT_ID}/forms/${VAULT_FORM_TOKEN}' \
--header "Authorization: Bearer ${ACCESS_TOKEN}"
API Endpoint
  • Endpoint: /vault/v1/tenants/{tenant}/forms/{form_token}
  • Method: GET
  • API Reference

Response:

{
"form_token": "32oufdHG56-O87sbsaWdRtZ7Q1_ziLAx",
"card": {
"main_brand": "VISA",
"co_brand": "CARTE_BANCAIRE",
"bin": "42424242",
"last_four_digits": "4242",
"truncated_pan": "424242******4242",
"issuer_name": "Credit Agricole S.A.",
"issuer_country": "FR",
"consumer_type": "CONSUMER",
"funding_source": "DEBIT"
},
"cvv_token": "uEq9stsNhuIJ9UDT0wY0tqy8nDmrNRRI",
"card_holder_name": "John Doe",
"selected_network": "CARTE_BANCAIRE"
}

Live example​

Loading demo…

Next steps​