diff --git a/.github/workflows/postman-collection-runner.yml b/.github/workflows/postman-collection-runner.yml index de8235c14e..b8ce65f4b6 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 7fdc0c6d5e..65ce1fdbc0 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 9c91c12f3b..8900b0ac38 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 152f4f04cd..ae4afb2ec3 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 133823d6cb..7e3c3c8ba0 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 1ad2ef9133..09b27d6bd7 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 12d51311e8..b5c2bc2857 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 8b834ee5d8..20c14d0dd1 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 366c917d2d..95bb714cb7 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 d85dc60149..8bbb4baf9d 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 850cc47031..6fa9f9450c 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 a6d2114f0f..c42b000551 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 1c88b83d08..095b4c33a4 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 cbd3a8137c..953d39c131 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 b33c168833..7c8043c48a 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 6cf078b5f8..630d4dfdca 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 5bbb4e7cf2..6652f42ee0 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 919ced993a..6dde49b0d6 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 3862f70536..7b74aca764 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 ccd43d25b8..ad5d9e89aa 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 9cfa3f40b5..24a2b25d6c 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, }), ) } @@ -4767,3 +4769,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 70c35b460e..99a6081730 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 d802339b67..721afaa1e4 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 308a00ffa1..69f06e5941 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 452e672547..cbf2cdea53 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -245,6 +245,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, @@ -289,6 +290,7 @@ where &key_store, auth_flow, &header_payload, + platform_merchant_account.as_ref(), ) .await?; core_utils::validate_profile_id_from_auth_layer( @@ -719,6 +721,7 @@ where &customer, key_store.clone(), &mut should_continue_capture, + platform_merchant_account.as_ref(), )) .await?; } @@ -826,8 +829,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, @@ -872,6 +875,7 @@ where &key_store, auth_flow, &header_payload, + platform_merchant_account.as_ref(), ) .await?; @@ -1024,6 +1028,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, @@ -1051,6 +1056,7 @@ where &profile, &key_store, &header_payload, + platform_merchant_account.as_ref(), ) .await?; @@ -1342,6 +1348,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, @@ -1380,6 +1387,7 @@ where auth_flow, eligible_routable_connectors, header_payload.clone(), + platform_merchant_account, ) .await?; @@ -1409,6 +1417,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, @@ -1440,6 +1449,7 @@ where call_connector_action, auth_flow, header_payload.clone(), + platform_merchant_account, ) .await?; @@ -1468,6 +1478,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, @@ -1486,6 +1497,7 @@ where req, payment_id, header_payload.clone(), + platform_merchant_account, ) .await?; @@ -1559,6 +1571,7 @@ where &profile, &key_store, &header_payload, + None, ) .await?; @@ -1627,9 +1640,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, @@ -1638,6 +1653,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; @@ -1664,6 +1680,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, @@ -1718,6 +1735,7 @@ pub trait PaymentRedirectFlow: Sync { flow_type, connector.clone(), resource_id.clone(), + platform_merchant_account, ) .await?; @@ -1725,6 +1743,7 @@ pub trait PaymentRedirectFlow: Sync { } #[cfg(feature = "v2")] + #[allow(clippy::too_many_arguments)] async fn handle_payments_redirect_response( &self, state: SessionState, @@ -1733,6 +1752,7 @@ pub trait PaymentRedirectFlow: Sync { key_store: domain::MerchantKeyStore, profile: domain::Profile, request: PaymentsRedirectResponseData, + platform_merchant_account: Option, ) -> RouterResponse { metrics::REDIRECTION_TRIGGERED.add( 1, @@ -1747,6 +1767,7 @@ pub trait PaymentRedirectFlow: Sync { key_store, profile, request, + platform_merchant_account, ) .await?; @@ -1773,6 +1794,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(); @@ -1808,6 +1830,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { connector_action, None, HeaderPayload::default(), + platform_merchant_account, )) .await?; let payments_response = match response { @@ -1913,6 +1936,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(); @@ -1945,6 +1969,7 @@ impl PaymentRedirectFlow for PaymentRedirectSync { connector_action, None, HeaderPayload::default(), + platform_merchant_account, ), ) .await?; @@ -2034,6 +2059,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(); @@ -2060,6 +2086,7 @@ impl PaymentRedirectFlow for PaymentRedirectSync { &profile, &merchant_key_store, &HeaderPayload::default(), + platform_merchant_account.as_ref(), ) .await?; @@ -2171,6 +2198,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(); @@ -2270,6 +2298,7 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { connector_action, None, HeaderPayload::with_source(enums::PaymentSource::ExternalAuthenticator), + platform_merchant_account, )) .await? } else { @@ -2302,6 +2331,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 c243686a76..eda869c3a6 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 38911bb2d6..4adb940341 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 a5993eb2f0..c830b7618d 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 427c10aab6..c6679e481f 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 f8d304bcb7..ebe49f59f6 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 0ee62ea73c..81ffa8e992 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 914ddf251e..72c04c6a54 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 9640693244..c309685a6a 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 374a33026d..71d99d03e4 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 36b338f2d5..b7b3420987 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 f9cdd192f1..1cd0010bdf 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 403ee54431..6419376f09 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 16b44436c1..3cc26e7b67 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 7337f08088..0e189583d0 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 a6a10be8e9..e5321b1f98 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 f93327b275..0e94fbe09c 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 2454b9fcbf..ec59ba3473 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 a895855cef..1da9a1a226 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 9aa905345c..dd28043ba2 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 c75794dd17..0e60407aa7 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 f8ce03d558..4782d237e2 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 035c4e8e2e..2c816ad39d 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 859422920e..5b9c90f3ad 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 d7b6ad0d34..40a2717fea 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 73d9cfdcae..ff9849958b 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 625a4d9c95..9ed724c36e 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 a57fd56e6c..9557aabbe8 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 0766382b66..00a94561be 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 4b43c42f3b..198692ac2d 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 fdc7d9ced0..cd8aa1c3c8 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 e5412c20fe..d35e321a7b 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 09cd7cfa10..bf84dd568a 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 1bfcc8ebe7..c7df4aeff0 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 04fa164849..fb83922935 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 37fb57e05f..beaacb79fc 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 415ea07cb9..1d573d007b 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 de2577e6c3..ccbf9598a5 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 0000000000..342380e093 --- /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 0000000000..cd07e163d7 --- /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);