Skip to main content

Advanced Flow

Introduction

The Advanced Flow gives you full control over how payment data is collected and processed. It separates the secure collection of sensitive card information (handled by the SecureFields SDK) from the actual payment creation (handled by the Payment API).

This flow is recommended if you want to:

  • Keep a low-level integration with maximum flexibility.
  • Control when and how payments are created.
  • Handle business rules before sending a payment request.

Below is the nominal sequence for a successful payment using the Advanced Flow:

Advanced Flow sequence

Flow Description

  1. Checkout initialization — The customer opens checkout; the Merchant Frontend initializes the SecureFields SDK and receives secure-fields configuration.
  2. Card data collection — The customer enters card details in secure fields. On submit, the SecureFields SDK returns a vault_form_token (no PAN/CVV touch your systems) which is sent to the Merchant Backend.
  3. Payment creation — The Merchant Backend calls the Payment API with the token, order data, 3DS preferences, and shopper redirection URL. The API forwards to the PSP, creating a pending payment and returning a 3DS redirect URL.
  4. 3DS authentication — The customer is redirected to the PSP’s 3DS page. After successful authentication, the PSP redirects back to your shopper_redirection_url with context (purse-redirection-data).
  5. Final update — The Payment API sends the final payment status to the Merchant Backend via webhook.

Before you start

  • Ask your Purse Integration Team for a Vault Tenant ID and your Payment API Key.

Collect card details

In this section, we will show you how to collect card details using the SecureFields SDK.

Beta Notice

The SecureFields SDK is currently in beta. Documentation may evolve. Contact us at [email protected] with questions or feedback.

1. 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.

You can get the ESM version from the CDN. Just replace purse.js with purse.esm.js.

2. Initialize SecureFields

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

async function init() {
const sf = await loadSecureFields('sandbox');

const secureForm = await sf.initSecureFields({
tenantId: '[your vault tenant_id]',
apiKey: '[your purse api_key (optional)]',
config: {
brands: ['VISA', 'MASTERCARD', 'CARTE_BANCAIRE', 'AMERICAN_EXPRESS'],
brandSelector: true,
fields: {
cardNumber: {
target: 'pan-placeholder',
placeholder: 'ex: 1234 5678 9012 3456',
ariaLabel: 'Numéro de carte',
iframeTitle: 'Numéro de carte'
},
cvv: {
target: 'cvv-placeholder',
placeholder: 'ex: 123',
ariaLabel: 'CVV',
iframeTitle: 'CVV',
},
expDate: {
target: 'exp-date',
placeholder: 'MM/YY',
ariaLabel: 'Expiry date',
iframeTitle: 'Expiry date',
},
holderName: {
target: 'holder',
placeholder: 'John Doe',
ariaLabel: 'Cardholder name',
iframeTitle: 'Cardholder name',
},
},
styles: {
color: "#181818"
}
}
}
);
}

init();

3. Render the fields

await secureForm.render();

4. Optionally update configuration

secureForm.setConfig({
fields: {
cardNumber: {
placeholder: 'ex :new placeholder',
},
},
styles: {
input: {
color: 'blue',
},
},
});

5. Listen for events

secureForm.on('ready', () => {
console.log('Secure fields are ready');
});

secureForm.on('change', (event) => {
console.log('Field changed', event);
});

6. Submit and receive vault_form_token

try {
const result = await secureForm.submit({
saveToken: false
});
console.log('vault_form_token received:', result.vault_form_token);
} catch (err) {
console.error('Submit failed', err.code, err.reason);
}

Create Payment

After successfully receiving the vault_form_token, you must pass this token to create a payment along with other required customer and order data when calling the payment creation endpoint.

To create a payment, call the /create_payment endpoint:

await fetch('https://api.purse-sandbox.com/payment/v2/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'xxx'
},
body: JSON.stringify({
"entity_id": "your entity id",
"amount": 100,
"currency": "EUR",
"order": { ... }, // order data
"split": [
{
"vault_form_token": "123",
"three_ds_authentication_options": {
"challenge_indicator": "NO_CHALLENGE_REQUESTED"
}
}
],
"browser": { ... } // browser data
})
});

For 3DS payments, see 3DS authentication.

Complete Integration Example

This example shows a complete implementation flow including initialization, rendering, event handling, form submission, payment processing, and error handling.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Purse Secure Fields — Minimal Example</title>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<!-- Include Purse Secure Fields via CDN -->
<script src="https://cdn.purse-sandbox.com/secure-fields/latest/purse.js"></script>
<style>
#pan-placeholder, #cvv-placeholder, #holder-placeholder, #expiry-date-placeholder {
height: 32px;
}
</style>
</head>
<body class="min-h-screen bg-gray-50 flex items-center justify-center p-6">
<div class="w-full max-w-md">
<div class="bg-white shadow rounded-xl p-6 border border-gray-100">
<h1 class="text-lg font-semibold text-gray-900 mb-1">Purse Secure Fields</h1>
<p class="text-sm text-gray-500 mb-6">Copy/paste example for documentation.</p>

<form id="payment-form" class="space-y-4">
<div id="payment-error" class="hidden text-sm text-red-600 bg-red-50 border border-red-200 rounded-md p-3"></div>

<div>
<label id="pan-label" class="block text-sm font-medium text-gray-700 mb-1">Card Number</label>
<div id="pan-placeholder" aria-labelledby="pan-label" class="w-full rounded-lg border border-gray-300 bg-white px-2 py-0 shadow-sm focus-within:border-indigo-500 focus-within:ring-1 focus-within:ring-indigo-500"></div>
</div>

<div>
<label id="cvv-label" class="block text-sm font-medium text-gray-700 mb-1">CVV</label>
<div id="cvv-placeholder" aria-labelledby="cvv-label" class="w-full rounded-lg border border-gray-300 bg-white px-2 py-0 shadow-sm focus-within:border-indigo-500 focus-within:ring-1 focus-within:ring-indigo-500"></div>
</div>

<div>
<label id="exp-date-label" class="block text-sm font-medium text-gray-700 mb-1">Expiry Date</label>
<div id="exp-date" aria-labelledby="exp-date-label" class="w-full rounded-lg border border-gray-300 bg-white px-2 py-0 shadow-sm focus-within:border-indigo-500 focus-within:ring-1 focus-within:ring-indigo-500"></div>
</div>

<div>
<label id="holder-label" class="block text-sm font-medium text-gray-700 mb-1">Holder Name</label>
<div id="holder" aria-labelledby="holder-label" class="w-full rounded-lg border border-gray-300 bg-white px-2 py-0 shadow-sm focus-within:border-indigo-500 focus-within:ring-1 focus-within:ring-indigo-500"></div>
</div>

<div class="flex items-center gap-3">
<button id="submit-button" type="submit" class="inline-flex items-center justify-center rounded-lg bg-indigo-600 px-4 py-2 text-white text-sm font-medium shadow hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-60">
Pay
</button>
<div id="loading-indicator" style="display: none;" class="text-sm text-gray-600">Processing...</div>
</div>

<div>
<label for="submit-result" class="block text-sm font-medium text-gray-700 mb-1">Submit result</label>
<pre id="submit-result" class="whitespace-pre-wrap text-xs bg-gray-50 border border-gray-200 rounded-lg p-3 text-gray-800"></pre>
</div>
</form>
</div>
</div>

<script>
(async function (purse) {
// 1) Initialize SecureFields
const supportedBrands = ['VISA', 'MASTERCARD', 'CARTE_BANCAIRE', 'AMERICAN_EXPRESS'];
const secureForm = await purse.initSecureFields({
tenantId: '[your vault tenant_id]',
apiKey: '[your purse api_key (optional)]',
config: {
brands: supportedBrands,
brandSelector: true,
fields: {
cardNumber: {
target: 'pan-placeholder',
placeholder: 'ex: 1234 5678 9012 3456',
ariaLabel: 'Card number',
iframeTitle: 'Card number',
},
cvv: {
target: 'cvv-placeholder',
placeholder: 'ex: 123',
ariaLabel: 'CVV',
iframeTitle: 'CVV',
},
expDate: {
target: 'exp-date',
placeholder: 'MM/YY',
ariaLabel: 'Expiry date',
iframeTitle: 'Expiry date',
},
holderName: {
target: 'holder',
placeholder: 'John Doe',
ariaLabel: 'Cardholder name',
iframeTitle: 'Cardholder name',
},
},
styles: {
input: {
fontSize: '14px',
lineHeight: '20px',
},
},
},
});

// 2) Render
await secureForm.render();

// 3) Events
secureForm.on('ready', function () {
const errorBox = document.getElementById('payment-error');
if (errorBox) {
errorBox.textContent = '';
errorBox.classList.add('hidden');
}
});

// 4) Submit
document.getElementById('payment-form').addEventListener('submit', async function (e) {
e.preventDefault();
const btn = document.getElementById('submit-button');
const spinner = document.getElementById('loading-indicator');
const pre = document.getElementById('submit-result');
if (pre) pre.textContent = '';
btn.disabled = true;
spinner.style.display = 'block';
try {
const result = await secureForm.submit({
saveToken: false, // true if you want to save the token
});

// In real life you would send the vault_form_token to the payment creation endpoint.
if (pre) pre.textContent = JSON.stringify(result, null, 2);
} catch (err) {
const anyErr = err || {};
const box = document.getElementById('payment-error');
if (box) {
box.textContent = 'Payment failed: ' + (anyErr.reason || 'Unknown error');
box.classList.remove('hidden');
}
if (pre) pre.textContent = JSON.stringify({ error: true, code: anyErr.code, reason: anyErr.reason || String(anyErr) }, null, 2);
} finally {
btn.disabled = false;
spinner.style.display = 'none';
}
});
})(window.PurseSecureFields);
</script>
</body>
</html>

Next Steps