Hosted Fields
Hosted fields let you collect card data directly in your web page without the card number ever touching your servers. Each sensitive field (card number, expiry, CVV) runs inside a separate iframe hosted on the Gatelithix PCI-zone domain.
This approach gives you full control over your checkout design while maintaining PCI DSS compliance. The card data is tokenized inside the iframe and only the resulting token is returned to your page.
Installation
Script Tag (CDN)
<script src="https://js.gatelithix.com/v1/hosted-fields.js"></script>npm
npm install @gatelithix/hosted-fieldsimport { HostedFields } from "@gatelithix/hosted-fields";Quick Start
1. Add Container Elements
Add empty elements where you want the card fields to appear:
<form id="payment-form">
<div>
<label>Card Number</label>
<div id="card-number"></div>
</div>
<div>
<label>Expiration</label>
<div id="card-expiry"></div>
</div>
<div>
<label>CVV</label>
<div id="card-cvv"></div>
</div>
<button type="submit" id="pay-btn">Pay</button>
</form>2. Create Hosted Fields
Initialize the hosted fields instance with your publishable key:
const fields = Gatelithix.HostedFields.create({
publishableKey: "pk_test_your_publishable_key",
fields: {
cardNumber: { container: "#card-number" },
expiry: { container: "#card-expiry" },
cvv: { container: "#card-cvv" },
},
styles: {
base: {
color: "#333",
fontSize: "16px",
fontFamily: "Inter, sans-serif",
"::placeholder": { color: "#aab7c4" },
},
invalid: { color: "#dc3545" },
},
});3. Handle Events
Listen for field state changes to provide real-time feedback:
fields.on("change", (event) => {
console.log(event.fieldType); // "cardNumber" | "expiry" | "cvv"
console.log(event.complete); // true when field is valid and complete
console.log(event.empty); // true when field is empty
console.log(event.error); // validation error message or null
console.log(event.brand); // card brand (cardNumber only): "visa", "mastercard", etc.
});
fields.on("focus", (event) => {
// Field received focus
});
fields.on("blur", (event) => {
// Field lost focus
});
fields.on("error", (event) => {
console.error("Field error:", event.error);
});4. Tokenize on Submit
When the user submits the form, call tokenize() to exchange the card data for a gateway token:
document.getElementById("payment-form").addEventListener("submit", async (e) => {
e.preventDefault();
const result = await fields.tokenize();
if (result.error) {
// Display error to user
console.error(result.error);
return;
}
// Send the token to your server
console.log("Token:", result.token);
console.log("Last 4:", result.last4);
console.log("Brand:", result.brand);
console.log("Exp:", result.expMonth + "/" + result.expYear);
// POST the token to your server to create a payment
await fetch("/api/charge", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ gateway_token: result.token }),
});
});Style Configuration
Pass a styles object when creating hosted fields. Styles are applied inside each iframe.
Style States
| State | When Applied |
|---|---|
base | Default styles for the input. |
focus | When the input has focus. |
invalid | When the input contains invalid data. |
complete | When the input is valid and complete. |
Allowed CSS Properties
For security, only the following CSS properties are allowed:
colorfontSizefontFamilyfontWeightfontStyleletterSpacingtextAlignlineHeightpadding::placeholder(supportscoloronly)
Properties containing url(), expression(), javascript:, or data: URIs are rejected.
Example
const styles = {
base: {
color: "#333",
fontSize: "16px",
fontFamily: "'Inter', system-ui, sans-serif",
fontWeight: "400",
lineHeight: "1.5",
padding: "12px",
"::placeholder": { color: "#9ca3af" },
},
focus: {
color: "#111",
},
invalid: {
color: "#dc3545",
},
complete: {
color: "#059669",
},
};Security
- PCI isolation: Each field runs in a separate iframe loaded from the Gatelithix PCI-zone domain. Card data never leaves the iframe.
- Origin validation: Iframes validate the parent page origin via
postMessageto prevent cross-origin attacks. - Style sanitization: Only allowlisted CSS properties are accepted. Values containing
url()or script injections are rejected. - Publishable keys only: Use
pk_test_orpk_live_keys. Never use secret keys in browser code. - Direct vault submission: The iframe submits card data directly to the Gatelithix vault service. The card number is never sent via
postMessage.
Full Integration Example
<!DOCTYPE html>
<html>
<head>
<title>Checkout</title>
<script src="https://js.gatelithix.com/v1/hosted-fields.js"></script>
<style>
.field-container {
border: 1px solid #e5e7eb;
border-radius: 6px;
padding: 0;
height: 44px;
margin-bottom: 12px;
}
.field-container.focused { border-color: #0066FF; }
.field-container.invalid { border-color: #dc3545; }
</style>
</head>
<body>
<form id="payment-form">
<div id="card-number" class="field-container"></div>
<div id="card-expiry" class="field-container"></div>
<div id="card-cvv" class="field-container"></div>
<button type="submit">Pay $50.00</button>
<div id="error-message"></div>
</form>
<script>
const fields = Gatelithix.HostedFields.create({
publishableKey: "pk_test_your_publishable_key",
fields: {
cardNumber: { container: "#card-number" },
expiry: { container: "#card-expiry" },
cvv: { container: "#card-cvv" },
},
styles: {
base: { fontSize: "16px", color: "#333" },
invalid: { color: "#dc3545" },
},
});
fields.on("focus", (e) => {
document.querySelector(`#card-${e.fieldType === "cardNumber" ? "number" : e.fieldType}`)
.classList.add("focused");
});
fields.on("blur", (e) => {
document.querySelector(`#card-${e.fieldType === "cardNumber" ? "number" : e.fieldType}`)
.classList.remove("focused");
});
fields.on("change", (e) => {
const el = document.querySelector(`#card-${e.fieldType === "cardNumber" ? "number" : e.fieldType}`);
el.classList.toggle("invalid", !!e.error);
});
document.getElementById("payment-form").addEventListener("submit", async (e) => {
e.preventDefault();
const result = await fields.tokenize();
if (result.error) {
document.getElementById("error-message").textContent = result.error;
return;
}
// Send result.token to your server
alert("Token: " + result.token);
});
</script>
</body>
</html>