Embedded Fields (EF)¶
Introduction¶
This guide covers how to integrate Embedded Fields (EF) into your merchant checkout page. Embedded Fields allow you to embed secure payment form fields directly into your website while maintaining PCI compliance.
This guide includes:
- Backend and Frontend Integration
- Customization Options
- Handling Callbacks
- Field Configuration
- Ensuring PCI Compliance
What are Embedded Fields?¶
Embedded Fields are a method of integrating payment forms directly into a merchant's website or application, rather than redirecting users to an external payment page. This provides a seamless checkout experience while keeping sensitive card data secure.
Pre-Integration Requirements¶
Before integrating Embedded Fields, ensure you have the following:
| Requirement | Description | Where to Obtain |
|---|---|---|
| Client ID & Secret | OIDC credentials for authentication | Merchant Portal (Merchant/API details) |
| x-client-key | Public key identifying the merchant (used in frontend) | Merchant Portal |
For environment-specific URLs and configuration values, see Environments.
Backend Integration¶
The backend integration involves two steps: obtaining an authentication token and creating a payment session.
Step 1: Obtain an Access Token¶
Authenticate using the OIDC Client Credentials flow to obtain an access token.
UAT Endpoint:
POST https://uat-auth.finrelay.io/realms/uat/protocol/openid-connect/token
Production Endpoint:
POST https://auth.finrelay.io/realms/production/protocol/openid-connect/token
Request Headers:
Content-Type: application/x-www-form-urlencoded
Request Body:
grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 300,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"scope": "email profile"
}
For more details on authentication, see Authentication.
Step 2: Create a Payment Session¶
Once authenticated, create a payment session by calling the sessions endpoint.
UAT Endpoint:
POST https://uat-api.finrelay.io/api/sessions
Production Endpoint:
POST https://api.finrelay.io/api/sessions
Request Headers:
Content-Type: application/json
Accept: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN
Request Body:
{
"terminal_id": "TERM001",
"reference": "ORDER-123456",
"description": "Order payment",
"currency": "EUR",
"amount": 25600,
"transaction_type": "AUTHORIZE",
"success_url": "https://merchant.example.com/success",
"cancel_url": "https://merchant.example.com/cancel",
"customer": {
"first_name": "John",
"last_name": "Doe",
"address": "123 Main Street",
"city": "Sampletown",
"country": "BA",
"postal_code": "10000",
"email": "john.doe@example.com",
"phone": "+1234567890"
},
"country": "BA"
}
Response:
{
"id": "tcNyatfpvOjxxIDEdcYH",
"terminal_id": "TERM001",
"reference": "ORDER-123456",
"description": "Order payment",
"currency": "EUR",
"amount": 25600,
"success_url": "https://merchant.example.com/success",
"cancel_url": "https://merchant.example.com/cancel",
"customer": {
"first_name": "John",
"last_name": "Doe",
"address": "123 Main Street",
"city": "Sampletown",
"country": "BA",
"postal_code": "10000",
"email": "john.doe@example.com",
"phone": "+1234567890"
},
"country": "BA",
"status": "CREATED",
"transaction_type": "AUTHORIZE"
}
The id field in the response is your session_id — use this to initialize the Embedded Fields on the frontend.
Session Request Parameters¶
| Field | Type | Required | Description |
|---|---|---|---|
terminal_id |
string | Yes | Terminal identifier |
reference |
string(40) | Yes | Unique order reference for the terminal |
description |
string | Yes | Order description |
currency |
string(3) | Yes | ISO 4217 currency code (e.g., "EUR") |
amount |
integer | Yes | Amount in minor units (e.g., 25600 = €256.00) |
transaction_type |
string | Yes | "AUTHORIZE" or "PURCHASE" |
success_url |
string | Yes | Redirect URL after successful payment |
cancel_url |
string | Yes | Redirect URL after cancellation or failure |
customer |
object | No | Customer details (pre-populated if provided) |
country |
string(2) | No | ISO 3166-1 alpha-2 country code |
Frontend Integration¶
To integrate Embedded Fields into your frontend, you need the session_id from the backend and your client_key from the merchant portal.
Step 1: Load the Embedded Fields Script¶
UAT:
<script src="https://uat-api.finrelay.io/embedded-fields.js"></script>
Production:
<script src="https://api.finrelay.io/embedded-fields.js"></script>
Step 2: Initialize the Payment Component¶
const initCheckout = async (sessionId, clientKey) => {
// Initialize the payment component
const paymentComponent = await window.PaymentComponent({
locale: 'en-US',
environment: 'test', // Use 'production' for live transactions
clientKey: clientKey,
sessionId: sessionId
});
// Create the card payment component
const cardPaymentComponent = paymentComponent.create('card');
// Mount it to a DOM element
cardPaymentComponent.mount('#card-wrapper');
return cardPaymentComponent;
};
Step 3: Submit the Payment¶
function submitPayment(cardPaymentComponent) {
const isValid = cardPaymentComponent.validate();
if (isValid) {
const customer = cardPaymentComponent.getCustomer();
cardPaymentComponent.submitPayment({
customer: {
...customer,
firstName: 'John',
lastName: 'Doe',
address: '123 Main Street',
city: 'Sampletown',
zip: '10000',
country: 'BA',
phone: '+1234567890',
email: 'john.doe@example.com'
},
callback: (response, error) => {
if (response?.approved) {
// Payment successful
console.log('Payment approved:', response);
} else {
// Payment failed
console.log('Payment failed:', response);
}
if (error) {
console.error('Payment error:', error);
}
}
});
}
}
Complete Example¶
<!DOCTYPE html>
<html>
<head>
<title>Checkout</title>
</head>
<body>
<div id="card-wrapper"></div>
<button id="pay-button">Pay Now</button>
<script src="https://uat-api.finrelay.io/embedded-fields.js"></script>
<script>
let cardPaymentComponent;
async function init() {
const sessionId = 'YOUR_SESSION_ID'; // From backend
const clientKey = 'YOUR_CLIENT_KEY'; // From merchant portal
const paymentComponent = await window.PaymentComponent({
locale: 'en-US',
environment: 'test',
clientKey: clientKey,
sessionId: sessionId
});
cardPaymentComponent = paymentComponent.create('card');
cardPaymentComponent.mount('#card-wrapper');
}
document.getElementById('pay-button').addEventListener('click', () => {
if (cardPaymentComponent.validate()) {
cardPaymentComponent.submitPayment({
callback: (response, error) => {
if (response?.approved) {
window.location.href = '/success';
} else {
alert('Payment failed');
}
}
});
}
});
init();
</script>
</body>
</html>
Field Configuration¶
The Embedded Fields component provides granular control over which fields are displayed and their validation requirements.
Card Payment Fields¶
These fields are always displayed and cannot be hidden:
| Field | Description | Configurable | Default State |
|---|---|---|---|
| Card Number | The 16-digit card number | No | Always visible, required |
| Expiry Date | Card expiration date (MM/YY) | No | Always visible, required |
| CVV/CVC | 3 or 4 digit security code | No | Always visible, required |
Customer Information Fields¶
These fields can be shown, hidden, or made required through the customerConfig object:
| Field | Show/Hide Property | Required Property | Default Visibility | Default Required |
|---|---|---|---|---|
| Cardholder Name | hasHolderName |
holderNameRequired |
Hidden | No |
| Cardholder Email | hasHolderEmail |
holderEmailRequired |
Hidden | No |
Configuration Reference¶
const componentConfig = {
componentConfig: {
customerConfig: {
// Cardholder Name Configuration
hasHolderName: true, // Show the cardholder name field
holderNameRequired: true, // Make the name field required
// Cardholder Email Configuration
hasHolderEmail: true, // Show the cardholder email field
holderEmailRequired: false // Email is optional
}
},
componentListeners: {
onChange: (e) => {
console.log('Field changed:', e);
},
onError: (e) => {
console.error('Error:', e);
}
}
};
const cardPaymentComponent = paymentComponent.create('card', componentConfig);
Configuration Options¶
hasHolderName (boolean)¶
Controls whether the cardholder name input field is displayed.
true: The name field is displayedfalse(default): The name field is hidden
holderNameRequired (boolean)¶
When hasHolderName is true, controls whether the field is required.
true: The name field must be filled before submittingfalse(default): The name field is optional
Note
Setting holderNameRequired: true without hasHolderName: true has no effect since the field is not visible.
hasHolderEmail (boolean)¶
Controls whether the cardholder email input field is displayed.
true: The email field is displayedfalse(default): The email field is hidden
holderEmailRequired (boolean)¶
When hasHolderEmail is true, controls whether the field is required.
true: The email field must be filled with a valid emailfalse(default): The email field is optional
Note
Setting holderEmailRequired: true without hasHolderEmail: true has no effect since the field is not visible.
Common Configuration Patterns¶
const componentConfig = {
componentConfig: {
customerConfig: {
// All customer fields hidden by default
}
}
};
const componentConfig = {
componentConfig: {
customerConfig: {
hasHolderName: true,
holderNameRequired: true
}
}
};
const componentConfig = {
componentConfig: {
customerConfig: {
hasHolderEmail: true,
holderEmailRequired: false
}
}
};
const componentConfig = {
componentConfig: {
customerConfig: {
hasHolderName: true,
holderNameRequired: true,
hasHolderEmail: true,
holderEmailRequired: true
}
}
};
Providing Customer Data at Submission¶
You can provide or override customer information when submitting the payment:
cardPaymentComponent.submitPayment({
customer: {
firstName: 'John',
lastName: 'Doe',
address: '123 Main Street',
city: 'Sampletown',
zip: '10000',
country: 'US',
phone: '+1-555-123-4567',
email: 'john.doe@example.com'
},
callback: (response, error) => {
// Handle response
}
});
| Customer Field | Description |
|---|---|
firstName |
Customer's first name |
lastName |
Customer's last name |
address |
Street address |
city |
City name |
zip |
Postal/ZIP code |
country |
ISO 3166-1 alpha-2 country code (e.g., 'US', 'GB', 'DE') |
phone |
Phone number |
email |
Email address |
Tip
Use cardPaymentComponent.getCustomer() to retrieve any customer data entered in the embedded fields, then spread it with your own data: { ...customer, firstName: 'Override' }.
Event Handling¶
Available Callbacks¶
| Callback | Description |
|---|---|
onChange |
Triggered when any field value changes |
onError |
Triggered when a validation or processing error occurs |
Example with Event Listeners¶
const componentConfig = {
componentConfig: {
customerConfig: {
hasHolderEmail: true,
holderEmailRequired: false
}
},
componentListeners: {
onChange: (event) => {
console.log('Field changed:', event);
// Use this to track form completion progress
},
onError: (error) => {
console.error('Error occurred:', error);
// Display error to user or log for debugging
}
}
};
const cardPaymentComponent = paymentComponent.create('card', componentConfig);
Validation¶
Use the validate() method to check if all required fields are filled correctly:
const isValid = cardPaymentComponent.validate();
// For detailed validation information:
const validationObject = cardPaymentComponent.getDataWithValidation();
// Returns which fields are invalid and their error messages
Customization¶
Embedded Fields can be customized to match your website's branding by overriding CSS variables.
CSS Variables¶
embedded-fields-card, #card-wrapper {
--lib-font-family: var(--app-font-family);
--lib-input-font-size: 1.7rem;
--lib-accent-color: #556a61;
--lib-accent-background-color: #DFE8E1;
--lib-label-font-size: 1rem;
--lib-warning-color: #ff8c70;
--lib-warning-background-color: #fff6ee;
--lib-input-color: #556a61;
--lib-input-focus-border-color: #86a599;
--lib-input-border-color: #d4d7d6;
--lib-input-focus-color: #179261;
--lib-place-holder-color: #e1e1e1;
--lib-input-border-radius: 0.5rem;
--lib-button-border-radius: 0.7rem;
--lib-button-font-size: 1.1rem;
}
CSS Selectors¶
For more granular control, use CSS selectors to target specific elements:
embedded-fields-card div.input-container.lib-input-root div.lib-input-wrapper input {
font-family: var(--app-font-family);
}
lib-input .lib-input-wrapper,
lib-iframe-input div.iframe-input-wrapper {
border-style: initial !important;
border-bottom: 1px solid var(--lib-input-border-color) !important;
border-radius: initial !important;
}
PCI Compliance¶
Payment Card Industry Data Security Standard (PCI DSS) compliance is essential when handling card data.
Why Embedded Fields Help with PCI Compliance¶
Embedded Fields are designed to minimize your PCI scope:
- Sensitive card data never touches your servers — card numbers, CVV, and expiration dates are captured directly by the secure iframe
- Tokenization — actual card data is replaced with secure tokens
- Encryption — all data transmission is encrypted
Merchant Levels¶
| Level | Transaction Volume | Validation Method |
|---|---|---|
| Level 1 | Over 6 million annually | External audit by QSA |
| Level 2 | 1-6 million annually | SAQ + external scan |
| Level 3 | 20,000-1 million online | SAQ + external scan |
| Level 4 | Under 20,000 online | SAQ |
Consequences of Non-Compliance¶
- Fines from payment networks
- Loss of payment processing privileges
- Legal action by regulatory bodies
- Reputation damage
- Increased risk of data breaches
Troubleshooting¶
Common Issues¶
Unable to Load Embedded Fields¶
- Verify the script URL is correct for your environment
- Check that your
client_keyis valid - Ensure the
session_idhas not expired
Unable to Create a Session¶
- Verify the
Authorizationheader is correct - Check that your access token has not expired
- Review the request payload for missing or invalid values
Payment Submission Fails¶
- Use
cardPaymentComponent.validate()to check for validation errors - Check
cardPaymentComponent.getDataWithValidation()for detailed error information - Review the
onErrorcallback for error details
Debugging Tips¶
Use the callback functions to monitor the payment flow:
const componentConfig = {
componentListeners: {
onChange: (e) => console.log('Change:', e),
onError: (e) => console.error('Error:', e)
}
};
Best Practices¶
-
Obtain Required Parameters First — Retrieve
sessionIdfrom your backend andclientKeyfrom the merchant portal before initializing -
Load Script Asynchronously — Load the embedded-fields.js script asynchronously to avoid blocking page rendering
-
Handle All Callbacks — Implement both
onChangeandonErrorcallbacks for better debugging and user experience -
Validate Before Submission — Always call
validate()beforesubmitPayment()to catch errors early -
Provide Clear Error Messages — Use validation results to show users exactly what needs to be corrected
-
Test in UAT First — Always test your integration in the UAT environment before going to production
FAQ¶
What is the difference between Embedded Fields and Hosted Payment Page?¶
Embedded Fields integrate payment forms directly into your website, giving you full control over the UI/UX while maintaining PCI compliance.
Hosted Payment Page (HPP) redirects customers to a hosted checkout page. See the Hosted Payment Page documentation for details.
How do I obtain the necessary API keys?¶
All credentials (Client ID, Client Secret, x-client-key) can be obtained from the Merchant Portal under Merchant/API details.
Can I customize the appearance of Embedded Fields?¶
Yes, you can customize the appearance using CSS variables and selectors. See the Customization section.
What currencies are supported?¶
The supported currencies depend on your merchant configuration. Contact your account manager for details.
How do I test my integration?¶
Use the UAT environment for testing. Set environment: 'test' when initializing the PaymentComponent.