Skip to content

Commit

Permalink
add dynamic payment link
Browse files Browse the repository at this point in the history
  • Loading branch information
Mrudul Vajpayee authored and Mrudul Vajpayee committed Jan 30, 2025
1 parent 3fdc41e commit 8a8006d
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 195 deletions.
4 changes: 2 additions & 2 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6967,7 +6967,7 @@ pub struct RetrievePaymentLinkRequest {
pub struct PaymentLinkResponse {
/// URL for rendering the open payment link
pub link: String,
/// URL for rendering the secure payment link
/// URL for rendering the secure payment link. It is deprecated now.
pub secure_link: Option<String>,
/// Identifier for the payment link
pub payment_link_id: String,
Expand Down Expand Up @@ -6997,7 +6997,7 @@ pub struct RetrievePaymentLinkResponse {
pub status: PaymentLinkStatus,
#[schema(value_type = Option<Currency>)]
pub currency: Option<api_enums::Currency>,
/// Secure payment link (with security checks and listing saved payment methods)
/// Secure payment link (with security checks and listing saved payment methods). It is deprecated now.
pub secure_link: Option<String>,
}

Expand Down
4 changes: 2 additions & 2 deletions crates/diesel_models/src/payment_link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct PaymentLink {
pub payment_link_config: Option<serde_json::Value>,
pub description: Option<String>,
pub profile_id: Option<common_utils::id_type::ProfileId>,
pub secure_link: Option<String>,
pub secure_link: Option<String>, //It is deprecated now.
}

#[derive(
Expand Down Expand Up @@ -55,5 +55,5 @@ pub struct PaymentLinkNew {
pub payment_link_config: Option<serde_json::Value>,
pub description: Option<String>,
pub profile_id: Option<common_utils::id_type::ProfileId>,
pub secure_link: Option<String>,
pub secure_link: Option<String>, //It is deprecated now.
}
2 changes: 1 addition & 1 deletion crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,7 @@ diesel::table! {
#[max_length = 64]
profile_id -> Nullable<Varchar>,
#[max_length = 255]
secure_link -> Nullable<Varchar>,
secure_link -> Nullable<Varchar>, //It is deprecated now.
}
}

Expand Down
2 changes: 0 additions & 2 deletions crates/diesel_models/src/schema_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,8 +982,6 @@ diesel::table! {
description -> Nullable<Varchar>,
#[max_length = 64]
profile_id -> Nullable<Varchar>,
#[max_length = 255]
secure_link -> Nullable<Varchar>,
}
}

Expand Down
217 changes: 117 additions & 100 deletions crates/router/src/core/payment_link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,98 +285,116 @@ pub async fn form_payment_link_data(
))
}

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: common_utils::id_type::PaymentId,
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PaymentLinkType {
SecureLink,
OpenLink,
}

impl PaymentLinkType {
#[cfg(feature = "v1")]
fn get_requested_payment_link_type(request_headers: &header::HeaderMap) -> PaymentLinkType {
match request_headers
.get("sec-fetch-dest")
.and_then(|v| v.to_str().ok())
{
Some("iframe") => PaymentLinkType::SecureLink,
Some(_) | None => PaymentLinkType::OpenLink,
}
}
}

#[cfg(feature = "v1")]
fn generate_secure_payment_link(
state: &SessionState,
request_headers: &header::HeaderMap,
payment_link: &PaymentLink,
payment_link_config: PaymentLinkConfig,
payment_details: api_models::payments::PaymentLinkDetails,
css_script: String,
) -> 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?;

validator::validate_secure_payment_link_render_request(
request_headers,
&payment_link,
payment_link,
&payment_link_config,
)?;
let secure_payment_link_details = api_models::payments::SecurePaymentLinkDetails {
enabled_saved_payment_method: payment_link_config.enabled_saved_payment_method,
hide_card_nickname_field: payment_link_config.hide_card_nickname_field,
show_card_form_by_default: payment_link_config.show_card_form_by_default,
payment_link_details: payment_details.to_owned(),
payment_button_text: payment_link_config.payment_button_text.clone(),
};
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(&payment_details);
let payment_link_data = services::PaymentLinkFormData {
js_script,
sdk_url: state.conf.payment_link.sdk_url.clone(),
css_script: css_script.clone(),
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()
)
})?;

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,
hide_card_nickname_field: payment_link_config.hide_card_nickname_field,
show_card_form_by_default: payment_link_config.show_card_form_by_default,
payment_link_details: *link_details.to_owned(),
payment_button_text: payment_link_config.payment_button_text,
};
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()
)
});
}
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),
locale: DEFAULT_LOCALE.to_string(),
};
logger::info!(
"payment link data, for building secure payment link {:?}",
link_data
);

let link_data = GenericLinks {
allowed_domains,
data: GenericLinksData::SecurePaymentLink(payment_link_data),
locale: DEFAULT_LOCALE.to_string(),
};
logger::info!(
"payment link data, for building secure payment link {:?}",
link_data
);
Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
link_data,
)))
}

Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
link_data,
)))
}
}
#[cfg(feature = "v1")]
fn generate_open_payment_link(
state: &SessionState,
payment_details: api_models::payments::PaymentLinkDetails,
js_script: String,
css_script: String,
) -> RouterResponse<services::PaymentLinkFormData> {
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),
)))
}

pub async fn initiate_payment_link_flow(
Expand All @@ -385,13 +403,15 @@ pub async fn initiate_payment_link_flow(
key_store: domain::MerchantKeyStore,
merchant_id: common_utils::id_type::MerchantId,
payment_id: common_utils::id_type::PaymentId,
request_headers: &header::HeaderMap,
) -> RouterResponse<services::PaymentLinkFormData> {
let (_, payment_details, payment_link_config) =
let (payment_link, 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)?;
let requested_payment_link_type =
PaymentLinkType::get_requested_payment_link_type(request_headers);

match payment_details {
PaymentLinkData::PaymentLinkStatusDetails(status_details) => {
Expand All @@ -407,22 +427,19 @@ pub async fn initiate_payment_link_flow(
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(),
PaymentLinkData::PaymentLinkDetails(payment_details) => match requested_payment_link_type {
PaymentLinkType::SecureLink => generate_secure_payment_link(
&state,
request_headers,
&payment_link,
payment_link_config,
*payment_details,
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),
)))
}
),
PaymentLinkType::OpenLink => {
generate_open_payment_link(&state, *payment_details, js_script, css_script)
}
},
}
}

Expand Down
35 changes: 0 additions & 35 deletions crates/router/src/core/payment_link/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,6 @@ pub fn validate_secure_payment_link_render_request(
)
})?;

// 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
Expand Down
14 changes: 2 additions & 12 deletions crates/router/src/core/payments/operations/payment_create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1571,16 +1571,6 @@ async fn create_payment_link(
locale_str.clone(),
);

let secure_link = payment_link_config.allowed_domains.as_ref().map(|_| {
format!(
"{}/payment_link/s/{}/{}?locale={}",
domain_name,
merchant_id.get_string_repr(),
payment_id.get_string_repr(),
locale_str,
)
});

let payment_link_config_encoded_value = payment_link_config.encode_to_value().change_context(
errors::ApiErrorResponse::InvalidDataValue {
field_name: "payment_link_config",
Expand All @@ -1601,7 +1591,7 @@ async fn create_payment_link(
description,
payment_link_config: Some(payment_link_config_encoded_value),
profile_id: Some(profile_id),
secure_link,
secure_link: None, //It is deprecated now.,
};
let payment_link_db = db
.insert_payment_link(payment_link_req)
Expand All @@ -1612,7 +1602,7 @@ async fn create_payment_link(

Ok(Some(api_models::payments::PaymentLinkResponse {
link: payment_link_db.link_to_pay.clone(),
secure_link: payment_link_db.secure_link,
secure_link: None, //It is deprecated now.
payment_link_id: payment_link_db.payment_link_id,
}))
}
4 changes: 0 additions & 4 deletions crates/router/src/routes/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1744,10 +1744,6 @@ impl PaymentLink {
web::resource("{merchant_id}/{payment_id}")
.route(web::get().to(payment_link::initiate_payment_link)),
)
.service(
web::resource("s/{merchant_id}/{payment_id}")
.route(web::get().to(payment_link::initiate_secure_payment_link)),
)
.service(
web::resource("status/{merchant_id}/{payment_id}")
.route(web::get().to(payment_link::payment_link_status)),
Expand Down
Loading

0 comments on commit 8a8006d

Please sign in to comment.