<!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 {
height: 32px;
min-height: 0;
}
#pan-placeholder iframe, #cvv-placeholder iframe {
height: 100% !important;
display: block;
}
</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 class="grid grid-cols-1 sm:grid-cols-3 gap-3 items-end">
<div class="sm:col-span-2">
<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 for="brand-select" class="block text-sm font-medium text-gray-700 mb-1">Brand</label>
<select id="brand-select" class="w-full h-8 rounded-lg border border-gray-300 bg-white px-2 py-1 shadow-sm focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500">
<option value="VISA">VISA</option>
<option value="MASTERCARD">MASTERCARD</option>
<option value="CARTE_BANCAIRE">CARTE_BANCAIRE</option>
<option value="AMERICAN_EXPRESS">AMERICAN_EXPRESS</option>
</select>
</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 for="cardholder-name" class="block text-sm font-medium text-gray-700 mb-1">Holder Name</label>
<input type="text" id="cardholder-name" class="w-full rounded-lg border border-gray-300 bg-white px-2 py-1 shadow-sm focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500" placeholder="John Doe" />
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label for="expiry-month" class="block text-sm font-medium text-gray-700 mb-1">Expiry Month</label>
<input type="text" id="expiry-month" class="w-full rounded-lg border border-gray-300 bg-white px-2 py-1 shadow-sm focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500" placeholder="MM" />
</div>
<div>
<label for="expiry-year" class="block text-sm font-medium text-gray-700 mb-1">Expiry Year</label>
<input type="text" id="expiry-year" class="w-full rounded-lg border border-gray-300 bg-white px-2 py-1 shadow-sm focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500" placeholder="YY" />
</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]',
config: {
brands: supportedBrands,
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',
},
},
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');
}
});
secureForm.on('brandDetected', function ({ brands }) {
const brandSelect = document.getElementById('brand-select');
if (!brandSelect || !brands || !brands.length) return;
const detected = String(brands[0]);
const has = Array.from(brandSelect.options).some(function (opt) { return opt.value === detected; });
if (has) brandSelect.value = detected;
});
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 expMonth = document.getElementById('expiry-month').value;
const expYear = document.getElementById('expiry-year').value;
const holder = document.getElementById('cardholder-name').value;
const selectedNetwork = document.getElementById('brand-select').value;
const result = await secureForm.submit({
expiryMonth: expMonth,
expiryYear: expYear,
cardHolderName: holder,
selectedNetwork: selectedNetwork,
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>