<div id="fields"></div>
<div id="error-block"></div>
body {
max-width: 768px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
background-color: #f8fafc;
}
#payment-button {
width: 100%;
background-color: #4caf50;
border: none;
color: white;
padding: 16px;
font-size: 16px;
font-weight: bold;
border-radius: 4px;
cursor: pointer;
margin-top: 20px;
transition: background-color 0.3s ease;
}
#payment-button:hover {
background-color: #45a049;
}
#error-block {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
padding: 15px;
border-radius: 4px;
margin-top: 20px;
display: none;
}
.error-item:not(:only-child) {
margin-bottom: 12px;
}
.error-title {
font-weight: bold;
margin-bottom: 5px;
}
.error-description {
font-size: 14px;
}
// -------------------------------
// Hosted Fields Callback Functions
// -------------------------------
function clientReadyCallback(event) {
console.log("Hosted fields client is ready:", event);
}
function invalidSessionCallback(event) {
console.error("Invalid session detected:", event);
displayError("Invalid session detected", event);
}
function challengeIssuedCallback(event) {
console.log("A challenge has been issued:", event);
}
function transactionAcceptedCallback(event) {
onTransactionComplete(true);
}
function transactionDeclinedCallback(event) {
onTransactionComplete(false);
}
function clientRedirectCallback(event) {
return false; // Skipping redirects in Codepen.io demo
}
function invalidInputCallback(event) {
console.error("Invalid input encountered:", event);
displayError("Invalid input encountered", event);
}
function inputValidityCallback(event) {
console.log("Input validity changed:", event);
}
function inputSubmitCallback(event) {
console.log("Input submitted:", event);
}
function sessionExpiredCallback(event) {
console.error("Session has expired:", event);
displayError("Session has expired", event);
}
function errorCallback(event) {
console.error("An error occurred in hosted fields:", event);
displayError("An error occurred in hosted fields", event);
}
// -------------------------------
// Helper: Display Errors
// -------------------------------
/**
* Appends a formatted error message to the error block.
*
* @param {string} description - A human-readable error description.
* @param {object} event - The event object containing error details.
*/
function displayError(description, event) {
const errorBlock = document.getElementById("error-block");
// Make the error block visible
errorBlock.style.display = "block";
// Build title from event type and errorCode if available
let title = "";
if (event && event.type) {
title += event.type;
}
if (event && event.errorCode) {
title += title ? " - " + event.errorCode : event.errorCode;
}
if (!title) {
title = "Error";
}
// Append the error's event field (event.event)
if (event && event.event) {
title += ` (Event: ${event.event})`;
} else {
title += " (Event: N/A)";
}
// Create the error item elements
const errorItem = document.createElement("div");
errorItem.classList.add("error-item");
const titleEl = document.createElement("div");
titleEl.classList.add("error-title");
titleEl.textContent = title;
const descriptionEl = document.createElement("div");
descriptionEl.classList.add("error-description");
descriptionEl.textContent =
description ||
(event && event.message ? event.message : "No further details provided.");
errorItem.appendChild(titleEl);
errorItem.appendChild(descriptionEl);
errorBlock.appendChild(errorItem);
}
// -------------------------------
// Payment Request Setup
// -------------------------------
// Create headers for the payment request
const headers = new Headers();
const demoApiKey = "test_93dc98ae-ff87-4671-a821-cda33fdfc26e"; // Demo API key: NEVER use this client-side in production
headers.append("Content-Type", "application/json");
headers.append("Accept", "application/json");
headers.append("Authorization", `Bearer ${demoApiKey}`);
// Define the payment payload data
const paymentData = {
pointOfSaleId: "0195a362-5bf4-7bb9-a419-2367fda11e6f",
amount: 100, // minor amount
currency: "DKK",
scaMode: "SKIP"
};
// Setup the fetch options
const requestOptions = {
method: "POST",
headers: headers,
body: JSON.stringify(paymentData),
redirect: "follow"
};
// -------------------------------
// Helper: Dynamically Load External Module
// -------------------------------
/**
* Dynamically loads an external script module.
*
* @param {string} scriptUrl - The URL of the external module.
* @param {Function} onLoadCallback - A callback function to run once the module loads.
*/
function loadExternalModule(scriptUrl, onLoadCallback) {
const scriptEl = document.createElement("script");
scriptEl.type = "module";
scriptEl.src = scriptUrl;
scriptEl.onload = onLoadCallback;
document.body.appendChild(scriptEl);
console.log("Dynamically loading external script:", scriptUrl);
}
// -------------------------------
// Helper: Create Payment Button
// -------------------------------
/**
* Creates and appends a payment button to the DOM.
* The button text is set dynamically using the converted amount (major) and currency.
*
* @param {number} amount - The payment amount (in minor units).
* @param {string} currency - The currency code.
*/
function createPaymentButton(amount, currency) {
// Convert minor amount to major amount (e.g., 100 -> 1.00)
const majorAmount = (amount / 100).toFixed(2);
const button = document.createElement("button");
button.type = "button";
button.id = "payment-button";
button.textContent = `Pay ${majorAmount} ${currency}`;
button.addEventListener("click", () => {
epay.createCardTransaction();
});
// Append the button right after the hosted fields container
const fieldsContainer = document.getElementById("fields");
fieldsContainer.insertAdjacentElement("afterend", button);
}
// -------------------------------
// Helper: Handle transaction completed
// -------------------------------
/**
* Function to handle the display after a transaction is completed.
*
* @param {boolean} success - Indicates whether the transaction was successful (true) or not (false).
*/
function onTransactionComplete(success) {
// Find the hosted fields container
const fieldsContainer = document.getElementById("fields");
const paymentButton = document.getElementById("payment-button");
// Hide fields and payment button
fieldsContainer.style.display = "none";
paymentButton.style.display = "none";
// Create a new container for the transaction message
const messageContainer = document.createElement("div");
messageContainer.id = "transaction-message";
messageContainer.style.marginTop = "20px";
// Insert the appropriate message based on the transaction result
if (success) {
messageContainer.innerHTML = `
<h2>✅ Payment Success</h2>
<p>Your transaction has been completed successfully.</p>
`;
} else {
messageContainer.innerHTML = `
<h2>❌ Payment Failed</h2>
<p>We're sorry, but your transaction could not be completed.</p>
`;
}
// Append the message container to the DOM
if (fieldsContainer && fieldsContainer.parentNode) {
fieldsContainer.parentNode.appendChild(messageContainer);
} else {
document.body.appendChild(messageContainer);
}
}
// -------------------------------
// Payment Request & Hosted Fields Initialization
// -------------------------------
fetch("https://payments.epay.eu/public/api/v1/cit", requestOptions)
.then((response) => response.json())
.then((result) => {
console.log("Payment response:", result);
// Extract session and payment details from the response
const sessionId = result.session.id;
const sessionKey = result.key;
const amount = result.session.amount;
const currency = result.session.currency;
// Dynamically load the external payment module using the URL from the response
loadExternalModule(result.javascript, () => {
// Initialize hosted fields with session data and callbacks
epay
.setSessionId(sessionId)
.setSessionKey(sessionKey)
.setCallbacks({
clientReady: clientReadyCallback,
invalidSession: invalidSessionCallback,
challengeIssued: challengeIssuedCallback,
transactionAccepted: transactionAcceptedCallback,
transactionDeclined: transactionDeclinedCallback,
clientRedirect: clientRedirectCallback,
invalidInput: invalidInputCallback,
inputValidity: inputValidityCallback,
inputSubmit: inputSubmitCallback,
sessionExpired: sessionExpiredCallback,
error: errorCallback
})
.init();
// Mount the hosted fields into the container with id "fields"
epay.mountFields("fields", {
// 💡 The configuration object. See https://docs.epay.eu/js for more info.
});
// Create the payment button using dynamic payment details
createPaymentButton(amount, currency);
});
})
.catch((error) => {
console.error("Error during payment request:", error);
displayError("Error during payment request", error);
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.