<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);
  });

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.