feat(payment-link): emit intent status to parent before rendering payment link UI (#7531)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Kashif
2025-03-19 15:55:27 +05:30
committed by GitHub
parent 10371af561
commit 6f35899b6d
6 changed files with 118 additions and 21 deletions

View File

@ -7944,6 +7944,7 @@ pub struct PaymentLinkDetails {
pub background_colour: Option<String>,
pub sdk_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
pub payment_link_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
pub status: api_enums::IntentStatus,
pub enable_button_only_on_form_ready: bool,
}

View File

@ -191,7 +191,7 @@ pub async fn form_payment_link_data(
let merchant_name = capitalize_first_char(&payment_link_config.seller_name);
let payment_link_status = check_payment_link_status(session_expiry);
let is_terminal_state = check_payment_link_invalid_conditions(
let is_payment_link_terminal_state = check_payment_link_invalid_conditions(
payment_intent.status,
&[
storage_enums::IntentStatus::Cancelled,
@ -201,9 +201,11 @@ pub async fn form_payment_link_data(
storage_enums::IntentStatus::RequiresMerchantAction,
storage_enums::IntentStatus::Succeeded,
storage_enums::IntentStatus::PartiallyCaptured,
storage_enums::IntentStatus::RequiresCustomerAction,
],
);
if is_terminal_state || payment_link_status == api_models::payments::PaymentLinkStatus::Expired
if is_payment_link_terminal_state
|| payment_link_status == api_models::payments::PaymentLinkStatus::Expired
{
let status = match payment_link_status {
api_models::payments::PaymentLinkStatus::Active => {
@ -211,7 +213,7 @@ pub async fn form_payment_link_data(
PaymentLinkStatusWrap::IntentStatus(payment_intent.status)
}
api_models::payments::PaymentLinkStatus::Expired => {
if is_terminal_state {
if is_payment_link_terminal_state {
logger::info!("displaying status page as the requested payment link has reached terminal state with payment status as {:?}", payment_intent.status);
PaymentLinkStatusWrap::IntentStatus(payment_intent.status)
} else {
@ -292,6 +294,7 @@ pub async fn form_payment_link_data(
payment_button_text_colour: payment_link_config.payment_button_text_colour.clone(),
sdk_ui_rules: payment_link_config.sdk_ui_rules.clone(),
payment_link_ui_rules: payment_link_config.payment_link_ui_rules.clone(),
status: payment_intent.status,
enable_button_only_on_form_ready: payment_link_config.enable_button_only_on_form_ready,
};

View File

@ -181,6 +181,30 @@ var hyper = null;
const translations = getTranslations(window.__PAYMENT_DETAILS.locale);
var isFramed = false;
try {
isFramed = window.parent.location !== window.location;
// If parent's window object is restricted, DOMException is
// thrown which concludes that the webpage is iframed
} catch (err) {
isFramed = true;
}
/**
* Trigger - on boot
* Use - emit latest payment status to parent window
*/
function emitPaymentStatus(paymentDetails) {
var message = {
payment: {
status: paymentDetails.status,
}
};
window.parent.postMessage(message, "*");
}
/**
* Trigger - init function invoked once the script tag is loaded
* Use
@ -190,13 +214,16 @@ const translations = getTranslations(window.__PAYMENT_DETAILS.locale);
* - Initialize event listeners for updating UI on screen size changes
* - Initialize SDK
**/
function boot() {
// @ts-ignore
var paymentDetails = window.__PAYMENT_DETAILS;
// Emit latest payment status
if (isFramed) {
emitPaymentStatus(paymentDetails);
}
if (paymentDetails.display_sdk_only) {
hide(".checkout-page")
var sdkDisplayWidth = document.querySelector('.hyper-checkout-sdk');

View File

@ -137,6 +137,10 @@ body > div {
font-size: 13px;
}
.hidden {
display: none;
}
.ellipsis-container-2 {
height: 2.5em;
overflow: hidden;

View File

@ -16,7 +16,7 @@
{{ rendered_js }}
</script>
</head>
<body onload="boot()">
<body onload="boot()" class="hidden">
<div>
<div class="hyper-checkout-status-wrap">
<div id="hyper-checkout-status-header"></div>

View File

@ -52,8 +52,8 @@ function invertToBW(color, bw, asArr) {
? hexToRgbArray(options.black)
: options.black
: asArr
? hexToRgbArray(options.white)
: options.white;
? hexToRgbArray(options.white)
: options.white;
}
function invert(color, bw) {
if (bw === void 0) {
@ -87,6 +87,31 @@ window.state = {
};
const translations = getTranslations(window.__PAYMENT_DETAILS.locale);
var isFramed = false;
try {
isFramed = window.parent.location !== window.location;
// If parent's window object is restricted, DOMException is
// thrown which concludes that the webpage is iframed
} catch (err) {
isFramed = true;
}
/**
* Trigger - on boot
* Use - emit latest payment status to parent window
*/
function emitPaymentStatus(paymentDetails) {
var message = {
payment: {
status: paymentDetails.status,
}
};
window.parent.postMessage(message, "*");
}
/**
* Trigger - init function invoked once the script tag is loaded
* Use
@ -100,20 +125,43 @@ function boot() {
// @ts-ignore
var paymentDetails = window.__PAYMENT_DETAILS;
// Attach document icon
if (paymentDetails.merchant_logo) {
var link = document.createElement("link");
link.rel = "icon";
link.href = paymentDetails.merchant_logo;
link.type = "image/x-icon";
document.head.appendChild(link);
// Emit latest payment status
if (isFramed) {
emitPaymentStatus(paymentDetails);
}
// Render status details
renderStatusDetails(paymentDetails);
if (shouldRenderUI(paymentDetails)) {
removeClass("body", "hidden");
// Attach document icon
if (paymentDetails.merchant_logo) {
var link = document.createElement("link");
link.rel = "icon";
link.href = paymentDetails.merchant_logo;
link.type = "image/x-icon";
document.head.appendChild(link);
}
// Add event listeners
initializeEventListeners(paymentDetails);
// Render status details
renderStatusDetails(paymentDetails);
// Add event listeners
initializeEventListeners(paymentDetails);
}
}
/**
* Trigger - on boot
* Use - Check if UI should be rendered based on some conditions
* @returns {Boolean}
*/
function shouldRenderUI(paymentDetails) {
var status = paymentDetails.status;
if (isFramed) {
switch (status) {
case "requires_customer_action": return false;
}
}
return true;
}
/**
@ -158,6 +206,7 @@ function renderStatusDetails(paymentDetails) {
).toTimeString();
break;
case "requires_customer_action":
case "processing":
statusDetails.imageSource = "https://live.hyperswitch.io/payment-link-assets/pending.png";
statusDetails.message = translations.paymentTakingLonger;
@ -279,7 +328,7 @@ function renderStatusDetails(paymentDetails) {
var innerText =
secondsLeft === 0
? translations.redirecting
: translations.redirectingIn + secondsLeft + " "+translations.seconds;
: translations.redirectingIn + secondsLeft + " " + translations.seconds;
// @ts-ignore
statusRedirectTextNode.innerText = innerText;
if (secondsLeft === 0) {
@ -341,5 +390,18 @@ function initializeEventListeners(paymentDetails) {
if (statusRedirectTextNode instanceof HTMLDivElement) {
statusRedirectTextNode.style.color = contrastBWColor;
}
};
};
function addClass(id, className) {
var element = document.querySelector(id);
if (element instanceof HTMLElement) {
element.classList.add(className);
}
}
function removeClass(id, className) {
var element = document.querySelector(id);
if (element instanceof HTMLElement) {
element.classList.remove(className);
}
}