mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 02:57:02 +08:00
feat(payment_link): add provision for secured payment links (#5357)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com>
This commit is contained in:
@ -3144,12 +3144,15 @@ pub async fn update_business_profile(
|
||||
let payment_link_config = request
|
||||
.payment_link_config
|
||||
.as_ref()
|
||||
.map(|pl_metadata| {
|
||||
pl_metadata.encode_to_value().change_context(
|
||||
.map(|payment_link_conf| match payment_link_conf.validate() {
|
||||
Ok(_) => payment_link_conf.encode_to_value().change_context(
|
||||
errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "payment_link_config",
|
||||
},
|
||||
)
|
||||
),
|
||||
Err(e) => Err(report!(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: e.to_string()
|
||||
})),
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
|
||||
@ -1,14 +1,21 @@
|
||||
use api_models::{admin as admin_types, payments::PaymentLinkStatusWrap};
|
||||
pub mod validator;
|
||||
use actix_web::http::header;
|
||||
use api_models::{
|
||||
admin::PaymentLinkConfig,
|
||||
payments::{PaymentLinkData, PaymentLinkStatusWrap},
|
||||
};
|
||||
use common_utils::{
|
||||
consts::{
|
||||
DEFAULT_BACKGROUND_COLOR, DEFAULT_DISPLAY_SDK_ONLY, DEFAULT_ENABLE_SAVED_PAYMENT_METHOD,
|
||||
DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, DEFAULT_SESSION_EXPIRY,
|
||||
DEFAULT_ALLOWED_DOMAINS, DEFAULT_BACKGROUND_COLOR, DEFAULT_DISPLAY_SDK_ONLY,
|
||||
DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG,
|
||||
DEFAULT_SDK_LAYOUT, DEFAULT_SESSION_EXPIRY,
|
||||
},
|
||||
ext_traits::{OptionExt, ValueExt},
|
||||
types::{AmountConvertor, MinorUnit, StringMajorUnitForCore},
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
use error_stack::{report, ResultExt};
|
||||
use futures::future;
|
||||
use hyperswitch_domain_models::api::{GenericLinks, GenericLinksData};
|
||||
use masking::{PeekInterface, Secret};
|
||||
use router_env::logger;
|
||||
use time::PrimitiveDateTime;
|
||||
@ -20,7 +27,9 @@ use crate::{
|
||||
routes::SessionState,
|
||||
services,
|
||||
types::{
|
||||
api::payment_link::PaymentLinkResponseExt, domain, storage::enums as storage_enums,
|
||||
api::payment_link::PaymentLinkResponseExt,
|
||||
domain,
|
||||
storage::{enums as storage_enums, payment_link::PaymentLink},
|
||||
transformers::ForeignFrom,
|
||||
},
|
||||
};
|
||||
@ -49,17 +58,17 @@ pub async fn retrieve_payment_link(
|
||||
Ok(services::ApplicationResponse::Json(response))
|
||||
}
|
||||
|
||||
pub async fn initiate_payment_link_flow(
|
||||
state: SessionState,
|
||||
pub async fn form_payment_link_data(
|
||||
state: &SessionState,
|
||||
merchant_account: domain::MerchantAccount,
|
||||
key_store: domain::MerchantKeyStore,
|
||||
merchant_id: common_utils::id_type::MerchantId,
|
||||
payment_id: String,
|
||||
) -> RouterResponse<services::PaymentLinkFormData> {
|
||||
) -> RouterResult<(PaymentLink, PaymentLinkData, PaymentLinkConfig)> {
|
||||
let db = &*state.store;
|
||||
let payment_intent = db
|
||||
.find_payment_intent_by_payment_id_merchant_id(
|
||||
&(&state).into(),
|
||||
&(state).into(),
|
||||
&payment_id,
|
||||
&merchant_id,
|
||||
&key_store,
|
||||
@ -84,21 +93,24 @@ pub async fn initiate_payment_link_flow(
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::PaymentLinkNotFound)?;
|
||||
|
||||
let payment_link_config = if let Some(pl_config_value) = payment_link.payment_link_config {
|
||||
extract_payment_link_config(pl_config_value)?
|
||||
} else {
|
||||
admin_types::PaymentLinkConfig {
|
||||
theme: DEFAULT_BACKGROUND_COLOR.to_string(),
|
||||
logo: DEFAULT_MERCHANT_LOGO.to_string(),
|
||||
seller_name: merchant_name_from_merchant_account,
|
||||
sdk_layout: DEFAULT_SDK_LAYOUT.to_owned(),
|
||||
display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY,
|
||||
enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD,
|
||||
}
|
||||
};
|
||||
let payment_link_config =
|
||||
if let Some(pl_config_value) = payment_link.payment_link_config.clone() {
|
||||
extract_payment_link_config(pl_config_value)?
|
||||
} else {
|
||||
PaymentLinkConfig {
|
||||
theme: DEFAULT_BACKGROUND_COLOR.to_string(),
|
||||
logo: DEFAULT_MERCHANT_LOGO.to_string(),
|
||||
seller_name: merchant_name_from_merchant_account,
|
||||
sdk_layout: DEFAULT_SDK_LAYOUT.to_owned(),
|
||||
display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY,
|
||||
enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD,
|
||||
allowed_domains: DEFAULT_ALLOWED_DOMAINS,
|
||||
}
|
||||
};
|
||||
|
||||
let profile_id = payment_link
|
||||
.profile_id
|
||||
.clone()
|
||||
.or(payment_intent.profile_id)
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Profile id missing in payment link and payment intent")?;
|
||||
@ -143,7 +155,6 @@ pub async fn initiate_payment_link_flow(
|
||||
|
||||
// converting first letter of merchant name to upperCase
|
||||
let merchant_name = capitalize_first_char(&payment_link_config.seller_name);
|
||||
let css_script = get_color_scheme_css(payment_link_config.clone());
|
||||
let payment_link_status = check_payment_link_status(session_expiry);
|
||||
|
||||
let is_terminal_state = check_payment_link_invalid_conditions(
|
||||
@ -205,78 +216,185 @@ pub async fn initiate_payment_link_flow(
|
||||
return_url: return_url.clone(),
|
||||
};
|
||||
|
||||
logger::info!(
|
||||
"payment link data, for building payment link status page {:?}",
|
||||
payment_details
|
||||
);
|
||||
let js_script = get_js_script(
|
||||
&api_models::payments::PaymentLinkData::PaymentLinkStatusDetails(payment_details),
|
||||
)?;
|
||||
let payment_link_error_data = services::PaymentLinkStatusData {
|
||||
js_script,
|
||||
css_script,
|
||||
};
|
||||
return Ok(services::ApplicationResponse::PaymentLinkForm(Box::new(
|
||||
services::api::PaymentLinkAction::PaymentLinkStatus(payment_link_error_data),
|
||||
)));
|
||||
return Ok((
|
||||
payment_link,
|
||||
PaymentLinkData::PaymentLinkStatusDetails(payment_details),
|
||||
payment_link_config,
|
||||
));
|
||||
};
|
||||
|
||||
let payment_details = api_models::payments::PaymentLinkDetails {
|
||||
amount,
|
||||
currency,
|
||||
payment_id: payment_intent.payment_id,
|
||||
merchant_name,
|
||||
order_details,
|
||||
return_url,
|
||||
session_expiry,
|
||||
pub_key: merchant_account.publishable_key,
|
||||
client_secret,
|
||||
merchant_logo: payment_link_config.logo.clone(),
|
||||
max_items_visible_after_collapse: 3,
|
||||
theme: payment_link_config.theme.clone(),
|
||||
merchant_description: payment_intent.description,
|
||||
sdk_layout: payment_link_config.sdk_layout.clone(),
|
||||
display_sdk_only: payment_link_config.display_sdk_only,
|
||||
enabled_saved_payment_method: payment_link_config.enabled_saved_payment_method,
|
||||
};
|
||||
let payment_link_details =
|
||||
PaymentLinkData::PaymentLinkDetails(api_models::payments::PaymentLinkDetails {
|
||||
amount,
|
||||
currency,
|
||||
payment_id: payment_intent.payment_id,
|
||||
merchant_name,
|
||||
order_details,
|
||||
return_url,
|
||||
session_expiry,
|
||||
pub_key: merchant_account.publishable_key,
|
||||
client_secret,
|
||||
merchant_logo: payment_link_config.logo.clone(),
|
||||
max_items_visible_after_collapse: 3,
|
||||
theme: payment_link_config.theme.clone(),
|
||||
merchant_description: payment_intent.description,
|
||||
sdk_layout: payment_link_config.sdk_layout.clone(),
|
||||
display_sdk_only: payment_link_config.display_sdk_only,
|
||||
});
|
||||
|
||||
let js_script = get_js_script(&api_models::payments::PaymentLinkData::PaymentLinkDetails(
|
||||
&payment_details,
|
||||
))?;
|
||||
Ok((payment_link, payment_link_details, payment_link_config))
|
||||
}
|
||||
|
||||
let html_meta_tags = get_meta_tags_html(payment_details);
|
||||
pub async fn initiate_secure_payment_link_flow(
|
||||
state: SessionState,
|
||||
merchant_account: domain::MerchantAccount,
|
||||
key_store: domain::MerchantKeyStore,
|
||||
merchant_id: common_utils::id_type::MerchantId,
|
||||
payment_id: String,
|
||||
request_headers: &header::HeaderMap,
|
||||
) -> RouterResponse<services::PaymentLinkFormData> {
|
||||
let (payment_link, payment_link_details, payment_link_config) =
|
||||
form_payment_link_data(&state, merchant_account, key_store, merchant_id, payment_id)
|
||||
.await?;
|
||||
|
||||
let payment_link_data = services::PaymentLinkFormData {
|
||||
js_script,
|
||||
sdk_url: state.conf.payment_link.sdk_url.clone(),
|
||||
css_script,
|
||||
html_meta_tags,
|
||||
};
|
||||
validator::validate_secure_payment_link_render_request(
|
||||
request_headers,
|
||||
&payment_link,
|
||||
&payment_link_config,
|
||||
)?;
|
||||
|
||||
logger::info!(
|
||||
"payment link data, for building payment link {:?}",
|
||||
payment_link_data
|
||||
);
|
||||
Ok(services::ApplicationResponse::PaymentLinkForm(Box::new(
|
||||
services::api::PaymentLinkAction::PaymentLinkFormData(payment_link_data),
|
||||
)))
|
||||
let css_script = get_color_scheme_css(&payment_link_config);
|
||||
|
||||
match payment_link_details {
|
||||
PaymentLinkData::PaymentLinkStatusDetails(ref status_details) => {
|
||||
let js_script = get_js_script(&payment_link_details)?;
|
||||
let payment_link_error_data = services::PaymentLinkStatusData {
|
||||
js_script,
|
||||
css_script,
|
||||
};
|
||||
logger::info!(
|
||||
"payment link data, for building payment link status page {:?}",
|
||||
status_details
|
||||
);
|
||||
Ok(services::ApplicationResponse::PaymentLinkForm(Box::new(
|
||||
services::api::PaymentLinkAction::PaymentLinkStatus(payment_link_error_data),
|
||||
)))
|
||||
}
|
||||
PaymentLinkData::PaymentLinkDetails(link_details) => {
|
||||
let secure_payment_link_details = api_models::payments::SecurePaymentLinkDetails {
|
||||
enabled_saved_payment_method: payment_link_config.enabled_saved_payment_method,
|
||||
payment_link_details: link_details.to_owned(),
|
||||
};
|
||||
let js_script = format!(
|
||||
"window.__PAYMENT_DETAILS = {}",
|
||||
serde_json::to_string(&secure_payment_link_details)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to serialize PaymentLinkData")?
|
||||
);
|
||||
let html_meta_tags = get_meta_tags_html(&link_details);
|
||||
let payment_link_data = services::PaymentLinkFormData {
|
||||
js_script,
|
||||
sdk_url: state.conf.payment_link.sdk_url.clone(),
|
||||
css_script,
|
||||
html_meta_tags,
|
||||
};
|
||||
let allowed_domains = payment_link_config
|
||||
.allowed_domains
|
||||
.clone()
|
||||
.ok_or(report!(errors::ApiErrorResponse::InternalServerError))
|
||||
.attach_printable_lazy(|| {
|
||||
format!(
|
||||
"Invalid list of allowed_domains found - {:?}",
|
||||
payment_link_config.allowed_domains.clone()
|
||||
)
|
||||
})?;
|
||||
|
||||
if allowed_domains.is_empty() {
|
||||
return Err(report!(errors::ApiErrorResponse::InternalServerError))
|
||||
.attach_printable_lazy(|| {
|
||||
format!(
|
||||
"Invalid list of allowed_domains found - {:?}",
|
||||
payment_link_config.allowed_domains.clone()
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
let link_data = GenericLinks {
|
||||
allowed_domains,
|
||||
data: GenericLinksData::SecurePaymentLink(payment_link_data),
|
||||
};
|
||||
logger::info!(
|
||||
"payment link data, for building secure payment link {:?}",
|
||||
link_data
|
||||
);
|
||||
|
||||
Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
|
||||
link_data,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn initiate_payment_link_flow(
|
||||
state: SessionState,
|
||||
merchant_account: domain::MerchantAccount,
|
||||
key_store: domain::MerchantKeyStore,
|
||||
merchant_id: common_utils::id_type::MerchantId,
|
||||
payment_id: String,
|
||||
) -> RouterResponse<services::PaymentLinkFormData> {
|
||||
let (_, payment_details, payment_link_config) =
|
||||
form_payment_link_data(&state, merchant_account, key_store, merchant_id, payment_id)
|
||||
.await?;
|
||||
|
||||
let css_script = get_color_scheme_css(&payment_link_config);
|
||||
let js_script = get_js_script(&payment_details)?;
|
||||
|
||||
match payment_details {
|
||||
PaymentLinkData::PaymentLinkStatusDetails(status_details) => {
|
||||
let payment_link_error_data = services::PaymentLinkStatusData {
|
||||
js_script,
|
||||
css_script,
|
||||
};
|
||||
logger::info!(
|
||||
"payment link data, for building payment link status page {:?}",
|
||||
status_details
|
||||
);
|
||||
Ok(services::ApplicationResponse::PaymentLinkForm(Box::new(
|
||||
services::api::PaymentLinkAction::PaymentLinkStatus(payment_link_error_data),
|
||||
)))
|
||||
}
|
||||
PaymentLinkData::PaymentLinkDetails(payment_details) => {
|
||||
let html_meta_tags = get_meta_tags_html(&payment_details);
|
||||
let payment_link_data = services::PaymentLinkFormData {
|
||||
js_script,
|
||||
sdk_url: state.conf.payment_link.sdk_url.clone(),
|
||||
css_script,
|
||||
html_meta_tags,
|
||||
};
|
||||
logger::info!(
|
||||
"payment link data, for building open payment link {:?}",
|
||||
payment_link_data
|
||||
);
|
||||
Ok(services::ApplicationResponse::PaymentLinkForm(Box::new(
|
||||
services::api::PaymentLinkAction::PaymentLinkFormData(payment_link_data),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The get_js_script function is used to inject dynamic value to payment_link sdk, which is unique to every payment.
|
||||
*/
|
||||
|
||||
fn get_js_script(
|
||||
payment_details: &api_models::payments::PaymentLinkData<'_>,
|
||||
) -> RouterResult<String> {
|
||||
fn get_js_script(payment_details: &PaymentLinkData) -> RouterResult<String> {
|
||||
let payment_details_str = serde_json::to_string(payment_details)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to serialize PaymentLinkData")?;
|
||||
Ok(format!("window.__PAYMENT_DETAILS = {payment_details_str};"))
|
||||
}
|
||||
|
||||
fn get_color_scheme_css(payment_link_config: api_models::admin::PaymentLinkConfig) -> String {
|
||||
let background_primary_color = payment_link_config.theme;
|
||||
fn get_color_scheme_css(payment_link_config: &PaymentLinkConfig) -> String {
|
||||
let background_primary_color = payment_link_config.theme.clone();
|
||||
format!(
|
||||
":root {{
|
||||
--primary-color: {background_primary_color};
|
||||
@ -284,12 +402,15 @@ fn get_color_scheme_css(payment_link_config: api_models::admin::PaymentLinkConfi
|
||||
)
|
||||
}
|
||||
|
||||
fn get_meta_tags_html(payment_details: api_models::payments::PaymentLinkDetails) -> String {
|
||||
fn get_meta_tags_html(payment_details: &api_models::payments::PaymentLinkDetails) -> String {
|
||||
format!(
|
||||
r#"<meta property="og:title" content="Payment request from {0}"/>
|
||||
<meta property="og:description" content="{1}"/>"#,
|
||||
payment_details.merchant_name,
|
||||
payment_details.merchant_description.unwrap_or_default()
|
||||
payment_details.merchant_name.clone(),
|
||||
payment_details
|
||||
.merchant_description
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
)
|
||||
}
|
||||
|
||||
@ -395,11 +516,12 @@ fn validate_order_details(
|
||||
|
||||
pub fn extract_payment_link_config(
|
||||
pl_config: serde_json::Value,
|
||||
) -> Result<api_models::admin::PaymentLinkConfig, error_stack::Report<errors::ApiErrorResponse>> {
|
||||
serde_json::from_value::<api_models::admin::PaymentLinkConfig>(pl_config.clone())
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||
) -> Result<PaymentLinkConfig, error_stack::Report<errors::ApiErrorResponse>> {
|
||||
serde_json::from_value::<PaymentLinkConfig>(pl_config).change_context(
|
||||
errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "payment_link_config",
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_payment_link_config_based_on_priority(
|
||||
@ -408,39 +530,39 @@ pub fn get_payment_link_config_based_on_priority(
|
||||
merchant_name: String,
|
||||
default_domain_name: String,
|
||||
payment_link_config_id: Option<String>,
|
||||
) -> Result<(admin_types::PaymentLinkConfig, String), error_stack::Report<errors::ApiErrorResponse>>
|
||||
{
|
||||
let (domain_name, business_theme_configs) = if let Some(business_config) = business_link_config
|
||||
{
|
||||
let extracted_value: api_models::admin::BusinessPaymentLinkConfig = business_config
|
||||
.parse_value("BusinessPaymentLinkConfig")
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "payment_link_config",
|
||||
})
|
||||
.attach_printable("Invalid payment_link_config given in business config")?;
|
||||
logger::info!(
|
||||
"domain name set to custom domain https://{:?}",
|
||||
extracted_value.domain_name
|
||||
);
|
||||
|
||||
(
|
||||
extracted_value
|
||||
.domain_name
|
||||
.clone()
|
||||
.map(|d_name| format!("https://{}", d_name))
|
||||
.unwrap_or_else(|| default_domain_name.clone()),
|
||||
payment_link_config_id
|
||||
.and_then(|id| {
|
||||
extracted_value
|
||||
.business_specific_configs
|
||||
.as_ref()
|
||||
.and_then(|specific_configs| specific_configs.get(&id).cloned())
|
||||
) -> Result<(PaymentLinkConfig, String), error_stack::Report<errors::ApiErrorResponse>> {
|
||||
let (domain_name, business_theme_configs, allowed_domains) =
|
||||
if let Some(business_config) = business_link_config {
|
||||
let extracted_value: api_models::admin::BusinessPaymentLinkConfig = business_config
|
||||
.parse_value("BusinessPaymentLinkConfig")
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "payment_link_config",
|
||||
})
|
||||
.or(extracted_value.default_config),
|
||||
)
|
||||
} else {
|
||||
(default_domain_name, None)
|
||||
};
|
||||
.attach_printable("Invalid payment_link_config given in business config")?;
|
||||
logger::info!(
|
||||
"domain name set to custom domain https://{:?}",
|
||||
extracted_value.domain_name
|
||||
);
|
||||
|
||||
(
|
||||
extracted_value
|
||||
.domain_name
|
||||
.clone()
|
||||
.map(|d_name| format!("https://{}", d_name))
|
||||
.unwrap_or_else(|| default_domain_name.clone()),
|
||||
payment_link_config_id
|
||||
.and_then(|id| {
|
||||
extracted_value
|
||||
.business_specific_configs
|
||||
.as_ref()
|
||||
.and_then(|specific_configs| specific_configs.get(&id).cloned())
|
||||
})
|
||||
.or(extracted_value.default_config),
|
||||
extracted_value.allowed_domains,
|
||||
)
|
||||
} else {
|
||||
(default_domain_name, None, None)
|
||||
};
|
||||
|
||||
let (theme, logo, seller_name, sdk_layout, display_sdk_only, enabled_saved_payment_method) = get_payment_link_config_value!(
|
||||
payment_create_link_config,
|
||||
@ -456,13 +578,14 @@ pub fn get_payment_link_config_based_on_priority(
|
||||
)
|
||||
);
|
||||
|
||||
let payment_link_config = admin_types::PaymentLinkConfig {
|
||||
let payment_link_config = PaymentLinkConfig {
|
||||
theme,
|
||||
logo,
|
||||
seller_name,
|
||||
sdk_layout,
|
||||
display_sdk_only,
|
||||
enabled_saved_payment_method,
|
||||
allowed_domains,
|
||||
};
|
||||
|
||||
Ok((payment_link_config, domain_name))
|
||||
@ -537,13 +660,14 @@ pub async fn get_payment_link_status(
|
||||
let payment_link_config = if let Some(pl_config_value) = payment_link.payment_link_config {
|
||||
extract_payment_link_config(pl_config_value)?
|
||||
} else {
|
||||
admin_types::PaymentLinkConfig {
|
||||
PaymentLinkConfig {
|
||||
theme: DEFAULT_BACKGROUND_COLOR.to_string(),
|
||||
logo: DEFAULT_MERCHANT_LOGO.to_string(),
|
||||
seller_name: merchant_name_from_merchant_account,
|
||||
sdk_layout: DEFAULT_SDK_LAYOUT.to_owned(),
|
||||
display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY,
|
||||
enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD,
|
||||
allowed_domains: DEFAULT_ALLOWED_DOMAINS,
|
||||
}
|
||||
};
|
||||
|
||||
@ -564,7 +688,7 @@ pub async fn get_payment_link_status(
|
||||
|
||||
// converting first letter of merchant name to upperCase
|
||||
let merchant_name = capitalize_first_char(&payment_link_config.seller_name);
|
||||
let css_script = get_color_scheme_css(payment_link_config.clone());
|
||||
let css_script = get_color_scheme_css(&payment_link_config);
|
||||
|
||||
let profile_id = payment_link
|
||||
.profile_id
|
||||
@ -603,9 +727,7 @@ pub async fn get_payment_link_status(
|
||||
theme: payment_link_config.theme.clone(),
|
||||
return_url,
|
||||
};
|
||||
let js_script = get_js_script(
|
||||
&api_models::payments::PaymentLinkData::PaymentLinkStatusDetails(payment_details),
|
||||
)?;
|
||||
let js_script = get_js_script(&PaymentLinkData::PaymentLinkStatusDetails(payment_details))?;
|
||||
let payment_link_status_data = services::PaymentLinkStatusData {
|
||||
js_script,
|
||||
css_script,
|
||||
|
||||
@ -93,7 +93,7 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="hide-scrollbar">
|
||||
<body id="payment-link" class="hide-scrollbar">
|
||||
<div id="payment-details-shimmer">
|
||||
<div class = "wrap">
|
||||
<box class="shine"></box>
|
||||
@ -324,6 +324,7 @@
|
||||
</div>
|
||||
<script>
|
||||
{{rendered_js}}
|
||||
{{payment_link_initiator}}
|
||||
{{logging_template}}
|
||||
</script>
|
||||
{{ hyperloader_sdk_link }}
|
||||
|
||||
@ -379,75 +379,6 @@ function showSDK(display_sdk_only) {
|
||||
hide("#sdk-spinner");
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger - post downloading SDK
|
||||
* Uses
|
||||
* - Instantiate SDK
|
||||
* - Create a payment widget
|
||||
* - Decide whether or not to show SDK (based on status)
|
||||
**/
|
||||
function initializeSDK() {
|
||||
// @ts-ignore
|
||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
||||
var client_secret = paymentDetails.client_secret;
|
||||
var appearance = {
|
||||
variables: {
|
||||
colorPrimary: paymentDetails.theme || "rgb(0, 109, 249)",
|
||||
fontFamily: "Work Sans, sans-serif",
|
||||
fontSizeBase: "16px",
|
||||
colorText: "rgb(51, 65, 85)",
|
||||
colorTextSecondary: "#334155B3",
|
||||
colorPrimaryText: "rgb(51, 65, 85)",
|
||||
colorTextPlaceholder: "#33415550",
|
||||
borderColor: "#33415550",
|
||||
colorBackground: "rgb(255, 255, 255)",
|
||||
},
|
||||
};
|
||||
// @ts-ignore
|
||||
hyper = window.Hyper(pub_key, {
|
||||
isPreloadEnabled: false,
|
||||
});
|
||||
widgets = hyper.widgets({
|
||||
appearance: appearance,
|
||||
clientSecret: client_secret,
|
||||
});
|
||||
var type =
|
||||
paymentDetails.sdk_layout === "spaced_accordion" ||
|
||||
paymentDetails.sdk_layout === "accordion"
|
||||
? "accordion"
|
||||
: paymentDetails.sdk_layout;
|
||||
|
||||
var enableSavedPaymentMethod = paymentDetails.enabled_saved_payment_method;
|
||||
var unifiedCheckoutOptions = {
|
||||
displaySavedPaymentMethodsCheckbox: enableSavedPaymentMethod,
|
||||
displaySavedPaymentMethods: enableSavedPaymentMethod,
|
||||
layout: {
|
||||
type: type, //accordion , tabs, spaced accordion
|
||||
spacedAccordionItems: paymentDetails.sdk_layout === "spaced_accordion",
|
||||
},
|
||||
branding: "never",
|
||||
wallets: {
|
||||
walletReturnUrl: paymentDetails.return_url,
|
||||
style: {
|
||||
theme: "dark",
|
||||
type: "default",
|
||||
height: 55,
|
||||
},
|
||||
},
|
||||
};
|
||||
unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions);
|
||||
mountUnifiedCheckout("#unified-checkout");
|
||||
showSDK(paymentDetails.display_sdk_only);
|
||||
|
||||
let shimmer = document.getElementById("payment-details-shimmer");
|
||||
shimmer.classList.add("reduce-opacity")
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(shimmer);
|
||||
}, 500)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Use - mount payment widget on the passed element
|
||||
* @param {String} id
|
||||
@ -525,17 +456,6 @@ function showMessage(msg) {
|
||||
addText("#payment-message", msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use - redirect to /payment_link/status
|
||||
*/
|
||||
function redirectToStatus() {
|
||||
var arr = window.location.pathname.split("/");
|
||||
arr.splice(0, 2);
|
||||
arr.unshift("status");
|
||||
arr.unshift("payment_link");
|
||||
window.location.href = window.location.origin + "/" + arr.join("/");
|
||||
}
|
||||
|
||||
function addText(id, msg) {
|
||||
var element = document.querySelector(id);
|
||||
element.innerText = msg;
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* Trigger - post downloading SDK
|
||||
* Uses
|
||||
* - Instantiate SDK
|
||||
* - Create a payment widget
|
||||
* - Decide whether or not to show SDK (based on status)
|
||||
**/
|
||||
function initializeSDK() {
|
||||
// @ts-ignore
|
||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
||||
var client_secret = paymentDetails.client_secret;
|
||||
var appearance = {
|
||||
variables: {
|
||||
colorPrimary: paymentDetails.theme || "rgb(0, 109, 249)",
|
||||
fontFamily: "Work Sans, sans-serif",
|
||||
fontSizeBase: "16px",
|
||||
colorText: "rgb(51, 65, 85)",
|
||||
colorTextSecondary: "#334155B3",
|
||||
colorPrimaryText: "rgb(51, 65, 85)",
|
||||
colorTextPlaceholder: "#33415550",
|
||||
borderColor: "#33415550",
|
||||
colorBackground: "rgb(255, 255, 255)",
|
||||
},
|
||||
};
|
||||
// @ts-ignore
|
||||
hyper = window.Hyper(pub_key, {
|
||||
isPreloadEnabled: false,
|
||||
});
|
||||
// @ts-ignore
|
||||
widgets = hyper.widgets({
|
||||
appearance: appearance,
|
||||
clientSecret: client_secret,
|
||||
});
|
||||
var type =
|
||||
paymentDetails.sdk_layout === "spaced_accordion" ||
|
||||
paymentDetails.sdk_layout === "accordion"
|
||||
? "accordion"
|
||||
: paymentDetails.sdk_layout;
|
||||
|
||||
var unifiedCheckoutOptions = {
|
||||
displaySavedPaymentMethodsCheckbox: false,
|
||||
layout: {
|
||||
type: type, //accordion , tabs, spaced accordion
|
||||
spacedAccordionItems: paymentDetails.sdk_layout === "spaced_accordion",
|
||||
},
|
||||
branding: "never",
|
||||
wallets: {
|
||||
walletReturnUrl: paymentDetails.return_url,
|
||||
style: {
|
||||
theme: "dark",
|
||||
type: "default",
|
||||
height: 55,
|
||||
},
|
||||
},
|
||||
};
|
||||
// @ts-ignore
|
||||
unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions);
|
||||
// @ts-ignore
|
||||
mountUnifiedCheckout("#unified-checkout");
|
||||
// @ts-ignore
|
||||
showSDK(paymentDetails.display_sdk_only);
|
||||
|
||||
let shimmer = document.getElementById("payment-details-shimmer");
|
||||
shimmer.classList.add("reduce-opacity");
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(shimmer);
|
||||
}, 500);
|
||||
|
||||
/**
|
||||
* Use - redirect to /payment_link/status
|
||||
*/
|
||||
function redirectToStatus() {
|
||||
var arr = window.location.pathname.split("/");
|
||||
arr.splice(0, 2);
|
||||
arr.unshift("status");
|
||||
arr.unshift("payment_link");
|
||||
window.location.href = window.location.origin + "/" + arr.join("/");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
// @ts-check
|
||||
|
||||
// Top level checks
|
||||
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;
|
||||
}
|
||||
|
||||
if (!isFramed) {
|
||||
function initializeSDK() {
|
||||
var errMsg = "You are not allowed to view this content.";
|
||||
var contentElement = document.getElementById("payment-link");
|
||||
if (contentElement instanceof HTMLDivElement) {
|
||||
contentElement.innerHTML = errMsg;
|
||||
} else {
|
||||
document.body.innerHTML = errMsg;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Trigger - post downloading SDK
|
||||
* Uses
|
||||
* - Instantiate SDK
|
||||
* - Create a payment widget
|
||||
* - Decide whether or not to show SDK (based on status)
|
||||
**/
|
||||
function initializeSDK() {
|
||||
// @ts-ignore
|
||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
||||
var client_secret = paymentDetails.client_secret;
|
||||
var appearance = {
|
||||
variables: {
|
||||
colorPrimary: paymentDetails.theme || "rgb(0, 109, 249)",
|
||||
fontFamily: "Work Sans, sans-serif",
|
||||
fontSizeBase: "16px",
|
||||
colorText: "rgb(51, 65, 85)",
|
||||
colorTextSecondary: "#334155B3",
|
||||
colorPrimaryText: "rgb(51, 65, 85)",
|
||||
colorTextPlaceholder: "#33415550",
|
||||
borderColor: "#33415550",
|
||||
colorBackground: "rgb(255, 255, 255)",
|
||||
},
|
||||
};
|
||||
// @ts-ignore
|
||||
hyper = window.Hyper(pub_key, {
|
||||
isPreloadEnabled: false,
|
||||
});
|
||||
// @ts-ignore
|
||||
widgets = hyper.widgets({
|
||||
appearance: appearance,
|
||||
clientSecret: client_secret,
|
||||
});
|
||||
var type =
|
||||
paymentDetails.sdk_layout === "spaced_accordion" ||
|
||||
paymentDetails.sdk_layout === "accordion"
|
||||
? "accordion"
|
||||
: paymentDetails.sdk_layout;
|
||||
|
||||
var enableSavedPaymentMethod = paymentDetails.enabled_saved_payment_method;
|
||||
var unifiedCheckoutOptions = {
|
||||
displaySavedPaymentMethodsCheckbox: enableSavedPaymentMethod,
|
||||
displaySavedPaymentMethods: enableSavedPaymentMethod,
|
||||
layout: {
|
||||
type: type, //accordion , tabs, spaced accordion
|
||||
spacedAccordionItems: paymentDetails.sdk_layout === "spaced_accordion",
|
||||
},
|
||||
branding: "never",
|
||||
wallets: {
|
||||
walletReturnUrl: paymentDetails.return_url,
|
||||
style: {
|
||||
theme: "dark",
|
||||
type: "default",
|
||||
height: 55,
|
||||
},
|
||||
},
|
||||
};
|
||||
// @ts-ignore
|
||||
unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions);
|
||||
// @ts-ignore
|
||||
mountUnifiedCheckout("#unified-checkout");
|
||||
// @ts-ignore
|
||||
showSDK(paymentDetails.display_sdk_only);
|
||||
|
||||
let shimmer = document.getElementById("payment-details-shimmer");
|
||||
shimmer.classList.add("reduce-opacity");
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(shimmer);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use - redirect to /payment_link/status
|
||||
*/
|
||||
function redirectToStatus() {
|
||||
var arr = window.location.pathname.split("/");
|
||||
arr.splice(0, 3);
|
||||
arr.unshift("status");
|
||||
arr.unshift("payment_link");
|
||||
window.location.href = window.location.origin + "/" + arr.join("/");
|
||||
}
|
||||
}
|
||||
118
crates/router/src/core/payment_link/validator.rs
Normal file
118
crates/router/src/core/payment_link/validator.rs
Normal file
@ -0,0 +1,118 @@
|
||||
use actix_http::header;
|
||||
use api_models::admin::PaymentLinkConfig;
|
||||
use common_utils::validation::validate_domain_against_allowed_domains;
|
||||
use error_stack::{report, ResultExt};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
core::errors::{self, RouterResult},
|
||||
types::storage::PaymentLink,
|
||||
};
|
||||
|
||||
pub fn validate_secure_payment_link_render_request(
|
||||
request_headers: &header::HeaderMap,
|
||||
payment_link: &PaymentLink,
|
||||
payment_link_config: &PaymentLinkConfig,
|
||||
) -> RouterResult<()> {
|
||||
let link_id = payment_link.payment_link_id.clone();
|
||||
let allowed_domains = payment_link_config
|
||||
.allowed_domains
|
||||
.clone()
|
||||
.ok_or(report!(errors::ApiErrorResponse::InvalidRequestUrl))
|
||||
.attach_printable_lazy(|| {
|
||||
format!(
|
||||
"Secure payment link was not generated for {}\nmissing allowed_domains",
|
||||
link_id
|
||||
)
|
||||
})?;
|
||||
|
||||
// Validate secure_link was generated
|
||||
if payment_link.secure_link.clone().is_none() {
|
||||
return Err(report!(errors::ApiErrorResponse::InvalidRequestUrl)).attach_printable_lazy(
|
||||
|| {
|
||||
format!(
|
||||
"Secure payment link was not generated for {}\nmissing secure_link",
|
||||
link_id
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch destination is "iframe"
|
||||
match request_headers.get("sec-fetch-dest").and_then(|v| v.to_str().ok()) {
|
||||
Some("iframe") => Ok(()),
|
||||
Some(requestor) => Err(report!(errors::ApiErrorResponse::AccessForbidden {
|
||||
resource: "payment_link".to_string(),
|
||||
}))
|
||||
.attach_printable_lazy(|| {
|
||||
format!(
|
||||
"Access to payment_link [{}] is forbidden when requested through {}",
|
||||
link_id, requestor
|
||||
)
|
||||
}),
|
||||
None => Err(report!(errors::ApiErrorResponse::AccessForbidden {
|
||||
resource: "payment_link".to_string(),
|
||||
}))
|
||||
.attach_printable_lazy(|| {
|
||||
format!(
|
||||
"Access to payment_link [{}] is forbidden when sec-fetch-dest is not present in request headers",
|
||||
link_id
|
||||
)
|
||||
}),
|
||||
}?;
|
||||
|
||||
// Validate origin / referer
|
||||
let domain_in_req = {
|
||||
let origin_or_referer = request_headers
|
||||
.get("origin")
|
||||
.or_else(|| request_headers.get("referer"))
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.ok_or_else(|| {
|
||||
report!(errors::ApiErrorResponse::AccessForbidden {
|
||||
resource: "payment_link".to_string(),
|
||||
})
|
||||
})
|
||||
.attach_printable_lazy(|| {
|
||||
format!(
|
||||
"Access to payment_link [{}] is forbidden when origin or referer is not present in request headers",
|
||||
link_id
|
||||
)
|
||||
})?;
|
||||
|
||||
let url = Url::parse(origin_or_referer)
|
||||
.map_err(|_| {
|
||||
report!(errors::ApiErrorResponse::AccessForbidden {
|
||||
resource: "payment_link".to_string(),
|
||||
})
|
||||
})
|
||||
.attach_printable_lazy(|| {
|
||||
format!("Invalid URL found in request headers {}", origin_or_referer)
|
||||
})?;
|
||||
|
||||
url.host_str()
|
||||
.and_then(|host| url.port().map(|port| format!("{}:{}", host, port)))
|
||||
.or_else(|| url.host_str().map(String::from))
|
||||
.ok_or_else(|| {
|
||||
report!(errors::ApiErrorResponse::AccessForbidden {
|
||||
resource: "payment_link".to_string(),
|
||||
})
|
||||
})
|
||||
.attach_printable_lazy(|| {
|
||||
format!("host or port not found in request headers {:?}", url)
|
||||
})?
|
||||
};
|
||||
|
||||
if validate_domain_against_allowed_domains(&domain_in_req, allowed_domains) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(report!(errors::ApiErrorResponse::AccessForbidden {
|
||||
resource: "payment_link".to_string(),
|
||||
}))
|
||||
.attach_printable_lazy(|| {
|
||||
format!(
|
||||
"Access to payment_link [{}] is forbidden from requestor - {}",
|
||||
link_id, domain_in_req
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1218,13 +1218,22 @@ async fn create_payment_link(
|
||||
) -> RouterResult<Option<api_models::payments::PaymentLinkResponse>> {
|
||||
let created_at @ last_modified_at = Some(common_utils::date_time::now());
|
||||
let payment_link_id = utils::generate_id(consts::ID_LENGTH, "plink");
|
||||
let payment_link = format!(
|
||||
let open_payment_link = format!(
|
||||
"{}/payment_link/{}/{}",
|
||||
domain_name,
|
||||
merchant_id.get_string_repr(),
|
||||
payment_id.clone()
|
||||
);
|
||||
|
||||
let secure_link = payment_link_config.allowed_domains.as_ref().map(|_| {
|
||||
format!(
|
||||
"{}/payment_link/s/{}/{}",
|
||||
domain_name,
|
||||
merchant_id.clone(),
|
||||
payment_id.clone()
|
||||
)
|
||||
});
|
||||
|
||||
let payment_link_config_encoded_value = payment_link_config.encode_to_value().change_context(
|
||||
errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "payment_link_config",
|
||||
@ -1235,7 +1244,7 @@ async fn create_payment_link(
|
||||
payment_link_id: payment_link_id.clone(),
|
||||
payment_id: payment_id.clone(),
|
||||
merchant_id: merchant_id.clone(),
|
||||
link_to_pay: payment_link.clone(),
|
||||
link_to_pay: open_payment_link.clone(),
|
||||
amount: MinorUnit::from(amount),
|
||||
currency: request.currency,
|
||||
created_at,
|
||||
@ -1245,6 +1254,7 @@ async fn create_payment_link(
|
||||
description,
|
||||
payment_link_config: Some(payment_link_config_encoded_value),
|
||||
profile_id: Some(profile_id),
|
||||
secure_link,
|
||||
};
|
||||
let payment_link_db = db
|
||||
.insert_payment_link(payment_link_req)
|
||||
@ -1254,7 +1264,8 @@ async fn create_payment_link(
|
||||
})?;
|
||||
|
||||
Ok(Some(api_models::payments::PaymentLinkResponse {
|
||||
link: payment_link_db.link_to_pay,
|
||||
link: payment_link_db.link_to_pay.clone(),
|
||||
secure_link: payment_link_db.secure_link,
|
||||
payment_link_id: payment_link_db.payment_link_id,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use actix_web::http::header;
|
||||
#[cfg(feature = "olap")]
|
||||
use common_utils::errors::CustomResult;
|
||||
use common_utils::validation::validate_domain_against_allowed_domains;
|
||||
use diesel_models::generic_link::PayoutLink;
|
||||
use error_stack::{report, ResultExt};
|
||||
use globset::Glob;
|
||||
pub use hyperswitch_domain_models::errors::StorageError;
|
||||
use router_env::{instrument, logger, tracing};
|
||||
use router_env::{instrument, tracing};
|
||||
use url::Url;
|
||||
|
||||
use super::helpers;
|
||||
@ -255,7 +253,7 @@ pub fn validate_payout_link_render_request(
|
||||
})?
|
||||
};
|
||||
|
||||
if is_domain_allowed(&domain_in_req, link_data.allowed_domains) {
|
||||
if validate_domain_against_allowed_domains(&domain_in_req, link_data.allowed_domains) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(report!(errors::ApiErrorResponse::AccessForbidden {
|
||||
@ -269,12 +267,3 @@ pub fn validate_payout_link_render_request(
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn is_domain_allowed(domain: &str, allowed_domains: HashSet<String>) -> bool {
|
||||
allowed_domains.iter().any(|allowed_domain| {
|
||||
Glob::new(allowed_domain)
|
||||
.map(|glob| glob.compile_matcher().is_match(domain))
|
||||
.map_err(|err| logger::error!("Invalid glob pattern! - {:?}", err))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1339,6 +1339,10 @@ impl PaymentLink {
|
||||
web::resource("{merchant_id}/{payment_id}")
|
||||
.route(web::get().to(initiate_payment_link)),
|
||||
)
|
||||
.service(
|
||||
web::resource("s/{merchant_id}/{payment_id}")
|
||||
.route(web::get().to(initiate_secure_payment_link)),
|
||||
)
|
||||
.service(
|
||||
web::resource("status/{merchant_id}/{payment_id}")
|
||||
.route(web::get().to(payment_link_status)),
|
||||
|
||||
@ -188,6 +188,7 @@ impl From<Flow> for ApiIdentifier {
|
||||
|
||||
Flow::PaymentLinkRetrieve
|
||||
| Flow::PaymentLinkInitiate
|
||||
| Flow::PaymentSecureLinkInitiate
|
||||
| Flow::PaymentLinkList
|
||||
| Flow::PaymentLinkStatus => Self::PaymentLink,
|
||||
|
||||
|
||||
@ -82,6 +82,39 @@ pub async fn initiate_payment_link(
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn initiate_secure_payment_link(
|
||||
state: web::Data<AppState>,
|
||||
req: actix_web::HttpRequest,
|
||||
path: web::Path<(common_utils::id_type::MerchantId, String)>,
|
||||
) -> impl Responder {
|
||||
let flow = Flow::PaymentSecureLinkInitiate;
|
||||
let (merchant_id, payment_id) = path.into_inner();
|
||||
let payload = api_models::payments::PaymentLinkInitiateRequest {
|
||||
payment_id,
|
||||
merchant_id: merchant_id.clone(),
|
||||
};
|
||||
let headers = req.headers();
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
payload.clone(),
|
||||
|state, auth, _, _| {
|
||||
initiate_secure_payment_link_flow(
|
||||
state,
|
||||
auth.merchant_account,
|
||||
auth.key_store,
|
||||
payload.merchant_id.clone(),
|
||||
payload.payment_id.clone(),
|
||||
headers,
|
||||
)
|
||||
},
|
||||
&crate::services::authentication::MerchantIdAuth(merchant_id),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Payment Link - List
|
||||
///
|
||||
/// To list the payment links
|
||||
|
||||
@ -47,7 +47,7 @@ use masking::{Maskable, PeekInterface};
|
||||
use router_env::{instrument, metrics::add_attributes, tracing, tracing_actix_web::RequestId, Tag};
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use tera::{Context, Tera};
|
||||
use tera::{Context, Error as TeraError, Tera};
|
||||
|
||||
use self::request::{HeaderExt, RequestBuilderExt};
|
||||
use super::{
|
||||
@ -989,14 +989,18 @@ where
|
||||
let link_type = boxed_generic_link_data.data.to_string();
|
||||
match build_generic_link_html(boxed_generic_link_data.data) {
|
||||
Ok(rendered_html) => {
|
||||
let domains_str = boxed_generic_link_data
|
||||
.allowed_domains
|
||||
.into_iter()
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ");
|
||||
let csp_header = format!("frame-ancestors 'self' {};", domains_str);
|
||||
let headers = HashSet::from([("content-security-policy", csp_header)]);
|
||||
http_response_html_data(rendered_html, Some(headers))
|
||||
let headers = if !boxed_generic_link_data.allowed_domains.is_empty() {
|
||||
let domains_str = boxed_generic_link_data
|
||||
.allowed_domains
|
||||
.into_iter()
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ");
|
||||
let csp_header = format!("frame-ancestors 'self' {};", domains_str);
|
||||
Some(HashSet::from([("content-security-policy", csp_header)]))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
http_response_html_data(rendered_html, headers)
|
||||
}
|
||||
Err(_) => {
|
||||
http_response_err(format!("Error while rendering {} HTML page", link_type))
|
||||
@ -1794,9 +1798,9 @@ pub fn build_redirection_form(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_payment_link_html(
|
||||
fn build_payment_link_template(
|
||||
payment_link_data: PaymentLinkFormData,
|
||||
) -> CustomResult<String, errors::ApiErrorResponse> {
|
||||
) -> CustomResult<(Tera, Context), errors::ApiErrorResponse> {
|
||||
let mut tera = Tera::default();
|
||||
|
||||
// Add modification to css template with dynamic data
|
||||
@ -1847,11 +1851,6 @@ pub fn build_payment_link_html(
|
||||
&get_preload_link_html_template(&payment_link_data.sdk_url),
|
||||
);
|
||||
|
||||
context.insert(
|
||||
"preload_link_tags",
|
||||
&get_preload_link_html_template(&payment_link_data.sdk_url),
|
||||
);
|
||||
|
||||
context.insert(
|
||||
"hyperloader_sdk_link",
|
||||
&get_hyper_loader_sdk(&payment_link_data.sdk_url),
|
||||
@ -1861,13 +1860,43 @@ pub fn build_payment_link_html(
|
||||
|
||||
context.insert("logging_template", &logging_template);
|
||||
|
||||
match tera.render("payment_link", &context) {
|
||||
Ok(rendered_html) => Ok(rendered_html),
|
||||
Err(tera_error) => {
|
||||
Ok((tera, context))
|
||||
}
|
||||
|
||||
pub fn build_payment_link_html(
|
||||
payment_link_data: PaymentLinkFormData,
|
||||
) -> CustomResult<String, errors::ApiErrorResponse> {
|
||||
let (tera, mut context) = build_payment_link_template(payment_link_data)
|
||||
.attach_printable("Failed to build payment link's HTML template")?;
|
||||
let payment_link_initiator =
|
||||
include_str!("../core/payment_link/payment_link_initiate/payment_link_initiator.js")
|
||||
.to_string();
|
||||
context.insert("payment_link_initiator", &payment_link_initiator);
|
||||
|
||||
tera.render("payment_link", &context)
|
||||
.map_err(|tera_error: TeraError| {
|
||||
crate::logger::warn!("{tera_error}");
|
||||
Err(errors::ApiErrorResponse::InternalServerError)?
|
||||
}
|
||||
}
|
||||
report!(errors::ApiErrorResponse::InternalServerError)
|
||||
})
|
||||
.attach_printable("Error while rendering open payment link's HTML template")
|
||||
}
|
||||
|
||||
pub fn build_secure_payment_link_html(
|
||||
payment_link_data: PaymentLinkFormData,
|
||||
) -> CustomResult<String, errors::ApiErrorResponse> {
|
||||
let (tera, mut context) = build_payment_link_template(payment_link_data)
|
||||
.attach_printable("Failed to build payment link's HTML template")?;
|
||||
let payment_link_initiator =
|
||||
include_str!("../core/payment_link/payment_link_initiate/secure_payment_link_initiator.js")
|
||||
.to_string();
|
||||
context.insert("payment_link_initiator", &payment_link_initiator);
|
||||
|
||||
tera.render("payment_link", &context)
|
||||
.map_err(|tera_error: TeraError| {
|
||||
crate::logger::warn!("{tera_error}");
|
||||
report!(errors::ApiErrorResponse::InternalServerError)
|
||||
})
|
||||
.attach_printable("Error while rendering secure payment link's HTML template")
|
||||
}
|
||||
|
||||
fn get_hyper_loader_sdk(sdk_url: &str) -> String {
|
||||
|
||||
@ -5,6 +5,7 @@ use hyperswitch_domain_models::api::{
|
||||
};
|
||||
use tera::{Context, Tera};
|
||||
|
||||
use super::build_secure_payment_link_html;
|
||||
use crate::core::errors;
|
||||
|
||||
pub fn build_generic_link_html(
|
||||
@ -24,6 +25,9 @@ pub fn build_generic_link_html(
|
||||
GenericLinksData::PayoutLinkStatus(pm_collect_data) => {
|
||||
build_payout_link_status_html(&pm_collect_data)
|
||||
}
|
||||
GenericLinksData::SecurePaymentLink(payment_link_data) => {
|
||||
build_secure_payment_link_html(payment_link_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,46 +49,58 @@ pub fn build_generic_expired_link_html(
|
||||
.attach_printable("Failed to render expired link HTML template")
|
||||
}
|
||||
|
||||
pub fn build_payout_link_html(
|
||||
fn build_html_template(
|
||||
link_data: &GenericLinkFormData,
|
||||
) -> CustomResult<String, errors::ApiErrorResponse> {
|
||||
let mut tera = Tera::default();
|
||||
document: &'static str,
|
||||
script: &'static str,
|
||||
styles: &'static str,
|
||||
) -> CustomResult<(Tera, Context), errors::ApiErrorResponse> {
|
||||
let mut tera: Tera = Tera::default();
|
||||
let mut context = Context::new();
|
||||
|
||||
// Insert dynamic context in CSS
|
||||
let css_dynamic_context = "{{ color_scheme }}";
|
||||
let css_template =
|
||||
include_str!("../../core/generic_link/payout_link/initiate/styles.css").to_string();
|
||||
let css_template = styles.to_string();
|
||||
let final_css = format!("{}\n{}", css_dynamic_context, css_template);
|
||||
let _ = tera.add_raw_template("payout_link_styles", &final_css);
|
||||
let _ = tera.add_raw_template("document_styles", &final_css);
|
||||
context.insert("color_scheme", &link_data.css_data);
|
||||
|
||||
// Insert dynamic context in JS
|
||||
let js_dynamic_context = "{{ script_data }}";
|
||||
let js_template = script.to_string();
|
||||
let final_js = format!("{}\n{}", js_dynamic_context, js_template);
|
||||
let _ = tera.add_raw_template("document_scripts", &final_js);
|
||||
context.insert("script_data", &link_data.js_data);
|
||||
|
||||
let css_style_tag = tera
|
||||
.render("payout_link_styles", &context)
|
||||
.render("document_styles", &context)
|
||||
.map(|css| format!("<style>{}</style>", css))
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to render payout link's CSS template")?;
|
||||
|
||||
// Insert dynamic context in JS
|
||||
let js_dynamic_context = "{{ payout_link_context }}";
|
||||
let js_template =
|
||||
include_str!("../../core/generic_link/payout_link/initiate/script.js").to_string();
|
||||
let final_js = format!("{}\n{}", js_dynamic_context, js_template);
|
||||
let _ = tera.add_raw_template("payout_link_script", &final_js);
|
||||
context.insert("payout_link_context", &link_data.js_data);
|
||||
.attach_printable("Failed to render CSS template")?;
|
||||
|
||||
let js_script_tag = tera
|
||||
.render("payout_link_script", &context)
|
||||
.render("document_scripts", &context)
|
||||
.map(|js| format!("<script>{}</script>", js))
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to render payout link's JS template")?;
|
||||
.attach_printable("Failed to render JS template")?;
|
||||
|
||||
// Build HTML
|
||||
let html_template =
|
||||
include_str!("../../core/generic_link/payout_link/initiate/index.html").to_string();
|
||||
let _ = tera.add_raw_template("payout_link", &html_template);
|
||||
// Insert HTML context
|
||||
let html_template = document.to_string();
|
||||
let _ = tera.add_raw_template("html_template", &html_template);
|
||||
context.insert("css_style_tag", &css_style_tag);
|
||||
context.insert("js_script_tag", &js_script_tag);
|
||||
|
||||
Ok((tera, context))
|
||||
}
|
||||
|
||||
pub fn build_payout_link_html(
|
||||
link_data: &GenericLinkFormData,
|
||||
) -> CustomResult<String, errors::ApiErrorResponse> {
|
||||
let document = include_str!("../../core/generic_link/payout_link/initiate/index.html");
|
||||
let script = include_str!("../../core/generic_link/payout_link/initiate/script.js");
|
||||
let styles = include_str!("../../core/generic_link/payout_link/initiate/styles.css");
|
||||
let (tera, mut context) = build_html_template(link_data, document, script, styles)
|
||||
.attach_printable("Failed to build context for payout link's HTML template")?;
|
||||
context.insert(
|
||||
"hyper_sdk_loader_script_tag",
|
||||
&format!(
|
||||
@ -93,7 +109,7 @@ pub fn build_payout_link_html(
|
||||
),
|
||||
);
|
||||
|
||||
tera.render("payout_link", &context)
|
||||
tera.render("html_template", &context)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to render payout link's HTML template")
|
||||
}
|
||||
@ -101,46 +117,14 @@ pub fn build_payout_link_html(
|
||||
pub fn build_pm_collect_link_html(
|
||||
link_data: &GenericLinkFormData,
|
||||
) -> CustomResult<String, errors::ApiErrorResponse> {
|
||||
let mut tera = Tera::default();
|
||||
let mut context = Context::new();
|
||||
|
||||
// Insert dynamic context in CSS
|
||||
let css_dynamic_context = "{{ color_scheme }}";
|
||||
let css_template =
|
||||
include_str!("../../core/generic_link/payment_method_collect/initiate/styles.css")
|
||||
.to_string();
|
||||
let final_css = format!("{}\n{}", css_dynamic_context, css_template);
|
||||
let _ = tera.add_raw_template("pm_collect_link_styles", &final_css);
|
||||
context.insert("color_scheme", &link_data.css_data);
|
||||
|
||||
let css_style_tag = tera
|
||||
.render("pm_collect_link_styles", &context)
|
||||
.map(|css| format!("<style>{}</style>", css))
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to render payment method collect link's CSS template")?;
|
||||
|
||||
// Insert dynamic context in JS
|
||||
let js_dynamic_context = "{{ collect_link_context }}";
|
||||
let js_template =
|
||||
include_str!("../../core/generic_link/payment_method_collect/initiate/script.js")
|
||||
.to_string();
|
||||
let final_js = format!("{}\n{}", js_dynamic_context, js_template);
|
||||
let _ = tera.add_raw_template("pm_collect_link_script", &final_js);
|
||||
context.insert("collect_link_context", &link_data.js_data);
|
||||
|
||||
let js_script_tag = tera
|
||||
.render("pm_collect_link_script", &context)
|
||||
.map(|js| format!("<script>{}</script>", js))
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to render payment method collect link's JS template")?;
|
||||
|
||||
// Build HTML
|
||||
let html_template =
|
||||
include_str!("../../core/generic_link/payment_method_collect/initiate/index.html")
|
||||
.to_string();
|
||||
let _ = tera.add_raw_template("payment_method_collect_link", &html_template);
|
||||
context.insert("css_style_tag", &css_style_tag);
|
||||
context.insert("js_script_tag", &js_script_tag);
|
||||
let document =
|
||||
include_str!("../../core/generic_link/payment_method_collect/initiate/index.html");
|
||||
let script = include_str!("../../core/generic_link/payment_method_collect/initiate/script.js");
|
||||
let styles = include_str!("../../core/generic_link/payment_method_collect/initiate/styles.css");
|
||||
let (tera, mut context) = build_html_template(link_data, document, script, styles)
|
||||
.attach_printable(
|
||||
"Failed to build context for payment method collect link's HTML template",
|
||||
)?;
|
||||
context.insert(
|
||||
"hyper_sdk_loader_script_tag",
|
||||
&format!(
|
||||
@ -149,7 +133,7 @@ pub fn build_pm_collect_link_html(
|
||||
),
|
||||
);
|
||||
|
||||
tera.render("payment_method_collect_link", &context)
|
||||
tera.render("html_template", &context)
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to render payment method collect link's HTML template")
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ impl PaymentLinkResponseExt for RetrievePaymentLinkResponse {
|
||||
expiry: payment_link.fulfilment_time,
|
||||
currency: payment_link.currency,
|
||||
status,
|
||||
secure_link: payment_link.secure_link,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1355,6 +1355,7 @@ impl ForeignFrom<(storage::PaymentLink, payments::PaymentLinkStatus)>
|
||||
description: payment_link_config.description,
|
||||
currency: payment_link_config.currency,
|
||||
status,
|
||||
secure_link: payment_link_config.secure_link,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user