fix: payment link styling for dynamic classes (#8273)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Kashif
2025-06-10 16:05:04 +05:30
committed by GitHub
parent 171ca3b564
commit be3fc6c742
14 changed files with 138 additions and 37 deletions

View File

@ -1,4 +1,6 @@
pub mod validator;
use std::collections::HashMap;
use actix_web::http::header;
use api_models::{
admin::PaymentLinkConfig,
@ -142,6 +144,7 @@ pub async fn form_payment_link_data(
payment_form_label_type: None,
show_card_terms: None,
is_setup_mandate_flow: None,
color_icon_card_cvc_error: None,
}
};
@ -316,13 +319,13 @@ pub async fn form_payment_link_data(
background_colour: payment_link_config.background_colour.clone(),
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,
payment_form_header_text: payment_link_config.payment_form_header_text.clone(),
payment_form_label_type: payment_link_config.payment_form_label_type,
show_card_terms: payment_link_config.show_card_terms,
is_setup_mandate_flow: payment_link_config.is_setup_mandate_flow,
color_icon_card_cvc_error: payment_link_config.color_icon_card_cvc_error.clone(),
capture_method: payment_attempt.capture_method,
setup_future_usage_applied: payment_attempt.setup_future_usage_applied,
};
@ -350,7 +353,7 @@ pub async fn initiate_secure_payment_link_flow(
&payment_link_config,
)?;
let css_script = get_color_scheme_css(&payment_link_config);
let css_script = get_payment_link_css_script(&payment_link_config)?;
match payment_link_details {
PaymentLinkData::PaymentLinkStatusDetails(ref status_details) => {
@ -380,12 +383,12 @@ pub async fn initiate_secure_payment_link_flow(
background_colour: payment_link_config.background_colour,
payment_button_text_colour: payment_link_config.payment_button_text_colour,
sdk_ui_rules: payment_link_config.sdk_ui_rules,
payment_link_ui_rules: payment_link_config.payment_link_ui_rules,
enable_button_only_on_form_ready: payment_link_config
.enable_button_only_on_form_ready,
payment_form_header_text: payment_link_config.payment_form_header_text,
payment_form_label_type: payment_link_config.payment_form_label_type,
show_card_terms: payment_link_config.show_card_terms,
color_icon_card_cvc_error: payment_link_config.color_icon_card_cvc_error,
};
let payment_details_str = serde_json::to_string(&secure_payment_link_details)
.change_context(errors::ApiErrorResponse::InternalServerError)
@ -446,7 +449,7 @@ pub async fn initiate_payment_link_flow(
let (_, payment_details, payment_link_config) =
form_payment_link_data(&state, merchant_context, merchant_id, payment_id).await?;
let css_script = get_color_scheme_css(&payment_link_config);
let css_script = get_payment_link_css_script(&payment_link_config)?;
let js_script = get_js_script(&payment_details)?;
match payment_details {
@ -494,6 +497,85 @@ fn get_js_script(payment_details: &PaymentLinkData) -> RouterResult<String> {
Ok(format!("window.__PAYMENT_DETAILS = '{url_encoded_str}';"))
}
fn camel_to_kebab(s: &str) -> String {
let mut result = String::new();
if s.is_empty() {
return result;
}
let chars: Vec<char> = s.chars().collect();
for (i, &ch) in chars.iter().enumerate() {
if ch.is_uppercase() {
let should_add_dash = i > 0
&& (chars.get(i - 1).map(|c| c.is_lowercase()).unwrap_or(false)
|| (i + 1 < chars.len()
&& chars.get(i + 1).map(|c| c.is_lowercase()).unwrap_or(false)
&& chars.get(i - 1).map(|c| c.is_uppercase()).unwrap_or(false)));
if should_add_dash {
result.push('-');
}
result.push(ch.to_ascii_lowercase());
} else {
result.push(ch);
}
}
result
}
fn generate_dynamic_css(
rules: &HashMap<String, HashMap<String, String>>,
) -> Result<String, errors::ApiErrorResponse> {
if rules.is_empty() {
return Ok(String::new());
}
let mut css_string = String::new();
css_string.push_str("/* Dynamically Injected UI Rules */\n");
for (selector, styles_map) in rules {
if selector.trim().is_empty() {
return Err(errors::ApiErrorResponse::InvalidRequestData {
message: "CSS selector cannot be empty.".to_string(),
});
}
css_string.push_str(selector);
css_string.push_str(" {\n");
for (prop_camel_case, css_value) in styles_map {
let css_property = camel_to_kebab(prop_camel_case);
css_string.push_str(" ");
css_string.push_str(&css_property);
css_string.push_str(": ");
css_string.push_str(css_value);
css_string.push_str(";\n");
}
css_string.push_str("}\n");
}
Ok(css_string)
}
fn get_payment_link_css_script(
payment_link_config: &PaymentLinkConfig,
) -> Result<String, errors::ApiErrorResponse> {
let custom_rules_css_option = payment_link_config
.payment_link_ui_rules
.as_ref()
.map(generate_dynamic_css)
.transpose()?;
let color_scheme_css = get_color_scheme_css(payment_link_config);
if let Some(custom_rules_css) = custom_rules_css_option {
Ok(format!("{}\n{}", color_scheme_css, custom_rules_css))
} else {
Ok(color_scheme_css)
}
}
fn get_color_scheme_css(payment_link_config: &PaymentLinkConfig) -> String {
let background_primary_color = payment_link_config
.background_colour
@ -706,6 +788,7 @@ pub fn get_payment_link_config_based_on_priority(
payment_form_label_type,
show_card_terms,
is_setup_mandate_flow,
color_icon_card_cvc_error,
) = get_payment_link_config_value!(
payment_create_link_config,
business_theme_configs,
@ -724,6 +807,7 @@ pub fn get_payment_link_config_based_on_priority(
(payment_form_label_type),
(show_card_terms),
(is_setup_mandate_flow),
(color_icon_card_cvc_error),
);
let payment_link_config =
@ -756,6 +840,7 @@ pub fn get_payment_link_config_based_on_priority(
payment_form_label_type,
show_card_terms,
is_setup_mandate_flow,
color_icon_card_cvc_error,
};
Ok((payment_link_config, domain_name))
@ -870,6 +955,7 @@ pub async fn get_payment_link_status(
payment_form_label_type: None,
show_card_terms: None,
is_setup_mandate_flow: None,
color_icon_card_cvc_error: None,
}
};

View File

@ -653,7 +653,7 @@ body {
}
#submit.not-ready {
background-color: #C2C2C2 !important;
background-color: #C2C2C2;
}
#submit-spinner {

View File

@ -291,12 +291,6 @@ function boot() {
// Add event listeners
initializeEventListeners(paymentDetails);
// Update payment link styles
var paymentLinkUiRules = paymentDetails.payment_link_ui_rules;
if (isObject(paymentLinkUiRules)) {
updatePaymentLinkUi(paymentLinkUiRules);
}
// Initialize SDK
// @ts-ignore
if (window.Hyper) {
@ -358,11 +352,11 @@ function initializeEventListeners(paymentDetails) {
if (payNowButtonText) {
if (paymentDetails.payment_button_text) {
payNowButtonText.textContent = paymentDetails.payment_button_text;
} else if (paymentDetails.is_setup_mandate_flow || (paymentDetails.amount==="0.00" && paymentDetails.setup_future_usage_applied ==="off_session")) {
} else if (paymentDetails.is_setup_mandate_flow || (paymentDetails.amount === "0.00" && paymentDetails.setup_future_usage_applied === "off_session")) {
payNowButtonText.textContent = translations.addPaymentMethod;
} else {
payNowButtonText.textContent = capture_type === "manual" ? translations.authorizePayment: translations.payNow;
payNowButtonText.textContent = capture_type === "manual" ? translations.authorizePayment : translations.payNow;
}
}
@ -1214,25 +1208,4 @@ function renderSDKHeader(paymentDetails) {
// sdkHeaderNode.append(sdkHeaderLogoNode);
sdkHeaderNode.append(sdkHeaderItemNode);
}
}
/**
* Trigger - post UI render
* Use - add CSS rules for the payment link
* @param {Object} paymentLinkUiRules
*/
function updatePaymentLinkUi(paymentLinkUiRules) {
Object.keys(paymentLinkUiRules).forEach(function (selector) {
try {
var node = document.querySelector(selector);
if (node instanceof HTMLElement) {
var styles = paymentLinkUiRules[selector];
Object.keys(styles).forEach(function (property) {
node.style[property] = styles[property];
});
}
} catch (error) {
console.error("Failed to apply styles to selector", selector, error);
}
})
}

View File

@ -14,6 +14,7 @@ function initializeSDK() {
var clientSecret = paymentDetails.client_secret;
var sdkUiRules = paymentDetails.sdk_ui_rules;
var labelType = paymentDetails.payment_form_label_type;
var colorIconCardCvcError = paymentDetails.color_icon_card_cvc_error;
var appearance = {
variables: {
colorPrimary: paymentDetails.theme || "rgb(0, 109, 249)",
@ -33,6 +34,9 @@ function initializeSDK() {
if (labelType !== null && typeof labelType === "string") {
appearance.labels = labelType;
}
if (colorIconCardCvcError !== null && typeof colorIconCardCvcError === "string") {
appearance.variables.colorIconCardCvcError = colorIconCardCvcError;
}
// @ts-ignore
hyper = window.Hyper(pub_key, {
isPreloadEnabled: false,

View File

@ -34,6 +34,7 @@ if (!isFramed) {
var clientSecret = paymentDetails.client_secret;
var sdkUiRules = paymentDetails.sdk_ui_rules;
var labelType = paymentDetails.payment_form_label_type;
var colorIconCardCvcError = paymentDetails.color_icon_card_cvc_error;
var appearance = {
variables: {
colorPrimary: paymentDetails.theme || "rgb(0, 109, 249)",
@ -53,6 +54,9 @@ if (!isFramed) {
if (labelType !== null && typeof labelType === "string") {
appearance.labels = labelType;
}
if (colorIconCardCvcError !== null && typeof colorIconCardCvcError === "string") {
appearance.variables.colorIconCardCvcError = colorIconCardCvcError;
}
// @ts-ignore
hyper = window.Hyper(pub_key, {
isPreloadEnabled: false,

View File

@ -5017,6 +5017,7 @@ impl ForeignFrom<api_models::admin::PaymentLinkConfigRequest>
payment_form_label_type: config.payment_form_label_type,
show_card_terms: config.show_card_terms,
is_setup_mandate_flow: config.is_setup_mandate_flow,
color_icon_card_cvc_error: config.color_icon_card_cvc_error,
}
}
}
@ -5092,6 +5093,7 @@ impl ForeignFrom<diesel_models::PaymentLinkConfigRequestForPayments>
payment_form_label_type: config.payment_form_label_type,
show_card_terms: config.show_card_terms,
is_setup_mandate_flow: config.is_setup_mandate_flow,
color_icon_card_cvc_error: config.color_icon_card_cvc_error,
}
}
}

View File

@ -2242,6 +2242,7 @@ impl ForeignFrom<api_models::admin::PaymentLinkConfigRequest>
payment_form_label_type: item.payment_form_label_type,
show_card_terms: item.show_card_terms,
is_setup_mandate_flow: item.is_setup_mandate_flow,
color_icon_card_cvc_error: item.color_icon_card_cvc_error,
}
}
}
@ -2277,6 +2278,7 @@ impl ForeignFrom<diesel_models::business_profile::PaymentLinkConfigRequest>
payment_form_label_type: item.payment_form_label_type,
show_card_terms: item.show_card_terms,
is_setup_mandate_flow: item.is_setup_mandate_flow,
color_icon_card_cvc_error: item.color_icon_card_cvc_error,
}
}
}