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 background_colour: Option<String>,
pub sdk_ui_rules: Option<HashMap<String, HashMap<String, String>>>, pub sdk_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
pub payment_link_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, 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 merchant_name = capitalize_first_char(&payment_link_config.seller_name);
let payment_link_status = check_payment_link_status(session_expiry); 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, payment_intent.status,
&[ &[
storage_enums::IntentStatus::Cancelled, storage_enums::IntentStatus::Cancelled,
@ -201,9 +201,11 @@ pub async fn form_payment_link_data(
storage_enums::IntentStatus::RequiresMerchantAction, storage_enums::IntentStatus::RequiresMerchantAction,
storage_enums::IntentStatus::Succeeded, storage_enums::IntentStatus::Succeeded,
storage_enums::IntentStatus::PartiallyCaptured, 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 { let status = match payment_link_status {
api_models::payments::PaymentLinkStatus::Active => { api_models::payments::PaymentLinkStatus::Active => {
@ -211,7 +213,7 @@ pub async fn form_payment_link_data(
PaymentLinkStatusWrap::IntentStatus(payment_intent.status) PaymentLinkStatusWrap::IntentStatus(payment_intent.status)
} }
api_models::payments::PaymentLinkStatus::Expired => { 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); 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) PaymentLinkStatusWrap::IntentStatus(payment_intent.status)
} else { } else {
@ -292,6 +294,7 @@ pub async fn form_payment_link_data(
payment_button_text_colour: payment_link_config.payment_button_text_colour.clone(), payment_button_text_colour: payment_link_config.payment_button_text_colour.clone(),
sdk_ui_rules: payment_link_config.sdk_ui_rules.clone(), sdk_ui_rules: payment_link_config.sdk_ui_rules.clone(),
payment_link_ui_rules: payment_link_config.payment_link_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, 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); 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 * Trigger - init function invoked once the script tag is loaded
* Use * Use
@ -190,13 +214,16 @@ const translations = getTranslations(window.__PAYMENT_DETAILS.locale);
* - Initialize event listeners for updating UI on screen size changes * - Initialize event listeners for updating UI on screen size changes
* - Initialize SDK * - Initialize SDK
**/ **/
function boot() { function boot() {
// @ts-ignore // @ts-ignore
var paymentDetails = window.__PAYMENT_DETAILS; var paymentDetails = window.__PAYMENT_DETAILS;
// Emit latest payment status
if (isFramed) {
emitPaymentStatus(paymentDetails);
}
if (paymentDetails.display_sdk_only) { if (paymentDetails.display_sdk_only) {
hide(".checkout-page") hide(".checkout-page")
var sdkDisplayWidth = document.querySelector('.hyper-checkout-sdk'); var sdkDisplayWidth = document.querySelector('.hyper-checkout-sdk');

View File

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

View File

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

View File

@ -52,8 +52,8 @@ function invertToBW(color, bw, asArr) {
? hexToRgbArray(options.black) ? hexToRgbArray(options.black)
: options.black : options.black
: asArr : asArr
? hexToRgbArray(options.white) ? hexToRgbArray(options.white)
: options.white; : options.white;
} }
function invert(color, bw) { function invert(color, bw) {
if (bw === void 0) { if (bw === void 0) {
@ -87,6 +87,31 @@ window.state = {
}; };
const translations = getTranslations(window.__PAYMENT_DETAILS.locale); 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 * Trigger - init function invoked once the script tag is loaded
* Use * Use
@ -100,20 +125,43 @@ function boot() {
// @ts-ignore // @ts-ignore
var paymentDetails = window.__PAYMENT_DETAILS; var paymentDetails = window.__PAYMENT_DETAILS;
// Attach document icon // Emit latest payment status
if (paymentDetails.merchant_logo) { if (isFramed) {
var link = document.createElement("link"); emitPaymentStatus(paymentDetails);
link.rel = "icon";
link.href = paymentDetails.merchant_logo;
link.type = "image/x-icon";
document.head.appendChild(link);
} }
// Render status details if (shouldRenderUI(paymentDetails)) {
renderStatusDetails(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 // Render status details
initializeEventListeners(paymentDetails); 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(); ).toTimeString();
break; break;
case "requires_customer_action":
case "processing": case "processing":
statusDetails.imageSource = "https://live.hyperswitch.io/payment-link-assets/pending.png"; statusDetails.imageSource = "https://live.hyperswitch.io/payment-link-assets/pending.png";
statusDetails.message = translations.paymentTakingLonger; statusDetails.message = translations.paymentTakingLonger;
@ -279,7 +328,7 @@ function renderStatusDetails(paymentDetails) {
var innerText = var innerText =
secondsLeft === 0 secondsLeft === 0
? translations.redirecting ? translations.redirecting
: translations.redirectingIn + secondsLeft + " "+translations.seconds; : translations.redirectingIn + secondsLeft + " " + translations.seconds;
// @ts-ignore // @ts-ignore
statusRedirectTextNode.innerText = innerText; statusRedirectTextNode.innerText = innerText;
if (secondsLeft === 0) { if (secondsLeft === 0) {
@ -341,5 +390,18 @@ function initializeEventListeners(paymentDetails) {
if (statusRedirectTextNode instanceof HTMLDivElement) { if (statusRedirectTextNode instanceof HTMLDivElement) {
statusRedirectTextNode.style.color = contrastBWColor; 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);
}
}