<!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>
<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) {
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',
},
},
},
});
await secureForm.render();
secureForm.on('ready', function () {
const errorBox = document.getElementById('payment-error');
if (errorBox) {
errorBox.textContent = '';
errorBox.classList.add('hidden');
}
});
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,
});
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>