Introduction
Hosted Fields enables easy integration of a vault credit card payment method while maintaining full control over the form UI.
- Light Integration: Minimal setup required.
- Secure Payment: PCI-compliant with iframe-based field isolation.
- Customizable: Full control over layout, styles, and translations.
- Vault Credit Card Only: Specifically designed for vault credit card forms.
Prerequisites
Ensure you have:
- Basic JavaScript, HTML, and CSS knowledge.
- An active credit card payment method.
- A Purse API key.
- A client session.
Limitations
Quick Start Guide
Step 1: Load the Headless Checkout SDK
Add the Headless Checkout SDK to your checkout page:
<script type="module">
import * as Purse from "https://cdn.purse-sandbox.com/headless-checkout/latest/purse.esm.js";
</script>
ℹ️ The SDK is served from Purse's CDN to meet PCI-DSS requirements. Do not self-host this file.
Create container elements where the hosted fields will be rendered:
<form id="payment-form">
<div class="field">
<label>Card Number</label>
<div class="row">
<div id="card-number"></div>
<div id="brand-selector"></div>
</div>
</div>
<div class="field">
<label>Cardholder Name</label>
<div id="cardholder-name"></div>
</div>
<div class="row">
<div class="field">
<label>Expiry Date</label>
<div id="expiry-date"></div>
</div>
<div class="field">
<label>CVV</label>
<div id="cvv"></div>
</div>
</div>
<button type="submit" id="submit-btn">Pay Now</button>
<div id="error"></div>
</form>
Step 3: Initialize the Checkout
Use your client session token to create an instance of the checkout:
API V2 (recommended)
import * as Purse from "https://cdn.purse-sandbox.com/headless-checkout/latest/purse.esm.js";
const checkout = await Purse.createHeadlessCheckout(clientSession.widget.data);
API V1
import * as Purse from "https://cdn.purse-sandbox.com/headless-checkout/latest/purse.esm.js";
const checkout = await Purse.createHeadlessCheckout({
apiKey: "YOUR_API_KEY",
entityId: "YOUR_ENTITY_ID",
environment: "sandbox",
paymentSession: "YOUR_SESSION"
});
Step 4: Render Hosted Fields
Subscribe to available payment methods, get the hosted fields instance, and render them:
checkout.paymentMethods.subscribe((methods) => {
const creditCard = methods.find(m => m.method === "creditcard");
if (creditCard) {
const hostedFields = creditCard.getHostedFields({
fields: {
brandSelector: {
target: "brand-selector"
},
cardNumber: {
target: "card-number",
placeholder: "1234 5678 9012 3456"
},
holderName: {
target: "cardholder-name",
placeholder: "John Doe"
},
expDate: {
target: "expiry-date",
placeholder: "MM/YY"
},
cvv: {
target: "cvv",
placeholder: "123"
}
}
});
hostedFields.render();
}
});
💡 The render() method mounts the secure iframes to the specified target elements.
Step 5: Customize Field Appearance
You can customize the appearance of the hosted fields using the theme configuration:
const hostedFields = creditCard.getHostedFields({
fields: {
brandSelector: { target: "brand-selector" },
cardNumber: { target: "card-number", placeholder: "1234 5678 9012 3456" },
holderName: { target: "cardholder-name", placeholder: "John Doe" },
expDate: { target: "expiry-date", placeholder: "MM/YY" },
cvv: { target: "cvv", placeholder: "123" }
},
theme: {
input: {
fontSize: "16px",
color: "#111827",
backgroundColor: "#ffffff",
':focus': {
color: "#111827"
},
':invalid': {
color: "#dc2626"
}
}
}
});
hostedFields.render();
Available Theme Properties
The theme object supports the following scopes for Hosted Fields:
global: Applies to all fields (colors, fonts, spacing)
input: Styles for input fields (padding, borders, focus states)
Hosted Fields only support global and input theme options. For more extensive theming including labels and helper text, consider using Hosted Form.
Each scope supports CSS properties and pseudo-selectors like :focus, :hover, :invalid, etc.
See Theme references to get the full list of available keys.
Custom Fonts
For compliance reasons, please contact us before using custom fonts. You need to provide the font file to the Purse team.
To use custom fonts, include the font reference in the theme.global.fontSrc property:
const hostedFields = creditCard.getHostedFields({
fields: {
brandSelector: { target: "brand-selector" },
cardNumber: { target: "card-number", placeholder: "1234 5678 9012 3456" },
holderName: { target: "cardholder-name", placeholder: "John Doe" },
expDate: { target: "expiry-date", placeholder: "MM/YY" },
cvv: { target: "cvv", placeholder: "123" }
},
theme: {
global: {
fontSrc: 'custom-font-file',
fontFamily: 'MyFont, sans-serif',
fontSize: '16px'
},
input: {
fontSize: "16px",
color: "#111827"
}
}
});
The following Google Fonts are pre-approved and ready to use in the secure fields:
- Inter
- Lato
- Montserrat
- Noto
- Nunito
- Open Sans
- Raleway
- Roboto
- Work Sans
Just set the fontFamily property to the font name.
Handle the form submission and call checkout.submitPayment():
document.getElementById("payment-form").addEventListener("submit", async (e) => {
e.preventDefault();
const submitBtn = document.getElementById("submit-btn");
const errorDiv = document.getElementById("error");
submitBtn.disabled = true;
submitBtn.textContent = "Processing...";
errorDiv.style.display = "none";
try {
await checkout.submitPayment();
console.log("Payment successful!");
} catch (error) {
errorDiv.textContent = error.message || "Payment failed";
errorDiv.style.display = "block";
} finally {
submitBtn.disabled = false;
submitBtn.textContent = "Pay Now";
}
});
Error Handling
Possible errors:
- Expired session → Session has expired, create a new client session
- Invalid field configuration → Missing required field targets
- Payment method is not supported → The payment method is not compatible with Hosted Fields
- Technical errors → The payment method cannot be initialized
Complete Example
Here's a full working example ready to copy-paste:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hosted Fields - Payment Form</title>
<style>
body {
font-family: system-ui, sans-serif;
max-width: 500px;
margin: 50px auto;
padding: 20px;
}
.field {
margin-bottom: 16px;
}
label {
display: block;
margin-bottom: 4px;
font-weight: 500;
}
.input-container {
border: 1px solid #ccc;
border-radius: 4px;
padding: 10px;
height: 40px;
}
.row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
button {
width: 100%;
padding: 12px;
background: #0066cc;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.error {
color: #dc2626;
font-size: 14px;
margin-top: 8px;
display: none;
}
</style>
</head>
<body>
<h1>Payment Form</h1>
<form id="payment-form">
<div class="field">
<label>Card Number</label>
<div class="row">
<div id="card-number" class="input-container"></div>
<div id="brand-selector"></div>
</div>
</div>
<div class="field">
<label>Cardholder Name</label>
<div id="cardholder-name" class="input-container"></div>
</div>
<div class="row">
<div class="field">
<label>Expiry Date</label>
<div id="expiry-date" class="input-container"></div>
</div>
<div class="field">
<label>CVV</label>
<div id="cvv" class="input-container"></div>
</div>
</div>
<button type="submit" id="submit-btn">Pay Now</button>
<div id="error" class="error"></div>
</form>
<script type="module">
import * as Purse from "https://cdn.purse-sandbox.com/headless-checkout/latest/purse.esm.js";
const checkout = await Purse.createHeadlessCheckout("client-session-token");
checkout.paymentMethods.subscribe((methods) => {
const creditCard = methods.find(m => m.method === "creditcard");
if (creditCard) {
const hostedFields = creditCard.getHostedFields({
fields: {
brandSelector: { target: "brand-selector" },
cardNumber: { target: "card-number", placeholder: "1234 5678 9012 3456" },
holderName: { target: "cardholder-name", placeholder: "John Doe" },
expDate: { target: "expiry-date", placeholder: "MM/YY" },
cvv: { target: "cvv", placeholder: "123" }
},
theme: {
input: {
fontSize: "16px",
color: "#111827",
':focus': {
color: "#111827"
},
':invalid': {
color: "#dc2626"
}
}
}
});
hostedFields.render();
}
});
document.getElementById("payment-form").addEventListener("submit", async (e) => {
e.preventDefault();
const submitBtn = document.getElementById("submit-btn");
const errorDiv = document.getElementById("error");
submitBtn.disabled = true;
submitBtn.textContent = "Processing...";
errorDiv.style.display = "none";
try {
await checkout.submitPayment();
console.log("Payment successful!");
} catch (error) {
errorDiv.textContent = error.message || "Payment failed";
errorDiv.style.display = "block";
} finally {
submitBtn.disabled = false;
submitBtn.textContent = "Pay Now";
}
});
</script>
</body>
</html>
Summary
Hosted Fields is ideal when you want:
- Full UI control for vault credit card forms
- PCI-compliant field handling without managing sensitive data
- Seamless integration into your existing checkout page
- Customizable styling to match your brand
For other payment methods or a simpler integration, consider using the Hosted Form or Headless Checkout SDK directly.