Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 33 additions & 3 deletions api-reference/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -6992,6 +6992,15 @@
"$ref": "#/components/schemas/PaymentLinkConfigRequest"
},
"nullable": true
},
"allowed_domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "A list of allowed domains (glob patterns) where this link can be embedded / opened from",
"uniqueItems": true,
"nullable": true
}
}
}
Expand Down Expand Up @@ -13792,6 +13801,15 @@
"enabled_saved_payment_method": {
"type": "boolean",
"description": "Enable saved payment method option for payment link"
},
"allowed_domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "A list of allowed domains (glob patterns) where this link can be embedded / opened from",
"uniqueItems": true,
"nullable": true
}
}
},
Expand Down Expand Up @@ -13865,10 +13883,17 @@
],
"properties": {
"link": {
"type": "string"
"type": "string",
"description": "URL for rendering the open payment link"
},
"secure_link": {
"type": "string",
"description": "URL for rendering the secure payment link",
"nullable": true
},
"payment_link_id": {
"type": "string"
"type": "string",
"description": "Identifier for the payment link"
}
}
},
Expand Down Expand Up @@ -19765,7 +19790,7 @@
},
"link_to_pay": {
"type": "string",
"description": "Payment Link"
"description": "Open payment link (without any security checks and listing SPMs)"
},
"amount": {
"type": "integer",
Expand Down Expand Up @@ -19799,6 +19824,11 @@
}
],
"nullable": true
},
"secure_link": {
"type": "string",
"description": "Secure payment link (with security checks and listing saved payment methods)",
"nullable": true
}
}
},
Expand Down
37 changes: 35 additions & 2 deletions crates/api_models/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1729,7 +1729,7 @@ impl BusinessGenericLinkConfig {
.map(|host_domain| link_utils::validate_strict_domain(&host_domain))
.unwrap_or(true);
if !host_domain_valid {
return Err("Invalid host domain name received");
return Err("Invalid host domain name received in payout_link_config");
}

let are_allowed_domains_valid = self
Expand All @@ -1738,7 +1738,7 @@ impl BusinessGenericLinkConfig {
.iter()
.all(|allowed_domain| link_utils::validate_wildcard_domain(allowed_domain));
if !are_allowed_domains_valid {
return Err("Invalid allowed domain names received");
return Err("Invalid allowed domain names received in payout_link_config");
}

Ok(())
Expand All @@ -1755,6 +1755,37 @@ pub struct BusinessPaymentLinkConfig {
pub default_config: Option<PaymentLinkConfigRequest>,
/// list of configs for multi theme setup
pub business_specific_configs: Option<HashMap<String, PaymentLinkConfigRequest>>,
/// A list of allowed domains (glob patterns) where this link can be embedded / opened from
#[schema(value_type = Option<HashSet<String>>)]
pub allowed_domains: Option<HashSet<String>>,
}

impl BusinessPaymentLinkConfig {
pub fn validate(&self) -> Result<(), &str> {
let host_domain_valid = self
.domain_name
.clone()
.map(|host_domain| link_utils::validate_strict_domain(&host_domain))
.unwrap_or(true);
if !host_domain_valid {
return Err("Invalid host domain name received in payment_link_config");
}

let are_allowed_domains_valid = self
.allowed_domains
.clone()
.map(|allowed_domains| {
allowed_domains
.iter()
.all(|allowed_domain| link_utils::validate_wildcard_domain(allowed_domain))
})
.unwrap_or(true);
if !are_allowed_domains_valid {
return Err("Invalid allowed domain names received in payment_link_config");
}

Ok(())
}
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
Expand Down Expand Up @@ -1793,6 +1824,8 @@ pub struct PaymentLinkConfig {
pub display_sdk_only: bool,
/// Enable saved payment method option for payment link
pub enabled_saved_payment_method: bool,
/// A list of allowed domains (glob patterns) where this link can be embedded / opened from
pub allowed_domains: Option<HashSet<String>>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
Expand Down
18 changes: 15 additions & 3 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5300,7 +5300,11 @@ pub struct RetrievePaymentLinkRequest {

#[derive(Clone, Debug, serde::Serialize, PartialEq, ToSchema)]
pub struct PaymentLinkResponse {
/// URL for rendering the open payment link
pub link: String,
/// URL for rendering the secure payment link
pub secure_link: Option<String>,
/// Identifier for the payment link
pub payment_link_id: String,
}

Expand All @@ -5311,7 +5315,7 @@ pub struct RetrievePaymentLinkResponse {
/// Identifier for Merchant
#[schema(value_type = String)]
pub merchant_id: id_type::MerchantId,
/// Payment Link
/// Open payment link (without any security checks and listing SPMs)
pub link_to_pay: String,
/// The payment amount. Amount for the payment in the lowest denomination of the currency
#[schema(value_type = i64, example = 6540)]
Expand All @@ -5328,6 +5332,8 @@ 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)
pub secure_link: Option<String>,
}

#[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]
Expand All @@ -5339,8 +5345,8 @@ pub struct PaymentLinkInitiateRequest {

#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum PaymentLinkData<'a> {
PaymentLinkDetails(&'a PaymentLinkDetails),
pub enum PaymentLinkData {
PaymentLinkDetails(PaymentLinkDetails),
PaymentLinkStatusDetails(PaymentLinkStatusDetails),
}

Expand All @@ -5362,7 +5368,13 @@ pub struct PaymentLinkDetails {
pub merchant_description: Option<String>,
pub sdk_layout: String,
pub display_sdk_only: bool,
}

#[derive(Debug, serde::Serialize, Clone)]
pub struct SecurePaymentLinkDetails {
pub enabled_saved_payment_method: bool,
#[serde(flatten)]
pub payment_link_details: PaymentLinkDetails,
}

#[derive(Debug, serde::Serialize)]
Expand Down
1 change: 1 addition & 0 deletions crates/common_utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ bytes = "1.6.0"
diesel = "2.1.5"
error-stack = "0.4.1"
futures = { version = "0.3.30", optional = true }
globset = "0.4.14"
hex = "0.4.3"
http = "0.2.12"
md5 = "0.7.0"
Expand Down
5 changes: 5 additions & 0 deletions crates/common_utils/src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Commonly used constants

use std::collections::HashSet;

/// Number of characters in a generated ID
pub const ID_LENGTH: usize = 20;

Expand Down Expand Up @@ -81,6 +83,9 @@ pub const DEFAULT_DISPLAY_SDK_ONLY: bool = false;
/// Default bool to enable saved payment method
pub const DEFAULT_ENABLE_SAVED_PAYMENT_METHOD: bool = false;

/// Default allowed domains for payment links
pub const DEFAULT_ALLOWED_DOMAINS: Option<HashSet<String>> = None;

/// Default ttl for Extended card info in redis (in seconds)
pub const DEFAULT_TTL_FOR_EXTENDED_CARD_INFO: u16 = 15 * 60;

Expand Down
24 changes: 24 additions & 0 deletions crates/common_utils/src/validation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Custom validations for some shared types.

use std::collections::HashSet;

use error_stack::report;
use globset::Glob;
use once_cell::sync::Lazy;
use regex::Regex;
#[cfg(feature = "logs")]
Expand Down Expand Up @@ -57,6 +60,27 @@ pub fn validate_email(email: &str) -> CustomResult<(), ValidationError> {
Ok(())
}

/// Checks whether a given domain matches against a list of valid domain glob patterns
pub fn validate_domain_against_allowed_domains(
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| {
let err_msg = format!(
"Invalid glob pattern for configured allowed_domain [{:?}]! - {:?}",
allowed_domain, err
);
#[cfg(feature = "logs")]
logger::error!(err_msg);
err_msg
})
.unwrap_or(false)
})
}

#[cfg(test)]
mod tests {
use fake::{faker::internet::en::SafeEmail, Fake};
Expand Down
2 changes: 2 additions & 0 deletions crates/diesel_models/src/payment_link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub struct PaymentLink {
pub payment_link_config: Option<serde_json::Value>,
pub description: Option<String>,
pub profile_id: Option<String>,
pub secure_link: Option<String>,
}

#[derive(
Expand Down Expand Up @@ -54,4 +55,5 @@ pub struct PaymentLinkNew {
pub payment_link_config: Option<serde_json::Value>,
pub description: Option<String>,
pub profile_id: Option<String>,
pub secure_link: Option<String>,
}
2 changes: 2 additions & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,8 @@ diesel::table! {
description -> Nullable<Varchar>,
#[max_length = 64]
profile_id -> Nullable<Varchar>,
#[max_length = 255]
secure_link -> Nullable<Varchar>,
}
}

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

Expand Down
2 changes: 2 additions & 0 deletions crates/hyperswitch_domain_models/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ pub enum GenericLinksData {
PayoutLink(GenericLinkFormData),
PayoutLinkStatus(GenericLinkStatusData),
PaymentMethodCollectStatus(GenericLinkStatusData),
SecurePaymentLink(PaymentLinkFormData),
}

impl Display for GenericLinksData {
Expand All @@ -84,6 +85,7 @@ impl Display for GenericLinksData {
Self::PayoutLink(_) => "PayoutLink",
Self::PayoutLinkStatus(_) => "PayoutLinkStatus",
Self::PaymentMethodCollectStatus(_) => "PaymentMethodCollectStatus",
Self::SecurePaymentLink(_) => "SecurePaymentLink",
}
)
}
Expand Down
1 change: 0 additions & 1 deletion crates/router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ dyn-clone = "1.0.17"
encoding_rs = "0.8.33"
error-stack = "0.4.1"
futures = "0.3.30"
globset = "0.4.14"
hex = "0.4.3"
http = "0.2.12"
hyper = "0.14.28"
Expand Down
9 changes: 6 additions & 3 deletions crates/router/src/core/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3139,12 +3139,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()?;

Expand Down
Loading
Loading