Introduction
Token payment enables a seamless experience for repeat purchases. Customers can save their payment method once and reuse it for future transactions, reducing friction and increasing conversion rates.
Always ask customers for explicit consent before saving their card. This builds trust and ensures compliance.
1. Save Card for Future Use
During checkout, offer customers the option to save their card for future purchases.
<div className="flex items-center gap-2 mb-4">
<input type="checkbox" id="save-card" name="save-card" className="rounded border-gray-300" />
<label htmlFor="save-card" className="text-sm text-gray-700">Save card for future purchases</label>
</div>
When submitting the payment, include the consent in the SDK call:
const saveCard = document.getElementById('save-card').checked;
const result = await secureForm.submit({ saveToken: saveCard });
To save a card during payment, your server calls the Payment API with the save_token flag:
curl -X POST 'https://api.purse-sandbox.com/payment/v2/payments' \
--header 'Content-Type: application/json' \
--header "x-api-key: ${API_KEY}" \
--header "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '{
"entity_id": "{{entity_id}}",
"amount": 100,
"currency": "EUR",
"order": { },
"split": [
{
"vault_form_token": "{{vault_form_token}}",
"save_token": true
}
],
"browser": { }
}'
2. Pay with a Saved Card
For subsequent purchases, customers can select a saved card. Only CVV is required for extra security.
Your server retrieves the saved tokens for the customer:
curl -X GET 'https://api.purse-sandbox.com/wallet/v3/wallet/merchants/{{merchant_id}}/customers/{{customer_reference}}/tokens' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ${ACCESS_TOKEN}"
Display saved cards for selection (client-side, using data returned by your server):
function displaySavedCards(tokens) {
tokens.forEach(token => {
const button = document.createElement('button');
button.textContent = token.description.display_token || token.id;
button.onclick = () => {
startPaymentFlow(token.id);
};
document.getElementById('saved-cards-list').appendChild(button);
});
}
Initialize SecureFields for CVV input (client-side):
import { loadSecureFields } from '@purse-eu/web-sdk';
let secureForm;
async function initCvvOnlyForm() {
const sf = await loadSecureFields('sandbox');
secureForm = await sf.initSecureFields({
tenantId: '[your vault tenant_id]',
apiKey: '[your purse api_key (optional)]',
config: {
fields: {
cvv: {
target: 'cvv-only-placeholder',
placeholder: 'ex: 123',
ariaLabel: 'CVV',
iframeTitle: 'CVV',
}
},
styles: {
color: '#181818'
}
}
});
await secureForm.render();
}
HTML placeholder for CVV field:
<div>
<label id="cvv-only-label" class="block text-sm font-medium text-gray-700 mb-1">CVV</label>
<div id="cvv-only-placeholder" aria-labelledby="cvv-only-label" class="w-full rounded-lg border border-gray-300 bg-white px-2 py-2"></div>
</div>
Submit the CVV (client-side), then send the resulting vault_form_token to your server to create the payment:
async function startPaymentFlow(selectedWalletTokenId) {
await initCvvOnlyForm();
const result = await secureForm.submit({ saveToken: false });
await fetch('/your-server/pay-with-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
vaultFormToken: result.vault_form_token,
walletTokenId: selectedWalletTokenId,
}),
});
}
Your server then calls the Payment API:
curl -X POST 'https://api.purse-sandbox.com/payment/v2/payments' \
--header 'Content-Type: application/json' \
--header "x-api-key: ${API_KEY}" \
--header "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '{
"entity_id": "{{entity_id}}",
"amount": 100,
"currency": "EUR",
"split": [
{
"vault_form_token": "{{vault_form_token}}",
"wallet_token": "{{wallet_token_id}}"
}
],
"browser": { }
}'
- Endpoint:
/wallet/v3/wallet/merchants/{merchant_id}/customers/{customer_reference}/tokens — list tokens
- Endpoint:
/payment/v2/payments — create payment
- Method:
GET (list), POST (payment)
3. Delete a Saved Card
Customers may want to remove a saved card for privacy or security reasons. Your server calls the Wallet API to delete the token:
curl -X DELETE 'https://api.purse-sandbox.com/wallet/v3/wallet/merchants/{{merchant_id}}/customers/{{customer_reference}}/tokens/{{token_id}}' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ${ACCESS_TOKEN}"
- Endpoint:
/wallet/v3/wallet/merchants/{merchant_id}/customers/{customer_reference}/tokens/{id}
- Method:
DELETE
- API Reference
For more details about tokenization and payment tokens, see the Token Payment Glossary.
Next Steps
Full Working Example
The flow below illustrates a complete token payment integration: the client-side handles the UI and SecureFields, while all API calls are made server-side.
Client-side — Display saved cards and collect CVV:
import { loadSecureFields } from '@purse-eu/web-sdk';
let secureForm;
async function initCvvOnlyForm() {
const sf = await loadSecureFields('sandbox');
secureForm = await sf.initSecureFields({
tenantId: '[your vault tenant_id]',
apiKey: '[your purse api_key (optional)]',
config: {
fields: {
cvv: {
target: 'cvv-only-placeholder',
placeholder: 'ex: 123',
ariaLabel: 'CVV',
iframeTitle: 'CVV',
}
},
styles: { color: '#181818' }
}
});
await secureForm.render();
}
async function displaySavedCards() {
const response = await fetch('/your-server/tokens');
const tokens = await response.json();
tokens.forEach(token => {
const button = document.createElement('button');
button.textContent = token.description.display_token || token.id;
button.onclick = async () => {
await initCvvOnlyForm();
const result = await secureForm.submit({ saveToken: false });
await fetch('/your-server/pay-with-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
vaultFormToken: result.vault_form_token,
walletTokenId: token.id,
}),
});
};
document.getElementById('saved-cards-list').appendChild(button);
});
}
async function deleteToken(tokenId) {
await fetch(`/your-server/tokens/${tokenId}`, { method: 'DELETE' });
}
Server-side — Wallet and Payment API calls:
curl -X GET 'https://api.purse-sandbox.com/wallet/v3/wallet/merchants/{{merchant_id}}/customers/{{customer_reference}}/tokens' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ${ACCESS_TOKEN}"
curl -X POST 'https://api.purse-sandbox.com/payment/v2/payments' \
--header 'Content-Type: application/json' \
--header "x-api-key: ${API_KEY}" \
--header "Authorization: Bearer ${ACCESS_TOKEN}" \
--data-raw '{
"entity_id": "{{entity_id}}",
"amount": 100,
"currency": "EUR",
"split": [
{
"vault_form_token": "{{vault_form_token}}",
"wallet_token": "{{wallet_token_id}}"
}
],
"browser": { }
}'
curl -X DELETE 'https://api.purse-sandbox.com/wallet/v3/wallet/merchants/{{merchant_id}}/customers/{{customer_reference}}/tokens/{{token_id}}' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer ${ACCESS_TOKEN}"
HTML placeholders:
<div id="saved-cards-list"></div>
<div>
<label id="cvv-only-label" class="block text-sm font-medium text-gray-700 mb-1">CVV</label>
<div id="cvv-only-placeholder" aria-labelledby="cvv-only-label" class="w-full rounded-lg border border-gray-300 bg-white px-2 py-2"></div>
</div>