diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index ed49b6f27b5e..c588bb87189f 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -95,15 +95,8 @@ pub struct MerchantAccountCreate { #[schema(value_type = Option,example = json!({"type": "single", "data": "signifyd"}))] pub frm_routing_algorithm: Option, - ///Will be used to expire client secret after certain amount of time to be supplied in seconds - ///(900) for 15 mins - #[schema(example = 900)] - pub intent_fulfillment_time: Option, - /// The id of the organization to which the merchant belongs to pub organization_id: Option, - - pub payment_link_config: Option, } #[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] @@ -185,16 +178,10 @@ pub struct MerchantAccountUpdate { #[schema(value_type = Option,example = json!({"type": "single", "data": "signifyd"}))] pub frm_routing_algorithm: Option, - ///Will be used to expire client secret after certain amount of time to be supplied in seconds - ///(900) for 15 mins - pub intent_fulfillment_time: Option, - /// The default business profile that must be used for creating merchant accounts and payments /// To unset this field, pass an empty string #[schema(max_length = 64)] pub default_profile: Option, - - pub payment_link_config: Option, } #[derive(Clone, Debug, ToSchema, Serialize)] @@ -288,8 +275,6 @@ pub struct MerchantAccountResponse { /// A enum value to indicate the status of recon service. By default it is not_requested. #[schema(value_type = ReconStatus, example = "not_requested")] pub recon_status: enums::ReconStatus, - - pub payment_link_config: Option, } #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] @@ -460,26 +445,6 @@ pub struct PrimaryBusinessDetails { pub business: String, } -#[derive(Clone, Debug, Deserialize, ToSchema, Serialize, PartialEq)] -#[serde(deny_unknown_fields)] -pub struct PaymentLinkConfig { - #[schema( - max_length = 255, - max_length = 255, - example = "https://i.imgur.com/RfxPFQo.png" - )] - pub merchant_logo: Option, - pub color_scheme: Option, -} - -#[derive(Clone, Debug, Deserialize, ToSchema, Serialize, PartialEq)] -#[serde(deny_unknown_fields)] - -pub struct PaymentLinkColorSchema { - pub background_primary_color: Option, - pub sdk_theme: Option, -} - #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] #[serde(deny_unknown_fields)] pub struct WebhookDetails { @@ -1048,6 +1013,13 @@ pub struct BusinessProfileCreate { /// Verified applepay domains for a particular profile pub applepay_verified_domains: Option>, + + /// Client Secret Default expiry for all payments created under this business profile + #[schema(example = 900)] + pub session_expiry: Option, + + /// Default Payment Link config for all payment links created under this business profile + pub payment_link_config: Option, } #[derive(Clone, Debug, ToSchema, Serialize)] @@ -1112,6 +1084,13 @@ pub struct BusinessProfileResponse { /// Verified applepay domains for a particular profile pub applepay_verified_domains: Option>, + + /// Client Secret Default expiry for all payments created under this business profile + #[schema(example = 900)] + pub session_expiry: Option, + + /// Default Payment Link config for all payment links created under this business profile + pub payment_link_config: Option, } #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] @@ -1169,4 +1148,41 @@ pub struct BusinessProfileUpdate { /// Verified applepay domains for a particular profile pub applepay_verified_domains: Option>, + + /// Client Secret Default expiry for all payments created under this business profile + #[schema(example = 900)] + pub session_expiry: Option, + + /// Default Payment Link config for all payment links created under this business profile + pub payment_link_config: Option, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct BusinessPaymentLinkConfig { + pub domain_name: Option, + #[serde(flatten)] + pub config: PaymentLinkConfigRequest, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct PaymentLinkConfigRequest { + /// custom theme for the payment link + #[schema(value_type = Option, max_length = 255, example = "#4E6ADD")] + pub theme: Option, + /// merchant display logo + #[schema(value_type = Option, max_length = 255, example = "https://i.pinimg.com/736x/4d/83/5c/4d835ca8aafbbb15f84d07d926fda473.jpg")] + pub logo: Option, + /// Custom merchant name for payment link + #[schema(value_type = Option, max_length = 255, example = "hyperswitch")] + pub seller_name: Option, +} + +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, ToSchema)] +pub struct PaymentLinkConfig { + /// custom theme for the payment link + pub theme: String, + /// merchant display logo + pub logo: String, + /// Custom merchant name for payment link + pub seller_name: String, } diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 5a894e868a3a..0bfee76304f7 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -296,8 +296,14 @@ pub struct PaymentsRequest { /// additional data that might be required by hyperswitch pub feature_metadata: Option, - /// payment link object required for generating the payment_link - pub payment_link_object: Option, + + /// Whether to get the payment link (if applicable) + #[schema(default = false, example = true)] + pub payment_link: Option, + + /// custom payment link config for the particular payment + #[schema(value_type = Option)] + pub payment_link_config: Option, /// The business profile to use for this payment, if not passed the default business profile /// associated with the merchant account will be used. @@ -314,6 +320,11 @@ pub struct PaymentsRequest { ///Request for an incremental authorization pub request_incremental_authorization: Option, + ///Will be used to expire client secret after certain amount of time to be supplied in seconds + ///(900) for 15 mins + #[schema(example = 900)] + pub session_expiry: Option, + /// additional data related to some frm connectors pub frm_metadata: Option, } @@ -3309,17 +3320,6 @@ mod tests { } } -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] -pub struct PaymentLinkObject { - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - pub link_expiry: Option, - pub merchant_custom_domain_name: Option, - #[schema(value_type = PaymentLinkConfig)] - pub payment_link_config: Option, - /// Custom merchant name for payment link - pub custom_merchant_name: Option, -} - #[derive(Default, Debug, serde::Deserialize, Clone, ToSchema, serde::Serialize)] pub struct RetrievePaymentLinkRequest { pub client_secret: Option, @@ -3339,10 +3339,10 @@ pub struct RetrievePaymentLinkResponse { pub amount: i64, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - pub link_expiry: Option, + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub expiry: Option, pub description: Option, - pub status: String, + pub status: PaymentLinkStatus, #[schema(value_type = Option)] pub currency: Option, } @@ -3360,14 +3360,15 @@ pub struct PaymentLinkDetails { pub pub_key: String, pub client_secret: String, pub payment_id: String, - #[serde(with = "common_utils::custom_serde::iso8601::option")] - pub expiry: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub session_expiry: PrimitiveDateTime, pub merchant_logo: String, pub return_url: String, pub merchant_name: String, pub order_details: Option>, pub max_items_visible_after_collapse: i8, - pub sdk_theme: Option, + pub theme: String, + pub merchant_description: Option, } #[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)] @@ -3424,6 +3425,13 @@ pub struct PaymentLinkListResponse { pub data: Vec, } +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct PaymentCreatePaymentLinkConfig { + #[serde(flatten)] + #[schema(value_type = Option)] + pub config: admin::PaymentLinkConfigRequest, +} + #[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct OrderDetailsWithStringAmount { /// Name of the product that is being purchased @@ -3437,3 +3445,9 @@ pub struct OrderDetailsWithStringAmount { /// Product Image link pub product_img_link: Option, } + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +pub enum PaymentLinkStatus { + Active, + Expired, +} diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs index 7f9533d7eadd..169cb972c066 100644 --- a/crates/common_utils/src/consts.rs +++ b/crates/common_utils/src/consts.rs @@ -33,11 +33,8 @@ pub const SURCHARGE_PERCENTAGE_PRECISION_LENGTH: u8 = 2; /// Header Key for application overhead of a request pub const X_HS_LATENCY: &str = "x-hs-latency"; -/// SDK Default Theme const -pub const DEFAULT_SDK_THEME: &str = "#7EA8F6"; - /// Default Payment Link Background color -pub const DEFAULT_BACKGROUND_COLOR: &str = "#E5E5E5"; +pub const DEFAULT_BACKGROUND_COLOR: &str = "#212E46"; /// Default product Img Link pub const DEFAULT_PRODUCT_IMG: &str = "https://i.imgur.com/On3VtKF.png"; @@ -50,3 +47,9 @@ pub const PROPHETPAY_REDIRECT_URL: &str = "https://ccm-thirdparty.cps.golf/hp/to /// Variable which store the card token for Prophetpay pub const PROPHETPAY_TOKEN: &str = "cctoken"; + +/// Payment intent fulfillment default timeout (in seconds) +pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60; + +/// Payment intent default client secret expiry (in seconds) +pub const DEFAULT_SESSION_EXPIRY: i64 = 15 * 60; diff --git a/crates/data_models/src/payments.rs b/crates/data_models/src/payments.rs index b3e2c2e520a5..cc6b03f89a5b 100644 --- a/crates/data_models/src/payments.rs +++ b/crates/data_models/src/payments.rs @@ -53,4 +53,5 @@ pub struct PaymentIntent { pub request_incremental_authorization: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + pub session_expiry: Option, } diff --git a/crates/data_models/src/payments/payment_intent.rs b/crates/data_models/src/payments/payment_intent.rs index 5389cfdd78de..80671ec7f61d 100644 --- a/crates/data_models/src/payments/payment_intent.rs +++ b/crates/data_models/src/payments/payment_intent.rs @@ -110,6 +110,7 @@ pub struct PaymentIntentNew { pub request_incremental_authorization: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + pub session_expiry: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -162,6 +163,7 @@ pub enum PaymentIntentUpdate { metadata: Option, payment_confirm_source: Option, updated_by: String, + session_expiry: Option, }, PaymentAttemptAndAttemptCountUpdate { active_attempt_id: String, @@ -226,6 +228,7 @@ pub struct PaymentIntentUpdateInternal { pub surcharge_applicable: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + pub session_expiry: Option, } impl From for PaymentIntentUpdateInternal { @@ -249,6 +252,7 @@ impl From for PaymentIntentUpdateInternal { metadata, payment_confirm_source, updated_by, + session_expiry, } => Self { amount: Some(amount), currency: Some(currency), @@ -268,6 +272,7 @@ impl From for PaymentIntentUpdateInternal { metadata, payment_confirm_source, updated_by, + session_expiry, ..Default::default() }, PaymentIntentUpdate::MetadataUpdate { diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index 700104aaaecc..ad66eb7f6f16 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -32,6 +32,8 @@ pub struct BusinessProfile { pub is_recon_enabled: bool, #[diesel(deserialize_as = super::OptionalDieselArray)] pub applepay_verified_domains: Option>, + pub payment_link_config: Option, + pub session_expiry: Option, } #[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)] @@ -55,6 +57,8 @@ pub struct BusinessProfileNew { pub is_recon_enabled: bool, #[diesel(deserialize_as = super::OptionalDieselArray)] pub applepay_verified_domains: Option>, + pub payment_link_config: Option, + pub session_expiry: Option, } #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] @@ -75,6 +79,8 @@ pub struct BusinessProfileUpdateInternal { pub is_recon_enabled: Option, #[diesel(deserialize_as = super::OptionalDieselArray)] pub applepay_verified_domains: Option>, + pub payment_link_config: Option, + pub session_expiry: Option, } impl From for BusinessProfile { @@ -97,6 +103,8 @@ impl From for BusinessProfile { payout_routing_algorithm: new.payout_routing_algorithm, is_recon_enabled: new.is_recon_enabled, applepay_verified_domains: new.applepay_verified_domains, + payment_link_config: new.payment_link_config, + session_expiry: new.session_expiry, } } } @@ -118,6 +126,8 @@ impl BusinessProfileUpdateInternal { payout_routing_algorithm, is_recon_enabled, applepay_verified_domains, + payment_link_config, + session_expiry, } = self; BusinessProfile { profile_name: profile_name.unwrap_or(source.profile_name), @@ -136,6 +146,8 @@ impl BusinessProfileUpdateInternal { payout_routing_algorithm, is_recon_enabled: is_recon_enabled.unwrap_or(source.is_recon_enabled), applepay_verified_domains, + payment_link_config, + session_expiry, ..source } } diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index df567f583572..6b546f90787e 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -55,18 +55,11 @@ pub struct PaymentIntent { pub request_incremental_authorization: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + pub session_expiry: Option, } #[derive( - Clone, - Debug, - Default, - Eq, - PartialEq, - Insertable, - router_derive::DebugAsDisplay, - Serialize, - Deserialize, + Clone, Debug, Eq, PartialEq, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize, )] #[diesel(table_name = payment_intent)] pub struct PaymentIntentNew { @@ -112,6 +105,8 @@ pub struct PaymentIntentNew { pub request_incremental_authorization: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub session_expiry: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -164,6 +159,7 @@ pub enum PaymentIntentUpdate { metadata: Option, payment_confirm_source: Option, updated_by: String, + session_expiry: Option, }, PaymentAttemptAndAttemptCountUpdate { active_attempt_id: String, @@ -229,6 +225,7 @@ pub struct PaymentIntentUpdateInternal { pub surcharge_applicable: Option, pub incremental_authorization_allowed: Option, pub authorization_count: Option, + pub session_expiry: Option, } impl PaymentIntentUpdate { @@ -261,6 +258,7 @@ impl PaymentIntentUpdate { surcharge_applicable, incremental_authorization_allowed, authorization_count, + session_expiry, } = self.into(); PaymentIntent { amount: amount.unwrap_or(source.amount), @@ -290,10 +288,10 @@ impl PaymentIntentUpdate { payment_confirm_source: payment_confirm_source.or(source.payment_confirm_source), updated_by, surcharge_applicable: surcharge_applicable.or(source.surcharge_applicable), - incremental_authorization_allowed: incremental_authorization_allowed .or(source.incremental_authorization_allowed), authorization_count: authorization_count.or(source.authorization_count), + session_expiry, ..source } } @@ -320,6 +318,7 @@ impl From for PaymentIntentUpdateInternal { metadata, payment_confirm_source, updated_by, + session_expiry, } => Self { amount: Some(amount), currency: Some(currency), @@ -339,6 +338,7 @@ impl From for PaymentIntentUpdateInternal { metadata, payment_confirm_source, updated_by, + session_expiry, ..Default::default() }, PaymentIntentUpdate::MetadataUpdate { diff --git a/crates/diesel_models/src/payment_link.rs b/crates/diesel_models/src/payment_link.rs index 999a6767d8f3..ed0e979d0268 100644 --- a/crates/diesel_models/src/payment_link.rs +++ b/crates/diesel_models/src/payment_link.rs @@ -18,17 +18,17 @@ pub struct PaymentLink { pub created_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601")] pub last_modified_at: PrimitiveDateTime, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + #[serde(with = "common_utils::custom_serde::iso8601::option")] pub fulfilment_time: Option, pub custom_merchant_name: Option, pub payment_link_config: Option, pub description: Option, + pub profile_id: Option, } #[derive( Clone, Debug, - Default, Eq, PartialEq, Insertable, @@ -48,9 +48,10 @@ pub struct PaymentLinkNew { pub created_at: Option, #[serde(with = "common_utils::custom_serde::iso8601::option")] pub last_modified_at: Option, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + #[serde(with = "common_utils::custom_serde::iso8601::option")] pub fulfilment_time: Option, pub custom_merchant_name: Option, pub payment_link_config: Option, pub description: Option, + pub profile_id: Option, } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index f4a0437c6ccb..f4e41cefdef7 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -83,6 +83,8 @@ diesel::table! { payout_routing_algorithm -> Nullable, is_recon_enabled -> Bool, applepay_verified_domains -> Nullable>>, + payment_link_config -> Nullable, + session_expiry -> Nullable, } } @@ -705,6 +707,7 @@ diesel::table! { request_incremental_authorization -> Nullable, incremental_authorization_allowed -> Nullable, authorization_count -> Nullable, + session_expiry -> Nullable, } } @@ -731,6 +734,8 @@ diesel::table! { payment_link_config -> Nullable, #[max_length = 255] description -> Nullable, + #[max_length = 64] + profile_id -> Nullable, } } diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index eff42c0cd7c4..afe761846304 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -24,6 +24,9 @@ pub const REQUEST_TIMEOUT_ERROR_MESSAGE_FROM_PSYNC: &str = ///Payment intent fulfillment default timeout (in seconds) pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60; +/// Payment intent default client secret expiry (in seconds) +pub const DEFAULT_SESSION_EXPIRY: i64 = 15 * 60; + // String literals pub(crate) const NO_ERROR_MESSAGE: &str = "No error message"; pub(crate) const NO_ERROR_CODE: &str = "No error code"; @@ -71,4 +74,10 @@ pub const VERIFY_CONNECTOR_ID_PREFIX: &str = "conn_verify"; #[cfg(feature = "olap")] pub const VERIFY_CONNECTOR_MERCHANT_ID: &str = "test_merchant"; +/// Max payment session expiry +pub const MAX_SESSION_EXPIRY: u32 = 7890000; + +/// Min payment session expiry +pub const MIN_SESSION_EXPIRY: u32 = 60; + pub const LOCKER_HEALTH_CALL_PATH: &str = "/health"; diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 84a2f442de8f..538105932060 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -140,17 +140,6 @@ pub async fn create_merchant_account( .transpose()? .map(Secret::new); - let payment_link_config = req - .payment_link_config - .as_ref() - .map(|pl_metadata| { - utils::Encode::::encode_to_value(pl_metadata) - .change_context(errors::ApiErrorResponse::InvalidDataValue { - field_name: "payment_link_config", - }) - }) - .transpose()?; - let organization_id = if let Some(organization_id) = req.organization_id.as_ref() { db.find_organization_by_org_id(organization_id) .await @@ -199,15 +188,15 @@ pub async fn create_merchant_account( primary_business_details, created_at: date_time::now(), modified_at: date_time::now(), + intent_fulfillment_time: None, frm_routing_algorithm: req.frm_routing_algorithm, - intent_fulfillment_time: req.intent_fulfillment_time.map(i64::from), payout_routing_algorithm: req.payout_routing_algorithm, id: None, organization_id, is_recon_enabled: false, default_profile: None, recon_status: diesel_models::enums::ReconStatus::NotRequested, - payment_link_config, + payment_link_config: None, }) } .await @@ -429,6 +418,8 @@ pub async fn update_business_profile_cascade( frm_routing_algorithm: None, payout_routing_algorithm: None, applepay_verified_domains: None, + payment_link_config: None, + session_expiry: None, }; let update_futures = business_profiles.iter().map(|business_profile| async { @@ -581,10 +572,10 @@ pub async fn merchant_account_update( publishable_key: None, primary_business_details, frm_routing_algorithm: req.frm_routing_algorithm, - intent_fulfillment_time: req.intent_fulfillment_time.map(i64::from), + intent_fulfillment_time: None, payout_routing_algorithm: req.payout_routing_algorithm, default_profile: business_profile_id_update, - payment_link_config: req.payment_link_config, + payment_link_config: None, }; let response = db @@ -1426,6 +1417,9 @@ pub async fn create_business_profile( request: api::BusinessProfileCreate, merchant_id: &str, ) -> RouterResponse { + if let Some(session_expiry) = &request.session_expiry { + helpers::validate_session_expiry(session_expiry.to_owned())?; + } let db = state.store.as_ref(); let key_store = db .get_merchant_key_store_by_merchant_id(merchant_id, &db.get_master_key().to_vec().into()) @@ -1539,6 +1533,10 @@ pub async fn update_business_profile( })? } + if let Some(session_expiry) = &request.session_expiry { + helpers::validate_session_expiry(session_expiry.to_owned())?; + } + let webhook_details = request .webhook_details .as_ref() @@ -1561,6 +1559,17 @@ pub async fn update_business_profile( .attach_printable("Invalid routing algorithm given")?; } + let payment_link_config = request + .payment_link_config + .as_ref() + .map(|pl_metadata| { + utils::Encode::::encode_to_value(pl_metadata) + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "payment_link_config", + }) + }) + .transpose()?; + let business_profile_update = storage::business_profile::BusinessProfileUpdateInternal { profile_name: request.profile_name, modified_at: Some(date_time::now()), @@ -1576,6 +1585,8 @@ pub async fn update_business_profile( payout_routing_algorithm: request.payout_routing_algorithm, is_recon_enabled: None, applepay_verified_domains: request.applepay_verified_domains, + payment_link_config, + session_expiry: request.session_expiry.map(i64::from), }; let updated_business_profile = db diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index 81b06f5f9aa8..f2043d392ab2 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -1,7 +1,8 @@ use api_models::admin as admin_types; use common_utils::{ consts::{ - DEFAULT_BACKGROUND_COLOR, DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_THEME, + DEFAULT_BACKGROUND_COLOR, DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, + DEFAULT_SESSION_EXPIRY, }, ext_traits::{OptionExt, ValueExt}, }; @@ -27,15 +28,20 @@ pub async fn retrieve_payment_link( payment_link_id: String, ) -> RouterResponse { let db = &*state.store; - let payment_link_object = db + let payment_link_config = db .find_payment_link_by_payment_link_id(&payment_link_id) .await .to_not_found_response(errors::ApiErrorResponse::PaymentLinkNotFound)?; - let status = check_payment_link_status(payment_link_object.fulfilment_time); + let session_expiry = payment_link_config.fulfilment_time.unwrap_or_else(|| { + common_utils::date_time::now() + .saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY)) + }); + + let status = check_payment_link_status(session_expiry); let response = api_models::payments::RetrievePaymentLinkResponse::foreign_from(( - payment_link_object, + payment_link_config, status, )); Ok(services::ApplicationResponse::Json(response)) @@ -74,15 +80,25 @@ pub async fn intiate_payment_link_flow( "use payment link for", )?; + let merchant_name_from_merchant_account = merchant_account + .merchant_name + .clone() + .map(|merchant_name| merchant_name.into_inner().peek().to_owned()) + .unwrap_or_default(); + let payment_link = db .find_payment_link_by_payment_link_id(&payment_link_id) .await .to_not_found_response(errors::ApiErrorResponse::PaymentLinkNotFound)?; - let payment_link_config = if let Some(pl_config) = payment_link.payment_link_config.clone() { - extract_payment_link_config(Some(pl_config))? + let payment_link_config = if let Some(pl_config_value) = payment_link.payment_link_config { + extract_payment_link_config(pl_config_value)? } else { - extract_payment_link_config(merchant_account.payment_link_config.clone())? + admin_types::PaymentLinkConfig { + theme: DEFAULT_BACKGROUND_COLOR.to_string(), + logo: DEFAULT_MERCHANT_LOGO.to_string(), + seller_name: merchant_name_from_merchant_account, + } }; let return_url = if let Some(payment_create_return_url) = payment_intent.return_url { @@ -102,8 +118,13 @@ pub async fn intiate_payment_link_flow( )?; let order_details = validate_order_details(payment_intent.order_details, currency)?; - let (default_sdk_theme, default_background_color) = - (DEFAULT_SDK_THEME, DEFAULT_BACKGROUND_COLOR); + let session_expiry = payment_link.fulfilment_time.unwrap_or_else(|| { + common_utils::date_time::now() + .saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY)) + }); + + // converting first letter of merchant name to upperCase + let merchant_name = capitalize_first_char(&payment_link_config.seller_name); let payment_details = api_models::payments::PaymentLinkDetails { amount: currency @@ -112,38 +133,20 @@ pub async fn intiate_payment_link_flow( .change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?, currency, payment_id: payment_intent.payment_id, - merchant_name: payment_link.custom_merchant_name.unwrap_or( - merchant_account - .merchant_name - .map(|merchant_name| merchant_name.into_inner().peek().to_owned()) - .unwrap_or_default(), - ), + merchant_name, order_details, return_url, - expiry: payment_link.fulfilment_time, + session_expiry, pub_key, client_secret, - merchant_logo: payment_link_config - .clone() - .map(|pl_config| { - pl_config - .merchant_logo - .unwrap_or(DEFAULT_MERCHANT_LOGO.to_string()) - }) - .unwrap_or_default(), + merchant_logo: payment_link_config.clone().logo, max_items_visible_after_collapse: 3, - sdk_theme: payment_link_config.clone().and_then(|pl_config| { - pl_config - .color_scheme - .map(|color| color.sdk_theme.unwrap_or(default_sdk_theme.to_string())) - }), + theme: payment_link_config.clone().theme, + merchant_description: payment_intent.description, }; let js_script = get_js_script(payment_details)?; - let css_script = get_color_scheme_css( - payment_link_config.clone(), - default_background_color.to_string(), - ); + let css_script = get_color_scheme_css(payment_link_config.clone()); let payment_link_data = services::PaymentLinkFormData { js_script, sdk_url: state.conf.payment_link.sdk_url.clone(), @@ -168,20 +171,8 @@ fn get_js_script( Ok(format!("window.__PAYMENT_DETAILS = {payment_details_str};")) } -fn get_color_scheme_css( - payment_link_config: Option, - default_primary_color: String, -) -> String { - let background_primary_color = payment_link_config - .and_then(|pl_config| { - pl_config.color_scheme.map(|color| { - color - .background_primary_color - .unwrap_or(default_primary_color.clone()) - }) - }) - .unwrap_or(default_primary_color); - +fn get_color_scheme_css(payment_link_config: api_models::admin::PaymentLinkConfig) -> String { + let background_primary_color = payment_link_config.theme; format!( ":root {{ --primary-color: {background_primary_color}; @@ -226,13 +217,15 @@ pub async fn list_payment_link( Ok(services::ApplicationResponse::Json(payment_link_list)) } -pub fn check_payment_link_status(fulfillment_time: Option) -> String { - let curr_time = Some(common_utils::date_time::now()); +pub fn check_payment_link_status( + max_age: PrimitiveDateTime, +) -> api_models::payments::PaymentLinkStatus { + let curr_time = common_utils::date_time::now(); - if curr_time > fulfillment_time { - "expired".to_string() + if curr_time > max_age { + api_models::payments::PaymentLinkStatus::Expired } else { - "active".to_string() + api_models::payments::PaymentLinkStatus::Active } } @@ -276,7 +269,8 @@ fn validate_order_details( .to_currency_base_unit(order.amount) .into_report() .change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?; - order_details_amount_string.product_name = order.product_name.clone(); + order_details_amount_string.product_name = + capitalize_first_char(&order.product_name.clone()); order_details_amount_string.quantity = order.quantity; order_details_amount_string_array.push(order_details_amount_string) } @@ -287,16 +281,91 @@ fn validate_order_details( Ok(updated_order_details) } -fn extract_payment_link_config( - pl_config: Option, -) -> Result, error_stack::Report> { - pl_config - .map(|config| { - serde_json::from_value::(config) - .into_report() - .change_context(errors::ApiErrorResponse::InvalidDataValue { - field_name: "payment_link_config", - }) +pub fn extract_payment_link_config( + pl_config: serde_json::Value, +) -> Result> { + serde_json::from_value::(pl_config.clone()) + .into_report() + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "payment_link_config", }) - .transpose() +} + +pub fn get_payment_link_config_based_on_priority( + payment_create_link_config: Option, + business_link_config: Option, + merchant_name: String, + default_domain_name: String, +) -> Result<(admin_types::PaymentLinkConfig, String), error_stack::Report> +{ + let (domain_name, business_config) = 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")?; + + ( + extracted_value + .domain_name + .clone() + .map(|d_name| format!("https://{}", d_name)) + .unwrap_or_else(|| default_domain_name.clone()), + Some(extracted_value.config), + ) + } else { + (default_domain_name, None) + }; + + let theme = payment_create_link_config + .as_ref() + .and_then(|pc_config| pc_config.config.theme.clone()) + .or_else(|| { + business_config + .as_ref() + .and_then(|business_config| business_config.theme.clone()) + }) + .unwrap_or(DEFAULT_BACKGROUND_COLOR.to_string()); + + let logo = payment_create_link_config + .as_ref() + .and_then(|pc_config| pc_config.config.logo.clone()) + .or_else(|| { + business_config + .as_ref() + .and_then(|business_config| business_config.logo.clone()) + }) + .unwrap_or(DEFAULT_MERCHANT_LOGO.to_string()); + + let seller_name = payment_create_link_config + .as_ref() + .and_then(|pc_config| pc_config.config.seller_name.clone()) + .or_else(|| { + business_config + .as_ref() + .and_then(|business_config| business_config.seller_name.clone()) + }) + .unwrap_or(merchant_name.clone()); + + let payment_link_config = admin_types::PaymentLinkConfig { + theme, + logo, + seller_name, + }; + + Ok((payment_link_config, domain_name)) +} + +fn capitalize_first_char(s: &str) -> String { + if let Some(first_char) = s.chars().next() { + let capitalized = first_char.to_uppercase(); + let mut result = capitalized.to_string(); + if let Some(remaining) = s.get(1..) { + result.push_str(remaining); + } + result + } else { + s.to_owned() + } } diff --git a/crates/router/src/core/payment_link/payment_link.html b/crates/router/src/core/payment_link/payment_link.html index 0ca4abd340d6..4fb5bb98efe6 100644 --- a/crates/router/src/core/payment_link/payment_link.html +++ b/crates/router/src/core/payment_link/payment_link.html @@ -1,9 +1,9 @@ - + - {{ hyperloader_sdk_link }} + Payments requested by HyperSwitch