diff --git a/.github/workflows/postman-collection-runner.yml b/.github/workflows/postman-collection-runner.yml index de8235c14ea..b8ce65f4b6c 100644 --- a/.github/workflows/postman-collection-runner.yml +++ b/.github/workflows/postman-collection-runner.yml @@ -17,7 +17,7 @@ env: CONNECTORS: stripe RUST_BACKTRACE: short RUSTUP_MAX_RETRIES: 10 - RUST_MIN_STACK: 8388608 + RUST_MIN_STACK: 10485760 jobs: runner: diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 7fdc0c6d5e8..65ce1fdbc0a 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -428,3 +428,6 @@ card_networks = "Visa, AmericanExpress, Mastercard" [network_tokenization_supported_connectors] connector_list = "cybersource" + +[platform] +enabled = true diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 9c91c12f3b4..8900b0ac385 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -444,3 +444,6 @@ card_networks = "Visa, AmericanExpress, Mastercard" [network_tokenization_supported_connectors] connector_list = "cybersource" + +[platform] +enabled = false diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 152f4f04cd8..ae4afb2ec3f 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -446,3 +446,6 @@ card_networks = "Visa, AmericanExpress, Mastercard" [network_tokenization_supported_connectors] connector_list = "cybersource" + +[platform] +enabled = false diff --git a/config/development.toml b/config/development.toml index 133823d6cb3..7e3c3c8ba07 100644 --- a/config/development.toml +++ b/config/development.toml @@ -830,3 +830,6 @@ entity_logo_url = "https://example.com/logo.svg" # Logo URL of the entity to be foreground_color = "#000000" # Foreground color of email text primary_color = "#006DF9" # Primary color of email body background_color = "#FFFFFF" # Background color of email body + +[platform] +enabled = true diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 1ad2ef91336..09b27d6bd7f 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -710,3 +710,6 @@ entity_logo_url = "https://example.com/logo.svg" # Logo URL of the entity to be foreground_color = "#000000" # Foreground color of email text primary_color = "#006DF9" # Primary color of email body background_color = "#FFFFFF" # Background color of email body + +[platform] +enabled = true diff --git a/crates/diesel_models/src/merchant_account.rs b/crates/diesel_models/src/merchant_account.rs index 12d51311e87..b5c2bc28572 100644 --- a/crates/diesel_models/src/merchant_account.rs +++ b/crates/diesel_models/src/merchant_account.rs @@ -51,6 +51,7 @@ pub struct MerchantAccount { pub payment_link_config: Option, pub pm_collect_link_config: Option, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v1")] @@ -83,6 +84,7 @@ pub struct MerchantAccountSetter { pub payment_link_config: Option, pub pm_collect_link_config: Option, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v1")] @@ -117,6 +119,7 @@ impl From for MerchantAccount { payment_link_config: item.payment_link_config, pm_collect_link_config: item.pm_collect_link_config, version: item.version, + is_platform_account: item.is_platform_account, } } } @@ -148,6 +151,7 @@ pub struct MerchantAccount { pub recon_status: storage_enums::ReconStatus, pub version: common_enums::ApiVersion, pub id: common_utils::id_type::MerchantId, + pub is_platform_account: bool, } #[cfg(feature = "v2")] @@ -165,6 +169,7 @@ impl From for MerchantAccount { organization_id: item.organization_id, recon_status: item.recon_status, version: item.version, + is_platform_account: item.is_platform_account, } } } @@ -182,6 +187,7 @@ pub struct MerchantAccountSetter { pub organization_id: common_utils::id_type::OrganizationId, pub recon_status: storage_enums::ReconStatus, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } impl MerchantAccount { @@ -228,6 +234,7 @@ pub struct MerchantAccountNew { pub payment_link_config: Option, pub pm_collect_link_config: Option, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v2")] @@ -244,6 +251,7 @@ pub struct MerchantAccountNew { pub recon_status: storage_enums::ReconStatus, pub id: common_utils::id_type::MerchantId, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v2")] @@ -258,6 +266,7 @@ pub struct MerchantAccountUpdateInternal { pub modified_at: time::PrimitiveDateTime, pub organization_id: Option, pub recon_status: Option, + pub is_platform_account: Option, } #[cfg(feature = "v2")] @@ -272,6 +281,7 @@ impl MerchantAccountUpdateInternal { modified_at, organization_id, recon_status, + is_platform_account, } = self; MerchantAccount { @@ -286,6 +296,7 @@ impl MerchantAccountUpdateInternal { recon_status: recon_status.unwrap_or(source.recon_status), version: source.version, id: source.id, + is_platform_account: is_platform_account.unwrap_or(source.is_platform_account), } } } @@ -319,6 +330,7 @@ pub struct MerchantAccountUpdateInternal { pub recon_status: Option, pub payment_link_config: Option, pub pm_collect_link_config: Option, + pub is_platform_account: Option, } #[cfg(feature = "v1")] @@ -350,6 +362,7 @@ impl MerchantAccountUpdateInternal { recon_status, payment_link_config, pm_collect_link_config, + is_platform_account, } = self; MerchantAccount { @@ -385,6 +398,7 @@ impl MerchantAccountUpdateInternal { payment_link_config: payment_link_config.or(source.payment_link_config), pm_collect_link_config: pm_collect_link_config.or(source.pm_collect_link_config), version: source.version, + is_platform_account: is_platform_account.unwrap_or(source.is_platform_account), } } } diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 8b834ee5d82..20c14d0dd10 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -74,6 +74,7 @@ pub struct PaymentIntent { pub id: common_utils::id_type::GlobalPaymentId, pub psd2_sca_exemption_type: Option, pub split_payments: Option, + pub platform_merchant_id: Option, } #[cfg(feature = "v1")] @@ -140,6 +141,7 @@ pub struct PaymentIntent { pub skip_external_tax_calculation: Option, pub psd2_sca_exemption_type: Option, pub split_payments: Option, + pub platform_merchant_id: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, diesel::AsExpression, PartialEq)] @@ -300,6 +302,7 @@ pub struct PaymentIntentNew { pub enable_payment_link: Option, pub apply_mit_exemption: Option, pub id: common_utils::id_type::GlobalPaymentId, + pub platform_merchant_id: Option, } #[cfg(feature = "v1")] @@ -366,6 +369,7 @@ pub struct PaymentIntentNew { pub tax_details: Option, pub skip_external_tax_calculation: Option, pub psd2_sca_exemption_type: Option, + pub platform_merchant_id: Option, pub split_payments: Option, } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 366c917d2d1..95bb714cb71 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -717,6 +717,7 @@ diesel::table! { payment_link_config -> Nullable, pm_collect_link_config -> Nullable, version -> ApiVersion, + is_platform_account -> Bool, } } @@ -970,6 +971,8 @@ diesel::table! { skip_external_tax_calculation -> Nullable, psd2_sca_exemption_type -> Nullable, split_payments -> Nullable, + #[max_length = 64] + platform_merchant_id -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index fcfe05e5731..9faf3830605 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -708,6 +708,7 @@ diesel::table! { version -> ApiVersion, #[max_length = 64] id -> Varchar, + is_platform_account -> Bool, } } @@ -933,6 +934,8 @@ diesel::table! { id -> Varchar, psd2_sca_exemption_type -> Nullable, split_payments -> Nullable, + #[max_length = 64] + platform_merchant_id -> Nullable, } } diff --git a/crates/hyperswitch_domain_models/src/errors/api_error_response.rs b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs index 850cc470316..6fa9f9450c8 100644 --- a/crates/hyperswitch_domain_models/src/errors/api_error_response.rs +++ b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs @@ -279,7 +279,10 @@ pub enum ApiErrorResponse { message = "Cookies are not found in the request" )] CookieNotFound, - + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_43", message = "API does not support platform account operation")] + PlatformAccountAuthNotSupported, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_44", message = "Invalid platform account operation")] + InvalidPlatformOperation, #[error(error_type = ErrorType::InvalidRequestError, code = "WE_01", message = "Failed to authenticate the webhook")] WebhookAuthenticationFailed, #[error(error_type = ErrorType::InvalidRequestError, code = "WE_02", message = "Bad request received in webhook")] @@ -667,6 +670,12 @@ impl ErrorSwitch for ApiErrorRespon ..Default::default() }) )), + Self::PlatformAccountAuthNotSupported => { + AER::BadRequest(ApiError::new("IR", 43, "API does not support platform operation", None)) + } + Self::InvalidPlatformOperation => { + AER::Unauthorized(ApiError::new("IR", 44, "Invalid platform account operation", None)) + } } } } diff --git a/crates/hyperswitch_domain_models/src/merchant_account.rs b/crates/hyperswitch_domain_models/src/merchant_account.rs index a6d2114f0fe..c42b0005513 100644 --- a/crates/hyperswitch_domain_models/src/merchant_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_account.rs @@ -47,6 +47,7 @@ pub struct MerchantAccount { pub payment_link_config: Option, pub pm_collect_link_config: Option, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v1")] @@ -81,6 +82,7 @@ pub struct MerchantAccountSetter { pub payment_link_config: Option, pub pm_collect_link_config: Option, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v1")] @@ -115,6 +117,7 @@ impl From for MerchantAccount { payment_link_config: item.payment_link_config, pm_collect_link_config: item.pm_collect_link_config, version: item.version, + is_platform_account: item.is_platform_account, } } } @@ -133,6 +136,7 @@ pub struct MerchantAccountSetter { pub modified_at: time::PrimitiveDateTime, pub organization_id: common_utils::id_type::OrganizationId, pub recon_status: diesel_models::enums::ReconStatus, + pub is_platform_account: bool, } #[cfg(feature = "v2")] @@ -149,6 +153,7 @@ impl From for MerchantAccount { modified_at, organization_id, recon_status, + is_platform_account, } = item; Self { id, @@ -161,6 +166,7 @@ impl From for MerchantAccount { modified_at, organization_id, recon_status, + is_platform_account, } } } @@ -178,6 +184,7 @@ pub struct MerchantAccount { pub modified_at: time::PrimitiveDateTime, pub organization_id: common_utils::id_type::OrganizationId, pub recon_status: diesel_models::enums::ReconStatus, + pub is_platform_account: bool, } impl MerchantAccount { @@ -233,6 +240,7 @@ pub enum MerchantAccountUpdate { }, UnsetDefaultProfile, ModifiedAtUpdate, + ToPlatformAccount, } #[cfg(feature = "v2")] @@ -252,6 +260,7 @@ pub enum MerchantAccountUpdate { recon_status: diesel_models::enums::ReconStatus, }, ModifiedAtUpdate, + ToPlatformAccount, } #[cfg(feature = "v1")] @@ -307,6 +316,7 @@ impl From for MerchantAccountUpdateInternal { organization_id: None, is_recon_enabled: None, recon_status: None, + is_platform_account: None, }, MerchantAccountUpdate::StorageSchemeUpdate { storage_scheme } => Self { storage_scheme: Some(storage_scheme), @@ -334,6 +344,7 @@ impl From for MerchantAccountUpdateInternal { recon_status: None, payment_link_config: None, pm_collect_link_config: None, + is_platform_account: None, }, MerchantAccountUpdate::ReconUpdate { recon_status } => Self { recon_status: Some(recon_status), @@ -361,6 +372,7 @@ impl From for MerchantAccountUpdateInternal { default_profile: None, payment_link_config: None, pm_collect_link_config: None, + is_platform_account: None, }, MerchantAccountUpdate::UnsetDefaultProfile => Self { default_profile: Some(None), @@ -388,6 +400,7 @@ impl From for MerchantAccountUpdateInternal { recon_status: None, payment_link_config: None, pm_collect_link_config: None, + is_platform_account: None, }, MerchantAccountUpdate::ModifiedAtUpdate => Self { modified_at: now, @@ -415,6 +428,35 @@ impl From for MerchantAccountUpdateInternal { recon_status: None, payment_link_config: None, pm_collect_link_config: None, + is_platform_account: None, + }, + MerchantAccountUpdate::ToPlatformAccount => Self { + modified_at: now, + merchant_name: None, + merchant_details: None, + return_url: None, + webhook_details: None, + sub_merchants_enabled: None, + parent_merchant_id: None, + enable_payment_response_hash: None, + payment_response_hash_key: None, + redirect_to_merchant_with_http_post: None, + publishable_key: None, + storage_scheme: None, + locker_id: None, + metadata: None, + routing_algorithm: None, + primary_business_details: None, + intent_fulfillment_time: None, + frm_routing_algorithm: None, + payout_routing_algorithm: None, + organization_id: None, + is_recon_enabled: None, + default_profile: None, + recon_status: None, + payment_link_config: None, + pm_collect_link_config: None, + is_platform_account: Some(true), }, } } @@ -440,6 +482,7 @@ impl From for MerchantAccountUpdateInternal { storage_scheme: None, organization_id: None, recon_status: None, + is_platform_account: None, }, MerchantAccountUpdate::StorageSchemeUpdate { storage_scheme } => Self { storage_scheme: Some(storage_scheme), @@ -450,6 +493,7 @@ impl From for MerchantAccountUpdateInternal { metadata: None, organization_id: None, recon_status: None, + is_platform_account: None, }, MerchantAccountUpdate::ReconUpdate { recon_status } => Self { recon_status: Some(recon_status), @@ -460,6 +504,7 @@ impl From for MerchantAccountUpdateInternal { storage_scheme: None, metadata: None, organization_id: None, + is_platform_account: None, }, MerchantAccountUpdate::ModifiedAtUpdate => Self { modified_at: now, @@ -470,6 +515,18 @@ impl From for MerchantAccountUpdateInternal { metadata: None, organization_id: None, recon_status: None, + is_platform_account: None, + }, + MerchantAccountUpdate::ToPlatformAccount => Self { + modified_at: now, + merchant_name: None, + merchant_details: None, + publishable_key: None, + storage_scheme: None, + metadata: None, + organization_id: None, + recon_status: None, + is_platform_account: Some(true), }, } } @@ -495,6 +552,7 @@ impl super::behaviour::Conversion for MerchantAccount { organization_id: self.organization_id, recon_status: self.recon_status, version: crate::consts::API_VERSION, + is_platform_account: self.is_platform_account, }; Ok(diesel_models::MerchantAccount::from(setter)) @@ -554,6 +612,7 @@ impl super::behaviour::Conversion for MerchantAccount { modified_at: item.modified_at, organization_id: item.organization_id, recon_status: item.recon_status, + is_platform_account: item.is_platform_account, }) } .await @@ -575,6 +634,7 @@ impl super::behaviour::Conversion for MerchantAccount { organization_id: self.organization_id, recon_status: self.recon_status, version: crate::consts::API_VERSION, + is_platform_account: self.is_platform_account, }) } } @@ -614,6 +674,7 @@ impl super::behaviour::Conversion for MerchantAccount { payment_link_config: self.payment_link_config, pm_collect_link_config: self.pm_collect_link_config, version: self.version, + is_platform_account: self.is_platform_account, }; Ok(diesel_models::MerchantAccount::from(setter)) @@ -691,6 +752,7 @@ impl super::behaviour::Conversion for MerchantAccount { payment_link_config: item.payment_link_config, pm_collect_link_config: item.pm_collect_link_config, version: item.version, + is_platform_account: item.is_platform_account, }) } .await @@ -729,6 +791,7 @@ impl super::behaviour::Conversion for MerchantAccount { payment_link_config: self.payment_link_config, pm_collect_link_config: self.pm_collect_link_config, version: crate::consts::API_VERSION, + is_platform_account: self.is_platform_account, }) } } diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 1c88b83d087..095b4c33a46 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -109,6 +109,7 @@ pub struct PaymentIntent { pub tax_details: Option, pub skip_external_tax_calculation: Option, pub psd2_sca_exemption_type: Option, + pub platform_merchant_id: Option, } impl PaymentIntent { @@ -365,6 +366,8 @@ pub struct PaymentIntent { pub payment_link_config: Option, /// The straight through routing algorithm id that is used for this payment. This overrides the default routing algorithm that is configured in business profile. pub routing_algorithm_id: Option, + /// Identifier for the platform merchant. + pub platform_merchant_id: Option, } #[cfg(feature = "v2")] @@ -411,6 +414,7 @@ impl PaymentIntent { profile: &business_profile::Profile, request: api_models::payments::PaymentsCreateIntentRequest, decrypted_payment_intent: DecryptedPaymentIntent, + platform_merchant_id: Option<&merchant_account::MerchantAccount>, ) -> CustomResult { let connector_metadata = request .get_connector_metadata_as_value() @@ -504,6 +508,8 @@ impl PaymentIntent { .payment_link_config .map(ApiModelToDieselModelConvertor::convert_from), routing_algorithm_id: request.routing_algorithm_id, + platform_merchant_id: platform_merchant_id + .map(|merchant_account| merchant_account.get_id().to_owned()), }) } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index cbd3a8137ca..953d39c131a 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1326,6 +1326,7 @@ impl behaviour::Conversion for PaymentIntent { customer_present, routing_algorithm_id, payment_link_config, + platform_merchant_id, } = self; Ok(DieselPaymentIntent { skip_external_tax_calculation: Some(amount_details.get_external_tax_action_as_bool()), @@ -1396,6 +1397,7 @@ impl behaviour::Conversion for PaymentIntent { payment_link_config, routing_algorithm_id, psd2_sca_exemption_type: None, + platform_merchant_id, split_payments: None, }) } @@ -1522,6 +1524,7 @@ impl behaviour::Conversion for PaymentIntent { customer_present: storage_model.customer_present.into(), payment_link_config: storage_model.payment_link_config, routing_algorithm_id: storage_model.routing_algorithm_id, + platform_merchant_id: storage_model.platform_merchant_id, }) } .await @@ -1594,6 +1597,7 @@ impl behaviour::Conversion for PaymentIntent { tax_details: amount_details.tax_details, enable_payment_link: Some(self.enable_payment_link.as_bool()), apply_mit_exemption: Some(self.apply_mit_exemption.as_bool()), + platform_merchant_id: self.platform_merchant_id, }) } } @@ -1660,6 +1664,7 @@ impl behaviour::Conversion for PaymentIntent { tax_details: self.tax_details, skip_external_tax_calculation: self.skip_external_tax_calculation, psd2_sca_exemption_type: self.psd2_sca_exemption_type, + platform_merchant_id: self.platform_merchant_id, }) } @@ -1748,6 +1753,7 @@ impl behaviour::Conversion for PaymentIntent { organization_id: storage_model.organization_id, skip_external_tax_calculation: storage_model.skip_external_tax_calculation, psd2_sca_exemption_type: storage_model.psd2_sca_exemption_type, + platform_merchant_id: storage_model.platform_merchant_id, }) } .await @@ -1812,6 +1818,7 @@ impl behaviour::Conversion for PaymentIntent { tax_details: self.tax_details, skip_external_tax_calculation: self.skip_external_tax_calculation, psd2_sca_exemption_type: self.psd2_sca_exemption_type, + platform_merchant_id: self.platform_merchant_id, }) } } diff --git a/crates/router/build.rs b/crates/router/build.rs index b33c168833d..7c8043c48ad 100644 --- a/crates/router/build.rs +++ b/crates/router/build.rs @@ -1,8 +1,8 @@ fn main() { - // Set thread stack size to 8 MiB for debug builds + // Set thread stack size to 10 MiB for debug builds // Reference: https://doc.rust-lang.org/std/thread/#stack-size #[cfg(debug_assertions)] - println!("cargo:rustc-env=RUST_MIN_STACK=8388608"); // 8 * 1024 * 1024 = 8 MiB + println!("cargo:rustc-env=RUST_MIN_STACK=10485760"); // 10 * 1024 * 1024 = 10 MiB #[cfg(feature = "vergen")] router_env::vergen::generate_cargo_instructions(); diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs index 6cf078b5f81..630d4dfdca0 100644 --- a/crates/router/src/compatibility/stripe/errors.rs +++ b/crates/router/src/compatibility/stripe/errors.rs @@ -278,6 +278,10 @@ pub enum StripeErrorCode { InvalidTenant, #[error(error_type = StripeErrorType::HyperswitchError, code = "HE_01", message = "Failed to convert amount to {amount_type} type")] AmountConversionFailed { amount_type: &'static str }, + #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Platform Bad Request")] + PlatformBadRequest, + #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Platform Unauthorized Request")] + PlatformUnauthorizedRequest, // [#216]: https://github.com/juspay/hyperswitch/issues/216 // Implement the remaining stripe error codes @@ -678,6 +682,8 @@ impl From for StripeErrorCode { errors::ApiErrorResponse::AmountConversionFailed { amount_type } => { Self::AmountConversionFailed { amount_type } } + errors::ApiErrorResponse::PlatformAccountAuthNotSupported => Self::PlatformBadRequest, + errors::ApiErrorResponse::InvalidPlatformOperation => Self::PlatformUnauthorizedRequest, } } } @@ -687,7 +693,7 @@ impl actix_web::ResponseError for StripeErrorCode { use reqwest::StatusCode; match self { - Self::Unauthorized => StatusCode::UNAUTHORIZED, + Self::Unauthorized | Self::PlatformUnauthorizedRequest => StatusCode::UNAUTHORIZED, Self::InvalidRequestUrl | Self::GenericNotFoundError { .. } => StatusCode::NOT_FOUND, Self::ParameterUnknown { .. } | Self::HyperswitchUnprocessableEntity { .. } => { StatusCode::UNPROCESSABLE_ENTITY @@ -751,6 +757,7 @@ impl actix_web::ResponseError for StripeErrorCode { | Self::CurrencyConversionFailed | Self::PaymentMethodDeleteFailed | Self::ExtendedCardInfoNotFound + | Self::PlatformBadRequest | Self::LinkConfigurationError { .. } => StatusCode::BAD_REQUEST, Self::RefundFailed | Self::PayoutFailed diff --git a/crates/router/src/compatibility/stripe/payment_intents.rs b/crates/router/src/compatibility/stripe/payment_intents.rs index 5bbb4e7cf22..6652f42ee00 100644 --- a/crates/router/src/compatibility/stripe/payment_intents.rs +++ b/crates/router/src/compatibility/stripe/payment_intents.rs @@ -92,6 +92,7 @@ pub async fn payment_intents_create( payments::CallConnectorAction::Trigger, eligible_connectors, hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -162,6 +163,7 @@ pub async fn payment_intents_retrieve( payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -240,6 +242,7 @@ pub async fn payment_intents_retrieve_with_gateway_creds( payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -316,6 +319,7 @@ pub async fn payment_intents_update( payments::CallConnectorAction::Trigger, eligible_connectors, hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -401,6 +405,7 @@ pub async fn payment_intents_confirm( payments::CallConnectorAction::Trigger, eligible_connectors, hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -472,6 +477,7 @@ pub async fn payment_intents_capture( payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -547,6 +553,7 @@ pub async fn payment_intents_cancel( payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, diff --git a/crates/router/src/compatibility/stripe/setup_intents.rs b/crates/router/src/compatibility/stripe/setup_intents.rs index 919ced993aa..6dde49b0d62 100644 --- a/crates/router/src/compatibility/stripe/setup_intents.rs +++ b/crates/router/src/compatibility/stripe/setup_intents.rs @@ -78,6 +78,7 @@ pub async fn setup_intents_create( payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -148,6 +149,7 @@ pub async fn setup_intents_retrieve( payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -224,6 +226,7 @@ pub async fn setup_intents_update( payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -301,6 +304,7 @@ pub async fn setup_intents_confirm( payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, diff --git a/crates/router/src/configs/secrets_transformers.rs b/crates/router/src/configs/secrets_transformers.rs index 3862f70536f..7b74aca7647 100644 --- a/crates/router/src/configs/secrets_transformers.rs +++ b/crates/router/src/configs/secrets_transformers.rs @@ -546,5 +546,6 @@ pub(crate) async fn fetch_raw_secrets( network_tokenization_service, network_tokenization_supported_connectors: conf.network_tokenization_supported_connectors, theme: conf.theme, + platform: conf.platform, } } diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index ccd43d25b8e..ad5d9e89aaa 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -129,6 +129,12 @@ pub struct Settings { pub network_tokenization_service: Option>, pub network_tokenization_supported_connectors: NetworkTokenizationSupportedConnectors, pub theme: ThemeSettings, + pub platform: Platform, +} + +#[derive(Debug, Deserialize, Clone, Default)] +pub struct Platform { + pub enabled: bool, } #[derive(Debug, Deserialize, Clone, Default)] diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index ccb563b0610..05aaf99421b 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -402,6 +402,7 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate { payment_link_config: None, pm_collect_link_config, version: hyperswitch_domain_models::consts::API_VERSION, + is_platform_account: false, }, ) } @@ -669,6 +670,7 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate { modified_at: date_time::now(), organization_id: organization.get_organization_id(), recon_status: diesel_models::enums::ReconStatus::NotRequested, + is_platform_account: false, }), ) } @@ -4769,3 +4771,35 @@ async fn locker_recipient_create_call( Ok(store_resp.card_reference) } + +pub async fn enable_platform_account( + state: SessionState, + merchant_id: id_type::MerchantId, +) -> RouterResponse<()> { + let db = state.store.as_ref(); + let key_manager_state = &(&state).into(); + let key_store = db + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &merchant_id, + &db.get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + + let merchant_account = db + .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + + db.update_merchant( + key_manager_state, + merchant_account, + storage::MerchantAccountUpdate::ToPlatformAccount, + &key_store, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while enabling platform merchant account") + .map(|_| services::ApplicationResponse::StatusOk) +} diff --git a/crates/router/src/core/fraud_check.rs b/crates/router/src/core/fraud_check.rs index 70c35b460e8..99a60817303 100644 --- a/crates/router/src/core/fraud_check.rs +++ b/crates/router/src/core/fraud_check.rs @@ -588,6 +588,7 @@ pub async fn post_payment_frm_core<'a, F, D>( customer: &Option, key_store: domain::MerchantKeyStore, should_continue_capture: &mut bool, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult> where F: Send + Clone, @@ -647,6 +648,7 @@ where payment_data, customer, should_continue_capture, + platform_merchant_account, ) .await?; logger::debug!("frm_post_tasks_data: {:?}", frm_data); diff --git a/crates/router/src/core/fraud_check/operation.rs b/crates/router/src/core/fraud_check/operation.rs index d802339b675..721afaa1e49 100644 --- a/crates/router/src/core/fraud_check/operation.rs +++ b/crates/router/src/core/fraud_check/operation.rs @@ -85,6 +85,7 @@ pub trait Domain: Send + Sync { _payment_data: &mut D, _customer: &Option, _should_continue_capture: &mut bool, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult> where F: Send + Clone, diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs index 308a00ffa1d..69f06e59416 100644 --- a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs +++ b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs @@ -226,6 +226,7 @@ where _payment_data: &mut D, _customer: &Option, _should_continue_capture: &mut bool, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult> { todo!() } @@ -244,6 +245,7 @@ where payment_data: &mut D, customer: &Option, _should_continue_capture: &mut bool, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult> { if matches!(frm_data.fraud_check.frm_status, FraudCheckStatus::Fraud) && matches!( @@ -277,6 +279,7 @@ where payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + platform_merchant_account.cloned(), )) .await?; logger::debug!("payment_id : {:?} has been cancelled since it has been found fraudulent by configured frm connector",payment_data.get_payment_attempt().payment_id); @@ -334,6 +337,7 @@ where payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + platform_merchant_account.cloned(), )) .await?; logger::debug!("payment_id : {:?} has been captured since it has been found legit by configured frm connector",payment_data.get_payment_attempt().payment_id); diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index e68bd09ba46..535a6b72bd6 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -242,6 +242,7 @@ pub async fn payments_operation_core( auth_flow: services::AuthFlow, eligible_connectors: Option>, header_payload: HeaderPayload, + platform_merchant_account: Option, ) -> RouterResult<(D, Req, Option, Option, Option)> where F: Send + Clone + Sync, @@ -286,6 +287,7 @@ where &key_store, auth_flow, &header_payload, + platform_merchant_account.as_ref(), ) .await?; core_utils::validate_profile_id_from_auth_layer( @@ -716,6 +718,7 @@ where &customer, key_store.clone(), &mut should_continue_capture, + platform_merchant_account.as_ref(), )) .await?; } @@ -823,8 +826,8 @@ pub async fn proxy_for_payments_operation_core( req: Req, call_connector_action: CallConnectorAction, auth_flow: services::AuthFlow, - header_payload: HeaderPayload, + platform_merchant_account: Option, ) -> RouterResult<(D, Req, Option, Option, Option)> where F: Send + Clone + Sync, @@ -869,6 +872,7 @@ where &key_store, auth_flow, &header_payload, + platform_merchant_account.as_ref(), ) .await?; @@ -1021,6 +1025,7 @@ pub async fn payments_intent_operation_core( req: Req, payment_id: id_type::GlobalPaymentId, header_payload: HeaderPayload, + platform_merchant_account: Option, ) -> RouterResult<(D, Req, Option)> where F: Send + Clone + Sync, @@ -1048,6 +1053,7 @@ where &profile, &key_store, &header_payload, + platform_merchant_account.as_ref(), ) .await?; @@ -1339,6 +1345,7 @@ pub async fn payments_core( call_connector_action: CallConnectorAction, eligible_connectors: Option>, header_payload: HeaderPayload, + platform_merchant_account: Option, ) -> RouterResponse where F: Send + Clone + Sync, @@ -1377,6 +1384,7 @@ where auth_flow, eligible_routable_connectors, header_payload.clone(), + platform_merchant_account, ) .await?; @@ -1406,6 +1414,7 @@ pub async fn proxy_for_payments_core( auth_flow: services::AuthFlow, call_connector_action: CallConnectorAction, header_payload: HeaderPayload, + platform_merchant_account: Option, ) -> RouterResponse where F: Send + Clone + Sync, @@ -1437,6 +1446,7 @@ where call_connector_action, auth_flow, header_payload.clone(), + platform_merchant_account, ) .await?; @@ -1465,6 +1475,7 @@ pub async fn payments_intent_core( req: Req, payment_id: id_type::GlobalPaymentId, header_payload: HeaderPayload, + platform_merchant_account: Option, ) -> RouterResponse where F: Send + Clone + Sync, @@ -1483,6 +1494,7 @@ where req, payment_id, header_payload.clone(), + platform_merchant_account, ) .await?; @@ -1556,6 +1568,7 @@ where &profile, &key_store, &header_payload, + None, ) .await?; @@ -1624,9 +1637,11 @@ pub trait PaymentRedirectFlow: Sync { connector_action: CallConnectorAction, connector: String, payment_id: id_type::PaymentId, + platform_merchant_account: Option, ) -> RouterResult; #[cfg(feature = "v2")] + #[allow(clippy::too_many_arguments)] async fn call_payment_flow( &self, state: &SessionState, @@ -1635,6 +1650,7 @@ pub trait PaymentRedirectFlow: Sync { merchant_key_store: domain::MerchantKeyStore, profile: domain::Profile, req: PaymentsRedirectResponseData, + platform_merchant_account: Option, ) -> RouterResult; fn get_payment_action(&self) -> services::PaymentAction; @@ -1661,6 +1677,7 @@ pub trait PaymentRedirectFlow: Sync { merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: PaymentsRedirectResponseData, + platform_merchant_account: Option, ) -> RouterResponse { metrics::REDIRECTION_TRIGGERED.add( 1, @@ -1715,6 +1732,7 @@ pub trait PaymentRedirectFlow: Sync { flow_type, connector.clone(), resource_id.clone(), + platform_merchant_account, ) .await?; @@ -1722,6 +1740,7 @@ pub trait PaymentRedirectFlow: Sync { } #[cfg(feature = "v2")] + #[allow(clippy::too_many_arguments)] async fn handle_payments_redirect_response( &self, state: SessionState, @@ -1730,6 +1749,7 @@ pub trait PaymentRedirectFlow: Sync { key_store: domain::MerchantKeyStore, profile: domain::Profile, request: PaymentsRedirectResponseData, + platform_merchant_account: Option, ) -> RouterResponse { metrics::REDIRECTION_TRIGGERED.add( 1, @@ -1744,6 +1764,7 @@ pub trait PaymentRedirectFlow: Sync { key_store, profile, request, + platform_merchant_account, ) .await?; @@ -1770,6 +1791,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { connector_action: CallConnectorAction, _connector: String, _payment_id: id_type::PaymentId, + platform_merchant_account: Option, ) -> RouterResult { let key_manager_state = &state.into(); @@ -1805,6 +1827,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { connector_action, None, HeaderPayload::default(), + platform_merchant_account, )) .await?; let payments_response = match response { @@ -1910,6 +1933,7 @@ impl PaymentRedirectFlow for PaymentRedirectSync { connector_action: CallConnectorAction, _connector: String, _payment_id: id_type::PaymentId, + platform_merchant_account: Option, ) -> RouterResult { let key_manager_state = &state.into(); @@ -1942,6 +1966,7 @@ impl PaymentRedirectFlow for PaymentRedirectSync { connector_action, None, HeaderPayload::default(), + platform_merchant_account, ), ) .await?; @@ -2031,6 +2056,7 @@ impl PaymentRedirectFlow for PaymentRedirectSync { merchant_key_store: domain::MerchantKeyStore, profile: domain::Profile, req: PaymentsRedirectResponseData, + platform_merchant_account: Option, ) -> RouterResult { let payment_id = req.payment_id.clone(); @@ -2057,6 +2083,7 @@ impl PaymentRedirectFlow for PaymentRedirectSync { &profile, &merchant_key_store, &HeaderPayload::default(), + platform_merchant_account.as_ref(), ) .await?; @@ -2168,6 +2195,7 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { connector_action: CallConnectorAction, connector: String, payment_id: id_type::PaymentId, + platform_merchant_account: Option, ) -> RouterResult { let merchant_id = merchant_account.get_id().clone(); let key_manager_state = &state.into(); @@ -2267,6 +2295,7 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { connector_action, None, HeaderPayload::with_source(enums::PaymentSource::ExternalAuthenticator), + platform_merchant_account, )) .await? } else { @@ -2299,6 +2328,7 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { connector_action, None, HeaderPayload::default(), + platform_merchant_account, ), ) .await? diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index c243686a76c..eda869c3a6c 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -3614,6 +3614,7 @@ mod tests { tax_details: None, skip_external_tax_calculation: None, psd2_sca_exemption_type: None, + platform_merchant_id: None, }; let req_cs = Some("1".to_string()); assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent).is_ok()); @@ -3684,6 +3685,7 @@ mod tests { tax_details: None, skip_external_tax_calculation: None, psd2_sca_exemption_type: None, + platform_merchant_id: None, }; let req_cs = Some("1".to_string()); assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent,).is_err()) @@ -3752,6 +3754,7 @@ mod tests { tax_details: None, skip_external_tax_calculation: None, psd2_sca_exemption_type: None, + platform_merchant_id: None, }; let req_cs = Some("1".to_string()); assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent).is_err()) diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 38911bb2d6d..4adb9403418 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -190,6 +190,7 @@ pub trait GetTracker: Send { mechant_key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>; #[cfg(feature = "v2")] @@ -203,6 +204,7 @@ pub trait GetTracker: Send { profile: &domain::Profile, mechant_key_store: &domain::MerchantKeyStore, header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>; } diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index a5993eb2f01..c830b7618d0 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -45,6 +45,7 @@ impl GetTracker, api::PaymentsCaptureR key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsCaptureRequest, PaymentData>, > { diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 427c10aab62..c6679e481f1 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -46,6 +46,7 @@ impl GetTracker, api::PaymentsCancelRe key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsCancelRequest, PaymentData>, > { diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index f8d304bcb79..ebe49f59f64 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -45,6 +45,7 @@ impl GetTracker, api::Paymen key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse< 'a, diff --git a/crates/router/src/core/payments/operations/payment_capture_v2.rs b/crates/router/src/core/payments/operations/payment_capture_v2.rs index 0ee62ea73c1..81ffa8e992f 100644 --- a/crates/router/src/core/payments/operations/payment_capture_v2.rs +++ b/crates/router/src/core/payments/operations/payment_capture_v2.rs @@ -142,6 +142,7 @@ impl GetTracker, PaymentsCaptureReques _profile: &domain::Profile, key_store: &domain::MerchantKeyStore, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; let key_manager_state = &state.into(); diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 914ddf251ef..72c04c6a549 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -48,6 +48,7 @@ impl GetTracker, api::PaymentsRequest> key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 96406932445..c309685a6a2 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -77,6 +77,7 @@ impl GetTracker, api::PaymentsRequest> key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let key_manager_state = &state.into(); diff --git a/crates/router/src/core/payments/operations/payment_confirm_intent.rs b/crates/router/src/core/payments/operations/payment_confirm_intent.rs index 374a33026da..71d99d03e41 100644 --- a/crates/router/src/core/payments/operations/payment_confirm_intent.rs +++ b/crates/router/src/core/payments/operations/payment_confirm_intent.rs @@ -158,6 +158,7 @@ impl GetTracker, PaymentsConfir profile: &domain::Profile, key_store: &domain::MerchantKeyStore, header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; let key_manager_state = &state.into(); diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 36b338f2d52..b7b3420987d 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -78,6 +78,7 @@ impl GetTracker, api::PaymentsRequest> merchant_key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; @@ -304,6 +305,7 @@ impl GetTracker, api::PaymentsRequest> attempt_id, profile_id.clone(), session_expiry, + platform_merchant_account, ) .await?; @@ -1308,6 +1310,7 @@ impl PaymentCreate { active_attempt_id: String, profile_id: common_utils::id_type::ProfileId, session_expiry: PrimitiveDateTime, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult { let created_at @ modified_at @ last_synced = common_utils::date_time::now(); @@ -1494,6 +1497,8 @@ impl PaymentCreate { tax_details, skip_external_tax_calculation, psd2_sca_exemption_type: request.psd2_sca_exemption_type, + platform_merchant_id: platform_merchant_account + .map(|platform_merchant_account| platform_merchant_account.get_id().to_owned()), }) } diff --git a/crates/router/src/core/payments/operations/payment_create_intent.rs b/crates/router/src/core/payments/operations/payment_create_intent.rs index f9cdd192f18..1cd0010bdf6 100644 --- a/crates/router/src/core/payments/operations/payment_create_intent.rs +++ b/crates/router/src/core/payments/operations/payment_create_intent.rs @@ -99,6 +99,7 @@ impl profile: &domain::Profile, key_store: &domain::MerchantKeyStore, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; let key_manager_state = &state.into(); @@ -137,6 +138,7 @@ impl profile, request.clone(), encrypted_data, + platform_merchant_account, ) .await?; diff --git a/crates/router/src/core/payments/operations/payment_get.rs b/crates/router/src/core/payments/operations/payment_get.rs index 403ee544314..6419376f090 100644 --- a/crates/router/src/core/payments/operations/payment_get.rs +++ b/crates/router/src/core/payments/operations/payment_get.rs @@ -119,6 +119,7 @@ impl GetTracker, PaymentsRetriev _profile: &domain::Profile, key_store: &domain::MerchantKeyStore, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; let key_manager_state = &state.into(); diff --git a/crates/router/src/core/payments/operations/payment_get_intent.rs b/crates/router/src/core/payments/operations/payment_get_intent.rs index 16b44436c18..3cc26e7b67f 100644 --- a/crates/router/src/core/payments/operations/payment_get_intent.rs +++ b/crates/router/src/core/payments/operations/payment_get_intent.rs @@ -89,6 +89,7 @@ impl GetTracker, Payme _profile: &domain::Profile, key_store: &domain::MerchantKeyStore, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; let key_manager_state = &state.into(); diff --git a/crates/router/src/core/payments/operations/payment_post_session_tokens.rs b/crates/router/src/core/payments/operations/payment_post_session_tokens.rs index 7337f080884..0e189583d0a 100644 --- a/crates/router/src/core/payments/operations/payment_post_session_tokens.rs +++ b/crates/router/src/core/payments/operations/payment_post_session_tokens.rs @@ -45,6 +45,7 @@ impl GetTracker, api::PaymentsPostSess key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse< 'a, diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index a6a10be8e9a..e5321b1f984 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -43,6 +43,7 @@ impl GetTracker, PaymentsCancelRequest key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index f93327b275c..0e94fbe09c6 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -44,6 +44,7 @@ impl GetTracker, api::PaymentsSessionR key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsSessionRequest, PaymentData>, > { diff --git a/crates/router/src/core/payments/operations/payment_session_intent.rs b/crates/router/src/core/payments/operations/payment_session_intent.rs index 2454b9fcbf3..ec59ba3473e 100644 --- a/crates/router/src/core/payments/operations/payment_session_intent.rs +++ b/crates/router/src/core/payments/operations/payment_session_intent.rs @@ -101,6 +101,7 @@ impl GetTracker, Payme _profile: &domain::Profile, key_store: &domain::MerchantKeyStore, header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; let key_manager_state = &state.into(); diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index a895855cefc..1da9a1a2264 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -43,6 +43,7 @@ impl GetTracker, api::PaymentsStartReq key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsStartRequest, PaymentData>, > { diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 9aa905345c8..dd28043ba2b 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -213,6 +213,7 @@ impl GetTracker, api::PaymentsRetrieve key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsRetrieveRequest, PaymentData>, > { diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index c75794dd17f..0e60407aa7c 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -56,6 +56,7 @@ impl GetTracker, api::PaymentsRequest> key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let (mut payment_intent, mut payment_attempt, currency): (_, _, storage_enums::Currency); diff --git a/crates/router/src/core/payments/operations/payment_update_intent.rs b/crates/router/src/core/payments/operations/payment_update_intent.rs index f8ce03d558e..4782d237e21 100644 --- a/crates/router/src/core/payments/operations/payment_update_intent.rs +++ b/crates/router/src/core/payments/operations/payment_update_intent.rs @@ -135,6 +135,7 @@ impl GetTracker, PaymentsUpda _profile: &domain::Profile, key_store: &domain::MerchantKeyStore, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; let key_manager_state = &state.into(); diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index 035c4e8e2ec..2c816ad39d1 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -48,6 +48,7 @@ impl key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse< 'a, diff --git a/crates/router/src/core/payments/operations/tax_calculation.rs b/crates/router/src/core/payments/operations/tax_calculation.rs index 859422920e1..5b9c90f3add 100644 --- a/crates/router/src/core/payments/operations/tax_calculation.rs +++ b/crates/router/src/core/payments/operations/tax_calculation.rs @@ -50,6 +50,7 @@ impl key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse< 'a, diff --git a/crates/router/src/core/payments/session_operation.rs b/crates/router/src/core/payments/session_operation.rs index d7b6ad0d345..40a2717fea7 100644 --- a/crates/router/src/core/payments/session_operation.rs +++ b/crates/router/src/core/payments/session_operation.rs @@ -42,6 +42,7 @@ pub async fn payments_session_core( payment_id: id_type::GlobalPaymentId, call_connector_action: CallConnectorAction, header_payload: HeaderPayload, + platform_merchant_account: Option, ) -> RouterResponse where F: Send + Clone + Sync, @@ -71,6 +72,7 @@ where payment_id, call_connector_action, header_payload.clone(), + platform_merchant_account, ) .await?; @@ -100,6 +102,7 @@ pub async fn payments_session_operation_core( payment_id: id_type::GlobalPaymentId, _call_connector_action: CallConnectorAction, header_payload: HeaderPayload, + platform_merchant_account: Option, ) -> RouterResult<(D, Req, Option, Option, Option)> where F: Send + Clone + Sync, @@ -132,6 +135,7 @@ where &profile, &key_store, &header_payload, + platform_merchant_account.as_ref(), ) .await?; diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index 73d9cfdcaee..ff9849958b5 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -617,6 +617,7 @@ async fn payments_incoming_webhook_flow( consume_or_trigger_flow.clone(), None, HeaderPayload::default(), + None, //Platform merchant account )) .await; // When mandate details are present in successful webhooks, and consuming webhooks are skipped during payment sync if the payment status is already updated to charged, this function is used to update the connector mandate details. @@ -1159,6 +1160,7 @@ async fn external_authentication_incoming_webhook_flow( payments::CallConnectorAction::Trigger, None, HeaderPayload::with_source(enums::PaymentSource::ExternalAuthenticator), + None, // Platform merchant account )) .await?; match payments_response { @@ -1356,6 +1358,7 @@ async fn frm_incoming_webhook_flow( payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + None, // Platform merchant account )) .await? } @@ -1385,6 +1388,7 @@ async fn frm_incoming_webhook_flow( payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + None, // Platform merchant account )) .await? } @@ -1543,6 +1547,7 @@ async fn bank_transfer_webhook_flow( payments::CallConnectorAction::Trigger, None, HeaderPayload::with_source(common_enums::PaymentSource::Webhook), + None, //Platform merchant account )) .await } else { diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 625a4d9c95b..9ed724c36ef 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -90,6 +90,7 @@ pub mod headers { pub const X_REDIRECT_URI: &str = "x-redirect-uri"; pub const X_TENANT_ID: &str = "x-tenant-id"; pub const X_CLIENT_SECRET: &str = "X-Client-Secret"; + pub const X_CONNECTED_MERCHANT_ID: &str = "x-connected-merchant-id"; pub const X_RESOURCE_TYPE: &str = "X-Resource-Type"; } diff --git a/crates/router/src/routes/admin.rs b/crates/router/src/routes/admin.rs index a57fd56e6c8..9557aabbe81 100644 --- a/crates/router/src/routes/admin.rs +++ b/crates/router/src/routes/admin.rs @@ -910,3 +910,26 @@ pub async fn merchant_account_transfer_keys( )) .await } + +/// Merchant Account - Platform Account +/// +/// Enable platform account +#[instrument(skip_all)] +pub async fn merchant_account_enable_platform_account( + state: web::Data, + req: HttpRequest, + path: web::Path, +) -> HttpResponse { + let flow = Flow::EnablePlatformAccount; + let merchant_id = path.into_inner(); + Box::pin(api::server_wrap( + flow, + state, + &req, + merchant_id, + |state, _, req, _| enable_platform_account(state, req), + &auth::AdminApiAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 2decddb806c..5865c1014fb 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1337,8 +1337,7 @@ impl MerchantAccount { #[cfg(all(feature = "olap", feature = "v1"))] impl MerchantAccount { pub fn server(state: AppState) -> Scope { - web::scope("/accounts") - .app_data(web::Data::new(state)) + let mut routes = web::scope("/accounts") .service(web::resource("").route(web::post().to(admin::merchant_account_create))) .service(web::resource("/list").route(web::get().to(admin::merchant_account_list))) .service( @@ -1358,7 +1357,14 @@ impl MerchantAccount { .route(web::get().to(admin::retrieve_merchant_account)) .route(web::post().to(admin::update_merchant_account)) .route(web::delete().to(admin::delete_merchant_account)), + ); + if state.conf.platform.enabled { + routes = routes.service( + web::resource("/{id}/platform") + .route(web::post().to(admin::merchant_account_enable_platform_account)), ) + } + routes.app_data(web::Data::new(state)) } } diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 4b43c42f3bc..198692ac2d6 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -48,7 +48,8 @@ impl From for ApiIdentifier { | Flow::MerchantsAccountUpdate | Flow::MerchantsAccountDelete | Flow::MerchantTransferKey - | Flow::MerchantAccountList => Self::MerchantAccount, + | Flow::MerchantAccountList + | Flow::EnablePlatformAccount => Self::MerchantAccount, Flow::OrganizationCreate | Flow::OrganizationRetrieve | Flow::OrganizationUpdate => { Self::Organization diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index a9005644beb..46f6a5e492a 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -85,6 +85,7 @@ pub async fn payments_create( header_payload.clone(), req, api::AuthFlow::Merchant, + auth.platform_merchant_account, ) }, match env::which() { @@ -143,6 +144,7 @@ pub async fn payments_create_intent( req, global_payment_id.clone(), header_payload.clone(), + auth.platform_merchant_account, ) }, match env::which() { @@ -206,6 +208,7 @@ pub async fn payments_get_intent( req, global_payment_id.clone(), header_payload.clone(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -261,6 +264,7 @@ pub async fn payments_update_intent( req.payload, global_payment_id.clone(), header_payload.clone(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -316,6 +320,7 @@ pub async fn payments_start( payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + None, ) }, &auth::MerchantIdAuth(merchant_id), @@ -390,6 +395,7 @@ pub async fn payments_retrieve( payments::CallConnectorAction::Trigger, None, header_payload.clone(), + auth.platform_merchant_account, ) }, auth::auth_type( @@ -460,6 +466,7 @@ pub async fn payments_retrieve_with_gateway_creds( payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -512,6 +519,7 @@ pub async fn payments_update( HeaderPayload::default(), req, auth_flow, + auth.platform_merchant_account, ) }, &*auth_type, @@ -570,6 +578,7 @@ pub async fn payments_post_session_tokens( payments::CallConnectorAction::Trigger, None, header_payload.clone(), + None, ) }, &auth::PublishableKeyAuth, @@ -632,6 +641,7 @@ pub async fn payments_confirm( header_payload.clone(), req, auth_flow, + auth.platform_merchant_account, ) }, &*auth_type, @@ -684,6 +694,7 @@ pub async fn payments_capture( payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -743,6 +754,7 @@ pub async fn payments_dynamic_tax_calculation( payments::CallConnectorAction::Trigger, None, header_payload.clone(), + None, ) }, &auth::PublishableKeyAuth, @@ -806,6 +818,7 @@ pub async fn payments_connector_session( payment_id, payments::CallConnectorAction::Trigger, header_payload.clone(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::PublishableKeyAuth), @@ -864,6 +877,7 @@ pub async fn payments_connector_session( payments::CallConnectorAction::Trigger, None, header_payload.clone(), + None, ) }, &auth::HeaderAuth(auth::PublishableKeyAuth), @@ -913,7 +927,7 @@ pub async fn payments_redirect_response( auth.merchant_account, auth.key_store, req, - + auth.platform_merchant_account, ) }, &auth::MerchantIdAuth(merchant_id), @@ -963,7 +977,7 @@ pub async fn payments_redirect_response_with_creds_identifier( auth.merchant_account, auth.key_store, req, - + auth.platform_merchant_account, ) }, &auth::MerchantIdAuth(merchant_id), @@ -1014,7 +1028,7 @@ pub async fn payments_complete_authorize_redirect( auth.merchant_account, auth.key_store, req, - + auth.platform_merchant_account, ) }, &auth::MerchantIdAuth(merchant_id), @@ -1080,6 +1094,7 @@ pub async fn payments_complete_authorize( payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + None, ) }, &*auth_type, @@ -1129,6 +1144,7 @@ pub async fn payments_cancel( payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -1409,6 +1425,7 @@ pub async fn payments_approve( payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + auth.platform_merchant_account, ) }, match env::which() { @@ -1473,6 +1490,7 @@ pub async fn payments_reject( payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + auth.platform_merchant_account, ) }, match env::which() { @@ -1502,6 +1520,7 @@ async fn authorize_verify_select( header_payload: HeaderPayload, req: api_models::payments::PaymentsRequest, auth_flow: api::AuthFlow, + platform_merchant_account: Option, ) -> errors::RouterResponse where Op: Sync @@ -1551,6 +1570,7 @@ where auth_flow, payments::CallConnectorAction::Trigger, header_payload, + platform_merchant_account, ) .await } else { @@ -1578,6 +1598,7 @@ where payments::CallConnectorAction::Trigger, eligible_connectors, header_payload, + platform_merchant_account, ) .await } @@ -1601,6 +1622,7 @@ where payments::CallConnectorAction::Trigger, eligible_connectors, header_payload, + platform_merchant_account, ) .await } @@ -1649,6 +1671,7 @@ pub async fn payments_incremental_authorization( payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -1734,6 +1757,7 @@ pub async fn post_3ds_payments_authorize( auth.merchant_account, auth.key_store, req, + auth.platform_merchant_account, ) }, &auth::MerchantIdAuth(merchant_id), @@ -2437,6 +2461,7 @@ pub async fn payments_finish_redirection( auth.key_store, auth.profile, req, + auth.platform_merchant_account ) }, &auth::PublishableKeyAndProfileIdAuth { diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index e5412c20fe2..d35e321a7be 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -63,6 +63,7 @@ mod detached; #[derive(Clone, Debug)] pub struct AuthenticationData { pub merchant_account: domain::MerchantAccount, + pub platform_merchant_account: Option, pub key_store: domain::MerchantKeyStore, pub profile_id: Option, } @@ -73,6 +74,7 @@ pub struct AuthenticationData { pub merchant_account: domain::MerchantAccount, pub key_store: domain::MerchantKeyStore, pub profile: domain::Profile, + pub platform_merchant_account: Option, } #[derive(Clone, Debug)] @@ -466,6 +468,28 @@ where .await .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + // Get connected merchant account if API call is done by Platform merchant account on behalf of connected merchant account + let (merchant, platform_merchant_account) = if state.conf().platform.enabled { + get_platform_merchant_account(state, request_headers, merchant).await? + } else { + (merchant, None) + }; + + let key_store = if platform_merchant_account.is_some() { + state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + merchant.get_id(), + &state.store().get_master_key().to_vec().into(), + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Failed to fetch merchant key store for the merchant id")? + } else { + key_store + }; + let profile = state .store() .find_business_profile_by_profile_id(key_manager_state, &key_store, &profile_id) @@ -474,6 +498,7 @@ where let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account, key_store, profile, }; @@ -563,8 +588,31 @@ where .await .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + // Get connected merchant account if API call is done by Platform merchant account on behalf of connected merchant account + let (merchant, platform_merchant_account) = if state.conf().platform.enabled { + get_platform_merchant_account(state, request_headers, merchant).await? + } else { + (merchant, None) + }; + + let key_store = if platform_merchant_account.is_some() { + state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + merchant.get_id(), + &state.store().get_master_key().to_vec().into(), + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Failed to fetch merchant key store for the merchant id")? + } else { + key_store + }; + let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account, key_store, profile_id, }; @@ -639,7 +687,8 @@ where merchant_id: Some(merchant_id), key_id: Some(key_id), } => { - let auth = construct_authentication_data(state, &merchant_id).await?; + let auth = + construct_authentication_data(state, &merchant_id, request_headers).await?; Ok(( auth.clone(), AuthenticationType::ApiKey { @@ -653,7 +702,8 @@ where merchant_id: Some(merchant_id), key_id: None, } => { - let auth = construct_authentication_data(state, &merchant_id).await?; + let auth = + construct_authentication_data(state, &merchant_id, request_headers).await?; Ok(( auth.clone(), AuthenticationType::PublishableKey { @@ -716,6 +766,7 @@ where let auth_data_v2 = AuthenticationData { merchant_account: auth_data.merchant_account, + platform_merchant_account: auth_data.platform_merchant_account, key_store: auth_data.key_store, profile, }; @@ -727,9 +778,10 @@ where async fn construct_authentication_data( state: &A, merchant_id: &id_type::MerchantId, + request_headers: &HeaderMap, ) -> RouterResult where - A: SessionStateInfo, + A: SessionStateInfo + Sync, { let key_store = state .store() @@ -752,8 +804,31 @@ where .await .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + // Get connected merchant account if API call is done by Platform merchant account on behalf of connected merchant account + let (merchant, platform_merchant_account) = if state.conf().platform.enabled { + get_platform_merchant_account(state, request_headers, merchant).await? + } else { + (merchant, None) + }; + + let key_store = if platform_merchant_account.is_some() { + state + .store() + .get_merchant_key_store_by_merchant_id( + &(&state.session_state()).into(), + merchant.get_id(), + &state.store().get_master_key().to_vec().into(), + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Failed to fetch merchant key store for the merchant id")? + } else { + key_store + }; + let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account, key_store, profile_id: None, }; @@ -1003,6 +1078,7 @@ where let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: None, }; @@ -1025,6 +1101,10 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + AdminApiAuth .authenticate_and_fetch(request_headers, state) .await?; @@ -1063,6 +1143,7 @@ where merchant_account: merchant, key_store, profile, + platform_merchant_account: None, }; Ok(( @@ -1084,6 +1165,10 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + AdminApiAuth .authenticate_and_fetch(request_headers, state) .await?; @@ -1182,6 +1267,34 @@ impl<'a> HeaderMapStruct<'a> { ) }) } + + pub fn get_id_type_from_header_if_present(&self, key: &str) -> RouterResult> + where + T: TryFrom< + std::borrow::Cow<'static, str>, + Error = error_stack::Report, + >, + { + self.headers + .get(key) + .map(|value| value.to_str()) + .transpose() + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "`{key}` in headers", + }) + .attach_printable(format!( + "Failed to convert header value to string for header key: {}", + key + ))? + .map(|value| { + T::try_from(std::borrow::Cow::Owned(value.to_owned())).change_context( + errors::ApiErrorResponse::InvalidRequestData { + message: format!("`{}` header is invalid", key), + }, + ) + }) + .transpose() + } } /// Get the merchant-id from `x-merchant-id` header @@ -1225,6 +1338,7 @@ where let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: None, }; @@ -1246,6 +1360,10 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + AdminApiAuth .authenticate_and_fetch(request_headers, state) .await?; @@ -1286,6 +1404,7 @@ where merchant_account: merchant, key_store, profile, + platform_merchant_account: None, }; Ok(( auth, @@ -1306,6 +1425,10 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + AdminApiAuth .authenticate_and_fetch(request_headers, state) .await?; @@ -1418,9 +1541,13 @@ where { async fn authenticate_and_fetch( &self, - _request_headers: &HeaderMap, + request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + let key_manager_state = &(&state.session_state()).into(); let key_store = state .store() @@ -1440,6 +1567,7 @@ where let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: None, }; @@ -1463,6 +1591,10 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + let key_manager_state = &(&state.session_state()).into(); let profile_id = get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? @@ -1497,6 +1629,7 @@ where merchant_account: merchant, key_store, profile, + platform_merchant_account: None, }; Ok(( auth.clone(), @@ -1525,6 +1658,10 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + let key_manager_state = &(&state.session_state()).into(); let key_store = state .store() @@ -1556,6 +1693,7 @@ where merchant_account: merchant, key_store, profile, + platform_merchant_account: None, }; Ok(( auth.clone(), @@ -1615,6 +1753,7 @@ where merchant_account, key_store, profile, + platform_merchant_account: None, }, AuthenticationType::PublishableKey { merchant_id }, )) @@ -1642,6 +1781,10 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + let publishable_key = get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; let key_manager_state = &(&state.session_state()).into(); @@ -1655,6 +1798,7 @@ where ( AuthenticationData { merchant_account, + platform_merchant_account: None, key_store, profile_id: None, }, @@ -1703,6 +1847,7 @@ where merchant_account, key_store, profile, + platform_merchant_account: None, }, AuthenticationType::PublishableKey { merchant_id }, )) @@ -1991,6 +2136,7 @@ where let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: Some(payload.profile_id), }; @@ -2073,6 +2219,7 @@ where merchant_account: merchant, key_store, profile, + platform_merchant_account: None, }; Ok(( @@ -2239,6 +2386,7 @@ where let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: Some(payload.profile_id), }; @@ -2314,6 +2462,7 @@ where merchant_account: merchant, key_store, profile, + platform_merchant_account: None, }; Ok(( auth.clone(), @@ -2448,6 +2597,7 @@ where let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: Some(payload.profile_id), }; @@ -2519,6 +2669,7 @@ where // if both of them are same then proceed with the profile id present in the request let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: Some(self.profile_id.clone()), }; @@ -2592,6 +2743,7 @@ where merchant_account: merchant, key_store, profile, + platform_merchant_account: None, }; Ok(( auth.clone(), @@ -2680,6 +2832,7 @@ where let merchant_id = merchant.get_id().clone(); let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: Some(payload.profile_id), }; @@ -2756,6 +2909,7 @@ where merchant_account: merchant, key_store, profile, + platform_merchant_account: None, }; Ok(( auth, @@ -2816,6 +2970,7 @@ where let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: Some(payload.profile_id), }; @@ -2934,6 +3089,7 @@ where let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: Some(payload.profile_id), }; @@ -3298,6 +3454,96 @@ where } } +async fn get_connected_merchant_account( + state: &A, + connected_merchant_id: id_type::MerchantId, + platform_org_id: id_type::OrganizationId, +) -> RouterResult +where + A: SessionStateInfo + Sync, +{ + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &connected_merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidPlatformOperation) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + + let connected_merchant_account = state + .store() + .find_merchant_account_by_merchant_id(key_manager_state, &connected_merchant_id, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidPlatformOperation) + .attach_printable("Failed to fetch merchant account for the merchant id")?; + + if platform_org_id != connected_merchant_account.organization_id { + return Err(errors::ApiErrorResponse::InvalidPlatformOperation) + .attach_printable("Access for merchant id Unauthorized"); + } + + Ok(connected_merchant_account) +} + +async fn get_platform_merchant_account( + state: &A, + request_headers: &HeaderMap, + merchant_account: domain::MerchantAccount, +) -> RouterResult<(domain::MerchantAccount, Option)> +where + A: SessionStateInfo + Sync, +{ + let connected_merchant_id = + get_and_validate_connected_merchant_id(request_headers, &merchant_account)?; + + match connected_merchant_id { + Some(merchant_id) => { + let connected_merchant_account = get_connected_merchant_account( + state, + merchant_id, + merchant_account.organization_id.clone(), + ) + .await?; + Ok((connected_merchant_account, Some(merchant_account))) + } + None => Ok((merchant_account, None)), + } +} + +fn get_and_validate_connected_merchant_id( + request_headers: &HeaderMap, + merchant_account: &domain::MerchantAccount, +) -> RouterResult> { + HeaderMapStruct::new(request_headers) + .get_id_type_from_header_if_present::( + headers::X_CONNECTED_MERCHANT_ID, + )? + .map(|merchant_id| { + merchant_account + .is_platform_account + .then_some(merchant_id) + .ok_or(errors::ApiErrorResponse::InvalidPlatformOperation) + }) + .transpose() + .attach_printable("Non platform_merchant_account using X_CONNECTED_MERCHANT_ID header") +} + +fn throw_error_if_platform_merchant_authentication_required( + request_headers: &HeaderMap, +) -> RouterResult<()> { + HeaderMapStruct::new(request_headers) + .get_id_type_from_header_if_present::( + headers::X_CONNECTED_MERCHANT_ID, + )? + .map_or(Ok(()), |_| { + Err(errors::ApiErrorResponse::PlatformAccountAuthNotSupported.into()) + }) +} + #[cfg(feature = "recon")] #[async_trait] impl AuthenticateAndFetch for JWTAuth diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 09cd7cfa103..bf84dd568a2 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -275,6 +275,7 @@ pub async fn generate_sample_data( tax_details: None, skip_external_tax_calculation: None, psd2_sca_exemption_type: None, + platform_merchant_id: None, }; let (connector_transaction_id, connector_transaction_data) = ConnectorTransactionId::form_id_and_data(attempt_id.clone()); diff --git a/crates/router/src/workflows/outgoing_webhook_retry.rs b/crates/router/src/workflows/outgoing_webhook_retry.rs index 1bfcc8ebe7b..c7df4aeff0c 100644 --- a/crates/router/src/workflows/outgoing_webhook_retry.rs +++ b/crates/router/src/workflows/outgoing_webhook_retry.rs @@ -400,6 +400,7 @@ async fn get_outgoing_webhook_content_and_event_type( CallConnectorAction::Avoid, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + None, //Platform merchant account )) .await? { diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index 04fa1648492..fb83922935e 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -91,6 +91,7 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { services::AuthFlow::Client, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + None, //Platform merchant account )) .await?; diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index 37fb57e05f3..beaacb79fc0 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -472,6 +472,7 @@ async fn payments_create_core() { payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + None, )) .await .unwrap(); @@ -734,6 +735,7 @@ async fn payments_create_core_adyen_no_redirect() { payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + None, )) .await .unwrap(); diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index 415ea07cb9f..1d573d007ba 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -234,6 +234,7 @@ async fn payments_create_core() { payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + None, )) .await .unwrap(); @@ -504,6 +505,7 @@ async fn payments_create_core_adyen_no_redirect() { payments::CallConnectorAction::Trigger, None, hyperswitch_domain_models::payments::HeaderPayload::default(), + None, )) .await .unwrap(); diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index de2577e6c34..ccbf9598a56 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -88,6 +88,8 @@ pub enum Flow { ConfigKeyCreate, /// ConfigKey fetch flow. ConfigKeyFetch, + /// Enable platform account flow. + EnablePlatformAccount, /// ConfigKey Update flow. ConfigKeyUpdate, /// ConfigKey Delete flow. diff --git a/migrations/2024-12-03-072318_platform_merchant_account/down.sql b/migrations/2024-12-03-072318_platform_merchant_account/down.sql new file mode 100644 index 00000000000..342380e0933 --- /dev/null +++ b/migrations/2024-12-03-072318_platform_merchant_account/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE merchant_account DROP COLUMN IF EXISTS is_platform_account; + +ALTER TABLE payment_intent DROP COLUMN IF EXISTS platform_merchant_id; diff --git a/migrations/2024-12-03-072318_platform_merchant_account/up.sql b/migrations/2024-12-03-072318_platform_merchant_account/up.sql new file mode 100644 index 00000000000..cd07e163d7c --- /dev/null +++ b/migrations/2024-12-03-072318_platform_merchant_account/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here +ALTER TABLE merchant_account ADD COLUMN IF NOT EXISTS is_platform_account BOOL NOT NULL DEFAULT FALSE; + +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS platform_merchant_id VARCHAR(64);