From 2b8eb09a16040957ac369c48e6095c343207f0d3 Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:24:19 +0530 Subject: [PATCH 01/51] feat(core): add SCA exemption field (#6578) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 8 ++++ api-reference/openapi_spec.json | 40 +++++++++++++++++++ crates/api_models/src/payments.rs | 4 ++ crates/common_enums/src/enums.rs | 25 +++++++++++- crates/diesel_models/src/enums.rs | 6 +-- crates/diesel_models/src/payment_intent.rs | 3 ++ crates/diesel_models/src/schema.rs | 1 + crates/diesel_models/src/schema_v2.rs | 1 + .../hyperswitch_domain_models/src/payments.rs | 1 + .../src/payments/payment_intent.rs | 4 ++ .../src/router_data.rs | 3 ++ crates/openapi/src/openapi.rs | 1 + crates/openapi/src/openapi_v2.rs | 1 + crates/router/src/core/authentication.rs | 2 + .../src/core/authentication/transformers.rs | 6 +++ .../core/fraud_check/flows/checkout_flow.rs | 1 + .../fraud_check/flows/fulfillment_flow.rs | 1 + .../core/fraud_check/flows/record_return.rs | 1 + .../src/core/fraud_check/flows/sale_flow.rs | 1 + .../fraud_check/flows/transaction_flow.rs | 1 + crates/router/src/core/mandate/utils.rs | 1 + crates/router/src/core/payments.rs | 1 + crates/router/src/core/payments/helpers.rs | 4 ++ .../payments/operations/payment_confirm.rs | 4 ++ .../payments/operations/payment_create.rs | 1 + .../payments/operations/payment_update.rs | 3 ++ .../router/src/core/payments/transformers.rs | 4 ++ crates/router/src/core/utils.rs | 8 ++++ crates/router/src/core/webhooks/utils.rs | 1 + .../router/src/services/conversion_impls.rs | 1 + crates/router/src/types.rs | 2 + .../router/src/types/api/verify_connector.rs | 1 + crates/router/src/utils/user/sample_data.rs | 1 + crates/router/tests/connectors/aci.rs | 2 + crates/router/tests/connectors/utils.rs | 1 + .../down.sql | 4 ++ .../up.sql | 6 +++ 37 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/down.sql create mode 100644 migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/up.sql diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 095e386d981a..2a5b1cfa3107 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -18645,6 +18645,14 @@ } } }, + "ScaExemptionType": { + "type": "string", + "description": "SCA Exemptions types available for authentication", + "enum": [ + "low_value", + "transaction_risk_analysis" + ] + }, "SdkInformation": { "type": "object", "description": "SDK Information if request is from SDK", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 247b308bd65e..e029bef94534 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -17019,6 +17019,14 @@ "type": "boolean", "description": "Whether to calculate tax for this payment intent", "nullable": true + }, + "psd2_sca_exemption_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ScaExemptionType" + } + ], + "nullable": true } } }, @@ -17389,6 +17397,14 @@ "type": "boolean", "description": "Whether to calculate tax for this payment intent", "nullable": true + }, + "psd2_sca_exemption_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ScaExemptionType" + } + ], + "nullable": true } } }, @@ -18583,6 +18599,14 @@ "type": "boolean", "description": "Whether to calculate tax for this payment intent", "nullable": true + }, + "psd2_sca_exemption_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ScaExemptionType" + } + ], + "nullable": true } }, "additionalProperties": false @@ -19616,6 +19640,14 @@ "type": "boolean", "description": "Whether to calculate tax for this payment intent", "nullable": true + }, + "psd2_sca_exemption_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ScaExemptionType" + } + ], + "nullable": true } } }, @@ -23284,6 +23316,14 @@ } } }, + "ScaExemptionType": { + "type": "string", + "description": "SCA Exemptions types available for authentication", + "enum": [ + "low_value", + "transaction_risk_analysis" + ] + }, "SdkInformation": { "type": "object", "description": "SDK Information if request is from SDK", diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 98bc7b754a46..06dd7dc79696 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -859,6 +859,10 @@ pub struct PaymentsRequest { /// Whether to calculate tax for this payment intent pub skip_external_tax_calculation: Option, + + /// Choose what kind of sca exemption is required for this payment + #[schema(value_type = Option)] + pub psd2_sca_exemption_type: Option, } #[cfg(feature = "v1")] diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 23dbab778256..781b5e3710a7 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -20,7 +20,7 @@ pub mod diesel_exports { DbMandateStatus as MandateStatus, DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, DbPaymentType as PaymentType, DbRefundStatus as RefundStatus, DbRequestIncrementalAuthorization as RequestIncrementalAuthorization, - DbWebhookDeliveryAttempt as WebhookDeliveryAttempt, + DbScaExemptionType as ScaExemptionType, DbWebhookDeliveryAttempt as WebhookDeliveryAttempt, }; } @@ -1668,6 +1668,29 @@ pub enum PaymentType { RecurringMandate, } +/// SCA Exemptions types available for authentication +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum ScaExemptionType { + #[default] + LowValue, + TransactionRiskAnalysis, +} + #[derive( Clone, Copy, diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs index 77d167402ef0..5de048e32ef9 100644 --- a/crates/diesel_models/src/enums.rs +++ b/crates/diesel_models/src/enums.rs @@ -20,9 +20,9 @@ pub mod diesel_exports { DbRefundStatus as RefundStatus, DbRefundType as RefundType, DbRequestIncrementalAuthorization as RequestIncrementalAuthorization, DbRoleScope as RoleScope, DbRoutingAlgorithmKind as RoutingAlgorithmKind, - DbTotpStatus as TotpStatus, DbTransactionType as TransactionType, - DbUserRoleVersion as UserRoleVersion, DbUserStatus as UserStatus, - DbWebhookDeliveryAttempt as WebhookDeliveryAttempt, + DbScaExemptionType as ScaExemptionType, DbTotpStatus as TotpStatus, + DbTransactionType as TransactionType, DbUserRoleVersion as UserRoleVersion, + DbUserStatus as UserStatus, DbWebhookDeliveryAttempt as WebhookDeliveryAttempt, }; } pub use common_enums::*; diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 26cb0b8c8a84..7826e2dadd25 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -72,6 +72,7 @@ pub struct PaymentIntent { pub routing_algorithm_id: Option, pub payment_link_config: Option, pub id: common_utils::id_type::GlobalPaymentId, + pub psd2_sca_exemption_type: Option, } #[cfg(feature = "v1")] @@ -136,6 +137,7 @@ pub struct PaymentIntent { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub psd2_sca_exemption_type: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq)] @@ -352,6 +354,7 @@ pub struct PaymentIntentNew { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub psd2_sca_exemption_type: Option, } #[cfg(feature = "v2")] diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 6dddbb754d54..d3e560fc048c 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -932,6 +932,7 @@ diesel::table! { organization_id -> Varchar, tax_details -> Nullable, skip_external_tax_calculation -> Nullable, + psd2_sca_exemption_type -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index d44dd3317d3f..f6bab9071cd0 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -895,6 +895,7 @@ diesel::table! { payment_link_config -> Nullable, #[max_length = 64] id -> Varchar, + psd2_sca_exemption_type -> Nullable, } } diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 1bab8ae3b76c..276035214a42 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -99,6 +99,7 @@ pub struct PaymentIntent { pub organization_id: id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub psd2_sca_exemption_type: Option, } impl PaymentIntent { diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index 4f2053ec6f99..d10dafe883a7 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1283,6 +1283,7 @@ impl behaviour::Conversion for PaymentIntent { customer_present: Some(customer_present.as_bool()), payment_link_config, routing_algorithm_id, + psd2_sca_exemption_type: None, }) } async fn convert_back( @@ -1551,6 +1552,7 @@ impl behaviour::Conversion for PaymentIntent { shipping_cost: self.shipping_cost, tax_details: self.tax_details, skip_external_tax_calculation: self.skip_external_tax_calculation, + psd2_sca_exemption_type: self.psd2_sca_exemption_type, }) } @@ -1638,6 +1640,7 @@ impl behaviour::Conversion for PaymentIntent { is_payment_processor_token_flow: storage_model.is_payment_processor_token_flow, 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, }) } .await @@ -1700,6 +1703,7 @@ impl behaviour::Conversion for PaymentIntent { shipping_cost: self.shipping_cost, tax_details: self.tax_details, skip_external_tax_calculation: self.skip_external_tax_calculation, + psd2_sca_exemption_type: self.psd2_sca_exemption_type, }) } } diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 0863ab59b45f..a3867ce3e62a 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -86,6 +86,9 @@ pub struct RouterData { pub header_payload: Option, pub connector_mandate_request_reference_id: Option, + + /// Contains the type of sca exemption required for the transaction + pub psd2_sca_exemption_type: Option, } // Different patterns of authentication. diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 6356b5f15bee..ed6efea27f83 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -254,6 +254,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::AcceptedCountries, api_models::admin::AcceptedCurrencies, api_models::enums::PaymentType, + api_models::enums::ScaExemptionType, api_models::enums::PaymentMethod, api_models::enums::PaymentMethodType, api_models::enums::ConnectorType, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 17c004169336..4198e90882e5 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -199,6 +199,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::AcceptedCurrencies, api_models::enums::ProductType, api_models::enums::PaymentType, + api_models::enums::ScaExemptionType, api_models::enums::PaymentMethod, api_models::enums::PaymentMethodType, api_models::enums::ConnectorType, diff --git a/crates/router/src/core/authentication.rs b/crates/router/src/core/authentication.rs index 29508b5c0f55..b4718a64de5a 100644 --- a/crates/router/src/core/authentication.rs +++ b/crates/router/src/core/authentication.rs @@ -39,6 +39,7 @@ pub async fn perform_authentication( email: Option, webhook_url: String, three_ds_requestor_url: String, + psd2_sca_exemption_type: Option, ) -> CustomResult { let router_data = transformers::construct_authentication_router_data( merchant_id, @@ -60,6 +61,7 @@ pub async fn perform_authentication( email, webhook_url, three_ds_requestor_url, + psd2_sca_exemption_type, )?; let response = Box::pin(utils::do_auth_connector_call( state, diff --git a/crates/router/src/core/authentication/transformers.rs b/crates/router/src/core/authentication/transformers.rs index 6b8a378a0701..bcbd1481711d 100644 --- a/crates/router/src/core/authentication/transformers.rs +++ b/crates/router/src/core/authentication/transformers.rs @@ -43,6 +43,7 @@ pub fn construct_authentication_router_data( email: Option, webhook_url: String, three_ds_requestor_url: String, + psd2_sca_exemption_type: Option, ) -> RouterResult { let router_request = types::authentication::ConnectorAuthenticationRequestData { payment_method_data, @@ -70,6 +71,7 @@ pub fn construct_authentication_router_data( types::PaymentAddress::default(), router_request, &merchant_connector_account, + psd2_sca_exemption_type, ) } @@ -94,6 +96,7 @@ pub fn construct_post_authentication_router_data( types::PaymentAddress::default(), router_request, &merchant_connector_account, + None, ) } @@ -119,6 +122,7 @@ pub fn construct_pre_authentication_router_data( types::PaymentAddress::default(), router_request, merchant_connector_account, + None, ) } @@ -129,6 +133,7 @@ pub fn construct_router_data( address: types::PaymentAddress, request_data: Req, merchant_connector_account: &payments_helpers::MerchantConnectorAccountType, + psd2_sca_exemption_type: Option, ) -> RouterResult> { let test_mode: Option = merchant_connector_account.is_test_mode_on(); let auth_type: types::ConnectorAuthType = merchant_connector_account @@ -185,6 +190,7 @@ pub fn construct_router_data( additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type, }) } diff --git a/crates/router/src/core/fraud_check/flows/checkout_flow.rs b/crates/router/src/core/fraud_check/flows/checkout_flow.rs index af59c6c42159..84ba1f26a5d1 100644 --- a/crates/router/src/core/fraud_check/flows/checkout_flow.rs +++ b/crates/router/src/core/fraud_check/flows/checkout_flow.rs @@ -161,6 +161,7 @@ impl ConstructFlowSpecificData( additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) } diff --git a/crates/router/src/core/fraud_check/flows/record_return.rs b/crates/router/src/core/fraud_check/flows/record_return.rs index 6bf5688104b8..6e29d01c81fb 100644 --- a/crates/router/src/core/fraud_check/flows/record_return.rs +++ b/crates/router/src/core/fraud_check/flows/record_return.rs @@ -129,6 +129,7 @@ impl ConstructFlowSpecificData( additional_merchant_data: router_data.additional_merchant_data, header_payload: router_data.header_payload, connector_mandate_request_reference_id: router_data.connector_mandate_request_reference_id, + psd2_sca_exemption_type: router_data.psd2_sca_exemption_type, } } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 8c82c38be714..b56260b407eb 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -353,6 +353,10 @@ impl GetTracker, api::PaymentsRequest> for Pa .setup_future_usage .or(payment_intent.setup_future_usage); + payment_intent.psd2_sca_exemption_type = request + .psd2_sca_exemption_type + .or(payment_intent.psd2_sca_exemption_type); + let browser_info = request .browser_info .clone() diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 1f0884fe3849..dcc2ba6a289a 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1479,6 +1479,7 @@ impl PaymentCreate { shipping_cost: request.shipping_cost, tax_details: None, skip_external_tax_calculation, + psd2_sca_exemption_type: request.psd2_sca_exemption_type, }) } diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 2f1a0c333fa6..98491cab1db8 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -271,6 +271,9 @@ impl GetTracker, api::PaymentsRequest> for Pa .or(payment_intent.feature_metadata); payment_intent.metadata = request.metadata.clone().or(payment_intent.metadata); payment_intent.frm_metadata = request.frm_metadata.clone().or(payment_intent.frm_metadata); + payment_intent.psd2_sca_exemption_type = request + .psd2_sca_exemption_type + .or(payment_intent.psd2_sca_exemption_type); Self::populate_payment_intent_with_request(&mut payment_intent, request); let token = token.or_else(|| payment_attempt.payment_token.clone()); diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 8a0983eccab0..d9bd374ef1a9 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -168,6 +168,7 @@ where additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id, + psd2_sca_exemption_type: None, }; Ok(router_data) } @@ -370,6 +371,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( additional_merchant_data: None, header_payload, connector_mandate_request_reference_id, + psd2_sca_exemption_type: None, }; Ok(router_data) @@ -502,6 +504,7 @@ pub async fn construct_router_data_for_psync<'a>( additional_merchant_data: None, header_payload, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) @@ -723,6 +726,7 @@ where }), header_payload, connector_mandate_request_reference_id, + psd2_sca_exemption_type: payment_data.payment_intent.psd2_sca_exemption_type, }; Ok(router_data) diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index fea112bc3b8b..167a4c590068 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -215,6 +215,7 @@ pub async fn construct_payout_router_data<'a, F>( additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) @@ -394,6 +395,7 @@ pub async fn construct_refund_router_data<'a, F>( additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) @@ -705,6 +707,7 @@ pub async fn construct_accept_dispute_router_data<'a>( additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) } @@ -801,6 +804,7 @@ pub async fn construct_submit_evidence_router_data<'a>( additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) } @@ -903,6 +907,7 @@ pub async fn construct_upload_file_router_data<'a>( additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) } @@ -1025,6 +1030,7 @@ pub async fn construct_payments_dynamic_tax_calculation_router_data<'a, F: Clone additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) } @@ -1124,6 +1130,7 @@ pub async fn construct_defend_dispute_router_data<'a>( additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) } @@ -1217,6 +1224,7 @@ pub async fn construct_retrieve_file_router_data<'a>( additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) } diff --git a/crates/router/src/core/webhooks/utils.rs b/crates/router/src/core/webhooks/utils.rs index fff675503ad4..f1e84b2226a7 100644 --- a/crates/router/src/core/webhooks/utils.rs +++ b/crates/router/src/core/webhooks/utils.rs @@ -123,6 +123,7 @@ pub async fn construct_webhook_router_data<'a>( additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) } diff --git a/crates/router/src/services/conversion_impls.rs b/crates/router/src/services/conversion_impls.rs index 33c655eed78a..6ac934cd2fd7 100644 --- a/crates/router/src/services/conversion_impls.rs +++ b/crates/router/src/services/conversion_impls.rs @@ -77,6 +77,7 @@ fn get_default_router_data( additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, } } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index bea009949f94..467ae31f7cf1 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -948,6 +948,7 @@ impl ForeignFrom<(&RouterData, T2) connector_mandate_request_reference_id: data .connector_mandate_request_reference_id .clone(), + psd2_sca_exemption_type: data.psd2_sca_exemption_type, } } } @@ -1013,6 +1014,7 @@ impl additional_merchant_data: data.additional_merchant_data.clone(), header_payload: data.header_payload.clone(), connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, } } } diff --git a/crates/router/src/types/api/verify_connector.rs b/crates/router/src/types/api/verify_connector.rs index a472296d2a75..c368a3fb37d0 100644 --- a/crates/router/src/types/api/verify_connector.rs +++ b/crates/router/src/types/api/verify_connector.rs @@ -118,6 +118,7 @@ impl VerifyConnectorData { additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, } } } diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 8ffb0f2001fb..600d610e428d 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -274,6 +274,7 @@ pub async fn generate_sample_data( shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + psd2_sca_exemption_type: None, }; let (connector_transaction_id, connector_transaction_data) = ConnectorTransactionId::form_id_and_data(attempt_id.clone()); diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 6f4855d1e22a..10c8a3dd012d 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -129,6 +129,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, } } @@ -199,6 +200,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, } } diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 52218b211ac1..3402b532fbfb 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -547,6 +547,7 @@ pub trait ConnectorActions: Connector { additional_merchant_data: None, header_payload: None, connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, } } diff --git a/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/down.sql b/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/down.sql new file mode 100644 index 000000000000..f43aff7f567d --- /dev/null +++ b/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent DROP COLUMN psd2_sca_exemption_type; + +DROP TYPE "ScaExemptionType"; \ No newline at end of file diff --git a/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/up.sql b/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/up.sql new file mode 100644 index 000000000000..6fa361a48022 --- /dev/null +++ b/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/up.sql @@ -0,0 +1,6 @@ +CREATE TYPE "ScaExemptionType" AS ENUM ( + 'low_value', + 'transaction_risk_analysis' +); + +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS psd2_sca_exemption_type "ScaExemptionType"; \ No newline at end of file From 0db3aed1533856b9892369d7bb2430d90d091756 Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:58:13 +0530 Subject: [PATCH 02/51] chore(deps): update cypress packages to address CVE (#6624) --- cypress-tests-v2/package-lock.json | 14 +-- cypress-tests-v2/package.json | 2 +- cypress-tests/package-lock.json | 186 ++++++++++++----------------- cypress-tests/package.json | 6 +- 4 files changed, 84 insertions(+), 124 deletions(-) diff --git a/cypress-tests-v2/package-lock.json b/cypress-tests-v2/package-lock.json index 36f801468a9f..cac88cab055b 100644 --- a/cypress-tests-v2/package-lock.json +++ b/cypress-tests-v2/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "devDependencies": { "@types/fs-extra": "^11.0.4", - "cypress": "^13.15.2", + "cypress": "^13.16.0", "cypress-mochawesome-reporter": "^3.8.2", "jsqr": "^1.4.0", "nanoid": "^5.0.8", @@ -727,8 +727,8 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "license": "MIT", @@ -742,9 +742,9 @@ } }, "node_modules/cypress": { - "version": "13.15.2", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.2.tgz", - "integrity": "sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A==", + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.0.tgz", + "integrity": "sha512-g6XcwqnvzXrqiBQR/5gN+QsyRmKRhls1y5E42fyOvsmU7JuY+wM6uHJWj4ZPttjabzbnRvxcik2WemR8+xT6FA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1057,7 +1057,7 @@ "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "get-stream": "^5.0.0", "human-signals": "^1.1.1", "is-stream": "^2.0.0", diff --git a/cypress-tests-v2/package.json b/cypress-tests-v2/package.json index 20403f9e7774..37a51db93cbe 100644 --- a/cypress-tests-v2/package.json +++ b/cypress-tests-v2/package.json @@ -15,7 +15,7 @@ "license": "ISC", "devDependencies": { "@types/fs-extra": "^11.0.4", - "cypress": "^13.15.2", + "cypress": "^13.16.0", "cypress-mochawesome-reporter": "^3.8.2", "jsqr": "^1.4.0", "prettier": "^3.3.2", diff --git a/cypress-tests/package-lock.json b/cypress-tests/package-lock.json index abac33d9e328..04390f76f1d0 100644 --- a/cypress-tests/package-lock.json +++ b/cypress-tests/package-lock.json @@ -8,13 +8,11 @@ "name": "test", "version": "1.0.0", "license": "ISC", - "dependencies": { - "prettier": "^3.3.2" - }, "devDependencies": { - "cypress": "^13.14.1", + "cypress": "^13.16.0", "cypress-mochawesome-reporter": "^3.8.2", - "jsqr": "^1.4.0" + "jsqr": "^1.4.0", + "prettier": "^3.3.2" } }, "node_modules/@colors/colors": { @@ -29,9 +27,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", - "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", + "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -41,16 +39,16 @@ "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "http-signature": "~1.3.6", + "form-data": "~4.0.0", + "http-signature": "~1.4.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.10.4", + "qs": "6.13.0", "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", + "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -290,9 +288,9 @@ } }, "node_modules/aws4": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", - "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true, "license": "MIT" }, @@ -548,9 +546,9 @@ } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", + "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", "dev": true, "funding": [ { @@ -707,9 +705,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -722,14 +720,14 @@ } }, "node_modules/cypress": { - "version": "13.14.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.14.1.tgz", - "integrity": "sha512-Wo+byPmjps66hACEH5udhXINEiN3qS3jWNGRzJOjrRJF3D0+YrcP2LVB1T7oYaVQM/S+eanqEvBWYc8cf7Vcbg==", + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.0.tgz", + "integrity": "sha512-g6XcwqnvzXrqiBQR/5gN+QsyRmKRhls1y5E42fyOvsmU7JuY+wM6uHJWj4ZPttjabzbnRvxcik2WemR8+xT6FA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "@cypress/request": "^3.0.1", + "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -740,6 +738,7 @@ "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", + "ci-info": "^4.0.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", "commander": "^6.2.1", @@ -754,7 +753,6 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -769,6 +767,7 @@ "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.3", + "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, @@ -1036,7 +1035,7 @@ "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "get-stream": "^5.0.0", "human-signals": "^1.1.1", "is-stream": "^2.0.0", @@ -1184,18 +1183,18 @@ } }, "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.12" + "node": ">= 6" } }, "node_modules/fs-extra": { @@ -1466,15 +1465,15 @@ } }, "node_modules/http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", "dev": true, "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", - "sshpk": "^1.14.1" + "sshpk": "^1.18.0" }, "engines": { "node": ">=0.10" @@ -1564,19 +1563,6 @@ "node": ">=8" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2481,9 +2467,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "dev": true, "license": "MIT", "engines": { @@ -2668,6 +2654,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -2721,13 +2708,6 @@ "dev": true, "license": "MIT" }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true, - "license": "MIT" - }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -2739,24 +2719,14 @@ "once": "^1.3.1" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/qs": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", - "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -2765,13 +2735,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -2831,13 +2794,6 @@ "dev": true, "license": "ISC" }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -3138,6 +3094,26 @@ "dev": true, "license": "MIT" }, + "node_modules/tldts": { + "version": "6.1.62", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.62.tgz", + "integrity": "sha512-TF+wo3MgTLbf37keEwQD0IxvOZO8UZxnpPJDg5iFGAASGxYzbX/Q0y944ATEjrfxG/pF1TWRHCPbFp49Mz1Y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.62" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.62", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.62.tgz", + "integrity": "sha512-ohONqbfobpuaylhqFbtCzc0dFFeNz85FVKSesgT8DS9OV3a25Yj730pTj7/dDtCqmgoCgEj6gDiU9XxgHKQlBw==", + "dev": true, + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -3163,29 +3139,26 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" + "node": ">=16" } }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4.0.0" + "bin": { + "tree-kill": "cli.js" } }, "node_modules/tslib": { @@ -3256,17 +3229,6 @@ "node": ">=8" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/cypress-tests/package.json b/cypress-tests/package.json index 7bb40354efdd..204e3393d0a1 100644 --- a/cypress-tests/package.json +++ b/cypress-tests/package.json @@ -15,11 +15,9 @@ "author": "", "license": "ISC", "devDependencies": { - "cypress": "^13.14.1", + "cypress": "^13.16.0", "cypress-mochawesome-reporter": "^3.8.2", - "jsqr": "^1.4.0" - }, - "dependencies": { + "jsqr": "^1.4.0", "prettier": "^3.3.2" } } From 57e64c26ca4251b493c87bfe93799faaab4ffa89 Mon Sep 17 00:00:00 2001 From: Apoorv Dixit <64925866+apoorvdixit88@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:04:53 +0530 Subject: [PATCH 03/51] feat(payments): add merchant order ref id filter (#6630) --- crates/api_models/src/payments.rs | 2 ++ .../src/payments/payment_intent.rs | 5 +++++ crates/router/src/types/storage/dispute.rs | 10 ++++++---- crates/router/src/types/storage/refund.rs | 17 +++++++++++------ .../storage_impl/src/payments/payment_intent.rs | 11 +++++++++++ 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 06dd7dc79696..c0cf816c68d0 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4927,6 +4927,8 @@ pub struct PaymentListFilterConstraints { pub order: Order, /// The List of all the card networks to filter payments list pub card_network: Option>, + /// The identifier for merchant order reference id + pub merchant_order_reference_id: Option, } impl PaymentListFilterConstraints { diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index d10dafe883a7..b0ffe519d630 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1002,6 +1002,7 @@ pub struct PaymentIntentListParams { pub limit: Option, pub order: api_models::payments::Order, pub card_network: Option>, + pub merchant_order_reference_id: Option, } impl From for PaymentIntentFetchConstraints { @@ -1036,6 +1037,7 @@ impl From for PaymentIntentFetchCo limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V1)), order: Default::default(), card_network: None, + merchant_order_reference_id: None, })) } } @@ -1061,6 +1063,7 @@ impl From for PaymentIntentFetchConstraints { limit: None, order: Default::default(), card_network: None, + merchant_order_reference_id: None, })) } } @@ -1084,6 +1087,7 @@ impl From for PaymentIntentF merchant_connector_id, order, card_network, + merchant_order_reference_id, } = value; if let Some(payment_intent_id) = payment_id { Self::Single { payment_intent_id } @@ -1107,6 +1111,7 @@ impl From for PaymentIntentF limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V2)), order, card_network, + merchant_order_reference_id, })) } } diff --git a/crates/router/src/types/storage/dispute.rs b/crates/router/src/types/storage/dispute.rs index 2d42aaa0a8a9..cf1ff52960fd 100644 --- a/crates/router/src/types/storage/dispute.rs +++ b/crates/router/src/types/storage/dispute.rs @@ -1,6 +1,6 @@ use async_bb8_diesel::AsyncRunQueryDsl; use common_utils::errors::CustomResult; -use diesel::{associations::HasTable, ExpressionMethods, QueryDsl}; +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, QueryDsl}; pub use diesel_models::dispute::{Dispute, DisputeNew, DisputeUpdate}; use diesel_models::{errors, query::generics::db_metrics, schema::dispute::dsl}; use error_stack::ResultExt; @@ -43,9 +43,11 @@ impl DisputeDbExt for Dispute { &dispute_list_constraints.dispute_id, ) { search_by_payment_or_dispute_id = true; - filter = filter - .filter(dsl::payment_id.eq(payment_id.to_owned())) - .or_filter(dsl::dispute_id.eq(dispute_id.to_owned())); + filter = filter.filter( + dsl::payment_id + .eq(payment_id.to_owned()) + .or(dsl::dispute_id.eq(dispute_id.to_owned())), + ); }; if !search_by_payment_or_dispute_id { diff --git a/crates/router/src/types/storage/refund.rs b/crates/router/src/types/storage/refund.rs index 8076c2c7a6e1..e46fc1a3f476 100644 --- a/crates/router/src/types/storage/refund.rs +++ b/crates/router/src/types/storage/refund.rs @@ -1,7 +1,7 @@ use api_models::payments::AmountFilter; use async_bb8_diesel::AsyncRunQueryDsl; use common_utils::errors::CustomResult; -use diesel::{associations::HasTable, ExpressionMethods, QueryDsl}; +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, QueryDsl}; pub use diesel_models::refund::{ Refund, RefundCoreWorkflow, RefundNew, RefundUpdate, RefundUpdateInternal, }; @@ -67,8 +67,11 @@ impl RefundDbExt for Refund { ) { search_by_pay_or_ref_id = true; filter = filter - .filter(dsl::payment_id.eq(pid.to_owned())) - .or_filter(dsl::refund_id.eq(ref_id.to_owned())) + .filter( + dsl::payment_id + .eq(pid.to_owned()) + .or(dsl::refund_id.eq(ref_id.to_owned())), + ) .limit(limit) .offset(offset); }; @@ -228,9 +231,11 @@ impl RefundDbExt for Refund { &refund_list_details.refund_id, ) { search_by_pay_or_ref_id = true; - filter = filter - .filter(dsl::payment_id.eq(pid.to_owned())) - .or_filter(dsl::refund_id.eq(ref_id.to_owned())); + filter = filter.filter( + dsl::payment_id + .eq(pid.to_owned()) + .or(dsl::refund_id.eq(ref_id.to_owned())), + ); }; if !search_by_pay_or_ref_id { diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index f26cf876ade8..15f1aa243708 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -871,6 +871,12 @@ impl PaymentIntentInterface for crate::RouterStore { query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); } + if let Some(merchant_order_reference_id) = ¶ms.merchant_order_reference_id { + query = query.filter( + pi_dsl::merchant_order_reference_id.eq(merchant_order_reference_id.clone()), + ) + } + if let Some(profile_id) = ¶ms.profile_id { query = query.filter(pi_dsl::profile_id.eq_any(profile_id.clone())); } @@ -1041,6 +1047,11 @@ impl PaymentIntentInterface for crate::RouterStore { if let Some(customer_id) = ¶ms.customer_id { query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); } + if let Some(merchant_order_reference_id) = ¶ms.merchant_order_reference_id { + query = query.filter( + pi_dsl::merchant_order_reference_id.eq(merchant_order_reference_id.clone()), + ) + } if let Some(profile_id) = ¶ms.profile_id { query = query.filter(pi_dsl::profile_id.eq_any(profile_id.clone())); } From 00686cc17fa20635e5fc04130aefb08a7c6c8cfc Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:05:33 +0530 Subject: [PATCH 04/51] ci(cypress): use ubuntu runner (#6655) --- .github/workflows/cypress-tests-runner.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cypress-tests-runner.yml b/.github/workflows/cypress-tests-runner.yml index 84680c945e10..e07b4e48d6ff 100644 --- a/.github/workflows/cypress-tests-runner.yml +++ b/.github/workflows/cypress-tests-runner.yml @@ -24,7 +24,7 @@ env: jobs: runner: name: Run Cypress tests - runs-on: hyperswitch-runners + runs-on: ubuntu-latest services: redis: @@ -190,7 +190,7 @@ jobs: ROUTER__SERVER__WORKERS: 4 shell: bash -leuo pipefail {0} run: | - scripts/execute_cypress.sh --parallel 3 + scripts/execute_cypress.sh kill "${{ env.PID }}" From 68876811a8817cdec09be407fbbbbf7f19992565 Mon Sep 17 00:00:00 2001 From: awasthi21 <107559116+awasthi21@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:42:51 +0530 Subject: [PATCH 05/51] feat(connector): [Elavon] Implement cards Flow (#6485) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .typos.toml | 1 + Cargo.lock | 1 + api-reference-v2/openapi_spec.json | 2 + api-reference/openapi_spec.json | 2 + config/config.example.toml | 2 +- config/deployments/integration_test.toml | 2 +- config/deployments/production.toml | 2 +- config/deployments/sandbox.toml | 2 +- config/development.toml | 2 +- config/docker_compose.toml | 2 +- crates/api_models/src/connector_enums.rs | 2 + crates/common_enums/src/connector_enums.rs | 1 + crates/connector_configs/src/connector.rs | 2 + .../connector_configs/toml/development.toml | 44 +- crates/connector_configs/toml/production.toml | 44 +- crates/connector_configs/toml/sandbox.toml | 44 +- crates/hyperswitch_connectors/Cargo.toml | 1 + .../src/connectors/elavon.rs | 226 ++++--- .../src/connectors/elavon/transformers.rs | 631 ++++++++++++++---- crates/hyperswitch_connectors/src/utils.rs | 17 + .../src/router_request_types.rs | 1 + .../payment_connector_required_fields.rs | 114 ++++ crates/router/src/core/admin.rs | 4 + crates/router/src/core/utils.rs | 1 + crates/router/src/types/api.rs | 4 +- crates/router/src/types/transformers.rs | 2 +- crates/router/tests/connectors/utils.rs | 2 + .../cypress/e2e/PaymentUtils/Elavon.js | 571 ++++++++++++++++ .../cypress/e2e/PaymentUtils/Utils.js | 2 + loadtest/config/development.toml | 2 +- 30 files changed, 1498 insertions(+), 235 deletions(-) create mode 100644 cypress-tests/cypress/e2e/PaymentUtils/Elavon.js diff --git a/.typos.toml b/.typos.toml index 109e411884a8..983ead3f75d6 100644 --- a/.typos.toml +++ b/.typos.toml @@ -19,6 +19,7 @@ HypoNoeLbFurNiederosterreichUWien = "HypoNoeLbFurNiederosterreichUWien" hypo_noe_lb_fur_niederosterreich_u_wien = "hypo_noe_lb_fur_niederosterreich_u_wien" IOT = "IOT" # British Indian Ocean Territory country code klick = "klick" # Swedish word for clicks +FPR = "FPR" # Fraud Prevention Rules LSO = "LSO" # Lesotho country code NAM = "NAM" # Namibia country code ND = "ND" # North Dakota state code diff --git a/Cargo.lock b/Cargo.lock index a2ede8c08c10..4118ea29c2da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4048,6 +4048,7 @@ dependencies = [ "mime", "once_cell", "qrcode", + "quick-xml", "rand", "regex", "reqwest 0.11.27", diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 2a5b1cfa3107..1494908c5fcd 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -5911,6 +5911,7 @@ "digitalvirgo", "dlocal", "ebanx", + "elavon", "fiserv", "fiservemea", "fiuu", @@ -18123,6 +18124,7 @@ "digitalvirgo", "dlocal", "ebanx", + "elavon", "fiserv", "fiservemea", "fiuu", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index e029bef94534..f624b4f68dda 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -8743,6 +8743,7 @@ "digitalvirgo", "dlocal", "ebanx", + "elavon", "fiserv", "fiservemea", "fiuu", @@ -22803,6 +22804,7 @@ "digitalvirgo", "dlocal", "ebanx", + "elavon", "fiserv", "fiservemea", "fiuu", diff --git a/config/config.example.toml b/config/config.example.toml index 08a32035c4b7..e55665ffc708 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -207,7 +207,7 @@ digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.stagin dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" -elavon.base_url = "https://api.demo.convergepay.com" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index e1ea99bc8d11..5f4de111ac8c 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -49,7 +49,7 @@ digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.stagin dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" -elavon.base_url = "https://api.demo.convergepay.com" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index e5f906460584..61738f04cdb2 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -53,7 +53,7 @@ digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.stagin dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" -elavon.base_url = "https://api.convergepay.com" +elavon.base_url = "https://api.convergepay.com/VirtualMerchant/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com" fiuu.base_url = "https://pay.merchant.razer.com/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 4421d1b1e967..24c7642200c9 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -53,7 +53,7 @@ digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.stagin dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" -elavon.base_url = "https://api.demo.convergepay.com" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" diff --git a/config/development.toml b/config/development.toml index a58497f4a51e..630cc4a2303c 100644 --- a/config/development.toml +++ b/config/development.toml @@ -222,7 +222,7 @@ digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.stagin dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" -elavon.base_url = "https://api.demo.convergepay.com" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index c293a7ac821e..354af98dbadf 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -137,7 +137,7 @@ digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.stagin dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" -elavon.base_url = "https://api.demo.convergepay.com" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" diff --git a/crates/api_models/src/connector_enums.rs b/crates/api_models/src/connector_enums.rs index 11b384009203..fe2c4f037bf5 100644 --- a/crates/api_models/src/connector_enums.rs +++ b/crates/api_models/src/connector_enums.rs @@ -74,6 +74,7 @@ pub enum Connector { Digitalvirgo, Dlocal, Ebanx, + Elavon, Fiserv, Fiservemea, Fiuu, @@ -216,6 +217,7 @@ impl Connector { | Self::Digitalvirgo | Self::Dlocal | Self::Ebanx + | Self::Elavon | Self::Fiserv | Self::Fiservemea | Self::Fiuu diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index e75a4038c077..9d17cdb61d4c 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -71,6 +71,7 @@ pub enum RoutableConnectors { Digitalvirgo, Dlocal, Ebanx, + Elavon, Fiserv, Fiservemea, Fiuu, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index b1cf0bd9f9c7..65b286f8423c 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -182,6 +182,7 @@ pub struct ConnectorConfig { pub digitalvirgo: Option, pub dlocal: Option, pub ebanx_payout: Option, + pub elavon: Option, pub fiserv: Option, pub fiservemea: Option, pub fiuu: Option, @@ -346,6 +347,7 @@ impl ConnectorConfig { Connector::Digitalvirgo => Ok(connector_data.digitalvirgo), Connector::Dlocal => Ok(connector_data.dlocal), Connector::Ebanx => Ok(connector_data.ebanx_payout), + Connector::Elavon => Ok(connector_data.elavon), Connector::Fiserv => Ok(connector_data.fiserv), Connector::Fiservemea => Ok(connector_data.fiservemea), Connector::Fiuu => Ok(connector_data.fiuu), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 5d41efc1a50f..8ed44df888f0 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -4312,4 +4312,46 @@ type="Radio" options=["Hyperswitch"] [fiuu.connector_webhook_details] -merchant_secret="Source verification key" \ No newline at end of file +merchant_secret="Source verification key" + +[elavon] +[[elavon.credit]] + payment_method_type = "Mastercard" +[[elavon.credit]] + payment_method_type = "Visa" +[[elavon.credit]] + payment_method_type = "Interac" +[[elavon.credit]] + payment_method_type = "AmericanExpress" +[[elavon.credit]] + payment_method_type = "JCB" +[[elavon.credit]] + payment_method_type = "DinersClub" +[[elavon.credit]] + payment_method_type = "Discover" +[[elavon.credit]] + payment_method_type = "CartesBancaires" +[[elavon.credit]] + payment_method_type = "UnionPay" +[[elavon.debit]] + payment_method_type = "Mastercard" +[[elavon.debit]] + payment_method_type = "Visa" +[[elavon.debit]] + payment_method_type = "Interac" +[[elavon.debit]] + payment_method_type = "AmericanExpress" +[[elavon.debit]] + payment_method_type = "JCB" +[[elavon.debit]] + payment_method_type = "DinersClub" +[[elavon.debit]] + payment_method_type = "Discover" +[[elavon.debit]] + payment_method_type = "CartesBancaires" +[[elavon.debit]] + payment_method_type = "UnionPay" +[elavon.connector_auth.SignatureKey] +api_key="Account Id" +key1="User ID" +api_secret="Pin" \ No newline at end of file diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index cc501a4c101e..887a1398b48f 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -3270,4 +3270,46 @@ type="Radio" options=["Hyperswitch"] [fiuu.connector_webhook_details] -merchant_secret="Source verification key" \ No newline at end of file +merchant_secret="Source verification key" + +[elavon] +[[elavon.credit]] + payment_method_type = "Mastercard" +[[elavon.credit]] + payment_method_type = "Visa" +[[elavon.credit]] + payment_method_type = "Interac" +[[elavon.credit]] + payment_method_type = "AmericanExpress" +[[elavon.credit]] + payment_method_type = "JCB" +[[elavon.credit]] + payment_method_type = "DinersClub" +[[elavon.credit]] + payment_method_type = "Discover" +[[elavon.credit]] + payment_method_type = "CartesBancaires" +[[elavon.credit]] + payment_method_type = "UnionPay" +[[elavon.debit]] + payment_method_type = "Mastercard" +[[elavon.debit]] + payment_method_type = "Visa" +[[elavon.debit]] + payment_method_type = "Interac" +[[elavon.debit]] + payment_method_type = "AmericanExpress" +[[elavon.debit]] + payment_method_type = "JCB" +[[elavon.debit]] + payment_method_type = "DinersClub" +[[elavon.debit]] + payment_method_type = "Discover" +[[elavon.debit]] + payment_method_type = "CartesBancaires" +[[elavon.debit]] + payment_method_type = "UnionPay" +[elavon.connector_auth.SignatureKey] +api_key="Account Id" +key1="User ID" +api_secret="Pin" \ No newline at end of file diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 5462c121c371..57cb91050422 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -4276,4 +4276,46 @@ type="Radio" options=["Hyperswitch"] [fiuu.connector_webhook_details] -merchant_secret="Source verification key" \ No newline at end of file +merchant_secret="Source verification key" + +[elavon] +[[elavon.credit]] + payment_method_type = "Mastercard" +[[elavon.credit]] + payment_method_type = "Visa" +[[elavon.credit]] + payment_method_type = "Interac" +[[elavon.credit]] + payment_method_type = "AmericanExpress" +[[elavon.credit]] + payment_method_type = "JCB" +[[elavon.credit]] + payment_method_type = "DinersClub" +[[elavon.credit]] + payment_method_type = "Discover" +[[elavon.credit]] + payment_method_type = "CartesBancaires" +[[elavon.credit]] + payment_method_type = "UnionPay" +[[elavon.debit]] + payment_method_type = "Mastercard" +[[elavon.debit]] + payment_method_type = "Visa" +[[elavon.debit]] + payment_method_type = "Interac" +[[elavon.debit]] + payment_method_type = "AmericanExpress" +[[elavon.debit]] + payment_method_type = "JCB" +[[elavon.debit]] + payment_method_type = "DinersClub" +[[elavon.debit]] + payment_method_type = "Discover" +[[elavon.debit]] + payment_method_type = "CartesBancaires" +[[elavon.debit]] + payment_method_type = "UnionPay" +[elavon.connector_auth.SignatureKey] +api_key="Account Id" +key1="User ID" +api_secret="Pin" \ No newline at end of file diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index ca9d338920f8..5f4def7587aa 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -23,6 +23,7 @@ image = { version = "0.25.1", default-features = false, features = ["png"] } mime = "0.3.17" once_cell = "1.19.0" qrcode = "0.14.0" +quick-xml = { version = "0.31.0", features = ["serialize"] } rand = "0.8.5" regex = "1.10.4" reqwest = { version = "0.11.27" } diff --git a/crates/hyperswitch_connectors/src/connectors/elavon.rs b/crates/hyperswitch_connectors/src/connectors/elavon.rs index 7e4408d301fa..b236e3a604c0 100644 --- a/crates/hyperswitch_connectors/src/connectors/elavon.rs +++ b/crates/hyperswitch_connectors/src/connectors/elavon.rs @@ -1,14 +1,16 @@ pub mod transformers; +use std::{collections::HashMap, str}; +use common_enums::{CaptureMethod, PaymentMethodType}; use common_utils::{ errors::CustomResult, - ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; -use error_stack::{report, ResultExt}; +use error_stack::report; use hyperswitch_domain_models::{ - router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, @@ -21,8 +23,8 @@ use hyperswitch_domain_models::{ }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -33,20 +35,40 @@ use hyperswitch_interfaces::{ types::{self, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{Secret, WithoutType}; +use serde::Serialize; use transformers as elavon; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{self, PaymentMethodDataType}, +}; +pub fn struct_to_xml( + item: &T, +) -> Result>, errors::ConnectorError> { + let xml_content = quick_xml::se::to_string_with_root("txn", &item).map_err(|e| { + router_env::logger::error!("Error serializing Struct: {:?}", e); + errors::ConnectorError::ResponseDeserializationFailed + })?; + + let mut result = HashMap::new(); + result.insert( + "xmldata".to_string(), + Secret::<_, WithoutType>::new(xml_content), + ); + Ok(result) +} #[derive(Clone)] pub struct Elavon { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Elavon { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &StringMajorUnitForConnector, } } } @@ -96,62 +118,18 @@ impl ConnectorCommon for Elavon { fn get_currency_unit(&self) -> api::CurrencyUnit { api::CurrencyUnit::Base - // TODO! Check connector documentation, on which unit they are processing the currency. - // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, - // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base } fn common_get_content_type(&self) -> &'static str { - "application/json" + "application/x-www-form-urlencoded" } fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.elavon.base_url.as_ref() } - - fn get_auth_header( - &self, - auth_type: &ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { - let auth = elavon::ElavonAuthType::try_from(auth_type) - .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - Ok(vec![( - headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), - )]) - } - - fn build_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - let response: elavon::ElavonErrorResponse = res - .response - .parse_struct("ElavonErrorResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - - Ok(ErrorResponse { - status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, - attempt_status: None, - connector_transaction_id: None, - }) - } } -impl ConnectorValidation for Elavon { - //TODO: implement functions when support enabled -} - -impl ConnectorIntegration for Elavon { - //TODO: implement sessions flow -} +impl ConnectorIntegration for Elavon {} impl ConnectorIntegration for Elavon {} @@ -173,9 +151,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}processxml.do", self.base_url(connectors))) } fn get_request_body( @@ -191,7 +169,10 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult { - let response: elavon::ElavonPaymentsResponse = res - .response - .parse_struct("Elavon PaymentsAuthorizeResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let response: elavon::ElavonPaymentsResponse = + utils::deserialize_xml_to_struct(&res.response)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); RouterData::try_from(ResponseRouterData { @@ -260,9 +239,21 @@ impl ConnectorIntegration for Ela fn get_url( &self, _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}processxml.do", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = elavon::SyncRequest::try_from(req)?; + router_env::logger::info!(raw_connector_request=?connector_req); + Ok(RequestContent::FormUrlEncoded(Box::new(struct_to_xml( + &connector_req, + )?))) } fn build_request( @@ -272,9 +263,12 @@ impl ConnectorIntegration for Ela ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() - .method(Method::Get) + .method(Method::Post) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() + .set_body(types::PaymentsSyncType::get_request_body( + self, req, connectors, + )?) .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) .build(), )) @@ -286,10 +280,7 @@ impl ConnectorIntegration for Ela event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: elavon::ElavonPaymentsResponse = res - .response - .parse_struct("elavon PaymentsSyncResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let response: elavon::ElavonSyncResponse = utils::deserialize_xml_to_struct(&res.response)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); RouterData::try_from(ResponseRouterData { @@ -324,17 +315,27 @@ impl ConnectorIntegration fo fn get_url( &self, _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}processxml.do", self.base_url(connectors))) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + let connector_router_data = elavon::ElavonRouterData::from((amount, req)); + let connector_req = elavon::PaymentsCaptureRequest::try_from(&connector_router_data)?; + router_env::logger::info!(raw_connector_request=?connector_req); + Ok(RequestContent::FormUrlEncoded(Box::new(struct_to_xml( + &connector_req, + )?))) } fn build_request( @@ -363,10 +364,8 @@ impl ConnectorIntegration fo event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: elavon::ElavonPaymentsResponse = res - .response - .parse_struct("Elavon PaymentsCaptureResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let response: elavon::ElavonPaymentsResponse = + utils::deserialize_xml_to_struct(&res.response)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); RouterData::try_from(ResponseRouterData { @@ -385,7 +384,15 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Elavon {} +impl ConnectorIntegration for Elavon { + fn build_request( + &self, + _req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("Cancel/Void flow".to_string()).into()) + } +} impl ConnectorIntegration for Elavon { fn get_headers( @@ -403,9 +410,9 @@ impl ConnectorIntegration for Elavon fn get_url( &self, _req: &RefundsRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}processxml.do", self.base_url(connectors))) } fn get_request_body( @@ -421,7 +428,10 @@ impl ConnectorIntegration for Elavon let connector_router_data = elavon::ElavonRouterData::from((refund_amount, req)); let connector_req = elavon::ElavonRefundRequest::try_from(&connector_router_data)?; - Ok(RequestContent::Json(Box::new(connector_req))) + router_env::logger::info!(raw_connector_request=?connector_req); + Ok(RequestContent::FormUrlEncoded(Box::new(struct_to_xml( + &connector_req, + )?))) } fn build_request( @@ -449,10 +459,8 @@ impl ConnectorIntegration for Elavon event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult, errors::ConnectorError> { - let response: elavon::RefundResponse = - res.response - .parse_struct("elavon RefundResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let response: elavon::ElavonPaymentsResponse = + utils::deserialize_xml_to_struct(&res.response)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); RouterData::try_from(ResponseRouterData { @@ -487,9 +495,20 @@ impl ConnectorIntegration for Elavon { fn get_url( &self, _req: &RefundSyncRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}processxml.do", self.base_url(connectors))) + } + fn get_request_body( + &self, + req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = elavon::SyncRequest::try_from(req)?; + router_env::logger::info!(raw_connector_request=?connector_req); + Ok(RequestContent::FormUrlEncoded(Box::new(struct_to_xml( + &connector_req, + )?))) } fn build_request( @@ -499,7 +518,7 @@ impl ConnectorIntegration for Elavon { ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() - .method(Method::Get) + .method(Method::Post) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -516,10 +535,7 @@ impl ConnectorIntegration for Elavon { event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: elavon::RefundResponse = res - .response - .parse_struct("elavon RefundSyncResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let response: elavon::ElavonSyncResponse = utils::deserialize_xml_to_struct(&res.response)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); RouterData::try_from(ResponseRouterData { @@ -561,3 +577,27 @@ impl webhooks::IncomingWebhook for Elavon { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorValidation for Elavon { + fn validate_capture_method( + &self, + capture_method: Option, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + CaptureMethod::Automatic | CaptureMethod::Manual => Ok(()), + CaptureMethod::ManualMultiple | CaptureMethod::Scheduled => Err( + utils::construct_not_implemented_error_report(capture_method, self.id()), + ), + } + } + fn validate_mandate_payment( + &self, + pm_type: Option, + pm_data: PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]); + utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/elavon/transformers.rs b/crates/hyperswitch_connectors/src/connectors/elavon/transformers.rs index 1dbf03ea2757..67561acf8db4 100644 --- a/crates/hyperswitch_connectors/src/connectors/elavon/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/elavon/transformers.rs @@ -1,52 +1,92 @@ -use common_enums::enums; -use common_utils::types::StringMinorUnit; +use cards::CardNumber; +use common_enums::{enums, Currency}; +use common_utils::{pii::Email, types::StringMajorUnit}; +use error_stack::ResultExt; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, - router_data::{ConnectorAuthType, RouterData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, - router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + router_request_types::{PaymentsAuthorizeData, ResponseId}, + router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, }; use hyperswitch_interfaces::errors; -use masking::Secret; +use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + types::{ + PaymentsCaptureResponseRouterData, PaymentsSyncResponseRouterData, + RefundsResponseRouterData, ResponseRouterData, + }, + utils::{CardData, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData as _}, }; -//TODO: Fill the struct with respective fields pub struct ElavonRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: StringMajorUnit, pub router_data: T, } -impl From<(StringMinorUnit, T)> for ElavonRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(StringMajorUnit, T)> for ElavonRouterData { + fn from((amount, item): (StringMajorUnit, T)) -> Self { Self { amount, router_data: item, } } } - -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] -pub struct ElavonPaymentsRequest { - amount: StringMinorUnit, - card: ElavonCard, +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +pub enum TransactionType { + CcSale, + CcAuthOnly, + CcComplete, + CcReturn, + TxnQuery, +} +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "UPPERCASE")] +pub enum SyncTransactionType { + Sale, + AuthOnly, + Return, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct ElavonCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum ElavonPaymentsRequest { + Card(CardPaymentRequest), + MandatePayment(MandatePaymentRequest), +} +#[derive(Debug, Serialize)] +pub struct CardPaymentRequest { + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_amount: StringMajorUnit, + pub ssl_card_number: CardNumber, + pub ssl_exp_date: Secret, + pub ssl_cvv2cvc2: Secret, + pub ssl_email: Email, + #[serde(skip_serializing_if = "Option::is_none")] + pub ssl_add_token: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ssl_get_token: Option, + pub ssl_transaction_currency: Currency, +} +#[derive(Debug, Serialize)] +pub struct MandatePaymentRequest { + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_amount: StringMajorUnit, + pub ssl_email: Email, + pub ssl_token: Secret, } impl TryFrom<&ElavonRouterData<&PaymentsAuthorizeRouterData>> for ElavonPaymentsRequest { @@ -54,175 +94,506 @@ impl TryFrom<&ElavonRouterData<&PaymentsAuthorizeRouterData>> for ElavonPayments fn try_from( item: &ElavonRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { + let auth = ElavonAuthType::try_from(&item.router_data.connector_auth_type)?; match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = ElavonCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; - Ok(Self { - amount: item.amount.clone(), - card, - }) - } + PaymentMethodData::Card(req_card) => Ok(Self::Card(CardPaymentRequest { + ssl_transaction_type: match item.router_data.request.is_auto_capture()? { + true => TransactionType::CcSale, + false => TransactionType::CcAuthOnly, + }, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), + ssl_amount: item.amount.clone(), + ssl_card_number: req_card.card_number.clone(), + ssl_exp_date: req_card.get_expiry_date_as_mmyy()?, + ssl_cvv2cvc2: req_card.card_cvc, + ssl_email: item.router_data.get_billing_email()?, + ssl_add_token: match item.router_data.request.is_mandate_payment() { + true => Some("Y".to_string()), + false => None, + }, + ssl_get_token: match item.router_data.request.is_mandate_payment() { + true => Some("Y".to_string()), + false => None, + }, + ssl_transaction_currency: item.router_data.request.currency, + })), + PaymentMethodData::MandatePayment => Ok(Self::MandatePayment(MandatePaymentRequest { + ssl_transaction_type: match item.router_data.request.is_auto_capture()? { + true => TransactionType::CcSale, + false => TransactionType::CcAuthOnly, + }, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), + ssl_amount: item.amount.clone(), + ssl_email: item.router_data.get_billing_email()?, + ssl_token: Secret::new(item.router_data.request.get_connector_mandate_id()?), + })), _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } } -//TODO: Fill the struct with respective fields -// Auth Struct pub struct ElavonAuthType { - pub(super) api_key: Secret, + pub(super) account_id: Secret, + pub(super) user_id: Secret, + pub(super) pin: Secret, } impl TryFrom<&ConnectorAuthType> for ElavonAuthType { type Error = error_stack::Report; fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + account_id: api_key.to_owned(), + user_id: key1.to_owned(), + pin: api_secret.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum ElavonPaymentStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for common_enums::AttemptStatus { - fn from(item: ElavonPaymentStatus) -> Self { - match item { - ElavonPaymentStatus::Succeeded => Self::Charged, - ElavonPaymentStatus::Failed => Self::Failure, - ElavonPaymentStatus::Processing => Self::Authorizing, - } - } + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +enum SslResult { + #[serde(rename = "0")] + ImportedBatchFile, + #[serde(other)] + DeclineOrUnauthorized, } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ElavonPaymentsResponse { - status: ElavonPaymentStatus, - id: String, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ElavonPaymentsResponse { + #[serde(rename = "txn")] + Success(PaymentResponse), + #[serde(rename = "txn")] + Error(ElavonErrorResponse), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ElavonErrorResponse { + error_code: String, + error_message: String, + error_name: String, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PaymentResponse { + ssl_result: SslResult, + ssl_txn_id: String, + ssl_result_message: String, + ssl_token: Option>, } -impl TryFrom> - for RouterData +impl + TryFrom< + ResponseRouterData, + > for RouterData { type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, + item: ResponseRouterData< + F, + ElavonPaymentsResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, ) -> Result { - Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: Box::new(None), - mandate_reference: Box::new(None), - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, + let status = get_payment_status(&item.response, item.data.request.is_auto_capture()?); + let response = match &item.response { + ElavonPaymentsResponse::Error(error) => Err(ErrorResponse { + code: error.error_code.clone(), + message: error.error_message.clone(), + reason: Some(error.error_message.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, }), + ElavonPaymentsResponse::Success(response) => { + if status == enums::AttemptStatus::Failure { + Err(ErrorResponse { + code: response.ssl_result_message.clone(), + message: response.ssl_result_message.clone(), + reason: Some(response.ssl_result_message.clone()), + attempt_status: None, + connector_transaction_id: Some(response.ssl_txn_id.clone()), + status_code: item.http_code, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + response.ssl_txn_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(Some(MandateReference { + connector_mandate_id: response + .ssl_token + .as_ref() + .map(|secret| secret.clone().expose()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + })), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(response.ssl_txn_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }) + } + } + }; + Ok(Self { + status, + response, ..item.data }) } } -//TODO: Fill the struct with respective fields -// REFUND : -// Type definition for RefundRequest -#[derive(Default, Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] +pub enum TransactionSyncStatus { + PEN, // Pended + OPN, // Unpended / release / open + REV, // Review + STL, // Settled + PST, // Failed due to post-auth rule + FPR, // Failed due to fraud prevention rules + PRE, // Failed due to pre-auth rule +} + +#[derive(Debug, Serialize)] +#[serde(rename = "txn")] +pub struct PaymentsCaptureRequest { + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_amount: StringMajorUnit, + pub ssl_txn_id: String, +} +#[derive(Debug, Serialize)] +#[serde(rename = "txn")] +pub struct PaymentsVoidRequest { + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_txn_id: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename = "txn")] pub struct ElavonRefundRequest { - pub amount: StringMinorUnit, + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_amount: StringMajorUnit, + pub ssl_txn_id: String, } +#[derive(Debug, Serialize)] +#[serde(rename = "txn")] +pub struct SyncRequest { + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_txn_id: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename = "txn")] +pub struct ElavonSyncResponse { + pub ssl_trans_status: TransactionSyncStatus, + pub ssl_transaction_type: SyncTransactionType, + pub ssl_txn_id: String, +} +impl TryFrom<&RefundSyncRouterData> for SyncRequest { + type Error = error_stack::Report; + fn try_from(item: &RefundSyncRouterData) -> Result { + let auth = ElavonAuthType::try_from(&item.connector_auth_type)?; + Ok(Self { + ssl_txn_id: item.request.get_connector_refund_id()?, + ssl_transaction_type: TransactionType::TxnQuery, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), + }) + } +} +impl TryFrom<&PaymentsSyncRouterData> for SyncRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsSyncRouterData) -> Result { + let auth = ElavonAuthType::try_from(&item.connector_auth_type)?; + Ok(Self { + ssl_txn_id: item + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?, + ssl_transaction_type: TransactionType::TxnQuery, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), + }) + } +} impl TryFrom<&ElavonRouterData<&RefundsRouterData>> for ElavonRefundRequest { type Error = error_stack::Report; fn try_from(item: &ElavonRouterData<&RefundsRouterData>) -> Result { + let auth = ElavonAuthType::try_from(&item.router_data.connector_auth_type)?; Ok(Self { - amount: item.amount.to_owned(), + ssl_txn_id: item.router_data.request.connector_transaction_id.clone(), + ssl_amount: item.amount.clone(), + ssl_transaction_type: TransactionType::CcReturn, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), }) } } -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { - match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping - } +impl TryFrom<&ElavonRouterData<&PaymentsCaptureRouterData>> for PaymentsCaptureRequest { + type Error = error_stack::Report; + fn try_from(item: &ElavonRouterData<&PaymentsCaptureRouterData>) -> Result { + let auth = ElavonAuthType::try_from(&item.router_data.connector_auth_type)?; + Ok(Self { + ssl_txn_id: item.router_data.request.connector_transaction_id.clone(), + ssl_amount: item.amount.clone(), + ssl_transaction_type: TransactionType::CcComplete, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), + }) } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RefundResponse { - id: String, - status: RefundStatus, +impl TryFrom> for PaymentsSyncRouterData { + type Error = error_stack::Report; + fn try_from( + item: PaymentsSyncResponseRouterData, + ) -> Result { + Ok(Self { + status: get_sync_status(item.data.status, &item.response), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.ssl_txn_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } } - -impl TryFrom> for RefundsRouterData { +impl TryFrom> for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.ssl_txn_id.clone(), + refund_status: get_refund_status(item.data.request.refund_status, &item.response), }), ..item.data }) } } -impl TryFrom> for RefundsRouterData { +impl TryFrom> + for PaymentsCaptureRouterData +{ type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: PaymentsCaptureResponseRouterData, ) -> Result { + let status = map_payment_status(&item.response, enums::AttemptStatus::Charged); + let response = match &item.response { + ElavonPaymentsResponse::Error(error) => Err(ErrorResponse { + code: error.error_code.clone(), + message: error.error_message.clone(), + reason: Some(error.error_message.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }), + ElavonPaymentsResponse::Success(response) => { + if status == enums::AttemptStatus::Failure { + Err(ErrorResponse { + code: response.ssl_result_message.clone(), + message: response.ssl_result_message.clone(), + reason: Some(response.ssl_result_message.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + response.ssl_txn_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(response.ssl_txn_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }) + } + } + }; Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + status, + response, + ..item.data + }) + } +} +impl TryFrom> + for RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + let status = enums::RefundStatus::from(&item.response); + let response = match &item.response { + ElavonPaymentsResponse::Error(error) => Err(ErrorResponse { + code: error.error_code.clone(), + message: error.error_message.clone(), + reason: Some(error.error_message.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, }), + ElavonPaymentsResponse::Success(response) => { + if status == enums::RefundStatus::Failure { + Err(ErrorResponse { + code: response.ssl_result_message.clone(), + message: response.ssl_result_message.clone(), + reason: Some(response.ssl_result_message.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }) + } else { + Ok(RefundsResponseData { + connector_refund_id: response.ssl_txn_id.clone(), + refund_status: enums::RefundStatus::from(&item.response), + }) + } + } + }; + Ok(Self { + response, ..item.data }) } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct ElavonErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, +trait ElavonResponseValidator { + fn is_successful(&self) -> bool; +} +impl ElavonResponseValidator for ElavonPaymentsResponse { + fn is_successful(&self) -> bool { + matches!(self, Self::Success(response) if response.ssl_result == SslResult::ImportedBatchFile) + } +} + +fn map_payment_status( + item: &ElavonPaymentsResponse, + success_status: enums::AttemptStatus, +) -> enums::AttemptStatus { + if item.is_successful() { + success_status + } else { + enums::AttemptStatus::Failure + } +} + +impl From<&ElavonPaymentsResponse> for enums::RefundStatus { + fn from(item: &ElavonPaymentsResponse) -> Self { + if item.is_successful() { + Self::Success + } else { + Self::Failure + } + } +} +fn get_refund_status( + prev_status: enums::RefundStatus, + item: &ElavonSyncResponse, +) -> enums::RefundStatus { + match item.ssl_trans_status { + TransactionSyncStatus::REV | TransactionSyncStatus::OPN | TransactionSyncStatus::PEN => { + prev_status + } + TransactionSyncStatus::STL => enums::RefundStatus::Success, + TransactionSyncStatus::PST | TransactionSyncStatus::FPR | TransactionSyncStatus::PRE => { + enums::RefundStatus::Failure + } + } +} +impl From<&ElavonSyncResponse> for enums::AttemptStatus { + fn from(item: &ElavonSyncResponse) -> Self { + match item.ssl_trans_status { + TransactionSyncStatus::REV + | TransactionSyncStatus::OPN + | TransactionSyncStatus::PEN => Self::Pending, + TransactionSyncStatus::STL => match item.ssl_transaction_type { + SyncTransactionType::Sale => Self::Charged, + SyncTransactionType::AuthOnly => Self::Authorized, + SyncTransactionType::Return => Self::Pending, + }, + TransactionSyncStatus::PST + | TransactionSyncStatus::FPR + | TransactionSyncStatus::PRE => Self::Failure, + } + } +} +fn get_sync_status( + prev_status: enums::AttemptStatus, + item: &ElavonSyncResponse, +) -> enums::AttemptStatus { + match item.ssl_trans_status { + TransactionSyncStatus::REV | TransactionSyncStatus::OPN | TransactionSyncStatus::PEN => { + prev_status + } + TransactionSyncStatus::STL => match item.ssl_transaction_type { + SyncTransactionType::Sale => enums::AttemptStatus::Charged, + SyncTransactionType::AuthOnly => enums::AttemptStatus::Authorized, + SyncTransactionType::Return => enums::AttemptStatus::Pending, + }, + TransactionSyncStatus::PST | TransactionSyncStatus::FPR | TransactionSyncStatus::PRE => { + enums::AttemptStatus::Failure + } + } +} + +fn get_payment_status( + item: &ElavonPaymentsResponse, + is_auto_capture: bool, +) -> enums::AttemptStatus { + if item.is_successful() { + if is_auto_capture { + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Authorized + } + } else { + enums::AttemptStatus::Failure + } } diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index d928bbfa4eb4..a7fe1cc4cd95 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -2250,3 +2250,20 @@ impl WalletData for hyperswitch_domain_models::payment_method_data::WalletData { } } } + +pub fn deserialize_xml_to_struct( + xml_data: &[u8], +) -> Result { + let response_str = std::str::from_utf8(xml_data) + .map_err(|e| { + router_env::logger::error!("Error converting response data to UTF-8: {:?}", e); + errors::ConnectorError::ResponseDeserializationFailed + })? + .trim(); + let result: T = quick_xml::de::from_str(response_str).map_err(|e| { + router_env::logger::error!("Error deserializing XML response: {:?}", e); + errors::ConnectorError::ResponseDeserializationFailed + })?; + + Ok(result) +} diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index c645d0b4b532..c282afa9d0d6 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -617,6 +617,7 @@ pub struct RefundsData { pub minor_payment_amount: MinorUnit, pub minor_refund_amount: MinorUnit, pub integrity_object: Option, + pub refund_status: storage_enums::RefundStatus, } #[derive(Debug, Clone, PartialEq)] diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index 8d5a4fe89b94..4e80ccf027ec 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -1355,6 +1355,63 @@ impl Default for settings::RequiredFields { ), } ), + ( + enums::Connector::Elavon, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Fiserv, RequiredFieldFinal { @@ -4399,6 +4456,63 @@ impl Default for settings::RequiredFields { ), } ), + ( + enums::Connector::Elavon, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Fiserv, RequiredFieldFinal { diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 97a40f38ac4b..a8d14187dc1a 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1347,6 +1347,10 @@ impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { ebanx::transformers::EbanxAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Elavon => { + elavon::transformers::ElavonAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Fiserv => { fiserv::transformers::FiservAuthType::try_from(self.auth_type)?; fiserv::transformers::FiservSessionObject::try_from(self.connector_meta_data)?; diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 167a4c590068..776692d6bec1 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -363,6 +363,7 @@ pub async fn construct_refund_router_data<'a, F>( browser_info, charges, integrity_object: None, + refund_status: refund.refund_status, }, response: Ok(types::RefundsResponseData { diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index f7909a971958..ad40f83a554e 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -410,7 +410,9 @@ impl ConnectorData { enums::Connector::Ebanx => { Ok(ConnectorEnum::Old(Box::new(connector::Ebanx::new()))) } - // enums::Connector::Elavon => Ok(ConnectorEnum::Old(Box::new(connector::Elavon))), + enums::Connector::Elavon => { + Ok(ConnectorEnum::Old(Box::new(connector::Elavon::new()))) + } enums::Connector::Fiserv => Ok(ConnectorEnum::Old(Box::new(&connector::Fiserv))), enums::Connector::Fiservemea => { Ok(ConnectorEnum::Old(Box::new(connector::Fiservemea::new()))) diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index ecec8211446a..c8a9cbea1ddd 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -234,7 +234,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Digitalvirgo => Self::Digitalvirgo, api_enums::Connector::Dlocal => Self::Dlocal, api_enums::Connector::Ebanx => Self::Ebanx, - // api_enums::Connector::Elavon => Self::Elavon, + api_enums::Connector::Elavon => Self::Elavon, api_enums::Connector::Fiserv => Self::Fiserv, api_enums::Connector::Fiservemea => Self::Fiservemea, api_enums::Connector::Fiuu => Self::Fiuu, diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 3402b532fbfb..a03148956ba2 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -406,6 +406,7 @@ pub trait ConnectorActions: Connector { browser_info: None, charges: None, integrity_object: None, + refund_status: enums::RefundStatus::Pending, }), payment_info, ); @@ -1035,6 +1036,7 @@ impl Default for PaymentRefundType { browser_info: None, charges: None, integrity_object: None, + refund_status: enums::RefundStatus::Pending, }; Self(data) } diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Elavon.js b/cypress-tests/cypress/e2e/PaymentUtils/Elavon.js new file mode 100644 index 000000000000..5b985df29dab --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/Elavon.js @@ -0,0 +1,571 @@ +const successfulNo3DSCardDetails = { + card_number: "4111111111111111", + card_exp_month: "06", + card_exp_year: "25", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; +const singleUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + single_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + multi_use: { + amount: 8000, + currency: "USD", + }, + }, +}; +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + billing: { + address: { + line1: "1467", + line2: "CA", + line3: "CA", + city: "Florence", + state: "Tuscany", + zip: "12345", + country: "IT", + first_name: "Max", + last_name: "Mustermann", + }, + email: "mauro.morandi@nexi.it", + phone: { + number: "9123456789", + country_code: "+91", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + No3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + email: "mauro.morandi@nexi.it", + }, + }, + billing: { + email: "mauro.morandi@nexi.it", + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + email: "mauro.morandi@nexi.it", + }, + }, + billing: { + email: "mauro.morandi@nexi.it", + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com" + }, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com" + }, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + email: "mauro.morandi@nexi.it", + + }, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + phone: { + number: "9123456789", + country_code: "+91", + }, + email: "mauro.morandi@nexi.it", + }, + }, + setup_future_usage: "off_session", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + phone: { + number: "9123456789", + country_code: "+91", + }, + email: "mauro.morandi@nexi.it", + }, + }, + setup_future_usage: "off_session", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + phone: { + number: "9123456789", + country_code: "+91", + }, + email: "mauro.morandi@nexi.it", + }, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + email: "mauro.morandi@nexi.it", + }, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + email: "mauro.morandi@nexi.it", + }, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + Capture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 6500, + amount_capturable: 0, + amount_received: 6500, + }, + }, + }, + PartialCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 6500, + amount_capturable: 0, + amount_received: 100, + }, + }, + }, + Refund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + VoidAfterConfirm: { + Request: {}, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Cancel/Void flow is not implemented", + code: "IR_00" + } + } + }, + }, + PartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + billing: { + email: "mauro.morandi@nexi.it", + }, + mandate_data: null, + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + billing: { + email: "mauro.morandi@nexi.it", + }, + currency: "USD", + mandate_data: null, + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js index fc1685a66213..39d8ab6c8af7 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js @@ -22,6 +22,7 @@ import { connectorDetails as wellsfargoConnectorDetails } from "./WellsFargo.js" import { connectorDetails as fiuuConnectorDetails } from "./Fiuu.js"; import { connectorDetails as worldpayConnectorDetails } from "./WorldPay.js"; import { connectorDetails as checkoutConnectorDetails } from "./Checkout.js"; +import { connectorDetails as elavonConnectorDetails } from "./Elavon.js"; const connectorDetails = { adyen: adyenConnectorDetails, @@ -39,6 +40,7 @@ const connectorDetails = { paybox: payboxConnectorDetails, paypal: paypalConnectorDetails, stripe: stripeConnectorDetails, + elavon: elavonConnectorDetails, trustpay: trustpayConnectorDetails, datatrans: datatransConnectorDetails, wellsfargo: wellsfargoConnectorDetails, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index b7a6fca7de45..8aeacaeca1fc 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -103,7 +103,7 @@ digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.stagin dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" -elavon.base_url = "https://api.demo.convergepay.com" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" From 26c61969ebe245920ac6fff08da5e990d741a236 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2024 00:21:59 +0000 Subject: [PATCH 06/51] chore(version): 2024.11.26.0 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2982af5e0db7..6be9517667fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.11.26.0 + +### Features + +- **connector:** + - [Paypal] implement vaulting for paypal cards via zero mandates ([#5324](https://github.com/juspay/hyperswitch/pull/5324)) ([`83e8bc0`](https://github.com/juspay/hyperswitch/commit/83e8bc0775c20e9d055e65bd13a2e8b1148092e1)) + - [Elavon] Implement cards Flow ([#6485](https://github.com/juspay/hyperswitch/pull/6485)) ([`6887681`](https://github.com/juspay/hyperswitch/commit/68876811a8817cdec09be407fbbbbf7f19992565)) +- **core:** Add SCA exemption field ([#6578](https://github.com/juspay/hyperswitch/pull/6578)) ([`2b8eb09`](https://github.com/juspay/hyperswitch/commit/2b8eb09a16040957ac369c48e6095c343207f0d3)) +- **payments:** Add merchant order ref id filter ([#6630](https://github.com/juspay/hyperswitch/pull/6630)) ([`57e64c2`](https://github.com/juspay/hyperswitch/commit/57e64c26ca4251b493c87bfe93799faaab4ffa89)) + +### Miscellaneous Tasks + +- **deps:** Update cypress packages to address CVE ([#6624](https://github.com/juspay/hyperswitch/pull/6624)) ([`0db3aed`](https://github.com/juspay/hyperswitch/commit/0db3aed1533856b9892369d7bb2430d90d091756)) + +**Full Changelog:** [`2024.11.25.0...2024.11.26.0`](https://github.com/juspay/hyperswitch/compare/2024.11.25.0...2024.11.26.0) + +- - - + ## 2024.11.25.0 ### Features From 710186f035c92a919e8f5a49565c6f8908f1803f Mon Sep 17 00:00:00 2001 From: sweta-kumari-sharma <77436883+Sweta-Kumari-Sharma@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:44:44 +0530 Subject: [PATCH 07/51] feat(connector): [INESPAY] add Connector Template Code (#6614) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 1 + config/deployments/integration_test.toml | 1 + config/deployments/production.toml | 1 + config/deployments/sandbox.toml | 1 + config/development.toml | 2 + config/docker_compose.toml | 2 + crates/api_models/src/connector_enums.rs | 2 + crates/common_enums/src/connector_enums.rs | 1 + crates/connector_configs/src/connector.rs | 2 + .../hyperswitch_connectors/src/connectors.rs | 11 +- .../src/connectors/inespay.rs | 563 ++++++++++++++++++ .../src/connectors/inespay/transformers.rs | 228 +++++++ .../src/default_implementations.rs | 32 + .../src/default_implementations_v2.rs | 22 + crates/hyperswitch_interfaces/src/configs.rs | 1 + crates/router/src/connector.rs | 16 +- crates/router/src/core/admin.rs | 4 + .../connector_integration_v2_impls.rs | 3 + crates/router/src/core/payments/flows.rs | 3 + crates/router/src/types/api.rs | 3 + crates/router/src/types/transformers.rs | 1 + crates/router/tests/connectors/inespay.rs | 421 +++++++++++++ crates/router/tests/connectors/main.rs | 1 + .../router/tests/connectors/sample_auth.toml | 3 + crates/test_utils/src/connector_auth.rs | 1 + loadtest/config/development.toml | 2 + scripts/add_connector.sh | 2 +- 27 files changed, 1316 insertions(+), 14 deletions(-) create mode 100644 crates/hyperswitch_connectors/src/connectors/inespay.rs create mode 100644 crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs create mode 100644 crates/router/tests/connectors/inespay.rs diff --git a/config/config.example.toml b/config/config.example.toml index e55665ffc708..191f2ba7f8b0 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -220,6 +220,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 5f4de111ac8c..00a544dc565e 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -62,6 +62,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 61738f04cdb2..0fe9095d2806 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -66,6 +66,7 @@ gocardless.base_url = "https://api.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://secure.api.itau/" jpmorgan.base_url = "https://api-ms.payments.jpmorgan.com/api/v2" klarna.base_url = "https://api{{klarna_region}}.klarna.com/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 24c7642200c9..82c347ae389c 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -66,6 +66,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" diff --git a/config/development.toml b/config/development.toml index 630cc4a2303c..ee6ea5dab0bb 100644 --- a/config/development.toml +++ b/config/development.toml @@ -130,6 +130,7 @@ cards = [ "gpayments", "helcim", "iatapay", + "inespay", "itaubank", "jpmorgan", "mollie", @@ -235,6 +236,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 354af98dbadf..ed0ede98d945 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -150,6 +150,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" @@ -249,6 +250,7 @@ cards = [ "gpayments", "helcim", "iatapay", + "inespay", "itaubank", "jpmorgan", "mollie", diff --git a/crates/api_models/src/connector_enums.rs b/crates/api_models/src/connector_enums.rs index fe2c4f037bf5..783ecb12b48d 100644 --- a/crates/api_models/src/connector_enums.rs +++ b/crates/api_models/src/connector_enums.rs @@ -84,6 +84,7 @@ pub enum Connector { Gocardless, Gpayments, Helcim, + // Inespay, Iatapay, Itaubank, //Jpmorgan, @@ -228,6 +229,7 @@ impl Connector { | Self::Gpayments | Self::Helcim | Self::Iatapay + // | Self::Inespay | Self::Itaubank //| Self::Jpmorgan | Self::Klarna diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 9d17cdb61d4c..c3bbf6e078fe 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -81,6 +81,7 @@ pub enum RoutableConnectors { Gocardless, Helcim, Iatapay, + // Inespay, Itaubank, //Jpmorgan, Klarna, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 65b286f8423c..0e68b04d27b4 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -192,6 +192,7 @@ pub struct ConnectorConfig { pub gocardless: Option, pub gpayments: Option, pub helcim: Option, + // pub inespay: Option, pub klarna: Option, pub mifinity: Option, pub mollie: Option, @@ -357,6 +358,7 @@ impl ConnectorConfig { Connector::Gocardless => Ok(connector_data.gocardless), Connector::Gpayments => Ok(connector_data.gpayments), Connector::Helcim => Ok(connector_data.helcim), + // Connector::Inespay => Ok(connector_data.inespay), Connector::Klarna => Ok(connector_data.klarna), Connector::Mifinity => Ok(connector_data.mifinity), Connector::Mollie => Ok(connector_data.mollie), diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index b764ab5b441d..d1cdb85e57f6 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -16,6 +16,7 @@ pub mod fiuu; pub mod forte; pub mod globepay; pub mod helcim; +pub mod inespay; pub mod jpmorgan; pub mod mollie; pub mod multisafepay; @@ -45,9 +46,9 @@ pub use self::{ bitpay::Bitpay, cashtocode::Cashtocode, coinbase::Coinbase, cryptopay::Cryptopay, deutschebank::Deutschebank, digitalvirgo::Digitalvirgo, dlocal::Dlocal, elavon::Elavon, fiserv::Fiserv, fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, globepay::Globepay, - helcim::Helcim, jpmorgan::Jpmorgan, mollie::Mollie, multisafepay::Multisafepay, - nexinets::Nexinets, nexixpay::Nexixpay, nomupay::Nomupay, novalnet::Novalnet, payeezy::Payeezy, - payu::Payu, powertranz::Powertranz, razorpay::Razorpay, shift4::Shift4, square::Square, - stax::Stax, taxjar::Taxjar, thunes::Thunes, tsys::Tsys, volt::Volt, worldline::Worldline, - worldpay::Worldpay, xendit::Xendit, zen::Zen, zsl::Zsl, + helcim::Helcim, inespay::Inespay, jpmorgan::Jpmorgan, mollie::Mollie, + multisafepay::Multisafepay, nexinets::Nexinets, nexixpay::Nexixpay, nomupay::Nomupay, + novalnet::Novalnet, payeezy::Payeezy, payu::Payu, powertranz::Powertranz, razorpay::Razorpay, + shift4::Shift4, square::Square, stax::Stax, taxjar::Taxjar, thunes::Thunes, tsys::Tsys, + volt::Volt, worldline::Worldline, worldpay::Worldpay, xendit::Xendit, zen::Zen, zsl::Zsl, }; diff --git a/crates/hyperswitch_connectors/src/connectors/inespay.rs b/crates/hyperswitch_connectors/src/connectors/inespay.rs new file mode 100644 index 000000000000..89bf50c60ca9 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/inespay.rs @@ -0,0 +1,563 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as inespay; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Inespay { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Inespay { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Inespay {} +impl api::PaymentSession for Inespay {} +impl api::ConnectorAccessToken for Inespay {} +impl api::MandateSetup for Inespay {} +impl api::PaymentAuthorize for Inespay {} +impl api::PaymentSync for Inespay {} +impl api::PaymentCapture for Inespay {} +impl api::PaymentVoid for Inespay {} +impl api::Refund for Inespay {} +impl api::RefundExecute for Inespay {} +impl api::RefundSync for Inespay {} +impl api::PaymentToken for Inespay {} + +impl ConnectorIntegration + for Inespay +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Inespay +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Inespay { + fn id(&self) -> &'static str { + "inespay" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + // TODO! Check connector documentation, on which unit they are processing the currency. + // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, + // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.inespay.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = inespay::InespayAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: inespay::InespayErrorResponse = res + .response + .parse_struct("InespayErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Inespay { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Inespay { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Inespay {} + +impl ConnectorIntegration for Inespay {} + +impl ConnectorIntegration for Inespay { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = inespay::InespayRouterData::from((amount, req)); + let connector_req = inespay::InespayPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: inespay::InespayPaymentsResponse = res + .response + .parse_struct("Inespay PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Inespay { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: inespay::InespayPaymentsResponse = res + .response + .parse_struct("inespay PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Inespay { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: inespay::InespayPaymentsResponse = res + .response + .parse_struct("Inespay PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Inespay {} + +impl ConnectorIntegration for Inespay { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = inespay::InespayRouterData::from((refund_amount, req)); + let connector_req = inespay::InespayRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: inespay::RefundResponse = res + .response + .parse_struct("inespay RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Inespay { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: inespay::RefundResponse = res + .response + .parse_struct("inespay RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Inespay { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs new file mode 100644 index 000000000000..296d76546c84 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct InespayRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for InespayRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct InespayPaymentsRequest { + amount: StringMinorUnit, + card: InespayCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct InespayCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&InespayRouterData<&PaymentsAuthorizeRouterData>> for InespayPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &InespayRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = InespayCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct InespayAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for InespayAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum InespayPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: InespayPaymentStatus) -> Self { + match item { + InespayPaymentStatus::Succeeded => Self::Charged, + InespayPaymentStatus::Failed => Self::Failure, + InespayPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct InespayPaymentsResponse { + status: InespayPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct InespayRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&InespayRouterData<&RefundsRouterData>> for InespayRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &InespayRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct InespayErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index bc86e713501f..50b28be2b0ba 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -107,6 +107,7 @@ default_imp_for_authorize_session_token!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -164,6 +165,7 @@ default_imp_for_calculate_tax!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Mollie, connectors::Multisafepay, @@ -218,6 +220,7 @@ default_imp_for_session_update!( connectors::Fiservemea, connectors::Forte, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Razorpay, connectors::Shift4, @@ -277,6 +280,7 @@ default_imp_for_post_session_tokens!( connectors::Fiservemea, connectors::Forte, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Razorpay, connectors::Shift4, @@ -335,6 +339,7 @@ default_imp_for_complete_authorize!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Multisafepay, connectors::Nomupay, @@ -389,6 +394,7 @@ default_imp_for_incremental_authorization!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -448,6 +454,7 @@ default_imp_for_create_customer!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Mollie, connectors::Multisafepay, @@ -505,6 +512,7 @@ default_imp_for_connector_redirect_response!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Multisafepay, connectors::Nexinets, @@ -559,6 +567,7 @@ default_imp_for_pre_processing_steps!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -616,6 +625,7 @@ default_imp_for_post_processing_steps!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -675,6 +685,7 @@ default_imp_for_approve!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -734,6 +745,7 @@ default_imp_for_reject!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -793,6 +805,7 @@ default_imp_for_webhook_source_verification!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -853,6 +866,7 @@ default_imp_for_accept_dispute!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -912,6 +926,7 @@ default_imp_for_submit_evidence!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -970,6 +985,7 @@ default_imp_for_defend_dispute!( connectors::Fiuu, connectors::Forte, connectors::Globepay, + connectors::Inespay, connectors::Jpmorgan, connectors::Helcim, connectors::Nomupay, @@ -1039,6 +1055,7 @@ default_imp_for_file_upload!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1091,6 +1108,7 @@ default_imp_for_payouts!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Mollie, connectors::Multisafepay, @@ -1151,6 +1169,7 @@ default_imp_for_payouts_create!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1212,6 +1231,7 @@ default_imp_for_payouts_retrieve!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1273,6 +1293,7 @@ default_imp_for_payouts_eligibility!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1334,6 +1355,7 @@ default_imp_for_payouts_fulfill!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1395,6 +1417,7 @@ default_imp_for_payouts_cancel!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1456,6 +1479,7 @@ default_imp_for_payouts_quote!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1517,6 +1541,7 @@ default_imp_for_payouts_recipient!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1578,6 +1603,7 @@ default_imp_for_payouts_recipient_account!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1639,6 +1665,7 @@ default_imp_for_frm_sale!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1700,6 +1727,7 @@ default_imp_for_frm_checkout!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1761,6 +1789,7 @@ default_imp_for_frm_transaction!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1822,6 +1851,7 @@ default_imp_for_frm_fulfillment!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1883,6 +1913,7 @@ default_imp_for_frm_record_return!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1941,6 +1972,7 @@ default_imp_for_revoking_mandates!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 75cbf192e554..7b19ca683655 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -223,6 +223,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -283,6 +284,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -338,6 +340,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -399,6 +402,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -459,6 +463,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -519,6 +524,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -589,6 +595,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -651,6 +658,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -713,6 +721,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -775,6 +784,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -837,6 +847,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -899,6 +910,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -961,6 +973,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1023,6 +1036,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1085,6 +1099,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1145,6 +1160,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1207,6 +1223,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1269,6 +1286,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1331,6 +1349,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1393,6 +1412,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1455,6 +1475,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, @@ -1514,6 +1535,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, connectors::Jpmorgan, connectors::Nomupay, connectors::Novalnet, diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs index 5e5eeea31b39..539b87c48089 100644 --- a/crates/hyperswitch_interfaces/src/configs.rs +++ b/crates/hyperswitch_interfaces/src/configs.rs @@ -47,6 +47,7 @@ pub struct Connectors { pub gpayments: ConnectorParams, pub helcim: ConnectorParams, pub iatapay: ConnectorParams, + pub inespay: ConnectorParams, pub itaubank: ConnectorParams, pub jpmorgan: ConnectorParams, pub klarna: ConnectorParams, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index e98730d006d7..b6668323ba9a 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -50,14 +50,14 @@ pub use hyperswitch_connectors::connectors::{ coinbase, coinbase::Coinbase, cryptopay, cryptopay::Cryptopay, deutschebank, deutschebank::Deutschebank, digitalvirgo, digitalvirgo::Digitalvirgo, dlocal, dlocal::Dlocal, elavon, elavon::Elavon, fiserv, fiserv::Fiserv, fiservemea, fiservemea::Fiservemea, fiuu, - fiuu::Fiuu, forte, forte::Forte, globepay, globepay::Globepay, helcim, helcim::Helcim, - jpmorgan, jpmorgan::Jpmorgan, mollie, mollie::Mollie, multisafepay, multisafepay::Multisafepay, - nexinets, nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, nomupay, nomupay::Nomupay, - novalnet, novalnet::Novalnet, payeezy, payeezy::Payeezy, payu, payu::Payu, powertranz, - powertranz::Powertranz, razorpay, razorpay::Razorpay, shift4, shift4::Shift4, square, - square::Square, stax, stax::Stax, taxjar, taxjar::Taxjar, thunes, thunes::Thunes, tsys, - tsys::Tsys, volt, volt::Volt, worldline, worldline::Worldline, worldpay, worldpay::Worldpay, - xendit, xendit::Xendit, zen, zen::Zen, zsl, zsl::Zsl, + fiuu::Fiuu, forte, forte::Forte, globepay, globepay::Globepay, helcim, helcim::Helcim, inespay, + inespay::Inespay, jpmorgan, jpmorgan::Jpmorgan, mollie, mollie::Mollie, multisafepay, + multisafepay::Multisafepay, nexinets, nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, + nomupay, nomupay::Nomupay, novalnet, novalnet::Novalnet, payeezy, payeezy::Payeezy, payu, + payu::Payu, powertranz, powertranz::Powertranz, razorpay, razorpay::Razorpay, shift4, + shift4::Shift4, square, square::Square, stax, stax::Stax, taxjar, taxjar::Taxjar, thunes, + thunes::Thunes, tsys, tsys::Tsys, volt, volt::Volt, worldline, worldline::Worldline, worldpay, + worldpay::Worldpay, xendit, xendit::Xendit, zen, zen::Zen, zsl, zsl::Zsl, }; #[cfg(feature = "dummy_connector")] diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index a8d14187dc1a..6d4dde53082c 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1393,6 +1393,10 @@ impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { iatapay::transformers::IatapayAuthType::try_from(self.auth_type)?; Ok(()) } + // api_enums::Connector::Inespay => { + // inespay::transformers::InespayAuthType::try_from(self.auth_type)?; + // Ok(()) + // } api_enums::Connector::Itaubank => { itaubank::transformers::ItaubankAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/core/payments/connector_integration_v2_impls.rs b/crates/router/src/core/payments/connector_integration_v2_impls.rs index 94c531a09630..44e8c25d67bf 100644 --- a/crates/router/src/core/payments/connector_integration_v2_impls.rs +++ b/crates/router/src/core/payments/connector_integration_v2_impls.rs @@ -1142,6 +1142,7 @@ default_imp_for_new_connector_integration_payouts!( connector::Gpayments, connector::Helcim, connector::Iatapay, + connector::Inespay, connector::Itaubank, connector::Jpmorgan, connector::Klarna, @@ -1789,6 +1790,7 @@ default_imp_for_new_connector_integration_frm!( connector::Gpayments, connector::Helcim, connector::Iatapay, + connector::Inespay, connector::Itaubank, connector::Jpmorgan, connector::Klarna, @@ -2284,6 +2286,7 @@ default_imp_for_new_connector_integration_connector_authentication!( connector::Gpayments, connector::Helcim, connector::Iatapay, + connector::Inespay, connector::Itaubank, connector::Jpmorgan, connector::Klarna, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 1358bcedba1d..9ba260f554f9 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -483,6 +483,7 @@ default_imp_for_connector_request_id!( connector::Gocardless, connector::Gpayments, connector::Iatapay, + connector::Inespay, connector::Itaubank, connector::Jpmorgan, connector::Klarna, @@ -1769,6 +1770,7 @@ default_imp_for_fraud_check!( connector::Gpayments, connector::Helcim, connector::Iatapay, + connector::Inespay, connector::Itaubank, connector::Jpmorgan, connector::Klarna, @@ -2432,6 +2434,7 @@ default_imp_for_connector_authentication!( connector::Gocardless, connector::Helcim, connector::Iatapay, + connector::Inespay, connector::Itaubank, connector::Jpmorgan, connector::Klarna, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index ad40f83a554e..d550c1978b26 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -434,6 +434,9 @@ impl ConnectorData { enums::Connector::Iatapay => { Ok(ConnectorEnum::Old(Box::new(connector::Iatapay::new()))) } + // enums::Connector::Inespay => { + // Ok(ConnectorEnum::Old(Box::new(connector::Inespay::new()))) + // } enums::Connector::Itaubank => { //enums::Connector::Jpmorgan => Ok(ConnectorEnum::Old(Box::new(connector::Jpmorgan))), Ok(ConnectorEnum::Old(Box::new(connector::Itaubank::new()))) diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index c8a9cbea1ddd..781387574934 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -249,6 +249,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { } api_enums::Connector::Helcim => Self::Helcim, api_enums::Connector::Iatapay => Self::Iatapay, + // api_enums::Connector::Inespay => Self::Inespay, api_enums::Connector::Itaubank => Self::Itaubank, //api_enums::Connector::Jpmorgan => Self::Jpmorgan, api_enums::Connector::Klarna => Self::Klarna, diff --git a/crates/router/tests/connectors/inespay.rs b/crates/router/tests/connectors/inespay.rs new file mode 100644 index 000000000000..6fb8914aec70 --- /dev/null +++ b/crates/router/tests/connectors/inespay.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct InespayTest; +impl ConnectorActions for InespayTest {} +impl utils::Connector for InespayTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Inespay; + utils::construct_connector_data_old( + Box::new(Inespay::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .inespay + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "inespay".to_string() + } +} + +static CONNECTOR: InespayTest = InespayTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index 9bb079b7a168..ef3ae2d14dbc 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -43,6 +43,7 @@ mod gocardless; mod gpayments; mod helcim; mod iatapay; +mod inespay; mod itaubank; mod jpmorgan; mod mifinity; diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index d2eec724597b..120ce5e9d269 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -287,6 +287,9 @@ api_secret = "Client Key" [thunes] api_key="API Key" +[inespay] +api_key="API Key" + [jpmorgan] api_key="API Key" diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 2b2dc1431131..4bb348d66793 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -49,6 +49,7 @@ pub struct ConnectorAuthentication { pub gpayments: Option, pub helcim: Option, pub iatapay: Option, + pub inespay: Option, pub itaubank: Option, pub jpmorgan: Option, pub mifinity: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 8aeacaeca1fc..dab85eb3cdd7 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -116,6 +116,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" @@ -215,6 +216,7 @@ cards = [ "gpayments", "helcim", "iatapay", + "inespay", "itaubank", "jpmorgan", "mollie", diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 9f1992bfa44e..e5a651283199 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") + connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res="$(echo ${sorted[@]})" sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp From 03423a1f76d324453052da985f998fd3f957ce90 Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:46:56 +0530 Subject: [PATCH 08/51] feat(users): Send welcome to community email in magic link signup (#6639) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/router/src/consts/user.rs | 2 + crates/router/src/core/user.rs | 25 +- .../email/assets/welcome_to_community.html | 306 ++++++++++++++++++ crates/router/src/services/email/types.rs | 22 ++ 4 files changed, 350 insertions(+), 5 deletions(-) create mode 100644 crates/router/src/services/email/assets/welcome_to_community.html diff --git a/crates/router/src/consts/user.rs b/crates/router/src/consts/user.rs index aa4279920828..32ca4ad31d7d 100644 --- a/crates/router/src/consts/user.rs +++ b/crates/router/src/consts/user.rs @@ -41,3 +41,5 @@ pub const EMAIL_SUBJECT_INVITATION: &str = "You have been invited to join Hypers pub const EMAIL_SUBJECT_MAGIC_LINK: &str = "Unlock Hyperswitch: Use Your Magic Link to Sign In"; pub const EMAIL_SUBJECT_RESET_PASSWORD: &str = "Get back to Hyperswitch - Reset Your Password Now"; pub const EMAIL_SUBJECT_NEW_PROD_INTENT: &str = "New Prod Intent"; +pub const EMAIL_SUBJECT_WELCOME_TO_COMMUNITY: &str = + "Thank you for signing up on Hyperswitch Dashboard!"; diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index bff6205f5db8..eedd9ac56723 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -246,26 +246,41 @@ pub async fn connect_account( ) .await?; - let email_contents = email_types::VerifyEmail { + let magic_link_email = email_types::VerifyEmail { recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?, settings: state.conf.clone(), subject: consts::user::EMAIL_SUBJECT_SIGNUP, auth_id, }; - let send_email_result = state + let magic_link_result = state .email_client .compose_and_send_email( - Box::new(email_contents), + Box::new(magic_link_email), state.conf.proxy.https_url.as_ref(), ) .await; - logger::info!(?send_email_result); + logger::info!(?magic_link_result); + + let welcome_to_community_email = email_types::WelcomeToCommunity { + recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?, + subject: consts::user::EMAIL_SUBJECT_WELCOME_TO_COMMUNITY, + }; + + let welcome_email_result = state + .email_client + .compose_and_send_email( + Box::new(welcome_to_community_email), + state.conf.proxy.https_url.as_ref(), + ) + .await; + + logger::info!(?welcome_email_result); return Ok(ApplicationResponse::Json( user_api::ConnectAccountResponse { - is_email_sent: send_email_result.is_ok(), + is_email_sent: magic_link_result.is_ok(), user_id: user_from_db.get_user_id().to_string(), }, )); diff --git a/crates/router/src/services/email/assets/welcome_to_community.html b/crates/router/src/services/email/assets/welcome_to_community.html new file mode 100644 index 000000000000..05f7fac1d55a --- /dev/null +++ b/crates/router/src/services/email/assets/welcome_to_community.html @@ -0,0 +1,306 @@ + + + + + + + Email Template + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ Follow us on + + Twitter + + + LinkedIn + +
+
+
+ + + diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index cedd17828f1b..d092afdc5deb 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -57,6 +57,7 @@ pub enum EmailBody { api_key_name: String, prefix: String, }, + WelcomeToCommunity, } pub mod html { @@ -145,6 +146,9 @@ Email : {user_email} prefix = prefix, expires_in = expires_in, ), + EmailBody::WelcomeToCommunity => { + include_str!("assets/welcome_to_community.html").to_string() + } } } } @@ -505,3 +509,21 @@ impl EmailData for ApiKeyExpiryReminder { }) } } + +pub struct WelcomeToCommunity { + pub recipient_email: domain::UserEmail, + pub subject: &'static str, +} + +#[async_trait::async_trait] +impl EmailData for WelcomeToCommunity { + async fn get_email_data(&self) -> CustomResult { + let body = html::get_html_body(EmailBody::WelcomeToCommunity); + + Ok(EmailContents { + subject: self.subject.to_string(), + body: external_services::email::IntermediateString::new(body), + recipient: self.recipient_email.clone().into_inner(), + }) + } +} From 108b1603fa44b2a56c278196edb5a1f76f5d3d03 Mon Sep 17 00:00:00 2001 From: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:47:12 +0530 Subject: [PATCH 09/51] refactor(payments_v2): use batch encryption for intent create and confirm intent (#6589) Co-authored-by: Sanchith Hegde Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../hyperswitch_domain_models/src/address.rs | 159 ++++++++++++++++++ crates/hyperswitch_domain_models/src/lib.rs | 1 + .../hyperswitch_domain_models/src/payments.rs | 39 +++-- .../src/payments/payment_attempt.rs | 70 +++++--- .../operations/payment_confirm_intent.rs | 30 +++- .../operations/payment_create_intent.rs | 71 ++++---- .../router/src/core/payments/transformers.rs | 6 +- .../src/macros/to_encryptable.rs | 16 +- 8 files changed, 307 insertions(+), 85 deletions(-) create mode 100644 crates/hyperswitch_domain_models/src/address.rs diff --git a/crates/hyperswitch_domain_models/src/address.rs b/crates/hyperswitch_domain_models/src/address.rs new file mode 100644 index 000000000000..85595c1ad9e3 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/address.rs @@ -0,0 +1,159 @@ +use masking::{PeekInterface, Secret}; + +#[derive(Default, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Address { + pub address: Option, + pub phone: Option, + pub email: Option, +} + +impl masking::SerializableSecret for Address {} + +impl Address { + /// Unify the address, giving priority to `self` when details are present in both + pub fn unify_address(self, other: Option<&Self>) -> Self { + let other_address_details = other.and_then(|address| address.address.as_ref()); + Self { + address: self + .address + .map(|address| address.unify_address_details(other_address_details)) + .or(other_address_details.cloned()), + email: self.email.or(other.and_then(|other| other.email.clone())), + phone: self.phone.or(other.and_then(|other| other.phone.clone())), + } + } +} + +#[derive(Clone, Default, Debug, Eq, serde::Deserialize, serde::Serialize, PartialEq)] +pub struct AddressDetails { + pub city: Option, + pub country: Option, + pub line1: Option>, + pub line2: Option>, + pub line3: Option>, + pub zip: Option>, + pub state: Option>, + pub first_name: Option>, + pub last_name: Option>, +} + +impl AddressDetails { + pub fn get_optional_full_name(&self) -> Option> { + match (self.first_name.as_ref(), self.last_name.as_ref()) { + (Some(first_name), Some(last_name)) => Some(Secret::new(format!( + "{} {}", + first_name.peek(), + last_name.peek() + ))), + (Some(name), None) | (None, Some(name)) => Some(name.to_owned()), + _ => None, + } + } + + /// Unify the address details, giving priority to `self` when details are present in both + pub fn unify_address_details(self, other: Option<&Self>) -> Self { + if let Some(other) = other { + let (first_name, last_name) = if self + .first_name + .as_ref() + .is_some_and(|first_name| !first_name.peek().trim().is_empty()) + { + (self.first_name, self.last_name) + } else { + (other.first_name.clone(), other.last_name.clone()) + }; + + Self { + first_name, + last_name, + city: self.city.or(other.city.clone()), + country: self.country.or(other.country), + line1: self.line1.or(other.line1.clone()), + line2: self.line2.or(other.line2.clone()), + line3: self.line3.or(other.line3.clone()), + zip: self.zip.or(other.zip.clone()), + state: self.state.or(other.state.clone()), + } + } else { + self + } + } +} + +#[derive(Debug, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct PhoneDetails { + pub number: Option>, + pub country_code: Option, +} + +impl From for Address { + fn from(address: api_models::payments::Address) -> Self { + Self { + address: address.address.map(AddressDetails::from), + phone: address.phone.map(PhoneDetails::from), + email: address.email, + } + } +} + +impl From for AddressDetails { + fn from(address: api_models::payments::AddressDetails) -> Self { + Self { + city: address.city, + country: address.country, + line1: address.line1, + line2: address.line2, + line3: address.line3, + zip: address.zip, + state: address.state, + first_name: address.first_name, + last_name: address.last_name, + } + } +} + +impl From for PhoneDetails { + fn from(phone: api_models::payments::PhoneDetails) -> Self { + Self { + number: phone.number, + country_code: phone.country_code, + } + } +} + +impl From
for api_models::payments::Address { + fn from(address: Address) -> Self { + Self { + address: address + .address + .map(api_models::payments::AddressDetails::from), + phone: address.phone.map(api_models::payments::PhoneDetails::from), + email: address.email, + } + } +} + +impl From for api_models::payments::AddressDetails { + fn from(address: AddressDetails) -> Self { + Self { + city: address.city, + country: address.country, + line1: address.line1, + line2: address.line2, + line3: address.line3, + zip: address.zip, + state: address.state, + first_name: address.first_name, + last_name: address.last_name, + } + } +} + +impl From for api_models::payments::PhoneDetails { + fn from(phone: PhoneDetails) -> Self { + Self { + number: phone.number, + country_code: phone.country_code, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 386e0f01f383..64c6c97a0fd0 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -1,3 +1,4 @@ +pub mod address; pub mod api; pub mod behaviour; pub mod business_profile; diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 276035214a42..4cb934032194 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; #[cfg(feature = "v2")] -use api_models::payments::Address; +use common_utils::ext_traits::ValueExt; use common_utils::{ self, crypto::Encryptable, @@ -28,11 +28,13 @@ use common_enums as storage_enums; use diesel_models::types::{FeatureMetadata, OrderDetailsWithAmount}; use self::payment_attempt::PaymentAttempt; +#[cfg(feature = "v1")] use crate::RemoteStorageObject; #[cfg(feature = "v2")] -use crate::{business_profile, merchant_account}; -#[cfg(feature = "v2")] -use crate::{errors, payment_method_data, ApiModelToDieselModelConvertor}; +use crate::{ + address::Address, business_profile, errors, merchant_account, payment_method_data, + ApiModelToDieselModelConvertor, +}; #[cfg(feature = "v1")] #[derive(Clone, Debug, PartialEq, serde::Serialize, ToEncryption)] @@ -349,10 +351,10 @@ pub struct PaymentIntent { pub merchant_reference_id: Option, /// The billing address for the order in a denormalized form. #[encrypt(ty = Value)] - pub billing_address: Option>>, + pub billing_address: Option>, /// The shipping address for the order in a denormalized form. #[encrypt(ty = Value)] - pub shipping_address: Option>>, + pub shipping_address: Option>, /// Capture method for the payment pub capture_method: storage_enums::CaptureMethod, /// Authentication type that is requested by the merchant for this payment. @@ -416,8 +418,7 @@ impl PaymentIntent { merchant_account: &merchant_account::MerchantAccount, profile: &business_profile::Profile, request: api_models::payments::PaymentsCreateIntentRequest, - billing_address: Option>>, - shipping_address: Option>>, + decrypted_payment_intent: DecryptedPaymentIntent, ) -> CustomResult { let connector_metadata = request .get_connector_metadata_as_value() @@ -480,8 +481,26 @@ impl PaymentIntent { frm_metadata: request.frm_metadata, customer_details: None, merchant_reference_id: request.merchant_reference_id, - billing_address, - shipping_address, + billing_address: decrypted_payment_intent + .billing_address + .as_ref() + .map(|data| { + data.clone() + .deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to decode billing address")?, + shipping_address: decrypted_payment_intent + .shipping_address + .as_ref() + .map(|data| { + data.clone() + .deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to decode shipping address")?, capture_method: request.capture_method.unwrap_or_default(), authentication_type: request.authentication_type.unwrap_or_default(), prerouting_algorithm: None, diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index ec1463d1b7b5..4ca6084c958f 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -1,6 +1,11 @@ #[cfg(all(feature = "v1", feature = "olap"))] use api_models::enums::Connector; use common_enums as storage_enums; +#[cfg(feature = "v2")] +use common_utils::{ + crypto::Encryptable, encryption::Encryption, ext_traits::ValueExt, + types::keymanager::ToEncryptable, +}; use common_utils::{ errors::{CustomResult, ValidationError}, id_type, pii, @@ -18,15 +23,19 @@ use error_stack::ResultExt; #[cfg(feature = "v2")] use masking::PeekInterface; use masking::Secret; +#[cfg(feature = "v2")] +use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; +#[cfg(feature = "v2")] +use serde_json::Value; use time::PrimitiveDateTime; #[cfg(all(feature = "v1", feature = "olap"))] use super::PaymentIntent; #[cfg(feature = "v2")] -use crate::merchant_key_store::MerchantKeyStore; +use crate::type_encryption::{crypto_operation, CryptoOperation}; #[cfg(feature = "v2")] -use crate::router_response_types; +use crate::{address::Address, merchant_key_store::MerchantKeyStore, router_response_types}; use crate::{ behaviour, errors, mandates::{MandateDataType, MandateDetails}, @@ -222,7 +231,7 @@ pub struct ErrorDetails { /// Few fields which are related are grouped together for better readability and understandability. /// These fields will be flattened and stored in the database in individual columns #[cfg(feature = "v2")] -#[derive(Clone, Debug, PartialEq, serde::Serialize)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, router_derive::ToEncryption)] pub struct PaymentAttempt { /// Payment id for the payment attempt pub payment_id: id_type::GlobalPaymentId, @@ -259,12 +268,11 @@ pub struct PaymentAttempt { pub connector_metadata: Option, pub payment_experience: Option, /// The insensitive data of the payment method data is stored here - // TODO: evaluate what details should be stored here. Use a domain type instead of serde_json::Value pub payment_method_data: Option, /// The result of the routing algorithm. /// This will store the list of connectors and other related information that was used to route the payment. // TODO: change this to type instead of serde_json::Value - pub routing_result: Option, + pub routing_result: Option, pub preprocessing_step_id: Option, /// Number of captures that have happened for the payment attempt pub multiple_capture_count: Option, @@ -306,8 +314,8 @@ pub struct PaymentAttempt { /// A reference to the payment at connector side. This is returned by the connector pub external_reference_id: Option, /// The billing address for the payment method - // TODO: use a type here instead of value - pub payment_method_billing_address: common_utils::crypto::OptionalEncryptableValue, + #[encrypt(ty = Value)] + pub payment_method_billing_address: Option>, /// The global identifier for the payment attempt pub id: id_type::GlobalAttemptId, /// The connector mandate details which are stored temporarily @@ -364,6 +372,7 @@ impl PaymentAttempt { cell_id: id_type::CellId, storage_scheme: storage_enums::MerchantStorageScheme, request: &api_models::payments::PaymentsConfirmIntentRequest, + encrypted_data: DecryptedPaymentAttempt, ) -> CustomResult { let id = id_type::GlobalAttemptId::generate(&cell_id); let intent_amount_details = payment_intent.amount_details.clone(); @@ -1755,13 +1764,39 @@ impl behaviour::Conversion for PaymentAttempt { where Self: Sized, { - use crate::type_encryption; - async { let connector_payment_id = storage_model .get_optional_connector_transaction_id() .cloned(); + let decrypted_data = crypto_operation( + state, + common_utils::type_name!(Self::DstType), + CryptoOperation::BatchDecrypt(EncryptedPaymentAttempt::to_encryptable( + EncryptedPaymentAttempt { + payment_method_billing_address: storage_model + .payment_method_billing_address, + }, + )), + key_manager_identifier, + key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation())?; + + let decrypted_data = EncryptedPaymentAttempt::from_encryptable(decrypted_data) + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Invalid batch operation data")?; + + let payment_method_billing_address = decrypted_data + .payment_method_billing_address + .map(|billing| { + billing.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Error while deserializing Address")?; + let amount_details = AttemptAmountDetails { net_amount: storage_model.net_amount, tax_on_surcharge: storage_model.tax_on_surcharge, @@ -1772,18 +1807,6 @@ impl behaviour::Conversion for PaymentAttempt { amount_to_capture: storage_model.amount_to_capture, }; - let inner_decrypt = |inner| async { - type_encryption::crypto_operation( - state, - common_utils::type_name!(Self::DstType), - type_encryption::CryptoOperation::DecryptOptional(inner), - key_manager_identifier.clone(), - key.peek(), - ) - .await - .and_then(|val| val.try_into_optionaloperation()) - }; - let error = storage_model .error_code .zip(storage_model.error_message) @@ -1838,10 +1861,7 @@ impl behaviour::Conversion for PaymentAttempt { authentication_applied: storage_model.authentication_applied, external_reference_id: storage_model.external_reference_id, connector: storage_model.connector, - payment_method_billing_address: inner_decrypt( - storage_model.payment_method_billing_address, - ) - .await?, + payment_method_billing_address, connector_mandate_detail: storage_model.connector_mandate_detail, }) } 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 0a03fc741f02..5965bdc88503 100644 --- a/crates/router/src/core/payments/operations/payment_confirm_intent.rs +++ b/crates/router/src/core/payments/operations/payment_confirm_intent.rs @@ -4,8 +4,10 @@ use api_models::{ payments::{ExtendedCardInfo, GetAddressFromPaymentMethodData, PaymentsConfirmIntentRequest}, }; use async_trait::async_trait; +use common_utils::{ext_traits::Encode, types::keymanager::ToEncryptable}; use error_stack::ResultExt; use hyperswitch_domain_models::payments::PaymentConfirmData; +use masking::PeekInterface; use router_env::{instrument, tracing}; use tracing_futures::Instrument; @@ -26,7 +28,7 @@ use crate::{ types::{ self, api::{self, ConnectorCallType, PaymentIdTypeExt}, - domain::{self}, + domain::{self, types as domain_types}, storage::{self, enums as storage_enums}, }, utils::{self, OptionExt}, @@ -176,12 +178,36 @@ impl GetTracker, PaymentsConfirmIntent let cell_id = state.conf.cell_information.id.clone(); + let batch_encrypted_data = domain_types::crypto_operation( + key_manager_state, + common_utils::type_name!(hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt), + domain_types::CryptoOperation::BatchEncrypt( + hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::to_encryptable( + hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt { + payment_method_billing_address: request.payment_method_data.billing.as_ref().map(|address| address.clone().encode_to_value()).transpose().change_context(errors::ApiErrorResponse::InternalServerError).attach_printable("Failed to encode payment_method_billing address")?.map(masking::Secret::new), + }, + ), + ), + common_utils::types::keymanager::Identifier::Merchant(merchant_account.get_id().to_owned()), + key_store.key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting payment intent details".to_string())?; + + let encrypted_data = + hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::from_encryptable(batch_encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting payment intent details")?; + let payment_attempt_domain_model = hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt::create_domain_model( &payment_intent, cell_id, storage_scheme, - request + request, + encrypted_data ) .await?; 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 b46992a6aed6..bf5b4fb80c9e 100644 --- a/crates/router/src/core/payments/operations/payment_create_intent.rs +++ b/crates/router/src/core/payments/operations/payment_create_intent.rs @@ -4,9 +4,11 @@ use api_models::{enums::FrmSuggestion, payments::PaymentsCreateIntentRequest}; use async_trait::async_trait; use common_utils::{ errors::CustomResult, - ext_traits::{AsyncExt, ValueExt}, + ext_traits::{AsyncExt, Encode, ValueExt}, + types::keymanager::ToEncryptable, }; use error_stack::ResultExt; +use masking::PeekInterface; use router_env::{instrument, tracing}; use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest}; @@ -18,7 +20,8 @@ use crate::{ routes::{app::ReqState, SessionState}, services, types::{ - api, domain, + api, + domain::{self, types as domain_types}, storage::{self, enums}, }, }; @@ -100,51 +103,39 @@ impl GetTracker, PaymentsCrea let key_manager_state = &state.into(); let storage_scheme = merchant_account.storage_scheme; - // Derivation of directly supplied Billing Address data in our Payment Create Request - // Encrypting our Billing Address Details to be stored in Payment Intent - let billing_address = request - .billing - .clone() - .async_map(|billing_details| { - create_encrypted_data(key_manager_state, key_store, billing_details) - }) - .await - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt billing details")? - .map(|encrypted_value| { - encrypted_value.deserialize_inner_value(|value| value.parse_value("Address")) - }) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to deserialize decrypted value to Address")?; - // Derivation of directly supplied Shipping Address data in our Payment Create Request - // Encrypting our Shipping Address Details to be stored in Payment Intent - let shipping_address = request - .shipping - .clone() - .async_map(|shipping_details| { - create_encrypted_data(key_manager_state, key_store, shipping_details) - }) - .await - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt shipping details")? - .map(|encrypted_value| { - encrypted_value.deserialize_inner_value(|value| value.parse_value("Address")) - }) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to deserialize decrypted value to Address")?; + let batch_encrypted_data = domain_types::crypto_operation( + key_manager_state, + common_utils::type_name!(hyperswitch_domain_models::payments::PaymentIntent), + domain_types::CryptoOperation::BatchEncrypt( + hyperswitch_domain_models::payments::FromRequestEncryptablePaymentIntent::to_encryptable( + hyperswitch_domain_models::payments::FromRequestEncryptablePaymentIntent { + shipping_address: request.shipping.clone().map(|address| address.encode_to_value()).transpose().change_context(errors::ApiErrorResponse::InternalServerError).attach_printable("Failed to encode shipping address")?.map(masking::Secret::new), + billing_address: request.billing.clone().map(|address| address.encode_to_value()).transpose().change_context(errors::ApiErrorResponse::InternalServerError).attach_printable("Failed to encode billing address")?.map(masking::Secret::new), + customer_details: None, + }, + ), + ), + common_utils::types::keymanager::Identifier::Merchant(merchant_account.get_id().to_owned()), + key_store.key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting payment intent details".to_string())?; + + let encrypted_data = + hyperswitch_domain_models::payments::FromRequestEncryptablePaymentIntent::from_encryptable(batch_encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting payment intent details")?; + let payment_intent_domain = hyperswitch_domain_models::payments::PaymentIntent::create_domain_model_from_request( payment_id, merchant_account, profile, request.clone(), - billing_address, - shipping_address, + encrypted_data, ) .await?; diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index d9bd374ef1a9..326975081016 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -947,11 +947,13 @@ where billing: payment_intent .billing_address .clone() - .map(|billing| billing.into_inner().expose()), + .map(|billing| billing.into_inner()) + .map(From::from), shipping: payment_intent .shipping_address .clone() - .map(|shipping| shipping.into_inner().expose()), + .map(|shipping| shipping.into_inner()) + .map(From::from), customer_id: payment_intent.customer_id.clone(), customer_present: payment_intent.customer_present.clone(), description: payment_intent.description.clone(), diff --git a/crates/router_derive/src/macros/to_encryptable.rs b/crates/router_derive/src/macros/to_encryptable.rs index dfcfb72169b3..561c3a723719 100644 --- a/crates/router_derive/src/macros/to_encryptable.rs +++ b/crates/router_derive/src/macros/to_encryptable.rs @@ -242,13 +242,17 @@ fn generate_to_encryptable( let inner_types = get_field_and_inner_types(&fields); - let inner_type = inner_types.first().map(|(_, ty)| ty).ok_or_else(|| { + let inner_type = inner_types.first().ok_or_else(|| { syn::Error::new( proc_macro2::Span::call_site(), "Please use the macro with attribute #[encrypt] on the fields you want to encrypt", ) })?; + let provided_ty = get_encryption_ty_meta(&inner_type.0) + .map(|ty| ty.value.clone()) + .unwrap_or(inner_type.1.clone()); + let structs = struct_types.iter().map(|(prefix, struct_type)| { let name = format_ident!("{}{}", prefix, struct_name); let temp_fields = struct_type.generate_struct_fields(&inner_types); @@ -275,15 +279,15 @@ fn generate_to_encryptable( let decrypted_name = format_ident!("Decrypted{}", struct_name); ( quote! { #decrypted_name }, - quote! { Secret<#inner_type> }, - quote! { Secret<#inner_type> }, + quote! { Secret<#provided_ty> }, + quote! { Secret<#provided_ty> }, ) } StructType::Encrypted => { let decrypted_name = format_ident!("Decrypted{}", struct_name); ( quote! { #decrypted_name }, - quote! { Secret<#inner_type> }, + quote! { Secret<#provided_ty> }, quote! { Encryption }, ) } @@ -291,8 +295,8 @@ fn generate_to_encryptable( let decrypted_update_name = format_ident!("DecryptedUpdate{}", struct_name); ( quote! { #decrypted_update_name }, - quote! { Secret<#inner_type> }, - quote! { Secret<#inner_type> }, + quote! { Secret<#provided_ty> }, + quote! { Secret<#provided_ty> }, ) } //Unreachable statement From c9df7b0557889c88ea20392dfe56bf651e22c9a7 Mon Sep 17 00:00:00 2001 From: Apoorv Dixit <64925866+apoorvdixit88@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:47:58 +0530 Subject: [PATCH 10/51] refactor(tenant): use tenant id type (#6643) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/common_utils/src/id_type.rs | 2 + .../common_utils/src/id_type/organization.rs | 2 +- crates/common_utils/src/id_type/tenant.rs | 22 ++++++++++ crates/common_utils/src/types/theme.rs | 12 +++--- crates/diesel_models/src/user/theme.rs | 4 +- crates/diesel_models/src/user_role.rs | 4 +- crates/drainer/src/handler.rs | 8 +++- crates/drainer/src/health_check.rs | 4 +- crates/drainer/src/lib.rs | 6 +-- crates/drainer/src/settings.rs | 16 +++---- crates/router/src/configs/settings.rs | 25 ++++++----- .../src/core/payment_methods/transformers.rs | 35 +++++++++++----- crates/router/src/core/routing/helpers.rs | 5 ++- crates/router/src/core/user.rs | 8 +++- crates/router/src/db/events.rs | 5 ++- .../src/db/merchant_connector_account.rs | 11 ++++- crates/router/src/db/merchant_key_store.rs | 5 ++- crates/router/src/routes/app.rs | 15 ++++--- crates/router/src/services/api.rs | 42 ++++++++++++------- crates/router/src/services/authentication.rs | 16 +++---- crates/router/src/services/authorization.rs | 10 +++-- crates/router/src/types/domain/user.rs | 8 ++-- crates/router/src/utils/user.rs | 2 +- crates/router/tests/cache.rs | 5 ++- crates/router/tests/connectors/aci.rs | 20 +++++++-- crates/router/tests/connectors/utils.rs | 30 ++++++++++--- crates/router/tests/payments.rs | 10 ++++- crates/router/tests/payments2.rs | 10 ++++- crates/router/tests/services.rs | 10 ++++- crates/scheduler/src/consumer.rs | 6 +-- crates/scheduler/src/producer.rs | 6 +-- crates/scheduler/src/scheduler.rs | 6 +-- 32 files changed, 252 insertions(+), 118 deletions(-) create mode 100644 crates/common_utils/src/id_type/tenant.rs diff --git a/crates/common_utils/src/id_type.rs b/crates/common_utils/src/id_type.rs index 3d57a72376ed..a8085564145c 100644 --- a/crates/common_utils/src/id_type.rs +++ b/crates/common_utils/src/id_type.rs @@ -12,6 +12,7 @@ mod payment; mod profile; mod refunds; mod routing; +mod tenant; #[cfg(feature = "v2")] mod global_id; @@ -40,6 +41,7 @@ pub use profile::ProfileId; pub use refunds::RefundReferenceId; pub use routing::RoutingId; use serde::{Deserialize, Serialize}; +pub use tenant::TenantId; use thiserror::Error; use crate::{fp_utils::when, generate_id_with_default_len}; diff --git a/crates/common_utils/src/id_type/organization.rs b/crates/common_utils/src/id_type/organization.rs index a83f35db1d89..2097fbb2450a 100644 --- a/crates/common_utils/src/id_type/organization.rs +++ b/crates/common_utils/src/id_type/organization.rs @@ -18,7 +18,7 @@ crate::impl_to_sql_from_sql_id_type!(OrganizationId); impl OrganizationId { /// Get an organization id from String - pub fn wrap(org_id: String) -> CustomResult { + pub fn try_from_string(org_id: String) -> CustomResult { Self::try_from(std::borrow::Cow::from(org_id)) } } diff --git a/crates/common_utils/src/id_type/tenant.rs b/crates/common_utils/src/id_type/tenant.rs new file mode 100644 index 000000000000..953bf82287af --- /dev/null +++ b/crates/common_utils/src/id_type/tenant.rs @@ -0,0 +1,22 @@ +use crate::errors::{CustomResult, ValidationError}; + +crate::id_type!( + TenantId, + "A type for tenant_id that can be used for unique identifier for a tenant" +); +crate::impl_id_type_methods!(TenantId, "tenant_id"); + +// This is to display the `TenantId` as TenantId(abcd) +crate::impl_debug_id_type!(TenantId); +crate::impl_try_from_cow_str_id_type!(TenantId, "tenant_id"); + +crate::impl_serializable_secret_id_type!(TenantId); +crate::impl_queryable_id_type!(TenantId); +crate::impl_to_sql_from_sql_id_type!(TenantId); + +impl TenantId { + /// Get tenant id from String + pub fn try_from_string(tenant_id: String) -> CustomResult { + Self::try_from(std::borrow::Cow::from(tenant_id)) + } +} diff --git a/crates/common_utils/src/types/theme.rs b/crates/common_utils/src/types/theme.rs index 03b4cf23a6c3..9ad9206acceb 100644 --- a/crates/common_utils/src/types/theme.rs +++ b/crates/common_utils/src/types/theme.rs @@ -12,15 +12,15 @@ pub enum ThemeLineage { // }, /// Org lineage variant Organization { - /// tenant_id: String - tenant_id: String, + /// tenant_id: TenantId + tenant_id: id_type::TenantId, /// org_id: OrganizationId org_id: id_type::OrganizationId, }, /// Merchant lineage variant Merchant { - /// tenant_id: String - tenant_id: String, + /// tenant_id: TenantId + tenant_id: id_type::TenantId, /// org_id: OrganizationId org_id: id_type::OrganizationId, /// merchant_id: MerchantId @@ -28,8 +28,8 @@ pub enum ThemeLineage { }, /// Profile lineage variant Profile { - /// tenant_id: String - tenant_id: String, + /// tenant_id: TenantId + tenant_id: id_type::TenantId, /// org_id: OrganizationId org_id: id_type::OrganizationId, /// merchant_id: MerchantId diff --git a/crates/diesel_models/src/user/theme.rs b/crates/diesel_models/src/user/theme.rs index 2f8152e419cd..9841e21443c0 100644 --- a/crates/diesel_models/src/user/theme.rs +++ b/crates/diesel_models/src/user/theme.rs @@ -9,7 +9,7 @@ use crate::schema::themes; #[diesel(table_name = themes, primary_key(theme_id), check_for_backend(diesel::pg::Pg))] pub struct Theme { pub theme_id: String, - pub tenant_id: String, + pub tenant_id: id_type::TenantId, pub org_id: Option, pub merchant_id: Option, pub profile_id: Option, @@ -23,7 +23,7 @@ pub struct Theme { #[diesel(table_name = themes)] pub struct ThemeNew { pub theme_id: String, - pub tenant_id: String, + pub tenant_id: id_type::TenantId, pub org_id: Option, pub merchant_id: Option, pub profile_id: Option, diff --git a/crates/diesel_models/src/user_role.rs b/crates/diesel_models/src/user_role.rs index ceddbfd61e4a..04f3264b45e3 100644 --- a/crates/diesel_models/src/user_role.rs +++ b/crates/diesel_models/src/user_role.rs @@ -24,7 +24,7 @@ pub struct UserRole { pub entity_id: Option, pub entity_type: Option, pub version: enums::UserRoleVersion, - pub tenant_id: String, + pub tenant_id: id_type::TenantId, } impl UserRole { @@ -88,7 +88,7 @@ pub struct UserRoleNew { pub entity_id: Option, pub entity_type: Option, pub version: enums::UserRoleVersion, - pub tenant_id: String, + pub tenant_id: id_type::TenantId, } #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] diff --git a/crates/drainer/src/handler.rs b/crates/drainer/src/handler.rs index d8a8bff5afc2..d0c26195453b 100644 --- a/crates/drainer/src/handler.rs +++ b/crates/drainer/src/handler.rs @@ -3,6 +3,7 @@ use std::{ sync::{atomic, Arc}, }; +use common_utils::id_type; use router_env::tracing::Instrument; use tokio::{ sync::{mpsc, oneshot}, @@ -34,12 +35,15 @@ pub struct HandlerInner { loop_interval: Duration, active_tasks: Arc, conf: DrainerSettings, - stores: HashMap>, + stores: HashMap>, running: Arc, } impl Handler { - pub fn from_conf(conf: DrainerSettings, stores: HashMap>) -> Self { + pub fn from_conf( + conf: DrainerSettings, + stores: HashMap>, + ) -> Self { let shutdown_interval = Duration::from_millis(conf.shutdown_interval.into()); let loop_interval = Duration::from_millis(conf.loop_interval.into()); diff --git a/crates/drainer/src/health_check.rs b/crates/drainer/src/health_check.rs index 48d5f3119056..2ca2c1cc79c1 100644 --- a/crates/drainer/src/health_check.rs +++ b/crates/drainer/src/health_check.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc}; use actix_web::{web, Scope}; use async_bb8_diesel::{AsyncConnection, AsyncRunQueryDsl}; -use common_utils::errors::CustomResult; +use common_utils::{errors::CustomResult, id_type}; use diesel_models::{Config, ConfigNew}; use error_stack::ResultExt; use router_env::{instrument, logger, tracing}; @@ -20,7 +20,7 @@ pub const TEST_STREAM_DATA: &[(&str, &str)] = &[("data", "sample_data")]; pub struct Health; impl Health { - pub fn server(conf: Settings, stores: HashMap>) -> Scope { + pub fn server(conf: Settings, stores: HashMap>) -> Scope { web::scope("health") .app_data(web::Data::new(conf)) .app_data(web::Data::new(stores)) diff --git a/crates/drainer/src/lib.rs b/crates/drainer/src/lib.rs index 5b67640663c6..6eb8c505e15a 100644 --- a/crates/drainer/src/lib.rs +++ b/crates/drainer/src/lib.rs @@ -14,7 +14,7 @@ use std::{collections::HashMap, sync::Arc}; mod secrets_transformers; use actix_web::dev::Server; -use common_utils::signals::get_allowed_signals; +use common_utils::{id_type, signals::get_allowed_signals}; use diesel_models::kv; use error_stack::ResultExt; use hyperswitch_interfaces::secrets_interface::secret_state::RawSecret; @@ -31,7 +31,7 @@ use crate::{ }; pub async fn start_drainer( - stores: HashMap>, + stores: HashMap>, conf: DrainerSettings, ) -> errors::DrainerResult<()> { let drainer_handler = handler::Handler::from_conf(conf, stores); @@ -62,7 +62,7 @@ pub async fn start_drainer( pub async fn start_web_server( conf: Settings, - stores: HashMap>, + stores: HashMap>, ) -> Result { let server = conf.server.clone(); let web_server = actix_web::HttpServer::new(move || { diff --git a/crates/drainer/src/settings.rs b/crates/drainer/src/settings.rs index 5b391b492e09..9b6c88b34665 100644 --- a/crates/drainer/src/settings.rs +++ b/crates/drainer/src/settings.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, path::PathBuf, sync::Arc}; -use common_utils::{ext_traits::ConfigExt, DbConnectionParams}; +use common_utils::{ext_traits::ConfigExt, id_type, DbConnectionParams}; use config::{Environment, File}; use external_services::managers::{ encryption_management::EncryptionManagementConfig, secrets_management::SecretsManagementConfig, @@ -122,23 +122,23 @@ pub struct Multitenancy { pub tenants: TenantConfig, } impl Multitenancy { - pub fn get_tenants(&self) -> &HashMap { + pub fn get_tenants(&self) -> &HashMap { &self.tenants.0 } - pub fn get_tenant_ids(&self) -> Vec { + pub fn get_tenant_ids(&self) -> Vec { self.tenants .0 .values() .map(|tenant| tenant.tenant_id.clone()) .collect() } - pub fn get_tenant(&self, tenant_id: &str) -> Option<&Tenant> { + pub fn get_tenant(&self, tenant_id: &id_type::TenantId) -> Option<&Tenant> { self.tenants.0.get(tenant_id) } } #[derive(Debug, Clone, Default)] -pub struct TenantConfig(pub HashMap); +pub struct TenantConfig(pub HashMap); impl<'de> Deserialize<'de> for TenantConfig { fn deserialize>(deserializer: D) -> Result { @@ -150,7 +150,7 @@ impl<'de> Deserialize<'de> for TenantConfig { clickhouse_database: String, } - let hashmap = >::deserialize(deserializer)?; + let hashmap = >::deserialize(deserializer)?; Ok(Self( hashmap @@ -172,9 +172,9 @@ impl<'de> Deserialize<'de> for TenantConfig { } } -#[derive(Debug, Deserialize, Clone, Default)] +#[derive(Debug, Deserialize, Clone)] pub struct Tenant { - pub tenant_id: String, + pub tenant_id: id_type::TenantId, pub base_url: String, pub schema: String, pub redis_key_prefix: String, diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 76b58f5b67b0..7b212ec6d1dc 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -6,7 +6,7 @@ use std::{ #[cfg(feature = "olap")] use analytics::{opensearch::OpenSearchConfig, ReportConfig}; use api_models::{enums, payment_methods::RequiredFieldInfo}; -use common_utils::ext_traits::ConfigExt; +use common_utils::{ext_traits::ConfigExt, id_type}; use config::{Environment, File}; use error_stack::ResultExt; #[cfg(feature = "email")] @@ -138,17 +138,17 @@ pub struct Multitenancy { } impl Multitenancy { - pub fn get_tenants(&self) -> &HashMap { + pub fn get_tenants(&self) -> &HashMap { &self.tenants.0 } - pub fn get_tenant_ids(&self) -> Vec { + pub fn get_tenant_ids(&self) -> Vec { self.tenants .0 .values() .map(|tenant| tenant.tenant_id.clone()) .collect() } - pub fn get_tenant(&self, tenant_id: &str) -> Option<&Tenant> { + pub fn get_tenant(&self, tenant_id: &id_type::TenantId) -> Option<&Tenant> { self.tenants.0.get(tenant_id) } } @@ -159,11 +159,11 @@ pub struct DecisionConfig { } #[derive(Debug, Clone, Default)] -pub struct TenantConfig(pub HashMap); +pub struct TenantConfig(pub HashMap); -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct Tenant { - pub tenant_id: String, + pub tenant_id: id_type::TenantId, pub base_url: String, pub schema: String, pub redis_key_prefix: String, @@ -743,8 +743,7 @@ pub struct LockerBasedRecipientConnectorList { #[derive(Debug, Deserialize, Clone, Default)] pub struct ConnectorRequestReferenceIdConfig { - pub merchant_ids_send_payment_id_as_connector_request_id: - HashSet, + pub merchant_ids_send_payment_id_as_connector_request_id: HashSet, } #[derive(Debug, Deserialize, Clone, Default)] @@ -970,7 +969,7 @@ pub struct ServerTls { #[cfg(feature = "v2")] #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] pub struct CellInformation { - pub id: common_utils::id_type::CellId, + pub id: id_type::CellId, } #[cfg(feature = "v2")] @@ -981,8 +980,8 @@ impl Default for CellInformation { // around the time of deserializing application settings. // And a panic at application startup is considered acceptable. #[allow(clippy::expect_used)] - let cell_id = common_utils::id_type::CellId::from_string("defid") - .expect("Failed to create a default for Cell Id"); + let cell_id = + id_type::CellId::from_string("defid").expect("Failed to create a default for Cell Id"); Self { id: cell_id } } } @@ -1120,7 +1119,7 @@ impl<'de> Deserialize<'de> for TenantConfig { clickhouse_database: String, } - let hashmap = >::deserialize(deserializer)?; + let hashmap = >::deserialize(deserializer)?; Ok(Self( hashmap diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index c0f54a30f3f2..c3fbfd8afbff 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -389,7 +389,7 @@ pub async fn mk_add_locker_request_hs( locker: &settings::Locker, payload: &StoreLockerReq, locker_choice: api_enums::LockerChoice, - tenant_id: String, + tenant_id: id_type::TenantId, request_id: Option, ) -> CustomResult { let payload = payload @@ -409,7 +409,10 @@ pub async fn mk_add_locker_request_hs( url.push_str("/cards/add"); let mut request = services::Request::new(services::Method::Post, &url); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.add_header(headers::X_TENANT_ID, tenant_id.into()); + request.add_header( + headers::X_TENANT_ID, + tenant_id.get_string_repr().to_owned().into(), + ); if let Some(req_id) = request_id { request.add_header( headers::X_REQUEST_ID, @@ -584,7 +587,7 @@ pub async fn mk_get_card_request_hs( merchant_id: &id_type::MerchantId, card_reference: &str, locker_choice: Option, - tenant_id: String, + tenant_id: id_type::TenantId, request_id: Option, ) -> CustomResult { let merchant_customer_id = customer_id.to_owned(); @@ -612,7 +615,10 @@ pub async fn mk_get_card_request_hs( url.push_str("/cards/retrieve"); let mut request = services::Request::new(services::Method::Post, &url); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.add_header(headers::X_TENANT_ID, tenant_id.into()); + request.add_header( + headers::X_TENANT_ID, + tenant_id.get_string_repr().to_owned().into(), + ); if let Some(req_id) = request_id { request.add_header( headers::X_REQUEST_ID, @@ -665,7 +671,7 @@ pub async fn mk_delete_card_request_hs( customer_id: &id_type::CustomerId, merchant_id: &id_type::MerchantId, card_reference: &str, - tenant_id: String, + tenant_id: id_type::TenantId, request_id: Option, ) -> CustomResult { let merchant_customer_id = customer_id.to_owned(); @@ -691,7 +697,10 @@ pub async fn mk_delete_card_request_hs( url.push_str("/cards/delete"); let mut request = services::Request::new(services::Method::Post, &url); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.add_header(headers::X_TENANT_ID, tenant_id.into()); + request.add_header( + headers::X_TENANT_ID, + tenant_id.get_string_repr().to_owned().into(), + ); if let Some(req_id) = request_id { request.add_header( headers::X_REQUEST_ID, @@ -711,7 +720,7 @@ pub async fn mk_delete_card_request_hs_by_id( id: &String, merchant_id: &id_type::MerchantId, card_reference: &str, - tenant_id: String, + tenant_id: id_type::TenantId, request_id: Option, ) -> CustomResult { let merchant_customer_id = id.to_owned(); @@ -737,7 +746,10 @@ pub async fn mk_delete_card_request_hs_by_id( url.push_str("/cards/delete"); let mut request = services::Request::new(services::Method::Post, &url); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.add_header(headers::X_TENANT_ID, tenant_id.into()); + request.add_header( + headers::X_TENANT_ID, + tenant_id.get_string_repr().to_owned().into(), + ); if let Some(req_id) = request_id { request.add_header( headers::X_REQUEST_ID, @@ -832,7 +844,7 @@ pub fn mk_crud_locker_request( locker: &settings::Locker, path: &str, req: api::TokenizePayloadEncrypted, - tenant_id: String, + tenant_id: id_type::TenantId, request_id: Option, ) -> CustomResult { let mut url = locker.basilisk_host.to_owned(); @@ -840,7 +852,10 @@ pub fn mk_crud_locker_request( let mut request = services::Request::new(services::Method::Post, &url); request.add_default_headers(); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.add_header(headers::X_TENANT_ID, tenant_id.into()); + request.add_header( + headers::X_TENANT_ID, + tenant_id.get_string_repr().to_owned().into(), + ); if let Some(req_id) = request_id { request.add_header( headers::X_REQUEST_ID, diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index aef89ea8ed92..264328796c8b 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -751,7 +751,10 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( &metrics::CONTEXT, 1, &add_attributes([ - ("tenant", state.tenant.tenant_id.clone()), + ( + "tenant", + state.tenant.tenant_id.get_string_repr().to_owned(), + ), ( "merchant_profile_id", format!( diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index eedd9ac56723..f01f6c5d7496 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1120,11 +1120,15 @@ pub async fn create_internal_user( } })?; - let default_tenant_id = common_utils::consts::DEFAULT_TENANT.to_string(); + let default_tenant_id = common_utils::id_type::TenantId::try_from_string( + common_utils::consts::DEFAULT_TENANT.to_owned(), + ) + .change_context(UserErrors::InternalServerError) + .attach_printable("Unable to parse default tenant id")?; if state.tenant.tenant_id != default_tenant_id { return Err(UserErrors::ForbiddenTenantId) - .attach_printable("Operation allowed only for the default tenant."); + .attach_printable("Operation allowed only for the default tenant"); } let internal_merchant_id = common_utils::id_type::MerchantId::get_internal_user_merchant_id( diff --git a/crates/router/src/db/events.rs b/crates/router/src/db/events.rs index 651c2ece610b..6bb7de1b7d99 100644 --- a/crates/router/src/db/events.rs +++ b/crates/router/src/db/events.rs @@ -732,7 +732,10 @@ mod tests { )) .await; let state = &Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let merchant_id = common_utils::id_type::MerchantId::try_from(std::borrow::Cow::from("merchant_1")) diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index be2c25d47670..687f6e8fea24 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -1502,8 +1502,12 @@ mod merchant_connector_account_cache_tests { Box::new(services::MockApiClient), )) .await; + let state = &Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); #[allow(clippy::expect_used)] let db = MockDb::new(&redis_interface::RedisSettings::default()) @@ -1685,7 +1689,10 @@ mod merchant_connector_account_cache_tests { )) .await; let state = &Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); #[allow(clippy::expect_used)] let db = MockDb::new(&redis_interface::RedisSettings::default()) diff --git a/crates/router/src/db/merchant_key_store.rs b/crates/router/src/db/merchant_key_store.rs index 65a5515a3911..9f12ec8e8fd8 100644 --- a/crates/router/src/db/merchant_key_store.rs +++ b/crates/router/src/db/merchant_key_store.rs @@ -348,7 +348,10 @@ mod tests { )) .await; let state = &Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); #[allow(clippy::expect_used)] let mock_db = MockDb::new(&redis_interface::RedisSettings::default()) diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index baa2ba4ae152..1584cfae2b95 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -7,6 +7,7 @@ use api_models::routing::RoutingRetrieveQuery; use common_enums::TransactionType; #[cfg(feature = "partial-auth")] use common_utils::crypto::Blake3; +use common_utils::id_type; #[cfg(feature = "email")] use external_services::email::{ no_email::NoEmailClient, ses::AwsSes, smtp::SmtpServer, EmailClientConfigs, EmailService, @@ -193,14 +194,14 @@ impl SessionStateInfo for SessionState { pub struct AppState { pub flow_name: String, pub global_store: Box, - pub stores: HashMap>, + pub stores: HashMap>, pub conf: Arc>, pub event_handler: EventsHandler, #[cfg(feature = "email")] pub email_client: Arc>, pub api_client: Box, #[cfg(feature = "olap")] - pub pools: HashMap, + pub pools: HashMap, #[cfg(feature = "olap")] pub opensearch_client: Arc, pub request_id: Option, @@ -209,7 +210,7 @@ pub struct AppState { pub grpc_client: Arc, } impl scheduler::SchedulerAppState for AppState { - fn get_tenants(&self) -> Vec { + fn get_tenants(&self) -> Vec { self.conf.multitenancy.get_tenant_ids() } } @@ -328,7 +329,7 @@ impl AppState { ); #[cfg(feature = "olap")] - let mut pools: HashMap = HashMap::new(); + let mut pools: HashMap = HashMap::new(); let mut stores = HashMap::new(); #[allow(clippy::expect_used)] let cache_store = get_cache_store(&conf.clone(), shut_down_signal, testable) @@ -443,7 +444,11 @@ impl AppState { .await } - pub fn get_session_state(self: Arc, tenant: &str, err: F) -> Result + pub fn get_session_state( + self: Arc, + tenant: &id_type::TenantId, + err: F, + ) -> Result where F: FnOnce() -> E + Copy, { diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index bdd650389a70..9416ff175a85 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -68,7 +68,7 @@ use crate::{ api_logs::{ApiEvent, ApiEventMetric, ApiEventsType}, connector_api_logs::ConnectorEvent, }, - logger, + headers, logger, routes::{ app::{AppStateInfo, ReqState, SessionStateInfo}, metrics, AppState, SessionState, @@ -722,33 +722,44 @@ where let mut event_type = payload.get_api_event_type(); let tenant_id = if !state.conf.multitenancy.enabled { - DEFAULT_TENANT.to_string() + common_utils::id_type::TenantId::try_from_string(DEFAULT_TENANT.to_owned()) + .attach_printable("Unable to get default tenant id") + .change_context(errors::ApiErrorResponse::InternalServerError.switch())? } else { let request_tenant_id = incoming_request_header .get(TENANT_HEADER) .and_then(|value| value.to_str().ok()) - .ok_or_else(|| errors::ApiErrorResponse::MissingTenantId.switch())?; + .ok_or_else(|| errors::ApiErrorResponse::MissingTenantId.switch()) + .and_then(|header_value| { + common_utils::id_type::TenantId::try_from_string(header_value.to_string()).map_err( + |_| { + errors::ApiErrorResponse::InvalidRequestData { + message: format!("`{}` header is invalid", headers::X_TENANT_ID), + } + .switch() + }, + ) + })?; state .conf .multitenancy - .get_tenant(request_tenant_id) + .get_tenant(&request_tenant_id) .map(|tenant| tenant.tenant_id.clone()) .ok_or( errors::ApiErrorResponse::InvalidTenant { - tenant_id: request_tenant_id.to_string(), + tenant_id: request_tenant_id.get_string_repr().to_string(), } .switch(), )? }; - let mut session_state = - Arc::new(app_state.clone()).get_session_state(tenant_id.as_str(), || { - errors::ApiErrorResponse::InvalidTenant { - tenant_id: tenant_id.clone(), - } - .switch() - })?; + let mut session_state = Arc::new(app_state.clone()).get_session_state(&tenant_id, || { + errors::ApiErrorResponse::InvalidTenant { + tenant_id: tenant_id.get_string_repr().to_string(), + } + .switch() + })?; session_state.add_request_id(request_id); let mut request_state = session_state.get_req_state(); @@ -757,9 +768,10 @@ where .event_context .record_info(("flow".to_string(), flow.to_string())); - request_state - .event_context - .record_info(("tenant_id".to_string(), tenant_id.to_string())); + request_state.event_context.record_info(( + "tenant_id".to_string(), + tenant_id.get_string_repr().to_string(), + )); // Currently auth failures are not recorded as API events let (auth_out, auth_type) = api_auth diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 2541a5dc7d48..2f5f55b84345 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -185,7 +185,7 @@ pub struct UserFromSinglePurposeToken { pub user_id: String, pub origin: domain::Origin, pub path: Vec, - pub tenant_id: Option, + pub tenant_id: Option, } #[cfg(feature = "olap")] @@ -196,7 +196,7 @@ pub struct SinglePurposeToken { pub origin: domain::Origin, pub path: Vec, pub exp: u64, - pub tenant_id: Option, + pub tenant_id: Option, } #[cfg(feature = "olap")] @@ -207,7 +207,7 @@ impl SinglePurposeToken { origin: domain::Origin, settings: &Settings, path: Vec, - tenant_id: Option, + tenant_id: Option, ) -> UserResult { let exp_duration = std::time::Duration::from_secs(consts::SINGLE_PURPOSE_TOKEN_TIME_IN_SECS); @@ -232,7 +232,7 @@ pub struct AuthToken { pub exp: u64, pub org_id: id_type::OrganizationId, pub profile_id: id_type::ProfileId, - pub tenant_id: Option, + pub tenant_id: Option, } #[cfg(feature = "olap")] @@ -244,7 +244,7 @@ impl AuthToken { settings: &Settings, org_id: id_type::OrganizationId, profile_id: id_type::ProfileId, - tenant_id: Option, + tenant_id: Option, ) -> UserResult { let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); let exp = jwt::generate_exp(exp_duration)?.as_secs(); @@ -268,7 +268,7 @@ pub struct UserFromToken { pub role_id: String, pub org_id: id_type::OrganizationId, pub profile_id: id_type::ProfileId, - pub tenant_id: Option, + pub tenant_id: Option, } pub struct UserIdFromAuth { @@ -282,7 +282,7 @@ pub struct SinglePurposeOrLoginToken { pub role_id: Option, pub purpose: Option, pub exp: u64, - pub tenant_id: Option, + pub tenant_id: Option, } pub trait AuthInfo { @@ -1110,7 +1110,7 @@ impl<'a> HeaderMapStruct<'a> { self.get_mandatory_header_value_by_key(headers::X_ORGANIZATION_ID) .map(|val| val.to_owned()) .and_then(|organization_id| { - id_type::OrganizationId::wrap(organization_id).change_context( + id_type::OrganizationId::try_from_string(organization_id).change_context( errors::ApiErrorResponse::InvalidRequestData { message: format!("`{}` header is invalid", headers::X_ORGANIZATION_ID), }, diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs index 35a0b159ab2e..87ff9f6abd59 100644 --- a/crates/router/src/services/authorization.rs +++ b/crates/router/src/services/authorization.rs @@ -112,12 +112,16 @@ pub fn check_permission( ) } -pub fn check_tenant(token_tenant_id: Option, header_tenant_id: &str) -> RouterResult<()> { +pub fn check_tenant( + token_tenant_id: Option, + header_tenant_id: &id_type::TenantId, +) -> RouterResult<()> { if let Some(tenant_id) = token_tenant_id { - if tenant_id != header_tenant_id { + if tenant_id != *header_tenant_id { return Err(ApiErrorResponse::InvalidJwtToken).attach_printable(format!( "Token tenant ID: '{}' does not match Header tenant ID: '{}'", - tenant_id, header_tenant_id + tenant_id.get_string_repr().to_owned(), + header_tenant_id.get_string_repr().to_owned() )); } } diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 4cb69e68ed8f..6d0d2a4ea07c 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -1106,20 +1106,20 @@ pub struct NoLevel; #[derive(Clone)] pub struct OrganizationLevel { - pub tenant_id: String, + pub tenant_id: id_type::TenantId, pub org_id: id_type::OrganizationId, } #[derive(Clone)] pub struct MerchantLevel { - pub tenant_id: String, + pub tenant_id: id_type::TenantId, pub org_id: id_type::OrganizationId, pub merchant_id: id_type::MerchantId, } #[derive(Clone)] pub struct ProfileLevel { - pub tenant_id: String, + pub tenant_id: id_type::TenantId, pub org_id: id_type::OrganizationId, pub merchant_id: id_type::MerchantId, pub profile_id: id_type::ProfileId, @@ -1156,7 +1156,7 @@ impl NewUserRole { } pub struct EntityInfo { - tenant_id: String, + tenant_id: id_type::TenantId, org_id: id_type::OrganizationId, merchant_id: Option, profile_id: Option, diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index 8a9daefb2872..f115a16c0622 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -92,7 +92,7 @@ pub async fn generate_jwt_auth_token_with_attributes( org_id: id_type::OrganizationId, role_id: String, profile_id: id_type::ProfileId, - tenant_id: Option, + tenant_id: Option, ) -> UserResult> { let token = AuthToken::new_token( user_id, diff --git a/crates/router/tests/cache.rs b/crates/router/tests/cache.rs index 8c3f34cd1e1b..55b92b4aace5 100644 --- a/crates/router/tests/cache.rs +++ b/crates/router/tests/cache.rs @@ -18,7 +18,10 @@ async fn invalidate_existing_cache_success() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let cache_key = "cacheKey".to_string(); let cache_key_value = "val".to_string(); diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 10c8a3dd012d..92cba2eec3f0 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -218,7 +218,10 @@ async fn payments_create_success() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); use router::connector::Aci; @@ -265,7 +268,10 @@ async fn payments_create_failure() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let connector = utils::construct_connector_data_old( Box::new(Aci::new()), @@ -328,7 +334,10 @@ async fn refund_for_successful_payments() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< types::api::Authorize, @@ -398,7 +407,10 @@ async fn refunds_create_failure() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let connector_integration: services::BoxedRefundConnectorIntegrationInterface< types::api::Execute, diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index a03148956ba2..f8f71a98283a 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -601,7 +601,10 @@ pub trait ConnectorActions: Connector { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let res = services::api::execute_connector_processing_step( &state, @@ -641,7 +644,10 @@ pub trait ConnectorActions: Connector { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let res = services::api::execute_connector_processing_step( &state, @@ -682,7 +688,10 @@ pub trait ConnectorActions: Connector { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let res = services::api::execute_connector_processing_step( &state, @@ -722,7 +731,10 @@ pub trait ConnectorActions: Connector { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let res = services::api::execute_connector_processing_step( &state, @@ -813,7 +825,10 @@ pub trait ConnectorActions: Connector { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let res = services::api::execute_connector_processing_step( &state, @@ -850,7 +865,10 @@ async fn call_connector< )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); services::api::execute_connector_processing_step( &state, diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index df4340a353e7..68ca08c8bd3c 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -295,7 +295,10 @@ async fn payments_create_core() { let merchant_id = id_type::MerchantId::try_from(Cow::from("juspay_merchant")).unwrap(); let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let key_manager_state = &(&state).into(); let key_store = state @@ -552,7 +555,10 @@ async fn payments_create_core_adyen_no_redirect() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let payment_id = diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index b5962d454fa1..90fe3a1f8477 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -56,7 +56,10 @@ async fn payments_create_core() { let merchant_id = id_type::MerchantId::try_from(Cow::from("juspay_merchant")).unwrap(); let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let key_manager_state = &(&state).into(); let key_store = state @@ -321,7 +324,10 @@ async fn payments_create_core_adyen_no_redirect() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let customer_id = format!("cust_{}", Uuid::new_v4()); diff --git a/crates/router/tests/services.rs b/crates/router/tests/services.rs index d907000cccd9..c014370b24f4 100644 --- a/crates/router/tests/services.rs +++ b/crates/router/tests/services.rs @@ -18,7 +18,10 @@ async fn get_redis_conn_failure() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let _ = state.store.get_redis_conn().map(|conn| { @@ -46,7 +49,10 @@ async fn get_redis_conn_success() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); // Act diff --git a/crates/scheduler/src/consumer.rs b/crates/scheduler/src/consumer.rs index 5791edd31b05..846f1137b129 100644 --- a/crates/scheduler/src/consumer.rs +++ b/crates/scheduler/src/consumer.rs @@ -7,7 +7,7 @@ use std::{ pub mod types; pub mod workflows; -use common_utils::{errors::CustomResult, signals::get_allowed_signals}; +use common_utils::{errors::CustomResult, id_type, signals::get_allowed_signals}; use diesel_models::enums; pub use diesel_models::{self, process_tracker as storage}; use error_stack::ResultExt; @@ -42,7 +42,7 @@ pub async fn start_consumer CustomResult<(), errors::ProcessTrackerError> where - F: Fn(&T, &str) -> CustomResult, + F: Fn(&T, &id_type::TenantId) -> CustomResult, { use std::time::Duration; @@ -88,7 +88,7 @@ where let start_time = std_time::Instant::now(); let tenants = state.get_tenants(); for tenant in tenants { - let session_state = app_state_to_session_state(state, tenant.as_str())?; + let session_state = app_state_to_session_state(state, &tenant)?; pt_utils::consumer_operation_handler( session_state.clone(), settings.clone(), diff --git a/crates/scheduler/src/producer.rs b/crates/scheduler/src/producer.rs index 6f710f55a341..b91434fcbb04 100644 --- a/crates/scheduler/src/producer.rs +++ b/crates/scheduler/src/producer.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use common_utils::errors::CustomResult; +use common_utils::{errors::CustomResult, id_type}; use diesel_models::enums::ProcessTrackerStatus; use error_stack::{report, ResultExt}; use router_env::{ @@ -27,7 +27,7 @@ pub async fn start_producer( app_state_to_session_state: F, ) -> CustomResult<(), errors::ProcessTrackerError> where - F: Fn(&T, &str) -> CustomResult, + F: Fn(&T, &id_type::TenantId) -> CustomResult, T: SchedulerAppState, U: SchedulerSessionState, { @@ -69,7 +69,7 @@ where interval.tick().await; let tenants = state.get_tenants(); for tenant in tenants { - let session_state = app_state_to_session_state(state, tenant.as_str())?; + let session_state = app_state_to_session_state(state, &tenant)?; match run_producer_flow(&session_state, &scheduler_settings).await { Ok(_) => (), Err(error) => { diff --git a/crates/scheduler/src/scheduler.rs b/crates/scheduler/src/scheduler.rs index 39a45d02ba96..2685c6311eaa 100644 --- a/crates/scheduler/src/scheduler.rs +++ b/crates/scheduler/src/scheduler.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use common_utils::errors::CustomResult; +use common_utils::{errors::CustomResult, id_type}; use storage_impl::mock_db::MockDb; #[cfg(feature = "kv_store")] use storage_impl::KVRouterStore; @@ -52,7 +52,7 @@ impl SchedulerInterface for MockDb {} #[async_trait::async_trait] pub trait SchedulerAppState: Send + Sync + Clone { - fn get_tenants(&self) -> Vec; + fn get_tenants(&self) -> Vec; } #[async_trait::async_trait] pub trait SchedulerSessionState: Send + Sync + Clone { @@ -71,7 +71,7 @@ pub async fn start_process_tracker< app_state_to_session_state: F, ) -> CustomResult<(), errors::ProcessTrackerError> where - F: Fn(&T, &str) -> CustomResult, + F: Fn(&T, &id_type::TenantId) -> CustomResult, { match scheduler_flow { SchedulerFlow::Producer => { From acb30ef6d144eaf13b237b830d1ac534259932a3 Mon Sep 17 00:00:00 2001 From: Sidharth-Singh10 <70999945+Sidharth-Singh10@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:49:45 +0530 Subject: [PATCH 11/51] refactor(connector): add amount conversion framework to Riskified (#6359) --- crates/router/src/connector/riskified.rs | 41 ++++++- .../connector/riskified/transformers/api.rs | 106 +++++++++++++----- crates/router/src/types/api/fraud_check.rs | 2 +- 3 files changed, 114 insertions(+), 35 deletions(-) diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs index 0f8ebb461342..7d9feec717a5 100644 --- a/crates/router/src/connector/riskified.rs +++ b/crates/router/src/connector/riskified.rs @@ -1,8 +1,10 @@ pub mod transformers; -use std::fmt::Debug; #[cfg(feature = "frm")] use base64::Engine; +use common_utils::types::{ + AmountConvertor, MinorUnit, StringMajorUnit, StringMajorUnitForConnector, +}; #[cfg(feature = "frm")] use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; #[cfg(feature = "frm")] @@ -14,6 +16,7 @@ use ring::hmac; #[cfg(feature = "frm")] use transformers as riskified; +use super::utils::convert_amount; #[cfg(feature = "frm")] use super::utils::{self as connector_utils, FrmTransactionRouterDataRequest}; use crate::{ @@ -35,10 +38,18 @@ use crate::{ utils::BytesExt, }; -#[derive(Debug, Clone)] -pub struct Riskified; +#[derive(Clone)] +pub struct Riskified { + amount_converter: &'static (dyn AmountConvertor + Sync), +} impl Riskified { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMajorUnitForConnector, + } + } + #[cfg(feature = "frm")] pub fn generate_authorization_signature( &self, @@ -173,7 +184,17 @@ impl req: &frm_types::FrmCheckoutRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let req_obj = riskified::RiskifiedPaymentsCheckoutRequest::try_from(req)?; + let amount = convert_amount( + self.amount_converter, + MinorUnit::new(req.request.amount), + req.request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?, + )?; + let req_data = riskified::RiskifiedRouterData::from((amount, req)); + let req_obj = riskified::RiskifiedPaymentsCheckoutRequest::try_from(&req_data)?; Ok(RequestContent::Json(Box::new(req_obj))) } @@ -293,7 +314,17 @@ impl Ok(RequestContent::Json(Box::new(req_obj))) } _ => { - let req_obj = riskified::TransactionSuccessRequest::try_from(req)?; + let amount = convert_amount( + self.amount_converter, + MinorUnit::new(req.request.amount), + req.request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?, + )?; + let req_data = riskified::RiskifiedRouterData::from((amount, req)); + let req_obj = riskified::TransactionSuccessRequest::try_from(&req_data)?; Ok(RequestContent::Json(Box::new(req_obj))) } } diff --git a/crates/router/src/connector/riskified/transformers/api.rs b/crates/router/src/connector/riskified/transformers/api.rs index cfa183e9e833..c2da0193f996 100644 --- a/crates/router/src/connector/riskified/transformers/api.rs +++ b/crates/router/src/connector/riskified/transformers/api.rs @@ -1,5 +1,10 @@ use api_models::payments::AdditionalPaymentData; -use common_utils::{ext_traits::ValueExt, id_type, pii::Email}; +use common_utils::{ + ext_traits::ValueExt, + id_type, + pii::Email, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, +}; use error_stack::{self, ResultExt}; use masking::Secret; use serde::{Deserialize, Serialize}; @@ -7,17 +12,37 @@ use time::PrimitiveDateTime; use crate::{ connector::utils::{ - AddressDetailsData, FraudCheckCheckoutRequest, FraudCheckTransactionRequest, RouterData, + convert_amount, AddressDetailsData, FraudCheckCheckoutRequest, + FraudCheckTransactionRequest, RouterData, }, core::{errors, fraud_check::types as core_types}, types::{ - self, api, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, + self, + api::{self, Fulfillment}, + fraud_check as frm_types, + storage::enums as storage_enums, ResponseId, ResponseRouterData, }, }; type Error = error_stack::Report; +pub struct RiskifiedRouterData { + pub amount: StringMajorUnit, + pub router_data: T, + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl From<(StringMajorUnit, T)> for RiskifiedRouterData { + fn from((amount, router_data): (StringMajorUnit, T)) -> Self { + Self { + amount, + router_data, + amount_converter: &StringMajorUnitForConnector, + } + } +} + #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] pub struct RiskifiedPaymentsCheckoutRequest { order: CheckoutRequest, @@ -35,7 +60,7 @@ pub struct CheckoutRequest { updated_at: PrimitiveDateTime, gateway: Option, browser_ip: Option, - total_price: i64, + total_price: StringMajorUnit, total_discounts: i64, cart_token: String, referring_site: String, @@ -60,13 +85,13 @@ pub struct PaymentDetails { #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] pub struct ShippingLines { - price: i64, + price: StringMajorUnit, title: Option, } #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] pub struct DiscountCodes { - amount: i64, + amount: StringMajorUnit, code: Option, } @@ -110,7 +135,7 @@ pub struct OrderAddress { #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] pub struct LineItem { - price: i64, + price: StringMajorUnit, quantity: i32, title: String, product_type: Option, @@ -132,9 +157,14 @@ pub struct RiskifiedMetadata { shipping_lines: Vec, } -impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutRequest { +impl TryFrom<&RiskifiedRouterData<&frm_types::FrmCheckoutRouterData>> + for RiskifiedPaymentsCheckoutRequest +{ type Error = Error; - fn try_from(payment_data: &frm_types::FrmCheckoutRouterData) -> Result { + fn try_from( + payment: &RiskifiedRouterData<&frm_types::FrmCheckoutRouterData>, + ) -> Result { + let payment_data = payment.router_data.clone(); let metadata: RiskifiedMetadata = payment_data .frm_metadata .clone() @@ -148,6 +178,33 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutReq let billing_address = payment_data.get_billing()?; let shipping_address = payment_data.get_shipping_address_with_phone_number()?; let address = payment_data.get_billing_address()?; + let line_items = payment_data + .request + .get_order_details()? + .iter() + .map(|order_detail| { + let price = convert_amount( + payment.amount_converter, + order_detail.amount, + payment_data.request.currency.ok_or_else(|| { + errors::ConnectorError::MissingRequiredField { + field_name: "currency", + } + })?, + )?; + + Ok(LineItem { + price, + quantity: i32::from(order_detail.quantity), + title: order_detail.product_name.clone(), + product_type: order_detail.product_type.clone(), + requires_shipping: order_detail.requires_shipping, + product_id: order_detail.product_id.clone(), + category: order_detail.category.clone(), + brand: order_detail.brand.clone(), + }) + }) + .collect::, Self::Error>>()?; Ok(Self { order: CheckoutRequest { @@ -156,23 +213,9 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutReq created_at: common_utils::date_time::now(), updated_at: common_utils::date_time::now(), gateway: payment_data.request.gateway.clone(), - total_price: payment_data.request.amount, + total_price: payment.amount.clone(), cart_token: payment_data.attempt_id.clone(), - line_items: payment_data - .request - .get_order_details()? - .iter() - .map(|order_detail| LineItem { - price: order_detail.amount.get_amount_as_i64(), // This should be changed to MinorUnit when we implement amount conversion for this connector. Additionally, the function get_amount_as_i64() should be avoided in the future. - quantity: i32::from(order_detail.quantity), - title: order_detail.product_name.clone(), - product_type: order_detail.product_type.clone(), - requires_shipping: order_detail.requires_shipping, - product_id: order_detail.product_id.clone(), - category: order_detail.category.clone(), - brand: order_detail.brand.clone(), - }) - .collect::>(), + line_items, source: Source::DesktopWeb, billing_address: OrderAddress::try_from(billing_address).ok(), shipping_address: OrderAddress::try_from(shipping_address).ok(), @@ -411,7 +454,7 @@ pub struct SuccessfulTransactionData { pub struct TransactionDecisionData { external_status: TransactionStatus, reason: Option, - amount: i64, + amount: StringMajorUnit, currency: storage_enums::Currency, #[serde(with = "common_utils::custom_serde::iso8601")] decided_at: PrimitiveDateTime, @@ -429,16 +472,21 @@ pub enum TransactionStatus { Approved, } -impl TryFrom<&frm_types::FrmTransactionRouterData> for TransactionSuccessRequest { +impl TryFrom<&RiskifiedRouterData<&frm_types::FrmTransactionRouterData>> + for TransactionSuccessRequest +{ type Error = Error; - fn try_from(item: &frm_types::FrmTransactionRouterData) -> Result { + fn try_from( + item_data: &RiskifiedRouterData<&frm_types::FrmTransactionRouterData>, + ) -> Result { + let item = item_data.router_data.clone(); Ok(Self { order: SuccessfulTransactionData { id: item.attempt_id.clone(), decision: TransactionDecisionData { external_status: TransactionStatus::Approved, reason: None, - amount: item.request.amount, + amount: item_data.amount.clone(), currency: item.request.get_currency()?, decided_at: common_utils::date_time::now(), payment_details: [TransactionPaymentDetails { diff --git a/crates/router/src/types/api/fraud_check.rs b/crates/router/src/types/api/fraud_check.rs index 2d1a42092f47..213aef9cf039 100644 --- a/crates/router/src/types/api/fraud_check.rs +++ b/crates/router/src/types/api/fraud_check.rs @@ -51,7 +51,7 @@ impl FraudCheckConnectorData { Ok(ConnectorEnum::Old(Box::new(&connector::Signifyd))) } enums::FrmConnectors::Riskified => { - Ok(ConnectorEnum::Old(Box::new(&connector::Riskified))) + Ok(ConnectorEnum::Old(Box::new(connector::Riskified::new()))) } } } From 9baa1ef65442c11dc57dd4b82a32c93f9542d45d Mon Sep 17 00:00:00 2001 From: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:50:33 +0530 Subject: [PATCH 12/51] ci(cypress): Add list and revoke for zero auth mandate payments (#6569) --- .../00013-ListAndRevokeMandate.cy.js | 53 ++++++++++++++++++- cypress-tests/cypress/support/commands.js | 8 +-- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/cypress-tests/cypress/e2e/PaymentTest/00013-ListAndRevokeMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00013-ListAndRevokeMandate.cy.js index 9091135d5396..f341db19f6cc 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00013-ListAndRevokeMandate.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00013-ListAndRevokeMandate.cy.js @@ -4,7 +4,7 @@ import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; let globalState; -describe("Card - SingleUse Mandates flow test", () => { +describe("Card - List and revoke Mandates flow test", () => { before("seed global state", () => { cy.task("getGlobalState").then((state) => { globalState = new State(state); @@ -71,4 +71,55 @@ describe("Card - SingleUse Mandates flow test", () => { }); } ); + context("Card - Zero auth CIT and MIT payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + it("Confirm No 3DS CIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "ZeroAuthMandate" + ]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.citForMandatesCallTest( + fixtures.citConfirmBody, + req_data, + res_data, + 0, + true, + "automatic", + "setup_mandate", + globalState + ); + if (should_continue) + should_continue = utils.should_continue_further(res_data); + }); + + it("list-mandate-call-test", () => { + cy.listMandateCallTest(globalState); + }); + + it("Confirm No 3DS MIT", () => { + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + 7000, + true, + "automatic", + globalState + ); + }); + + it("list-mandate-call-test", () => { + cy.listMandateCallTest(globalState); + }); + + it("revoke-mandate-call-test", () => { + cy.revokeMandateCallTest(globalState); + }); + }); }); diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 5ed6617ae42d..3df6c901d977 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -1969,9 +1969,7 @@ Cypress.Commands.add( } else if (response.body.authentication_type === "no_three_ds") { if (response.body.connector === "fiuu") { expect(response.body.status).to.equal("failed"); - } else { - expect(response.body.status).to.equal("succeeded"); - } + } } else { throw new Error( `Invalid authentication type ${response.body.authentication_type}` @@ -2051,9 +2049,7 @@ Cypress.Commands.add( } else if (response.body.authentication_type === "no_three_ds") { if (response.body.connector === "fiuu") { expect(response.body.status).to.equal("failed"); - } else { - expect(response.body.status).to.equal("succeeded"); - } + } } else { throw new Error( `Invalid authentication type ${response.body.authentication_type}` From ea81432e3eb72d9a2e139e26741a42cdd8d31202 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:59:08 +0530 Subject: [PATCH 13/51] ci(cypress): add testcases for nti based mit flow (#6567) --- .../PaymentTest/00020-MandatesUsingNTID.cy.js | 221 ++++++++++++++++++ .../{00020-UPI.cy.js => 00021-UPI.cy.js} | 0 ...ariations.cy.js => 00022-Variations.cy.js} | 0 ...thods.cy.js => 00023-PaymentMethods.cy.js} | 0 ...ic.cy.js => 00024-ConnectorAgnostic.cy.js} | 0 .../cypress/fixtures/create-ntid-mit.json | 49 ++++ cypress-tests/cypress/fixtures/imports.js | 2 + cypress-tests/cypress/support/commands.js | 75 ++++++ 8 files changed, 347 insertions(+) create mode 100644 cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTID.cy.js rename cypress-tests/cypress/e2e/PaymentTest/{00020-UPI.cy.js => 00021-UPI.cy.js} (100%) rename cypress-tests/cypress/e2e/PaymentTest/{00021-Variations.cy.js => 00022-Variations.cy.js} (100%) rename cypress-tests/cypress/e2e/PaymentTest/{00022-PaymentMethods.cy.js => 00023-PaymentMethods.cy.js} (100%) rename cypress-tests/cypress/e2e/PaymentTest/{00023-ConnectorAgnostic.cy.js => 00024-ConnectorAgnostic.cy.js} (100%) create mode 100644 cypress-tests/cypress/fixtures/create-ntid-mit.json diff --git a/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTID.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTID.cy.js new file mode 100644 index 000000000000..edd46f7f8348 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTID.cy.js @@ -0,0 +1,221 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; +let connector; + +describe("Card - Mandates using Network Transaction Id flow test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + connector = globalState.get("connectorId"); + }); + }); + + afterEach("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context( + "Card - NoThreeDS Create and Confirm Automatic MIT payment flow test", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue || connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT", () => { + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + 7000, + true, + "automatic", + globalState + ); + }); + } + ); + + context( + "Card - NoThreeDS Create and Confirm Manual MIT payment flow test", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue || connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT", () => { + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + 7000, + true, + "manual", + globalState + ); + }); + } + ); + + context( + "Card - NoThreeDS Create and Confirm Automatic multiple MITs payment flow test", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue || connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT", () => { + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + 7000, + true, + "automatic", + globalState + ); + }); + it("Confirm No 3DS MIT", () => { + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + 7000, + true, + "automatic", + globalState + ); + }); + } + ); + + context( + "Card - NoThreeDS Create and Confirm Manual multiple MITs payment flow test", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue || connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT 1", () => { + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + 6500, + true, + "manual", + globalState + ); + }); + + it("mit-capture-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + + cy.captureCallTest( + fixtures.captureBody, + req_data, + res_data, + 6500, + globalState + ); + if (should_continue) + should_continue = utils.should_continue_further(res_data); + }); + + it("Confirm No 3DS MIT 2", () => { + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + 6500, + true, + "manual", + globalState + ); + }); + + it("mit-capture-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + + cy.captureCallTest( + fixtures.captureBody, + req_data, + res_data, + 6500, + globalState + ); + if (should_continue) + should_continue = utils.should_continue_further(res_data); + }); + } + ); + + context( + "Card - ThreeDS Create and Confirm Automatic multiple MITs payment flow test", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue || connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT", () => { + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + 7000, + true, + "automatic", + globalState + ); + }); + it("Confirm No 3DS MIT", () => { + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + 7000, + true, + "automatic", + globalState + ); + }); + } + ); + + context( + "Card - ThreeDS Create and Confirm Manual multiple MITs payment flow", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue || connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT", () => { + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + 7000, + true, + "automatic", + globalState + ); + }); + } + ); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00020-UPI.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00021-UPI.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/PaymentTest/00020-UPI.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00021-UPI.cy.js diff --git a/cypress-tests/cypress/e2e/PaymentTest/00021-Variations.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00022-Variations.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/PaymentTest/00021-Variations.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00022-Variations.cy.js diff --git a/cypress-tests/cypress/e2e/PaymentTest/00022-PaymentMethods.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00023-PaymentMethods.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/PaymentTest/00022-PaymentMethods.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00023-PaymentMethods.cy.js diff --git a/cypress-tests/cypress/e2e/PaymentTest/00023-ConnectorAgnostic.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnostic.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/PaymentTest/00023-ConnectorAgnostic.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnostic.cy.js diff --git a/cypress-tests/cypress/fixtures/create-ntid-mit.json b/cypress-tests/cypress/fixtures/create-ntid-mit.json new file mode 100644 index 000000000000..93b7e95e2a0a --- /dev/null +++ b/cypress-tests/cypress/fixtures/create-ntid-mit.json @@ -0,0 +1,49 @@ +{ + "amount": 999, + "currency": "USD", + "confirm": true, + "payment_method": "card", + "return_url": "https://hyperswitch.io", + "email": "example@email.com", + "recurring_details": { + "type": "network_transaction_id_and_card_details", + "data": { + "card_number": "4242424242424242", + "card_exp_month": "11", + "card_exp_year": "2024", + "card_holder_name": "joseph Doe", + "network_transaction_id": "MCC5ZRGMI0925" + } + }, + "off_session": true, + "billing": { + "address": { + "first_name": "John", + "last_name": "Doe", + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US" + }, + "phone": { + "number": "9123456789", + "country_code": "+91" + } + }, + "browser_info": { + "ip_address": "129.0.0.1", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "language": "en-US", + "color_depth": 30, + "screen_height": 1117, + "screen_width": 1728, + "time_zone": -330, + "java_enabled": true, + "java_script_enabled": true + } + } + \ No newline at end of file diff --git a/cypress-tests/cypress/fixtures/imports.js b/cypress-tests/cypress/fixtures/imports.js index 900e4f4ea3ab..49fea00491fb 100644 --- a/cypress-tests/cypress/fixtures/imports.js +++ b/cypress-tests/cypress/fixtures/imports.js @@ -24,6 +24,7 @@ import updateBusinessProfile from "./update-business-profile.json"; import updateConnectorBody from "./update-connector-body.json"; import customerUpdateBody from "./update-customer-body.json"; import voidBody from "./void-payment-body.json"; +import ntidConfirmBody from "./create-ntid-mit.json"; export { apiKeyCreateBody, @@ -44,6 +45,7 @@ export { merchantCreateBody, merchantUpdateBody, mitConfirmBody, + ntidConfirmBody, pmIdConfirmBody, refundBody, routingConfigBody, diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 3df6c901d977..48a62fb4388b 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -2087,6 +2087,81 @@ Cypress.Commands.add( } ); +Cypress.Commands.add( + "mitUsingNTID", + (requestBody, amount, confirm, capture_method, globalState) => { + + requestBody.amount = amount; + requestBody.confirm = confirm; + requestBody.capture_method = capture_method; + + if (globalState.get("connectorId") !== "cybersource") { + return; + } + + const apiKey = globalState.get("apiKey"); + const baseUrl = globalState.get("baseUrl"); + const url = `${baseUrl}/payments`; + + cy.request({ + method: "POST", + url: url, + headers: { + "Content-Type": "application/json", + "api-key": apiKey, + }, + failOnStatusCode: false, + body: requestBody, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + if (response.status === 200) { + expect(response.headers["content-type"]).to.include("application/json"); + + globalState.set("paymentID", response.body.payment_id); + + if (response.body.capture_method === "automatic") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + cy.log(nextActionUrl); + } else if (response.body.authentication_type === "no_three_ds") { + expect(response.body.status).to.equal("succeeded"); + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` + ); + } + } else if (response.body.capture_method === "manual") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + cy.log(nextActionUrl); + } else if (response.body.authentication_type === "no_three_ds") { + expect(response.body.status).to.equal("requires_capture"); + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` + ); + } + } else { + throw new Error( + `Invalid capture method ${response.body.capture_method}` + ); + } + } else { + throw new Error( + `Error Response: ${response.status}\n${response.body.error.message}\n${response.body.error.code}` + ); + } + }); + } +); + Cypress.Commands.add("listMandateCallTest", (globalState) => { const customerId = globalState.get("customerId"); cy.request({ From 29a0885a8fc7b718f8b87866e2638e8bfad3c8f3 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:06:43 +0530 Subject: [PATCH 14/51] feat(router): [Cybersource] add PLN to the currency config (#6628) --- config/config.example.toml | 8 ++++---- config/deployments/integration_test.toml | 8 ++++---- config/deployments/production.toml | 8 ++++---- config/deployments/sandbox.toml | 8 ++++---- config/development.toml | 8 ++++---- config/docker_compose.toml | 8 ++++---- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index 191f2ba7f8b0..0436cea6b484 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -543,10 +543,10 @@ apple_pay = { currency = "USD" } google_pay = { currency = "USD" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } paze = { currency = "USD" } diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 00a544dc565e..ce6f38d84a46 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -309,10 +309,10 @@ klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,C sofort = { country = "AT,BE,DE,IT,NL,ES", currency = "EUR" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } paze = { currency = "USD" } diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 0fe9095d2806..81985e83bcc0 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -283,10 +283,10 @@ google_pay = { currency = "USD" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } paze = { currency = "USD" } diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 82c347ae389c..4f98dc1ef093 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -286,10 +286,10 @@ apple_pay = { currency = "USD" } google_pay = { currency = "USD" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } paze = { currency = "USD" } diff --git a/config/development.toml b/config/development.toml index ee6ea5dab0bb..4cf2e1d4a805 100644 --- a/config/development.toml +++ b/config/development.toml @@ -461,10 +461,10 @@ google_pay = { currency = "USD" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } paze = { currency = "USD" } diff --git a/config/docker_compose.toml b/config/docker_compose.toml index ed0ede98d945..bf7779863ca5 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -462,10 +462,10 @@ apple_pay = { currency = "USD" } google_pay = { currency = "USD" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } paze = { currency = "USD" } From 02479a12b18dc68e2787ae237580fcb46348374e Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:46:32 +0530 Subject: [PATCH 15/51] refactor(authn): Enable cookies in Integ (#6599) --- config/config.example.toml | 1 + config/deployments/integration_test.toml | 1 + config/deployments/production.toml | 1 + config/deployments/sandbox.toml | 1 + config/development.toml | 1 + config/docker_compose.toml | 1 + crates/router/src/configs/settings.rs | 1 + crates/router/src/core/user.rs | 2 +- crates/router/src/routes/user.rs | 2 +- crates/router/src/services/authentication.rs | 71 +++++++++++++++++--- loadtest/config/development.toml | 1 + 11 files changed, 71 insertions(+), 12 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index 0436cea6b484..4bdcdcf79dfb 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -403,6 +403,7 @@ two_factor_auth_expiry_in_secs = 300 # Number of seconds after which 2FA should totp_issuer_name = "Hyperswitch" # Name of the issuer for TOTP base_url = "" # Base url used for user specific redirects and emails force_two_factor_auth = false # Whether to force two factor authentication for all users +force_cookies = true # Whether to use only cookies for JWT extraction and authentication #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index ce6f38d84a46..a4e1b1e9b13f 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -145,6 +145,7 @@ two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Integ" base_url = "https://integ.hyperswitch.io" force_two_factor_auth = false +force_cookies = true [frm] enabled = true diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 81985e83bcc0..a859d08ac4ab 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -152,6 +152,7 @@ two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Production" base_url = "https://live.hyperswitch.io" force_two_factor_auth = true +force_cookies = false [frm] enabled = false diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 4f98dc1ef093..070a32ef87b6 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -152,6 +152,7 @@ two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Sandbox" base_url = "https://app.hyperswitch.io" force_two_factor_auth = false +force_cookies = false [frm] enabled = true diff --git a/config/development.toml b/config/development.toml index 4cf2e1d4a805..2388607a4898 100644 --- a/config/development.toml +++ b/config/development.toml @@ -329,6 +329,7 @@ two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Dev" base_url = "http://localhost:8080" force_two_factor_auth = false +force_cookies = true [bank_config.eps] stripe = { banks = "arzte_und_apotheker_bank,austrian_anadi_bank_ag,bank_austria,bankhaus_carl_spangler,bankhaus_schelhammer_und_schattera_ag,bawag_psk_ag,bks_bank_ag,brull_kallmus_bank_ag,btv_vier_lander_bank,capital_bank_grawe_gruppe_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_alpeadriabank_international_ag,hypo_noe_lb_fur_niederosterreich_u_wien,hypo_oberosterreich_salzburg_steiermark,hypo_tirol_bank_ag,hypo_vorarlberg_bank_ag,hypo_bank_burgenland_aktiengesellschaft,marchfelder_bank,oberbank_ag,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag,vr_bank_braunau" } diff --git a/config/docker_compose.toml b/config/docker_compose.toml index bf7779863ca5..d72141d9c372 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -57,6 +57,7 @@ two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch" base_url = "http://localhost:8080" force_two_factor_auth = false +force_cookies = true [locker] host = "" diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 7b212ec6d1dc..4e559a261b9e 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -557,6 +557,7 @@ pub struct UserSettings { pub totp_issuer_name: String, pub base_url: String, pub force_two_factor_auth: bool, + pub force_cookies: bool, } #[derive(Debug, Deserialize, Clone)] diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index f01f6c5d7496..c6501dac3bd7 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -294,7 +294,7 @@ pub async fn connect_account( pub async fn signout( state: SessionState, - user_from_token: auth::UserFromToken, + user_from_token: auth::UserIdFromAuth, ) -> UserResponse<()> { tfa_utils::delete_totp_from_redis(&state, &user_from_token.user_id).await?; tfa_utils::delete_recovery_code_from_redis(&state, &user_from_token.user_id).await?; diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index 068c2f30c795..8fc0dad452a9 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -130,7 +130,7 @@ pub async fn signout(state: web::Data, http_req: HttpRequest) -> HttpR &http_req, (), |state, user, _, _| user_core::signout(state, user), - &auth::DashboardNoPermissionAuth, + &auth::AnyPurposeOrLoginTokenAuth, api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 2f5f55b84345..c05e4514aaa3 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -871,6 +871,47 @@ where } } +#[cfg(feature = "olap")] +#[derive(Debug)] +pub struct AnyPurposeOrLoginTokenAuth; + +#[cfg(feature = "olap")] +#[async_trait] +impl AuthenticateAndFetch for AnyPurposeOrLoginTokenAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(UserIdFromAuth, AuthenticationType)> { + let payload = + parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + + let purpose_exists = payload.purpose.is_some(); + let role_id_exists = payload.role_id.is_some(); + + if purpose_exists ^ role_id_exists { + Ok(( + UserIdFromAuth { + user_id: payload.user_id.clone(), + }, + AuthenticationType::SinglePurposeOrLoginJwt { + user_id: payload.user_id, + purpose: payload.purpose, + role_id: payload.role_id, + }, + )) + } else { + Err(errors::ApiErrorResponse::InvalidJwtToken.into()) + } + } +} + #[derive(Debug, Default)] pub struct AdminApiAuth; @@ -2504,17 +2545,27 @@ where T: serde::de::DeserializeOwned, A: SessionStateInfo + Sync, { - let token = match get_cookie_from_header(headers).and_then(cookies::parse_cookie) { - Ok(cookies) => cookies, - Err(error) => { - let token = get_jwt_from_authorization_header(headers); - if token.is_err() { - logger::error!(?error); - } - token?.to_owned() - } + let cookie_token_result = get_cookie_from_header(headers).and_then(cookies::parse_cookie); + let auth_header_token_result = get_jwt_from_authorization_header(headers); + let force_cookie = state.conf().user.force_cookies; + + logger::info!( + user_agent = ?headers.get(headers::USER_AGENT), + header_names = ?headers.keys().collect::>(), + is_token_equal = + auth_header_token_result.as_deref().ok() == cookie_token_result.as_deref().ok(), + cookie_error = ?cookie_token_result.as_ref().err(), + token_error = ?auth_header_token_result.as_ref().err(), + force_cookie, + ); + + let final_token = if force_cookie { + cookie_token_result? + } else { + auth_header_token_result?.to_owned() }; - decode_jwt(&token, state).await + + decode_jwt(&final_token, state).await } #[cfg(feature = "v1")] diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index dab85eb3cdd7..a3ac1159ddb0 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -36,6 +36,7 @@ password_validity_in_days = 90 two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch" force_two_factor_auth = false +force_cookies = true [locker] host = "" From 8fbb7663089d4790628109944e5fb5a57ccdaf00 Mon Sep 17 00:00:00 2001 From: Uzair Khan Date: Tue, 26 Nov 2024 18:54:42 +0530 Subject: [PATCH 16/51] feat(analytics): add `sessionized_metrics` for disputes analytics (#6573) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/analytics/src/clickhouse.rs | 4 + crates/analytics/src/disputes/accumulators.rs | 12 +- crates/analytics/src/disputes/core.rs | 46 ++++--- crates/analytics/src/disputes/metrics.rs | 16 +++ .../dispute_status_metric.rs | 120 ++++++++++++++++++ .../metrics/sessionized_metrics/mod.rs | 8 ++ .../total_amount_disputed.rs | 118 +++++++++++++++++ .../total_dispute_lost_amount.rs | 119 +++++++++++++++++ crates/analytics/src/sqlx.rs | 2 + crates/analytics/src/types.rs | 1 + crates/api_models/src/analytics.rs | 11 ++ crates/api_models/src/analytics/disputes.rs | 7 +- crates/api_models/src/events.rs | 6 + 13 files changed, 447 insertions(+), 23 deletions(-) create mode 100644 crates/analytics/src/disputes/metrics/sessionized_metrics/dispute_status_metric.rs create mode 100644 crates/analytics/src/disputes/metrics/sessionized_metrics/mod.rs create mode 100644 crates/analytics/src/disputes/metrics/sessionized_metrics/total_amount_disputed.rs create mode 100644 crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs diff --git a/crates/analytics/src/clickhouse.rs b/crates/analytics/src/clickhouse.rs index 546b57f99af2..f56e875f720a 100644 --- a/crates/analytics/src/clickhouse.rs +++ b/crates/analytics/src/clickhouse.rs @@ -139,6 +139,9 @@ impl AnalyticsDataSource for ClickhouseClient { | AnalyticsCollection::Dispute => { TableEngine::CollapsingMergeTree { sign: "sign_flag" } } + AnalyticsCollection::DisputeSessionized => { + TableEngine::CollapsingMergeTree { sign: "sign_flag" } + } AnalyticsCollection::SdkEvents | AnalyticsCollection::SdkEventsAnalytics | AnalyticsCollection::ApiEvents @@ -439,6 +442,7 @@ impl ToSql for AnalyticsCollection { Self::ConnectorEvents => Ok("connector_events_audit".to_string()), Self::OutgoingWebhookEvent => Ok("outgoing_webhook_events_audit".to_string()), Self::Dispute => Ok("dispute".to_string()), + Self::DisputeSessionized => Ok("sessionizer_dispute".to_string()), Self::ActivePaymentsAnalytics => Ok("active_payments".to_string()), } } diff --git a/crates/analytics/src/disputes/accumulators.rs b/crates/analytics/src/disputes/accumulators.rs index 1997d75d3230..41bd3beebdb7 100644 --- a/crates/analytics/src/disputes/accumulators.rs +++ b/crates/analytics/src/disputes/accumulators.rs @@ -5,8 +5,8 @@ use super::metrics::DisputeMetricRow; #[derive(Debug, Default)] pub struct DisputeMetricsAccumulator { pub disputes_status_rate: RateAccumulator, - pub total_amount_disputed: SumAccumulator, - pub total_dispute_lost_amount: SumAccumulator, + pub disputed_amount: DisputedAmountAccumulator, + pub dispute_lost_amount: DisputedAmountAccumulator, } #[derive(Debug, Default)] pub struct RateAccumulator { @@ -17,7 +17,7 @@ pub struct RateAccumulator { } #[derive(Debug, Default)] #[repr(transparent)] -pub struct SumAccumulator { +pub struct DisputedAmountAccumulator { pub total: Option, } @@ -29,7 +29,7 @@ pub trait DisputeMetricAccumulator { fn collect(self) -> Self::MetricOutput; } -impl DisputeMetricAccumulator for SumAccumulator { +impl DisputeMetricAccumulator for DisputedAmountAccumulator { type MetricOutput = Option; #[inline] fn add_metrics_bucket(&mut self, metrics: &DisputeMetricRow) { @@ -92,8 +92,8 @@ impl DisputeMetricsAccumulator { disputes_challenged: challenge_rate, disputes_won: won_rate, disputes_lost: lost_rate, - total_amount_disputed: self.total_amount_disputed.collect(), - total_dispute_lost_amount: self.total_dispute_lost_amount.collect(), + disputed_amount: self.disputed_amount.collect(), + dispute_lost_amount: self.dispute_lost_amount.collect(), total_dispute, } } diff --git a/crates/analytics/src/disputes/core.rs b/crates/analytics/src/disputes/core.rs index b8b44a757dec..85d1a62a1d94 100644 --- a/crates/analytics/src/disputes/core.rs +++ b/crates/analytics/src/disputes/core.rs @@ -5,8 +5,8 @@ use api_models::analytics::{ DisputeDimensions, DisputeMetrics, DisputeMetricsBucketIdentifier, DisputeMetricsBucketResponse, }, - AnalyticsMetadata, DisputeFilterValue, DisputeFiltersResponse, GetDisputeFilterRequest, - GetDisputeMetricRequest, MetricsResponse, + DisputeFilterValue, DisputeFiltersResponse, DisputesAnalyticsMetadata, DisputesMetricsResponse, + GetDisputeFilterRequest, GetDisputeMetricRequest, }; use error_stack::ResultExt; use router_env::{ @@ -30,7 +30,7 @@ pub async fn get_metrics( pool: &AnalyticsProvider, auth: &AuthInfo, req: GetDisputeMetricRequest, -) -> AnalyticsResult> { +) -> AnalyticsResult> { let mut metrics_accumulator: HashMap< DisputeMetricsBucketIdentifier, DisputeMetricsAccumulator, @@ -87,14 +87,17 @@ pub async fn get_metrics( logger::debug!(bucket_id=?id, bucket_value=?value, "Bucket row for metric {metric}"); let metrics_builder = metrics_accumulator.entry(id).or_default(); match metric { - DisputeMetrics::DisputeStatusMetric => metrics_builder + DisputeMetrics::DisputeStatusMetric + | DisputeMetrics::SessionizedDisputeStatusMetric => metrics_builder .disputes_status_rate .add_metrics_bucket(&value), - DisputeMetrics::TotalAmountDisputed => metrics_builder - .total_amount_disputed - .add_metrics_bucket(&value), - DisputeMetrics::TotalDisputeLostAmount => metrics_builder - .total_dispute_lost_amount + DisputeMetrics::TotalAmountDisputed + | DisputeMetrics::SessionizedTotalAmountDisputed => { + metrics_builder.disputed_amount.add_metrics_bucket(&value) + } + DisputeMetrics::TotalDisputeLostAmount + | DisputeMetrics::SessionizedTotalDisputeLostAmount => metrics_builder + .dispute_lost_amount .add_metrics_bucket(&value), } } @@ -105,18 +108,31 @@ pub async fn get_metrics( metrics_accumulator ); } + let mut total_disputed_amount = 0; + let mut total_dispute_lost_amount = 0; let query_data: Vec = metrics_accumulator .into_iter() - .map(|(id, val)| DisputeMetricsBucketResponse { - values: val.collect(), - dimensions: id, + .map(|(id, val)| { + let collected_values = val.collect(); + if let Some(amount) = collected_values.disputed_amount { + total_disputed_amount += amount; + } + if let Some(amount) = collected_values.dispute_lost_amount { + total_dispute_lost_amount += amount; + } + + DisputeMetricsBucketResponse { + values: collected_values, + dimensions: id, + } }) .collect(); - Ok(MetricsResponse { + Ok(DisputesMetricsResponse { query_data, - meta_data: [AnalyticsMetadata { - current_time_range: req.time_range, + meta_data: [DisputesAnalyticsMetadata { + total_disputed_amount: Some(total_disputed_amount), + total_dispute_lost_amount: Some(total_dispute_lost_amount), }], }) } diff --git a/crates/analytics/src/disputes/metrics.rs b/crates/analytics/src/disputes/metrics.rs index dd1aa3c1bbd4..ad7ed81aaeee 100644 --- a/crates/analytics/src/disputes/metrics.rs +++ b/crates/analytics/src/disputes/metrics.rs @@ -1,4 +1,5 @@ mod dispute_status_metric; +mod sessionized_metrics; mod total_amount_disputed; mod total_dispute_lost_amount; @@ -92,6 +93,21 @@ where .load_metrics(dimensions, auth, filters, granularity, time_range, pool) .await } + Self::SessionizedTotalAmountDisputed => { + sessionized_metrics::TotalAmountDisputed::default() + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedDisputeStatusMetric => { + sessionized_metrics::DisputeStatusMetric::default() + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedTotalDisputeLostAmount => { + sessionized_metrics::TotalDisputeLostAmount::default() + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } } } } diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics/dispute_status_metric.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics/dispute_status_metric.rs new file mode 100644 index 000000000000..c5c0b91a173d --- /dev/null +++ b/crates/analytics/src/disputes/metrics/sessionized_metrics/dispute_status_metric.rs @@ -0,0 +1,120 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + disputes::{DisputeDimensions, DisputeFilters, DisputeMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::DisputeMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; +#[derive(Default)] +pub(crate) struct DisputeStatusMetric {} + +#[async_trait::async_trait] +impl super::DisputeMetric for DisputeStatusMetric +where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[DisputeDimensions], + auth: &AuthInfo, + filters: &DisputeFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + { + let mut query_builder = QueryBuilder::new(AnalyticsCollection::DisputeSessionized); + + for dim in dimensions { + query_builder.add_select_column(dim).switch()?; + } + + query_builder.add_select_column("dispute_status").switch()?; + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range.set_filter_clause(&mut query_builder).switch()?; + + for dim in dimensions { + query_builder.add_group_by_clause(dim).switch()?; + } + + query_builder + .add_group_by_clause("dispute_status") + .switch()?; + + if let Some(granularity) = granularity.as_ref() { + granularity + .set_group_by_clause(&mut query_builder) + .switch()?; + } + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + DisputeMetricsBucketIdentifier::new( + i.dispute_stage.as_ref().map(|i| i.0), + i.connector.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics/mod.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics/mod.rs new file mode 100644 index 000000000000..4d41194634db --- /dev/null +++ b/crates/analytics/src/disputes/metrics/sessionized_metrics/mod.rs @@ -0,0 +1,8 @@ +mod dispute_status_metric; +mod total_amount_disputed; +mod total_dispute_lost_amount; +pub(super) use dispute_status_metric::DisputeStatusMetric; +pub(super) use total_amount_disputed::TotalAmountDisputed; +pub(super) use total_dispute_lost_amount::TotalDisputeLostAmount; + +pub use super::{DisputeMetric, DisputeMetricAnalytics, DisputeMetricRow}; diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics/total_amount_disputed.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_amount_disputed.rs new file mode 100644 index 000000000000..0767bdaf85d2 --- /dev/null +++ b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_amount_disputed.rs @@ -0,0 +1,118 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + disputes::{DisputeDimensions, DisputeFilters, DisputeMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::DisputeMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; +#[derive(Default)] +pub(crate) struct TotalAmountDisputed {} + +#[async_trait::async_trait] +impl super::DisputeMetric for TotalAmountDisputed +where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[DisputeDimensions], + auth: &AuthInfo, + filters: &DisputeFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::DisputeSessionized); + + for dim in dimensions { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Sum { + field: "dispute_amount", + alias: Some("total"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder.add_group_by_clause(dim).switch()?; + } + + if let Some(granularity) = granularity.as_ref() { + granularity + .set_group_by_clause(&mut query_builder) + .switch()?; + } + query_builder + .add_filter_clause("dispute_status", "dispute_won") + .switch()?; + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + DisputeMetricsBucketIdentifier::new( + i.dispute_stage.as_ref().map(|i| i.0), + i.connector.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, crate::query::PostProcessingError>>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs new file mode 100644 index 000000000000..f4f4d8608620 --- /dev/null +++ b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs @@ -0,0 +1,119 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + disputes::{DisputeDimensions, DisputeFilters, DisputeMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::DisputeMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; +#[derive(Default)] +pub(crate) struct TotalDisputeLostAmount {} + +#[async_trait::async_trait] +impl super::DisputeMetric for TotalDisputeLostAmount +where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[DisputeDimensions], + auth: &AuthInfo, + filters: &DisputeFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::DisputeSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Sum { + field: "dispute_amount", + alias: Some("total"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder.add_group_by_clause(dim).switch()?; + } + + if let Some(granularity) = granularity.as_ref() { + granularity + .set_group_by_clause(&mut query_builder) + .switch()?; + } + + query_builder + .add_filter_clause("dispute_status", "dispute_lost") + .switch()?; + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + DisputeMetricsBucketIdentifier::new( + i.dispute_stage.as_ref().map(|i| i.0), + i.connector.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, crate::query::PostProcessingError>>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/sqlx.rs b/crates/analytics/src/sqlx.rs index 16523d5d0a78..1d91ce17c6ab 100644 --- a/crates/analytics/src/sqlx.rs +++ b/crates/analytics/src/sqlx.rs @@ -932,6 +932,8 @@ impl ToSql for AnalyticsCollection { Self::OutgoingWebhookEvent => Err(error_stack::report!(ParsingError::UnknownError) .attach_printable("OutgoingWebhookEvents table is not implemented for Sqlx"))?, Self::Dispute => Ok("dispute".to_string()), + Self::DisputeSessionized => Err(error_stack::report!(ParsingError::UnknownError) + .attach_printable("DisputeSessionized table is not implemented for Sqlx"))?, } } } diff --git a/crates/analytics/src/types.rs b/crates/analytics/src/types.rs index 6bdd11fcd733..86056338106b 100644 --- a/crates/analytics/src/types.rs +++ b/crates/analytics/src/types.rs @@ -38,6 +38,7 @@ pub enum AnalyticsCollection { ConnectorEvents, OutgoingWebhookEvent, Dispute, + DisputeSessionized, ApiEventsAnalytics, ActivePaymentsAnalytics, } diff --git a/crates/api_models/src/analytics.rs b/crates/api_models/src/analytics.rs index 70c0e0e78dc8..ee9046521549 100644 --- a/crates/api_models/src/analytics.rs +++ b/crates/api_models/src/analytics.rs @@ -346,6 +346,11 @@ pub struct SdkEventFilterValue { pub values: Vec, } +#[derive(Debug, serde::Serialize)] +pub struct DisputesAnalyticsMetadata { + pub total_disputed_amount: Option, + pub total_dispute_lost_amount: Option, +} #[derive(Debug, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct MetricsResponse { @@ -373,6 +378,12 @@ pub struct RefundsMetricsResponse { pub query_data: Vec, pub meta_data: [RefundsAnalyticsMetadata; 1], } +#[derive(Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DisputesMetricsResponse { + pub query_data: Vec, + pub meta_data: [DisputesAnalyticsMetadata; 1], +} #[derive(Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct GetApiEventFiltersRequest { diff --git a/crates/api_models/src/analytics/disputes.rs b/crates/api_models/src/analytics/disputes.rs index edb85c129e67..2509d83e1320 100644 --- a/crates/api_models/src/analytics/disputes.rs +++ b/crates/api_models/src/analytics/disputes.rs @@ -24,6 +24,9 @@ pub enum DisputeMetrics { DisputeStatusMetric, TotalAmountDisputed, TotalDisputeLostAmount, + SessionizedDisputeStatusMetric, + SessionizedTotalAmountDisputed, + SessionizedTotalDisputeLostAmount, } #[derive( @@ -122,8 +125,8 @@ pub struct DisputeMetricsBucketValue { pub disputes_challenged: Option, pub disputes_won: Option, pub disputes_lost: Option, - pub total_amount_disputed: Option, - pub total_dispute_lost_amount: Option, + pub disputed_amount: Option, + pub dispute_lost_amount: Option, pub total_dispute: Option, } #[derive(Debug, serde::Serialize)] diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index dad624ef87c4..72a0d592513f 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -173,6 +173,12 @@ impl ApiEventMetric for RefundsMetricsResponse { Some(ApiEventsType::Miscellaneous) } } + +impl ApiEventMetric for DisputesMetricsResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Miscellaneous) + } +} #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl ApiEventMetric for PaymentMethodIntentConfirmInternal { fn get_api_event_type(&self) -> Option { From e922f96cee7e34493f0022b0c56455357eddc4f8 Mon Sep 17 00:00:00 2001 From: Sarthak Soni <76486416+Sarthak1799@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:54:55 +0530 Subject: [PATCH 17/51] feat: Added grpc based health check (#6441) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 1 + config/deployments/env_specific.toml | 1 + crates/api_models/Cargo.toml | 1 + crates/api_models/src/health_check.rs | 13 ++ crates/external_services/build.rs | 18 ++- crates/external_services/src/grpc_client.rs | 35 +++- .../src/grpc_client/dynamic_routing.rs | 17 +- .../src/grpc_client/health_check_client.rs | 149 ++++++++++++++++++ crates/router/Cargo.toml | 2 +- crates/router/src/core/health_check.rs | 23 +++ crates/router/src/routes/health.rs | 15 ++ crates/storage_impl/src/errors.rs | 6 + proto/health_check.proto | 21 +++ 13 files changed, 285 insertions(+), 17 deletions(-) create mode 100644 crates/external_services/src/grpc_client/health_check_client.rs create mode 100644 proto/health_check.proto diff --git a/config/config.example.toml b/config/config.example.toml index 4bdcdcf79dfb..ba14ed881cf4 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -790,3 +790,4 @@ connector_list = "cybersource" # Supported connectors for network tokenization [grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration host = "localhost" # Client Host port = 7000 # Client Port +service = "dynamo" # Service name diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index 0eab330652a8..fa0c0484a773 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -326,3 +326,4 @@ check_token_status_url= "" # base url to check token status from token servic [grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration host = "localhost" # Client Host port = 7000 # Client Port +service = "dynamo" # Service name diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index e8668aab5024..a5d702e26fb1 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -20,6 +20,7 @@ v1 = ["common_utils/v1"] v2 = ["common_utils/v2", "customer_v2"] customer_v2 = ["common_utils/customer_v2"] payment_methods_v2 = ["common_utils/payment_methods_v2"] +dynamic_routing = [] [dependencies] actix-web = { version = "4.5.1", optional = true } diff --git a/crates/api_models/src/health_check.rs b/crates/api_models/src/health_check.rs index 1e86e2964c78..4a1c009e43eb 100644 --- a/crates/api_models/src/health_check.rs +++ b/crates/api_models/src/health_check.rs @@ -1,3 +1,4 @@ +use std::collections::hash_map::HashMap; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RouterHealthCheckResponse { pub database: bool, @@ -9,10 +10,22 @@ pub struct RouterHealthCheckResponse { #[cfg(feature = "olap")] pub opensearch: bool, pub outgoing_request: bool, + #[cfg(feature = "dynamic_routing")] + pub grpc_health_check: HealthCheckMap, } impl common_utils::events::ApiEventMetric for RouterHealthCheckResponse {} +/// gRPC based services eligible for Health check +#[derive(Debug, Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum HealthCheckServices { + /// Dynamic routing service + DynamicRoutingService, +} + +pub type HealthCheckMap = HashMap; + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct SchedulerHealthCheckResponse { pub database: bool, diff --git a/crates/external_services/build.rs b/crates/external_services/build.rs index 605ef6997157..0a4938e94b46 100644 --- a/crates/external_services/build.rs +++ b/crates/external_services/build.rs @@ -3,11 +3,21 @@ fn main() -> Result<(), Box> { #[cfg(feature = "dynamic_routing")] { // Get the directory of the current crate - let proto_file = router_env::workspace_path() - .join("proto") - .join("success_rate.proto"); + + let proto_path = router_env::workspace_path().join("proto"); + let success_rate_proto_file = proto_path.join("success_rate.proto"); + + let health_check_proto_file = proto_path.join("health_check.proto"); + let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?); + // Compile the .proto file - tonic_build::compile_protos(proto_file).expect("Failed to compile success rate proto file"); + tonic_build::configure() + .out_dir(out_dir) + .compile( + &[success_rate_proto_file, health_check_proto_file], + &[proto_path], + ) + .expect("Failed to compile proto files"); } Ok(()) } diff --git a/crates/external_services/src/grpc_client.rs b/crates/external_services/src/grpc_client.rs index e7b229a80708..8981a1094d69 100644 --- a/crates/external_services/src/grpc_client.rs +++ b/crates/external_services/src/grpc_client.rs @@ -1,11 +1,28 @@ /// Dyanimc Routing Client interface implementation #[cfg(feature = "dynamic_routing")] pub mod dynamic_routing; +/// gRPC based Heath Check Client interface implementation +#[cfg(feature = "dynamic_routing")] +pub mod health_check_client; use std::{fmt::Debug, sync::Arc}; #[cfg(feature = "dynamic_routing")] use dynamic_routing::{DynamicRoutingClientConfig, RoutingStrategy}; +#[cfg(feature = "dynamic_routing")] +use health_check_client::HealthCheckClient; +#[cfg(feature = "dynamic_routing")] +use http_body_util::combinators::UnsyncBoxBody; +#[cfg(feature = "dynamic_routing")] +use hyper::body::Bytes; +#[cfg(feature = "dynamic_routing")] +use hyper_util::client::legacy::connect::HttpConnector; use serde; +#[cfg(feature = "dynamic_routing")] +use tonic::Status; + +#[cfg(feature = "dynamic_routing")] +/// Hyper based Client type for maintaining connection pool for all gRPC services +pub type Client = hyper_util::client::legacy::Client>; /// Struct contains all the gRPC Clients #[derive(Debug, Clone)] @@ -13,6 +30,9 @@ pub struct GrpcClients { /// The routing client #[cfg(feature = "dynamic_routing")] pub dynamic_routing: RoutingStrategy, + /// Health Check client for all gRPC services + #[cfg(feature = "dynamic_routing")] + pub health_client: HealthCheckClient, } /// Type that contains the configs required to construct a gRPC client with its respective services. #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, Default)] @@ -29,17 +49,30 @@ impl GrpcClientSettings { /// This function will be called at service startup. #[allow(clippy::expect_used)] pub async fn get_grpc_client_interface(&self) -> Arc { + #[cfg(feature = "dynamic_routing")] + let client = + hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) + .http2_only(true) + .build_http(); + #[cfg(feature = "dynamic_routing")] let dynamic_routing_connection = self .dynamic_routing_client .clone() - .get_dynamic_routing_connection() + .get_dynamic_routing_connection(client.clone()) .await .expect("Failed to establish a connection with the Dynamic Routing Server"); + #[cfg(feature = "dynamic_routing")] + let health_client = HealthCheckClient::build_connections(self, client) + .await + .expect("Failed to build gRPC connections"); + Arc::new(GrpcClients { #[cfg(feature = "dynamic_routing")] dynamic_routing: dynamic_routing_connection, + #[cfg(feature = "dynamic_routing")] + health_client, }) } } diff --git a/crates/external_services/src/grpc_client/dynamic_routing.rs b/crates/external_services/src/grpc_client/dynamic_routing.rs index 3264f065b513..7ec42de0d7ca 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing.rs @@ -6,9 +6,6 @@ use api_models::routing::{ }; use common_utils::{errors::CustomResult, ext_traits::OptionExt, transformers::ForeignTryFrom}; use error_stack::ResultExt; -use http_body_util::combinators::UnsyncBoxBody; -use hyper::body::Bytes; -use hyper_util::client::legacy::connect::HttpConnector; use router_env::logger; use serde; use success_rate::{ @@ -18,7 +15,8 @@ use success_rate::{ InvalidateWindowsResponse, LabelWithStatus, UpdateSuccessRateWindowConfig, UpdateSuccessRateWindowRequest, UpdateSuccessRateWindowResponse, }; -use tonic::Status; + +use super::Client; #[allow( missing_docs, unused_qualifications, @@ -45,8 +43,6 @@ pub enum DynamicRoutingError { SuccessRateBasedRoutingFailure(String), } -type Client = hyper_util::client::legacy::Client>; - /// Type that consists of all the services provided by the client #[derive(Debug, Clone)] pub struct RoutingStrategy { @@ -64,6 +60,8 @@ pub enum DynamicRoutingClientConfig { host: String, /// The port of the client port: u16, + /// Service name + service: String, }, #[default] /// If the dynamic routing client config has been disabled @@ -74,13 +72,10 @@ impl DynamicRoutingClientConfig { /// establish connection with the server pub async fn get_dynamic_routing_connection( self, + client: Client, ) -> Result> { - let client = - hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) - .http2_only(true) - .build_http(); let success_rate_client = match self { - Self::Enabled { host, port } => { + Self::Enabled { host, port, .. } => { let uri = format!("http://{}:{}", host, port).parse::()?; logger::info!("Connection established with dynamic routing gRPC Server"); Some(SuccessRateCalculatorClient::with_origin(client, uri)) diff --git a/crates/external_services/src/grpc_client/health_check_client.rs b/crates/external_services/src/grpc_client/health_check_client.rs new file mode 100644 index 000000000000..94d78df7955c --- /dev/null +++ b/crates/external_services/src/grpc_client/health_check_client.rs @@ -0,0 +1,149 @@ +use std::{collections::HashMap, fmt::Debug}; + +use api_models::health_check::{HealthCheckMap, HealthCheckServices}; +use common_utils::{errors::CustomResult, ext_traits::AsyncExt}; +use error_stack::ResultExt; +pub use health_check::{ + health_check_response::ServingStatus, health_client::HealthClient, HealthCheckRequest, + HealthCheckResponse, +}; +use router_env::logger; + +#[allow( + missing_docs, + unused_qualifications, + clippy::unwrap_used, + clippy::as_conversions, + clippy::use_self +)] +pub mod health_check { + tonic::include_proto!("grpc.health.v1"); +} + +use super::{Client, DynamicRoutingClientConfig, GrpcClientSettings}; + +/// Result type for Dynamic Routing +pub type HealthCheckResult = CustomResult; +/// Dynamic Routing Errors +#[derive(Debug, Clone, thiserror::Error)] +pub enum HealthCheckError { + /// The required input is missing + #[error("Missing fields: {0} for building the Health check connection")] + MissingFields(String), + /// Error from gRPC Server + #[error("Error from gRPC Server : {0}")] + ConnectionError(String), + /// status is invalid + #[error("Invalid Status from server")] + InvalidStatus, +} + +/// Health Check Client type +#[derive(Debug, Clone)] +pub struct HealthCheckClient { + /// Health clients for all gRPC based services + pub clients: HashMap>, +} + +impl HealthCheckClient { + /// Build connections to all gRPC services + pub async fn build_connections( + config: &GrpcClientSettings, + client: Client, + ) -> Result> { + let dynamic_routing_config = &config.dynamic_routing_client; + let connection = match dynamic_routing_config { + DynamicRoutingClientConfig::Enabled { + host, + port, + service, + } => Some((host.clone(), *port, service.clone())), + _ => None, + }; + + let mut client_map = HashMap::new(); + + if let Some(conn) = connection { + let uri = format!("http://{}:{}", conn.0, conn.1).parse::()?; + let health_client = HealthClient::with_origin(client, uri); + + client_map.insert(HealthCheckServices::DynamicRoutingService, health_client); + } + + Ok(Self { + clients: client_map, + }) + } + /// Perform health check for all services involved + pub async fn perform_health_check( + &self, + config: &GrpcClientSettings, + ) -> HealthCheckResult { + let dynamic_routing_config = &config.dynamic_routing_client; + let connection = match dynamic_routing_config { + DynamicRoutingClientConfig::Enabled { + host, + port, + service, + } => Some((host.clone(), *port, service.clone())), + _ => None, + }; + + let health_client = self + .clients + .get(&HealthCheckServices::DynamicRoutingService); + + // SAFETY : This is a safe cast as there exists a valid + // integer value for this variant + #[allow(clippy::as_conversions)] + let expected_status = ServingStatus::Serving as i32; + + let mut service_map = HealthCheckMap::new(); + + let health_check_succeed = connection + .as_ref() + .async_map(|conn| self.get_response_from_grpc_service(conn.2.clone(), health_client)) + .await + .transpose() + .change_context(HealthCheckError::ConnectionError( + "error calling dynamic routing service".to_string(), + )) + .map_err(|err| logger::error!(error=?err)) + .ok() + .flatten() + .is_some_and(|resp| resp.status == expected_status); + + connection.and_then(|_conn| { + service_map.insert( + HealthCheckServices::DynamicRoutingService, + health_check_succeed, + ) + }); + + Ok(service_map) + } + + async fn get_response_from_grpc_service( + &self, + service: String, + client: Option<&HealthClient>, + ) -> HealthCheckResult { + let request = tonic::Request::new(HealthCheckRequest { service }); + + let mut client = client + .ok_or(HealthCheckError::MissingFields( + "[health_client]".to_string(), + ))? + .clone(); + + let response = client + .check(request) + .await + .change_context(HealthCheckError::ConnectionError( + "Failed to call dynamic routing service".to_string(), + ))? + .into_inner(); + + Ok(response) + } +} diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index b794443a047f..f6e1f0efc69f 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -37,7 +37,7 @@ v2 = ["customer_v2", "payment_methods_v2", "common_default", "api_models/v2", "d v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1", "common_utils/v1"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswitch_domain_models/customer_v2", "storage_impl/customer_v2"] payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2", "storage_impl/payment_methods_v2", "common_utils/payment_methods_v2"] -dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing"] +dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing", "api_models/dynamic_routing"] # Partial Auth # The feature reduces the overhead of the router authenticating the merchant for every request, and trusts on `x-merchant-id` header to be present in the request. diff --git a/crates/router/src/core/health_check.rs b/crates/router/src/core/health_check.rs index 83faee677d43..31e8cc75f5bd 100644 --- a/crates/router/src/core/health_check.rs +++ b/crates/router/src/core/health_check.rs @@ -1,5 +1,7 @@ #[cfg(feature = "olap")] use analytics::health_check::HealthCheck; +#[cfg(feature = "dynamic_routing")] +use api_models::health_check::HealthCheckMap; use api_models::health_check::HealthState; use error_stack::ResultExt; use router_env::logger; @@ -28,6 +30,11 @@ pub trait HealthCheckInterface { async fn health_check_opensearch( &self, ) -> CustomResult; + + #[cfg(feature = "dynamic_routing")] + async fn health_check_grpc( + &self, + ) -> CustomResult; } #[async_trait::async_trait] @@ -158,4 +165,20 @@ impl HealthCheckInterface for app::SessionState { logger::debug!("Outgoing request successful"); Ok(HealthState::Running) } + + #[cfg(feature = "dynamic_routing")] + async fn health_check_grpc( + &self, + ) -> CustomResult { + let health_client = &self.grpc_client.health_client; + let grpc_config = &self.conf.grpc_client; + + let health_check_map = health_client + .perform_health_check(grpc_config) + .await + .change_context(errors::HealthCheckGRPCServiceError::FailedToCallService)?; + + logger::debug!("Health check successful"); + Ok(health_check_map) + } } diff --git a/crates/router/src/routes/health.rs b/crates/router/src/routes/health.rs index c1cf28d00ad7..0f2e63336470 100644 --- a/crates/router/src/routes/health.rs +++ b/crates/router/src/routes/health.rs @@ -95,6 +95,19 @@ async fn deep_health_check_func( logger::debug!("Analytics health check end"); + logger::debug!("gRPC health check begin"); + + #[cfg(feature = "dynamic_routing")] + let grpc_health_check = state.health_check_grpc().await.map_err(|error| { + let message = error.to_string(); + error.change_context(errors::ApiErrorResponse::HealthCheckError { + component: "gRPC services", + message, + }) + })?; + + logger::debug!("gRPC health check end"); + logger::debug!("Opensearch health check begin"); #[cfg(feature = "olap")] @@ -129,6 +142,8 @@ async fn deep_health_check_func( #[cfg(feature = "olap")] opensearch: opensearch_status.into(), outgoing_request: outgoing_check.into(), + #[cfg(feature = "dynamic_routing")] + grpc_health_check, }; Ok(api::ApplicationResponse::Json(response)) diff --git a/crates/storage_impl/src/errors.rs b/crates/storage_impl/src/errors.rs index 1cee96f49ebd..10d7f4dc8e82 100644 --- a/crates/storage_impl/src/errors.rs +++ b/crates/storage_impl/src/errors.rs @@ -292,3 +292,9 @@ pub enum HealthCheckLockerError { #[error("Failed to establish Locker connection")] FailedToCallLocker, } + +#[derive(Debug, Clone, thiserror::Error)] +pub enum HealthCheckGRPCServiceError { + #[error("Failed to establish connection with gRPC service")] + FailedToCallService, +} diff --git a/proto/health_check.proto b/proto/health_check.proto new file mode 100644 index 000000000000..b246f59f6906 --- /dev/null +++ b/proto/health_check.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package grpc.health.v1; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + SERVICE_UNKNOWN = 3; // Used only by the Watch method. + } + ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} \ No newline at end of file From 75fe9c0c285f640967af33b1d969af9ce48c5b17 Mon Sep 17 00:00:00 2001 From: Kashif Date: Tue, 26 Nov 2024 18:57:12 +0530 Subject: [PATCH 18/51] feat(payments): propagate additional payment method data for google pay during MIT (#6644) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/api_models/src/payment_methods.rs | 33 ++++++++++++ .../src/payment_method_data.rs | 12 ++++- .../router/src/core/payment_methods/cards.rs | 7 +++ .../payments/operations/payment_create.rs | 54 ++++++++++--------- .../router/src/core/payments/tokenization.rs | 29 +++++++--- 5 files changed, 102 insertions(+), 33 deletions(-) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 0343ee02119b..0bb5e65213f3 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -820,7 +820,9 @@ pub struct PaymentMethodResponse { pub enum PaymentMethodsData { Card(CardDetailsPaymentMethod), BankDetails(PaymentMethodDataBankCreds), + WalletDetails(PaymentMethodDataWalletInfo), } + #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct CardDetailsPaymentMethod { pub last4_digits: Option, @@ -847,6 +849,37 @@ pub struct PaymentMethodDataBankCreds { pub connector_details: Vec, } +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct PaymentMethodDataWalletInfo { + /// Last 4 digits of the card number + pub last4: String, + /// The information of the payment method + pub card_network: String, + /// The type of payment method + #[serde(rename = "type")] + pub card_type: String, +} + +impl From for PaymentMethodDataWalletInfo { + fn from(item: payments::additional_info::WalletAdditionalDataForCard) -> Self { + Self { + last4: item.last4, + card_network: item.card_network, + card_type: item.card_type, + } + } +} + +impl From for payments::additional_info::WalletAdditionalDataForCard { + fn from(item: PaymentMethodDataWalletInfo) -> Self { + Self { + last4: item.last4, + card_network: item.card_network, + card_type: item.card_type, + } + } +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct BankAccountTokenData { pub payment_method_type: api_enums::PaymentMethodType, diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index 9e4d6cfbec5a..ea994946ca86 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -1,5 +1,5 @@ use api_models::{ - mandates, + mandates, payment_methods, payments::{additional_info as payment_additional_types, ExtendedCardInfo}, }; use common_enums::enums as api_enums; @@ -1708,3 +1708,13 @@ impl From for ExtendedCardInfo { } } } + +impl From for payment_methods::PaymentMethodDataWalletInfo { + fn from(item: GooglePayWalletData) -> Self { + Self { + last4: item.info.card_details, + card_network: item.info.card_network, + card_type: item.pm_type, + } + } +} diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 4073b7a4d9ba..955cc37b0d9c 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -5453,6 +5453,7 @@ pub async fn get_masked_bank_details( PaymentMethodsData::BankDetails(bank_details) => Ok(Some(MaskedBankDetails { mask: bank_details.mask, })), + PaymentMethodsData::WalletDetails(_) => Ok(None), }, None => Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable("Unable to fetch payment method data"), @@ -5492,6 +5493,12 @@ pub async fn get_bank_account_connector_details( message: "Card is not a valid entity".to_string(), } .into()), + PaymentMethodsData::WalletDetails(_) => { + Err(errors::ApiErrorResponse::UnprocessableEntity { + message: "Wallet is not a valid entity".to_string(), + } + .into()) + } PaymentMethodsData::BankDetails(bank_details) => { let connector_details = bank_details .connector_details diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index dcc2ba6a289a..697c06603da0 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1132,33 +1132,39 @@ impl PaymentCreate { if additional_pm_data.is_none() { // If recurring payment is made using payment_method_id, then fetch payment_method_data from retrieved payment_method object - additional_pm_data = payment_method_info - .as_ref() - .and_then(|pm_info| { - pm_info - .payment_method_data - .clone() - .map(|x| x.into_inner().expose()) - .and_then(|v| { - serde_json::from_value::(v) - .map_err(|err| { - logger::error!( - "Unable to deserialize payment methods data: {:?}", - err - ) + additional_pm_data = payment_method_info.as_ref().and_then(|pm_info| { + pm_info + .payment_method_data + .clone() + .map(|x| x.into_inner().expose()) + .and_then(|v| { + serde_json::from_value::(v) + .map_err(|err| { + logger::error!( + "Unable to deserialize payment methods data: {:?}", + err + ) + }) + .ok() + }) + .and_then(|pmd| match pmd { + PaymentMethodsData::Card(card) => { + Some(api_models::payments::AdditionalPaymentData::Card(Box::new( + api::CardDetailFromLocker::from(card).into(), + ))) + } + PaymentMethodsData::WalletDetails(wallet) => match payment_method_type { + Some(enums::PaymentMethodType::GooglePay) => { + Some(api_models::payments::AdditionalPaymentData::Wallet { + apple_pay: None, + google_pay: Some(wallet.into()), }) - .ok() - }) - .and_then(|pmd| match pmd { - PaymentMethodsData::Card(crd) => { - Some(api::CardDetailFromLocker::from(crd)) } _ => None, - }) - }) - .map(|card| { - api_models::payments::AdditionalPaymentData::Card(Box::new(card.into())) - }); + }, + _ => None, + }) + }); }; let additional_pm_data_value = additional_pm_data diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index a6f3018ed0dd..14f64cab1535 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -5,7 +5,9 @@ use std::collections::HashMap; not(feature = "payment_methods_v2") ))] use api_models::payment_methods::PaymentMethodsData; -use api_models::payments::ConnectorMandateReferenceId; +use api_models::{ + payment_methods::PaymentMethodDataWalletInfo, payments::ConnectorMandateReferenceId, +}; use common_enums::{ConnectorMandateStatus, PaymentMethod}; use common_utils::{ crypto::Encryptable, @@ -248,15 +250,26 @@ where None => None, }; - let pm_card_details = resp.card.as_ref().map(|card| { - PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())) - }); + let optional_pm_details = match ( + resp.card.as_ref(), + save_payment_method_data.request.get_payment_method_data(), + ) { + (Some(card), _) => Some(PaymentMethodsData::Card( + CardDetailsPaymentMethod::from(card.clone()), + )), + ( + _, + domain::PaymentMethodData::Wallet(domain::WalletData::GooglePay(googlepay)), + ) => Some(PaymentMethodsData::WalletDetails( + PaymentMethodDataWalletInfo::from(googlepay), + )), + _ => None, + }; + let key_manager_state = state.into(); let pm_data_encrypted: Option>> = - pm_card_details - .async_map(|pm_card| { - create_encrypted_data(&key_manager_state, key_store, pm_card) - }) + optional_pm_details + .async_map(|pm| create_encrypted_data(&key_manager_state, key_store, pm)) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) From 31204941ee24fe7b23344ba9b4a2615c46f33bb0 Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:22:16 +0530 Subject: [PATCH 19/51] feat(connector): [Netcetera] add sca exemption (#6611) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../connector/netcetera/netcetera_types.rs | 26 +++++++++++++++++++ .../src/connector/netcetera/transformers.rs | 14 ++-------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/crates/router/src/connector/netcetera/netcetera_types.rs b/crates/router/src/connector/netcetera/netcetera_types.rs index 2718fb3b238a..68a01b26fba8 100644 --- a/crates/router/src/connector/netcetera/netcetera_types.rs +++ b/crates/router/src/connector/netcetera/netcetera_types.rs @@ -1391,6 +1391,32 @@ impl From for Browser { } } +impl From> for ThreeDSRequestor { + fn from(value: Option) -> Self { + // if sca exemption is provided, we need to set the challenge indicator to NoChallengeRequestedTransactionalRiskAnalysis + let three_ds_requestor_challenge_ind = + if let Some(common_enums::ScaExemptionType::TransactionRiskAnalysis) = value { + Some(SingleOrListElement::Single( + ThreeDSRequestorChallengeIndicator::NoChallengeRequestedTransactionalRiskAnalysis, + )) + } else { + None + }; + + Self { + three_ds_requestor_authentication_ind: ThreeDSRequestorAuthenticationIndicator::Payment, + three_ds_requestor_authentication_info: None, + three_ds_requestor_challenge_ind, + three_ds_requestor_prior_authentication_info: None, + three_ds_requestor_dec_req_ind: None, + three_ds_requestor_dec_max_time: None, + app_ip: None, + three_ds_requestor_spc_support: None, + spc_incomp_ind: None, + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub enum ChallengeWindowSizeEnum { #[serde(rename = "01")] diff --git a/crates/router/src/connector/netcetera/transformers.rs b/crates/router/src/connector/netcetera/transformers.rs index adcfb17f14b3..ab228d95a9e5 100644 --- a/crates/router/src/connector/netcetera/transformers.rs +++ b/crates/router/src/connector/netcetera/transformers.rs @@ -456,18 +456,8 @@ impl TryFrom<&NetceteraRouterData<&types::authentication::ConnectorAuthenticatio let now = common_utils::date_time::now(); let request = item.router_data.request.clone(); let pre_authn_data = request.pre_authentication_data.clone(); - let three_ds_requestor = netcetera_types::ThreeDSRequestor { - three_ds_requestor_authentication_ind: - netcetera_types::ThreeDSRequestorAuthenticationIndicator::Payment, - three_ds_requestor_authentication_info: None, - three_ds_requestor_challenge_ind: None, - three_ds_requestor_prior_authentication_info: None, - three_ds_requestor_dec_req_ind: None, - three_ds_requestor_dec_max_time: None, - app_ip: None, - three_ds_requestor_spc_support: None, - spc_incomp_ind: None, - }; + let three_ds_requestor = + netcetera_types::ThreeDSRequestor::from(item.router_data.psd2_sca_exemption_type); let card = utils::get_card_details(request.payment_method_data, "netcetera")?; let cardholder_account = netcetera_types::CardholderAccount { acct_type: None, From 682947866e6afc197c71bbd255f22ae427704590 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:47:24 +0530 Subject: [PATCH 20/51] fix(core): add payment_id as query param in merchant return url (#6665) Co-authored-by: Chikke Srujan --- crates/router/src/core/payments/helpers.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index d4f5b3b49f0d..8435f09e8f3c 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -2948,11 +2948,13 @@ pub fn make_merchant_url_with_response( .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("Expected client secret to be `Some`")?; + let payment_id = redirection_response.payment_id.get_string_repr().to_owned(); let merchant_url_with_response = if business_profile.redirect_to_merchant_with_http_post { url::Url::parse_with_params( url, &[ ("status", status_check.to_string()), + ("payment_id", payment_id), ( "payment_intent_client_secret", payment_client_secret.peek().to_string(), @@ -2971,6 +2973,7 @@ pub fn make_merchant_url_with_response( url, &[ ("status", status_check.to_string()), + ("payment_id", payment_id), ( "payment_intent_client_secret", payment_client_secret.peek().to_string(), From d4b482c21cf57b022c7bbadc1a3a9c9d9e5d4f03 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 00:21:52 +0000 Subject: [PATCH 21/51] chore(version): 2024.11.27.0 --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6be9517667fd..eb6f79e96246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,34 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.11.27.0 + +### Features + +- **analytics:** Add `sessionized_metrics` for disputes analytics ([#6573](https://github.com/juspay/hyperswitch/pull/6573)) ([`8fbb766`](https://github.com/juspay/hyperswitch/commit/8fbb7663089d4790628109944e5fb5a57ccdaf00)) +- **connector:** + - [INESPAY] add Connector Template Code ([#6614](https://github.com/juspay/hyperswitch/pull/6614)) ([`710186f`](https://github.com/juspay/hyperswitch/commit/710186f035c92a919e8f5a49565c6f8908f1803f)) + - [Netcetera] add sca exemption ([#6611](https://github.com/juspay/hyperswitch/pull/6611)) ([`3120494`](https://github.com/juspay/hyperswitch/commit/31204941ee24fe7b23344ba9b4a2615c46f33bb0)) +- **payments:** Propagate additional payment method data for google pay during MIT ([#6644](https://github.com/juspay/hyperswitch/pull/6644)) ([`75fe9c0`](https://github.com/juspay/hyperswitch/commit/75fe9c0c285f640967af33b1d969af9ce48c5b17)) +- **router:** [Cybersource] add PLN to the currency config ([#6628](https://github.com/juspay/hyperswitch/pull/6628)) ([`29a0885`](https://github.com/juspay/hyperswitch/commit/29a0885a8fc7b718f8b87866e2638e8bfad3c8f3)) +- **users:** Send welcome to community email in magic link signup ([#6639](https://github.com/juspay/hyperswitch/pull/6639)) ([`03423a1`](https://github.com/juspay/hyperswitch/commit/03423a1f76d324453052da985f998fd3f957ce90)) +- Added grpc based health check ([#6441](https://github.com/juspay/hyperswitch/pull/6441)) ([`e922f96`](https://github.com/juspay/hyperswitch/commit/e922f96cee7e34493f0022b0c56455357eddc4f8)) + +### Bug Fixes + +- **core:** Add payment_id as query param in merchant return url ([#6665](https://github.com/juspay/hyperswitch/pull/6665)) ([`6829478`](https://github.com/juspay/hyperswitch/commit/682947866e6afc197c71bbd255f22ae427704590)) + +### Refactors + +- **authn:** Enable cookies in Integ ([#6599](https://github.com/juspay/hyperswitch/pull/6599)) ([`02479a1`](https://github.com/juspay/hyperswitch/commit/02479a12b18dc68e2787ae237580fcb46348374e)) +- **connector:** Add amount conversion framework to Riskified ([#6359](https://github.com/juspay/hyperswitch/pull/6359)) ([`acb30ef`](https://github.com/juspay/hyperswitch/commit/acb30ef6d144eaf13b237b830d1ac534259932a3)) +- **payments_v2:** Use batch encryption for intent create and confirm intent ([#6589](https://github.com/juspay/hyperswitch/pull/6589)) ([`108b160`](https://github.com/juspay/hyperswitch/commit/108b1603fa44b2a56c278196edb5a1f76f5d3d03)) +- **tenant:** Use tenant id type ([#6643](https://github.com/juspay/hyperswitch/pull/6643)) ([`c9df7b0`](https://github.com/juspay/hyperswitch/commit/c9df7b0557889c88ea20392dfe56bf651e22c9a7)) + +**Full Changelog:** [`2024.11.26.0...2024.11.27.0`](https://github.com/juspay/hyperswitch/compare/2024.11.26.0...2024.11.27.0) + +- - - + ## 2024.11.26.0 ### Features From f3424b7576554215945f61b52f38e43bb1e5a8b7 Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Wed, 27 Nov 2024 19:37:10 +0530 Subject: [PATCH 22/51] fix(users): Check lineage across entities in invite (#6677) --- crates/router/src/core/user.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index c6501dac3bd7..2087d01dbb4b 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -642,6 +642,38 @@ async fn handle_existing_user_invitation( return Err(UserErrors::UserExists.into()); } + let (org_id, merchant_id, profile_id) = match role_info.get_entity_type() { + EntityType::Organization => (Some(&user_from_token.org_id), None, None), + EntityType::Merchant => ( + Some(&user_from_token.org_id), + Some(&user_from_token.merchant_id), + None, + ), + EntityType::Profile => ( + Some(&user_from_token.org_id), + Some(&user_from_token.merchant_id), + Some(&user_from_token.profile_id), + ), + }; + + if state + .global_store + .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { + user_id: invitee_user_from_db.get_user_id(), + org_id, + merchant_id, + profile_id, + entity_id: None, + version: None, + status: None, + limit: Some(1), + }) + .await + .is_ok_and(|data| data.is_empty().not()) + { + return Err(UserErrors::UserExists.into()); + } + let user_role = domain::NewUserRole { user_id: invitee_user_from_db.get_user_id().to_owned(), role_id: request.role_id.clone(), From 4b45d21269437479435302aa1ea7d3d741e2a009 Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:35:53 +0530 Subject: [PATCH 23/51] refactor(core): add error handling wrapper to wehbook (#6636) --- crates/api_models/src/connector_enums.rs | 3 + .../src/connectors/cashtocode.rs | 3 +- .../src/connectors/worldline.rs | 3 +- .../src/connectors/zen.rs | 3 +- .../src/connectors/zsl.rs | 3 +- crates/hyperswitch_interfaces/src/webhooks.rs | 29 +++++- crates/router/src/connector/adyen.rs | 2 + crates/router/src/connector/adyenplatform.rs | 31 +++++-- crates/router/src/connector/braintree.rs | 2 + crates/router/src/core/webhooks/incoming.rs | 93 +++++++++++++++---- .../router/src/core/webhooks/incoming_v2.rs | 4 +- .../connector_integration_interface.rs | 8 +- 12 files changed, 151 insertions(+), 33 deletions(-) diff --git a/crates/api_models/src/connector_enums.rs b/crates/api_models/src/connector_enums.rs index 783ecb12b48d..3d027c026d7e 100644 --- a/crates/api_models/src/connector_enums.rs +++ b/crates/api_models/src/connector_enums.rs @@ -282,6 +282,9 @@ impl Connector { pub fn is_pre_processing_required_before_authorize(&self) -> bool { matches!(self, Self::Airwallex) } + pub fn should_acknowledge_webhook_for_resource_not_found_errors(&self) -> bool { + matches!(self, Self::Adyenplatform) + } #[cfg(feature = "dummy_connector")] pub fn validate_dummy_connector_enabled( &self, diff --git a/crates/hyperswitch_connectors/src/connectors/cashtocode.rs b/crates/hyperswitch_connectors/src/connectors/cashtocode.rs index c72b63aebc05..1ee6f10c6350 100644 --- a/crates/hyperswitch_connectors/src/connectors/cashtocode.rs +++ b/crates/hyperswitch_connectors/src/connectors/cashtocode.rs @@ -30,7 +30,7 @@ use hyperswitch_interfaces::{ errors, events::connector_api_logs::ConnectorEvent, types::{PaymentsAuthorizeType, Response}, - webhooks, + webhooks::{self, IncomingWebhookFlowError}, }; use masking::{Mask, PeekInterface, Secret}; use transformers as cashtocode; @@ -420,6 +420,7 @@ impl webhooks::IncomingWebhook for Cashtocode { fn get_webhook_api_response( &self, request: &webhooks::IncomingWebhookRequestDetails<'_>, + _error_kind: Option, ) -> CustomResult, errors::ConnectorError> { let status = "EXECUTED".to_string(); let obj: transformers::CashtocodePaymentsSyncResponse = request diff --git a/crates/hyperswitch_connectors/src/connectors/worldline.rs b/crates/hyperswitch_connectors/src/connectors/worldline.rs index c5a8466175c1..e40d48172243 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldline.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldline.rs @@ -41,7 +41,7 @@ use hyperswitch_interfaces::{ PaymentsAuthorizeType, PaymentsCaptureType, PaymentsSyncType, PaymentsVoidType, RefundExecuteType, RefundSyncType, Response, }, - webhooks, + webhooks::{self, IncomingWebhookFlowError}, }; use masking::{ExposeInterface, Mask, PeekInterface}; use ring::hmac; @@ -814,6 +814,7 @@ impl webhooks::IncomingWebhook for Worldline { fn get_webhook_api_response( &self, request: &webhooks::IncomingWebhookRequestDetails<'_>, + _error_kind: Option, ) -> CustomResult< hyperswitch_domain_models::api::ApplicationResponse, errors::ConnectorError, diff --git a/crates/hyperswitch_connectors/src/connectors/zen.rs b/crates/hyperswitch_connectors/src/connectors/zen.rs index a90b10fbd866..5dc9e4d3176d 100644 --- a/crates/hyperswitch_connectors/src/connectors/zen.rs +++ b/crates/hyperswitch_connectors/src/connectors/zen.rs @@ -41,7 +41,7 @@ use hyperswitch_interfaces::{ errors, events::connector_api_logs::ConnectorEvent, types::{PaymentsAuthorizeType, PaymentsSyncType, RefundExecuteType, RefundSyncType, Response}, - webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, + webhooks::{IncomingWebhook, IncomingWebhookFlowError, IncomingWebhookRequestDetails}, }; use masking::{Mask, PeekInterface, Secret}; use transformers::{self as zen, ZenPaymentStatus, ZenWebhookTxnType}; @@ -671,6 +671,7 @@ impl IncomingWebhook for Zen { fn get_webhook_api_response( &self, _request: &IncomingWebhookRequestDetails<'_>, + _error_kind: Option, ) -> CustomResult, errors::ConnectorError> { Ok(ApplicationResponse::Json(serde_json::json!({ "status": "ok" diff --git a/crates/hyperswitch_connectors/src/connectors/zsl.rs b/crates/hyperswitch_connectors/src/connectors/zsl.rs index 0a833a1f84ad..13007b72601e 100644 --- a/crates/hyperswitch_connectors/src/connectors/zsl.rs +++ b/crates/hyperswitch_connectors/src/connectors/zsl.rs @@ -36,7 +36,7 @@ use hyperswitch_interfaces::{ errors, events::connector_api_logs::ConnectorEvent, types::{self, Response}, - webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, + webhooks::{IncomingWebhook, IncomingWebhookFlowError, IncomingWebhookRequestDetails}, }; use masking::{ExposeInterface, Secret}; use transformers::{self as zsl, get_status}; @@ -442,6 +442,7 @@ impl IncomingWebhook for Zsl { fn get_webhook_api_response( &self, _request: &IncomingWebhookRequestDetails<'_>, + _error_kind: Option, ) -> CustomResult, errors::ConnectorError> { Ok(ApplicationResponse::TextPlain("CALLBACK-OK".to_string())) } diff --git a/crates/hyperswitch_interfaces/src/webhooks.rs b/crates/hyperswitch_interfaces/src/webhooks.rs index f5240aed9ca5..9a1a3f997932 100644 --- a/crates/hyperswitch_interfaces/src/webhooks.rs +++ b/crates/hyperswitch_interfaces/src/webhooks.rs @@ -2,7 +2,9 @@ use common_utils::{crypto, errors::CustomResult, ext_traits::ValueExt}; use error_stack::ResultExt; -use hyperswitch_domain_models::api::ApplicationResponse; +use hyperswitch_domain_models::{ + api::ApplicationResponse, errors::api_error_response::ApiErrorResponse, +}; use masking::{ExposeInterface, Secret}; use crate::{api::ConnectorCommon, errors}; @@ -22,6 +24,30 @@ pub struct IncomingWebhookRequestDetails<'a> { pub query_params: String, } +/// IncomingWebhookFlowError enum defining the error type for incoming webhook +#[derive(Debug)] +pub enum IncomingWebhookFlowError { + /// Resource not found for the webhook + ResourceNotFound, + /// Internal error for the webhook + InternalError, +} + +impl From<&ApiErrorResponse> for IncomingWebhookFlowError { + fn from(api_error_response: &ApiErrorResponse) -> Self { + match api_error_response { + ApiErrorResponse::WebhookResourceNotFound + | ApiErrorResponse::DisputeNotFound { .. } + | ApiErrorResponse::PayoutNotFound + | ApiErrorResponse::MandateNotFound + | ApiErrorResponse::PaymentNotFound + | ApiErrorResponse::RefundNotFound + | ApiErrorResponse::AuthenticationNotFound { .. } => Self::ResourceNotFound, + _ => Self::InternalError, + } + } +} + /// Trait defining incoming webhook #[async_trait::async_trait] pub trait IncomingWebhook: ConnectorCommon + Sync { @@ -203,6 +229,7 @@ pub trait IncomingWebhook: ConnectorCommon + Sync { fn get_webhook_api_response( &self, _request: &IncomingWebhookRequestDetails<'_>, + _error_kind: Option, ) -> CustomResult, errors::ConnectorError> { Ok(ApplicationResponse::StatusOk) } diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index c9ccd8a9c629..f3b7414a923a 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -7,6 +7,7 @@ use common_utils::{ }; use diesel_models::{enums as storage_enums, enums}; use error_stack::{report, ResultExt}; +use hyperswitch_interfaces::webhooks::IncomingWebhookFlowError; use masking::{ExposeInterface, Secret}; use ring::hmac; use router_env::{instrument, tracing}; @@ -1880,6 +1881,7 @@ impl api::IncomingWebhook for Adyen { fn get_webhook_api_response( &self, _request: &api::IncomingWebhookRequestDetails<'_>, + _error_kind: Option, ) -> CustomResult, errors::ConnectorError> { Ok(services::api::ApplicationResponse::TextPlain( diff --git a/crates/router/src/connector/adyenplatform.rs b/crates/router/src/connector/adyenplatform.rs index 3da1a2d33be2..2ee80699a5a0 100644 --- a/crates/router/src/connector/adyenplatform.rs +++ b/crates/router/src/connector/adyenplatform.rs @@ -13,6 +13,8 @@ use error_stack::report; use error_stack::ResultExt; #[cfg(feature = "payouts")] use http::HeaderName; +use hyperswitch_interfaces::webhooks::IncomingWebhookFlowError; +use masking::Maskable; #[cfg(feature = "payouts")] use masking::Secret; #[cfg(feature = "payouts")] @@ -27,11 +29,7 @@ use crate::{ configs::settings, core::errors::{self, CustomResult}, headers, - services::{ - self, - request::{self, Mask}, - ConnectorValidation, - }, + services::{self, request::Mask, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon}, @@ -67,7 +65,7 @@ impl ConnectorCommon for Adyenplatform { fn get_auth_header( &self, auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { let auth = adyenplatform::AdyenplatformAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -209,7 +207,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), types::PayoutFulfillType::get_content_type(self) @@ -401,6 +399,25 @@ impl api::IncomingWebhook for Adyenplatform { } } + fn get_webhook_api_response( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + error_kind: Option, + ) -> CustomResult, errors::ConnectorError> + { + if error_kind.is_some() { + Ok(services::api::ApplicationResponse::JsonWithHeaders(( + serde_json::Value::Null, + vec![( + "x-http-code".to_string(), + Maskable::Masked(Secret::new("404".to_string())), + )], + ))) + } else { + Ok(services::api::ApplicationResponse::StatusOk) + } + } + fn get_webhook_event_type( &self, #[cfg(feature = "payouts")] request: &api::IncomingWebhookRequestDetails<'_>, diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs index dba261218801..40155f7117f5 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/router/src/connector/braintree.rs @@ -10,6 +10,7 @@ use common_utils::{ }; use diesel_models::enums; use error_stack::{report, Report, ResultExt}; +use hyperswitch_interfaces::webhooks::IncomingWebhookFlowError; use masking::{ExposeInterface, PeekInterface, Secret}; use ring::hmac; use sha1::{Digest, Sha1}; @@ -980,6 +981,7 @@ impl api::IncomingWebhook for Braintree { fn get_webhook_api_response( &self, _request: &api::IncomingWebhookRequestDetails<'_>, + _error_kind: Option, ) -> CustomResult, errors::ConnectorError> { Ok(services::api::ApplicationResponse::TextPlain( diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index 3532f1e3fd72..5eb489da788e 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -12,7 +12,7 @@ use hyperswitch_domain_models::{ router_request_types::VerifyWebhookSourceRequestData, router_response_types::{VerifyWebhookSourceResponseData, VerifyWebhookStatus}, }; -use hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails; +use hyperswitch_interfaces::webhooks::{IncomingWebhookFlowError, IncomingWebhookRequestDetails}; use masking::{ExposeInterface, PeekInterface}; use router_env::{instrument, metrics::add_attributes, tracing, tracing_actix_web::RequestId}; @@ -209,7 +209,7 @@ async fn incoming_webhooks_core( ); let response = connector - .get_webhook_api_response(&request_details) + .get_webhook_api_response(&request_details, None) .switch() .attach_printable("Failed while early return in case of event type parsing")?; @@ -260,14 +260,25 @@ async fn incoming_webhooks_core( let merchant_connector_account = match merchant_connector_account { Some(merchant_connector_account) => merchant_connector_account, None => { - Box::pin(helper_utils::get_mca_from_object_reference_id( + match Box::pin(helper_utils::get_mca_from_object_reference_id( &state, object_ref_id.clone(), &merchant_account, &connector_name, &key_store, )) - .await? + .await + { + Ok(mca) => mca, + Err(error) => { + return handle_incoming_webhook_error( + error, + &connector, + connector_name.as_str(), + &request_details, + ); + } + } } }; @@ -358,7 +369,7 @@ async fn incoming_webhooks_core( id: profile_id.get_string_repr().to_owned(), })?; - match flow_type { + let result_response = match flow_type { api::WebhookFlow::Payment => Box::pin(payments_incoming_webhook_flow( state.clone(), req_state, @@ -372,7 +383,7 @@ async fn incoming_webhooks_core( event_type, )) .await - .attach_printable("Incoming webhook flow for payments failed")?, + .attach_printable("Incoming webhook flow for payments failed"), api::WebhookFlow::Refund => Box::pin(refunds_incoming_webhook_flow( state.clone(), @@ -385,7 +396,7 @@ async fn incoming_webhooks_core( event_type, )) .await - .attach_printable("Incoming webhook flow for refunds failed")?, + .attach_printable("Incoming webhook flow for refunds failed"), api::WebhookFlow::Dispute => Box::pin(disputes_incoming_webhook_flow( state.clone(), @@ -399,7 +410,7 @@ async fn incoming_webhooks_core( event_type, )) .await - .attach_printable("Incoming webhook flow for disputes failed")?, + .attach_printable("Incoming webhook flow for disputes failed"), api::WebhookFlow::BankTransfer => Box::pin(bank_transfer_webhook_flow( state.clone(), @@ -411,9 +422,9 @@ async fn incoming_webhooks_core( source_verified, )) .await - .attach_printable("Incoming bank-transfer webhook flow failed")?, + .attach_printable("Incoming bank-transfer webhook flow failed"), - api::WebhookFlow::ReturnResponse => WebhookResponseTracker::NoEffect, + api::WebhookFlow::ReturnResponse => Ok(WebhookResponseTracker::NoEffect), api::WebhookFlow::Mandate => Box::pin(mandates_incoming_webhook_flow( state.clone(), @@ -425,7 +436,7 @@ async fn incoming_webhooks_core( event_type, )) .await - .attach_printable("Incoming webhook flow for mandates failed")?, + .attach_printable("Incoming webhook flow for mandates failed"), api::WebhookFlow::ExternalAuthentication => { Box::pin(external_authentication_incoming_webhook_flow( @@ -442,7 +453,7 @@ async fn incoming_webhooks_core( merchant_connector_account, )) .await - .attach_printable("Incoming webhook flow for external authentication failed")? + .attach_printable("Incoming webhook flow for external authentication failed") } api::WebhookFlow::FraudCheck => Box::pin(frm_incoming_webhook_flow( state.clone(), @@ -455,7 +466,7 @@ async fn incoming_webhooks_core( business_profile, )) .await - .attach_printable("Incoming webhook flow for fraud check failed")?, + .attach_printable("Incoming webhook flow for fraud check failed"), #[cfg(feature = "payouts")] api::WebhookFlow::Payout => Box::pin(payouts_incoming_webhook_flow( @@ -468,10 +479,22 @@ async fn incoming_webhooks_core( source_verified, )) .await - .attach_printable("Incoming webhook flow for payouts failed")?, + .attach_printable("Incoming webhook flow for payouts failed"), _ => Err(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unsupported Flow Type received in incoming webhooks")?, + .attach_printable("Unsupported Flow Type received in incoming webhooks"), + }; + + match result_response { + Ok(response) => response, + Err(error) => { + return handle_incoming_webhook_error( + error, + &connector, + connector_name.as_str(), + &request_details, + ); + } } } else { metrics::WEBHOOK_INCOMING_FILTERED_COUNT.add( @@ -486,7 +509,7 @@ async fn incoming_webhooks_core( }; let response = connector - .get_webhook_api_response(&request_details) + .get_webhook_api_response(&request_details, None) .switch() .attach_printable("Could not get incoming webhook api response from connector")?; @@ -497,6 +520,44 @@ async fn incoming_webhooks_core( Ok((response, webhook_effect, serialized_request)) } +fn handle_incoming_webhook_error( + error: error_stack::Report, + connector: &ConnectorEnum, + connector_name: &str, + request_details: &IncomingWebhookRequestDetails<'_>, +) -> errors::RouterResult<( + services::ApplicationResponse, + WebhookResponseTracker, + serde_json::Value, +)> { + logger::error!(?error, "Incoming webhook flow failed"); + + // fetch the connector enum from the connector name + let connector_enum = api_models::connector_enums::Connector::from_str(connector_name) + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "connector", + }) + .attach_printable_lazy(|| format!("unable to parse connector name {connector_name:?}"))?; + + // get the error response from the connector + if connector_enum.should_acknowledge_webhook_for_resource_not_found_errors() { + let response = connector + .get_webhook_api_response( + request_details, + Some(IncomingWebhookFlowError::from(error.current_context())), + ) + .switch() + .attach_printable("Failed to get incoming webhook api response from connector")?; + Ok(( + response, + WebhookResponseTracker::NoEffect, + serde_json::Value::Null, + )) + } else { + Err(error) + } +} + #[allow(clippy::too_many_arguments)] #[instrument(skip_all)] async fn payments_incoming_webhook_flow( diff --git a/crates/router/src/core/webhooks/incoming_v2.rs b/crates/router/src/core/webhooks/incoming_v2.rs index 0deb91efaa8d..c1734794fcc7 100644 --- a/crates/router/src/core/webhooks/incoming_v2.rs +++ b/crates/router/src/core/webhooks/incoming_v2.rs @@ -192,7 +192,7 @@ async fn incoming_webhooks_core( ); let response = connector - .get_webhook_api_response(&request_details) + .get_webhook_api_response(&request_details, None) .switch() .attach_printable("Failed while early return in case of event type parsing")?; @@ -367,7 +367,7 @@ async fn incoming_webhooks_core( }; let response = connector - .get_webhook_api_response(&request_details) + .get_webhook_api_response(&request_details, None) .switch() .attach_printable("Could not get incoming webhook api response from connector")?; diff --git a/crates/router/src/services/connector_integration_interface.rs b/crates/router/src/services/connector_integration_interface.rs index 53690227f592..ccbf65917f1a 100644 --- a/crates/router/src/services/connector_integration_interface.rs +++ b/crates/router/src/services/connector_integration_interface.rs @@ -1,7 +1,8 @@ use common_utils::{crypto, errors::CustomResult, request::Request}; use hyperswitch_domain_models::{router_data::RouterData, router_data_v2::RouterDataV2}; use hyperswitch_interfaces::{ - authentication::ExternalAuthenticationPayload, connector_integration_v2::ConnectorIntegrationV2, + authentication::ExternalAuthenticationPayload, + connector_integration_v2::ConnectorIntegrationV2, webhooks::IncomingWebhookFlowError, }; use super::{BoxedConnectorIntegrationV2, ConnectorValidation}; @@ -279,11 +280,12 @@ impl api::IncomingWebhook for ConnectorEnum { fn get_webhook_api_response( &self, request: &IncomingWebhookRequestDetails<'_>, + error_kind: Option, ) -> CustomResult, errors::ConnectorError> { match self { - Self::Old(connector) => connector.get_webhook_api_response(request), - Self::New(connector) => connector.get_webhook_api_response(request), + Self::Old(connector) => connector.get_webhook_api_response(request, error_kind), + Self::New(connector) => connector.get_webhook_api_response(request, error_kind), } } From b1c4e30e929fb8d31d854765d5f1ddad5e77f065 Mon Sep 17 00:00:00 2001 From: likhinbopanna <131246334+likhinbopanna@users.noreply.github.com> Date: Wed, 27 Nov 2024 23:48:13 +0530 Subject: [PATCH 24/51] ci(cypress): Move MIT requests to configs and add Paybox Mandates (#6650) --- .../e2e/PaymentTest/00009-RefundPayment.cy.js | 14 + .../00011-CreateSingleuseMandate.cy.js | 21 + .../00012-CreateMultiuseMandate.cy.js | 35 ++ .../00013-ListAndRevokeMandate.cy.js | 14 + .../PaymentTest/00015-ZeroAuthMandate.cy.js | 21 + .../PaymentTest/00019-MandatesUsingPMID.cy.js | 63 +++ .../PaymentTest/00020-MandatesUsingNTID.cy.js | 63 +++ .../e2e/PaymentTest/00022-Variations.cy.js | 37 +- .../cypress/e2e/PaymentUtils/Adyen.js | 18 + .../cypress/e2e/PaymentUtils/BankOfAmerica.js | 18 + .../cypress/e2e/PaymentUtils/Cybersource.js | 18 + .../cypress/e2e/PaymentUtils/Fiuu.js | 22 + .../cypress/e2e/PaymentUtils/Noon.js | 18 + .../cypress/e2e/PaymentUtils/Paybox.js | 531 +++++++++++++++++- .../cypress/e2e/PaymentUtils/Stripe.js | 18 + .../cypress/e2e/PaymentUtils/WellsFargo.js | 18 + cypress-tests/cypress/support/commands.js | 102 +++- 17 files changed, 988 insertions(+), 43 deletions(-) diff --git a/cypress-tests/cypress/e2e/PaymentTest/00009-RefundPayment.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00009-RefundPayment.cy.js index 9eb64c0acf23..4eef1ac87371 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00009-RefundPayment.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00009-RefundPayment.cy.js @@ -815,8 +815,15 @@ describe("Card - Refund flow - No 3DS", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -825,8 +832,15 @@ describe("Card - Refund flow - No 3DS", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 7000, true, "automatic", diff --git a/cypress-tests/cypress/e2e/PaymentTest/00011-CreateSingleuseMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00011-CreateSingleuseMandate.cy.js index 5c1409138263..bace2a780183 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00011-CreateSingleuseMandate.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00011-CreateSingleuseMandate.cy.js @@ -49,8 +49,15 @@ describe("Card - SingleUse Mandates flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -112,8 +119,15 @@ describe("Card - SingleUse Mandates flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 6500, true, "manual", @@ -197,8 +211,15 @@ describe("Card - SingleUse Mandates flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 7000, true, "automatic", diff --git a/cypress-tests/cypress/e2e/PaymentTest/00012-CreateMultiuseMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00012-CreateMultiuseMandate.cy.js index 3596af70fe1b..312c74c23c08 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00012-CreateMultiuseMandate.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00012-CreateMultiuseMandate.cy.js @@ -49,8 +49,15 @@ describe("Card - MultiUse Mandates flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -58,8 +65,15 @@ describe("Card - MultiUse Mandates flow test", () => { ); }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -121,8 +135,15 @@ describe("Card - MultiUse Mandates flow test", () => { }); it("Confirm No 3DS MIT 1", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 6500, true, "manual", @@ -149,8 +170,15 @@ describe("Card - MultiUse Mandates flow test", () => { }); it("Confirm No 3DS MIT 2", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 6500, true, "manual", @@ -230,8 +258,15 @@ describe("Card - MultiUse Mandates flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 6500, true, "automatic", diff --git a/cypress-tests/cypress/e2e/PaymentTest/00013-ListAndRevokeMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00013-ListAndRevokeMandate.cy.js index f341db19f6cc..cad829ba3107 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00013-ListAndRevokeMandate.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00013-ListAndRevokeMandate.cy.js @@ -49,8 +49,15 @@ describe("Card - List and revoke Mandates flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -105,8 +112,15 @@ describe("Card - List and revoke Mandates flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "MITAutoCapture" + ]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 7000, true, "automatic", diff --git a/cypress-tests/cypress/e2e/PaymentTest/00015-ZeroAuthMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00015-ZeroAuthMandate.cy.js index b90831427276..15e7597286a3 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00015-ZeroAuthMandate.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00015-ZeroAuthMandate.cy.js @@ -47,8 +47,15 @@ describe("Card - SingleUse Mandates flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -89,8 +96,15 @@ describe("Card - SingleUse Mandates flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -98,8 +112,15 @@ describe("Card - SingleUse Mandates flow test", () => { ); }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 7000, true, "automatic", diff --git a/cypress-tests/cypress/e2e/PaymentTest/00019-MandatesUsingPMID.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00019-MandatesUsingPMID.cy.js index b2951e375862..401f662c67e2 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00019-MandatesUsingPMID.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00019-MandatesUsingPMID.cy.js @@ -67,8 +67,15 @@ describe("Card - Mandates using Payment Method Id flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingPMId( fixtures.pmIdConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -148,8 +155,15 @@ describe("Card - Mandates using Payment Method Id flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingPMId( fixtures.pmIdConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -193,8 +207,15 @@ describe("Card - Mandates using Payment Method Id flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingPMId( fixtures.pmIdConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -202,8 +223,15 @@ describe("Card - Mandates using Payment Method Id flow test", () => { ); }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingPMId( fixtures.pmIdConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -265,8 +293,15 @@ describe("Card - Mandates using Payment Method Id flow test", () => { }); it("Confirm No 3DS MIT 1", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingPMId( fixtures.pmIdConfirmBody, + req_data, + res_data, 6500, true, "manual", @@ -293,8 +328,15 @@ describe("Card - Mandates using Payment Method Id flow test", () => { }); it("Confirm No 3DS MIT 2", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingPMId( fixtures.pmIdConfirmBody, + req_data, + res_data, 6500, true, "manual", @@ -361,8 +403,15 @@ describe("Card - Mandates using Payment Method Id flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingPMId( fixtures.pmIdConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -370,8 +419,15 @@ describe("Card - Mandates using Payment Method Id flow test", () => { ); }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingPMId( fixtures.pmIdConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -438,8 +494,15 @@ describe("Card - Mandates using Payment Method Id flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingPMId( fixtures.pmIdConfirmBody, + req_data, + res_data, 7000, true, "automatic", diff --git a/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTID.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTID.cy.js index edd46f7f8348..dcccfd390bf1 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTID.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTID.cy.js @@ -29,8 +29,15 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingNTID( fixtures.ntidConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -52,8 +59,15 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingNTID( fixtures.ntidConfirmBody, + req_data, + res_data, 7000, true, "manual", @@ -75,8 +89,15 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingNTID( fixtures.ntidConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -84,8 +105,15 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { ); }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingNTID( fixtures.ntidConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -107,8 +135,15 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { }); it("Confirm No 3DS MIT 1", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingNTID( fixtures.ntidConfirmBody, + req_data, + res_data, 6500, true, "manual", @@ -135,8 +170,15 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { }); it("Confirm No 3DS MIT 2", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingNTID( fixtures.ntidConfirmBody, + req_data, + res_data, 6500, true, "manual", @@ -176,8 +218,15 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingNTID( fixtures.ntidConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -185,8 +234,15 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { ); }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingNTID( fixtures.ntidConfirmBody, + req_data, + res_data, 7000, true, "automatic", @@ -208,8 +264,15 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitUsingNTID( fixtures.ntidConfirmBody, + req_data, + res_data, 7000, true, "automatic", diff --git a/cypress-tests/cypress/e2e/PaymentTest/00022-Variations.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00022-Variations.cy.js index b24b9f10f791..035b7da97abc 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00022-Variations.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00022-Variations.cy.js @@ -463,9 +463,15 @@ describe("Corner cases", () => { let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ "Void" ]; - let commonData = getConnectorDetails(globalState.get("commons"))["card_pm"]["Void"]; + let commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["Void"]; let req_data = data["Request"]; - let res_data = utils.getConnectorFlowDetails(data, commonData, "ResponseCustom"); + let res_data = utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ); cy.voidCallTest(fixtures.voidBody, req_data, res_data, globalState); if (should_continue) @@ -595,9 +601,15 @@ describe("Corner cases", () => { let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ "Refund" ]; - let commonData = getConnectorDetails(globalState.get("commons"))["card_pm"]["Refund"]; + let commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["Refund"]; let req_data = data["Request"]; - let res_data = utils.getConnectorFlowDetails(data, commonData, "ResponseCustom"); + let res_data = utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ); cy.refundCallTest( fixtures.refundBody, req_data, @@ -658,9 +670,15 @@ describe("Corner cases", () => { let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ "Refund" ]; - let commonData = getConnectorDetails(globalState.get("commons"))["card_pm"]["Refund"]; + let commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["Refund"]; let req_data = data["Request"]; - let res_data = utils.getConnectorFlowDetails(data, commonData, "ResponseCustom"); + let res_data = utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ); cy.refundCallTest( fixtures.refundBody, req_data, @@ -734,8 +752,15 @@ describe("Corner cases", () => { }); it("Confirm No 3DS MIT", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "MITAutoCapture" + ]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + req_data, + res_data, 65000, true, "manual", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js b/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js index 9ce384c7a744..81525041147d 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js @@ -398,6 +398,24 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js b/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js index 4a443e0962a2..af4dee5d5377 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js @@ -395,6 +395,24 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js b/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js index ca24a50bcef3..72dc8f6479b8 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js @@ -458,6 +458,24 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js index 4a910c7897c1..d82a5f128d68 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js @@ -360,6 +360,28 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "failed", + error_code: "The currency not allow for the RecordType", + error_message: "The currency not allow for the RecordType", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "failed", + error_code: "The currency not allow for the RecordType", + error_message: "The currency not allow for the RecordType", + }, + }, + }, PaymentMethodIdMandateNo3DSAutoCapture: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Noon.js b/cypress-tests/cypress/e2e/PaymentUtils/Noon.js index 70c056b6c92e..38b0c0c1117a 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Noon.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Noon.js @@ -468,6 +468,24 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { Response: { status: 501, diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js b/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js index d1eb91692daa..70bfa5c32404 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js @@ -6,6 +6,57 @@ const successfulNo3DSCardDetails = { card_cvc: "222", }; +const successfulThreeDSTestCardDetails = { + card_number: "4000000000001091", + card_exp_month: "01", + card_exp_year: "25", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; + +const singleUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + single_use: { + amount: 7000, + currency: "EUR", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + multi_use: { + amount: 6500, + currency: "EUR", + }, + }, +}; + +const customerAcceptance = { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, +}; + export const connectorDetails = { card_pm: { PaymentIntent: { @@ -21,6 +72,22 @@ export const connectorDetails = { }, }, }, + PaymentIntentOffSession: { + Request: { + currency: "EUR", + amount: 6500, + authentication_type: "no_three_ds", + customer_acceptance: null, + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, No3DSManualCapture: { Request: { currency: "EUR", @@ -52,7 +119,43 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "succeeded", + }, + }, + }, + "3DSManualCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + setup_future_usage: "on_session", + }, + }, + }, + "3DSAutoCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + setup_future_usage: "on_session", }, }, }, @@ -67,10 +170,10 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "succeeded", amount: 6500, - amount_capturable: 6500, - amount_received: null, + amount_capturable: 0, + amount_received: 6500, }, }, }, @@ -79,10 +182,24 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "partially_captured", amount: 6500, - amount_capturable: 6500, - amount_received: null, + amount_capturable: 0, + amount_received: 100, + }, + }, + }, + VoidAfterConfirm: { + Request: {}, + Response: { + status: 501, + body: { + status: "cancelled", + error: { + type: "invalid_request", + message: "Cancel/Void flow is not implemented", + code: "IR_00", + }, }, }, }, @@ -131,7 +248,405 @@ export const connectorDetails = { }, }, }, - + MandateSingleUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + reason: + "Capture Not allowed in case of Creating the Subscriber is not supported by Paybox", + }, + }, + }, + }, + MandateSingleUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + reason: + "Capture Not allowed in case of Creating the Subscriber is not supported by Paybox", + }, + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + reason: + "Capture Not allowed in case of Creating the Subscriber is not supported by Paybox", + }, + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + reason: + "Capture Not allowed in case of Creating the Subscriber is not supported by Paybox", + }, + }, + }, + }, + MandateMultiUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MITAutoCapture: { + Request: { + currency: "EUR", + amount: 6500, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: { + currency: "EUR", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + reason: + "Capture Not allowed in case of Creating the Subscriber is not supported by Paybox", + }, + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandate3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + reason: + "Capture Not allowed in case of Creating the Subscriber is not supported by Paybox", + }, + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + ZeroAuthMandate: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "EUR", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + reason: + "Capture Not allowed in case of Creating the Subscriber is not supported by Paybox", + }, + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, InvalidCardNumber: { Request: { currency: "EUR", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js b/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js index 98af56b882c6..984fe63b2fbe 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js @@ -448,6 +448,24 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js b/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js index b25a6dc7e8bf..608343f1083d 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js @@ -362,6 +362,24 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 48a62fb4388b..17c2270efef6 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -1926,7 +1926,18 @@ Cypress.Commands.add( Cypress.Commands.add( "mitForMandatesCallTest", - (requestBody, amount, confirm, capture_method, globalState) => { + ( + requestBody, + req_data, + res_data, + amount, + confirm, + capture_method, + globalState + ) => { + for (const key in req_data) { + requestBody[key] = req_data[key]; + } requestBody.amount = amount; requestBody.confirm = confirm; requestBody.capture_method = capture_method; @@ -1966,10 +1977,13 @@ Cypress.Commands.add( .to.have.property("redirect_to_url"); const nextActionUrl = response.body.next_action.redirect_to_url; cy.log(nextActionUrl); + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); + } } else if (response.body.authentication_type === "no_three_ds") { - if (response.body.connector === "fiuu") { - expect(response.body.status).to.equal("failed"); - } + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); + } } else { throw new Error( `Invalid authentication type ${response.body.authentication_type}` @@ -1982,11 +1996,12 @@ Cypress.Commands.add( .to.have.property("redirect_to_url"); const nextActionUrl = response.body.next_action.redirect_to_url; cy.log(nextActionUrl); + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); + } } else if (response.body.authentication_type === "no_three_ds") { - if (response.body.connector === "fiuu") { - expect(response.body.status).to.equal("failed"); - } else { - expect(response.body.status).to.equal("requires_capture"); + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); } } else { throw new Error( @@ -2009,9 +2024,7 @@ Cypress.Commands.add( ); } } else { - throw new Error( - `Error Response: ${response.status}\n${response.body.error.message}\n${response.body.error.code}` - ); + defaultErrorHandler(response, res_data); } }); } @@ -2019,7 +2032,18 @@ Cypress.Commands.add( Cypress.Commands.add( "mitUsingPMId", - (requestBody, amount, confirm, capture_method, globalState) => { + ( + requestBody, + req_data, + res_data, + amount, + confirm, + capture_method, + globalState + ) => { + for (const key in req_data) { + requestBody[key] = req_data[key]; + } requestBody.amount = amount; requestBody.confirm = confirm; requestBody.capture_method = capture_method; @@ -2046,10 +2070,13 @@ Cypress.Commands.add( .to.have.property("redirect_to_url"); const nextActionUrl = response.body.next_action.redirect_to_url; cy.log(nextActionUrl); + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); + } } else if (response.body.authentication_type === "no_three_ds") { - if (response.body.connector === "fiuu") { - expect(response.body.status).to.equal("failed"); - } + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); + } } else { throw new Error( `Invalid authentication type ${response.body.authentication_type}` @@ -2062,11 +2089,12 @@ Cypress.Commands.add( .to.have.property("redirect_to_url"); const nextActionUrl = response.body.next_action.redirect_to_url; cy.log(nextActionUrl); + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); + } } else if (response.body.authentication_type === "no_three_ds") { - if (response.body.connector === "fiuu") { - expect(response.body.status).to.equal("failed"); - } else { - expect(response.body.status).to.equal("requires_capture"); + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); } } else { throw new Error( @@ -2079,9 +2107,7 @@ Cypress.Commands.add( ); } } else { - throw new Error( - `Error Response: ${response.status}\n${response.body.error.message}\n${response.body.error.code}` - ); + defaultErrorHandler(response, res_data); } }); } @@ -2089,8 +2115,18 @@ Cypress.Commands.add( Cypress.Commands.add( "mitUsingNTID", - (requestBody, amount, confirm, capture_method, globalState) => { - + ( + requestBody, + req_data, + res_data, + amount, + confirm, + capture_method, + globalState + ) => { + for (const key in req_data) { + requestBody[key] = req_data[key]; + } requestBody.amount = amount; requestBody.confirm = confirm; requestBody.capture_method = capture_method; @@ -2127,8 +2163,13 @@ Cypress.Commands.add( .to.have.property("redirect_to_url"); const nextActionUrl = response.body.next_action.redirect_to_url; cy.log(nextActionUrl); + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); + } } else if (response.body.authentication_type === "no_three_ds") { - expect(response.body.status).to.equal("succeeded"); + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); + } } else { throw new Error( `Invalid authentication type ${response.body.authentication_type}` @@ -2141,8 +2182,13 @@ Cypress.Commands.add( .to.have.property("redirect_to_url"); const nextActionUrl = response.body.next_action.redirect_to_url; cy.log(nextActionUrl); + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); + } } else if (response.body.authentication_type === "no_three_ds") { - expect(response.body.status).to.equal("requires_capture"); + for (const key in res_data.body) { + expect(res_data.body[key], [key]).to.equal(response.body[key]); + } } else { throw new Error( `Invalid authentication type ${response.body.authentication_type}` @@ -2154,9 +2200,7 @@ Cypress.Commands.add( ); } } else { - throw new Error( - `Error Response: ${response.status}\n${response.body.error.message}\n${response.body.error.code}` - ); + defaultErrorHandler(response, res_data); } }); } From 9be012826abe87ffa2d0cea5423aed3e50449de2 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 28 Nov 2024 00:22:13 +0000 Subject: [PATCH 25/51] chore(version): 2024.11.28.0 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb6f79e96246..dd80af8cf7a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.11.28.0 + +### Bug Fixes + +- **users:** Check lineage across entities in invite ([#6677](https://github.com/juspay/hyperswitch/pull/6677)) ([`f3424b7`](https://github.com/juspay/hyperswitch/commit/f3424b7576554215945f61b52f38e43bb1e5a8b7)) + +### Refactors + +- **core:** Add error handling wrapper to wehbook ([#6636](https://github.com/juspay/hyperswitch/pull/6636)) ([`4b45d21`](https://github.com/juspay/hyperswitch/commit/4b45d21269437479435302aa1ea7d3d741e2a009)) + +**Full Changelog:** [`2024.11.27.0...2024.11.28.0`](https://github.com/juspay/hyperswitch/compare/2024.11.27.0...2024.11.28.0) + +- - - + ## 2024.11.27.0 ### Features From 2c865156a24ab834e690e2fa3a1c3584f4831b50 Mon Sep 17 00:00:00 2001 From: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:38:31 +0530 Subject: [PATCH 26/51] ci(cypress): Added Config Testcases (#6622) Co-authored-by: Pa1NarK <69745008+pixincreate@users.noreply.github.com> --- .../PaymentTest/00024-ConnectorAgnostic.cy.js | 38 +- .../e2e/PaymentTest/00025-ConfigTest.cy.js | 407 ++++++++++++++++++ .../cypress/fixtures/business-profile.json | 13 + .../fixtures/create-business-profile.json | 3 - cypress-tests/cypress/fixtures/imports.js | 6 +- .../fixtures/update-business-profile.json | 3 - cypress-tests/cypress/support/commands.js | 63 ++- 7 files changed, 512 insertions(+), 21 deletions(-) create mode 100644 cypress-tests/cypress/e2e/PaymentTest/00025-ConfigTest.cy.js create mode 100644 cypress-tests/cypress/fixtures/business-profile.json delete mode 100644 cypress-tests/cypress/fixtures/create-business-profile.json delete mode 100644 cypress-tests/cypress/fixtures/update-business-profile.json diff --git a/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnostic.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnostic.cy.js index 64abe296b294..29b0097253df 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnostic.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnostic.cy.js @@ -26,7 +26,7 @@ describe("Connector Agnostic Tests", () => { it("Create Business Profile", () => { cy.createBusinessProfileTest( - fixtures.createBusinessProfile, + fixtures.businessProfile.bpCreate, globalState ); }); @@ -85,7 +85,7 @@ describe("Connector Agnostic Tests", () => { it("Create Business Profile", () => { cy.createBusinessProfileTest( - fixtures.createBusinessProfile, + fixtures.businessProfile.bpCreate, globalState ); }); @@ -101,8 +101,12 @@ describe("Connector Agnostic Tests", () => { it("Enable Connector Agnostic for Business Profile", () => { cy.UpdateBusinessProfileTest( - fixtures.updateBusinessProfile, - true, + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector globalState ); }); @@ -141,7 +145,10 @@ describe("Connector Agnostic Tests", () => { }); it("Create Business Profile", () => { - cy.createBusinessProfileTest(fixtures.createBusinessProfile, globalState); + cy.createBusinessProfileTest( + fixtures.businessProfile.bpCreate, + globalState + ); }); it("connector-create-call-test", () => { @@ -159,8 +166,12 @@ describe("Connector Agnostic Tests", () => { it("Enable Connector Agnostic for Business Profile", () => { cy.UpdateBusinessProfileTest( - fixtures.updateBusinessProfile, - true, + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector globalState ); }); @@ -205,7 +216,10 @@ describe("Connector Agnostic Tests", () => { }); it("Create Business Profile", () => { - cy.createBusinessProfileTest(fixtures.createBusinessProfile, globalState); + cy.createBusinessProfileTest( + fixtures.businessProfile.bpCreate, + globalState + ); }); it("connector-create-call-test", () => { @@ -219,8 +233,12 @@ describe("Connector Agnostic Tests", () => { it("Enable Connector Agnostic for Business Profile", () => { cy.UpdateBusinessProfileTest( - fixtures.updateBusinessProfile, - true, + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector globalState ); }); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00025-ConfigTest.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00025-ConfigTest.cy.js new file mode 100644 index 000000000000..724233852c78 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00025-ConfigTest.cy.js @@ -0,0 +1,407 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import { payment_methods_enabled } from "../PaymentUtils/Commons"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Config Tests", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context( + "Update collect_billing_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + it("Create Business Profile", () => { + cy.createBusinessProfileTest( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("connector-create-call-test", () => { + cy.createConnectorCallTest( + "payment_processor", + fixtures.createConnectorBody, + payment_methods_enabled, + globalState + ); + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Update collect_billing_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + true, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + let req_data = data["Request"]; + let res_data = data["Response"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + req_data, + res_data, + "no_three_ds", + "automatic", + globalState + ); + + if (should_continue) + should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + + context( + "Update collect_shipping_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + it("Update collect_shipping_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + let req_data = data["Request"]; + let res_data = data["Response"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + req_data, + res_data, + "no_three_ds", + "automatic", + globalState + ); + + if (should_continue) + should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + + context( + "Update always_collect_billing_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + it("Update always_collect_billing_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + true, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + let req_data = data["Request"]; + let res_data = data["Response"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + req_data, + res_data, + "no_three_ds", + "automatic", + globalState + ); + + if (should_continue) + should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + + context( + "Update always_collect_shipping_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + it("Update always_collect_shipping_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + true, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + let req_data = data["Request"]; + let res_data = data["Response"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + req_data, + res_data, + "no_three_ds", + "automatic", + globalState + ); + + if (should_continue) + should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + + context( + "Update always_collect_shipping_details_from_wallet_connector & collect_shipping_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + it("Update both always & collect_shipping_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + true, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + true, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + let req_data = data["Request"]; + let res_data = data["Response"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + req_data, + res_data, + "no_three_ds", + "automatic", + globalState + ); + + if (should_continue) + should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + context( + "Update always_collect_billing_details_from_wallet_connector & to collect_billing_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + it("Update both always & collect_billing_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + true, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + true, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + let req_data = data["Request"]; + let res_data = data["Response"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + req_data, + res_data, + "no_three_ds", + "automatic", + globalState + ); + + if (should_continue) + should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + + context( + "Update all config(Collect address config) to false and verifying in payment method list, both config should be false", + () => { + let should_continue = true; + + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + it("Create Business Profile", () => { + cy.createBusinessProfileTest( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("connector-create-call-test", () => { + cy.createConnectorCallTest( + "payment_processor", + fixtures.createConnectorBody, + payment_methods_enabled, + globalState + ); + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Update all config to false", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + let data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + let req_data = data["Request"]; + let res_data = data["Response"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + req_data, + res_data, + "no_three_ds", + "automatic", + globalState + ); + + if (should_continue) + should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); +}); diff --git a/cypress-tests/cypress/fixtures/business-profile.json b/cypress-tests/cypress/fixtures/business-profile.json new file mode 100644 index 000000000000..8d7bf637b592 --- /dev/null +++ b/cypress-tests/cypress/fixtures/business-profile.json @@ -0,0 +1,13 @@ +{ + "bpCreate": { + "profile_name": "default" + }, + + "bpUpdate": { + "is_connector_agnostic_mit_enabled": true, + "collect_shipping_details_from_wallet_connector": true, + "collect_billing_details_from_wallet_connector": true, + "always_collect_billing_details_from_wallet_connector": true, + "always_collect_shipping_details_from_wallet_connector": true + } +} diff --git a/cypress-tests/cypress/fixtures/create-business-profile.json b/cypress-tests/cypress/fixtures/create-business-profile.json deleted file mode 100644 index cdce86361570..000000000000 --- a/cypress-tests/cypress/fixtures/create-business-profile.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "profile_name": "default" -} \ No newline at end of file diff --git a/cypress-tests/cypress/fixtures/imports.js b/cypress-tests/cypress/fixtures/imports.js index 49fea00491fb..58768394cbc4 100644 --- a/cypress-tests/cypress/fixtures/imports.js +++ b/cypress-tests/cypress/fixtures/imports.js @@ -1,8 +1,8 @@ +import businessProfile from "./business-profile.json"; import captureBody from "./capture-flow-body.json"; import configs from "./configs.json"; import confirmBody from "./confirm-body.json"; import apiKeyCreateBody from "./create-api-key-body.json"; -import createBusinessProfile from "./create-business-profile.json"; import createConfirmPaymentBody from "./create-confirm-body.json"; import createConnectorBody from "./create-connector-body.json"; import customerCreateBody from "./create-customer-body.json"; @@ -20,7 +20,6 @@ import routingConfigBody from "./routing-config-body.json"; import saveCardConfirmBody from "./save-card-confirm-body.json"; import sessionTokenBody from "./session-token.json"; import apiKeyUpdateBody from "./update-api-key-body.json"; -import updateBusinessProfile from "./update-business-profile.json"; import updateConnectorBody from "./update-connector-body.json"; import customerUpdateBody from "./update-customer-body.json"; import voidBody from "./void-payment-body.json"; @@ -29,11 +28,11 @@ import ntidConfirmBody from "./create-ntid-mit.json"; export { apiKeyCreateBody, apiKeyUpdateBody, + businessProfile, captureBody, citConfirmBody, configs, confirmBody, - createBusinessProfile, createConfirmPaymentBody, createConnectorBody, createPaymentBody, @@ -51,7 +50,6 @@ export { routingConfigBody, saveCardConfirmBody, sessionTokenBody, - updateBusinessProfile, updateConnectorBody, voidBody, }; diff --git a/cypress-tests/cypress/fixtures/update-business-profile.json b/cypress-tests/cypress/fixtures/update-business-profile.json deleted file mode 100644 index 9d69534bae63..000000000000 --- a/cypress-tests/cypress/fixtures/update-business-profile.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "is_connector_agnostic_mit_enabled": true -} diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 17c2270efef6..ee64d3247c53 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -196,9 +196,25 @@ Cypress.Commands.add( Cypress.Commands.add( "UpdateBusinessProfileTest", - (updateBusinessProfile, is_connector_agnostic_mit_enabled, globalState) => { + ( + updateBusinessProfile, + is_connector_agnostic_mit_enabled, + collect_billing_details_from_wallet_connector, + collect_shipping_details_from_wallet_connector, + always_collect_billing_details_from_wallet_connector, + always_collect_shipping_details_from_wallet_connector, + globalState + ) => { updateBusinessProfile.is_connector_agnostic_mit_enabled = is_connector_agnostic_mit_enabled; + updateBusinessProfile.collect_shipping_details_from_wallet_connector = + collect_shipping_details_from_wallet_connector; + updateBusinessProfile.collect_billing_details_from_wallet_connector = + collect_billing_details_from_wallet_connector; + updateBusinessProfile.always_collect_billing_details_from_wallet_connector = + always_collect_billing_details_from_wallet_connector; + updateBusinessProfile.always_collect_shipping_details_from_wallet_connector = + always_collect_shipping_details_from_wallet_connector; const merchant_id = globalState.get("merchantId"); const profile_id = globalState.get("profileId"); cy.request({ @@ -213,6 +229,24 @@ Cypress.Commands.add( failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); + if (response.status === 200) { + globalState.set( + "collectBillingDetails", + response.body.collect_billing_details_from_wallet_connector + ); + globalState.set( + "collectShippingDetails", + response.body.collect_shipping_details_from_wallet_connector + ); + globalState.set( + "alwaysCollectBillingDetails", + response.body.always_collect_billing_details_from_wallet_connector + ); + globalState.set( + "alwaysCollectShippingDetails", + response.body.always_collect_shipping_details_from_wallet_connector + ); + } }); } ); @@ -1001,6 +1035,33 @@ Cypress.Commands.add("paymentMethodsCallTest", (globalState) => { expect(response.headers["content-type"]).to.include("application/json"); expect(response.body).to.have.property("redirect_url"); expect(response.body).to.have.property("payment_methods"); + if ( + globalState.get("collectBillingDetails") === true || + globalState.get("alwaysCollectBillingDetails") === true + ) { + expect( + response.body.collect_billing_details_from_wallets, + "collectBillingDetailsFromWallets" + ).to.be.true; + } else + expect( + response.body.collect_billing_details_from_wallets, + "collectBillingDetailsFromWallets" + ).to.be.false; + + if ( + globalState.get("collectShippingDetails") === true || + globalState.get("alwaysCollectShippingDetails") === true + ) { + expect( + response.body.collect_shipping_details_from_wallets, + "collectShippingDetailsFromWallets" + ).to.be.true; + } else + expect( + response.body.collect_shipping_details_from_wallets, + "collectShippingDetailsFromWallets" + ).to.be.false; globalState.set("paymentID", paymentIntentID); cy.log(response); }); From 93459fde5fb95f31e8f1429e806cde8e7496dd84 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar <83278309+tsdk02@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:29:55 +0530 Subject: [PATCH 27/51] fix(analytics): fix bugs in payments page metrics in Analytics V2 dashboard (#6654) --- .../src/payment_intents/accumulator.rs | 10 ++- crates/analytics/src/payment_intents/core.rs | 71 +---------------- .../analytics/src/payment_intents/sankey.rs | 78 ++++++++++++------- crates/analytics/src/payments/accumulator.rs | 19 +++-- .../sessionized_metrics/failure_reasons.rs | 5 -- crates/api_models/src/analytics.rs | 18 ++--- 6 files changed, 80 insertions(+), 121 deletions(-) diff --git a/crates/analytics/src/payment_intents/accumulator.rs b/crates/analytics/src/payment_intents/accumulator.rs index ef3cd3129c48..d8f27501b567 100644 --- a/crates/analytics/src/payment_intents/accumulator.rs +++ b/crates/analytics/src/payment_intents/accumulator.rs @@ -273,8 +273,14 @@ impl PaymentIntentMetricAccumulator for PaymentsDistributionAccumulator { } } - if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) { - self.total += total; + if status.as_ref() != &storage_enums::IntentStatus::RequiresCustomerAction + && status.as_ref() != &storage_enums::IntentStatus::RequiresPaymentMethod + && status.as_ref() != &storage_enums::IntentStatus::RequiresMerchantAction + && status.as_ref() != &storage_enums::IntentStatus::RequiresConfirmation + { + if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) { + self.total += total; + } } } } diff --git a/crates/analytics/src/payment_intents/core.rs b/crates/analytics/src/payment_intents/core.rs index 3654cad8c09c..0b66dfda58ca 100644 --- a/crates/analytics/src/payment_intents/core.rs +++ b/crates/analytics/src/payment_intents/core.rs @@ -8,10 +8,9 @@ use api_models::analytics::{ }, GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, PaymentIntentFilterValue, PaymentIntentFiltersResponse, PaymentIntentsAnalyticsMetadata, PaymentIntentsMetricsResponse, - SankeyResponse, }; use bigdecimal::ToPrimitive; -use common_enums::{Currency, IntentStatus}; +use common_enums::Currency; use common_utils::{errors::CustomResult, types::TimeRange}; use currency_conversion::{conversion::convert, types::ExchangeRates}; use error_stack::ResultExt; @@ -24,7 +23,7 @@ use router_env::{ use super::{ filters::{get_payment_intent_filter_for_dimension, PaymentIntentFilterRow}, metrics::PaymentIntentMetricRow, - sankey::{get_sankey_data, SessionizerRefundStatus}, + sankey::{get_sankey_data, SankeyRow}, PaymentIntentMetricsAccumulator, }; use crate::{ @@ -51,7 +50,7 @@ pub async fn get_sankey( pool: &AnalyticsProvider, auth: &AuthInfo, req: TimeRange, -) -> AnalyticsResult { +) -> AnalyticsResult> { match pool { AnalyticsProvider::Sqlx(_) => Err(AnalyticsError::NotImplemented( "Sankey not implemented for sqlx", @@ -62,69 +61,7 @@ pub async fn get_sankey( let sankey_rows = get_sankey_data(ckh_pool, auth, &req) .await .change_context(AnalyticsError::UnknownError)?; - let mut sankey_response = SankeyResponse::default(); - for i in sankey_rows { - match ( - i.status.as_ref(), - i.refunds_status.unwrap_or_default().as_ref(), - i.attempt_count, - ) { - (IntentStatus::Succeeded, SessionizerRefundStatus::FullRefunded, 1) => { - sankey_response.refunded += i.count; - sankey_response.normal_success += i.count - } - (IntentStatus::Succeeded, SessionizerRefundStatus::PartialRefunded, 1) => { - sankey_response.partial_refunded += i.count; - sankey_response.normal_success += i.count - } - (IntentStatus::Succeeded, SessionizerRefundStatus::FullRefunded, _) => { - sankey_response.refunded += i.count; - sankey_response.smart_retried_success += i.count - } - (IntentStatus::Succeeded, SessionizerRefundStatus::PartialRefunded, _) => { - sankey_response.partial_refunded += i.count; - sankey_response.smart_retried_success += i.count - } - ( - IntentStatus::Succeeded - | IntentStatus::PartiallyCaptured - | IntentStatus::PartiallyCapturedAndCapturable - | IntentStatus::RequiresCapture, - SessionizerRefundStatus::NotRefunded, - 1, - ) => sankey_response.normal_success += i.count, - ( - IntentStatus::Succeeded - | IntentStatus::PartiallyCaptured - | IntentStatus::PartiallyCapturedAndCapturable - | IntentStatus::RequiresCapture, - SessionizerRefundStatus::NotRefunded, - _, - ) => sankey_response.smart_retried_success += i.count, - (IntentStatus::Failed, _, 1) => sankey_response.normal_failure += i.count, - (IntentStatus::Failed, _, _) => { - sankey_response.smart_retried_failure += i.count - } - (IntentStatus::Cancelled, _, _) => sankey_response.cancelled += i.count, - (IntentStatus::Processing, _, _) => sankey_response.pending += i.count, - (IntentStatus::RequiresCustomerAction, _, _) => { - sankey_response.customer_awaited += i.count - } - (IntentStatus::RequiresMerchantAction, _, _) => { - sankey_response.merchant_awaited += i.count - } - (IntentStatus::RequiresPaymentMethod, _, _) => { - sankey_response.pm_awaited += i.count - } - (IntentStatus::RequiresConfirmation, _, _) => { - sankey_response.confirmation_awaited += i.count - } - i @ (_, _, _) => { - router_env::logger::error!(status=?i, "Unknown status in sankey data"); - } - } - } - Ok(sankey_response) + Ok(sankey_rows) } } } diff --git a/crates/analytics/src/payment_intents/sankey.rs b/crates/analytics/src/payment_intents/sankey.rs index 53fd03562f13..626dcef27443 100644 --- a/crates/analytics/src/payment_intents/sankey.rs +++ b/crates/analytics/src/payment_intents/sankey.rs @@ -5,7 +5,6 @@ use common_utils::{ }; use error_stack::ResultExt; use router_env::logger; -use time::PrimitiveDateTime; use crate::{ clickhouse::ClickhouseClient, @@ -13,29 +12,19 @@ use crate::{ types::{AnalyticsCollection, DBEnumWrapper, MetricsError, MetricsResult}, }; -#[derive(Debug, PartialEq, Eq, serde::Deserialize, Hash)] -pub struct PaymentIntentMetricRow { - pub profile_id: Option, - pub connector: Option, - pub authentication_type: Option>, - pub payment_method: Option, - pub payment_method_type: Option, - pub card_network: Option, - pub merchant_id: Option, - pub card_last_4: Option, - pub card_issuer: Option, - pub error_reason: Option, - pub first_attempt: Option, - pub total: Option, - pub count: Option, - #[serde(with = "common_utils::custom_serde::iso8601::option")] - pub start_bucket: Option, - #[serde(with = "common_utils::custom_serde::iso8601::option")] - pub end_bucket: Option, -} - #[derive( - Debug, Default, serde::Deserialize, strum::AsRefStr, strum::EnumString, strum::Display, + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumIter, + strum::EnumString, )] #[serde(rename_all = "snake_case")] pub enum SessionizerRefundStatus { @@ -45,13 +34,36 @@ pub enum SessionizerRefundStatus { PartialRefunded, } -#[derive(Debug, serde::Deserialize)] +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumIter, + strum::EnumString, +)] +#[serde(rename_all = "snake_case")] +pub enum SessionizerDisputeStatus { + DisputePresent, + #[default] + NotDisputed, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct SankeyRow { + pub count: i64, pub status: DBEnumWrapper, #[serde(default)] pub refunds_status: Option>, - pub attempt_count: i64, - pub count: i64, + #[serde(default)] + pub dispute_status: Option>, + pub first_attempt: i64, } impl TryInto for serde_json::Value { @@ -90,7 +102,12 @@ pub async fn get_sankey_data( .change_context(MetricsError::QueryBuildingError)?; query_builder - .add_select_column("attempt_count") + .add_select_column("dispute_status") + .attach_printable("Error adding select clause") + .change_context(MetricsError::QueryBuildingError)?; + + query_builder + .add_select_column("(attempt_count = 1) as first_attempt") .attach_printable("Error adding select clause") .change_context(MetricsError::QueryBuildingError)?; @@ -112,7 +129,12 @@ pub async fn get_sankey_data( .change_context(MetricsError::QueryBuildingError)?; query_builder - .add_group_by_clause("attempt_count") + .add_group_by_clause("dispute_status") + .attach_printable("Error adding group by clause") + .change_context(MetricsError::QueryBuildingError)?; + + query_builder + .add_group_by_clause("first_attempt") .attach_printable("Error adding group by clause") .change_context(MetricsError::QueryBuildingError)?; diff --git a/crates/analytics/src/payments/accumulator.rs b/crates/analytics/src/payments/accumulator.rs index 291d7364071a..20ccc634068d 100644 --- a/crates/analytics/src/payments/accumulator.rs +++ b/crates/analytics/src/payments/accumulator.rs @@ -218,12 +218,19 @@ impl PaymentMetricAccumulator for PaymentsDistributionAccumulator { } } } - if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) { - self.total += total; - if metrics.first_attempt.unwrap_or(false) { - self.total_without_retries += total; - } else { - self.total_with_only_retries += total; + if status.as_ref() != &storage_enums::AttemptStatus::AuthenticationFailed + && status.as_ref() != &storage_enums::AttemptStatus::PaymentMethodAwaited + && status.as_ref() != &storage_enums::AttemptStatus::DeviceDataCollectionPending + && status.as_ref() != &storage_enums::AttemptStatus::ConfirmationAwaited + && status.as_ref() != &storage_enums::AttemptStatus::Unresolved + { + if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) { + self.total += total; + if metrics.first_attempt.unwrap_or(false) { + self.total_without_retries += total; + } else { + self.total_with_only_retries += total; + } } } } diff --git a/crates/analytics/src/payments/metrics/sessionized_metrics/failure_reasons.rs b/crates/analytics/src/payments/metrics/sessionized_metrics/failure_reasons.rs index bcbce0502d2d..c472c12795f7 100644 --- a/crates/analytics/src/payments/metrics/sessionized_metrics/failure_reasons.rs +++ b/crates/analytics/src/payments/metrics/sessionized_metrics/failure_reasons.rs @@ -160,11 +160,6 @@ where .switch()?; } - outer_query_builder - .set_limit_by(5, &filtered_dimensions) - .attach_printable("Error adding limit clause") - .switch()?; - outer_query_builder .execute_query::(pool) .await diff --git a/crates/api_models/src/analytics.rs b/crates/api_models/src/analytics.rs index ee9046521549..d25f35589b67 100644 --- a/crates/api_models/src/analytics.rs +++ b/crates/api_models/src/analytics.rs @@ -458,17 +458,9 @@ pub struct GetDisputeMetricRequest { #[derive(Clone, Debug, Default, serde::Serialize)] #[serde(rename_all = "snake_case")] pub struct SankeyResponse { - pub normal_success: i64, - pub normal_failure: i64, - pub cancelled: i64, - pub smart_retried_success: i64, - pub smart_retried_failure: i64, - pub pending: i64, - pub partial_refunded: i64, - pub refunded: i64, - pub disputed: i64, - pub pm_awaited: i64, - pub customer_awaited: i64, - pub merchant_awaited: i64, - pub confirmation_awaited: i64, + pub count: i64, + pub status: String, + pub refunds_status: Option, + pub dispute_status: Option, + pub first_attempt: i64, } From 707f48ceda789185187d23e35f483e117c67b81b Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:31:57 +0530 Subject: [PATCH 28/51] feat: add support for sdk session call in v2 (#6502) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 5 - crates/api_models/src/events/payment.rs | 1 - crates/api_models/src/payments.rs | 11 + crates/common_utils/src/macros.rs | 35 +++ crates/common_utils/src/types.rs | 2 +- .../src/merchant_connector_account.rs | 123 ++++++-- .../hyperswitch_domain_models/src/payments.rs | 3 + crates/router/src/core/payments.rs | 128 +++++++- .../src/core/payments/flows/session_flow.rs | 50 +++- crates/router/src/core/payments/operations.rs | 11 +- .../operations/payment_create_intent.rs | 1 + .../payments/operations/payment_get_intent.rs | 1 + .../payments/operations/payment_response.rs | 4 +- .../operations/payment_session_intent.rs | 281 ++++++++++++++++++ .../src/core/payments/session_operation.rs | 186 ++++++++++++ .../router/src/core/payments/transformers.rs | 182 +++++++++++- crates/router/src/routes/app.rs | 1 - crates/router/src/routes/payments.rs | 65 +++- 18 files changed, 1029 insertions(+), 61 deletions(-) create mode 100644 crates/router/src/core/payments/operations/payment_session_intent.rs create mode 100644 crates/router/src/core/payments/session_operation.rs diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 1494908c5fcd..0edeb537dc1c 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -15344,7 +15344,6 @@ "type": "object", "required": [ "payment_id", - "client_secret", "session_token" ], "properties": { @@ -15352,10 +15351,6 @@ "type": "string", "description": "The identifier for the payment" }, - "client_secret": { - "type": "string", - "description": "This is a token which expires after 15 minutes, used from the client to authenticate and create sessions from the SDK" - }, "session_token": { "type": "array", "items": { diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index 6fdb7d59b0f8..6015682f7c38 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -402,7 +402,6 @@ impl ApiEventMetric for PaymentsManualUpdateResponse { } } -#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsSessionResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index c0cf816c68d0..45b9bac53acb 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -6084,6 +6084,7 @@ pub struct ApplepayErrorResponse { pub status_message: String, } +#[cfg(feature = "v1")] #[derive(Default, Debug, serde::Serialize, Clone, ToSchema)] pub struct PaymentsSessionResponse { /// The identifier for the payment @@ -6096,6 +6097,16 @@ pub struct PaymentsSessionResponse { pub session_token: Vec, } +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, Clone, ToSchema)] +pub struct PaymentsSessionResponse { + /// The identifier for the payment + #[schema(value_type = String)] + pub payment_id: id_type::GlobalPaymentId, + /// The list of session token object + pub session_token: Vec, +} + #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct PaymentRetrieveBody { /// The identifier for the Merchant Account. diff --git a/crates/common_utils/src/macros.rs b/crates/common_utils/src/macros.rs index 21cec6f60fce..fe1289acba03 100644 --- a/crates/common_utils/src/macros.rs +++ b/crates/common_utils/src/macros.rs @@ -369,6 +369,41 @@ mod id_type { } } +/// Create new generic list wrapper +#[macro_export] +macro_rules! create_list_wrapper { + ( + $wrapper_name:ident, + $type_name: ty, + impl_functions: { + $($function_def: tt)* + } + ) => { + pub struct $wrapper_name(Vec<$type_name>); + impl $wrapper_name { + pub fn new(list: Vec<$type_name>) -> Self { + Self(list) + } + pub fn iter(&self) -> std::slice::Iter<'_, $type_name> { + self.0.iter() + } + $($function_def)* + } + impl Iterator for $wrapper_name { + type Item = $type_name; + fn next(&mut self) -> Option { + self.0.pop() + } + } + + impl FromIterator<$type_name> for $wrapper_name { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } + } + }; +} + /// Get the type name for a type #[macro_export] macro_rules! type_name { diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 84a70e44a32a..2a271acb62bb 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -845,7 +845,7 @@ mod client_secret_type { Ok(row) } } - + crate::impl_serializable_secret_id_type!(ClientSecret); #[cfg(test)] mod client_secret_tests { #![allow(clippy::expect_used)] diff --git a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs index f0719ba35892..51c75113dcda 100644 --- a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs @@ -1,3 +1,7 @@ +#[cfg(feature = "v2")] +use api_models::admin; +#[cfg(feature = "v2")] +use common_utils::ext_traits::ValueExt; use common_utils::{ crypto::Encryptable, date_time, @@ -9,11 +13,15 @@ use common_utils::{ use diesel_models::{enums, merchant_connector_account::MerchantConnectorAccountUpdateInternal}; use error_stack::ResultExt; use masking::{PeekInterface, Secret}; +#[cfg(feature = "v2")] +use router_env::logger; use rustc_hash::FxHashMap; use serde_json::Value; use super::behaviour; #[cfg(feature = "v2")] +use crate::errors::api_error_response::ApiErrorResponse; +#[cfg(feature = "v2")] use crate::router_data; use crate::type_encryption::{crypto_operation, CryptoOperation}; @@ -90,6 +98,27 @@ impl MerchantConnectorAccount { self.id.clone() } + pub fn get_metadata(&self) -> Option { + self.metadata.clone() + } + + pub fn get_parsed_payment_methods_enabled( + &self, + ) -> Vec> { + self.payment_methods_enabled + .clone() + .unwrap_or_default() + .into_iter() + .map(|payment_methods_enabled| { + payment_methods_enabled + .parse_value::("payment_methods_enabled") + .change_context(ApiErrorResponse::InvalidDataValue { + field_name: "payment_methods_enabled", + }) + }) + .collect() + } + pub fn is_disabled(&self) -> bool { self.disabled.unwrap_or(false) } @@ -530,31 +559,73 @@ impl From for MerchantConnectorAccountUpdateInte } } -#[derive(Debug)] -pub struct MerchantConnectorAccounts(Vec); - -impl MerchantConnectorAccounts { - pub fn new(merchant_connector_accounts: Vec) -> Self { - Self(merchant_connector_accounts) - } - - pub fn is_merchant_connector_account_id_in_connector_mandate_details( - &self, - profile_id: Option<&id_type::ProfileId>, - connector_mandate_details: &diesel_models::PaymentsMandateReference, - ) -> bool { - let mca_ids = self - .0 - .iter() - .filter(|mca| { - mca.disabled.is_some_and(|disabled| !disabled) - && profile_id.is_some_and(|profile_id| *profile_id == mca.profile_id) - }) - .map(|mca| mca.get_id()) - .collect::>(); +common_utils::create_list_wrapper!( + MerchantConnectorAccounts, + MerchantConnectorAccount, + impl_functions: { + #[cfg(feature = "v2")] + pub fn get_connector_and_supporting_payment_method_type_for_session_call( + &self, + ) -> Vec<(&MerchantConnectorAccount, common_enums::PaymentMethodType)> { + let connector_and_supporting_payment_method_type = self.iter().flat_map(|connector_account| { + connector_account + .get_parsed_payment_methods_enabled() + // TODO: make payment_methods_enabled strict type in DB + .into_iter() + .filter_map(|parsed_payment_method_result| { + parsed_payment_method_result + .inspect_err(|err| { + logger::error!(session_token_parsing_error=?err); + }) + .ok() + }) + .flat_map(|parsed_payment_methods_enabled| { + parsed_payment_methods_enabled + .payment_method_types + .unwrap_or_default() + .into_iter() + .filter(|payment_method_type| { + let is_invoke_sdk_client = matches!( + payment_method_type.payment_experience, + Some(api_models::enums::PaymentExperience::InvokeSdkClient) + ); + is_invoke_sdk_client + }) + .map(|payment_method_type| { + (connector_account, payment_method_type.payment_method_type) + }) + .collect::>() + }) + .collect::>() + }).collect(); + connector_and_supporting_payment_method_type + } + pub fn filter_based_on_profile_and_connector_type( + self, + profile_id: &id_type::ProfileId, + connector_type: common_enums::ConnectorType, + ) -> Self { + self.into_iter() + .filter(|mca| &mca.profile_id == profile_id && mca.connector_type == connector_type) + .collect() + } + pub fn is_merchant_connector_account_id_in_connector_mandate_details( + &self, + profile_id: Option<&id_type::ProfileId>, + connector_mandate_details: &diesel_models::PaymentsMandateReference, + ) -> bool { + let mca_ids = self + .iter() + .filter(|mca| { + mca.disabled.is_some_and(|disabled| !disabled) + && profile_id.is_some_and(|profile_id| *profile_id == mca.profile_id) + }) + .map(|mca| mca.get_id()) + .collect::>(); - connector_mandate_details - .keys() - .any(|mca_id| mca_ids.contains(mca_id)) + connector_mandate_details + .keys() + .any(|mca_id| mca_ids.contains(mca_id)) + } } -} +); diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 4cb934032194..b7a6c12500d1 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -1,6 +1,8 @@ #[cfg(feature = "v2")] use std::marker::PhantomData; +#[cfg(feature = "v2")] +use api_models::payments::SessionToken; #[cfg(feature = "v2")] use common_utils::ext_traits::ValueExt; use common_utils::{ @@ -566,6 +568,7 @@ where { pub flow: PhantomData, pub payment_intent: PaymentIntent, + pub sessions_token: Vec, } // TODO: Check if this can be merged with existing payment data diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index ada8160c6d94..5eb97e6eebe2 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -8,6 +8,8 @@ pub mod operations; #[cfg(feature = "retry")] pub mod retry; pub mod routing; +#[cfg(feature = "v2")] +pub mod session_operation; pub mod tokenization; pub mod transformers; pub mod types; @@ -56,6 +58,8 @@ use router_env::{instrument, metrics::add_attributes, tracing}; #[cfg(feature = "olap")] use router_types::transformers::ForeignFrom; use scheduler::utils as pt_utils; +#[cfg(feature = "v2")] +pub use session_operation::payments_session_core; #[cfg(feature = "olap")] use strum::IntoEnumIterator; use time; @@ -3111,6 +3115,119 @@ where } } +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +pub async fn call_multiple_connectors_service( + state: &SessionState, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + connectors: Vec, + _operation: &Op, + mut payment_data: D, + customer: &Option, + _session_surcharge_details: Option, + business_profile: &domain::Profile, + header_payload: HeaderPayload, +) -> RouterResult +where + Op: Debug, + F: Send + Clone, + + // To create connector flow specific interface data + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature, + + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + let call_connectors_start_time = Instant::now(); + let mut join_handlers = Vec::with_capacity(connectors.len()); + for session_connector_data in connectors.iter() { + let merchant_connector_id = session_connector_data + .connector + .merchant_connector_id + .as_ref() + .get_required_value("merchant_connector_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("connector id is not set")?; + // TODO: make this DB call parallel + let merchant_connector_account = state + .store + .find_merchant_connector_account_by_id(&state.into(), merchant_connector_id, key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_connector_id.get_string_repr().to_owned(), + })?; + let connector_id = session_connector_data.connector.connector.id(); + let router_data = payment_data + .construct_router_data( + state, + connector_id, + merchant_account, + key_store, + customer, + &merchant_connector_account, + None, + None, + ) + .await?; + + let res = router_data.decide_flows( + state, + &session_connector_data.connector, + CallConnectorAction::Trigger, + None, + business_profile, + header_payload.clone(), + ); + + join_handlers.push(res); + } + + let result = join_all(join_handlers).await; + + for (connector_res, session_connector) in result.into_iter().zip(connectors) { + let connector_name = session_connector.connector.connector_name.to_string(); + match connector_res { + Ok(connector_response) => { + if let Ok(router_types::PaymentsResponseData::SessionResponse { + session_token, + .. + }) = connector_response.response.clone() + { + // If session token is NoSessionTokenReceived, it is not pushed into the sessions_token as there is no response or there can be some error + // In case of error, that error is already logged + if !matches!( + session_token, + api_models::payments::SessionToken::NoSessionTokenReceived, + ) { + payment_data.push_sessions_token(session_token); + } + } + if let Err(connector_error_response) = connector_response.response { + logger::error!( + "sessions_connector_error {} {:?}", + connector_name, + connector_error_response + ); + } + } + Err(api_error) => { + logger::error!("sessions_api_error {} {:?}", connector_name, api_error); + } + } + } + + let call_connectors_end_time = Instant::now(); + let call_connectors_duration = + call_connectors_end_time.saturating_duration_since(call_connectors_start_time); + tracing::info!(duration = format!("Duration taken: {}", call_connectors_duration.as_millis())); + + Ok(payment_data) +} + #[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] pub async fn call_multiple_connectors_service( @@ -3137,9 +3254,6 @@ where // To construct connector flow specific api dyn api::Connector: services::api::ConnectorIntegration, - - // To perform router related operation for PaymentResponse - PaymentResponse: Operation, { let call_connectors_start_time = Instant::now(); let mut join_handlers = Vec::with_capacity(connectors.len()); @@ -3574,6 +3688,7 @@ pub fn is_preprocessing_required_for_wallets(connector_name: String) -> bool { connector_name == *"trustpay" || connector_name == *"payme" } +#[cfg(feature = "v1")] #[instrument(skip_all)] pub async fn construct_profile_id_and_get_mca<'a, F, D>( state: &'a SessionState, @@ -3588,7 +3703,6 @@ where F: Clone, D: OperationSessionGetters + Send + Sync + Clone, { - #[cfg(feature = "v1")] let profile_id = payment_data .get_payment_intent() .profile_id @@ -6977,7 +7091,7 @@ impl OperationSessionGetters for PaymentIntentData { } fn get_sessions_token(&self) -> Vec { - todo!() + self.sessions_token.clone() } fn get_token_data(&self) -> Option<&storage::PaymentTokenData> { @@ -7033,8 +7147,8 @@ impl OperationSessionSetters for PaymentIntentData { todo!() } - fn push_sessions_token(&mut self, _token: api::SessionToken) { - todo!() + fn push_sessions_token(&mut self, token: api::SessionToken) { + self.sessions_token.push(token); } fn set_surcharge_details(&mut self, _surcharge_details: Option) { diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 43f855182e89..265046f42d96 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -6,6 +6,8 @@ use common_utils::{ types::{AmountConvertor, StringMajorUnitForConnector}, }; use error_stack::{Report, ResultExt}; +#[cfg(feature = "v2")] +use hyperswitch_domain_models::payments::PaymentIntentData; use masking::ExposeInterface; use router_env::metrics::add_attributes; @@ -26,12 +28,12 @@ use crate::{ utils::OptionExt, }; +#[cfg(feature = "v2")] #[async_trait] impl ConstructFlowSpecificData - for PaymentData + for PaymentIntentData { - #[cfg(feature = "v1")] async fn construct_router_data<'a>( &self, state: &routes::SessionState, @@ -39,14 +41,11 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, customer: &Option, - merchant_connector_account: &helpers::MerchantConnectorAccountType, + merchant_connector_account: &domain::MerchantConnectorAccount, merchant_recipient_data: Option, header_payload: Option, ) -> RouterResult { - Box::pin(transformers::construct_payment_router_data::< - api::Session, - types::PaymentsSessionData, - >( + Box::pin(transformers::construct_payment_router_data_for_sdk_session( state, self.clone(), connector_id, @@ -60,7 +59,24 @@ impl .await } - #[cfg(feature = "v2")] + async fn get_merchant_recipient_data<'a>( + &self, + _state: &routes::SessionState, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + _merchant_connector_account: &helpers::MerchantConnectorAccountType, + _connector: &api::ConnectorData, + ) -> RouterResult> { + Ok(None) + } +} + +#[cfg(feature = "v1")] +#[async_trait] +impl + ConstructFlowSpecificData + for PaymentData +{ async fn construct_router_data<'a>( &self, state: &routes::SessionState, @@ -68,11 +84,25 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, customer: &Option, - merchant_connector_account: &domain::MerchantConnectorAccount, + merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_recipient_data: Option, header_payload: Option, ) -> RouterResult { - todo!() + Box::pin(transformers::construct_payment_router_data::< + api::Session, + types::PaymentsSessionData, + >( + state, + self.clone(), + connector_id, + merchant_account, + key_store, + customer, + merchant_connector_account, + merchant_recipient_data, + header_payload, + )) + .await } async fn get_merchant_recipient_data<'a>( diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index e936f3725455..5cdfff60b451 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -17,6 +17,8 @@ pub mod payment_reject; pub mod payment_response; #[cfg(feature = "v1")] pub mod payment_session; +#[cfg(feature = "v2")] +pub mod payment_session_intent; #[cfg(feature = "v1")] pub mod payment_start; #[cfg(feature = "v1")] @@ -45,10 +47,6 @@ use async_trait::async_trait; use error_stack::{report, ResultExt}; use router_env::{instrument, tracing}; -#[cfg(feature = "v2")] -pub use self::payment_confirm_intent::PaymentIntentConfirm; -#[cfg(feature = "v2")] -pub use self::payment_create_intent::PaymentIntentCreate; #[cfg(feature = "v2")] pub use self::payment_get::PaymentGet; #[cfg(feature = "v2")] @@ -64,6 +62,11 @@ pub use self::{ payments_incremental_authorization::PaymentIncrementalAuthorization, tax_calculation::PaymentSessionUpdate, }; +#[cfg(feature = "v2")] +pub use self::{ + payment_confirm_intent::PaymentIntentConfirm, payment_create_intent::PaymentIntentCreate, + payment_session_intent::PaymentSessionIntent, +}; use super::{helpers, CustomerDetails, OperationSessionGetters, OperationSessionSetters}; use crate::{ core::errors::{self, CustomResult, RouterResult}, 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 bf5b4fb80c9e..988e040f3769 100644 --- a/crates/router/src/core/payments/operations/payment_create_intent.rs +++ b/crates/router/src/core/payments/operations/payment_create_intent.rs @@ -158,6 +158,7 @@ impl GetTracker, PaymentsCrea let payment_data = payments::PaymentIntentData { flow: PhantomData, payment_intent, + sessions_token: vec![], }; let get_trackers_response = operations::GetTrackerResponse { payment_data }; 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 6424ff5a2b35..344f10434bdd 100644 --- a/crates/router/src/core/payments/operations/payment_get_intent.rs +++ b/crates/router/src/core/payments/operations/payment_get_intent.rs @@ -101,6 +101,7 @@ impl GetTracker, PaymentsGetI let payment_data = payments::PaymentIntentData { flow: PhantomData, payment_intent, + sessions_token: vec![], }; let get_trackers_response = operations::GetTrackerResponse { payment_data }; diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 8fb36764c970..e3f9ad5d9054 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -13,7 +13,9 @@ use error_stack::{report, ResultExt}; use futures::FutureExt; use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; #[cfg(feature = "v2")] -use hyperswitch_domain_models::payments::{PaymentConfirmData, PaymentStatusData}; +use hyperswitch_domain_models::payments::{ + PaymentConfirmData, PaymentIntentData, PaymentStatusData, +}; use router_derive; use router_env::{instrument, logger, metrics::add_attributes, tracing}; use storage_impl::DataModelExt; diff --git a/crates/router/src/core/payments/operations/payment_session_intent.rs b/crates/router/src/core/payments/operations/payment_session_intent.rs new file mode 100644 index 000000000000..ee490408cb11 --- /dev/null +++ b/crates/router/src/core/payments/operations/payment_session_intent.rs @@ -0,0 +1,281 @@ +use std::marker::PhantomData; + +use api_models::payments::PaymentsSessionRequest; +use async_trait::async_trait; +use common_utils::errors::CustomResult; +use error_stack::ResultExt; +use router_env::{instrument, logger, tracing}; + +use super::{BoxedOperation, Domain, GetTracker, Operation, ValidateRequest}; +use crate::{ + core::{ + errors::{self, RouterResult, StorageErrorExt}, + payments::{self, operations, operations::ValidateStatusForOperation}, + }, + routes::SessionState, + types::{api, domain, storage::enums}, + utils::ext_traits::OptionExt, +}; + +#[derive(Debug, Clone, Copy)] +pub struct PaymentSessionIntent; + +impl ValidateStatusForOperation for PaymentSessionIntent { + /// Validate if the current operation can be performed on the current status of the payment intent + fn validate_status_for_operation( + &self, + intent_status: common_enums::IntentStatus, + ) -> Result<(), errors::ApiErrorResponse> { + match intent_status { + common_enums::IntentStatus::RequiresPaymentMethod => Ok(()), + common_enums::IntentStatus::Cancelled + | common_enums::IntentStatus::Processing + | common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::RequiresCapture + | common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::RequiresConfirmation + | common_enums::IntentStatus::PartiallyCapturedAndCapturable + | common_enums::IntentStatus::Succeeded + | common_enums::IntentStatus::Failed => { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: format!( + "You cannot create session token for this payment because it has status {intent_status}. Expected status is requires_payment_method.", + ), + }) + } + } + } +} + +impl Operation for &PaymentSessionIntent { + type Data = payments::PaymentIntentData; + fn to_validate_request( + &self, + ) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> + { + Ok(*self) + } + fn to_get_tracker( + &self, + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> { + Ok(*self) + } + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + Ok(*self) + } +} + +impl Operation for PaymentSessionIntent { + type Data = payments::PaymentIntentData; + fn to_validate_request( + &self, + ) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> + { + Ok(self) + } + fn to_get_tracker( + &self, + ) -> RouterResult<&(dyn GetTracker + Send + Sync)> { + Ok(self) + } + fn to_domain(&self) -> RouterResult<&dyn Domain> { + Ok(self) + } +} + +type PaymentsCreateIntentOperation<'b, F> = + BoxedOperation<'b, F, PaymentsSessionRequest, payments::PaymentIntentData>; + +#[async_trait] +impl GetTracker, PaymentsSessionRequest> + for PaymentSessionIntent +{ + #[instrument(skip_all)] + async fn get_trackers<'a>( + &'a self, + state: &'a SessionState, + payment_id: &common_utils::id_type::GlobalPaymentId, + _request: &PaymentsSessionRequest, + merchant_account: &domain::MerchantAccount, + _profile: &domain::Profile, + key_store: &domain::MerchantKeyStore, + header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + ) -> RouterResult>> { + let db = &*state.store; + let key_manager_state = &state.into(); + let storage_scheme = merchant_account.storage_scheme; + + let payment_intent = db + .find_payment_intent_by_id(key_manager_state, payment_id, key_store, storage_scheme) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + self.validate_status_for_operation(payment_intent.status)?; + + let client_secret = header_payload + .client_secret + .as_ref() + .get_required_value("client_secret header")?; + payment_intent.validate_client_secret(client_secret)?; + + let payment_data = payments::PaymentIntentData { + flow: PhantomData, + payment_intent, + sessions_token: vec![], + }; + + let get_trackers_response = operations::GetTrackerResponse { payment_data }; + + Ok(get_trackers_response) + } +} + +impl ValidateRequest> + for PaymentSessionIntent +{ + #[instrument(skip_all)] + fn validate_request<'a, 'b>( + &'b self, + _request: &PaymentsSessionRequest, + merchant_account: &'a domain::MerchantAccount, + ) -> RouterResult { + Ok(operations::ValidateResult { + merchant_id: merchant_account.get_id().to_owned(), + storage_scheme: merchant_account.storage_scheme, + requeue: false, + }) + } +} + +#[async_trait] +impl Domain> + for PaymentSessionIntent +{ + #[instrument(skip_all)] + async fn get_customer_details<'a>( + &'a self, + state: &SessionState, + payment_data: &mut payments::PaymentIntentData, + merchant_key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + ) -> CustomResult< + ( + BoxedOperation<'a, F, PaymentsSessionRequest, payments::PaymentIntentData>, + Option, + ), + errors::StorageError, + > { + match payment_data.payment_intent.customer_id.clone() { + Some(id) => { + let customer = state + .store + .find_customer_by_global_id( + &state.into(), + id.get_string_repr(), + &payment_data.payment_intent.merchant_id, + merchant_key_store, + storage_scheme, + ) + .await?; + Ok((Box::new(self), Some(customer))) + } + None => Ok((Box::new(self), None)), + } + } + + #[instrument(skip_all)] + async fn make_pm_data<'a>( + &'a self, + _state: &'a SessionState, + _payment_data: &mut payments::PaymentIntentData, + _storage_scheme: enums::MerchantStorageScheme, + _merchant_key_store: &domain::MerchantKeyStore, + _customer: &Option, + _business_profile: &domain::Profile, + ) -> RouterResult<( + PaymentsCreateIntentOperation<'a, F>, + Option, + Option, + )> { + Ok((Box::new(self), None, None)) + } + + async fn perform_routing<'a>( + &'a self, + merchant_account: &domain::MerchantAccount, + _business_profile: &domain::Profile, + state: &SessionState, + payment_data: &mut payments::PaymentIntentData, + merchant_key_store: &domain::MerchantKeyStore, + ) -> CustomResult { + let db = &state.store; + let all_connector_accounts = db + .find_merchant_connector_account_by_merchant_id_and_disabled_list( + &state.into(), + merchant_account.get_id(), + false, + merchant_key_store, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Database error when querying for merchant connector accounts")?; + let all_connector_accounts = domain::MerchantConnectorAccounts::new(all_connector_accounts); + let profile_id = &payment_data.payment_intent.profile_id; + let filtered_connector_accounts = all_connector_accounts + .filter_based_on_profile_and_connector_type( + profile_id, + common_enums::ConnectorType::PaymentProcessor, + ); + let connector_and_supporting_payment_method_type = filtered_connector_accounts + .get_connector_and_supporting_payment_method_type_for_session_call(); + let mut session_connector_data = + Vec::with_capacity(connector_and_supporting_payment_method_type.len()); + for (merchant_connector_account, payment_method_type) in + connector_and_supporting_payment_method_type + { + let connector_type = api::GetToken::from(payment_method_type); + if let Ok(connector_data) = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + &merchant_connector_account.connector_name.to_string(), + connector_type, + Some(merchant_connector_account.get_id()), + ) + .inspect_err(|err| { + logger::error!(session_token_error=?err); + }) { + let new_session_connector_data = + api::SessionConnectorData::new(payment_method_type, connector_data, None); + session_connector_data.push(new_session_connector_data) + }; + } + + Ok(api::ConnectorCallType::SessionMultiple( + session_connector_data, + )) + } + + #[instrument(skip_all)] + async fn guard_payment_against_blocklist<'a>( + &'a self, + _state: &SessionState, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + _payment_data: &mut payments::PaymentIntentData, + ) -> CustomResult { + Ok(false) + } +} + +impl From for api::GetToken { + fn from(value: api_models::enums::PaymentMethodType) -> Self { + match value { + api_models::enums::PaymentMethodType::GooglePay => Self::GpayMetadata, + api_models::enums::PaymentMethodType::ApplePay => Self::ApplePayMetadata, + api_models::enums::PaymentMethodType::SamsungPay => Self::SamsungPayMetadata, + api_models::enums::PaymentMethodType::Paypal => Self::PaypalSdkMetadata, + api_models::enums::PaymentMethodType::Paze => Self::PazeMetadata, + _ => Self::Connector, + } + } +} diff --git a/crates/router/src/core/payments/session_operation.rs b/crates/router/src/core/payments/session_operation.rs new file mode 100644 index 000000000000..d7b6ad0d345b --- /dev/null +++ b/crates/router/src/core/payments/session_operation.rs @@ -0,0 +1,186 @@ +use std::fmt::Debug; + +pub use common_enums::enums::CallConnectorAction; +use common_utils::id_type; +use error_stack::ResultExt; +pub use hyperswitch_domain_models::{ + mandates::{CustomerAcceptance, MandateData}, + payment_address::PaymentAddress, + payments::HeaderPayload, + router_data::{PaymentMethodToken, RouterData}, + router_request_types::CustomerDetails, +}; +use router_env::{instrument, tracing}; + +use crate::{ + core::{ + errors::{self, utils::StorageErrorExt, RouterResult}, + payments::{ + call_multiple_connectors_service, + flows::{ConstructFlowSpecificData, Feature}, + operations, + operations::{BoxedOperation, Operation}, + transformers, OperationSessionGetters, OperationSessionSetters, + }, + }, + errors::RouterResponse, + routes::{app::ReqState, SessionState}, + services, + types::{self as router_types, api, domain}, +}; + +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +pub async fn payments_session_core( + state: SessionState, + req_state: ReqState, + merchant_account: domain::MerchantAccount, + profile: domain::Profile, + key_store: domain::MerchantKeyStore, + operation: Op, + req: Req, + payment_id: id_type::GlobalPaymentId, + call_connector_action: CallConnectorAction, + header_payload: HeaderPayload, +) -> RouterResponse +where + F: Send + Clone + Sync, + Req: Send + Sync, + FData: Send + Sync + Clone, + Op: Operation + Send + Sync + Clone, + Req: Debug, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + Res: transformers::ToResponse, + // To create connector flow specific interface data + D: ConstructFlowSpecificData, + RouterData: Feature, + + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + let (payment_data, _req, customer, connector_http_status_code, external_latency) = + payments_session_operation_core::<_, _, _, _, _>( + &state, + req_state, + merchant_account.clone(), + key_store, + profile, + operation.clone(), + req, + payment_id, + call_connector_action, + header_payload.clone(), + ) + .await?; + + Res::generate_response( + payment_data, + customer, + &state.base_url, + operation, + &state.conf.connector_request_reference_id_config, + connector_http_status_code, + external_latency, + header_payload.x_hs_latency, + &merchant_account, + ) +} + +#[allow(clippy::too_many_arguments, clippy::type_complexity)] +#[instrument(skip_all, fields(payment_id, merchant_id))] +pub async fn payments_session_operation_core( + state: &SessionState, + _req_state: ReqState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + profile: domain::Profile, + operation: Op, + req: Req, + payment_id: id_type::GlobalPaymentId, + _call_connector_action: CallConnectorAction, + header_payload: HeaderPayload, +) -> RouterResult<(D, Req, Option, Option, Option)> +where + F: Send + Clone + Sync, + Req: Send + Sync, + Op: Operation + Send + Sync, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + + // To create connector flow specific interface data + D: ConstructFlowSpecificData, + RouterData: Feature, + + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, + FData: Send + Sync + Clone, +{ + let operation: BoxedOperation<'_, F, Req, D> = Box::new(operation); + + let _validate_result = operation + .to_validate_request()? + .validate_request(&req, &merchant_account)?; + + let operations::GetTrackerResponse { mut payment_data } = operation + .to_get_tracker()? + .get_trackers( + state, + &payment_id, + &req, + &merchant_account, + &profile, + &key_store, + &header_payload, + ) + .await?; + + let (_operation, customer) = operation + .to_domain()? + .get_customer_details( + state, + &mut payment_data, + &key_store, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound) + .attach_printable("Failed while fetching/creating customer")?; + + let connector = operation + .to_domain()? + .perform_routing( + &merchant_account, + &profile, + &state.clone(), + &mut payment_data, + &key_store, + ) + .await?; + + let payment_data = match connector { + api::ConnectorCallType::PreDetermined(_connector) => { + todo!() + } + api::ConnectorCallType::Retryable(_connectors) => todo!(), + api::ConnectorCallType::Skip => todo!(), + api::ConnectorCallType::SessionMultiple(connectors) => { + // todo: call surcharge manager for session token call. + Box::pin(call_multiple_connectors_service( + state, + &merchant_account, + &key_store, + connectors, + &operation, + payment_data, + &customer, + None, + &profile, + header_payload.clone(), + )) + .await? + } + }; + + Ok((payment_data, req, customer, None, None)) +} diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 326975081016..4baeade05abf 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -7,8 +7,7 @@ use api_models::payments::{ use common_enums::{Currency, RequestIncrementalAuthorization}; use common_utils::{ consts::X_HS_LATENCY, - fp_utils, - pii::Email, + fp_utils, pii, types::{self as common_utils_type, AmountConvertor, MinorUnit, StringMajorUnitForConnector}, }; use diesel_models::{ @@ -17,7 +16,7 @@ use diesel_models::{ }; use error_stack::{report, ResultExt}; #[cfg(feature = "v2")] -use hyperswitch_domain_models::payments::PaymentConfirmData; +use hyperswitch_domain_models::payments::{PaymentConfirmData, PaymentIntentData}; #[cfg(feature = "v2")] use hyperswitch_domain_models::ApiModelToDieselModelConvertor; use hyperswitch_domain_models::{payments::payment_intent::CustomerData, router_request_types}; @@ -510,6 +509,152 @@ pub async fn construct_router_data_for_psync<'a>( Ok(router_data) } +#[cfg(feature = "v2")] +#[instrument(skip_all)] +#[allow(clippy::too_many_arguments)] +pub async fn construct_payment_router_data_for_sdk_session<'a>( + _state: &'a SessionState, + payment_data: PaymentIntentData, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + customer: &'a Option, + merchant_connector_account: &domain::MerchantConnectorAccount, + _merchant_recipient_data: Option, + header_payload: Option, +) -> RouterResult { + fp_utils::when(merchant_connector_account.is_disabled(), || { + Err(errors::ApiErrorResponse::MerchantConnectorAccountDisabled) + })?; + + let auth_type: types::ConnectorAuthType = merchant_connector_account + .get_connector_account_details() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while parsing value for ConnectorAuthType")?; + + // TODO: Take Globalid and convert to connector reference id + let customer_id = customer + .to_owned() + .map(|customer| customer.id.clone()) + .map(std::borrow::Cow::Owned) + .map(common_utils::id_type::CustomerId::try_from) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Invalid global customer generated, not able to convert to reference id", + )?; + let email = customer + .as_ref() + .and_then(|customer| customer.email.clone()) + .map(pii::Email::from); + let order_details = payment_data + .payment_intent + .order_details + .clone() + .map(|order_details| { + order_details + .into_iter() + .map(|order_detail| order_detail.expose()) + .collect() + }); + // TODO: few fields are repeated in both routerdata and request + let request = types::PaymentsSessionData { + amount: payment_data + .payment_intent + .amount_details + .order_amount + .get_amount_as_i64(), + currency: payment_data.payment_intent.amount_details.currency, + country: payment_data + .payment_intent + .billing_address + .and_then(|billing_address| { + billing_address + .get_inner() + .address + .as_ref() + .and_then(|address| address.country) + }), + // TODO: populate surcharge here + surcharge_details: None, + order_details, + email, + minor_amount: payment_data.payment_intent.amount_details.order_amount, + }; + + // TODO: evaluate the fields in router data, if they are required or not + let router_data = types::RouterData { + flow: PhantomData, + merchant_id: merchant_account.get_id().clone(), + // TODO: evaluate why we need customer id at the connector level. We already have connector customer id. + customer_id, + connector: connector_id.to_owned(), + // TODO: evaluate why we need payment id at the connector level. We already have connector reference id + payment_id: payment_data.payment_intent.id.get_string_repr().to_owned(), + // TODO: evaluate why we need attempt id at the connector level. We already have connector reference id + attempt_id: "".to_string(), + status: enums::AttemptStatus::Started, + payment_method: enums::PaymentMethod::Wallet, + connector_auth_type: auth_type, + description: payment_data + .payment_intent + .description + .as_ref() + .map(|description| description.get_string_repr()) + .map(ToOwned::to_owned), + // TODO: evaluate why we need to send merchant's return url here + // This should be the return url of application, since application takes care of the redirection + return_url: payment_data + .payment_intent + .return_url + .as_ref() + .map(|description| description.get_string_repr()) + .map(ToOwned::to_owned), + // TODO: Create unified address + address: hyperswitch_domain_models::payment_address::PaymentAddress::default(), + auth_type: payment_data.payment_intent.authentication_type, + connector_meta_data: merchant_connector_account.get_metadata(), + connector_wallets_details: None, + request, + response: Err(hyperswitch_domain_models::router_data::ErrorResponse::default()), + amount_captured: None, + minor_amount_captured: None, + access_token: None, + session_token: None, + reference_id: None, + payment_method_status: None, + payment_method_token: None, + connector_customer: None, + recurring_mandate_payment_data: None, + // TODO: This has to be generated as the reference id based on the connector configuration + // Some connectros might not accept accept the global id. This has to be done when generating the reference id + connector_request_reference_id: "".to_string(), + preprocessing_id: None, + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + // TODO: take this based on the env + test_mode: Some(true), + payment_method_balance: None, + connector_api_version: None, + connector_http_status_code: None, + external_latency: None, + apple_pay_flow: None, + frm_metadata: None, + refund_id: None, + dispute_id: None, + connector_response: None, + integrity_check: Ok(()), + additional_merchant_data: None, + header_payload, + connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, + }; + + Ok(router_data) +} + #[cfg(feature = "v2")] #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] @@ -852,6 +997,35 @@ where } } +#[cfg(feature = "v2")] +impl ToResponse for api::PaymentsSessionResponse +where + F: Clone, + Op: Debug, + D: OperationSessionGetters, +{ + #[allow(clippy::too_many_arguments)] + fn generate_response( + payment_data: D, + _customer: Option, + _base_url: &str, + _operation: Op, + _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, + _connector_http_status_code: Option, + _external_latency: Option, + _is_latency_header_enabled: Option, + _merchant_account: &domain::MerchantAccount, + ) -> RouterResponse { + Ok(services::ApplicationResponse::JsonWithHeaders(( + Self { + session_token: payment_data.get_sessions_token(), + payment_id: payment_data.get_payment_intent().id.clone(), + }, + vec![], + ))) + } +} + #[cfg(feature = "v1")] impl ToResponse for api::PaymentsDynamicTaxCalculationResponse where @@ -1503,7 +1677,7 @@ where .and_then(|customer_data| customer_data.email.clone()) .or(customer_details_encrypted_data.email.or(customer .as_ref() - .and_then(|customer| customer.email.clone().map(Email::from)))), + .and_then(|customer| customer.email.clone().map(pii::Email::from)))), phone: customer_table_response .as_ref() .and_then(|customer_data| customer_data.phone.clone()) diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 1584cfae2b95..0f13e08d53e1 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1676,7 +1676,6 @@ impl PayoutLink { route } } - pub struct Profile; #[cfg(all(feature = "olap", feature = "v2"))] impl Profile { diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 48b15c3b2047..93f50f3ed9a2 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -695,8 +695,61 @@ pub async fn payments_connector_session( state: web::Data, req: actix_web::HttpRequest, json_payload: web::Json, + path: web::Path, ) -> impl Responder { - "Session Response" + use hyperswitch_domain_models::payments::PaymentIntentData; + let flow = Flow::PaymentsSessionToken; + + let global_payment_id = path.into_inner(); + tracing::Span::current().record("payment_id", global_payment_id.get_string_repr()); + + let internal_payload = internal_payload_types::PaymentsGenericRequestWithResourceId { + global_payment_id, + payload: json_payload.into_inner(), + }; + + let header_payload = match HeaderPayload::foreign_try_from(req.headers()) { + Ok(headers) => headers, + Err(err) => { + return api::log_and_return_error_response(err); + } + }; + + let locking_action = internal_payload.get_locking_input(flow.clone()); + + Box::pin(api::server_wrap( + flow, + state, + &req, + internal_payload, + |state, auth: auth::AuthenticationData, req, req_state| { + let payment_id = req.global_payment_id; + let request = req.payload; + let operation = payments::operations::PaymentSessionIntent; + payments::payments_session_core::< + api_types::Session, + payment_types::PaymentsSessionResponse, + _, + _, + _, + PaymentIntentData, + >( + state, + req_state, + auth.merchant_account, + auth.profile, + auth.key_store, + operation, + request, + payment_id, + payments::CallConnectorAction::Trigger, + header_payload.clone(), + ) + }, + &auth::HeaderAuth(auth::PublishableKeyAuth), + locking_action, + )) + .await } #[cfg(feature = "v1")] @@ -1786,6 +1839,16 @@ impl GetLockingInput for payment_types::PaymentsSessionRequest { } } +#[cfg(feature = "v2")] +impl GetLockingInput for payment_types::PaymentsSessionRequest { + fn get_locking_input(&self, flow: F) -> api_locking::LockAction + where + F: types::FlowMetric, + { + api_locking::LockAction::NotApplicable + } +} + #[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsDynamicTaxCalculationRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction From 5a98ed65a94a6e8204a3ea34f834033654fdbaa7 Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 28 Nov 2024 19:53:20 +0530 Subject: [PATCH 29/51] feat(connector): worldpay - add dynamic fields and update terminal status mapping (#6468) --- .../src/query/payment_attempt.rs | 4 +- .../src/connectors/worldpay.rs | 4 +- .../src/connectors/worldpay/transformers.rs | 32 +- .../src/payments/payment_attempt.rs | 2 +- .../payment_connector_required_fields.rs | 284 +++++++++++--- crates/router/src/core/refunds.rs | 6 +- crates/router/src/db/kafka_store.rs | 2 +- .../src/mock_db/payment_attempt.rs | 2 +- .../src/payments/payment_attempt.rs | 9 +- .../cypress/e2e/PaymentUtils/WorldPay.js | 367 ++++++++++++++---- .../cypress/fixtures/confirm-body.json | 2 +- .../cypress/fixtures/create-confirm-body.json | 2 +- .../cypress/fixtures/create-mandate-cit.json | 2 +- .../cypress/fixtures/create-mandate-mit.json | 2 +- .../cypress/fixtures/create-pm-id-mit.json | 2 +- .../fixtures/save-card-confirm-body.json | 2 +- cypress-tests/cypress/support/commands.js | 16 +- 17 files changed, 564 insertions(+), 176 deletions(-) diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index 46a84488898b..9dd13e5b0300 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -97,14 +97,14 @@ impl PaymentAttempt { #[cfg(feature = "v1")] pub async fn find_by_connector_transaction_id_payment_id_merchant_id( conn: &PgPooledConn, - connector_transaction_id: &str, + connector_transaction_id: &common_utils::types::ConnectorTransactionId, payment_id: &common_utils::id_type::PaymentId, merchant_id: &common_utils::id_type::MerchantId, ) -> StorageResult { generics::generic_find_one::<::Table, _, _>( conn, dsl::connector_transaction_id - .eq(connector_transaction_id.to_owned()) + .eq(connector_transaction_id.get_id().to_owned()) .and(dsl::payment_id.eq(payment_id.to_owned())) .and(dsl::merchant_id.eq(merchant_id.to_owned())), ) diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay.rs b/crates/hyperswitch_connectors/src/connectors/worldpay.rs index fd67bb60128a..be8f9bca3527 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay.rs @@ -617,7 +617,7 @@ impl ConnectorIntegration fo .map(|id| id.to_string()) }); Ok(PaymentsCaptureRouterData { - status: enums::AttemptStatus::Pending, + status: enums::AttemptStatus::from(response.outcome.clone()), response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::foreign_try_from(( response, @@ -967,12 +967,12 @@ impl ConnectorIntegration for Worldpa }); Ok(RefundExecuteRouterData { response: Ok(RefundsResponseData { + refund_status: enums::RefundStatus::from(response.outcome.clone()), connector_refund_id: ResponseIdStr::foreign_try_from(( response, optional_correlation_id, ))? .id, - refund_status: enums::RefundStatus::Pending, }), ..data.clone() }) diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs index 757760965e76..8ecc3ad792ce 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs @@ -560,7 +560,7 @@ impl From for enums::AttemptStatus { fn from(item: PaymentOutcome) -> Self { match item { PaymentOutcome::Authorized => Self::Authorized, - PaymentOutcome::SentForSettlement => Self::CaptureInitiated, + PaymentOutcome::SentForSettlement => Self::Charged, PaymentOutcome::ThreeDsDeviceDataRequired => Self::DeviceDataCollectionPending, PaymentOutcome::ThreeDsAuthenticationFailed => Self::AuthenticationFailed, PaymentOutcome::ThreeDsChallenged => Self::AuthenticationPending, @@ -574,20 +574,38 @@ impl From for enums::AttemptStatus { } } +impl From for enums::RefundStatus { + fn from(item: PaymentOutcome) -> Self { + match item { + PaymentOutcome::SentForPartialRefund | PaymentOutcome::SentForRefund => Self::Success, + PaymentOutcome::Refused + | PaymentOutcome::FraudHighRisk + | PaymentOutcome::Authorized + | PaymentOutcome::SentForSettlement + | PaymentOutcome::ThreeDsDeviceDataRequired + | PaymentOutcome::ThreeDsAuthenticationFailed + | PaymentOutcome::ThreeDsChallenged + | PaymentOutcome::SentForCancellation + | PaymentOutcome::ThreeDsUnavailable => Self::Failure, + } + } +} + impl From<&EventType> for enums::AttemptStatus { fn from(value: &EventType) -> Self { match value { EventType::SentForAuthorization => Self::Authorizing, - EventType::SentForSettlement => Self::CaptureInitiated, + EventType::SentForSettlement => Self::Charged, EventType::Settled => Self::Charged, EventType::Authorized => Self::Authorized, - EventType::Refused | EventType::SettlementFailed => Self::Failure, - EventType::Cancelled - | EventType::SentForRefund + EventType::Refused + | EventType::SettlementFailed + | EventType::Expired + | EventType::Cancelled + | EventType::Error => Self::Failure, + EventType::SentForRefund | EventType::RefundFailed | EventType::Refunded - | EventType::Error - | EventType::Expired | EventType::Unknown => Self::Pending, } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 4ca6084c958f..289b1bab37df 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -81,7 +81,7 @@ pub trait PaymentAttemptInterface { #[cfg(feature = "v1")] async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( &self, - connector_transaction_id: &str, + connector_transaction_id: &ConnectorTransactionId, payment_id: &id_type::PaymentId, merchant_id: &id_type::MerchantId, storage_scheme: storage_enums::MerchantStorageScheme, diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index 4e80ccf027ec..d359335bf128 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -3132,35 +3132,39 @@ impl Default for settings::RequiredFields { enums::Connector::Worldpay, RequiredFieldFinal { mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ) - ]), + non_mandate: { + let mut pmd_fields = HashMap::from([ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ) + ]); + pmd_fields.extend(get_worldpay_billing_required_fields()); + pmd_fields + }, common: HashMap::new(), } ), @@ -6324,35 +6328,39 @@ impl Default for settings::RequiredFields { enums::Connector::Worldpay, RequiredFieldFinal { mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ) - ]), + non_mandate: { + let mut pmd_fields = HashMap::from([ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ) + ]); + pmd_fields.extend(get_worldpay_billing_required_fields()); + pmd_fields + }, common: HashMap::new(), } ), @@ -12736,3 +12744,163 @@ impl Default for settings::RequiredFields { ])) } } + +pub fn get_worldpay_billing_required_fields() -> HashMap { + HashMap::from([ + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + }, + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "AF".to_string(), + "AU".to_string(), + "AW".to_string(), + "AZ".to_string(), + "BS".to_string(), + "BH".to_string(), + "BD".to_string(), + "BB".to_string(), + "BZ".to_string(), + "BM".to_string(), + "BT".to_string(), + "BO".to_string(), + "BA".to_string(), + "BW".to_string(), + "BR".to_string(), + "BN".to_string(), + "BG".to_string(), + "BI".to_string(), + "KH".to_string(), + "CA".to_string(), + "CV".to_string(), + "KY".to_string(), + "CL".to_string(), + "CO".to_string(), + "KM".to_string(), + "CD".to_string(), + "CR".to_string(), + "CZ".to_string(), + "DZ".to_string(), + "DK".to_string(), + "DJ".to_string(), + "ST".to_string(), + "DO".to_string(), + "EC".to_string(), + "EG".to_string(), + "SV".to_string(), + "ER".to_string(), + "ET".to_string(), + "FK".to_string(), + "FJ".to_string(), + "GM".to_string(), + "GE".to_string(), + "GH".to_string(), + "GI".to_string(), + "GT".to_string(), + "GN".to_string(), + "GY".to_string(), + "HT".to_string(), + "HN".to_string(), + "HK".to_string(), + "HU".to_string(), + "IS".to_string(), + "IN".to_string(), + "ID".to_string(), + "IR".to_string(), + "IQ".to_string(), + "IE".to_string(), + "IL".to_string(), + "IT".to_string(), + "JM".to_string(), + "JP".to_string(), + "JO".to_string(), + "KZ".to_string(), + "KE".to_string(), + "KW".to_string(), + "LA".to_string(), + "LB".to_string(), + "LS".to_string(), + "LR".to_string(), + "LY".to_string(), + "LT".to_string(), + "MO".to_string(), + "MK".to_string(), + "MG".to_string(), + "MW".to_string(), + "MY".to_string(), + "MV".to_string(), + "MR".to_string(), + "MU".to_string(), + "MX".to_string(), + "MD".to_string(), + "MN".to_string(), + "MA".to_string(), + "MZ".to_string(), + "MM".to_string(), + "NA".to_string(), + "NZ".to_string(), + "NI".to_string(), + "NG".to_string(), + "KP".to_string(), + "NO".to_string(), + "AR".to_string(), + "PK".to_string(), + "PG".to_string(), + "PY".to_string(), + "PE".to_string(), + "UY".to_string(), + "PH".to_string(), + "PL".to_string(), + "GB".to_string(), + "QA".to_string(), + "OM".to_string(), + "RO".to_string(), + "RU".to_string(), + "RW".to_string(), + "WS".to_string(), + "SG".to_string(), + "ST".to_string(), + "ZA".to_string(), + "KR".to_string(), + "LK".to_string(), + "SH".to_string(), + "SD".to_string(), + "SR".to_string(), + "SZ".to_string(), + "SE".to_string(), + "CH".to_string(), + "SY".to_string(), + "TW".to_string(), + "TJ".to_string(), + "TZ".to_string(), + "TH".to_string(), + "TT".to_string(), + "TN".to_string(), + "TR".to_string(), + "UG".to_string(), + "UA".to_string(), + "US".to_string(), + "UZ".to_string(), + "VU".to_string(), + "VE".to_string(), + "VN".to_string(), + "ZM".to_string(), + "ZW".to_string(), + ], + }, + value: None, + }, + ), + ]) +} diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index c3e49a49bb14..c706d8854af5 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -8,7 +8,7 @@ use std::collections::HashMap; use api_models::admin::MerchantConnectorInfo; use common_utils::{ ext_traits::{AsyncExt, ValueExt}, - types::{ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit}, + types::{ConnectorTransactionId, MinorUnit}, }; use diesel_models::process_tracker::business_status; use error_stack::{report, ResultExt}; @@ -446,7 +446,7 @@ pub async fn refund_retrieve_core( let payment_attempt = db .find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( - refund.get_connector_transaction_id(), + &refund.connector_transaction_id, payment_id, merchant_id, merchant_account.storage_scheme, @@ -1451,7 +1451,7 @@ pub async fn trigger_refund_execute_workflow( let payment_attempt = db .find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( - refund.get_connector_transaction_id(), + &refund.connector_transaction_id, &refund_core.payment_id, &refund.merchant_id, merchant_account.storage_scheme, diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index a9df1abbc083..e37ff3aa1678 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1454,7 +1454,7 @@ impl PaymentAttemptInterface for KafkaStore { #[cfg(feature = "v1")] async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( &self, - connector_transaction_id: &str, + connector_transaction_id: &common_utils::types::ConnectorTransactionId, payment_id: &id_type::PaymentId, merchant_id: &id_type::MerchantId, storage_scheme: MerchantStorageScheme, diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 83691f46129b..0415625f7ebb 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -254,7 +254,7 @@ impl PaymentAttemptInterface for MockDb { #[cfg(feature = "v1")] async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( &self, - _connector_transaction_id: &str, + _connector_transaction_id: &common_utils::types::ConnectorTransactionId, _payment_id: &common_utils::id_type::PaymentId, _merchant_id: &common_utils::id_type::MerchantId, _storage_scheme: storage_enums::MerchantStorageScheme, diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 9b9121952b16..4eedfd5005e6 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -151,7 +151,7 @@ impl PaymentAttemptInterface for RouterStore { #[instrument(skip_all)] async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( &self, - connector_transaction_id: &str, + connector_transaction_id: &ConnectorTransactionId, payment_id: &common_utils::id_type::PaymentId, merchant_id: &common_utils::id_type::MerchantId, _storage_scheme: MerchantStorageScheme, @@ -786,7 +786,7 @@ impl PaymentAttemptInterface for KVRouterStore { #[instrument(skip_all)] async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( &self, - connector_transaction_id: &str, + connector_transaction_id: &ConnectorTransactionId, payment_id: &common_utils::id_type::PaymentId, merchant_id: &common_utils::id_type::MerchantId, storage_scheme: MerchantStorageScheme, @@ -811,8 +811,9 @@ impl PaymentAttemptInterface for KVRouterStore { MerchantStorageScheme::RedisKv => { // We assume that PaymentAttempt <=> PaymentIntent is a one-to-one relation for now let lookup_id = format!( - "pa_conn_trans_{}_{connector_transaction_id}", - merchant_id.get_string_repr() + "pa_conn_trans_{}_{}", + merchant_id.get_string_repr(), + connector_transaction_id.get_id() ); let lookup = fallback_reverse_lookup_not_found!( self.get_lookup_by_lookup_id(&lookup_id, storage_scheme) diff --git a/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js b/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js index 5b950219fb25..d45458369c59 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js @@ -78,15 +78,17 @@ const payment_method_data_3ds = { billing: null }; -const singleUseMandateData = { - customer_acceptance: { - acceptance_type: "offline", - accepted_at: "1963-05-03T04:07:52.723Z", - online: { - ip_address: "125.0.0.1", - user_agent: "amet irure esse", - }, +const offileCustomerAcceptance = { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", }, +}; + +const singleUseMandateData = { + customer_acceptance: offileCustomerAcceptance, mandate_type: { single_use: { amount: 8000, @@ -102,7 +104,8 @@ export const connectorDetails = { currency: "USD", customer_acceptance: null, setup_future_usage: "on_session", - }, Response: { + }, + Response: { status: 200, body: { status: "requires_payment_method", @@ -117,7 +120,6 @@ export const connectorDetails = { payment_method_data: { card: successfulNoThreeDsCardDetailsRequest, }, - currency: "USD", customer_acceptance: null, setup_future_usage: "on_session", billing: billing, @@ -127,7 +129,7 @@ export const connectorDetails = { body: { status: "requires_capture", payment_method: "card", - payment_method_type: "debit", + payment_method_type: "credit", attempt_count: 1, payment_method_data: paymentMethodDataNoThreeDsResponse, }, @@ -140,16 +142,15 @@ export const connectorDetails = { payment_method_data: { card: successfulNoThreeDsCardDetailsRequest, }, - currency: "USD", customer_acceptance: null, setup_future_usage: "on_session", }, Response: { status: 200, body: { - status: "processing", + status: "succeeded", payment_method: "card", - payment_method_type: "debit", + payment_method_type: "credit", attempt_count: 1, payment_method_data: paymentMethodDataNoThreeDsResponse, }, @@ -168,9 +169,9 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "succeeded", amount: 6500, - amount_capturable: 6500, + amount_capturable: 0, }, }, }, @@ -187,9 +188,9 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "partially_captured", amount: 6500, - amount_capturable: 6500, + amount_capturable: 0, }, }, }, @@ -226,23 +227,38 @@ export const connectorDetails = { }, currency: "USD", setup_future_usage: "on_session", - customer_acceptance: { - acceptance_type: "offline", - accepted_at: "1963-05-03T04:07:52.723Z", - online: { - ip_address: "127.0.0.1", - user_agent: "amet irure esse", - }, + customer_acceptance: offileCustomerAcceptance, + }, + Response: { + body: { + status: "requires_capture", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, }, + setup_future_usage: "off_session", + customer_acceptance: offileCustomerAcceptance, }, Response: { - status: 400, + status: 200, body: { - error: { - type: "invalid_request", - message: "Missing required param: payment_method_data", - code: "IR_04" - } + status: "requires_capture", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", }, }, }, @@ -255,22 +271,43 @@ export const connectorDetails = { currency: "USD", setup_future_usage: "on_session", browser_info, - customer_acceptance: { - acceptance_type: "offline", - accepted_at: "1963-05-03T04:07:52.723Z", - online: { - ip_address: "127.0.0.1", - user_agent: "amet irure esse", - }, - }, + customer_acceptance: offileCustomerAcceptance, }, Response: { status: 200, body: { - status: "processing" + status: "succeeded" }, } }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + setup_future_usage: "off_session", + customer_acceptance: offileCustomerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, "3DSManualCapture": { Request: { payment_method: "card", @@ -278,7 +315,6 @@ export const connectorDetails = { payment_method_data: { card: successfulThreeDsTestCardDetailsRequest, }, - currency: "USD", customer_acceptance: null, setup_future_usage: "on_session", browser_info, @@ -313,10 +349,6 @@ export const connectorDetails = { }, }, }, - - /** - * Variation cases - */ CaptureCapturedAmount: { Request: { Request: { @@ -334,7 +366,7 @@ export const connectorDetails = { error: { type: "invalid_request", message: - "This Payment could not be captured because it has a capture_method of automatic. The expected state is manual_multiple", + "This Payment could not be captured because it has a payment.status of succeeded. The expected state is requires_capture, partially_captured_and_capturable, processing", code: "IR_14", }, }, @@ -346,7 +378,6 @@ export const connectorDetails = { payment_method_data: { card: successfulNoThreeDsCardDetailsRequest, }, - currency: "USD", customer_acceptance: null, }, Response: { @@ -355,37 +386,17 @@ export const connectorDetails = { error: { type: "invalid_request", message: - "You cannot confirm this payment because it has status processing", + "You cannot confirm this payment because it has status succeeded", code: "IR_16", }, }, }, }, - - /** - * Not implemented or not ready for running test cases - * - Refunds - * - Mandates - */ Refund: { Request: {}, Response: { body: { - error: { - type: "invalid_request", - message: "This Payment could not be refund because it has a status of processing. The expected state is succeeded, partially_captured", - code: "IR_14" - } - } - }, - ResponseCustom: { - status: 400, - body: { - error: { - type: "invalid_request", - message: "This Payment could not be refund because it has a status of processing. The expected state is succeeded, partially_captured", - code: "IR_14", - }, + status: "succeeded" }, }, }, @@ -393,11 +404,35 @@ export const connectorDetails = { Request: {}, Response: { body: { - error: { - type: "invalid_request", - message: "This Payment could not be refund because it has a status of processing. The expected state is succeeded, partially_captured", - code: "IR_14" - } + status: "succeeded" + } + } + }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + }, + Response: { + body: { + status: "succeeded" + } + } + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + }, + Response: { + body: { + status: "succeeded" } } }, @@ -405,14 +440,74 @@ export const connectorDetails = { Request: {}, Response: { body: { - error: { - type: "invalid_request", - message: "Refund does not exist in our records.", - code: "HE_02" - } + status: "succeeded" } } }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", @@ -423,12 +518,116 @@ export const connectorDetails = { mandate_data: singleUseMandateData, }, Response: { + trigger_skip: true, + status: 200, body: { - error: { - type: "invalid_request", - message: "Setup Mandate flow for Worldpay is not implemented", - code: "IR_00" - } + error_code: "internalErrorOccurred", + error_message: "We cannot currently process your request. Please contact support.", + status: "failed", + payment_method_id: null + }, + }, + }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + trigger_skip: true, + status: 200, + body: { + error_code: "internalErrorOccurred", + error_message: "We cannot currently process your request. Please contact support.", + status: "failed", + payment_method_id: null + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: offileCustomerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: offileCustomerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandate3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDsTestCardDetailsRequest, + }, + currency: "USD", + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: offileCustomerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDsTestCardDetailsRequest, + }, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: offileCustomerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", }, }, }, diff --git a/cypress-tests/cypress/fixtures/confirm-body.json b/cypress-tests/cypress/fixtures/confirm-body.json index fa4769b627f4..d92be2d91e7c 100644 --- a/cypress-tests/cypress/fixtures/confirm-body.json +++ b/cypress-tests/cypress/fixtures/confirm-body.json @@ -29,7 +29,7 @@ "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "language": "en-US", - "color_depth": 30, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/fixtures/create-confirm-body.json b/cypress-tests/cypress/fixtures/create-confirm-body.json index a779557ed5e7..2a9e8b19ee12 100644 --- a/cypress-tests/cypress/fixtures/create-confirm-body.json +++ b/cypress-tests/cypress/fixtures/create-confirm-body.json @@ -70,7 +70,7 @@ "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "language": "en-US", - "color_depth": 30, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/fixtures/create-mandate-cit.json b/cypress-tests/cypress/fixtures/create-mandate-cit.json index c96284ea99ba..d33cb8b91c7f 100644 --- a/cypress-tests/cypress/fixtures/create-mandate-cit.json +++ b/cypress-tests/cypress/fixtures/create-mandate-cit.json @@ -80,7 +80,7 @@ "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "language": "en-US", - "color_depth": 30, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/fixtures/create-mandate-mit.json b/cypress-tests/cypress/fixtures/create-mandate-mit.json index 9612eac32091..7b70279979aa 100644 --- a/cypress-tests/cypress/fixtures/create-mandate-mit.json +++ b/cypress-tests/cypress/fixtures/create-mandate-mit.json @@ -26,7 +26,7 @@ "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "language": "en-US", - "color_depth": 30, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/fixtures/create-pm-id-mit.json b/cypress-tests/cypress/fixtures/create-pm-id-mit.json index c78cf2a74c5a..77d3c76b8b22 100644 --- a/cypress-tests/cypress/fixtures/create-pm-id-mit.json +++ b/cypress-tests/cypress/fixtures/create-pm-id-mit.json @@ -31,7 +31,7 @@ "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "language": "en-US", - "color_depth": 30, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/fixtures/save-card-confirm-body.json b/cypress-tests/cypress/fixtures/save-card-confirm-body.json index 17a860fd1885..615cec8abf72 100644 --- a/cypress-tests/cypress/fixtures/save-card-confirm-body.json +++ b/cypress-tests/cypress/fixtures/save-card-confirm-body.json @@ -32,7 +32,7 @@ "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "language": "en-US", - "color_depth": 30, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index ee64d3247c53..5a6e1b4783e4 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -1793,13 +1793,13 @@ Cypress.Commands.add( for (const key in response.body.attempts) { if ( response.body.attempts[key].attempt_id === - `${payment_id}_${attempt}` && + `${payment_id}_${attempt}` && response.body.status === "succeeded" ) { expect(response.body.attempts[key].status).to.equal("charged"); } else if ( response.body.attempts[key].attempt_id === - `${payment_id}_${attempt}` && + `${payment_id}_${attempt}` && response.body.status === "requires_customer_action" ) { expect(response.body.attempts[key].status).to.equal( @@ -1915,8 +1915,10 @@ Cypress.Commands.add( ); expect(response.body.customer, "customer").to.not.be.empty; expect(response.body.profile_id, "profile_id").to.not.be.null; - expect(response.body.payment_method_id, "payment_method_id").to.not.be - .null; + if (response.body.status !== "failed") { + expect(response.body.payment_method_id, "payment_method_id").to.not.be + .null; + } if (requestBody.mandate_data === null) { expect(response.body).to.have.property("payment_method_id"); @@ -2195,11 +2197,11 @@ Cypress.Commands.add( if (globalState.get("connectorId") !== "cybersource") { return; } - + const apiKey = globalState.get("apiKey"); const baseUrl = globalState.get("baseUrl"); const url = `${baseUrl}/payments`; - + cy.request({ method: "POST", url: url, @@ -2211,7 +2213,7 @@ Cypress.Commands.add( body: requestBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - + if (response.status === 200) { expect(response.headers["content-type"]).to.include("application/json"); From 60bc7d89774b7f7a3463eb4e29ec54e4a7163f25 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 29 Nov 2024 00:22:09 +0000 Subject: [PATCH 30/51] chore(version): 2024.11.29.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd80af8cf7a5..a215cbe05578 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.11.29.0 + +### Features + +- **connector:** Worldpay - add dynamic fields and update terminal status mapping ([#6468](https://github.com/juspay/hyperswitch/pull/6468)) ([`5a98ed6`](https://github.com/juspay/hyperswitch/commit/5a98ed65a94a6e8204a3ea34f834033654fdbaa7)) +- Add support for sdk session call in v2 ([#6502](https://github.com/juspay/hyperswitch/pull/6502)) ([`707f48c`](https://github.com/juspay/hyperswitch/commit/707f48ceda789185187d23e35f483e117c67b81b)) + +### Bug Fixes + +- **analytics:** Fix bugs in payments page metrics in Analytics V2 dashboard ([#6654](https://github.com/juspay/hyperswitch/pull/6654)) ([`93459fd`](https://github.com/juspay/hyperswitch/commit/93459fde5fb95f31e8f1429e806cde8e7496dd84)) + +**Full Changelog:** [`2024.11.28.0...2024.11.29.0`](https://github.com/juspay/hyperswitch/compare/2024.11.28.0...2024.11.29.0) + +- - - + ## 2024.11.28.0 ### Bug Fixes From abcaa539eccdae86c7a68fd4ce60ab9889f9fb43 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar <83278309+tsdk02@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:16:16 +0530 Subject: [PATCH 31/51] fix(analytics): fix first_attempt filter value parsing for Payments (#6667) --- crates/analytics/src/query.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/analytics/src/query.rs b/crates/analytics/src/query.rs index e80f762c41bf..caa112ec1759 100644 --- a/crates/analytics/src/query.rs +++ b/crates/analytics/src/query.rs @@ -459,7 +459,8 @@ impl ToSql for common_utils::id_type::CustomerId { impl ToSql for bool { fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result { - Ok(self.to_string().to_owned()) + let flag = *self; + Ok(i8::from(flag).to_string()) } } From b1cdff0950f32b38e3ff0eeac2b726ba0f671051 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar <83278309+tsdk02@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:16:31 +0530 Subject: [PATCH 32/51] fix(opensearch): handle empty free-text query search in global search (#6685) --- crates/analytics/src/opensearch.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/analytics/src/opensearch.rs b/crates/analytics/src/opensearch.rs index 84a2b9db3d4e..e8726840a2ea 100644 --- a/crates/analytics/src/opensearch.rs +++ b/crates/analytics/src/opensearch.rs @@ -510,14 +510,15 @@ impl OpenSearchQueryBuilder { case_sensitive_filters: Vec<&(String, Vec)>, ) -> Vec { let mut filter_array = Vec::new(); - - filter_array.push(json!({ - "multi_match": { - "type": "phrase", - "query": self.query, - "lenient": true - } - })); + if !self.query.is_empty() { + filter_array.push(json!({ + "multi_match": { + "type": "phrase", + "query": self.query, + "lenient": true + } + })); + } let case_sensitive_json_filters = case_sensitive_filters .into_iter() From 05726262e6a3f6fcb18c0dbe41c18e4d6e84608b Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:26:57 +0530 Subject: [PATCH 33/51] refactor(router): [ZSL] remove partially capture status (#6689) --- .../src/connectors/zsl/transformers.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/zsl/transformers.rs b/crates/hyperswitch_connectors/src/connectors/zsl/transformers.rs index 810193251137..b2ba05b7cdec 100644 --- a/crates/hyperswitch_connectors/src/connectors/zsl/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/zsl/transformers.rs @@ -414,20 +414,10 @@ impl TryFrom() .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - let txn_amount = item - .response - .txn_amt - .parse::() - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - let status = if txn_amount > paid_amount { - enums::AttemptStatus::PartialCharged - } else { - enums::AttemptStatus::Charged - }; if item.response.status == "0" { Ok(Self { - status, + status: enums::AttemptStatus::Charged, amount_captured: Some(paid_amount), response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.txn_id.clone()), From 6a2070172b8d845e6db36b7789defddf8ea4e1e9 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:28:14 +0530 Subject: [PATCH 34/51] fix(router): populate card network in the network transaction id based MIT flow (#6690) --- config/config.example.toml | 3 +++ config/deployments/production.toml | 2 ++ crates/router/src/connector/cybersource/transformers.rs | 2 +- crates/router/src/core/payments/helpers.rs | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index ba14ed881cf4..76a38192909f 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -787,6 +787,9 @@ check_token_status_url= "" # base url to check token status from token servic [network_tokenization_supported_connectors] connector_list = "cybersource" # Supported connectors for network tokenization +[network_transaction_id_supported_connectors] +connector_list = "stripe,adyen,cybersource" # Supported connectors for network transaction id + [grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration host = "localhost" # Client Host port = 7000 # Client Port diff --git a/config/deployments/production.toml b/config/deployments/production.toml index a859d08ac4ab..c266b94bba6a 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -176,6 +176,8 @@ bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay" # M card.credit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card card.debit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card +[network_transaction_id_supported_connectors] +connector_list = "stripe,adyen,cybersource" [payouts] payout_eligibility = true # Defaults the eligibility of a payout method to true in case connector does not provide checks for payout eligibility diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index 22fa8965b7ae..2ea36e3e35e5 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -620,7 +620,7 @@ impl .as_ref() .map(|card_network| match card_network.to_lowercase().as_str() { "amex" => "internet", - "discover" => "dipb", + "discover" => "internet", "mastercard" => "spa", "visa" => "internet", _ => "internet", diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 8435f09e8f3c..219a4d90519b 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4674,7 +4674,7 @@ pub async fn get_additional_payment_data( api_models::payments::AdditionalPaymentData::Card(Box::new( api_models::payments::AdditionalCardInfo { card_issuer: card_info.card_issuer, - card_network, + card_network: card_info.card_network, bank_code: card_info.bank_code, card_type: card_info.card_type, card_issuing_country: card_info.card_issuing_country, From 9998c557c9c88496ffbee883e7fc4b76614cff50 Mon Sep 17 00:00:00 2001 From: Debarati Ghatak <88573135+cookieg13@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:32:21 +0530 Subject: [PATCH 35/51] feat(connector): [Adyen] Fetch email from customer email for payment request (#6676) --- .../src/connector/adyen/transformers.rs | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index b6dea0eac214..12cef3aa9f78 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -728,7 +728,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for JCSVoucherData { Ok(Self { first_name: item.get_billing_first_name()?, last_name: item.get_optional_billing_last_name(), - shopper_email: item.get_billing_email()?, + shopper_email: item.get_billing_email().or(item.request.get_email())?, telephone_number: item.get_billing_phone_number()?, }) } @@ -2552,11 +2552,18 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for DokuBankData { Ok(Self { first_name: item.get_billing_first_name()?, last_name: item.get_optional_billing_last_name(), - shopper_email: item.get_billing_email()?, + shopper_email: item.get_billing_email().or(item.request.get_email())?, }) } } +fn get_optional_shopper_email(item: &types::PaymentsAuthorizeRouterData) -> Option { + match item.get_billing_email() { + Ok(email) => Some(email), + Err(_) => item.request.get_optional_email(), + } +} + impl<'a> TryFrom<&domain::payments::CardRedirectData> for AdyenPaymentMethod<'a> { type Error = Error; fn try_from( @@ -2593,6 +2600,7 @@ impl<'a> let amount = get_amount_data(item); let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?; let shopper_interaction = AdyenShopperInteraction::from(item.router_data); + let shopper_email = get_optional_shopper_email(item.router_data); let (recurring_processing_model, store_payment_method, shopper_reference) = get_recurring_processing_model(item.router_data)?; let browser_info = None; @@ -2694,7 +2702,7 @@ impl<'a> mpi_data: None, telephone_number: None, shopper_name: None, - shopper_email: None, + shopper_email, shopper_locale: None, social_security_number: None, billing_address: None, @@ -2742,7 +2750,7 @@ impl<'a> let return_url = item.router_data.request.get_return_url()?; let card_holder_name = item.router_data.get_optional_billing_full_name(); let payment_method = AdyenPaymentMethod::try_from((card_data, card_holder_name))?; - let shopper_email = item.router_data.get_optional_billing_email(); + let shopper_email = get_optional_shopper_email(item.router_data); let shopper_name = get_shopper_name(item.router_data.get_optional_billing()); Ok(AdyenPaymentRequest { @@ -2801,6 +2809,7 @@ impl<'a> let return_url = item.router_data.request.get_return_url()?; let payment_method = AdyenPaymentMethod::try_from((bank_debit_data, item.router_data))?; let country_code = get_country_code(item.router_data.get_optional_billing()); + let shopper_email = get_optional_shopper_email(item.router_data); let request = AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -2814,7 +2823,7 @@ impl<'a> mpi_data: None, shopper_name: None, shopper_locale: None, - shopper_email: item.router_data.get_optional_billing_email(), + shopper_email, social_security_number: None, telephone_number: None, billing_address: None, @@ -2860,6 +2869,7 @@ impl<'a> let billing_address = get_address_info(item.router_data.get_optional_billing()).and_then(Result::ok); let shopper_name = get_shopper_name(item.router_data.get_optional_billing()); + let shopper_email = get_optional_shopper_email(item.router_data); let request = AdyenPaymentRequest { amount, @@ -2873,7 +2883,7 @@ impl<'a> additional_data, shopper_name, shopper_locale: None, - shopper_email: item.router_data.get_optional_billing_email(), + shopper_email, social_security_number, mpi_data: None, telephone_number: None, @@ -2913,6 +2923,7 @@ impl<'a> let shopper_interaction = AdyenShopperInteraction::from(item.router_data); let payment_method = AdyenPaymentMethod::try_from((bank_transfer_data, item.router_data))?; let return_url = item.router_data.request.get_return_url()?; + let shopper_email = get_optional_shopper_email(item.router_data); let request = AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -2926,7 +2937,7 @@ impl<'a> mpi_data: None, shopper_name: None, shopper_locale: None, - shopper_email: item.router_data.get_optional_billing_email(), + shopper_email, social_security_number: None, telephone_number: None, billing_address: None, @@ -2965,6 +2976,7 @@ impl<'a> let shopper_interaction = AdyenShopperInteraction::from(item.router_data); let return_url = item.router_data.request.get_router_return_url()?; let payment_method = AdyenPaymentMethod::try_from(gift_card_data)?; + let shopper_email = get_optional_shopper_email(item.router_data); let request = AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -2978,7 +2990,7 @@ impl<'a> mpi_data: None, shopper_name: None, shopper_locale: None, - shopper_email: item.router_data.get_optional_billing_email(), + shopper_email, telephone_number: None, billing_address: None, delivery_address: None, @@ -3028,6 +3040,7 @@ impl<'a> let line_items = Some(get_line_items(item)); let billing_address = get_address_info(item.router_data.get_optional_billing()).and_then(Result::ok); + let shopper_email = get_optional_shopper_email(item.router_data); Ok(AdyenPaymentRequest { amount, @@ -3042,7 +3055,7 @@ impl<'a> mpi_data: None, telephone_number: None, shopper_name: None, - shopper_email: item.router_data.get_optional_billing_email(), + shopper_email, shopper_locale, social_security_number: None, billing_address, @@ -3094,11 +3107,13 @@ fn get_shopper_email( .as_ref() .ok_or(errors::ConnectorError::MissingPaymentMethodType)?; match payment_method_type { - storage_enums::PaymentMethodType::Paypal => Ok(Some(item.get_billing_email()?)), - _ => Ok(item.get_optional_billing_email()), + storage_enums::PaymentMethodType::Paypal => { + Ok(Some(item.get_billing_email().or(item.request.get_email())?)) + } + _ => Ok(get_optional_shopper_email(item)), } } else { - Ok(item.get_optional_billing_email()) + Ok(get_optional_shopper_email(item)) } } @@ -3205,7 +3220,7 @@ impl<'a> get_recurring_processing_model(item.router_data)?; let return_url = item.router_data.request.get_return_url()?; let shopper_name = get_shopper_name(item.router_data.get_optional_billing()); - let shopper_email = item.router_data.get_optional_billing_email(); + let shopper_email = get_optional_shopper_email(item.router_data); let billing_address = get_address_info(item.router_data.get_optional_billing()).and_then(Result::ok); let delivery_address = @@ -3273,7 +3288,7 @@ impl<'a> let shopper_interaction = AdyenShopperInteraction::from(item.router_data); let return_url = item.router_data.request.get_return_url()?; let shopper_name = get_shopper_name(item.router_data.get_optional_billing()); - let shopper_email = item.router_data.get_optional_billing_email(); + let shopper_email = get_optional_shopper_email(item.router_data); let telephone_number = item .router_data .get_billing_phone() From ae7d16e23699c8ed95a7e2eab7539cfe20f847d0 Mon Sep 17 00:00:00 2001 From: Prajjwal Kumar Date: Fri, 29 Nov 2024 15:32:56 +0530 Subject: [PATCH 36/51] refactor(currency_conversion): release redis lock if api call fails (#6671) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/router/src/utils/currency.rs | 45 ++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/crates/router/src/utils/currency.rs b/crates/router/src/utils/currency.rs index 2173478ab673..9ab2780da732 100644 --- a/crates/router/src/utils/currency.rs +++ b/crates/router/src/utils/currency.rs @@ -7,6 +7,7 @@ use error_stack::ResultExt; use masking::PeekInterface; use once_cell::sync::Lazy; use redis_interface::DelReply; +use router_env::{instrument, tracing}; use rust_decimal::Decimal; use strum::IntoEnumIterator; use tokio::{sync::RwLock, time::sleep}; @@ -150,11 +151,13 @@ impl TryFrom for ExchangeRates { let mut conversion_usable: HashMap = HashMap::new(); for (curr, conversion) in value.conversion { let enum_curr = enums::Currency::from_str(curr.as_str()) - .change_context(ForexCacheError::ConversionError)?; + .change_context(ForexCacheError::ConversionError) + .attach_printable("Unable to Convert currency received")?; conversion_usable.insert(enum_curr, CurrencyFactors::from(conversion)); } let base_curr = enums::Currency::from_str(value.base_currency.as_str()) - .change_context(ForexCacheError::ConversionError)?; + .change_context(ForexCacheError::ConversionError) + .attach_printable("Unable to convert base currency")?; Ok(Self { base_currency: base_curr, conversion: conversion_usable, @@ -170,6 +173,8 @@ impl From for CurrencyFactors { } } } + +#[instrument(skip_all)] pub async fn get_forex_rates( state: &SessionState, call_delay: i64, @@ -235,6 +240,7 @@ async fn successive_fetch_and_save_forex( Ok(rates) => Ok(successive_save_data_to_redis_local(state, rates).await?), Err(error) => stale_redis_data.ok_or({ logger::error!(?error); + release_redis_lock(state).await?; ForexCacheError::ApiUnresponsive.into() }), } @@ -254,9 +260,9 @@ async fn successive_save_data_to_redis_local( ) -> CustomResult { Ok(save_forex_to_redis(state, &forex) .await - .async_and_then(|_rates| async { release_redis_lock(state).await }) + .async_and_then(|_rates| release_redis_lock(state)) .await - .async_and_then(|_val| async { Ok(save_forex_to_local(forex.clone()).await) }) + .async_and_then(|_val| save_forex_to_local(forex.clone())) .await .map_or_else( |error| { @@ -336,11 +342,15 @@ async fn fetch_forex_rates( false, ) .await - .change_context(ForexCacheError::ApiUnresponsive)?; + .change_context(ForexCacheError::ApiUnresponsive) + .attach_printable("Primary forex fetch api unresponsive")?; let forex_response = response .json::() .await - .change_context(ForexCacheError::ParsingError)?; + .change_context(ForexCacheError::ParsingError) + .attach_printable( + "Unable to parse response received from primary api into ForexResponse", + )?; logger::info!("{:?}", forex_response); @@ -392,11 +402,16 @@ pub async fn fallback_fetch_forex_rates( false, ) .await - .change_context(ForexCacheError::ApiUnresponsive)?; + .change_context(ForexCacheError::ApiUnresponsive) + .attach_printable("Fallback forex fetch api unresponsive")?; + let fallback_forex_response = response .json::() .await - .change_context(ForexCacheError::ParsingError)?; + .change_context(ForexCacheError::ParsingError) + .attach_printable( + "Unable to parse response received from falback api into ForexResponse", + )?; logger::info!("{:?}", fallback_forex_response); let mut conversions: HashMap = HashMap::new(); @@ -453,6 +468,7 @@ async fn release_redis_lock( .delete_key(REDIX_FOREX_CACHE_KEY) .await .change_context(ForexCacheError::RedisLockReleaseFailed) + .attach_printable("Unable to release redis lock") } async fn acquire_redis_lock(state: &SessionState) -> CustomResult { @@ -475,6 +491,7 @@ async fn acquire_redis_lock(state: &SessionState) -> CustomResult Date: Fri, 29 Nov 2024 15:34:11 +0530 Subject: [PATCH 37/51] feat(connector): [REDSYS] add Connector Template Code (#6659) --- config/config.example.toml | 1 + config/deployments/integration_test.toml | 1 + config/deployments/production.toml | 1 + config/deployments/sandbox.toml | 1 + config/development.toml | 2 + config/docker_compose.toml | 2 + crates/api_models/src/connector_enums.rs | 2 + crates/common_enums/src/connector_enums.rs | 1 + .../hyperswitch_connectors/src/connectors.rs | 6 +- .../src/connectors/redsys.rs | 563 ++++++++++++++++++ .../src/connectors/redsys/transformers.rs | 228 +++++++ .../src/default_implementations.rs | 32 + .../src/default_implementations_v2.rs | 22 + crates/hyperswitch_interfaces/src/configs.rs | 1 + crates/router/src/connector.rs | 9 +- .../connector_integration_v2_impls.rs | 3 + crates/router/src/core/payments/flows.rs | 3 + crates/router/src/types/api.rs | 1 + crates/router/src/types/transformers.rs | 1 + crates/router/tests/connectors/main.rs | 1 + crates/router/tests/connectors/redsys.rs | 421 +++++++++++++ .../router/tests/connectors/sample_auth.toml | 3 + crates/test_utils/src/connector_auth.rs | 1 + loadtest/config/development.toml | 1 + scripts/add_connector.sh | 2 +- 25 files changed, 1302 insertions(+), 7 deletions(-) create mode 100644 crates/hyperswitch_connectors/src/connectors/redsys.rs create mode 100644 crates/hyperswitch_connectors/src/connectors/redsys/transformers.rs create mode 100644 crates/router/tests/connectors/redsys.rs diff --git a/config/config.example.toml b/config/config.example.toml index 76a38192909f..03ebedf0d35e 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -252,6 +252,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index a4e1b1e9b13f..fbf80ced5f17 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -93,6 +93,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" riskified.base_url = "https://sandbox.riskified.com/api" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index c266b94bba6a..befd70795d7d 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -97,6 +97,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://api.juspay.in" +redsys.base_url = "https://sis.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://wh.riskified.com/api/" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 070a32ef87b6..2defc5729cf2 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -97,6 +97,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" diff --git a/config/development.toml b/config/development.toml index 2388607a4898..d8251cfce7bf 100644 --- a/config/development.toml +++ b/config/development.toml @@ -155,6 +155,7 @@ cards = [ "plaid", "powertranz", "prophetpay", + "redsys", "shift4", "square", "stax", @@ -268,6 +269,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index d72141d9c372..976a2fa2a42c 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -183,6 +183,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" @@ -276,6 +277,7 @@ cards = [ "plaid", "powertranz", "prophetpay", + "redsys", "shift4", "square", "stax", diff --git a/crates/api_models/src/connector_enums.rs b/crates/api_models/src/connector_enums.rs index 3d027c026d7e..4931b8dbd922 100644 --- a/crates/api_models/src/connector_enums.rs +++ b/crates/api_models/src/connector_enums.rs @@ -113,6 +113,7 @@ pub enum Connector { Prophetpay, Rapyd, Razorpay, + // Redsys, Shift4, Square, Stax, @@ -251,6 +252,7 @@ impl Connector { | Self::Powertranz | Self::Prophetpay | Self::Rapyd + // | Self::Redsys | Self::Shift4 | Self::Square | Self::Stax diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index c3bbf6e078fe..421a51205dea 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -108,6 +108,7 @@ pub enum RoutableConnectors { Prophetpay, Rapyd, Razorpay, + // Redsys, Riskified, Shift4, Signifyd, diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index d1cdb85e57f6..fdd87a25e8a5 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -28,6 +28,7 @@ pub mod payeezy; pub mod payu; pub mod powertranz; pub mod razorpay; +pub mod redsys; pub mod shift4; pub mod square; pub mod stax; @@ -49,6 +50,7 @@ pub use self::{ helcim::Helcim, inespay::Inespay, jpmorgan::Jpmorgan, mollie::Mollie, multisafepay::Multisafepay, nexinets::Nexinets, nexixpay::Nexixpay, nomupay::Nomupay, novalnet::Novalnet, payeezy::Payeezy, payu::Payu, powertranz::Powertranz, razorpay::Razorpay, - shift4::Shift4, square::Square, stax::Stax, taxjar::Taxjar, thunes::Thunes, tsys::Tsys, - volt::Volt, worldline::Worldline, worldpay::Worldpay, xendit::Xendit, zen::Zen, zsl::Zsl, + redsys::Redsys, shift4::Shift4, square::Square, stax::Stax, taxjar::Taxjar, thunes::Thunes, + tsys::Tsys, volt::Volt, worldline::Worldline, worldpay::Worldpay, xendit::Xendit, zen::Zen, + zsl::Zsl, }; diff --git a/crates/hyperswitch_connectors/src/connectors/redsys.rs b/crates/hyperswitch_connectors/src/connectors/redsys.rs new file mode 100644 index 000000000000..760ce148b40a --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/redsys.rs @@ -0,0 +1,563 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as redsys; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Redsys { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Redsys { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Redsys {} +impl api::PaymentSession for Redsys {} +impl api::ConnectorAccessToken for Redsys {} +impl api::MandateSetup for Redsys {} +impl api::PaymentAuthorize for Redsys {} +impl api::PaymentSync for Redsys {} +impl api::PaymentCapture for Redsys {} +impl api::PaymentVoid for Redsys {} +impl api::Refund for Redsys {} +impl api::RefundExecute for Redsys {} +impl api::RefundSync for Redsys {} +impl api::PaymentToken for Redsys {} + +impl ConnectorIntegration + for Redsys +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Redsys +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Redsys { + fn id(&self) -> &'static str { + "redsys" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + // TODO! Check connector documentation, on which unit they are processing the currency. + // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, + // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.redsys.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = redsys::RedsysAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: redsys::RedsysErrorResponse = res + .response + .parse_struct("RedsysErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Redsys { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Redsys { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Redsys {} + +impl ConnectorIntegration for Redsys {} + +impl ConnectorIntegration for Redsys { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = redsys::RedsysRouterData::from((amount, req)); + let connector_req = redsys::RedsysPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: redsys::RedsysPaymentsResponse = res + .response + .parse_struct("Redsys PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Redsys { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: redsys::RedsysPaymentsResponse = res + .response + .parse_struct("redsys PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Redsys { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: redsys::RedsysPaymentsResponse = res + .response + .parse_struct("Redsys PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Redsys {} + +impl ConnectorIntegration for Redsys { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = redsys::RedsysRouterData::from((refund_amount, req)); + let connector_req = redsys::RedsysRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: redsys::RefundResponse = + res.response + .parse_struct("redsys RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Redsys { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: redsys::RefundResponse = res + .response + .parse_struct("redsys RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Redsys { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/redsys/transformers.rs b/crates/hyperswitch_connectors/src/connectors/redsys/transformers.rs new file mode 100644 index 000000000000..78329b5719d2 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/redsys/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct RedsysRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for RedsysRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct RedsysPaymentsRequest { + amount: StringMinorUnit, + card: RedsysCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct RedsysCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&RedsysRouterData<&PaymentsAuthorizeRouterData>> for RedsysPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &RedsysRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = RedsysCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct RedsysAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for RedsysAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum RedsysPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: RedsysPaymentStatus) -> Self { + match item { + RedsysPaymentStatus::Succeeded => Self::Charged, + RedsysPaymentStatus::Failed => Self::Failure, + RedsysPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct RedsysPaymentsResponse { + status: RedsysPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct RedsysRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&RedsysRouterData<&RefundsRouterData>> for RedsysRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &RedsysRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct RedsysErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 50b28be2b0ba..25306458e8f7 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -119,6 +119,7 @@ default_imp_for_authorize_session_token!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Taxjar, @@ -177,6 +178,7 @@ default_imp_for_calculate_tax!( connectors::Payu, connectors::Powertranz, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -223,6 +225,7 @@ default_imp_for_session_update!( connectors::Inespay, connectors::Jpmorgan, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -283,6 +286,7 @@ default_imp_for_post_session_tokens!( connectors::Inespay, connectors::Jpmorgan, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Taxjar, @@ -348,6 +352,7 @@ default_imp_for_complete_authorize!( connectors::Payeezy, connectors::Payu, connectors::Razorpay, + connectors::Redsys, connectors::Stax, connectors::Square, connectors::Taxjar, @@ -406,6 +411,7 @@ default_imp_for_incremental_authorization!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -466,6 +472,7 @@ default_imp_for_create_customer!( connectors::Payu, connectors::Powertranz, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Square, connectors::Taxjar, @@ -522,6 +529,7 @@ default_imp_for_connector_redirect_response!( connectors::Payu, connectors::Powertranz, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -578,6 +586,7 @@ default_imp_for_pre_processing_steps!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Stax, connectors::Square, connectors::Taxjar, @@ -637,6 +646,7 @@ default_imp_for_post_processing_steps!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -697,6 +707,7 @@ default_imp_for_approve!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -757,6 +768,7 @@ default_imp_for_reject!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -817,6 +829,7 @@ default_imp_for_webhook_source_verification!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -878,6 +891,7 @@ default_imp_for_accept_dispute!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -938,6 +952,7 @@ default_imp_for_submit_evidence!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -998,6 +1013,7 @@ default_imp_for_defend_dispute!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1067,6 +1083,7 @@ default_imp_for_file_upload!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1120,6 +1137,7 @@ default_imp_for_payouts!( connectors::Payu, connectors::Powertranz, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Square, connectors::Stax, @@ -1181,6 +1199,7 @@ default_imp_for_payouts_create!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1243,6 +1262,7 @@ default_imp_for_payouts_retrieve!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1305,6 +1325,7 @@ default_imp_for_payouts_eligibility!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1367,6 +1388,7 @@ default_imp_for_payouts_fulfill!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1429,6 +1451,7 @@ default_imp_for_payouts_cancel!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1491,6 +1514,7 @@ default_imp_for_payouts_quote!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1553,6 +1577,7 @@ default_imp_for_payouts_recipient!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1615,6 +1640,7 @@ default_imp_for_payouts_recipient_account!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1677,6 +1703,7 @@ default_imp_for_frm_sale!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1739,6 +1766,7 @@ default_imp_for_frm_checkout!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1801,6 +1829,7 @@ default_imp_for_frm_transaction!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1863,6 +1892,7 @@ default_imp_for_frm_fulfillment!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1925,6 +1955,7 @@ default_imp_for_frm_record_return!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1984,6 +2015,7 @@ default_imp_for_revoking_mandates!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 7b19ca683655..6a30a180fe7b 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -235,6 +235,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -296,6 +297,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -352,6 +354,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -414,6 +417,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -475,6 +479,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -536,6 +541,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -607,6 +613,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -670,6 +677,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -733,6 +741,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -796,6 +805,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -859,6 +869,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -922,6 +933,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -985,6 +997,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1048,6 +1061,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1111,6 +1125,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1172,6 +1187,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1235,6 +1251,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1298,6 +1315,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1361,6 +1379,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1424,6 +1443,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1487,6 +1507,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, @@ -1547,6 +1568,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Mollie, connectors::Multisafepay, connectors::Razorpay, + connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Square, diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs index 539b87c48089..d6e195bbec40 100644 --- a/crates/hyperswitch_interfaces/src/configs.rs +++ b/crates/hyperswitch_interfaces/src/configs.rs @@ -76,6 +76,7 @@ pub struct Connectors { pub prophetpay: ConnectorParams, pub rapyd: ConnectorParams, pub razorpay: ConnectorParamsWithKeys, + pub redsys: ConnectorParams, pub riskified: ConnectorParams, pub shift4: ConnectorParams, pub signifyd: ConnectorParams, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index b6668323ba9a..5874f4ba463b 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -54,10 +54,11 @@ pub use hyperswitch_connectors::connectors::{ inespay::Inespay, jpmorgan, jpmorgan::Jpmorgan, mollie, mollie::Mollie, multisafepay, multisafepay::Multisafepay, nexinets, nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, nomupay, nomupay::Nomupay, novalnet, novalnet::Novalnet, payeezy, payeezy::Payeezy, payu, - payu::Payu, powertranz, powertranz::Powertranz, razorpay, razorpay::Razorpay, shift4, - shift4::Shift4, square, square::Square, stax, stax::Stax, taxjar, taxjar::Taxjar, thunes, - thunes::Thunes, tsys, tsys::Tsys, volt, volt::Volt, worldline, worldline::Worldline, worldpay, - worldpay::Worldpay, xendit, xendit::Xendit, zen, zen::Zen, zsl, zsl::Zsl, + payu::Payu, powertranz, powertranz::Powertranz, razorpay, razorpay::Razorpay, redsys, + redsys::Redsys, shift4, shift4::Shift4, square, square::Square, stax, stax::Stax, taxjar, + taxjar::Taxjar, thunes, thunes::Thunes, tsys, tsys::Tsys, volt, volt::Volt, worldline, + worldline::Worldline, worldpay, worldpay::Worldpay, xendit, xendit::Xendit, zen, zen::Zen, zsl, + zsl::Zsl, }; #[cfg(feature = "dummy_connector")] diff --git a/crates/router/src/core/payments/connector_integration_v2_impls.rs b/crates/router/src/core/payments/connector_integration_v2_impls.rs index 44e8c25d67bf..8afd019d0801 100644 --- a/crates/router/src/core/payments/connector_integration_v2_impls.rs +++ b/crates/router/src/core/payments/connector_integration_v2_impls.rs @@ -1170,6 +1170,7 @@ default_imp_for_new_connector_integration_payouts!( connector::Prophetpay, connector::Rapyd, connector::Razorpay, + connector::Redsys, connector::Riskified, connector::Signifyd, connector::Square, @@ -1818,6 +1819,7 @@ default_imp_for_new_connector_integration_frm!( connector::Prophetpay, connector::Rapyd, connector::Razorpay, + connector::Redsys, connector::Riskified, connector::Signifyd, connector::Square, @@ -2314,6 +2316,7 @@ default_imp_for_new_connector_integration_connector_authentication!( connector::Prophetpay, connector::Rapyd, connector::Razorpay, + connector::Redsys, connector::Riskified, connector::Signifyd, connector::Square, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 9ba260f554f9..df319724bc40 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -511,6 +511,7 @@ default_imp_for_connector_request_id!( connector::Prophetpay, connector::Rapyd, connector::Razorpay, + connector::Redsys, connector::Riskified, connector::Shift4, connector::Signifyd, @@ -1799,6 +1800,7 @@ default_imp_for_fraud_check!( connector::Prophetpay, connector::Rapyd, connector::Razorpay, + connector::Redsys, connector::Shift4, connector::Square, connector::Stax, @@ -2462,6 +2464,7 @@ default_imp_for_connector_authentication!( connector::Prophetpay, connector::Rapyd, connector::Razorpay, + connector::Redsys, connector::Riskified, connector::Shift4, connector::Signifyd, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index d550c1978b26..f5fa2d9a37eb 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -486,6 +486,7 @@ impl ConnectorData { enums::Connector::Rapyd => { Ok(ConnectorEnum::Old(Box::new(connector::Rapyd::new()))) } + // enums::Connector::Redsys => Ok(ConnectorEnum::Old(Box::new(connector::Redsys))), enums::Connector::Shift4 => { Ok(ConnectorEnum::Old(Box::new(connector::Shift4::new()))) } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 781387574934..2ff243ec4b92 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -280,6 +280,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Prophetpay => Self::Prophetpay, api_enums::Connector::Rapyd => Self::Rapyd, api_enums::Connector::Razorpay => Self::Razorpay, + // api_enums::Connector::Redsys => Self::Redsys, api_enums::Connector::Shift4 => Self::Shift4, api_enums::Connector::Signifyd => { Err(common_utils::errors::ValidationError::InvalidValue { diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index ef3ae2d14dbc..dcedb171675a 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -74,6 +74,7 @@ mod powertranz; mod prophetpay; mod rapyd; mod razorpay; +mod redsys; mod shift4; mod square; mod stax; diff --git a/crates/router/tests/connectors/redsys.rs b/crates/router/tests/connectors/redsys.rs new file mode 100644 index 000000000000..532bbb6f5509 --- /dev/null +++ b/crates/router/tests/connectors/redsys.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct RedsysTest; +impl ConnectorActions for RedsysTest {} +impl utils::Connector for RedsysTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Redsys; + utils::construct_connector_data_old( + Box::new(Redsys::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .redsys + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "redsys".to_string() + } +} + +static CONNECTOR: RedsysTest = RedsysTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index 120ce5e9d269..d099c16254b0 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -268,6 +268,9 @@ api_key="API Key" [nexixpay] api_key="API Key" +[redsys] +api_key="API Key" + [wellsfargopayout] api_key = "Consumer Key" key1 = "Gateway Entity Id" diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 4bb348d66793..3fab02e64d19 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -77,6 +77,7 @@ pub struct ConnectorAuthentication { pub prophetpay: Option, pub rapyd: Option, pub razorpay: Option, + pub redsys: Option, pub shift4: Option, pub square: Option, pub stax: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index a3ac1159ddb0..81bcf01fddc1 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -149,6 +149,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index e5a651283199..fd555a416135 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") + connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay redsys shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res="$(echo ${sorted[@]})" sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp From b1d1073389f58c480a53a27be24aa91554520ff1 Mon Sep 17 00:00:00 2001 From: Debarati Ghatak <88573135+cookieg13@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:38:22 +0530 Subject: [PATCH 38/51] feat(payments): [Payment links] add showCardFormByDefault config for payment links (#6663) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 14 +++++++++++++- api-reference/openapi_spec.json | 14 +++++++++++++- crates/api_models/src/admin.rs | 5 +++++ crates/api_models/src/payments.rs | 2 ++ crates/diesel_models/src/business_profile.rs | 1 + crates/diesel_models/src/payment_intent.rs | 2 ++ crates/hyperswitch_domain_models/src/lib.rs | 3 +++ crates/router/src/consts.rs | 3 +++ crates/router/src/core/payment_link.rs | 11 +++++++++-- .../payment_link_initiator.js | 1 + .../secure_payment_link_initiator.js | 1 + crates/router/src/core/payments/transformers.rs | 2 ++ crates/router/src/types/transformers.rs | 2 ++ 13 files changed, 57 insertions(+), 4 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 0edeb537dc1c..15103188df57 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -11993,7 +11993,8 @@ "sdk_layout", "display_sdk_only", "enabled_saved_payment_method", - "hide_card_nickname_field" + "hide_card_nickname_field", + "show_card_form_by_default" ], "properties": { "theme": { @@ -12024,6 +12025,10 @@ "type": "boolean", "description": "Hide card nickname field option for payment link" }, + "show_card_form_by_default": { + "type": "boolean", + "description": "Show card form by default for payment link" + }, "allowed_domains": { "type": "array", "items": { @@ -12095,6 +12100,13 @@ "example": true, "nullable": true }, + "show_card_form_by_default": { + "type": "boolean", + "description": "Show card form by default for payment link", + "default": true, + "example": true, + "nullable": true + }, "transaction_details": { "type": "array", "items": { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index f624b4f68dda..ecce327d7ffb 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -15170,7 +15170,8 @@ "sdk_layout", "display_sdk_only", "enabled_saved_payment_method", - "hide_card_nickname_field" + "hide_card_nickname_field", + "show_card_form_by_default" ], "properties": { "theme": { @@ -15201,6 +15202,10 @@ "type": "boolean", "description": "Hide card nickname field option for payment link" }, + "show_card_form_by_default": { + "type": "boolean", + "description": "Show card form by default for payment link" + }, "allowed_domains": { "type": "array", "items": { @@ -15272,6 +15277,13 @@ "example": true, "nullable": true }, + "show_card_form_by_default": { + "type": "boolean", + "description": "Show card form by default for payment link", + "default": true, + "example": true, + "nullable": true + }, "transaction_details": { "type": "array", "items": { diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 8ba50649236b..7e1465c9d929 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -2673,6 +2673,9 @@ pub struct PaymentLinkConfigRequest { /// Hide card nickname field option for payment link #[schema(default = false, example = true)] pub hide_card_nickname_field: Option, + /// Show card form by default for payment link + #[schema(default = true, example = true)] + pub show_card_form_by_default: Option, /// Dynamic details related to merchant to be rendered in payment link pub transaction_details: Option>, } @@ -2718,6 +2721,8 @@ pub struct PaymentLinkConfig { pub enabled_saved_payment_method: bool, /// Hide card nickname field option for payment link pub hide_card_nickname_field: bool, + /// Show card form by default for payment link + pub show_card_form_by_default: bool, /// A list of allowed domains (glob patterns) where this link can be embedded / opened from pub allowed_domains: Option>, /// Dynamic details related to merchant to be rendered in payment link diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 45b9bac53acb..fb60937e9b13 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -6670,6 +6670,7 @@ pub struct PaymentLinkDetails { pub sdk_layout: String, pub display_sdk_only: bool, pub hide_card_nickname_field: bool, + pub show_card_form_by_default: bool, pub locale: Option, pub transaction_details: Option>, } @@ -6678,6 +6679,7 @@ pub struct PaymentLinkDetails { pub struct SecurePaymentLinkDetails { pub enabled_saved_payment_method: bool, pub hide_card_nickname_field: bool, + pub show_card_form_by_default: bool, #[serde(flatten)] pub payment_link_details: PaymentLinkDetails, } diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index f4c7b86850eb..1f8209397ae1 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -543,6 +543,7 @@ pub struct PaymentLinkConfigRequest { pub display_sdk_only: Option, pub enabled_saved_payment_method: Option, pub hide_card_nickname_field: Option, + pub show_card_form_by_default: Option, } common_utils::impl_to_sql_from_sql_json!(BusinessPaymentLinkConfig); diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 7826e2dadd25..9f0bf17230da 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -156,6 +156,8 @@ pub struct PaymentLinkConfigRequestForPayments { pub enabled_saved_payment_method: Option, /// Hide card nickname field option for payment link pub hide_card_nickname_field: Option, + /// Show card form by default for payment link + pub show_card_form_by_default: Option, /// Dynamic details related to merchant to be rendered in payment link pub transaction_details: Option>, } diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 64c6c97a0fd0..d94e921c120b 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -192,6 +192,7 @@ impl ApiModelToDieselModelConvertor display_sdk_only: item.display_sdk_only, enabled_saved_payment_method: item.enabled_saved_payment_method, hide_card_nickname_field: item.hide_card_nickname_field, + show_card_form_by_default: item.show_card_form_by_default, transaction_details: item.transaction_details.map(|transaction_details| { transaction_details .into_iter() @@ -213,6 +214,7 @@ impl ApiModelToDieselModelConvertor display_sdk_only, enabled_saved_payment_method, hide_card_nickname_field, + show_card_form_by_default, transaction_details, } = self; api_models::admin::PaymentLinkConfigRequest { @@ -223,6 +225,7 @@ impl ApiModelToDieselModelConvertor display_sdk_only, enabled_saved_payment_method, hide_card_nickname_field, + show_card_form_by_default, transaction_details: transaction_details.map(|transaction_details| { transaction_details .into_iter() diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 51385593e9d1..9b02c67ce6a7 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -153,6 +153,9 @@ pub const DEFAULT_ALLOWED_DOMAINS: Option> = None; /// Default hide card nickname field pub const DEFAULT_HIDE_CARD_NICKNAME_FIELD: bool = false; +/// Show card form by default for payment links +pub const DEFAULT_SHOW_CARD_FORM: bool = true; + /// Default bool for Display sdk only pub const DEFAULT_DISPLAY_SDK_ONLY: bool = false; diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index 91829e56f2cc..3555eb1193e7 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -24,7 +24,7 @@ use crate::{ consts::{ self, DEFAULT_ALLOWED_DOMAINS, DEFAULT_BACKGROUND_COLOR, DEFAULT_DISPLAY_SDK_ONLY, DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, DEFAULT_HIDE_CARD_NICKNAME_FIELD, - DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, + DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, DEFAULT_SHOW_CARD_FORM, }, errors::RouterResponse, get_payment_link_config_value, get_payment_link_config_value_based_on_priority, @@ -126,6 +126,7 @@ pub async fn form_payment_link_data( display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY, enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, hide_card_nickname_field: DEFAULT_HIDE_CARD_NICKNAME_FIELD, + show_card_form_by_default: DEFAULT_SHOW_CARD_FORM, allowed_domains: DEFAULT_ALLOWED_DOMAINS, transaction_details: None, } @@ -267,6 +268,7 @@ pub async fn form_payment_link_data( sdk_layout: payment_link_config.sdk_layout.clone(), display_sdk_only: payment_link_config.display_sdk_only, hide_card_nickname_field: payment_link_config.hide_card_nickname_field, + show_card_form_by_default: payment_link_config.show_card_form_by_default, locale, transaction_details: payment_link_config.transaction_details.clone(), }; @@ -325,6 +327,7 @@ pub async fn initiate_secure_payment_link_flow( let secure_payment_link_details = api_models::payments::SecurePaymentLinkDetails { enabled_saved_payment_method: payment_link_config.enabled_saved_payment_method, hide_card_nickname_field: payment_link_config.hide_card_nickname_field, + show_card_form_by_default: payment_link_config.show_card_form_by_default, payment_link_details: *link_details.to_owned(), }; let js_script = format!( @@ -618,6 +621,7 @@ pub fn get_payment_link_config_based_on_priority( display_sdk_only, enabled_saved_payment_method, hide_card_nickname_field, + show_card_form_by_default, ) = get_payment_link_config_value!( payment_create_link_config, business_theme_configs, @@ -630,7 +634,8 @@ pub fn get_payment_link_config_based_on_priority( enabled_saved_payment_method, DEFAULT_ENABLE_SAVED_PAYMENT_METHOD ), - (hide_card_nickname_field, DEFAULT_HIDE_CARD_NICKNAME_FIELD) + (hide_card_nickname_field, DEFAULT_HIDE_CARD_NICKNAME_FIELD), + (show_card_form_by_default, DEFAULT_SHOW_CARD_FORM) ); let payment_link_config = PaymentLinkConfig { theme, @@ -640,6 +645,7 @@ pub fn get_payment_link_config_based_on_priority( display_sdk_only, enabled_saved_payment_method, hide_card_nickname_field, + show_card_form_by_default, allowed_domains, transaction_details: payment_create_link_config .and_then(|payment_link_config| payment_link_config.theme_config.transaction_details), @@ -743,6 +749,7 @@ pub async fn get_payment_link_status( display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY, enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, hide_card_nickname_field: DEFAULT_HIDE_CARD_NICKNAME_FIELD, + show_card_form_by_default: DEFAULT_SHOW_CARD_FORM, allowed_domains: DEFAULT_ALLOWED_DOMAINS, transaction_details: None, } diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js index 1264915592d6..b79e2284a567 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js @@ -56,6 +56,7 @@ function initializeSDK() { height: 55, }, }, + showCardFormByDefault: paymentDetails.show_card_form_by_default, hideCardNicknameField: false, }; // @ts-ignore diff --git a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js index 5080970ce3c5..4bddc6904be5 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js @@ -81,6 +81,7 @@ if (!isFramed) { }, }, hideCardNicknameField: hideCardNicknameField, + showCardFormByDefault: paymentDetails.show_card_form_by_default, }; // @ts-ignore unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions); diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 4baeade05abf..9e0557d5b3eb 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -3699,6 +3699,7 @@ impl ForeignFrom display_sdk_only: config.display_sdk_only, enabled_saved_payment_method: config.enabled_saved_payment_method, hide_card_nickname_field: config.hide_card_nickname_field, + show_card_form_by_default: config.show_card_form_by_default, transaction_details: config.transaction_details.map(|transaction_details| { transaction_details .iter() @@ -3752,6 +3753,7 @@ impl ForeignFrom display_sdk_only: config.display_sdk_only, enabled_saved_payment_method: config.enabled_saved_payment_method, hide_card_nickname_field: config.hide_card_nickname_field, + show_card_form_by_default: config.show_card_form_by_default, transaction_details: config.transaction_details.map(|transaction_details| { transaction_details .iter() diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 2ff243ec4b92..4ae026689579 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1945,6 +1945,7 @@ impl ForeignFrom display_sdk_only: item.display_sdk_only, enabled_saved_payment_method: item.enabled_saved_payment_method, hide_card_nickname_field: item.hide_card_nickname_field, + show_card_form_by_default: item.show_card_form_by_default, } } } @@ -1961,6 +1962,7 @@ impl ForeignFrom display_sdk_only: item.display_sdk_only, enabled_saved_payment_method: item.enabled_saved_payment_method, hide_card_nickname_field: item.hide_card_nickname_field, + show_card_form_by_default: item.show_card_form_by_default, transaction_details: None, } } From 880ad1e883fb42f73c2805287e64bc2c2dcbb9f3 Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:39:24 +0530 Subject: [PATCH 39/51] fix(users): Mark user as verified if user logins from SSO (#6694) --- crates/router/src/core/user.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 2087d01dbb4b..b181cf797e33 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -2345,13 +2345,24 @@ pub async fn sso_sign( .await?; // TODO: Use config to handle not found error - let user_from_db = state + let user_from_db: domain::UserFromStorage = state .global_store .find_user_by_email(&email.into_inner()) .await .map(Into::into) .to_not_found_response(UserErrors::UserNotFound)?; + if !user_from_db.is_verified() { + state + .global_store + .update_user_by_user_id( + user_from_db.get_user_id(), + storage_user::UserUpdate::VerifyUser, + ) + .await + .change_context(UserErrors::InternalServerError)?; + } + let next_flow = if let Some(user_from_single_purpose_token) = user_from_single_purpose_token { let current_flow = domain::CurrentFlow::new(user_from_single_purpose_token, domain::SPTFlow::SSO.into())?; From 9212f77684b04115332d9be5c3d20bdc56b02160 Mon Sep 17 00:00:00 2001 From: Apoorv Dixit <64925866+apoorvdixit88@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:39:55 +0530 Subject: [PATCH 40/51] feat(users): add tenant id reads in user roles (#6661) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/diesel_models/src/query/user_role.rs | 208 +++++++++--------- crates/router/src/analytics.rs | 2 + crates/router/src/core/user.rs | 56 +++++ crates/router/src/core/user_role.rs | 69 ++++++ crates/router/src/db/kafka_store.rs | 6 + crates/router/src/db/user_role.rs | 82 +++++-- crates/router/src/services/authentication.rs | 3 + .../src/types/domain/user/decision_manager.rs | 33 ++- crates/router/src/utils/user_role.rs | 7 + 9 files changed, 340 insertions(+), 126 deletions(-) diff --git a/crates/diesel_models/src/query/user_role.rs b/crates/diesel_models/src/query/user_role.rs index ed018cc2381e..bb07f6718245 100644 --- a/crates/diesel_models/src/query/user_role.rs +++ b/crates/diesel_models/src/query/user_role.rs @@ -1,7 +1,11 @@ use async_bb8_diesel::AsyncRunQueryDsl; use common_utils::id_type; use diesel::{ - associations::HasTable, debug_query, pg::Pg, result::Error as DieselError, + associations::HasTable, + debug_query, + pg::Pg, + result::Error as DieselError, + sql_types::{Bool, Nullable}, BoolExpressionMethods, ExpressionMethods, QueryDsl, }; use error_stack::{report, ResultExt}; @@ -22,89 +26,70 @@ impl UserRoleNew { } impl UserRole { - pub async fn find_by_user_id( - conn: &PgPooledConn, - user_id: String, - version: UserRoleVersion, - ) -> StorageResult { - generics::generic_find_one::<::Table, _, _>( - conn, - dsl::user_id.eq(user_id).and(dsl::version.eq(version)), - ) - .await - } - - pub async fn find_by_user_id_merchant_id( - conn: &PgPooledConn, - user_id: String, - merchant_id: id_type::MerchantId, - version: UserRoleVersion, - ) -> StorageResult { - generics::generic_find_one::<::Table, _, _>( - conn, - dsl::user_id - .eq(user_id) - .and(dsl::merchant_id.eq(merchant_id)) - .and(dsl::version.eq(version)), - ) - .await - } - - pub async fn list_by_user_id( - conn: &PgPooledConn, - user_id: String, - version: UserRoleVersion, - ) -> StorageResult> { - generics::generic_filter::<::Table, _, _, _>( - conn, - dsl::user_id.eq(user_id).and(dsl::version.eq(version)), - None, - None, - Some(dsl::created_at.asc()), - ) - .await - } - - pub async fn list_by_merchant_id( - conn: &PgPooledConn, - merchant_id: id_type::MerchantId, - version: UserRoleVersion, - ) -> StorageResult> { - generics::generic_filter::<::Table, _, _, _>( - conn, - dsl::merchant_id - .eq(merchant_id) - .and(dsl::version.eq(version)), - None, - None, - Some(dsl::created_at.asc()), + fn check_user_in_lineage( + tenant_id: id_type::TenantId, + org_id: Option, + merchant_id: Option, + profile_id: Option, + ) -> Box< + dyn diesel::BoxableExpression<::Table, Pg, SqlType = Nullable> + + 'static, + > { + // Checking in user roles, for a user in token hierarchy, only one of the relations will be true: + // either tenant level, org level, merchant level, or profile level + // Tenant-level: (tenant_id = ? && org_id = null && merchant_id = null && profile_id = null) + // Org-level: (org_id = ? && merchant_id = null && profile_id = null) + // Merchant-level: (org_id = ? && merchant_id = ? && profile_id = null) + // Profile-level: (org_id = ? && merchant_id = ? && profile_id = ?) + Box::new( + // Tenant-level condition + dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.is_null()) + .and(dsl::merchant_id.is_null()) + .and(dsl::profile_id.is_null()) + .or( + // Org-level condition + dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.eq(org_id.clone())) + .and(dsl::merchant_id.is_null()) + .and(dsl::profile_id.is_null()), + ) + .or( + // Merchant-level condition + dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.eq(org_id.clone())) + .and(dsl::merchant_id.eq(merchant_id.clone())) + .and(dsl::profile_id.is_null()), + ) + .or( + // Profile-level condition + dsl::tenant_id + .eq(tenant_id) + .and(dsl::org_id.eq(org_id)) + .and(dsl::merchant_id.eq(merchant_id)) + .and(dsl::profile_id.eq(profile_id)), + ), ) - .await } - pub async fn find_by_user_id_org_id_merchant_id_profile_id( + pub async fn find_by_user_id_tenant_id_org_id_merchant_id_profile_id( conn: &PgPooledConn, user_id: String, + tenant_id: id_type::TenantId, org_id: id_type::OrganizationId, merchant_id: id_type::MerchantId, profile_id: id_type::ProfileId, version: UserRoleVersion, ) -> StorageResult { - // Checking in user roles, for a user in token hierarchy, only one of the relation will be true, either org level, merchant level or profile level - // (org_id = ? && merchant_id = null && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = ?) - let check_lineage = dsl::org_id - .eq(org_id.clone()) - .and(dsl::merchant_id.is_null().and(dsl::profile_id.is_null())) - .or(dsl::org_id.eq(org_id.clone()).and( - dsl::merchant_id - .eq(merchant_id.clone()) - .and(dsl::profile_id.is_null()), - )) - .or(dsl::org_id.eq(org_id).and( - dsl::merchant_id - .eq(merchant_id) - .and(dsl::profile_id.eq(profile_id)), - )); + let check_lineage = Self::check_user_in_lineage( + tenant_id, + Some(org_id), + Some(merchant_id), + Some(profile_id), + ); let predicate = dsl::user_id .eq(user_id) @@ -114,30 +99,46 @@ impl UserRole { generics::generic_find_one::<::Table, _, _>(conn, predicate).await } - pub async fn update_by_user_id_org_id_merchant_id_profile_id( + #[allow(clippy::too_many_arguments)] + pub async fn update_by_user_id_tenant_id_org_id_merchant_id_profile_id( conn: &PgPooledConn, user_id: String, + tenant_id: id_type::TenantId, org_id: id_type::OrganizationId, merchant_id: Option, profile_id: Option, update: UserRoleUpdate, version: UserRoleVersion, ) -> StorageResult { - // Checking in user roles, for a user in token hierarchy, only one of the relation will be true, either org level, merchant level or profile level - // (org_id = ? && merchant_id = null && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = ?) - let check_lineage = dsl::org_id - .eq(org_id.clone()) - .and(dsl::merchant_id.is_null().and(dsl::profile_id.is_null())) - .or(dsl::org_id.eq(org_id.clone()).and( - dsl::merchant_id - .eq(merchant_id.clone()) + let check_lineage = dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.is_null()) + .and(dsl::merchant_id.is_null()) + .and(dsl::profile_id.is_null()) + .or( + // Org-level condition + dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.eq(org_id.clone())) + .and(dsl::merchant_id.is_null()) + .and(dsl::profile_id.is_null()), + ) + .or( + // Merchant-level condition + dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.eq(org_id.clone())) + .and(dsl::merchant_id.eq(merchant_id.clone())) .and(dsl::profile_id.is_null()), - )) - .or(dsl::org_id.eq(org_id).and( - dsl::merchant_id - .eq(merchant_id) + ) + .or( + // Profile-level condition + dsl::tenant_id + .eq(tenant_id) + .and(dsl::org_id.eq(org_id)) + .and(dsl::merchant_id.eq(merchant_id)) .and(dsl::profile_id.eq(profile_id)), - )); + ); let predicate = dsl::user_id .eq(user_id) @@ -153,29 +154,21 @@ impl UserRole { .await } - pub async fn delete_by_user_id_org_id_merchant_id_profile_id( + pub async fn delete_by_user_id_tenant_id_org_id_merchant_id_profile_id( conn: &PgPooledConn, user_id: String, + tenant_id: id_type::TenantId, org_id: id_type::OrganizationId, merchant_id: id_type::MerchantId, profile_id: id_type::ProfileId, version: UserRoleVersion, ) -> StorageResult { - // Checking in user roles, for a user in token hierarchy, only one of the relation will be true, either org level, merchant level or profile level - // (org_id = ? && merchant_id = null && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = ?) - let check_lineage = dsl::org_id - .eq(org_id.clone()) - .and(dsl::merchant_id.is_null().and(dsl::profile_id.is_null())) - .or(dsl::org_id.eq(org_id.clone()).and( - dsl::merchant_id - .eq(merchant_id.clone()) - .and(dsl::profile_id.is_null()), - )) - .or(dsl::org_id.eq(org_id).and( - dsl::merchant_id - .eq(merchant_id) - .and(dsl::profile_id.eq(profile_id)), - )); + let check_lineage = Self::check_user_in_lineage( + tenant_id, + Some(org_id), + Some(merchant_id), + Some(profile_id), + ); let predicate = dsl::user_id .eq(user_id) @@ -190,6 +183,7 @@ impl UserRole { pub async fn generic_user_roles_list_for_user( conn: &PgPooledConn, user_id: String, + tenant_id: id_type::TenantId, org_id: Option, merchant_id: Option, profile_id: Option, @@ -199,7 +193,7 @@ impl UserRole { limit: Option, ) -> StorageResult> { let mut query = ::table() - .filter(dsl::user_id.eq(user_id)) + .filter(dsl::user_id.eq(user_id).and(dsl::tenant_id.eq(tenant_id))) .into_boxed(); if let Some(org_id) = org_id { @@ -248,9 +242,11 @@ impl UserRole { } } + #[allow(clippy::too_many_arguments)] pub async fn generic_user_roles_list_for_org_and_extra( conn: &PgPooledConn, user_id: Option, + tenant_id: id_type::TenantId, org_id: id_type::OrganizationId, merchant_id: Option, profile_id: Option, @@ -258,7 +254,7 @@ impl UserRole { limit: Option, ) -> StorageResult> { let mut query = ::table() - .filter(dsl::org_id.eq(org_id)) + .filter(dsl::org_id.eq(org_id).and(dsl::tenant_id.eq(tenant_id))) .into_boxed(); if let Some(user_id) = user_id { diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs index 96f41f75ee07..d957c3071ff0 100644 --- a/crates/router/src/analytics.rs +++ b/crates/router/src/analytics.rs @@ -1864,6 +1864,7 @@ pub mod routes { .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: &auth.user_id, + tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id), org_id: Some(&auth.org_id), merchant_id: None, profile_id: None, @@ -1987,6 +1988,7 @@ pub mod routes { .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: &auth.user_id, + tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id), org_id: Some(&auth.org_id), merchant_id: None, profile_id: None, diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index b181cf797e33..22623c4ca660 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -614,6 +614,10 @@ async fn handle_existing_user_invitation( .global_store .find_user_role_by_user_id_and_lineage( invitee_user_from_db.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, &user_from_token.merchant_id, &user_from_token.profile_id, @@ -630,6 +634,10 @@ async fn handle_existing_user_invitation( .global_store .find_user_role_by_user_id_and_lineage( invitee_user_from_db.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, &user_from_token.merchant_id, &user_from_token.profile_id, @@ -660,6 +668,10 @@ async fn handle_existing_user_invitation( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: invitee_user_from_db.get_user_id(), + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id, merchant_id, profile_id, @@ -962,6 +974,10 @@ pub async fn resend_invite( .global_store .find_user_role_by_user_id_and_lineage( user.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, &user_from_token.merchant_id, &user_from_token.profile_id, @@ -985,6 +1001,10 @@ pub async fn resend_invite( .global_store .find_user_role_by_user_id_and_lineage( user.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, &user_from_token.merchant_id, &user_from_token.profile_id, @@ -1064,6 +1084,10 @@ pub async fn accept_invite_from_email_token_only_flow( utils::user_role::get_lineage_for_user_id_and_entity_for_accepting_invite( &state, &user_token.user_id, + user_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), entity.entity_id.clone(), entity.entity_type, ) @@ -1074,6 +1098,10 @@ pub async fn accept_invite_from_email_token_only_flow( let (update_v1_result, update_v2_result) = utils::user_role::update_v1_and_v2_user_roles_in_db( &state, user_from_db.get_user_id(), + user_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &org_id, merchant_id.as_ref(), profile_id.as_ref(), @@ -1260,6 +1288,10 @@ pub async fn list_user_roles_details( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: required_user.get_user_id(), + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id: Some(&user_from_token.org_id), merchant_id: (requestor_role_info.get_entity_type() <= EntityType::Merchant) .then_some(&user_from_token.merchant_id), @@ -2446,6 +2478,10 @@ pub async fn list_orgs_for_user( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: user_from_token.user_id.as_str(), + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id: None, merchant_id: None, profile_id: None, @@ -2511,6 +2547,10 @@ pub async fn list_merchants_for_user_in_org( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: user_from_token.user_id.as_str(), + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id: Some(&user_from_token.org_id), merchant_id: None, profile_id: None, @@ -2590,6 +2630,10 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: user_from_token.user_id.as_str(), + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id: Some(&user_from_token.org_id), merchant_id: Some(&user_from_token.merchant_id), profile_id: None, @@ -2666,6 +2710,10 @@ pub async fn switch_org_for_user( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: &user_from_token.user_id, + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id: Some(&request.org_id), merchant_id: None, profile_id: None, @@ -2839,6 +2887,10 @@ pub async fn switch_merchant_for_user_in_org( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: &user_from_token.user_id, + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id: Some(&user_from_token.org_id), merchant_id: Some(&request.merchant_id), profile_id: None, @@ -2955,6 +3007,10 @@ pub async fn switch_profile_for_user_in_org_and_merchant( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload{ user_id:&user_from_token.user_id, + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id: Some(&user_from_token.org_id), merchant_id: Some(&user_from_token.merchant_id), profile_id:Some(&request.profile_id), diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index 6641e553fd80..eaa655a07f3a 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -159,6 +159,10 @@ pub async fn update_user_role( .global_store .find_user_role_by_user_id_and_lineage( user_to_be_updated.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, &user_from_token.merchant_id, &user_from_token.profile_id, @@ -213,6 +217,10 @@ pub async fn update_user_role( .global_store .update_user_role_by_user_id_and_lineage( user_to_be_updated.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, Some(&user_from_token.merchant_id), Some(&user_from_token.profile_id), @@ -232,6 +240,10 @@ pub async fn update_user_role( .global_store .find_user_role_by_user_id_and_lineage( user_to_be_updated.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, &user_from_token.merchant_id, &user_from_token.profile_id, @@ -286,6 +298,10 @@ pub async fn update_user_role( .global_store .update_user_role_by_user_id_and_lineage( user_to_be_updated.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, Some(&user_from_token.merchant_id), Some(&user_from_token.profile_id), @@ -320,6 +336,10 @@ pub async fn accept_invitations_v2( utils::user_role::get_lineage_for_user_id_and_entity_for_accepting_invite( &state, &user_from_token.user_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), entity.entity_id, entity.entity_type, ) @@ -335,6 +355,10 @@ pub async fn accept_invitations_v2( utils::user_role::update_v1_and_v2_user_roles_in_db( &state, user_from_token.user_id.as_str(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id, merchant_id.as_ref(), profile_id.as_ref(), @@ -372,6 +396,10 @@ pub async fn accept_invitations_pre_auth( utils::user_role::get_lineage_for_user_id_and_entity_for_accepting_invite( &state, &user_token.user_id, + user_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), entity.entity_id, entity.entity_type, ) @@ -387,6 +415,10 @@ pub async fn accept_invitations_pre_auth( utils::user_role::update_v1_and_v2_user_roles_in_db( &state, user_token.user_id.as_str(), + user_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id, merchant_id.as_ref(), profile_id.as_ref(), @@ -473,6 +505,10 @@ pub async fn delete_user_role( .global_store .find_user_role_by_user_id_and_lineage( user_from_db.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, &user_from_token.merchant_id, &user_from_token.profile_id, @@ -520,6 +556,10 @@ pub async fn delete_user_role( .global_store .delete_user_role_by_user_id_and_lineage( user_from_db.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, &user_from_token.merchant_id, &user_from_token.profile_id, @@ -535,6 +575,10 @@ pub async fn delete_user_role( .global_store .find_user_role_by_user_id_and_lineage( user_from_db.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, &user_from_token.merchant_id, &user_from_token.profile_id, @@ -582,6 +626,10 @@ pub async fn delete_user_role( .global_store .delete_user_role_by_user_id_and_lineage( user_from_db.get_user_id(), + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), &user_from_token.org_id, &user_from_token.merchant_id, &user_from_token.profile_id, @@ -602,6 +650,11 @@ pub async fn delete_user_role( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: user_from_db.get_user_id(), + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), + org_id: None, merchant_id: None, profile_id: None, @@ -650,6 +703,10 @@ pub async fn list_users_in_lineage( &state, ListUserRolesByOrgIdPayload { user_id: None, + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id: &user_from_token.org_id, merchant_id: None, profile_id: None, @@ -665,6 +722,10 @@ pub async fn list_users_in_lineage( &state, ListUserRolesByOrgIdPayload { user_id: None, + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id: &user_from_token.org_id, merchant_id: Some(&user_from_token.merchant_id), profile_id: None, @@ -680,6 +741,10 @@ pub async fn list_users_in_lineage( &state, ListUserRolesByOrgIdPayload { user_id: None, + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id: &user_from_token.org_id, merchant_id: Some(&user_from_token.merchant_id), profile_id: Some(&user_from_token.profile_id), @@ -779,6 +844,10 @@ pub async fn list_invitations_for_user( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: &user_from_token.user_id, + tenant_id: user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), org_id: None, merchant_id: None, profile_id: None, diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index e37ff3aa1678..436755ea7204 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -3048,6 +3048,7 @@ impl UserRoleInterface for KafkaStore { async fn find_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: &id_type::MerchantId, profile_id: &id_type::ProfileId, @@ -3056,6 +3057,7 @@ impl UserRoleInterface for KafkaStore { self.diesel_store .find_user_role_by_user_id_and_lineage( user_id, + tenant_id, org_id, merchant_id, profile_id, @@ -3067,6 +3069,7 @@ impl UserRoleInterface for KafkaStore { async fn update_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, profile_id: Option<&id_type::ProfileId>, @@ -3076,6 +3079,7 @@ impl UserRoleInterface for KafkaStore { self.diesel_store .update_user_role_by_user_id_and_lineage( user_id, + tenant_id, org_id, merchant_id, profile_id, @@ -3088,6 +3092,7 @@ impl UserRoleInterface for KafkaStore { async fn delete_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: &id_type::MerchantId, profile_id: &id_type::ProfileId, @@ -3096,6 +3101,7 @@ impl UserRoleInterface for KafkaStore { self.diesel_store .delete_user_role_by_user_id_and_lineage( user_id, + tenant_id, org_id, merchant_id, profile_id, diff --git a/crates/router/src/db/user_role.rs b/crates/router/src/db/user_role.rs index e4e564dc9a40..0da518983265 100644 --- a/crates/router/src/db/user_role.rs +++ b/crates/router/src/db/user_role.rs @@ -15,6 +15,7 @@ use crate::{ pub struct ListUserRolesByOrgIdPayload<'a> { pub user_id: Option<&'a String>, + pub tenant_id: &'a id_type::TenantId, pub org_id: &'a id_type::OrganizationId, pub merchant_id: Option<&'a id_type::MerchantId>, pub profile_id: Option<&'a id_type::ProfileId>, @@ -24,6 +25,7 @@ pub struct ListUserRolesByOrgIdPayload<'a> { pub struct ListUserRolesByUserIdPayload<'a> { pub user_id: &'a str, + pub tenant_id: &'a id_type::TenantId, pub org_id: Option<&'a id_type::OrganizationId>, pub merchant_id: Option<&'a id_type::MerchantId>, pub profile_id: Option<&'a id_type::ProfileId>, @@ -43,15 +45,18 @@ pub trait UserRoleInterface { async fn find_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: &id_type::MerchantId, profile_id: &id_type::ProfileId, version: enums::UserRoleVersion, ) -> CustomResult; + #[allow(clippy::too_many_arguments)] async fn update_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, profile_id: Option<&id_type::ProfileId>, @@ -62,6 +67,7 @@ pub trait UserRoleInterface { async fn delete_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: &id_type::MerchantId, profile_id: &id_type::ProfileId, @@ -98,15 +104,17 @@ impl UserRoleInterface for Store { async fn find_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: &id_type::MerchantId, profile_id: &id_type::ProfileId, version: enums::UserRoleVersion, ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; - storage::UserRole::find_by_user_id_org_id_merchant_id_profile_id( + storage::UserRole::find_by_user_id_tenant_id_org_id_merchant_id_profile_id( &conn, user_id.to_owned(), + tenant_id.to_owned(), org_id.to_owned(), merchant_id.to_owned(), profile_id.to_owned(), @@ -120,6 +128,7 @@ impl UserRoleInterface for Store { async fn update_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, profile_id: Option<&id_type::ProfileId>, @@ -127,9 +136,10 @@ impl UserRoleInterface for Store { version: enums::UserRoleVersion, ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; - storage::UserRole::update_by_user_id_org_id_merchant_id_profile_id( + storage::UserRole::update_by_user_id_tenant_id_org_id_merchant_id_profile_id( &conn, user_id.to_owned(), + tenant_id.to_owned(), org_id.to_owned(), merchant_id.cloned(), profile_id.cloned(), @@ -144,15 +154,17 @@ impl UserRoleInterface for Store { async fn delete_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: &id_type::MerchantId, profile_id: &id_type::ProfileId, version: enums::UserRoleVersion, ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; - storage::UserRole::delete_by_user_id_org_id_merchant_id_profile_id( + storage::UserRole::delete_by_user_id_tenant_id_org_id_merchant_id_profile_id( &conn, user_id.to_owned(), + tenant_id.to_owned(), org_id.to_owned(), merchant_id.to_owned(), profile_id.to_owned(), @@ -170,6 +182,7 @@ impl UserRoleInterface for Store { storage::UserRole::generic_user_roles_list_for_user( &conn, payload.user_id.to_owned(), + payload.tenant_id.to_owned(), payload.org_id.cloned(), payload.merchant_id.cloned(), payload.profile_id.cloned(), @@ -190,6 +203,7 @@ impl UserRoleInterface for Store { storage::UserRole::generic_user_roles_list_for_org_and_extra( &conn, payload.user_id.cloned(), + payload.tenant_id.to_owned(), payload.org_id.to_owned(), payload.merchant_id.cloned(), payload.profile_id.cloned(), @@ -243,6 +257,7 @@ impl UserRoleInterface for MockDb { async fn find_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: &id_type::MerchantId, profile_id: &id_type::ProfileId, @@ -251,21 +266,32 @@ impl UserRoleInterface for MockDb { let user_roles = self.user_roles.lock().await; for user_role in user_roles.iter() { - let org_level_check = user_role.org_id.as_ref() == Some(org_id) + let tenant_level_check = user_role.tenant_id == *tenant_id + && user_role.org_id.is_none() && user_role.merchant_id.is_none() && user_role.profile_id.is_none(); - let merchant_level_check = user_role.org_id.as_ref() == Some(org_id) + let org_level_check = user_role.tenant_id == *tenant_id + && user_role.org_id.as_ref() == Some(org_id) + && user_role.merchant_id.is_none() + && user_role.profile_id.is_none(); + + let merchant_level_check = user_role.tenant_id == *tenant_id + && user_role.org_id.as_ref() == Some(org_id) && user_role.merchant_id.as_ref() == Some(merchant_id) && user_role.profile_id.is_none(); - let profile_level_check = user_role.org_id.as_ref() == Some(org_id) + let profile_level_check = user_role.tenant_id == *tenant_id + && user_role.org_id.as_ref() == Some(org_id) && user_role.merchant_id.as_ref() == Some(merchant_id) && user_role.profile_id.as_ref() == Some(profile_id); // Check if any condition matches and the version matches if user_role.user_id == user_id - && (org_level_check || merchant_level_check || profile_level_check) + && (tenant_level_check + || org_level_check + || merchant_level_check + || profile_level_check) && user_role.version == version { return Ok(user_role.clone()); @@ -282,6 +308,7 @@ impl UserRoleInterface for MockDb { async fn update_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, profile_id: Option<&id_type::ProfileId>, @@ -291,21 +318,32 @@ impl UserRoleInterface for MockDb { let mut user_roles = self.user_roles.lock().await; for user_role in user_roles.iter_mut() { - let org_level_check = user_role.org_id.as_ref() == Some(org_id) + let tenant_level_check = user_role.tenant_id == *tenant_id + && user_role.org_id.is_none() + && user_role.merchant_id.is_none() + && user_role.profile_id.is_none(); + + let org_level_check = user_role.tenant_id == *tenant_id + && user_role.org_id.as_ref() == Some(org_id) && user_role.merchant_id.is_none() && user_role.profile_id.is_none(); - let merchant_level_check = user_role.org_id.as_ref() == Some(org_id) + let merchant_level_check = user_role.tenant_id == *tenant_id + && user_role.org_id.as_ref() == Some(org_id) && user_role.merchant_id.as_ref() == merchant_id && user_role.profile_id.is_none(); - let profile_level_check = user_role.org_id.as_ref() == Some(org_id) + let profile_level_check = user_role.tenant_id == *tenant_id + && user_role.org_id.as_ref() == Some(org_id) && user_role.merchant_id.as_ref() == merchant_id && user_role.profile_id.as_ref() == profile_id; - // Check if the user role matches the conditions and the version matches + // Check if any condition matches and the version matches if user_role.user_id == user_id - && (org_level_check || merchant_level_check || profile_level_check) + && (tenant_level_check + || org_level_check + || merchant_level_check + || profile_level_check) && user_role.version == version { match &update { @@ -336,6 +374,7 @@ impl UserRoleInterface for MockDb { async fn delete_user_role_by_user_id_and_lineage( &self, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: &id_type::MerchantId, profile_id: &id_type::ProfileId, @@ -345,21 +384,32 @@ impl UserRoleInterface for MockDb { // Find the position of the user role to delete let index = user_roles.iter().position(|role| { - let org_level_check = role.org_id.as_ref() == Some(org_id) + let tenant_level_check = role.tenant_id == *tenant_id + && role.org_id.is_none() + && role.merchant_id.is_none() + && role.profile_id.is_none(); + + let org_level_check = role.tenant_id == *tenant_id + && role.org_id.as_ref() == Some(org_id) && role.merchant_id.is_none() && role.profile_id.is_none(); - let merchant_level_check = role.org_id.as_ref() == Some(org_id) + let merchant_level_check = role.tenant_id == *tenant_id + && role.org_id.as_ref() == Some(org_id) && role.merchant_id.as_ref() == Some(merchant_id) && role.profile_id.is_none(); - let profile_level_check = role.org_id.as_ref() == Some(org_id) + let profile_level_check = role.tenant_id == *tenant_id + && role.org_id.as_ref() == Some(org_id) && role.merchant_id.as_ref() == Some(merchant_id) && role.profile_id.as_ref() == Some(profile_id); // Check if the user role matches the conditions and the version matches role.user_id == user_id - && (org_level_check || merchant_level_check || profile_level_check) + && (tenant_level_check + || org_level_check + || merchant_level_check + || profile_level_check) && role.version == version }); diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index c05e4514aaa3..d50933b708da 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -273,6 +273,7 @@ pub struct UserFromToken { pub struct UserIdFromAuth { pub user_id: String, + pub tenant_id: Option, } #[cfg(feature = "olap")] @@ -858,6 +859,7 @@ where Ok(( UserIdFromAuth { user_id: payload.user_id.clone(), + tenant_id: payload.tenant_id, }, AuthenticationType::SinglePurposeOrLoginJwt { user_id: payload.user_id, @@ -899,6 +901,7 @@ where Ok(( UserIdFromAuth { user_id: payload.user_id.clone(), + tenant_id: payload.tenant_id, }, AuthenticationType::SinglePurposeOrLoginJwt { user_id: payload.user_id, diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs index 634c781da7fc..10990da6ccbd 100644 --- a/crates/router/src/types/domain/user/decision_manager.rs +++ b/crates/router/src/types/domain/user/decision_manager.rs @@ -1,4 +1,5 @@ use common_enums::TokenPurpose; +use common_utils::id_type; use diesel_models::{enums::UserStatus, user_role::UserRole}; use error_stack::{report, ResultExt}; use masking::Secret; @@ -24,9 +25,10 @@ impl UserFlow { user: &UserFromStorage, path: &[TokenPurpose], state: &SessionState, + user_tenant_id: &id_type::TenantId, ) -> UserResult { match self { - Self::SPTFlow(flow) => flow.is_required(user, path, state).await, + Self::SPTFlow(flow) => flow.is_required(user, path, state, user_tenant_id).await, Self::JWTFlow(flow) => flow.is_required(user, state).await, } } @@ -50,6 +52,7 @@ impl SPTFlow { user: &UserFromStorage, path: &[TokenPurpose], state: &SessionState, + user_tenant_id: &id_type::TenantId, ) -> UserResult { match self { // Auth @@ -68,6 +71,7 @@ impl SPTFlow { .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: user.get_user_id(), + tenant_id: user_tenant_id, org_id: None, merchant_id: None, profile_id: None, @@ -220,6 +224,7 @@ pub struct CurrentFlow { origin: Origin, current_flow_index: usize, path: Vec, + tenant_id: Option, } impl CurrentFlow { @@ -239,6 +244,7 @@ impl CurrentFlow { origin: token.origin, current_flow_index: index, path, + tenant_id: token.tenant_id, }) } @@ -247,12 +253,21 @@ impl CurrentFlow { let remaining_flows = flows.iter().skip(self.current_flow_index + 1); for flow in remaining_flows { - if flow.is_required(&user, &self.path, state).await? { + if flow + .is_required( + &user, + &self.path, + state, + self.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id), + ) + .await? + { return Ok(NextFlow { origin: self.origin.clone(), next_flow: *flow, user, path: self.path, + tenant_id: self.tenant_id, }); } } @@ -265,6 +280,7 @@ pub struct NextFlow { next_flow: UserFlow, user: UserFromStorage, path: Vec, + tenant_id: Option, } impl NextFlow { @@ -276,12 +292,16 @@ impl NextFlow { let flows = origin.get_flows(); let path = vec![]; for flow in flows { - if flow.is_required(&user, &path, state).await? { + if flow + .is_required(&user, &path, state, &state.tenant.tenant_id) + .await? + { return Ok(Self { origin, next_flow: *flow, user, path, + tenant_id: Some(state.tenant.tenant_id.clone()), }); } } @@ -304,6 +324,7 @@ impl NextFlow { .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: self.user.get_user_id(), + tenant_id: self.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id), org_id: None, merchant_id: None, profile_id: None, @@ -352,12 +373,16 @@ impl NextFlow { .ok_or(UserErrors::InternalServerError)?; let remaining_flows = flows.iter().skip(index + 1); for flow in remaining_flows { - if flow.is_required(&user, &self.path, state).await? { + if flow + .is_required(&user, &self.path, state, &state.tenant.tenant_id) + .await? + { return Ok(Self { origin: self.origin.clone(), next_flow: *flow, user, path: self.path, + tenant_id: Some(state.tenant.tenant_id.clone()), }); } } diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index aaf313a196e5..0bd0e81149f5 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -133,6 +133,7 @@ pub async fn set_role_permissions_in_cache_if_required( pub async fn update_v1_and_v2_user_roles_in_db( state: &SessionState, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, profile_id: Option<&id_type::ProfileId>, @@ -145,6 +146,7 @@ pub async fn update_v1_and_v2_user_roles_in_db( .global_store .update_user_role_by_user_id_and_lineage( user_id, + tenant_id, org_id, merchant_id, profile_id, @@ -161,6 +163,7 @@ pub async fn update_v1_and_v2_user_roles_in_db( .global_store .update_user_role_by_user_id_and_lineage( user_id, + tenant_id, org_id, merchant_id, profile_id, @@ -210,6 +213,7 @@ pub async fn get_single_merchant_id( pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( state: &SessionState, user_id: &str, + tenant_id: &id_type::TenantId, entity_id: String, entity_type: EntityType, ) -> UserResult< @@ -231,6 +235,7 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id, + tenant_id, org_id: Some(&org_id), merchant_id: None, profile_id: None, @@ -275,6 +280,7 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id, + tenant_id, org_id: None, merchant_id: Some(&merchant_id), profile_id: None, @@ -320,6 +326,7 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id, + tenant_id: &state.tenant.tenant_id, org_id: None, merchant_id: None, profile_id: Some(&profile_id), From 96393ff3d6b11d4726a6cb2224236414507d9848 Mon Sep 17 00:00:00 2001 From: Anurag Thakur Date: Fri, 29 Nov 2024 15:58:59 +0530 Subject: [PATCH 41/51] fix(openapi): Standardise API naming scheme for V2 (#6510) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../api-reference/api-key/api-key--create.mdx | 2 +- .../api-reference/api-key/api-key--list.mdx | 2 +- .../api-key/api-key--retrieve.mdx | 2 +- .../api-reference/api-key/api-key--revoke.mdx | 2 +- .../api-reference/api-key/api-key--update.mdx | 2 +- .../merchant-connector--list.mdx | 2 +- .../connector-account--create.mdx | 2 +- .../connector-account--delete.mdx | 2 +- .../connector-account--retrieve.mdx | 2 +- .../connector-account--update.mdx | 2 +- .../business-profile--list.mdx | 2 +- .../merchant-account--create.mdx | 2 +- .../merchant-account--retrieve.mdx | 2 +- .../merchant-account--update.mdx | 2 +- .../merchant-account/profile--list.mdx | 3 - .../organization--merchant-account--list.mdx | 2 +- ...er-saved-payment-methods-for-a-payment.mdx | 3 + .../list-payment-methods-for-a-customer.mdx | 3 + .../payment-method--confirm-intent.mdx | 3 + .../payment-method--create-intent.mdx | 3 + .../payment-method--create.mdx | 3 + .../payment-method--delete.mdx | 3 + .../payment-method--retrieve.mdx | 3 + .../payment-method--update.mdx | 3 + .../profile/merchant-connector--list.mdx | 2 +- .../profile--activate-routing-algorithm.mdx | 2 +- .../profile--deactivate-routing-algorithm.mdx | 2 +- ...ile--retrieve-active-routing-algorithm.mdx | 2 +- ...eve-default-fallback-routing-algorithm.mdx | 2 +- ...ate-default-fallback-routing-algorithm.mdx | 2 +- .../api-reference/routing/routing--create.mdx | 2 +- .../routing/routing--retrieve.mdx | 2 +- api-reference-v2/mint.json | 23 +- api-reference-v2/openapi_spec.json | 510 +++++++++++++++++- api-reference/openapi_spec.json | 12 +- crates/api_models/src/organization.rs | 40 +- crates/api_models/src/payment_methods.rs | 2 +- .../hyperswitch_domain_models/src/payments.rs | 4 +- crates/openapi/src/openapi_v2.rs | 15 + crates/openapi/src/routes/api_keys.rs | 10 +- crates/openapi/src/routes/merchant_account.rs | 8 +- .../src/routes/merchant_connector_account.rs | 8 +- crates/openapi/src/routes/organization.rs | 2 +- crates/openapi/src/routes/payment_method.rs | 173 ++++++ crates/openapi/src/routes/profile.rs | 14 +- crates/openapi/src/routes/routing.rs | 4 +- crates/router/src/routes/app.rs | 38 +- crates/router/src/routes/payment_methods.rs | 47 -- crates/router/src/types/api/admin.rs | 4 + cypress-tests-v2/cypress/support/commands.js | 52 +- 50 files changed, 872 insertions(+), 167 deletions(-) delete mode 100644 api-reference-v2/api-reference/merchant-account/profile--list.mdx create mode 100644 api-reference-v2/api-reference/payment-methods/list-customer-saved-payment-methods-for-a-payment.mdx create mode 100644 api-reference-v2/api-reference/payment-methods/list-payment-methods-for-a-customer.mdx create mode 100644 api-reference-v2/api-reference/payment-methods/payment-method--confirm-intent.mdx create mode 100644 api-reference-v2/api-reference/payment-methods/payment-method--create-intent.mdx create mode 100644 api-reference-v2/api-reference/payment-methods/payment-method--create.mdx create mode 100644 api-reference-v2/api-reference/payment-methods/payment-method--delete.mdx create mode 100644 api-reference-v2/api-reference/payment-methods/payment-method--retrieve.mdx create mode 100644 api-reference-v2/api-reference/payment-methods/payment-method--update.mdx diff --git a/api-reference-v2/api-reference/api-key/api-key--create.mdx b/api-reference-v2/api-reference/api-key/api-key--create.mdx index a92a8ea77fd3..abc1dcda10f2 100644 --- a/api-reference-v2/api-reference/api-key/api-key--create.mdx +++ b/api-reference-v2/api-reference/api-key/api-key--create.mdx @@ -1,3 +1,3 @@ --- -openapi: post /v2/api_keys +openapi: post /v2/api-keys --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/api-key/api-key--list.mdx b/api-reference-v2/api-reference/api-key/api-key--list.mdx index 5975e9bd6cab..fb84b35fbc7c 100644 --- a/api-reference-v2/api-reference/api-key/api-key--list.mdx +++ b/api-reference-v2/api-reference/api-key/api-key--list.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/api_keys/list +openapi: get /v2/api-keys/list --- diff --git a/api-reference-v2/api-reference/api-key/api-key--retrieve.mdx b/api-reference-v2/api-reference/api-key/api-key--retrieve.mdx index ee7970122d4c..728643633576 100644 --- a/api-reference-v2/api-reference/api-key/api-key--retrieve.mdx +++ b/api-reference-v2/api-reference/api-key/api-key--retrieve.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/api_keys/{id} +openapi: get /v2/api-keys/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/api-key/api-key--revoke.mdx b/api-reference-v2/api-reference/api-key/api-key--revoke.mdx index 9362743088b7..b7ffd42e4493 100644 --- a/api-reference-v2/api-reference/api-key/api-key--revoke.mdx +++ b/api-reference-v2/api-reference/api-key/api-key--revoke.mdx @@ -1,3 +1,3 @@ --- -openapi: delete /v2/api_keys/{id} +openapi: delete /v2/api-keys/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/api-key/api-key--update.mdx b/api-reference-v2/api-reference/api-key/api-key--update.mdx index c682cf1ee9e8..2434e4981fc6 100644 --- a/api-reference-v2/api-reference/api-key/api-key--update.mdx +++ b/api-reference-v2/api-reference/api-key/api-key--update.mdx @@ -1,3 +1,3 @@ --- -openapi: put /v2/api_keys/{id} +openapi: put /v2/api-keys/{id} --- diff --git a/api-reference-v2/api-reference/business-profile/merchant-connector--list.mdx b/api-reference-v2/api-reference/business-profile/merchant-connector--list.mdx index 6560f45e5fa2..93c5a980c27e 100644 --- a/api-reference-v2/api-reference/business-profile/merchant-connector--list.mdx +++ b/api-reference-v2/api-reference/business-profile/merchant-connector--list.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/profiles/{profile_id}/connector_accounts +openapi: get /v2/profiles/{profile_id}/connector-accounts --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/connector-account/connector-account--create.mdx b/api-reference-v2/api-reference/connector-account/connector-account--create.mdx index d8cac2bab392..d672d6fa34dc 100644 --- a/api-reference-v2/api-reference/connector-account/connector-account--create.mdx +++ b/api-reference-v2/api-reference/connector-account/connector-account--create.mdx @@ -1,3 +1,3 @@ --- -openapi: post /v2/connector_accounts +openapi: post /v2/connector-accounts --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/connector-account/connector-account--delete.mdx b/api-reference-v2/api-reference/connector-account/connector-account--delete.mdx index 5c959648fffc..15fdd6644126 100644 --- a/api-reference-v2/api-reference/connector-account/connector-account--delete.mdx +++ b/api-reference-v2/api-reference/connector-account/connector-account--delete.mdx @@ -1,3 +1,3 @@ --- -openapi: delete /v2/connector_accounts/{id} +openapi: delete /v2/connector-accounts/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/connector-account/connector-account--retrieve.mdx b/api-reference-v2/api-reference/connector-account/connector-account--retrieve.mdx index 918de0312769..dbd26b9b10b1 100644 --- a/api-reference-v2/api-reference/connector-account/connector-account--retrieve.mdx +++ b/api-reference-v2/api-reference/connector-account/connector-account--retrieve.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/connector_accounts/{id} +openapi: get /v2/connector-accounts/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/connector-account/connector-account--update.mdx b/api-reference-v2/api-reference/connector-account/connector-account--update.mdx index 6ccd052fb9bf..fe864d538f8f 100644 --- a/api-reference-v2/api-reference/connector-account/connector-account--update.mdx +++ b/api-reference-v2/api-reference/connector-account/connector-account--update.mdx @@ -1,3 +1,3 @@ --- -openapi: put /v2/connector_accounts/{id} +openapi: put /v2/connector-accounts/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-account/business-profile--list.mdx b/api-reference-v2/api-reference/merchant-account/business-profile--list.mdx index 97deb0832cc1..069bd602ddf4 100644 --- a/api-reference-v2/api-reference/merchant-account/business-profile--list.mdx +++ b/api-reference-v2/api-reference/merchant-account/business-profile--list.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/merchant_accounts/{id}/profiles +openapi: get /v2/merchant-accounts/{id}/profiles --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-account/merchant-account--create.mdx b/api-reference-v2/api-reference/merchant-account/merchant-account--create.mdx index d870b811aae8..38aed603f62e 100644 --- a/api-reference-v2/api-reference/merchant-account/merchant-account--create.mdx +++ b/api-reference-v2/api-reference/merchant-account/merchant-account--create.mdx @@ -1,3 +1,3 @@ --- -openapi: post /v2/merchant_accounts +openapi: post /v2/merchant-accounts --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-account/merchant-account--retrieve.mdx b/api-reference-v2/api-reference/merchant-account/merchant-account--retrieve.mdx index d082565234e8..3b744fb14063 100644 --- a/api-reference-v2/api-reference/merchant-account/merchant-account--retrieve.mdx +++ b/api-reference-v2/api-reference/merchant-account/merchant-account--retrieve.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/merchant_accounts/{id} +openapi: get /v2/merchant-accounts/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-account/merchant-account--update.mdx b/api-reference-v2/api-reference/merchant-account/merchant-account--update.mdx index 51f80ceea302..eb2e92d0652f 100644 --- a/api-reference-v2/api-reference/merchant-account/merchant-account--update.mdx +++ b/api-reference-v2/api-reference/merchant-account/merchant-account--update.mdx @@ -1,3 +1,3 @@ --- -openapi: put /v2/merchant_accounts/{id} +openapi: put /v2/merchant-accounts/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-account/profile--list.mdx b/api-reference-v2/api-reference/merchant-account/profile--list.mdx deleted file mode 100644 index e14bc0d6ef34..000000000000 --- a/api-reference-v2/api-reference/merchant-account/profile--list.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: get /v2/merchant_accounts/{account_id}/profiles ---- \ No newline at end of file diff --git a/api-reference-v2/api-reference/organization/organization--merchant-account--list.mdx b/api-reference-v2/api-reference/organization/organization--merchant-account--list.mdx index 58d467dc5726..9a03e8713d12 100644 --- a/api-reference-v2/api-reference/organization/organization--merchant-account--list.mdx +++ b/api-reference-v2/api-reference/organization/organization--merchant-account--list.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/organization/{id}/merchant_accounts +openapi: get /v2/organization/{id}/merchant-accounts --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/list-customer-saved-payment-methods-for-a-payment.mdx b/api-reference-v2/api-reference/payment-methods/list-customer-saved-payment-methods-for-a-payment.mdx new file mode 100644 index 000000000000..7809830b820b --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/list-customer-saved-payment-methods-for-a-payment.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payments/{id}/saved-payment-methods +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/list-payment-methods-for-a-customer.mdx b/api-reference-v2/api-reference/payment-methods/list-payment-methods-for-a-customer.mdx new file mode 100644 index 000000000000..ef5a27f9604d --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/list-payment-methods-for-a-customer.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/customers/{id}/saved-payment-methods +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--confirm-intent.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--confirm-intent.mdx new file mode 100644 index 000000000000..134374a7b6c1 --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--confirm-intent.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/payment-methods/{id}/confirm-intent +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--create-intent.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--create-intent.mdx new file mode 100644 index 000000000000..42cf716f2ab9 --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--create-intent.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/payment-methods/create-intent +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--create.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--create.mdx new file mode 100644 index 000000000000..1dce5179a94d --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--create.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/payment-methods +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--delete.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--delete.mdx new file mode 100644 index 000000000000..210bf843f97e --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--delete.mdx @@ -0,0 +1,3 @@ +--- +openapi: delete /v2/payment-methods/{id} +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--retrieve.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--retrieve.mdx new file mode 100644 index 000000000000..957d9760b3f3 --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--retrieve.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payment-methods/{id} +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--update.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--update.mdx new file mode 100644 index 000000000000..0adee195a6fa --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--update.mdx @@ -0,0 +1,3 @@ +--- +openapi: patch /v2/payment-methods/{id}/update-saved-payment-method +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/merchant-connector--list.mdx b/api-reference-v2/api-reference/profile/merchant-connector--list.mdx index 81f640436f46..55218be7c0b4 100644 --- a/api-reference-v2/api-reference/profile/merchant-connector--list.mdx +++ b/api-reference-v2/api-reference/profile/merchant-connector--list.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/profiles/{id}/connector_accounts +openapi: get /v2/profiles/{id}/connector-accounts --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--activate-routing-algorithm.mdx b/api-reference-v2/api-reference/profile/profile--activate-routing-algorithm.mdx index 7225f422e5ac..ea9ee7596a02 100644 --- a/api-reference-v2/api-reference/profile/profile--activate-routing-algorithm.mdx +++ b/api-reference-v2/api-reference/profile/profile--activate-routing-algorithm.mdx @@ -1,3 +1,3 @@ --- -openapi: patch /v2/profiles/{id}/activate_routing_algorithm +openapi: patch /v2/profiles/{id}/activate-routing-algorithm --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--deactivate-routing-algorithm.mdx b/api-reference-v2/api-reference/profile/profile--deactivate-routing-algorithm.mdx index 87aac8b93799..4d6b2d620c66 100644 --- a/api-reference-v2/api-reference/profile/profile--deactivate-routing-algorithm.mdx +++ b/api-reference-v2/api-reference/profile/profile--deactivate-routing-algorithm.mdx @@ -1,3 +1,3 @@ --- -openapi: patch /v2/profiles/{id}/deactivate_routing_algorithm +openapi: patch /v2/profiles/{id}/deactivate-routing-algorithm --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--retrieve-active-routing-algorithm.mdx b/api-reference-v2/api-reference/profile/profile--retrieve-active-routing-algorithm.mdx index 86d2d35d57c5..143837676c22 100644 --- a/api-reference-v2/api-reference/profile/profile--retrieve-active-routing-algorithm.mdx +++ b/api-reference-v2/api-reference/profile/profile--retrieve-active-routing-algorithm.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/profiles/{id}/routing_algorithm +openapi: get /v2/profiles/{id}/routing-algorithm --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--retrieve-default-fallback-routing-algorithm.mdx b/api-reference-v2/api-reference/profile/profile--retrieve-default-fallback-routing-algorithm.mdx index 1bc383c278fd..ebaad7c53ae3 100644 --- a/api-reference-v2/api-reference/profile/profile--retrieve-default-fallback-routing-algorithm.mdx +++ b/api-reference-v2/api-reference/profile/profile--retrieve-default-fallback-routing-algorithm.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/profiles/{id}/fallback_routing +openapi: get /v2/profiles/{id}/fallback-routing --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--update-default-fallback-routing-algorithm.mdx b/api-reference-v2/api-reference/profile/profile--update-default-fallback-routing-algorithm.mdx index 76f4d4fa77f5..b5df6a57ef8a 100644 --- a/api-reference-v2/api-reference/profile/profile--update-default-fallback-routing-algorithm.mdx +++ b/api-reference-v2/api-reference/profile/profile--update-default-fallback-routing-algorithm.mdx @@ -1,3 +1,3 @@ --- -openapi: patch /v2/profiles/{id}/fallback_routing +openapi: patch /v2/profiles/{id}/fallback-routing --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/routing/routing--create.mdx b/api-reference-v2/api-reference/routing/routing--create.mdx index 65ef15008f24..438abd8e2316 100644 --- a/api-reference-v2/api-reference/routing/routing--create.mdx +++ b/api-reference-v2/api-reference/routing/routing--create.mdx @@ -1,3 +1,3 @@ --- -openapi: post /v2/routing_algorithm +openapi: post /v2/routing-algorithm --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/routing/routing--retrieve.mdx b/api-reference-v2/api-reference/routing/routing--retrieve.mdx index 776ff69e0045..10db0200e18d 100644 --- a/api-reference-v2/api-reference/routing/routing--retrieve.mdx +++ b/api-reference-v2/api-reference/routing/routing--retrieve.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/routing_algorithm/{id} +openapi: get /v2/routing-algorithm/{id} --- \ No newline at end of file diff --git a/api-reference-v2/mint.json b/api-reference-v2/mint.json index c0723a63f3a6..aed89492443d 100644 --- a/api-reference-v2/mint.json +++ b/api-reference-v2/mint.json @@ -23,7 +23,9 @@ "navigation": [ { "group": "Get Started", - "pages": ["introduction"] + "pages": [ + "introduction" + ] }, { "group": "Essentials", @@ -43,6 +45,19 @@ "api-reference/payments/payments--get" ] }, + { + "group": "Payment Methods", + "pages": [ + "api-reference/payment-methods/payment-method--create", + "api-reference/payment-methods/payment-method--retrieve", + "api-reference/payment-methods/payment-method--update", + "api-reference/payment-methods/payment-method--delete", + "api-reference/payment-methods/payment-method--create-intent", + "api-reference/payment-methods/payment-method--confirm-intent", + "api-reference/payment-methods/list-customer-saved-payment-methods-for-a-payment", + "api-reference/payment-methods/list-payment-methods-for-a-customer" + ] + }, { "group": "Organization", "pages": [ @@ -119,8 +134,10 @@ "github": "https://github.com/juspay/hyperswitch", "linkedin": "https://www.linkedin.com/company/hyperswitch" }, - "openapi": ["openapi_spec.json"], + "openapi": [ + "openapi_spec.json" + ], "api": { "maintainOrder": true } -} +} \ No newline at end of file diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 15103188df57..2b66d1755ff8 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -164,7 +164,7 @@ ] } }, - "/v2/organization/{id}/merchant_accounts": { + "/v2/organization/{id}/merchant-accounts": { "get": { "tags": [ "Organization" @@ -208,7 +208,7 @@ ] } }, - "/v2/connector_accounts": { + "/v2/connector-accounts": { "post": { "tags": [ "Merchant Connector Account" @@ -285,7 +285,7 @@ ] } }, - "/v2/connector_accounts/{id}": { + "/v2/connector-accounts/{id}": { "get": { "tags": [ "Merchant Connector Account" @@ -445,7 +445,7 @@ ] } }, - "/v2/merchant_accounts": { + "/v2/merchant-accounts": { "post": { "tags": [ "Merchant Account" @@ -524,7 +524,7 @@ ] } }, - "/v2/merchant_accounts/{id}": { + "/v2/merchant-accounts/{id}": { "get": { "tags": [ "Merchant Account" @@ -630,7 +630,7 @@ ] } }, - "/v2/merchant_accounts/{id}/profiles": { + "/v2/merchant-accounts/{id}/profiles": { "get": { "tags": [ "Merchant Account" @@ -907,7 +907,7 @@ ] } }, - "/v2/profiles/{id}/connector_accounts": { + "/v2/profiles/{id}/connector-accounts": { "get": { "tags": [ "Business Profile" @@ -966,7 +966,7 @@ ] } }, - "/v2/profiles/{id}/activate_routing_algorithm": { + "/v2/profiles/{id}/activate-routing-algorithm": { "patch": { "tags": [ "Profile" @@ -1033,7 +1033,7 @@ ] } }, - "/v2/profiles/{id}/deactivate_routing_algorithm": { + "/v2/profiles/{id}/deactivate-routing-algorithm": { "patch": { "tags": [ "Profile" @@ -1086,7 +1086,7 @@ ] } }, - "/v2/profiles/{id}/fallback_routing": { + "/v2/profiles/{id}/fallback-routing": { "patch": { "tags": [ "Profile" @@ -1197,13 +1197,13 @@ ] } }, - "/v2/profiles/{id}/routing_algorithm": { + "/v2/profiles/{id}/routing-algorithm": { "get": { "tags": [ "Profile" ], "summary": "Profile - Retrieve Active Routing Algorithm", - "description": "Retrieve active routing algorithm under the profile", + "description": "_\nRetrieve active routing algorithm under the profile", "operationId": "Retrieve the active routing algorithm under the profile", "parameters": [ { @@ -1271,7 +1271,7 @@ ] } }, - "/v2/routing_algorithm": { + "/v2/routing-algorithm": { "post": { "tags": [ "Routing" @@ -1326,7 +1326,7 @@ ] } }, - "/v2/routing_algorithm/{id}": { + "/v2/routing-algorithm/{id}": { "get": { "tags": [ "Routing" @@ -1376,7 +1376,7 @@ ] } }, - "/v2/api_keys": { + "/v2/api-keys": { "post": { "tags": [ "API Key" @@ -1416,7 +1416,7 @@ ] } }, - "/v2/api_keys/{id}": { + "/v2/api-keys/{id}": { "get": { "tags": [ "API Key" @@ -1545,7 +1545,7 @@ ] } }, - "/v2/api_keys/list": { + "/v2/api-keys/list": { "get": { "tags": [ "API Key" @@ -2017,6 +2017,332 @@ ] } }, + "/v2/payments/{id}/saved-payment-methods": { + "get": { + "tags": [ + "Payment Methods" + ], + "summary": "List customer saved payment methods for a payment", + "description": "To filter and list the applicable payment methods for a particular Customer ID, is to be associated with a payment", + "operationId": "List all Payment Methods for a Customer", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodListRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Payment Methods retrieved for customer tied to its respective client-secret passed in the param", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerPaymentMethodsListResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + }, + "404": { + "description": "Payment Methods does not exist in records" + } + }, + "security": [ + { + "publishable_key": [] + } + ] + } + }, + "/v2/customers/{id}/saved-payment-methods": { + "get": { + "tags": [ + "Payment Methods" + ], + "summary": "List saved payment methods for a Customer", + "description": "To filter and list the applicable payment methods for a particular Customer ID, to be used in a non-payments context", + "operationId": "List all Payment Methods for a Customer", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodListRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Payment Methods retrieved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerPaymentMethodsListResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + }, + "404": { + "description": "Payment Methods does not exist in records" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/v2/payment-methods": { + "post": { + "tags": [ + "Payment Methods" + ], + "summary": "Payment Method - Create", + "description": "Creates and stores a payment method against a customer. In case of cards, this API should be used only by PCI compliant merchants.", + "operationId": "Create Payment Method", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodCreate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Payment Method Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/v2/payment-methods/create-intent": { + "post": { + "tags": [ + "Payment Methods" + ], + "summary": "Payment Method - Create Intent", + "description": "Creates a payment method for customer with billing information and other metadata.", + "operationId": "Create Payment Method Intent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodIntentCreate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Payment Method Intent Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/v2/payment-methods/{id}/confirm-intent": { + "post": { + "tags": [ + "Payment Methods" + ], + "summary": "Payment Method - Confirm Intent", + "description": "Update a payment method with customer's payment method related information.", + "operationId": "Confirm Payment Method Intent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodIntentConfirm" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Payment Method Intent Confirmed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/v2/payment-methods/{id}/update-saved-payment-method": { + "patch": { + "tags": [ + "Payment Methods" + ], + "summary": "Payment Method - Update", + "description": "Update an existing payment method of a customer.", + "operationId": "Update Payment Method", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Payment Method Update", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodResponse" + } + } + } + }, + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/v2/payment-methods/{id}": { + "get": { + "tags": [ + "Payment Methods" + ], + "summary": "Payment Method - Retrieve", + "description": "Retrieves a payment method of a customer.", + "operationId": "Retrieve Payment Method", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The unique identifier for the Payment Method", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Payment Method Retrieved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodResponse" + } + } + } + }, + "404": { + "description": "Payment Method Not Found" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "delete": { + "tags": [ + "Payment Methods" + ], + "summary": "Payment Method - Delete", + "description": "Deletes a payment method of a customer.", + "operationId": "Delete Payment Method", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The unique identifier for the Payment Method", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Payment Method Retrieved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodDeleteResponse" + } + } + } + }, + "404": { + "description": "Payment Method Not Found" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, "/v2/refunds": { "post": { "tags": [ @@ -11255,14 +11581,17 @@ ], "properties": { "organization_name": { - "type": "string" + "type": "string", + "description": "Name of the organization" }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true } }, @@ -11271,27 +11600,31 @@ "OrganizationResponse": { "type": "object", "required": [ - "organization_id", + "id", "modified_at", "created_at" ], "properties": { - "organization_id": { + "id": { "type": "string", + "description": "The unique identifier for the Organization", "example": "org_q98uSGAYbjEwqs0mJwnz", "maxLength": 64, "minLength": 1 }, "organization_name": { "type": "string", + "description": "Name of the Organization", "nullable": true }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true }, "modified_at": { @@ -11309,14 +11642,17 @@ "properties": { "organization_name": { "type": "string", + "description": "Name of the organization", "nullable": true }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true } }, @@ -12918,6 +13254,68 @@ } } }, + "PaymentMethodIntentConfirm": { + "type": "object", + "required": [ + "client_secret", + "payment_method_data", + "payment_method_type", + "payment_method_subtype" + ], + "properties": { + "client_secret": { + "type": "string", + "description": "For SDK based calls, client_secret would be required" + }, + "customer_id": { + "type": "string", + "description": "The unique identifier of the customer.", + "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", + "nullable": true, + "maxLength": 64, + "minLength": 1 + }, + "payment_method_data": { + "$ref": "#/components/schemas/PaymentMethodCreateData" + }, + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" + } + }, + "additionalProperties": false + }, + "PaymentMethodIntentCreate": { + "type": "object", + "required": [ + "customer_id" + ], + "properties": { + "metadata": { + "type": "object", + "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.", + "nullable": true + }, + "billing": { + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], + "nullable": true + }, + "customer_id": { + "type": "string", + "description": "The unique identifier of the customer.", + "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", + "maxLength": 64, + "minLength": 1 + } + }, + "additionalProperties": false + }, "PaymentMethodIssuerCode": { "type": "string", "enum": [ @@ -12959,6 +13357,78 @@ } ] }, + "PaymentMethodListRequest": { + "type": "object", + "properties": { + "client_secret": { + "type": "string", + "description": "This is a 15 minute expiry token which shall be used from the client to authenticate and perform sessions from the SDK", + "example": "secret_k2uj3he2893eiu2d", + "nullable": true, + "maxLength": 30, + "minLength": 30 + }, + "accepted_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CountryAlpha2" + }, + "description": "The two-letter ISO currency code", + "example": [ + "US", + "UK", + "IN" + ], + "nullable": true + }, + "amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "accepted_currencies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Currency" + }, + "description": "The three-letter ISO currency code", + "example": [ + "USD", + "EUR" + ], + "nullable": true + }, + "recurring_enabled": { + "type": "boolean", + "description": "Indicates whether the payment method is eligible for recurring payments", + "example": true, + "nullable": true + }, + "card_networks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CardNetwork" + }, + "description": "Indicates whether the payment method is eligible for card netwotks", + "example": [ + "visa", + "mastercard" + ], + "nullable": true + }, + "limit": { + "type": "integer", + "format": "int64", + "description": "Indicates the limit of last used payment methods", + "example": 1, + "nullable": true + } + }, + "additionalProperties": false + }, "PaymentMethodListResponse": { "type": "object", "required": [ diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index ecce327d7ffb..d2133a3e68e4 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -14432,14 +14432,17 @@ ], "properties": { "organization_name": { - "type": "string" + "type": "string", + "description": "Name of the organization" }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true } }, @@ -14455,20 +14458,24 @@ "properties": { "organization_id": { "type": "string", + "description": "The unique identifier for the Organization", "example": "org_q98uSGAYbjEwqs0mJwnz", "maxLength": 64, "minLength": 1 }, "organization_name": { "type": "string", + "description": "Name of the Organization", "nullable": true }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true }, "modified_at": { @@ -14486,14 +14493,17 @@ "properties": { "organization_name": { "type": "string", + "description": "Name of the organization", "nullable": true }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true } }, diff --git a/crates/api_models/src/organization.rs b/crates/api_models/src/organization.rs index f95a15951165..c6bc3924d115 100644 --- a/crates/api_models/src/organization.rs +++ b/crates/api_models/src/organization.rs @@ -22,9 +22,14 @@ pub struct OrganizationId { #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] pub struct OrganizationCreateRequest { + /// Name of the organization pub organization_name: String, + + /// Details about the organization #[schema(value_type = Option)] pub organization_details: Option, + + /// Metadata is useful for storing additional, unstructured information on an object. #[schema(value_type = Option)] pub metadata: Option, } @@ -32,20 +37,53 @@ pub struct OrganizationCreateRequest { #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] pub struct OrganizationUpdateRequest { + /// Name of the organization pub organization_name: Option, + + /// Details about the organization #[schema(value_type = Option)] pub organization_details: Option, + + /// Metadata is useful for storing additional, unstructured information on an object. #[schema(value_type = Option)] pub metadata: Option, } - +#[cfg(feature = "v1")] #[derive(Debug, serde::Serialize, Clone, ToSchema)] pub struct OrganizationResponse { + /// The unique identifier for the Organization #[schema(value_type = String, max_length = 64, min_length = 1, example = "org_q98uSGAYbjEwqs0mJwnz")] pub organization_id: id_type::OrganizationId, + + /// Name of the Organization pub organization_name: Option, + + /// Details about the organization #[schema(value_type = Option)] pub organization_details: Option, + + /// Metadata is useful for storing additional, unstructured information on an object. + #[schema(value_type = Option)] + pub metadata: Option, + pub modified_at: time::PrimitiveDateTime, + pub created_at: time::PrimitiveDateTime, +} + +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, Clone, ToSchema)] +pub struct OrganizationResponse { + /// The unique identifier for the Organization + #[schema(value_type = String, max_length = 64, min_length = 1, example = "org_q98uSGAYbjEwqs0mJwnz")] + pub id: id_type::OrganizationId, + + /// Name of the Organization + pub organization_name: Option, + + /// Details about the organization + #[schema(value_type = Option)] + pub organization_details: Option, + + /// Metadata is useful for storing additional, unstructured information on an object. #[schema(value_type = Option)] pub metadata: Option, pub modified_at: time::PrimitiveDateTime, diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 0bb5e65213f3..2c2aa4861c55 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -778,7 +778,7 @@ pub struct PaymentMethodResponse { #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema, Clone)] pub struct PaymentMethodResponse { /// Unique identifier for a merchant - #[schema(example = "merchant_1671528864", value_type = String)] + #[schema(value_type = String, example = "merchant_1671528864")] pub merchant_id: id_type::MerchantId, /// The unique identifier of the customer. diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index b7a6c12500d1..006d78e2f7ab 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -125,7 +125,7 @@ impl PaymentIntent { publishable_key: String, ) -> CustomResult { let start_redirection_url = &format!( - "{}/v2/payments/{}/start_redirection?publishable_key={}&profile_id={}", + "{}/v2/payments/{}/start-redirection?publishable_key={}&profile_id={}", base_url, self.get_id().get_string_repr(), publishable_key, @@ -144,7 +144,7 @@ impl PaymentIntent { publishable_key: &str, ) -> CustomResult { let finish_redirection_url = format!( - "{base_url}/v2/payments/{}/finish_redirection/{publishable_key}/{}", + "{base_url}/v2/payments/{}/finish-redirection/{publishable_key}/{}", self.id.get_string_repr(), self.profile_id.get_string_repr() ); diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 4198e90882e5..a756d9fb1b10 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -127,6 +127,17 @@ Never share your secret api keys. Keep them guarded and secure. routes::payments::payments_confirm_intent, routes::payments::payment_status, + //Routes for payment methods + routes::payment_method::list_customer_payment_method_for_payment, + routes::payment_method::list_customer_payment_method_api, + routes::payment_method::create_payment_method_api, + routes::payment_method::create_payment_method_intent_api, + routes::payment_method::confirm_payment_method_intent_api, + routes::payment_method::payment_method_update_api, + routes::payment_method::payment_method_retrieve_api, + routes::payment_method::payment_method_delete_api, + + //Routes for refunds routes::refunds::refunds_create, ), @@ -170,9 +181,12 @@ Never share your secret api keys. Keep them guarded and secure. api_models::customers::CustomerRequest, api_models::customers::CustomerDeleteResponse, api_models::payment_methods::PaymentMethodCreate, + api_models::payment_methods::PaymentMethodIntentCreate, + api_models::payment_methods::PaymentMethodIntentConfirm, api_models::payment_methods::PaymentMethodResponse, api_models::payment_methods::PaymentMethodResponseData, api_models::payment_methods::CustomerPaymentMethod, + api_models::payment_methods::PaymentMethodListRequest, api_models::payment_methods::PaymentMethodListResponse, api_models::payment_methods::ResponsePaymentMethodsEnabled, api_models::payment_methods::ResponsePaymentMethodTypes, @@ -189,6 +203,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payment_methods::PaymentMethodCreateData, api_models::payment_methods::CardDetail, api_models::payment_methods::CardDetailUpdate, + api_models::payment_methods::CardType, api_models::payment_methods::RequestPaymentMethodTypes, api_models::payment_methods::CardType, api_models::payment_methods::PaymentMethodListData, diff --git a/crates/openapi/src/routes/api_keys.rs b/crates/openapi/src/routes/api_keys.rs index cfc4c09ce461..964fa60fcf59 100644 --- a/crates/openapi/src/routes/api_keys.rs +++ b/crates/openapi/src/routes/api_keys.rs @@ -25,7 +25,7 @@ pub async fn api_key_create() {} /// displayed only once on creation, so ensure you store it securely. #[utoipa::path( post, - path = "/v2/api_keys", + path = "/v2/api-keys", request_body= CreateApiKeyRequest, responses( (status = 200, description = "API Key created", body = CreateApiKeyResponse), @@ -64,7 +64,7 @@ pub async fn api_key_retrieve() {} /// Retrieve information about the specified API Key. #[utoipa::path( get, - path = "/v2/api_keys/{id}", + path = "/v2/api-keys/{id}", params ( ("id" = String, Path, description = "The unique identifier for the API Key") ), @@ -106,7 +106,7 @@ pub async fn api_key_update() {} /// Update information for the specified API Key. #[utoipa::path( put, - path = "/v2/api_keys/{id}", + path = "/v2/api-keys/{id}", request_body = UpdateApiKeyRequest, params ( ("id" = String, Path, description = "The unique identifier for the API Key") @@ -150,7 +150,7 @@ pub async fn api_key_revoke() {} /// authenticating with our APIs. #[utoipa::path( delete, - path = "/v2/api_keys/{id}", + path = "/v2/api-keys/{id}", params ( ("id" = String, Path, description = "The unique identifier for the API Key") ), @@ -191,7 +191,7 @@ pub async fn api_key_list() {} /// List all the API Keys associated to a merchant account. #[utoipa::path( get, - path = "/v2/api_keys/list", + path = "/v2/api-keys/list", params( ("limit" = Option, Query, description = "The maximum number of API Keys to include in the response"), ("skip" = Option, Query, description = "The number of API Keys to skip when retrieving the list of API keys."), diff --git a/crates/openapi/src/routes/merchant_account.rs b/crates/openapi/src/routes/merchant_account.rs index 022a5e6c006a..a3bf96ab897f 100644 --- a/crates/openapi/src/routes/merchant_account.rs +++ b/crates/openapi/src/routes/merchant_account.rs @@ -50,7 +50,7 @@ pub async fn merchant_account_create() {} /// Before creating the merchant account, it is mandatory to create an organization. #[utoipa::path( post, - path = "/v2/merchant_accounts", + path = "/v2/merchant-accounts", params( ( "X-Organization-Id" = String, Header, @@ -128,7 +128,7 @@ pub async fn retrieve_merchant_account() {} /// Retrieve a *merchant* account details. #[utoipa::path( get, - path = "/v2/merchant_accounts/{id}", + path = "/v2/merchant-accounts/{id}", params (("id" = String, Path, description = "The unique identifier for the merchant account")), responses( (status = 200, description = "Merchant Account Retrieved", body = MerchantAccountResponse), @@ -190,7 +190,7 @@ pub async fn update_merchant_account() {} /// Updates details of an existing merchant account. Helpful in updating merchant details such as email, contact details, or other configuration details like webhook, routing algorithm etc #[utoipa::path( put, - path = "/v2/merchant_accounts/{id}", + path = "/v2/merchant-accounts/{id}", request_body ( content = MerchantAccountUpdate, examples( @@ -300,7 +300,7 @@ pub async fn payment_connector_list_profile() {} /// List profiles for an Merchant #[utoipa::path( get, - path = "/v2/merchant_accounts/{id}/profiles", + path = "/v2/merchant-accounts/{id}/profiles", params (("id" = String, Path, description = "The unique identifier for the Merchant")), responses( (status = 200, description = "profile list retrieved successfully", body = Vec), diff --git a/crates/openapi/src/routes/merchant_connector_account.rs b/crates/openapi/src/routes/merchant_connector_account.rs index 29092b5bba08..372f8688a26c 100644 --- a/crates/openapi/src/routes/merchant_connector_account.rs +++ b/crates/openapi/src/routes/merchant_connector_account.rs @@ -67,7 +67,7 @@ pub async fn connector_create() {} #[cfg(feature = "v2")] #[utoipa::path( post, - path = "/v2/connector_accounts", + path = "/v2/connector-accounts", request_body( content = MerchantConnectorCreate, examples( @@ -152,7 +152,7 @@ pub async fn connector_retrieve() {} #[cfg(feature = "v2")] #[utoipa::path( get, - path = "/v2/connector_accounts/{id}", + path = "/v2/connector-accounts/{id}", params( ("id" = i32, Path, description = "The unique identifier for the Merchant Connector") ), @@ -241,7 +241,7 @@ pub async fn connector_update() {} #[cfg(feature = "v2")] #[utoipa::path( put, - path = "/v2/connector_accounts/{id}", + path = "/v2/connector-accounts/{id}", request_body( content = MerchantConnectorUpdate, examples( @@ -310,7 +310,7 @@ pub async fn connector_delete() {} #[cfg(feature = "v2")] #[utoipa::path( delete, - path = "/v2/connector_accounts/{id}", + path = "/v2/connector-accounts/{id}", params( ("id" = i32, Path, description = "The unique identifier for the Merchant Connector") ), diff --git a/crates/openapi/src/routes/organization.rs b/crates/openapi/src/routes/organization.rs index ce3199343cfd..d677131d5dba 100644 --- a/crates/openapi/src/routes/organization.rs +++ b/crates/openapi/src/routes/organization.rs @@ -150,7 +150,7 @@ pub async fn organization_update() {} /// List merchant accounts for an Organization #[utoipa::path( get, - path = "/v2/organization/{id}/merchant_accounts", + path = "/v2/organization/{id}/merchant-accounts", params (("id" = String, Path, description = "The unique identifier for the Organization")), responses( (status = 200, description = "Merchant Account list retrieved successfully", body = Vec), diff --git a/crates/openapi/src/routes/payment_method.rs b/crates/openapi/src/routes/payment_method.rs index 3bc593aa5b2b..b38a23426788 100644 --- a/crates/openapi/src/routes/payment_method.rs +++ b/crates/openapi/src/routes/payment_method.rs @@ -31,6 +31,7 @@ operation_id = "Create a Payment Method", security(("api_key" = [])) )] +#[cfg(feature = "v1")] pub async fn create_payment_method_api() {} /// List payment methods for a Merchant @@ -84,6 +85,7 @@ pub async fn list_payment_method_api() {} operation_id = "List all Payment Methods for a Customer", security(("api_key" = [])) )] +#[cfg(feature = "v1")] pub async fn list_customer_payment_method_api() {} /// List customer saved payment methods for a Payment @@ -130,6 +132,7 @@ pub async fn list_customer_payment_method_api_client() {} operation_id = "Retrieve a Payment method", security(("api_key" = [])) )] +#[cfg(feature = "v1")] pub async fn payment_method_retrieve_api() {} /// Payment Method - Update @@ -151,6 +154,7 @@ pub async fn payment_method_retrieve_api() {} operation_id = "Update a Payment method", security(("api_key" = []), ("publishable_key" = [])) )] +#[cfg(feature = "v1")] pub async fn payment_method_update_api() {} /// Payment Method - Delete @@ -170,6 +174,7 @@ pub async fn payment_method_update_api() {} operation_id = "Delete a Payment method", security(("api_key" = [])) )] +#[cfg(feature = "v1")] pub async fn payment_method_delete_api() {} /// Payment Method - Set Default Payment Method for Customer @@ -192,3 +197,171 @@ pub async fn payment_method_delete_api() {} security(("ephemeral_key" = [])) )] pub async fn default_payment_method_set_api() {} + +/// Payment Method - Create Intent +/// +/// Creates a payment method for customer with billing information and other metadata. +#[utoipa::path( + post, + path = "/v2/payment-methods/create-intent", + request_body( + content = PaymentMethodIntentCreate, + // TODO: Add examples + ), + responses( + (status = 200, description = "Payment Method Intent Created", body = PaymentMethodResponse), + (status = 400, description = "Invalid Data"), + ), + tag = "Payment Methods", + operation_id = "Create Payment Method Intent", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn create_payment_method_intent_api() {} + +/// Payment Method - Confirm Intent +/// +/// Update a payment method with customer's payment method related information. +#[utoipa::path( + post, + path = "/v2/payment-methods/{id}/confirm-intent", + request_body( + content = PaymentMethodIntentConfirm, + // TODO: Add examples + ), + responses( + (status = 200, description = "Payment Method Intent Confirmed", body = PaymentMethodResponse), + (status = 400, description = "Invalid Data"), + ), + tag = "Payment Methods", + operation_id = "Confirm Payment Method Intent", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn confirm_payment_method_intent_api() {} + +/// Payment Method - Create +/// +/// Creates and stores a payment method against a customer. In case of cards, this API should be used only by PCI compliant merchants. +#[utoipa::path( + post, + path = "/v2/payment-methods", + request_body( + content = PaymentMethodCreate, + // TODO: Add examples + ), + responses( + (status = 200, description = "Payment Method Created", body = PaymentMethodResponse), + (status = 400, description = "Invalid Data"), + ), + tag = "Payment Methods", + operation_id = "Create Payment Method", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn create_payment_method_api() {} + +/// Payment Method - Retrieve +/// +/// Retrieves a payment method of a customer. +#[utoipa::path( + get, + path = "/v2/payment-methods/{id}", + params ( + ("id" = String, Path, description = "The unique identifier for the Payment Method"), + ), + responses( + (status = 200, description = "Payment Method Retrieved", body = PaymentMethodResponse), + (status = 404, description = "Payment Method Not Found"), + ), + tag = "Payment Methods", + operation_id = "Retrieve Payment Method", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn payment_method_retrieve_api() {} + +/// Payment Method - Update +/// +/// Update an existing payment method of a customer. +#[utoipa::path( + patch, + path = "/v2/payment-methods/{id}/update-saved-payment-method", + request_body( + content = PaymentMethodUpdate, + // TODO: Add examples + ), + responses( + (status = 200, description = "Payment Method Update", body = PaymentMethodResponse), + (status = 400, description = "Invalid Data"), + ), + tag = "Payment Methods", + operation_id = "Update Payment Method", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn payment_method_update_api() {} + +/// Payment Method - Delete +/// +/// Deletes a payment method of a customer. +#[utoipa::path( + delete, + path = "/v2/payment-methods/{id}", + params ( + ("id" = String, Path, description = "The unique identifier for the Payment Method"), + ), + responses( + (status = 200, description = "Payment Method Retrieved", body = PaymentMethodDeleteResponse), + (status = 404, description = "Payment Method Not Found"), + ), + tag = "Payment Methods", + operation_id = "Delete Payment Method", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn payment_method_delete_api() {} + +/// List customer saved payment methods for a payment +/// +/// To filter and list the applicable payment methods for a particular Customer ID, is to be associated with a payment +#[utoipa::path( + get, + path = "/v2/payments/{id}/saved-payment-methods", + request_body( + content = PaymentMethodListRequest, + // TODO: Add examples and add param for customer_id + ), + responses( + (status = 200, description = "Payment Methods retrieved for customer tied to its respective client-secret passed in the param", body = CustomerPaymentMethodsListResponse), + (status = 400, description = "Invalid Data"), + (status = 404, description = "Payment Methods does not exist in records") + ), + tag = "Payment Methods", + operation_id = "List all Payment Methods for a Customer", + security(("publishable_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn list_customer_payment_method_for_payment() {} + +/// List saved payment methods for a Customer +/// +/// To filter and list the applicable payment methods for a particular Customer ID, to be used in a non-payments context +#[utoipa::path( + get, + path = "/v2/customers/{id}/saved-payment-methods", + request_body( + content = PaymentMethodListRequest, + // TODO: Add examples and add param for customer_id + ), + responses( + (status = 200, description = "Payment Methods retrieved", body = CustomerPaymentMethodsListResponse), + (status = 400, description = "Invalid Data"), + (status = 404, description = "Payment Methods does not exist in records") + ), + tag = "Payment Methods", + operation_id = "List all Payment Methods for a Customer", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn list_customer_payment_method_api() {} diff --git a/crates/openapi/src/routes/profile.rs b/crates/openapi/src/routes/profile.rs index d88568653a44..cc484aa3f953 100644 --- a/crates/openapi/src/routes/profile.rs +++ b/crates/openapi/src/routes/profile.rs @@ -210,7 +210,7 @@ pub async fn profile_update() {} /// Activates a routing algorithm under a profile #[utoipa::path( patch, - path = "/v2/profiles/{id}/activate_routing_algorithm", + path = "/v2/profiles/{id}/activate-routing-algorithm", request_body ( content = RoutingAlgorithmId, examples( ( "Activate a routing algorithm" = ( @@ -240,7 +240,7 @@ pub async fn routing_link_config() {} /// Deactivates a routing algorithm under a profile #[utoipa::path( patch, - path = "/v2/profiles/{id}/deactivate_routing_algorithm", + path = "/v2/profiles/{id}/deactivate-routing-algorithm", params( ("id" = String, Path, description = "The unique identifier for the profile"), ), @@ -263,7 +263,7 @@ pub async fn routing_unlink_config() {} /// Update the default fallback routing algorithm for the profile #[utoipa::path( patch, - path = "/v2/profiles/{id}/fallback_routing", + path = "/v2/profiles/{id}/fallback-routing", request_body = Vec, params( ("id" = String, Path, description = "The unique identifier for the profile"), @@ -307,11 +307,11 @@ pub async fn profile_retrieve() {} #[cfg(feature = "v2")] /// Profile - Retrieve Active Routing Algorithm -/// +///_ /// Retrieve active routing algorithm under the profile #[utoipa::path( get, - path = "/v2/profiles/{id}/routing_algorithm", + path = "/v2/profiles/{id}/routing-algorithm", params( ("id" = String, Path, description = "The unique identifier for the profile"), ("limit" = Option, Query, description = "The number of records of the algorithms to be returned"), @@ -334,7 +334,7 @@ pub async fn routing_retrieve_linked_config() {} /// Retrieve the default fallback routing algorithm for the profile #[utoipa::path( get, - path = "/v2/profiles/{id}/fallback_routing", + path = "/v2/profiles/{id}/fallback-routing", params( ("id" = String, Path, description = "The unique identifier for the profile"), ), @@ -353,7 +353,7 @@ pub async fn routing_retrieve_default_config() {} /// List Connector Accounts for the profile #[utoipa::path( get, - path = "/v2/profiles/{id}/connector_accounts", + path = "/v2/profiles/{id}/connector-accounts", params( ("id" = String, Path, description = "The unique identifier for the business profile"), ( diff --git a/crates/openapi/src/routes/routing.rs b/crates/openapi/src/routes/routing.rs index 67a22c2ca640..b144fd046ad4 100644 --- a/crates/openapi/src/routes/routing.rs +++ b/crates/openapi/src/routes/routing.rs @@ -26,7 +26,7 @@ pub async fn routing_create_config() {} /// Create a routing algorithm #[utoipa::path( post, - path = "/v2/routing_algorithm", + path = "/v2/routing-algorithm", request_body = RoutingConfigRequest, responses( (status = 200, description = "Routing Algorithm created", body = RoutingDictionaryRecord), @@ -94,7 +94,7 @@ pub async fn routing_retrieve_config() {} #[utoipa::path( get, - path = "/v2/routing_algorithm/{id}", + path = "/v2/routing-algorithm/{id}", params( ("id" = String, Path, description = "The unique identifier for a routing algorithm"), ), diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 0f13e08d53e1..bb0c547d7f1d 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -556,11 +556,15 @@ impl Payments { ) .service(web::resource("").route(web::get().to(payments::payment_status))) .service( - web::resource("/start_redirection") + web::resource("/start-redirection") .route(web::get().to(payments::payments_start_redirection)), ) .service( - web::resource("/finish_redirection/{publishable_key}/{profile_id}") + web::resource("/saved-payment-methods") + .route(web::get().to(list_customer_payment_method_for_payment)), + ) + .service( + web::resource("/finish-redirection/{publishable_key}/{profile_id}") .route(web::get().to(payments::payments_finish_redirection)), ), ); @@ -715,7 +719,7 @@ pub struct Routing; #[cfg(all(feature = "olap", feature = "v2"))] impl Routing { pub fn server(state: AppState) -> Scope { - web::scope("/v2/routing_algorithm") + web::scope("/v2/routing-algorithm") .app_data(web::Data::new(state.clone())) .service( web::resource("").route(web::post().to(|state, req, payload| { @@ -968,7 +972,7 @@ impl Customers { #[cfg(all(feature = "oltp", feature = "v2", feature = "payment_methods_v2"))] { route = route.service( - web::resource("/{customer_id}/saved_payment_methods") + web::resource("/{customer_id}/saved-payment-methods") .route(web::get().to(list_customer_payment_method_api)), ); } @@ -1113,7 +1117,7 @@ impl Payouts { #[cfg(all(feature = "oltp", feature = "v2", feature = "payment_methods_v2",))] impl PaymentMethods { pub fn server(state: AppState) -> Scope { - let mut route = web::scope("/v2/payment_methods").app_data(web::Data::new(state)); + let mut route = web::scope("/v2/payment-methods").app_data(web::Data::new(state)); route = route .service(web::resource("").route(web::post().to(create_payment_method_api))) .service( @@ -1125,7 +1129,7 @@ impl PaymentMethods { .route(web::post().to(confirm_payment_method_intent_api)), ) .service( - web::resource("/{id}/update_saved_payment_method") + web::resource("/{id}/update-saved-payment-method") .route(web::patch().to(payment_method_update_api)), ) .service(web::resource("/{id}").route(web::get().to(payment_method_retrieve_api))) @@ -1267,7 +1271,7 @@ impl Organization { .route(web::put().to(admin::organization_update)), ) .service( - web::resource("/merchant_accounts") + web::resource("/merchant-accounts") .route(web::get().to(admin::merchant_account_list)), ), ) @@ -1279,7 +1283,7 @@ pub struct MerchantAccount; #[cfg(all(feature = "v2", feature = "olap"))] impl MerchantAccount { pub fn server(state: AppState) -> Scope { - web::scope("/v2/merchant_accounts") + web::scope("/v2/merchant-accounts") .app_data(web::Data::new(state)) .service(web::resource("").route(web::post().to(admin::merchant_account_create))) .service( @@ -1329,7 +1333,7 @@ pub struct MerchantConnectorAccount; #[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v2"))] impl MerchantConnectorAccount { pub fn server(state: AppState) -> Scope { - let mut route = web::scope("/v2/connector_accounts").app_data(web::Data::new(state)); + let mut route = web::scope("/v2/connector-accounts").app_data(web::Data::new(state)); #[cfg(feature = "olap")] { @@ -1526,7 +1530,7 @@ pub struct ApiKeys; #[cfg(all(feature = "olap", feature = "v2"))] impl ApiKeys { pub fn server(state: AppState) -> Scope { - web::scope("/v2/api_keys") + web::scope("/v2/api-keys") .app_data(web::Data::new(state)) .service(web::resource("").route(web::post().to(api_keys::api_key_create))) .service(web::resource("/list").route(web::get().to(api_keys::api_key_list))) @@ -1691,16 +1695,16 @@ impl Profile { .route(web::put().to(profiles::profile_update)), ) .service( - web::resource("/connector_accounts") + web::resource("/connector-accounts") .route(web::get().to(admin::connector_list)), ) .service( - web::resource("/fallback_routing") + web::resource("/fallback-routing") .route(web::get().to(routing::routing_retrieve_default_config)) .route(web::patch().to(routing::routing_update_default_config)), ) .service( - web::resource("/activate_routing_algorithm").route(web::patch().to( + web::resource("/activate-routing-algorithm").route(web::patch().to( |state, req, path, payload| { routing::routing_link_config( state, @@ -1713,7 +1717,7 @@ impl Profile { )), ) .service( - web::resource("/deactivate_routing_algorithm").route(web::patch().to( + web::resource("/deactivate-routing-algorithm").route(web::patch().to( |state, req, path| { routing::routing_unlink_config( state, @@ -1724,7 +1728,7 @@ impl Profile { }, )), ) - .service(web::resource("/routing_algorithm").route(web::get().to( + .service(web::resource("/routing-algorithm").route(web::get().to( |state, req, query_params, path| { routing::routing_retrieve_linked_config( state, @@ -1999,7 +2003,7 @@ impl User { ) .service(web::resource("/verify_email").route(web::post().to(user::verify_email))) .service( - web::resource("/v2/verify_email").route(web::post().to(user::verify_email)), + web::resource("/v2/verify-email").route(web::post().to(user::verify_email)), ) .service( web::resource("/verify_email_request") @@ -2053,7 +2057,7 @@ impl User { .route(web::post().to(user_role::accept_invitations_v2)), ) .service( - web::resource("/pre_auth").route( + web::resource("/pre-auth").route( web::post().to(user_role::accept_invitations_pre_auth), ), ), diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 7296a510248a..8ee31ecf943a 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -508,30 +508,6 @@ pub async fn list_customer_payment_method_api( } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -/// List payment methods for a Customer v2 -/// -/// To filter and list the applicable payment methods for a particular Customer ID, is to be associated with a payment -#[utoipa::path( - get, - path = "v2/payments/{payment_id}/saved_payment_methods", - params ( - ("client-secret" = String, Path, description = "A secret known only to your application and the authorization server"), - ("accepted_country" = Vec, Query, description = "The two-letter ISO currency code"), - ("accepted_currency" = Vec, Path, description = "The three-letter ISO currency code"), - ("minimum_amount" = i64, Query, description = "The minimum amount accepted for processing by the particular payment method."), - ("maximum_amount" = i64, Query, description = "The maximum amount amount accepted for processing by the particular payment method."), - ("recurring_payment_enabled" = bool, Query, description = "Indicates whether the payment method is eligible for recurring payments"), - ("installment_payment_enabled" = bool, Query, description = "Indicates whether the payment method is eligible for installment payments"), - ), - responses( - (status = 200, description = "Payment Methods retrieved for customer tied to its respective client-secret passed in the param", body = CustomerPaymentMethodsListResponse), - (status = 400, description = "Invalid Data"), - (status = 404, description = "Payment Methods does not exist in records") - ), - tag = "Payment Methods", - operation_id = "List all Payment Methods for a Customer", - security(("publishable_key" = [])) -)] #[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] pub async fn list_customer_payment_method_for_payment( state: web::Data, @@ -575,29 +551,6 @@ pub async fn list_customer_payment_method_for_payment( feature = "payment_methods_v2", feature = "customer_v2" ))] -/// List payment methods for a Customer v2 -/// -/// To filter and list the applicable payment methods for a particular Customer ID, to be used in a non-payments context -#[utoipa::path( - get, - path = "v2/customers/{customer_id}/saved_payment_methods", - params ( - ("accepted_country" = Vec, Query, description = "The two-letter ISO currency code"), - ("accepted_currency" = Vec, Path, description = "The three-letter ISO currency code"), - ("minimum_amount" = i64, Query, description = "The minimum amount accepted for processing by the particular payment method."), - ("maximum_amount" = i64, Query, description = "The maximum amount amount accepted for processing by the particular payment method."), - ("recurring_payment_enabled" = bool, Query, description = "Indicates whether the payment method is eligible for recurring payments"), - ("installment_payment_enabled" = bool, Query, description = "Indicates whether the payment method is eligible for installment payments"), - ), - responses( - (status = 200, description = "Payment Methods retrieved", body = CustomerPaymentMethodsListResponse), - (status = 400, description = "Invalid Data"), - (status = 404, description = "Payment Methods does not exist in records") - ), - tag = "Payment Methods", - operation_id = "List all Payment Methods for a Customer", - security(("api_key" = [])) -)] #[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] pub async fn list_customer_payment_method_api( state: web::Data, diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index c81fc7ceb484..85275a768dfd 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -34,6 +34,10 @@ use crate::{ impl ForeignFrom for OrganizationResponse { fn foreign_from(org: diesel_models::organization::Organization) -> Self { Self { + #[cfg(feature = "v2")] + id: org.get_organization_id(), + + #[cfg(feature = "v1")] organization_id: org.get_organization_id(), organization_name: org.get_organization_name(), organization_details: org.organization_details, diff --git a/cypress-tests-v2/cypress/support/commands.js b/cypress-tests-v2/cypress/support/commands.js index eb4ca3423eb3..b6955c542a70 100644 --- a/cypress-tests-v2/cypress/support/commands.js +++ b/cypress-tests-v2/cypress/support/commands.js @@ -64,10 +64,10 @@ Cypress.Commands.add( if (response.status === 200) { expect(response.body) - .to.have.property("organization_id") + .to.have.property("id") .and.to.include("org_") .and.to.be.a("string").and.not.be.empty; - globalState.set("organizationId", response.body.organization_id); + globalState.set("organizationId", response.body.id); cy.task("setGlobalState", globalState.data); expect(response.body).to.have.property("metadata").and.to.equal(null); } else { @@ -99,7 +99,7 @@ Cypress.Commands.add("organizationRetrieveCall", (globalState) => { if (response.status === 200) { expect(response.body) - .to.have.property("organization_id") + .to.have.property("id") .and.to.include("org_") .and.to.be.a("string").and.not.be.empty; expect(response.body.organization_name) @@ -107,7 +107,7 @@ Cypress.Commands.add("organizationRetrieveCall", (globalState) => { .and.to.be.a("string").and.not.be.empty; if (organization_id === undefined || organization_id === null) { - globalState.set("organizationId", response.body.organization_id); + globalState.set("organizationId", response.body.id); cy.task("setGlobalState", globalState.data); } } else { @@ -144,14 +144,14 @@ Cypress.Commands.add( if (response.status === 200) { expect(response.body) - .to.have.property("organization_id") + .to.have.property("id") .and.to.include("org_") .and.to.be.a("string").and.not.be.empty; expect(response.body).to.have.property("metadata").and.to.be.a("object") .and.not.be.empty; if (organization_id === undefined || organization_id === null) { - globalState.set("organizationId", response.body.organization_id); + globalState.set("organizationId", response.body.id); cy.task("setGlobalState", globalState.data); } } else { @@ -174,7 +174,7 @@ Cypress.Commands.add( const key_id_type = "publishable_key"; const key_id = validateEnv(base_url, key_id_type); const organization_id = globalState.get("organizationId"); - const url = `${base_url}/v2/merchant_accounts`; + const url = `${base_url}/v2/merchant-accounts`; const merchant_name = merchantAccountCreateBody.merchant_name .replaceAll(" ", "") @@ -223,7 +223,7 @@ Cypress.Commands.add("merchantAccountRetrieveCall", (globalState) => { const key_id_type = "publishable_key"; const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/merchant_accounts/${merchant_id}`; + const url = `${base_url}/v2/merchant-accounts/${merchant_id}`; cy.request({ method: "GET", @@ -265,7 +265,7 @@ Cypress.Commands.add( const key_id_type = "publishable_key"; const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/merchant_accounts/${merchant_id}`; + const url = `${base_url}/v2/merchant-accounts/${merchant_id}`; const merchant_name = merchantAccountUpdateBody.merchant_name; @@ -456,7 +456,7 @@ Cypress.Commands.add( const base_url = globalState.get("baseUrl"); const merchant_id = globalState.get("merchantId"); const profile_id = globalState.get("profileId"); - const url = `${base_url}/v2/connector_accounts`; + const url = `${base_url}/v2/connector-accounts`; const customHeaders = { "x-merchant-id": merchant_id, @@ -536,7 +536,7 @@ Cypress.Commands.add("mcaRetrieveCall", (globalState) => { const connector_name = globalState.get("connectorId"); const merchant_connector_id = globalState.get("merchantConnectorId"); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/connector_accounts/${merchant_connector_id}`; + const url = `${base_url}/v2/connector-accounts/${merchant_connector_id}`; const customHeaders = { "x-merchant-id": merchant_id, @@ -590,7 +590,7 @@ Cypress.Commands.add( const merchant_connector_id = globalState.get("merchantConnectorId"); const merchant_id = globalState.get("merchantId"); const profile_id = globalState.get("profileId"); - const url = `${base_url}/v2/connector_accounts/${merchant_connector_id}`; + const url = `${base_url}/v2/connector-accounts/${merchant_connector_id}`; const customHeaders = { "x-merchant-id": merchant_id, @@ -653,7 +653,7 @@ Cypress.Commands.add("apiKeyCreateCall", (apiKeyCreateBody, globalState) => { const key_id_type = "key_id"; const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/api_keys`; + const url = `${base_url}/v2/api-keys`; const customHeaders = { "x-merchant-id": merchant_id, @@ -703,7 +703,7 @@ Cypress.Commands.add("apiKeyRetrieveCall", (globalState) => { const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); const api_key_id = globalState.get("apiKeyId"); - const url = `${base_url}/v2/api_keys/${api_key_id}`; + const url = `${base_url}/v2/api-keys/${api_key_id}`; const customHeaders = { "x-merchant-id": merchant_id, @@ -750,7 +750,7 @@ Cypress.Commands.add("apiKeyUpdateCall", (apiKeyUpdateBody, globalState) => { const key_id_type = "key_id"; const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/api_keys/${api_key_id}`; + const url = `${base_url}/v2/api-keys/${api_key_id}`; const customHeaders = { "x-merchant-id": merchant_id, @@ -801,7 +801,7 @@ Cypress.Commands.add( const api_key = globalState.get("userInfoToken"); const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); - const url = `${base_url}/v2/routing_algorithm`; + const url = `${base_url}/v2/routing-algorithm`; // Update request body routingSetupBody.algorithm.data = payload.data; @@ -847,7 +847,7 @@ Cypress.Commands.add( const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); const routing_algorithm_id = globalState.get("routingAlgorithmId"); - const url = `${base_url}/v2/profiles/${profile_id}/activate_routing_algorithm`; + const url = `${base_url}/v2/profiles/${profile_id}/activate-routing-algorithm`; // Update request body routingActivationBody.routing_algorithm_id = routing_algorithm_id; @@ -885,7 +885,7 @@ Cypress.Commands.add("routingActivationRetrieveCall", (globalState) => { const profile_id = globalState.get("profileId"); const query_params = "limit=10"; const routing_algorithm_id = globalState.get("routingAlgorithmId"); - const url = `${base_url}/v2/profiles/${profile_id}/routing_algorithm?${query_params}`; + const url = `${base_url}/v2/profiles/${profile_id}/routing-algorithm?${query_params}`; cy.request({ method: "GET", @@ -922,7 +922,7 @@ Cypress.Commands.add("routingDeactivateCall", (globalState) => { const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); const routing_algorithm_id = globalState.get("routingAlgorithmId"); - const url = `${base_url}/v2/profiles/${profile_id}/deactivate_routing_algorithm`; + const url = `${base_url}/v2/profiles/${profile_id}/deactivate-routing-algorithm`; cy.request({ method: "PATCH", @@ -957,7 +957,7 @@ Cypress.Commands.add("routingRetrieveCall", (globalState) => { const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); const routing_algorithm_id = globalState.get("routingAlgorithmId"); - const url = `${base_url}/v2/routing_algorithm/${routing_algorithm_id}`; + const url = `${base_url}/v2/routing-algorithm/${routing_algorithm_id}`; cy.request({ method: "GET", @@ -996,7 +996,7 @@ Cypress.Commands.add( const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); const routing_algorithm_id = globalState.get("routingAlgorithmId"); - const url = `${base_url}/v2/profiles/${profile_id}/fallback_routing`; + const url = `${base_url}/v2/profiles/${profile_id}/fallback-routing`; // Update request body routingDefaultFallbackBody = payload; @@ -1029,7 +1029,7 @@ Cypress.Commands.add("routingFallbackRetrieveCall", (globalState) => { const api_key = globalState.get("userInfoToken"); const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); - const url = `${base_url}/v2/profiles/${profile_id}/fallback_routing`; + const url = `${base_url}/v2/profiles/${profile_id}/fallback-routing`; cy.request({ method: "GET", @@ -1166,7 +1166,7 @@ Cypress.Commands.add("merchantAccountsListCall", (globalState) => { const key_id_type = "publishable_key"; const key_id = validateEnv(base_url, key_id_type); const organization_id = globalState.get("organizationId"); - const url = `${base_url}/v2/organization/${organization_id}/merchant_accounts`; + const url = `${base_url}/v2/organization/${organization_id}/merchant-accounts`; cy.request({ method: "GET", @@ -1204,7 +1204,7 @@ Cypress.Commands.add("businessProfilesListCall", (globalState) => { const api_key = globalState.get("adminApiKey"); const base_url = globalState.get("baseUrl"); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/merchant_accounts/${merchant_id}/profiles`; + const url = `${base_url}/v2/merchant-accounts/${merchant_id}/profiles`; const customHeaders = { "x-merchant-id": merchant_id, @@ -1246,7 +1246,7 @@ Cypress.Commands.add("mcaListCall", (globalState, service_type) => { const base_url = globalState.get("baseUrl"); const merchant_id = globalState.get("merchantId"); const profile_id = globalState.get("profileId"); - const url = `${base_url}/v2/profiles/${profile_id}/connector_accounts`; + const url = `${base_url}/v2/profiles/${profile_id}/connector-accounts`; const customHeaders = { "x-merchant-id": merchant_id, @@ -1308,7 +1308,7 @@ Cypress.Commands.add("apiKeysListCall", (globalState) => { const key_id_type = "key_id"; const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/api_keys/list`; + const url = `${base_url}/v2/api-keys/list`; const customHeaders = { "x-merchant-id": merchant_id, From 55fe82fdcd78df9608842190f1423088740d1087 Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:42:09 +0530 Subject: [PATCH 42/51] refactor(users): Use domain email type in user DB functions (#6699) --- .../src/errors/api_error_response.rs | 8 ++++ .../router/src/compatibility/stripe/errors.rs | 3 +- crates/router/src/core/user.rs | 44 ++++++------------- crates/router/src/core/user_role.rs | 2 +- crates/router/src/db/kafka_store.rs | 6 +-- crates/router/src/db/user.rs | 23 +++++----- crates/router/src/services/authentication.rs | 14 +++--- .../src/services/authentication/cookies.rs | 6 +-- crates/router/src/services/authorization.rs | 10 +++-- crates/router/src/services/email/types.rs | 11 +++-- crates/router/src/types/domain/user.rs | 6 ++- crates/router/src/utils/user.rs | 2 +- 12 files changed, 68 insertions(+), 67 deletions(-) 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 b02da3960756..850cc4703167 100644 --- a/crates/hyperswitch_domain_models/src/errors/api_error_response.rs +++ b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs @@ -274,6 +274,11 @@ pub enum ApiErrorResponse { LinkConfigurationError { message: String }, #[error(error_type = ErrorType::InvalidRequestError, code = "IR_41", message = "Payout validation failed")] PayoutFailed { data: Option }, + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_42", + message = "Cookies are not found in the request" + )] + CookieNotFound, #[error(error_type = ErrorType::InvalidRequestError, code = "WE_01", message = "Failed to authenticate the webhook")] WebhookAuthenticationFailed, @@ -627,6 +632,9 @@ impl ErrorSwitch for ApiErrorRespon Self::PayoutFailed { data } => { AER::BadRequest(ApiError::new("IR", 41, "Payout failed while processing with connector.", Some(Extra { data: data.clone(), ..Default::default()}))) }, + Self::CookieNotFound => { + AER::Unauthorized(ApiError::new("IR", 42, "Cookies are not found in the request", None)) + }, Self::WebhookAuthenticationFailed => { AER::Unauthorized(ApiError::new("WE", 1, "Webhook authentication failed", None)) diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs index efe0ac157e01..6cf078b5f817 100644 --- a/crates/router/src/compatibility/stripe/errors.rs +++ b/crates/router/src/compatibility/stripe/errors.rs @@ -444,7 +444,8 @@ impl From for StripeErrorCode { | errors::ApiErrorResponse::GenericUnauthorized { .. } | errors::ApiErrorResponse::AccessForbidden { .. } | errors::ApiErrorResponse::InvalidCookie - | errors::ApiErrorResponse::InvalidEphemeralKey => Self::Unauthorized, + | errors::ApiErrorResponse::InvalidEphemeralKey + | errors::ApiErrorResponse::CookieNotFound => Self::Unauthorized, errors::ApiErrorResponse::InvalidRequestUrl | errors::ApiErrorResponse::InvalidHttpMethod | errors::ApiErrorResponse::InvalidCardIin diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 22623c4ca660..1168ea87bf5c 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -166,7 +166,7 @@ pub async fn signin_token_only_flow( ) -> UserResponse { let user_from_db: domain::UserFromStorage = state .global_store - .find_user_by_email(&request.email) + .find_user_by_email(&domain::UserEmail::from_pii_email(request.email)?) .await .to_not_found_response(UserErrors::InvalidCredentials)? .into(); @@ -191,7 +191,10 @@ pub async fn connect_account( request: user_api::ConnectAccountRequest, auth_id: Option, ) -> UserResponse { - let find_user = state.global_store.find_user_by_email(&request.email).await; + let find_user = state + .global_store + .find_user_by_email(&domain::UserEmail::from_pii_email(request.email.clone())?) + .await; if let Ok(found_user) = find_user { let user_from_db: domain::UserFromStorage = found_user.into(); @@ -369,7 +372,7 @@ pub async fn forgot_password( let user_from_db = state .global_store - .find_user_by_email(&user_email.into_inner()) + .find_user_by_email(&user_email) .await .map_err(|e| { if e.current_context().is_db_not_found() { @@ -453,11 +456,7 @@ pub async fn reset_password_token_only_flow( let user_from_db: domain::UserFromStorage = state .global_store - .find_user_by_email( - &email_token - .get_email() - .change_context(UserErrors::InternalServerError)?, - ) + .find_user_by_email(&email_token.get_email()?) .await .change_context(UserErrors::InternalServerError)? .into(); @@ -564,10 +563,7 @@ async fn handle_invitation( } let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?; - let invitee_user = state - .global_store - .find_user_by_email(&invitee_email.into_inner()) - .await; + let invitee_user = state.global_store.find_user_by_email(&invitee_email).await; if let Ok(invitee_user) = invitee_user { handle_existing_user_invitation( @@ -958,7 +954,7 @@ pub async fn resend_invite( let invitee_email = domain::UserEmail::from_pii_email(request.email)?; let user: domain::UserFromStorage = state .global_store - .find_user_by_email(&invitee_email.clone().into_inner()) + .find_user_by_email(&invitee_email) .await .map_err(|e| { if e.current_context().is_db_not_found() { @@ -1065,11 +1061,7 @@ pub async fn accept_invite_from_email_token_only_flow( let user_from_db: domain::UserFromStorage = state .global_store - .find_user_by_email( - &email_token - .get_email() - .change_context(UserErrors::InternalServerError)?, - ) + .find_user_by_email(&email_token.get_email()?) .await .change_context(UserErrors::InternalServerError)? .into(); @@ -1525,11 +1517,7 @@ pub async fn verify_email_token_only_flow( let user_from_email = state .global_store - .find_user_by_email( - &email_token - .get_email() - .change_context(UserErrors::InternalServerError)?, - ) + .find_user_by_email(&email_token.get_email()?) .await .change_context(UserErrors::InternalServerError)?; @@ -1572,7 +1560,7 @@ pub async fn send_verification_mail( let user_email = domain::UserEmail::try_from(req.email)?; let user = state .global_store - .find_user_by_email(&user_email.into_inner()) + .find_user_by_email(&user_email) .await .map_err(|e| { if e.current_context().is_db_not_found() { @@ -1669,11 +1657,7 @@ pub async fn user_from_email( let user_from_db: domain::UserFromStorage = state .global_store - .find_user_by_email( - &email_token - .get_email() - .change_context(UserErrors::InternalServerError)?, - ) + .find_user_by_email(&email_token.get_email()?) .await .change_context(UserErrors::InternalServerError)? .into(); @@ -2379,7 +2363,7 @@ pub async fn sso_sign( // TODO: Use config to handle not found error let user_from_db: domain::UserFromStorage = state .global_store - .find_user_by_email(&email.into_inner()) + .find_user_by_email(&email) .await .map(Into::into) .to_not_found_response(UserErrors::UserNotFound)?; diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index eaa655a07f3a..273b863afe8d 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -472,7 +472,7 @@ pub async fn delete_user_role( ) -> UserResponse<()> { let user_from_db: domain::UserFromStorage = state .global_store - .find_user_by_email(&domain::UserEmail::from_pii_email(request.email)?.into_inner()) + .find_user_by_email(&domain::UserEmail::from_pii_email(request.email)?) .await .map_err(|e| { if e.current_context().is_db_not_found() { diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 436755ea7204..ec45d9e18d70 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use common_enums::enums::MerchantStorageScheme; use common_utils::{ errors::CustomResult, - id_type, pii, + id_type, types::{keymanager::KeyManagerState, theme::ThemeLineage}, }; use diesel_models::{ @@ -2983,7 +2983,7 @@ impl UserInterface for KafkaStore { async fn find_user_by_email( &self, - user_email: &pii::Email, + user_email: &domain::UserEmail, ) -> CustomResult { self.diesel_store.find_user_by_email(user_email).await } @@ -3007,7 +3007,7 @@ impl UserInterface for KafkaStore { async fn update_user_by_email( &self, - user_email: &pii::Email, + user_email: &domain::UserEmail, user: storage::UserUpdate, ) -> CustomResult { self.diesel_store diff --git a/crates/router/src/db/user.rs b/crates/router/src/db/user.rs index 14bed15fa453..089c9fc6eb7d 100644 --- a/crates/router/src/db/user.rs +++ b/crates/router/src/db/user.rs @@ -3,11 +3,10 @@ use error_stack::report; use masking::Secret; use router_env::{instrument, tracing}; -use super::MockDb; +use super::{domain, MockDb}; use crate::{ connection, core::errors::{self, CustomResult}, - pii, services::Store, }; pub mod sample_data; @@ -22,7 +21,7 @@ pub trait UserInterface { async fn find_user_by_email( &self, - user_email: &pii::Email, + user_email: &domain::UserEmail, ) -> CustomResult; async fn find_user_by_id( @@ -38,7 +37,7 @@ pub trait UserInterface { async fn update_user_by_email( &self, - user_email: &pii::Email, + user_email: &domain::UserEmail, user: storage::UserUpdate, ) -> CustomResult; @@ -70,10 +69,10 @@ impl UserInterface for Store { #[instrument(skip_all)] async fn find_user_by_email( &self, - user_email: &pii::Email, + user_email: &domain::UserEmail, ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; - storage::User::find_by_user_email(&conn, user_email) + storage::User::find_by_user_email(&conn, user_email.get_inner()) .await .map_err(|error| report!(errors::StorageError::from(error))) } @@ -104,11 +103,11 @@ impl UserInterface for Store { #[instrument(skip_all)] async fn update_user_by_email( &self, - user_email: &pii::Email, + user_email: &domain::UserEmail, user: storage::UserUpdate, ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; - storage::User::update_by_user_email(&conn, user_email, user) + storage::User::update_by_user_email(&conn, user_email.get_inner(), user) .await .map_err(|error| report!(errors::StorageError::from(error))) } @@ -171,12 +170,12 @@ impl UserInterface for MockDb { async fn find_user_by_email( &self, - user_email: &pii::Email, + user_email: &domain::UserEmail, ) -> CustomResult { let users = self.users.lock().await; users .iter() - .find(|user| user.email.eq(user_email)) + .find(|user| user.email.eq(user_email.get_inner())) .cloned() .ok_or( errors::StorageError::ValueNotFound(format!( @@ -253,13 +252,13 @@ impl UserInterface for MockDb { async fn update_user_by_email( &self, - user_email: &pii::Email, + user_email: &domain::UserEmail, update_user: storage::UserUpdate, ) -> CustomResult { let mut users = self.users.lock().await; users .iter_mut() - .find(|user| user.email.eq(user_email)) + .find(|user| user.email.eq(user_email.get_inner())) .map(|user| { *user = match &update_user { storage::UserUpdate::VerifyUser => storage::User { diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index d50933b708da..724539ad08a0 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -2548,7 +2548,8 @@ where T: serde::de::DeserializeOwned, A: SessionStateInfo + Sync, { - let cookie_token_result = get_cookie_from_header(headers).and_then(cookies::parse_cookie); + let cookie_token_result = + get_cookie_from_header(headers).and_then(cookies::get_jwt_from_cookies); let auth_header_token_result = get_jwt_from_authorization_header(headers); let force_cookie = state.conf().user.force_cookies; @@ -3115,7 +3116,7 @@ pub fn is_ephemeral_auth( pub fn is_jwt_auth(headers: &HeaderMap) -> bool { headers.get(headers::AUTHORIZATION).is_some() || get_cookie_from_header(headers) - .and_then(cookies::parse_cookie) + .and_then(cookies::get_jwt_from_cookies) .is_ok() } @@ -3177,10 +3178,13 @@ pub fn get_jwt_from_authorization_header(headers: &HeaderMap) -> RouterResult<&s } pub fn get_cookie_from_header(headers: &HeaderMap) -> RouterResult<&str> { - headers + let cookie = headers .get(cookies::get_cookie_header()) - .and_then(|header_value| header_value.to_str().ok()) - .ok_or(errors::ApiErrorResponse::InvalidCookie.into()) + .ok_or(report!(errors::ApiErrorResponse::CookieNotFound))?; + + cookie + .to_str() + .change_context(errors::ApiErrorResponse::InvalidCookie) } pub fn strip_jwt_token(token: &str) -> RouterResult<&str> { diff --git a/crates/router/src/services/authentication/cookies.rs b/crates/router/src/services/authentication/cookies.rs index 965188408004..4a297c0f0eb9 100644 --- a/crates/router/src/services/authentication/cookies.rs +++ b/crates/router/src/services/authentication/cookies.rs @@ -49,7 +49,7 @@ pub fn remove_cookie_response() -> UserResponse<()> { Ok(ApplicationResponse::JsonWithHeaders(((), header))) } -pub fn parse_cookie(cookies: &str) -> RouterResult { +pub fn get_jwt_from_cookies(cookies: &str) -> RouterResult { Cookie::split_parse(cookies) .find_map(|cookie| { cookie @@ -57,8 +57,8 @@ pub fn parse_cookie(cookies: &str) -> RouterResult { .filter(|parsed_cookie| parsed_cookie.name() == JWT_TOKEN_COOKIE_NAME) .map(|parsed_cookie| parsed_cookie.value().to_owned()) }) - .ok_or(report!(ApiErrorResponse::InvalidCookie)) - .attach_printable("Cookie Parsing Failed") + .ok_or(report!(ApiErrorResponse::InvalidJwtToken)) + .attach_printable("Unable to find JWT token in cookies") } #[cfg(feature = "olap")] diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs index 87ff9f6abd59..8f48ef068ce9 100644 --- a/crates/router/src/services/authorization.rs +++ b/crates/router/src/services/authorization.rs @@ -40,10 +40,12 @@ where i64::try_from(token.exp).change_context(ApiErrorResponse::InternalServerError)?; let cache_ttl = token_expiry - common_utils::date_time::now_unix_timestamp(); - set_role_info_in_cache(state, &token.role_id, &role_info, cache_ttl) - .await - .map_err(|e| logger::error!("Failed to set role info in cache {e:?}")) - .ok(); + if cache_ttl > 0 { + set_role_info_in_cache(state, &token.role_id, &role_info, cache_ttl) + .await + .map_err(|e| logger::error!("Failed to set role info in cache {e:?}")) + .ok(); + } Ok(role_info) } diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index d092afdc5deb..8c966d79d80c 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -1,9 +1,6 @@ use api_models::user::dashboard_metadata::ProdIntent; use common_enums::EntityType; -use common_utils::{ - errors::{self, CustomResult}, - pii, -}; +use common_utils::{errors::CustomResult, pii}; use error_stack::ResultExt; use external_services::email::{EmailContents, EmailData, EmailError}; use masking::{ExposeInterface, Secret}; @@ -183,7 +180,7 @@ impl EmailToken { entity: Option, flow: domain::Origin, settings: &configs::Settings, - ) -> CustomResult { + ) -> UserResult { let expiration_duration = std::time::Duration::from_secs(consts::EMAIL_TOKEN_TIME_IN_SECS); let exp = jwt::generate_exp(expiration_duration)?.as_secs(); let token_payload = Self { @@ -195,8 +192,10 @@ impl EmailToken { jwt::generate_jwt(&token_payload, settings).await } - pub fn get_email(&self) -> CustomResult { + pub fn get_email(&self) -> UserResult { pii::Email::try_from(self.email.clone()) + .change_context(UserErrors::InternalServerError) + .and_then(domain::UserEmail::from_pii_email) } pub fn get_entity(&self) -> Option<&Entity> { diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 6d0d2a4ea07c..21212bf6b626 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -131,6 +131,10 @@ impl UserEmail { self.0 } + pub fn get_inner(&self) -> &pii::Email { + &self.0 + } + pub fn get_secret(self) -> Secret { (*self.0).clone() } @@ -617,7 +621,7 @@ impl NewUser { pub async fn check_if_already_exists_in_db(&self, state: SessionState) -> UserResult<()> { if state .global_store - .find_user_by_email(&self.get_email().into_inner()) + .find_user_by_email(&self.get_email()) .await .is_ok() { diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index f115a16c0622..443db741ae34 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -124,7 +124,7 @@ pub async fn get_user_from_db_by_email( ) -> CustomResult { state .global_store - .find_user_by_email(&email.into_inner()) + .find_user_by_email(&email) .await .map(UserFromStorage::from) } From 22b5a93e02156cd92adf5e24a3fd8d0e39e8d429 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 00:23:14 +0000 Subject: [PATCH 43/51] chore(version): 2024.12.02.0 --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a215cbe05578..454a55456873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,34 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.12.02.0 + +### Features + +- **connector:** + - [Adyen] Fetch email from customer email for payment request ([#6676](https://github.com/juspay/hyperswitch/pull/6676)) ([`9998c55`](https://github.com/juspay/hyperswitch/commit/9998c557c9c88496ffbee883e7fc4b76614cff50)) + - [REDSYS] add Connector Template Code ([#6659](https://github.com/juspay/hyperswitch/pull/6659)) ([`19cbcdd`](https://github.com/juspay/hyperswitch/commit/19cbcdd979bb74119d80c37c313fd0ffeb58bb8d)) +- **payments:** [Payment links] add showCardFormByDefault config for payment links ([#6663](https://github.com/juspay/hyperswitch/pull/6663)) ([`b1d1073`](https://github.com/juspay/hyperswitch/commit/b1d1073389f58c480a53a27be24aa91554520ff1)) +- **users:** Add tenant id reads in user roles ([#6661](https://github.com/juspay/hyperswitch/pull/6661)) ([`9212f77`](https://github.com/juspay/hyperswitch/commit/9212f77684b04115332d9be5c3d20bdc56b02160)) + +### Bug Fixes + +- **analytics:** Fix first_attempt filter value parsing for Payments ([#6667](https://github.com/juspay/hyperswitch/pull/6667)) ([`abcaa53`](https://github.com/juspay/hyperswitch/commit/abcaa539eccdae86c7a68fd4ce60ab9889f9fb43)) +- **openapi:** Standardise API naming scheme for V2 ([#6510](https://github.com/juspay/hyperswitch/pull/6510)) ([`96393ff`](https://github.com/juspay/hyperswitch/commit/96393ff3d6b11d4726a6cb2224236414507d9848)) +- **opensearch:** Handle empty free-text query search in global search ([#6685](https://github.com/juspay/hyperswitch/pull/6685)) ([`b1cdff0`](https://github.com/juspay/hyperswitch/commit/b1cdff0950f32b38e3ff0eeac2b726ba0f671051)) +- **router:** Populate card network in the network transaction id based MIT flow ([#6690](https://github.com/juspay/hyperswitch/pull/6690)) ([`6a20701`](https://github.com/juspay/hyperswitch/commit/6a2070172b8d845e6db36b7789defddf8ea4e1e9)) +- **users:** Mark user as verified if user logins from SSO ([#6694](https://github.com/juspay/hyperswitch/pull/6694)) ([`880ad1e`](https://github.com/juspay/hyperswitch/commit/880ad1e883fb42f73c2805287e64bc2c2dcbb9f3)) + +### Refactors + +- **currency_conversion:** Release redis lock if api call fails ([#6671](https://github.com/juspay/hyperswitch/pull/6671)) ([`ae7d16e`](https://github.com/juspay/hyperswitch/commit/ae7d16e23699c8ed95a7e2eab7539cfe20f847d0)) +- **router:** [ZSL] remove partially capture status ([#6689](https://github.com/juspay/hyperswitch/pull/6689)) ([`0572626`](https://github.com/juspay/hyperswitch/commit/05726262e6a3f6fcb18c0dbe41c18e4d6e84608b)) +- **users:** Use domain email type in user DB functions ([#6699](https://github.com/juspay/hyperswitch/pull/6699)) ([`55fe82f`](https://github.com/juspay/hyperswitch/commit/55fe82fdcd78df9608842190f1423088740d1087)) + +**Full Changelog:** [`2024.11.29.0...2024.12.02.0`](https://github.com/juspay/hyperswitch/compare/2024.11.29.0...2024.12.02.0) + +- - - + ## 2024.11.29.0 ### Features From b097d7f5a984b32421494ea033029d01d034fab8 Mon Sep 17 00:00:00 2001 From: Anurag Thakur Date: Mon, 2 Dec 2024 14:59:35 +0530 Subject: [PATCH 44/51] fix(openapi): Revert Standardise API naming scheme for V2 Dashboard Changes (#6712) --- crates/router/src/routes/app.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index bb0c547d7f1d..7a8571479f43 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -2003,7 +2003,7 @@ impl User { ) .service(web::resource("/verify_email").route(web::post().to(user::verify_email))) .service( - web::resource("/v2/verify-email").route(web::post().to(user::verify_email)), + web::resource("/v2/verify_email").route(web::post().to(user::verify_email)), ) .service( web::resource("/verify_email_request") @@ -2057,7 +2057,7 @@ impl User { .route(web::post().to(user_role::accept_invitations_v2)), ) .service( - web::resource("/pre-auth").route( + web::resource("/pre_auth").route( web::post().to(user_role::accept_invitations_pre_auth), ), ), From 15342f7fe7fb081a49fc2d2bb0f7ef5457160442 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:55:44 +0000 Subject: [PATCH 45/51] chore(version): 2024.12.02.1 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 454a55456873..5f748c6924ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.12.02.1 + +### Bug Fixes + +- **openapi:** Revert Standardise API naming scheme for V2 Dashboard Changes ([#6712](https://github.com/juspay/hyperswitch/pull/6712)) ([`b097d7f`](https://github.com/juspay/hyperswitch/commit/b097d7f5a984b32421494ea033029d01d034fab8)) + +**Full Changelog:** [`2024.12.02.0...2024.12.02.1`](https://github.com/juspay/hyperswitch/compare/2024.12.02.0...2024.12.02.1) + +- - - + ## 2024.12.02.0 ### Features From 982b26a8c2851266b6e8615699479a05ab62e519 Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:28:54 +0530 Subject: [PATCH 46/51] ci: update ntid card expiry (#6714) --- .../cypress/e2e/PaymentMethodListUtils/Stripe.js | 2 +- cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js | 4 ++-- cypress-tests/cypress/e2e/PaymentUtils/Checkout.js | 4 ++-- cypress-tests/cypress/e2e/PaymentUtils/Commons.js | 4 ++-- cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js | 8 ++++---- cypress-tests/cypress/e2e/PaymentUtils/Datatrans.js | 2 +- cypress-tests/cypress/e2e/PaymentUtils/Elavon.js | 2 +- cypress-tests/cypress/e2e/PaymentUtils/Fiservemea.js | 2 +- cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js | 2 +- cypress-tests/cypress/e2e/PaymentUtils/Nmi.js | 4 ++-- cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js | 4 ++-- cypress-tests/cypress/e2e/PaymentUtils/Paybox.js | 4 ++-- cypress-tests/cypress/e2e/PaymentUtils/Paypal.js | 4 ++-- cypress-tests/cypress/e2e/PaymentUtils/Stripe.js | 8 ++++---- cypress-tests/cypress/e2e/PaymentUtils/Trustpay.js | 2 +- cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js | 4 ++-- cypress-tests/cypress/e2e/RoutingUtils/Stripe.js | 4 ++-- cypress-tests/cypress/fixtures/create-confirm-body.json | 2 +- cypress-tests/cypress/fixtures/create-mandate-cit.json | 2 +- cypress-tests/cypress/fixtures/create-ntid-mit.json | 2 +- 20 files changed, 35 insertions(+), 35 deletions(-) diff --git a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Stripe.js b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Stripe.js index 9ba678114369..32d138fdc1f2 100644 --- a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Stripe.js +++ b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Stripe.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "737", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js b/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js index af4dee5d5377..6f383367e057 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -9,7 +9,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000000000001091", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Checkout.js b/cypress-tests/cypress/e2e/PaymentUtils/Checkout.js index 4653e3e77c2f..3307e90c3718 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Checkout.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Checkout.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -9,7 +9,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4242424242424242", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js index 0b15113c413a..a0eb936152b2 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js @@ -44,7 +44,7 @@ function normalise(input) { const successfulNo3DSCardDetails = { card_number: "4111111111111111", card_exp_month: "08", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "999", }; @@ -52,7 +52,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4111111111111111", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "999", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js b/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js index 72dc8f6479b8..c0fcfdd1b53a 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -9,7 +9,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000000000001091", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -58,7 +58,7 @@ const payment_method_data_no3ds = { card_isin: "424242", card_extended_bin: null, card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: null, payment_checks: { avs_response: { @@ -82,7 +82,7 @@ const payment_method_data_3ds = { card_isin: "400000", card_extended_bin: null, card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: null, payment_checks: null, authentication_data: null, diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Datatrans.js b/cypress-tests/cypress/e2e/PaymentUtils/Datatrans.js index a2285f2c101d..13bbf7e7d348 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Datatrans.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Datatrans.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4444090101010103", card_exp_month: "06", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Elavon.js b/cypress-tests/cypress/e2e/PaymentUtils/Elavon.js index 5b985df29dab..bb34e805a5a3 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Elavon.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Elavon.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4111111111111111", card_exp_month: "06", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Fiservemea.js b/cypress-tests/cypress/e2e/PaymentUtils/Fiservemea.js index 9c5c9e40975e..1460b474220f 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Fiservemea.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Fiservemea.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "5204740000001002", card_exp_month: "10", - card_exp_year: "24", + card_exp_year: "50", card_holder_name: "Joseph Doe", card_cvc: "002", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js b/cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js index 954a4475b7bb..7f4927d347f6 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js @@ -3,7 +3,7 @@ import { getCustomExchange } from "./Commons"; const successfulNo3DSCardDetails = { card_number: "4012000033330026", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Nmi.js b/cypress-tests/cypress/e2e/PaymentUtils/Nmi.js index f6350ac84bdd..2206c516de54 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Nmi.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Nmi.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4000000000002503", card_exp_month: "08", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "999", }; @@ -9,7 +9,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000000000002503", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "999", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js index e0db22b347d2..0b3b2820aa51 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4200000000000000", card_exp_month: "12", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "Max Mustermann", card_cvc: "123", }; @@ -9,7 +9,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000000000001091", card_exp_month: "12", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "Max Mustermann", card_cvc: "123", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js b/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js index 70bfa5c32404..a7a4afc07beb 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js @@ -9,7 +9,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000000000001091", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -657,7 +657,7 @@ export const connectorDetails = { card: { card_number: "123456", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }, diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Paypal.js b/cypress-tests/cypress/e2e/PaymentUtils/Paypal.js index 4e7f49b1375e..715bfae395cf 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Paypal.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Paypal.js @@ -3,7 +3,7 @@ import { getCustomExchange } from "./Commons"; const successfulNo3DSCardDetails = { card_number: "4012000033330026", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -11,7 +11,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4868719460707704", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js b/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js index 984fe63b2fbe..5c899e87d9c2 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js @@ -3,7 +3,7 @@ import { getCustomExchange } from "./Commons"; const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "737", }; @@ -11,7 +11,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000002500003155", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "737", }; @@ -60,7 +60,7 @@ const payment_method_data_3ds = { card_isin: "400000", card_extended_bin: null, card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: null, payment_checks: null, authentication_data: null, @@ -78,7 +78,7 @@ const payment_method_data_no3ds = { card_isin: "424242", card_extended_bin: null, card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: null, payment_checks: { cvc_check: "pass", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Trustpay.js b/cypress-tests/cypress/e2e/PaymentUtils/Trustpay.js index 36d8906fadbd..21975b4dea39 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Trustpay.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Trustpay.js @@ -3,7 +3,7 @@ import { getCustomExchange } from "./Commons"; const successfulNo3DSCardDetails = { card_number: "4200000000000000", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js b/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js index 608343f1083d..56f3caba8fac 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -9,7 +9,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000000000001091", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; diff --git a/cypress-tests/cypress/e2e/RoutingUtils/Stripe.js b/cypress-tests/cypress/e2e/RoutingUtils/Stripe.js index 9a40c07028e8..21e885ac137c 100644 --- a/cypress-tests/cypress/e2e/RoutingUtils/Stripe.js +++ b/cypress-tests/cypress/e2e/RoutingUtils/Stripe.js @@ -3,7 +3,7 @@ import {} from "./Commons"; const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "737", }; @@ -11,7 +11,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000002500003155", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "737", }; diff --git a/cypress-tests/cypress/fixtures/create-confirm-body.json b/cypress-tests/cypress/fixtures/create-confirm-body.json index 2a9e8b19ee12..df533116e56f 100644 --- a/cypress-tests/cypress/fixtures/create-confirm-body.json +++ b/cypress-tests/cypress/fixtures/create-confirm-body.json @@ -27,7 +27,7 @@ "card": { "card_number": "4242424242424242", "card_exp_month": "01", - "card_exp_year": "24", + "card_exp_year": "50", "card_holder_name": "joseph Doe", "card_cvc": "123" } diff --git a/cypress-tests/cypress/fixtures/create-mandate-cit.json b/cypress-tests/cypress/fixtures/create-mandate-cit.json index d33cb8b91c7f..d23c1f805432 100644 --- a/cypress-tests/cypress/fixtures/create-mandate-cit.json +++ b/cypress-tests/cypress/fixtures/create-mandate-cit.json @@ -18,7 +18,7 @@ "card": { "card_number": "4242424242424242", "card_exp_month": "10", - "card_exp_year": "25", + "card_exp_year": "50", "card_holder_name": "joseph Doe", "card_cvc": "123" } diff --git a/cypress-tests/cypress/fixtures/create-ntid-mit.json b/cypress-tests/cypress/fixtures/create-ntid-mit.json index 93b7e95e2a0a..61e86828338b 100644 --- a/cypress-tests/cypress/fixtures/create-ntid-mit.json +++ b/cypress-tests/cypress/fixtures/create-ntid-mit.json @@ -10,7 +10,7 @@ "data": { "card_number": "4242424242424242", "card_exp_month": "11", - "card_exp_year": "2024", + "card_exp_year": "2050", "card_holder_name": "joseph Doe", "network_transaction_id": "MCC5ZRGMI0925" } From 797a0db7733c5b387564fb1bbc106d054c8dffa6 Mon Sep 17 00:00:00 2001 From: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:29:28 +0530 Subject: [PATCH 47/51] feat(payment_methods_v2): implement a barebones version of list customer payment methods v2 (#6649) --- api-reference-v2/openapi_spec.json | 8 +- api-reference/openapi_spec.json | 4 +- crates/api_models/src/payment_methods.rs | 14 +- crates/common_utils/src/id_type/global_id.rs | 4 +- .../src/id_type/global_id/payment.rs | 4 +- .../src/id_type/global_id/payment_methods.rs | 5 +- .../src/id_type/global_id/refunds.rs | 2 +- .../src/payment_methods.rs | 7 +- crates/router/src/core/payment_methods.rs | 179 ++++++++++-------- .../router/src/core/payment_methods/cards.rs | 30 +-- crates/router/src/core/payments.rs | 2 +- crates/router/src/lib.rs | 9 +- crates/router/src/routes/app.rs | 8 +- crates/router/src/services/authentication.rs | 48 ----- .../src/types/storage/payment_method.rs | 36 ++++ 15 files changed, 173 insertions(+), 187 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 2b66d1755ff8..e3f54813a43d 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -7153,6 +7153,7 @@ "customer_id", "payment_method_type", "recurring_enabled", + "created", "requires_cvv", "is_default" ], @@ -7210,9 +7211,8 @@ "created": { "type": "string", "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the customer was created", - "example": "2023-01-18T11:04:09.922Z", - "nullable": true + "description": "A timestamp (ISO 8601 code) that determines when the payment method was created", + "example": "2023-01-18T11:04:09.922Z" }, "surcharge_details": { "allOf": [ @@ -13542,7 +13542,7 @@ "created": { "type": "string", "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the customer was created", + "description": "A timestamp (ISO 8601 code) that determines when the payment method was created", "example": "2023-01-18T11:04:09.922Z", "nullable": true }, diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index d2133a3e68e4..25ed2648cd4a 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9739,7 +9739,7 @@ "created": { "type": "string", "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the customer was created", + "description": "A timestamp (ISO 8601 code) that determines when the payment method was created", "example": "2023-01-18T11:04:09.922Z", "nullable": true }, @@ -16326,7 +16326,7 @@ "created": { "type": "string", "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the customer was created", + "description": "A timestamp (ISO 8601 code) that determines when the payment method was created", "example": "2023-01-18T11:04:09.922Z", "nullable": true }, diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 2c2aa4861c55..283a0d662d71 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -755,7 +755,7 @@ pub struct PaymentMethodResponse { #[schema(value_type = Option, example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, - /// A timestamp (ISO 8601 code) that determines when the customer was created + /// A timestamp (ISO 8601 code) that determines when the payment method was created #[schema(value_type = Option, example = "2023-01-18T11:04:09.922Z")] #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub created: Option, @@ -801,7 +801,7 @@ pub struct PaymentMethodResponse { #[schema(example = true)] pub recurring_enabled: bool, - /// A timestamp (ISO 8601 code) that determines when the customer was created + /// A timestamp (ISO 8601 code) that determines when the payment method was created #[schema(value_type = Option, example = "2023-01-18T11:04:09.922Z")] #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub created: Option, @@ -1802,10 +1802,10 @@ pub struct CustomerPaymentMethod { #[schema(example = json!({"mask": "0000"}))] pub bank: Option, - /// A timestamp (ISO 8601 code) that determines when the customer was created - #[schema(value_type = Option,example = "2023-01-18T11:04:09.922Z")] - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - pub created: Option, + /// A timestamp (ISO 8601 code) that determines when the payment method was created + #[schema(value_type = PrimitiveDateTime, example = "2023-01-18T11:04:09.922Z")] + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created: time::PrimitiveDateTime, /// Surcharge details for this saved card pub surcharge_details: Option, @@ -1890,7 +1890,7 @@ pub struct CustomerPaymentMethod { #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, - /// A timestamp (ISO 8601 code) that determines when the customer was created + /// A timestamp (ISO 8601 code) that determines when the payment method was created #[schema(value_type = Option,example = "2023-01-18T11:04:09.922Z")] #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub created: Option, diff --git a/crates/common_utils/src/id_type/global_id.rs b/crates/common_utils/src/id_type/global_id.rs index 5490dcda7bd4..a54df7585870 100644 --- a/crates/common_utils/src/id_type/global_id.rs +++ b/crates/common_utils/src/id_type/global_id.rs @@ -120,7 +120,7 @@ pub(crate) enum GlobalIdError { impl GlobalId { /// Create a new global id from entity and cell information /// The entity prefix is used to identify the entity, `cus` for customers, `pay`` for payments etc. - pub fn generate(cell_id: CellId, entity: GlobalEntity) -> Self { + pub fn generate(cell_id: &CellId, entity: GlobalEntity) -> Self { let prefix = format!("{}_{}", cell_id.get_string_repr(), entity.prefix()); let id = generate_time_ordered_id(&prefix); let alphanumeric_id = AlphaNumericId::new_unchecked(id); @@ -201,7 +201,7 @@ mod global_id_tests { let cell_id_string = "12345"; let entity = GlobalEntity::Customer; let cell_id = CellId::from_str(cell_id_string).unwrap(); - let global_id = GlobalId::generate(cell_id, entity); + let global_id = GlobalId::generate(&cell_id, entity); /// Generate a regex for globalid /// Eg - 12abc_cus_abcdefghijklmnopqrstuvwxyz1234567890 diff --git a/crates/common_utils/src/id_type/global_id/payment.rs b/crates/common_utils/src/id_type/global_id/payment.rs index 934d710604c5..5a2da3998bb6 100644 --- a/crates/common_utils/src/id_type/global_id/payment.rs +++ b/crates/common_utils/src/id_type/global_id/payment.rs @@ -20,7 +20,7 @@ impl GlobalPaymentId { } /// Generate a new GlobalPaymentId from a cell id - pub fn generate(cell_id: crate::id_type::CellId) -> Self { + pub fn generate(cell_id: &crate::id_type::CellId) -> Self { let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Payment); Self(global_id) } @@ -57,7 +57,7 @@ crate::impl_to_sql_from_sql_global_id_type!(GlobalAttemptId); impl GlobalAttemptId { /// Generate a new GlobalAttemptId from a cell id pub fn generate(cell_id: &super::CellId) -> Self { - let global_id = super::GlobalId::generate(cell_id.clone(), super::GlobalEntity::Attempt); + let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Attempt); Self(global_id) } diff --git a/crates/common_utils/src/id_type/global_id/payment_methods.rs b/crates/common_utils/src/id_type/global_id/payment_methods.rs index f6f394242cca..40bd6ec0df4a 100644 --- a/crates/common_utils/src/id_type/global_id/payment_methods.rs +++ b/crates/common_utils/src/id_type/global_id/payment_methods.rs @@ -28,10 +28,7 @@ pub enum GlobalPaymentMethodIdError { impl GlobalPaymentMethodId { /// Create a new GlobalPaymentMethodId from cell id information - pub fn generate(cell_id: &str) -> error_stack::Result { - let cell_id = CellId::from_str(cell_id) - .change_context(GlobalPaymentMethodIdError::ConstructionError) - .attach_printable("Failed to construct CellId from str")?; + pub fn generate(cell_id: &CellId) -> error_stack::Result { let global_id = GlobalId::generate(cell_id, GlobalEntity::PaymentMethod); Ok(Self(global_id)) } diff --git a/crates/common_utils/src/id_type/global_id/refunds.rs b/crates/common_utils/src/id_type/global_id/refunds.rs index 64e475161140..0aac9bf5808e 100644 --- a/crates/common_utils/src/id_type/global_id/refunds.rs +++ b/crates/common_utils/src/id_type/global_id/refunds.rs @@ -26,7 +26,7 @@ impl GlobalRefundId { } /// Generate a new GlobalRefundId from a cell id - pub fn generate(cell_id: crate::id_type::CellId) -> Self { + pub fn generate(cell_id: &crate::id_type::CellId) -> Self { let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Refund); Self(global_id) } diff --git a/crates/hyperswitch_domain_models/src/payment_methods.rs b/crates/hyperswitch_domain_models/src/payment_methods.rs index 083d6e475019..4e7d727839b2 100644 --- a/crates/hyperswitch_domain_models/src/payment_methods.rs +++ b/crates/hyperswitch_domain_models/src/payment_methods.rs @@ -12,7 +12,7 @@ use masking::{PeekInterface, Secret}; use time::PrimitiveDateTime; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -use crate::type_encryption::EncryptedJsonType; +use crate::type_encryption::OptionalEncryptableJsonType; use crate::type_encryption::{crypto_operation, AsyncLift, CryptoOperation}; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -80,9 +80,8 @@ pub struct PaymentMethod { pub last_modified: PrimitiveDateTime, pub payment_method_type: Option, pub payment_method_subtype: Option, - pub payment_method_data: Option< - Encryptable>>, - >, + pub payment_method_data: + OptionalEncryptableJsonType, pub locker_id: Option, pub last_used_at: PrimitiveDateTime, pub connector_mandate_details: Option, diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 8b8b3f33ed80..599d696ec06d 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -865,9 +865,10 @@ pub async fn create_payment_method( .attach_printable("Unable to encrypt Payment method billing address")?; // create pm - let payment_method_id = id_type::GlobalPaymentMethodId::generate("random_cell_id") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to generate GlobalPaymentMethodId")?; + let payment_method_id = + id_type::GlobalPaymentMethodId::generate(&state.conf.cell_information.id) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to generate GlobalPaymentMethodId")?; let payment_method = create_payment_method_for_intent( state, @@ -978,9 +979,10 @@ pub async fn payment_method_intent_create( // create pm entry - let payment_method_id = id_type::GlobalPaymentMethodId::generate("random_cell_id") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to generate GlobalPaymentMethodId")?; + let payment_method_id = + id_type::GlobalPaymentMethodId::generate(&state.conf.cell_information.id) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to generate GlobalPaymentMethodId")?; let payment_method = create_payment_method_for_intent( state, @@ -1324,69 +1326,82 @@ pub async fn vault_payment_method( feature = "customer_v2" ))] async fn get_pm_list_context( - state: &SessionState, - payment_method: &enums::PaymentMethod, - _key_store: &domain::MerchantKeyStore, - pm: &domain::PaymentMethod, - _parent_payment_method_token: Option, + payment_method_type: enums::PaymentMethod, + payment_method: &domain::PaymentMethod, is_payment_associated: bool, ) -> Result, error_stack::Report> { - let payment_method_retrieval_context = match payment_method { - enums::PaymentMethod::Card => { - let card_details = cards::get_card_details_with_locker_fallback(pm, state).await?; - - card_details.as_ref().map(|card| PaymentMethodListContext { - card_details: Some(card.clone()), - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: is_payment_associated.then_some( + let payment_method_data = payment_method + .payment_method_data + .clone() + .map(|payment_method_data| payment_method_data.into_inner().expose().into_inner()); + + let payment_method_retrieval_context = match payment_method_data { + Some(payment_methods::PaymentMethodsData::Card(card)) => { + Some(PaymentMethodListContext::Card { + card_details: api::CardDetailFromLocker::from(card), + token_data: is_payment_associated.then_some( storage::PaymentTokenData::permanent_card( - Some(pm.get_id().clone()), - pm.locker_id + Some(payment_method.get_id().clone()), + payment_method + .locker_id .as_ref() - .map(|id| id.get_string_repr().clone()) - .or(Some(pm.get_id().get_string_repr().to_owned())), - pm.locker_id + .map(|id| id.get_string_repr().to_owned()) + .or_else(|| Some(payment_method.get_id().get_string_repr().to_owned())), + payment_method + .locker_id .as_ref() - .map(|id| id.get_string_repr().clone()) - .unwrap_or(pm.get_id().get_string_repr().to_owned()), + .map(|id| id.get_string_repr().to_owned()) + .unwrap_or_else(|| { + payment_method.get_id().get_string_repr().to_owned() + }), ), ), }) } + Some(payment_methods::PaymentMethodsData::BankDetails(bank_details)) => { + let get_bank_account_token_data = + || -> errors::CustomResult { + let connector_details = bank_details + .connector_details + .first() + .cloned() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to obtain bank account connector details")?; + + let payment_method_subtype = payment_method + .get_payment_method_subtype() + .get_required_value("payment_method_subtype") + .attach_printable("PaymentMethodType not found")?; + + Ok(payment_methods::BankAccountTokenData { + payment_method_type: payment_method_subtype, + payment_method: payment_method_type, + connector_details, + }) + }; - enums::PaymentMethod::BankDebit => { // Retrieve the pm_auth connector details so that it can be tokenized - let bank_account_token_data = cards::get_bank_account_connector_details(pm) - .await - .unwrap_or_else(|err| { - logger::error!(error=?err); - None - }); - + let bank_account_token_data = get_bank_account_token_data() + .inspect_err(|error| logger::error!(?error)) + .ok(); bank_account_token_data.map(|data| { let token_data = storage::PaymentTokenData::AuthBankDebit(data); - PaymentMethodListContext { - card_details: None, - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: is_payment_associated.then_some(token_data), + PaymentMethodListContext::Bank { + token_data: is_payment_associated.then_some(token_data), } }) } - - _ => Some(PaymentMethodListContext { - card_details: None, - #[cfg(feature = "payouts")] - bank_transfer_details: None, - hyperswitch_token_data: is_payment_associated.then_some( - storage::PaymentTokenData::temporary_generic(generate_id( - consts::ID_LENGTH, - "token", - )), - ), - }), + Some(payment_methods::PaymentMethodsData::WalletDetails(_)) | None => { + Some(PaymentMethodListContext::TemporaryToken { + token_data: is_payment_associated.then_some( + storage::PaymentTokenData::temporary_generic(generate_id( + consts::ID_LENGTH, + "token", + )), + ), + }) + } }; Ok(payment_method_retrieval_context) @@ -1471,9 +1486,9 @@ pub async fn list_customer_payment_method( let key_manager_state = &(state).into(); let customer = db - .find_customer_by_merchant_reference_id_merchant_id( + .find_customer_by_global_id( key_manager_state, - customer_id, + customer_id.get_string_repr(), merchant_account.get_id(), &key_store, merchant_account.storage_scheme, @@ -1509,25 +1524,23 @@ pub async fn list_customer_payment_method( .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; let mut filtered_saved_payment_methods_ctx = Vec::new(); - for pm in saved_payment_methods.into_iter() { - let payment_method = pm + for payment_method in saved_payment_methods.into_iter() { + let payment_method_type = payment_method .get_payment_method_type() .get_required_value("payment_method")?; let parent_payment_method_token = is_payment_associated.then(|| generate_id(consts::ID_LENGTH, "token")); - let pm_list_context = get_pm_list_context( - state, - &payment_method, - &key_store, - &pm, - parent_payment_method_token.clone(), - is_payment_associated, - ) - .await?; + let pm_list_context = + get_pm_list_context(payment_method_type, &payment_method, is_payment_associated) + .await?; if let Some(ctx) = pm_list_context { - filtered_saved_payment_methods_ctx.push((ctx, parent_payment_method_token, pm)); + filtered_saved_payment_methods_ctx.push(( + ctx, + parent_payment_method_token, + payment_method, + )); } } @@ -1576,6 +1589,8 @@ pub async fn list_customer_payment_method( is_guest_customer: is_payment_associated.then_some(false), //to return this key only when the request is tied to a payment intent }; + /* + TODO: Implement surcharge for v2 if is_payment_associated { Box::pin(cards::perform_surcharge_ops( payments_info.as_ref().map(|pi| pi.payment_intent.clone()), @@ -1587,6 +1602,7 @@ pub async fn list_customer_payment_method( )) .await?; } + */ Ok(services::ApplicationResponse::Json(response)) } @@ -1661,15 +1677,20 @@ async fn generate_saved_pm_response( requires_cvv && !(off_session_payment_flag && pm.connector_mandate_details.is_some()) }; - let pmd = if let Some(card) = pm_list_context.card_details.as_ref() { - Some(api::PaymentMethodListData::Card(card.clone())) - } else if cfg!(feature = "payouts") { - pm_list_context - .bank_transfer_details - .clone() - .map(api::PaymentMethodListData::Bank) - } else { - None + let pmd = match &pm_list_context { + PaymentMethodListContext::Card { card_details, .. } => { + Some(api::PaymentMethodListData::Card(card_details.clone())) + } + #[cfg(feature = "payouts")] + PaymentMethodListContext::BankTransfer { + bank_transfer_details, + .. + } => Some(api::PaymentMethodListData::Bank( + bank_transfer_details.clone(), + )), + PaymentMethodListContext::Bank { .. } | PaymentMethodListContext::TemporaryToken { .. } => { + None + } }; let pma = api::CustomerPaymentMethod { @@ -1680,7 +1701,7 @@ async fn generate_saved_pm_response( payment_method_subtype: pm.get_payment_method_subtype(), payment_method_data: pmd, recurring_enabled: mca_enabled, - created: Some(pm.created_at), + created: pm.created_at, bank: bank_details, surcharge_details: None, requires_cvv: requires_cvv @@ -1952,8 +1973,8 @@ impl pm_types::SavedPMLPaymentsInfo { let token = parent_payment_method_token .as_ref() .get_required_value("parent_payment_method_token")?; - let hyperswitch_token_data = pm_list_context - .hyperswitch_token_data + let token_data = pm_list_context + .get_token_data() .get_required_value("PaymentTokenData")?; let intent_fulfillment_time = self @@ -1962,7 +1983,7 @@ impl pm_types::SavedPMLPaymentsInfo { .unwrap_or(common_utils::consts::DEFAULT_INTENT_FULFILLMENT_TIME); pm_routes::ParentPaymentMethodToken::create_key_for_token((token, pma.payment_method_type)) - .insert(intent_fulfillment_time, hyperswitch_token_data, state) + .insert(intent_fulfillment_time, token_data, state) .await?; Ok(()) diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 955cc37b0d9c..9cfe6e6ec738 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -5328,14 +5328,6 @@ pub async fn get_card_details_with_locker_fallback( }) } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -pub async fn get_card_details_with_locker_fallback( - pm: &domain::PaymentMethod, - state: &routes::SessionState, -) -> errors::RouterResult> { - todo!() -} - #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") @@ -5365,14 +5357,6 @@ pub async fn get_card_details_without_locker_fallback( }) } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -pub async fn get_card_details_without_locker_fallback( - pm: &domain::PaymentMethod, - state: &routes::SessionState, -) -> errors::RouterResult { - todo!() -} - #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") @@ -5460,13 +5444,13 @@ pub async fn get_masked_bank_details( } } +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] pub async fn get_bank_account_connector_details( pm: &domain::PaymentMethod, ) -> errors::RouterResult> { - #[cfg(all( - any(feature = "v2", feature = "v1"), - not(feature = "payment_methods_v2") - ))] let payment_method_data = pm .payment_method_data .clone() @@ -5481,12 +5465,6 @@ pub async fn get_bank_account_connector_details( ) .transpose()?; - #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] - let payment_method_data = pm - .payment_method_data - .clone() - .map(|x| x.into_inner().expose().into_inner()); - match payment_method_data { Some(pmd) => match pmd { PaymentMethodsData::Card(_) => Err(errors::ApiErrorResponse::UnprocessableEntity { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 5eb97e6eebe2..fd61fcacf5af 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1019,7 +1019,7 @@ where .to_validate_request()? .validate_request(&req, &merchant_account)?; - let payment_id = id_type::GlobalPaymentId::generate(state.conf.cell_information.id.clone()); + let payment_id = id_type::GlobalPaymentId::generate(&state.conf.cell_information.id.clone()); tracing::Span::current().record("global_payment_id", payment_id.get_string_repr()); diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 829216db1dd9..839dd472423a 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -132,7 +132,7 @@ pub fn mk_app( .service(routes::Forex::server(state.clone())); } - server_app = server_app.service(routes::Profile::server(state.clone())) + server_app = server_app.service(routes::Profile::server(state.clone())); } server_app = server_app .service(routes::Payments::server(state.clone())) @@ -141,6 +141,11 @@ pub fn mk_app( .service(routes::MerchantConnectorAccount::server(state.clone())) .service(routes::Webhooks::server(state.clone())); + #[cfg(feature = "oltp")] + { + server_app = server_app.service(routes::PaymentMethods::server(state.clone())); + } + #[cfg(feature = "v1")] { server_app = server_app @@ -157,8 +162,6 @@ pub fn mk_app( { server_app = server_app .service(routes::EphemeralKey::server(state.clone())) - .service(routes::Webhooks::server(state.clone())) - .service(routes::PaymentMethods::server(state.clone())) .service(routes::Poll::server(state.clone())) } diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 7a8571479f43..1964c1cbfa5b 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -954,6 +954,10 @@ pub struct Customers; impl Customers { pub fn server(state: AppState) -> Scope { let mut route = web::scope("/v2/customers").app_data(web::Data::new(state)); + #[cfg(all(feature = "olap", feature = "v2", feature = "customer_v2"))] + { + route = route.service(web::resource("/list").route(web::get().to(customers_list))) + } #[cfg(all(feature = "oltp", feature = "v2", feature = "customer_v2"))] { route = route @@ -965,10 +969,6 @@ impl Customers { .route(web::delete().to(customers_delete)), ) } - #[cfg(all(feature = "olap", feature = "v2", feature = "customer_v2"))] - { - route = route.service(web::resource("/list").route(web::get().to(customers_list))) - } #[cfg(all(feature = "oltp", feature = "v2", feature = "payment_methods_v2"))] { route = route.service( diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 724539ad08a0..1967eafd180c 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -1323,7 +1323,6 @@ where #[derive(Debug)] pub struct EphemeralKeyAuth; -// #[cfg(feature = "v1")] #[async_trait] impl AuthenticateAndFetch for EphemeralKeyAuth where @@ -2987,43 +2986,6 @@ pub fn get_auth_type_and_flow( Ok((Box::new(HeaderAuth(ApiKeyAuth)), api::AuthFlow::Merchant)) } -#[cfg(feature = "v1")] -pub fn check_client_secret_and_get_auth( - headers: &HeaderMap, - payload: &impl ClientSecretFetch, -) -> RouterResult<( - Box>, - api::AuthFlow, -)> -where - T: SessionStateInfo + Sync + Send, - ApiKeyAuth: AuthenticateAndFetch, - PublishableKeyAuth: AuthenticateAndFetch, -{ - let api_key = get_api_key(headers)?; - if api_key.starts_with("pk_") { - payload - .get_client_secret() - .check_value_present("client_secret") - .map_err(|_| errors::ApiErrorResponse::MissingRequiredField { - field_name: "client_secret", - })?; - return Ok(( - Box::new(HeaderAuth(PublishableKeyAuth)), - api::AuthFlow::Client, - )); - } - - if payload.get_client_secret().is_some() { - return Err(errors::ApiErrorResponse::InvalidRequestData { - message: "client_secret is not a valid parameter".to_owned(), - } - .into()); - } - Ok((Box::new(HeaderAuth(ApiKeyAuth)), api::AuthFlow::Merchant)) -} - -#[cfg(feature = "v2")] pub fn check_client_secret_and_get_auth( headers: &HeaderMap, payload: &impl ClientSecretFetch, @@ -3056,11 +3018,9 @@ where } .into()); } - Ok((Box::new(HeaderAuth(ApiKeyAuth)), api::AuthFlow::Merchant)) } -#[cfg(feature = "v1")] pub async fn get_ephemeral_or_other_auth( headers: &HeaderMap, is_merchant_flow: bool, @@ -3093,7 +3053,6 @@ where } } -#[cfg(feature = "v1")] pub fn is_ephemeral_auth( headers: &HeaderMap, ) -> RouterResult>> { @@ -3106,13 +3065,6 @@ pub fn is_ephemeral_auth( } } -#[cfg(feature = "v2")] -pub fn is_ephemeral_auth( - headers: &HeaderMap, -) -> RouterResult>> { - todo!() -} - pub fn is_jwt_auth(headers: &HeaderMap) -> bool { headers.get(headers::AUTHORIZATION).is_some() || get_cookie_from_header(headers) diff --git a/crates/router/src/types/storage/payment_method.rs b/crates/router/src/types/storage/payment_method.rs index 3214f911f567..bc5f6651b6b5 100644 --- a/crates/router/src/types/storage/payment_method.rs +++ b/crates/router/src/types/storage/payment_method.rs @@ -105,6 +105,10 @@ impl PaymentTokenData { } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PaymentMethodListContext { pub card_details: Option, @@ -113,6 +117,38 @@ pub struct PaymentMethodListContext { pub bank_transfer_details: Option, } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum PaymentMethodListContext { + Card { + card_details: api::CardDetailFromLocker, + token_data: Option, + }, + Bank { + token_data: Option, + }, + #[cfg(feature = "payouts")] + BankTransfer { + bank_transfer_details: api::BankPayout, + token_data: Option, + }, + TemporaryToken { + token_data: Option, + }, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl PaymentMethodListContext { + pub(crate) fn get_token_data(&self) -> Option { + match self { + Self::Card { token_data, .. } + | Self::Bank { token_data } + | Self::BankTransfer { token_data, .. } + | Self::TemporaryToken { token_data } => token_data.clone(), + } + } +} + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct PaymentMethodStatusTrackingData { pub payment_method_id: String, From 9a59d0a5ff682cd7a983a63e90113afc846aeac6 Mon Sep 17 00:00:00 2001 From: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:00:44 +0530 Subject: [PATCH 48/51] chore: address Rust 1.83.0 clippy lints and enable more clippy lints (#6705) --- .clippy.toml | 1 + Cargo.toml | 6 ++ api-reference-v2/openapi_spec.json | 2 +- api-reference/openapi_spec.json | 4 +- .../mod.rs => sessionized_metrics.rs} | 0 .../mod.rs => sessionized_metrics.rs} | 0 .../mod.rs => sessionized_metrics.rs} | 0 .../mod.rs => sessionized_metrics.rs} | 0 crates/api_models/src/admin.rs | 1 - crates/api_models/src/analytics.rs | 3 - crates/api_models/src/api_keys.rs | 2 +- crates/api_models/src/conditional_configs.rs | 1 - crates/api_models/src/enums.rs | 1 - .../src/{errors/mod.rs => errors.rs} | 0 crates/api_models/src/payments.rs | 11 +-- crates/api_models/src/pm_auth.rs | 1 - crates/api_models/src/routing.rs | 1 - crates/api_models/src/user/sample_data.rs | 1 - crates/cards/src/validate.rs | 6 -- crates/common_enums/src/transformers.rs | 6 +- crates/common_utils/src/crypto.rs | 13 --- crates/common_utils/src/custom_serde.rs | 1 - crates/common_utils/src/errors.rs | 1 - crates/common_utils/src/ext_traits.rs | 44 +--------- crates/common_utils/src/pii.rs | 1 - crates/common_utils/src/signals.rs | 14 ---- crates/common_utils/src/types.rs | 4 +- crates/common_utils/src/types/keymanager.rs | 4 +- crates/connector_configs/src/common_config.rs | 1 - crates/connector_configs/src/connector.rs | 1 - crates/diesel_models/src/configs.rs | 1 - crates/diesel_models/src/lib.rs | 1 - crates/diesel_models/src/organization.rs | 2 +- crates/diesel_models/src/payment_attempt.rs | 2 +- crates/diesel_models/src/reverse_lookup.rs | 1 - crates/diesel_models/src/services/logger.rs | 5 -- crates/drainer/src/services.rs | 1 - crates/euclid/src/dssa/analyzer.rs | 6 +- crates/euclid/src/dssa/graph.rs | 2 +- crates/euclid/src/dssa/types.rs | 2 +- crates/euclid/src/frontend/ast.rs | 1 - crates/euclid/src/frontend/ast/lowering.rs | 4 - crates/euclid/src/frontend/ast/parser.rs | 4 +- crates/events/src/lib.rs | 2 - crates/external_services/src/file_storage.rs | 2 - .../src/file_storage/file_system.rs | 2 - .../src/hashicorp_vault/core.rs | 2 - .../src/managers/encryption_management.rs | 2 - .../src/managers/secrets_management.rs | 2 - crates/external_services/src/no_encryption.rs | 2 - .../connectors/multisafepay/transformers.rs | 1 - .../src/connectors/nexinets/transformers.rs | 1 - .../src/connectors/novalnet/transformers.rs | 1 - .../src/connectors/razorpay/transformers.rs | 4 - .../src/connectors/tsys/transformers.rs | 1 - .../src/connectors/worldpay.rs | 2 +- crates/hyperswitch_connectors/src/utils.rs | 2 +- .../hyperswitch_constraint_graph/src/graph.rs | 16 ++-- .../src/merchant_account.rs | 1 - .../src/payment_method_data.rs | 5 -- .../src/secrets_interface.rs | 10 ++- .../src/secrets_interface/secret_state.rs | 4 - crates/kgraph_utils/src/types.rs | 2 +- crates/masking/src/abs.rs | 2 - crates/masking/src/boxed.rs | 1 - crates/masking/src/diesel.rs | 10 +-- crates/masking/src/lib.rs | 3 - crates/masking/src/maskable.rs | 18 ---- crates/masking/src/secret.rs | 4 - crates/masking/src/serde.rs | 9 +- crates/masking/src/string.rs | 1 - crates/masking/src/strong_secret.rs | 4 - crates/masking/src/vec.rs | 1 - crates/openapi/src/routes/payments.rs | 6 -- crates/openapi/src/routes/routing.rs | 2 - .../src/connector/plaid/transformers.rs | 4 - crates/redis_interface/src/commands.rs | 2 - crates/redis_interface/src/errors.rs | 2 - crates/redis_interface/src/types.rs | 2 - .../src/connector/adyen/transformers.rs | 82 +++++++++---------- .../src/connector/braintree/transformers.rs | 5 -- crates/router/src/connector/datatrans.rs | 2 +- .../src/connector/globalpay/requests.rs | 17 ---- .../src/connector/paypal/transformers.rs | 1 - .../connector/riskified/transformers/api.rs | 1 - .../connector/signifyd/transformers/api.rs | 1 - crates/router/src/connector/utils.rs | 2 +- .../router/src/connector/wellsfargopayout.rs | 4 +- crates/router/src/core/admin.rs | 12 +-- .../router/src/core/payment_methods/cards.rs | 4 +- .../router/src/core/payment_methods/utils.rs | 4 +- crates/router/src/core/payments.rs | 6 +- crates/router/src/core/payments/helpers.rs | 2 +- .../router/src/core/pm_auth/transformers.rs | 2 +- crates/router/src/core/refunds.rs | 1 - crates/router/src/core/routing/helpers.rs | 2 +- crates/router/src/core/user.rs | 4 +- crates/router/src/core/user_role/role.rs | 2 +- .../src/db/user_authentication_method.rs | 2 +- crates/router/src/routes/payment_link.rs | 1 - crates/router/src/routes/payments.rs | 4 +- crates/router/src/services/api/client.rs | 2 - .../src/services/authentication/decision.rs | 3 - crates/router/src/services/kafka.rs | 4 +- .../src/services/kafka/authentication.rs | 2 +- .../services/kafka/authentication_event.rs | 2 +- crates/router/src/services/kafka/dispute.rs | 2 +- .../src/services/kafka/dispute_event.rs | 2 +- .../router/src/services/kafka/fraud_check.rs | 2 +- .../src/services/kafka/fraud_check_event.rs | 2 +- .../src/services/kafka/payment_attempt.rs | 2 +- .../services/kafka/payment_attempt_event.rs | 2 +- .../src/services/kafka/payment_intent.rs | 2 +- .../services/kafka/payment_intent_event.rs | 2 +- crates/router/src/services/kafka/payout.rs | 2 +- crates/router/src/services/kafka/refund.rs | 2 +- .../router/src/services/kafka/refund_event.rs | 2 +- crates/router/src/services/logger.rs | 2 - .../router/src/types/storage/payment_link.rs | 2 +- crates/router/src/types/transformers.rs | 4 +- crates/router/src/utils/user_role.rs | 2 +- crates/router/src/workflows/payment_sync.rs | 1 - crates/router/tests/connectors/aci.rs | 2 - crates/router_derive/src/lib.rs | 5 +- crates/router_derive/src/macros/operation.rs | 2 +- crates/router_env/src/lib.rs | 2 - .../src/{logger/mod.rs => logger.rs} | 2 - crates/router_env/src/logger/config.rs | 2 - crates/router_env/src/logger/formatter.rs | 14 ---- crates/router_env/src/logger/setup.rs | 4 - crates/router_env/src/logger/storage.rs | 2 - crates/router_env/src/logger/types.rs | 7 -- crates/router_env/tests/logger.rs | 3 +- .../some_module.rs => test_module.rs} | 5 +- crates/router_env/tests/test_module/mod.rs | 1 - crates/scheduler/src/{db/mod.rs => db.rs} | 0 crates/storage_impl/src/mock_db.rs | 2 +- crates/storage_impl/src/redis/cache.rs | 2 +- crates/storage_impl/src/redis/kv_store.rs | 4 +- 139 files changed, 147 insertions(+), 417 deletions(-) create mode 100644 .clippy.toml rename crates/analytics/src/disputes/metrics/{sessionized_metrics/mod.rs => sessionized_metrics.rs} (100%) rename crates/analytics/src/payment_intents/metrics/{sessionized_metrics/mod.rs => sessionized_metrics.rs} (100%) rename crates/analytics/src/payments/metrics/{sessionized_metrics/mod.rs => sessionized_metrics.rs} (100%) rename crates/analytics/src/refunds/metrics/{sessionized_metrics/mod.rs => sessionized_metrics.rs} (100%) rename crates/api_models/src/{errors/mod.rs => errors.rs} (100%) delete mode 100644 crates/diesel_models/src/services/logger.rs rename crates/router_env/src/{logger/mod.rs => logger.rs} (98%) rename crates/router_env/tests/{test_module/some_module.rs => test_module.rs} (90%) delete mode 100644 crates/router_env/tests/test_module/mod.rs rename crates/scheduler/src/{db/mod.rs => db.rs} (100%) diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 000000000000..4296655a040f --- /dev/null +++ b/.clippy.toml @@ -0,0 +1 @@ +allow-dbg-in-tests = true diff --git a/Cargo.toml b/Cargo.toml index 90eb996b82b3..38d895d767af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,16 @@ unused_qualifications = "warn" [workspace.lints.clippy] as_conversions = "warn" +cloned_instead_of_copied = "warn" +dbg_macro = "warn" expect_used = "warn" +fn_params_excessive_bools = "warn" index_refutable_slice = "warn" indexing_slicing = "warn" large_futures = "warn" match_on_vec_items = "warn" missing_panics_doc = "warn" +mod_module_files = "warn" out_of_bounds_indexing = "warn" panic = "warn" panic_in_result_fn = "warn" @@ -32,10 +36,12 @@ print_stderr = "warn" print_stdout = "warn" todo = "warn" unimplemented = "warn" +unnecessary_self_imports = "warn" unreachable = "warn" unwrap_in_result = "warn" unwrap_used = "warn" use_self = "warn" +wildcard_dependencies = "warn" # Lints to allow option_map_unit_fn = "allow" diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index e3f54813a43d..d8ab5a7a20dd 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -4176,7 +4176,7 @@ }, "bank_account_bic": { "type": "string", - "description": "Bank account details for Giropay\nBank account bic code", + "description": "Bank account bic code", "nullable": true }, "bank_account_iban": { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 25ed2648cd4a..f903879c483c 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -867,7 +867,6 @@ "Payments" ], "summary": "Payments - Complete Authorize", - "description": "\n", "operationId": "Complete Authorize a Payment", "parameters": [ { @@ -918,7 +917,6 @@ "Payments" ], "summary": "Payments - Post Session Tokens", - "description": "\n", "operationId": "Create Post Session Tokens for a Payment", "requestBody": { "content": { @@ -6757,7 +6755,7 @@ }, "bank_account_bic": { "type": "string", - "description": "Bank account details for Giropay\nBank account bic code", + "description": "Bank account bic code", "nullable": true }, "bank_account_iban": { diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics/mod.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics.rs similarity index 100% rename from crates/analytics/src/disputes/metrics/sessionized_metrics/mod.rs rename to crates/analytics/src/disputes/metrics/sessionized_metrics.rs diff --git a/crates/analytics/src/payment_intents/metrics/sessionized_metrics/mod.rs b/crates/analytics/src/payment_intents/metrics/sessionized_metrics.rs similarity index 100% rename from crates/analytics/src/payment_intents/metrics/sessionized_metrics/mod.rs rename to crates/analytics/src/payment_intents/metrics/sessionized_metrics.rs diff --git a/crates/analytics/src/payments/metrics/sessionized_metrics/mod.rs b/crates/analytics/src/payments/metrics/sessionized_metrics.rs similarity index 100% rename from crates/analytics/src/payments/metrics/sessionized_metrics/mod.rs rename to crates/analytics/src/payments/metrics/sessionized_metrics.rs diff --git a/crates/analytics/src/refunds/metrics/sessionized_metrics/mod.rs b/crates/analytics/src/refunds/metrics/sessionized_metrics.rs similarity index 100% rename from crates/analytics/src/refunds/metrics/sessionized_metrics/mod.rs rename to crates/analytics/src/refunds/metrics/sessionized_metrics.rs diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 7e1465c9d929..f0a8c9994382 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -1531,7 +1531,6 @@ pub struct MerchantConnectorUpdate { #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(deny_unknown_fields)] - pub struct ConnectorWalletDetails { /// This field contains the Apple Pay certificates and credentials for iOS and Web Apple Pay flow #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/api_models/src/analytics.rs b/crates/api_models/src/analytics.rs index d25f35589b67..b6d4044c5f3c 100644 --- a/crates/api_models/src/analytics.rs +++ b/crates/api_models/src/analytics.rs @@ -277,7 +277,6 @@ pub struct PaymentIntentFilterValue { #[derive(Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] - pub struct GetRefundFilterRequest { pub time_range: TimeRange, #[serde(default)] @@ -292,7 +291,6 @@ pub struct RefundFiltersResponse { #[derive(Debug, serde::Serialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] - pub struct RefundFilterValue { pub dimension: RefundDimensions, pub values: Vec, @@ -435,7 +433,6 @@ pub struct DisputeFiltersResponse { #[derive(Debug, serde::Serialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] - pub struct DisputeFilterValue { pub dimension: DisputeDimensions, pub values: Vec, diff --git a/crates/api_models/src/api_keys.rs b/crates/api_models/src/api_keys.rs index d25cd989b0f0..3c4d566f2354 100644 --- a/crates/api_models/src/api_keys.rs +++ b/crates/api_models/src/api_keys.rs @@ -212,7 +212,7 @@ mod never { { struct NeverVisitor; - impl<'de> serde::de::Visitor<'de> for NeverVisitor { + impl serde::de::Visitor<'_> for NeverVisitor { type Value = (); fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/crates/api_models/src/conditional_configs.rs b/crates/api_models/src/conditional_configs.rs index 555e7bd955f0..3aed34e47a76 100644 --- a/crates/api_models/src/conditional_configs.rs +++ b/crates/api_models/src/conditional_configs.rs @@ -92,7 +92,6 @@ pub struct ConditionalConfigReq { pub algorithm: Option>, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] - pub struct DecisionManagerRequest { pub name: Option, pub program: Option>, diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 646ad122d7c9..c00afac64622 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -141,7 +141,6 @@ pub enum FrmConnectors { )] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] - pub enum TaxConnectors { Taxjar, } diff --git a/crates/api_models/src/errors/mod.rs b/crates/api_models/src/errors.rs similarity index 100% rename from crates/api_models/src/errors/mod.rs rename to crates/api_models/src/errors.rs diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index fb60937e9b13..f46fd450bd43 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -2457,7 +2457,6 @@ pub enum AdditionalPaymentData { } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] - pub struct KlarnaSdkPaymentMethod { pub payment_type: Option, } @@ -2504,7 +2503,6 @@ pub enum BankRedirectData { Giropay { /// The billing details for bank redirection billing_details: Option, - /// Bank account details for Giropay #[schema(value_type = Option)] /// Bank account bic code @@ -3682,7 +3680,6 @@ pub enum WalletResponseData { } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] - pub struct KlarnaSdkPaymentMethodResponse { pub payment_type: Option, } @@ -4196,7 +4193,6 @@ pub struct ReceiverDetails { #[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema, router_derive::PolymorphicSchema)] #[generate_schemas(PaymentsCreateResponseOpenApi)] - pub struct PaymentsResponse { /// Unique identifier for the payment. This ensures idempotency for multiple payments /// that have been done by a single merchant. @@ -6355,7 +6351,7 @@ mod payment_id_type { struct PaymentIdVisitor; struct OptionalPaymentIdVisitor; - impl<'de> Visitor<'de> for PaymentIdVisitor { + impl Visitor<'_> for PaymentIdVisitor { type Value = PaymentIdType; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -6433,7 +6429,7 @@ mod payment_id_type { struct PaymentIdVisitor; struct OptionalPaymentIdVisitor; - impl<'de> Visitor<'de> for PaymentIdVisitor { + impl Visitor<'_> for PaymentIdVisitor { type Value = PaymentIdType; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -6507,7 +6503,7 @@ pub mod amount { // This is defined to provide guarded deserialization of amount // which itself handles zero and non-zero values internally - impl<'de> de::Visitor<'de> for AmountVisitor { + impl de::Visitor<'_> for AmountVisitor { type Value = Amount; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -6707,7 +6703,6 @@ pub struct PaymentLinkStatusDetails { #[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)] #[serde(deny_unknown_fields)] - pub struct PaymentLinkListConstraints { /// limit on the number of objects to return pub limit: Option, diff --git a/crates/api_models/src/pm_auth.rs b/crates/api_models/src/pm_auth.rs index 8ed52907da8f..e97efcf92d3d 100644 --- a/crates/api_models/src/pm_auth.rs +++ b/crates/api_models/src/pm_auth.rs @@ -22,7 +22,6 @@ pub struct LinkTokenCreateResponse { #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] - pub struct ExchangeTokenCreateRequest { pub public_token: String, pub client_secret: Option, diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index 389af3dab7bf..dbfa770cbf1d 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -473,7 +473,6 @@ impl RoutingAlgorithmRef { } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] - pub struct RoutingDictionaryRecord { #[schema(value_type = String)] pub id: common_utils::id_type::RoutingId, diff --git a/crates/api_models/src/user/sample_data.rs b/crates/api_models/src/user/sample_data.rs index dc95de913faf..bfcbcb046c53 100644 --- a/crates/api_models/src/user/sample_data.rs +++ b/crates/api_models/src/user/sample_data.rs @@ -1,5 +1,4 @@ use common_enums::{AuthenticationType, CountryAlpha2}; -use common_utils::{self}; use time::PrimitiveDateTime; use crate::enums::Connector; diff --git a/crates/cards/src/validate.rs b/crates/cards/src/validate.rs index d3515ef02f0d..d3a27da4823b 100644 --- a/crates/cards/src/validate.rs +++ b/crates/cards/src/validate.rs @@ -10,14 +10,10 @@ use router_env::{logger, which as router_env_which, Env}; use serde::{Deserialize, Deserializer, Serialize}; use thiserror::Error; -/// /// Minimum limit of a card number will not be less than 8 by ISO standards -/// pub const MIN_CARD_NUMBER_LENGTH: usize = 8; -/// /// Maximum limit of a card number will not exceed 19 by ISO standards -/// pub const MAX_CARD_NUMBER_LENGTH: usize = 19; #[derive(Debug, Deserialize, Serialize, Error)] @@ -138,11 +134,9 @@ pub fn sanitize_card_number(card_number: &str) -> Result Result, CardNumberValidationErr> { let data = number.chars().try_fold( Vec::with_capacity(MAX_CARD_NUMBER_LENGTH), diff --git a/crates/common_enums/src/transformers.rs b/crates/common_enums/src/transformers.rs index f5f7ba3decd8..ddf55d293719 100644 --- a/crates/common_enums/src/transformers.rs +++ b/crates/common_enums/src/transformers.rs @@ -1914,7 +1914,7 @@ mod custom_serde { struct FieldVisitor; - impl<'de> Visitor<'de> for FieldVisitor { + impl Visitor<'_> for FieldVisitor { type Value = CountryAlpha2; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { @@ -1946,7 +1946,7 @@ mod custom_serde { struct FieldVisitor; - impl<'de> Visitor<'de> for FieldVisitor { + impl Visitor<'_> for FieldVisitor { type Value = CountryAlpha3; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { @@ -1978,7 +1978,7 @@ mod custom_serde { struct FieldVisitor; - impl<'de> Visitor<'de> for FieldVisitor { + impl Visitor<'_> for FieldVisitor { type Value = u32; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { diff --git a/crates/common_utils/src/crypto.rs b/crates/common_utils/src/crypto.rs index c7f1c286c595..e8c2b2d5a0bf 100644 --- a/crates/common_utils/src/crypto.rs +++ b/crates/common_utils/src/crypto.rs @@ -238,7 +238,6 @@ impl VerifySignature for HmacSha512 { } } -/// /// Blake3 #[derive(Debug)] pub struct Blake3(String); @@ -437,9 +436,7 @@ pub fn generate_cryptographically_secure_random_bytes() -> [u8; bytes } -/// /// A wrapper type to store the encrypted data for sensitive pii domain data types -/// #[derive(Debug, Clone)] pub struct Encryptable { inner: T, @@ -447,9 +444,7 @@ pub struct Encryptable { } impl> Encryptable> { - /// /// constructor function to be used by the encryptor and decryptor to generate the data type - /// pub fn new( masked_data: Secret, encrypted_data: Secret, EncryptionStrategy>, @@ -462,33 +457,25 @@ impl> Encryptable> { } impl Encryptable { - /// /// Get the inner data while consuming self - /// #[inline] pub fn into_inner(self) -> T { self.inner } - /// /// Get the reference to inner value - /// #[inline] pub fn get_inner(&self) -> &T { &self.inner } - /// /// Get the inner encrypted data while consuming self - /// #[inline] pub fn into_encrypted(self) -> Secret, EncryptionStrategy> { self.encrypted } - /// /// Deserialize inner value and return new Encryptable object - /// pub fn deserialize_inner_value( self, f: F, diff --git a/crates/common_utils/src/custom_serde.rs b/crates/common_utils/src/custom_serde.rs index 79e0c5b85e76..63ef30011f77 100644 --- a/crates/common_utils/src/custom_serde.rs +++ b/crates/common_utils/src/custom_serde.rs @@ -202,7 +202,6 @@ pub mod timestamp { } /// - pub mod json_string { use serde::de::{self, Deserialize, DeserializeOwned, Deserializer}; use serde_json; diff --git a/crates/common_utils/src/errors.rs b/crates/common_utils/src/errors.rs index 38b89cbfaf7e..e62606b45859 100644 --- a/crates/common_utils/src/errors.rs +++ b/crates/common_utils/src/errors.rs @@ -7,7 +7,6 @@ use crate::types::MinorUnit; /// error_stack::Report specific extendability /// /// Effectively, equivalent to `Result>` -/// pub type CustomResult = error_stack::Result; /// Parsing Errors diff --git a/crates/common_utils/src/ext_traits.rs b/crates/common_utils/src/ext_traits.rs index 5ad53ec85335..945056aafefd 100644 --- a/crates/common_utils/src/ext_traits.rs +++ b/crates/common_utils/src/ext_traits.rs @@ -1,7 +1,5 @@ -//! //! This module holds traits for extending functionalities for existing datatypes //! & inbuilt datatypes. -//! use error_stack::ResultExt; use masking::{ExposeInterface, PeekInterface, Secret, Strategy}; @@ -14,10 +12,8 @@ use crate::{ fp_utils::when, }; -/// /// Encode interface /// An interface for performing type conversions and serialization -/// pub trait Encode<'e> where Self: 'e + std::fmt::Debug, @@ -27,62 +23,49 @@ where /// Converting `Self` into an intermediate representation `

` /// and then performing encoding operation using the `Serialize` trait from `serde` /// Specifically to convert into json, by using `serde_json` - /// fn convert_and_encode

(&'e self) -> CustomResult where P: TryFrom<&'e Self> + Serialize, Result>::Error>: ResultExt, >::Error> as ResultExt>::Ok: Serialize; - /// /// Converting `Self` into an intermediate representation `

` /// and then performing encoding operation using the `Serialize` trait from `serde` /// Specifically, to convert into urlencoded, by using `serde_urlencoded` - /// fn convert_and_url_encode

(&'e self) -> CustomResult where P: TryFrom<&'e Self> + Serialize, Result>::Error>: ResultExt, >::Error> as ResultExt>::Ok: Serialize; - /// /// Functionality, for specifically encoding `Self` into `String` /// after serialization by using `serde::Serialize` - /// fn url_encode(&'e self) -> CustomResult where Self: Serialize; - /// /// Functionality, for specifically encoding `Self` into `String` /// after serialization by using `serde::Serialize` /// specifically, to convert into JSON `String`. - /// fn encode_to_string_of_json(&'e self) -> CustomResult where Self: Serialize; - /// /// Functionality, for specifically encoding `Self` into `String` /// after serialization by using `serde::Serialize` /// specifically, to convert into XML `String`. - /// fn encode_to_string_of_xml(&'e self) -> CustomResult where Self: Serialize; - /// /// Functionality, for specifically encoding `Self` into `serde_json::Value` /// after serialization by using `serde::Serialize` - /// fn encode_to_value(&'e self) -> CustomResult where Self: Serialize; - /// /// Functionality, for specifically encoding `Self` into `Vec` /// after serialization by using `serde::Serialize` - /// fn encode_to_vec(&'e self) -> CustomResult, errors::ParsingError> where Self: Serialize; @@ -165,13 +148,9 @@ where } } -/// /// Extending functionalities of `bytes::Bytes` -/// pub trait BytesExt { - /// /// Convert `bytes::Bytes` into type `` using `serde::Deserialize` - /// fn parse_struct<'de, T>( &'de self, type_name: &'static str, @@ -199,13 +178,9 @@ impl BytesExt for bytes::Bytes { } } -/// /// Extending functionalities of `[u8]` for performing parsing -/// pub trait ByteSliceExt { - /// /// Convert `[u8]` into type `` by using `serde::Deserialize` - /// fn parse_struct<'de, T>( &'de self, type_name: &'static str, @@ -229,13 +204,9 @@ impl ByteSliceExt for [u8] { } } -/// /// Extending functionalities of `serde_json::Value` for performing parsing -/// pub trait ValueExt { - /// /// Convert `serde_json::Value` into type `` by using `serde::Deserialize` - /// fn parse_value(self, type_name: &'static str) -> CustomResult where T: serde::de::DeserializeOwned; @@ -277,22 +248,16 @@ impl ValueExt for crypto::Encryptable { } } -/// /// Extending functionalities of `String` for performing parsing -/// pub trait StringExt { - /// /// Convert `String` into type `` (which being an `enum`) - /// fn parse_enum(self, enum_name: &'static str) -> CustomResult where T: std::str::FromStr, // Requirement for converting the `Err` variant of `FromStr` to `Report` ::Err: std::error::Error + Send + Sync + 'static; - /// /// Convert `serde_json::Value` into type `` by using `serde::Deserialize` - /// fn parse_struct<'de>( &'de self, type_name: &'static str, @@ -327,25 +292,20 @@ impl StringExt for String { } } -/// /// Extending functionalities of Wrapper types for idiomatic -/// #[cfg(feature = "async_ext")] #[cfg_attr(feature = "async_ext", async_trait::async_trait)] pub trait AsyncExt { /// Output type of the map function type WrappedSelf; - /// + /// Extending map by allowing functions which are async - /// async fn async_map(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future + Send; - /// /// Extending the `and_then` by allowing functions which are async - /// async fn async_and_then(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, @@ -469,9 +429,7 @@ where /// Extension trait for deserializing XML strings using `quick-xml` crate pub trait XmlExt { - /// /// Deserialize an XML string into the specified type ``. - /// fn parse_xml(self) -> Result where T: serde::de::DeserializeOwned; diff --git a/crates/common_utils/src/pii.rs b/crates/common_utils/src/pii.rs index 9d4b96200ba7..5fd0e8078a25 100644 --- a/crates/common_utils/src/pii.rs +++ b/crates/common_utils/src/pii.rs @@ -348,7 +348,6 @@ where } /// Strategy for masking UPI VPA's - #[derive(Debug)] pub enum UpiVpaMaskingStrategy {} diff --git a/crates/common_utils/src/signals.rs b/crates/common_utils/src/signals.rs index 5bde366bf3c5..d008aef290e8 100644 --- a/crates/common_utils/src/signals.rs +++ b/crates/common_utils/src/signals.rs @@ -6,10 +6,8 @@ use futures::StreamExt; use router_env::logger; use tokio::sync::mpsc; -/// /// This functions is meant to run in parallel to the application. /// It will send a signal to the receiver when a SIGTERM or SIGINT is received -/// #[cfg(not(target_os = "windows"))] pub async fn signal_handler(mut sig: signal_hook_tokio::Signals, sender: mpsc::Sender<()>) { if let Some(signal) = sig.next().await { @@ -34,47 +32,35 @@ pub async fn signal_handler(mut sig: signal_hook_tokio::Signals, sender: mpsc::S } } -/// /// This functions is meant to run in parallel to the application. /// It will send a signal to the receiver when a SIGTERM or SIGINT is received -/// #[cfg(target_os = "windows")] pub async fn signal_handler(_sig: DummySignal, _sender: mpsc::Sender<()>) {} -/// /// This function is used to generate a list of signals that the signal_handler should listen for -/// #[cfg(not(target_os = "windows"))] pub fn get_allowed_signals() -> Result { signal_hook_tokio::Signals::new([signal_hook::consts::SIGTERM, signal_hook::consts::SIGINT]) } -/// /// This function is used to generate a list of signals that the signal_handler should listen for -/// #[cfg(target_os = "windows")] pub fn get_allowed_signals() -> Result { Ok(DummySignal) } -/// /// Dummy Signal Handler for windows -/// #[cfg(target_os = "windows")] #[derive(Debug, Clone)] pub struct DummySignal; #[cfg(target_os = "windows")] impl DummySignal { - /// /// Dummy handler for signals in windows (empty) - /// pub fn handle(&self) -> Self { self.clone() } - /// /// Hollow implementation, for windows compatibility - /// pub fn close(self) {} } diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 2a271acb62bb..1e547a497135 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -334,7 +334,6 @@ impl AmountConvertor for FloatMajorUnitForConnector { } /// Connector required amount type - #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq)] pub struct MinorUnitForConnector; @@ -503,7 +502,6 @@ impl Sum for MinorUnit { } /// Connector specific types to send - #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)] pub struct StringMinorUnit(String); @@ -749,7 +747,7 @@ mod client_secret_type { { struct ClientSecretVisitor; - impl<'de> Visitor<'de> for ClientSecretVisitor { + impl Visitor<'_> for ClientSecretVisitor { type Value = ClientSecret; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/common_utils/src/types/keymanager.rs b/crates/common_utils/src/types/keymanager.rs index 078f1f3fcd81..09d26bd91ef8 100644 --- a/crates/common_utils/src/types/keymanager.rs +++ b/crates/common_utils/src/types/keymanager.rs @@ -393,7 +393,7 @@ impl<'de> Deserialize<'de> for DecryptedData { { struct DecryptedDataVisitor; - impl<'de> Visitor<'de> for DecryptedDataVisitor { + impl Visitor<'_> for DecryptedDataVisitor { type Value = DecryptedData; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -449,7 +449,7 @@ impl<'de> Deserialize<'de> for EncryptedData { { struct EncryptedDataVisitor; - impl<'de> Visitor<'de> for EncryptedDataVisitor { + impl Visitor<'_> for EncryptedDataVisitor { type Value = EncryptedData; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/connector_configs/src/common_config.rs b/crates/connector_configs/src/common_config.rs index 26c9d33f405a..8f6e03fb3987 100644 --- a/crates/connector_configs/src/common_config.rs +++ b/crates/connector_configs/src/common_config.rs @@ -106,7 +106,6 @@ pub struct ApiModelMetaData { #[serde_with::skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] - pub enum KlarnaEndpoint { Europe, NorthAmerica, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 0e68b04d27b4..524bfa717092 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -73,7 +73,6 @@ pub enum ApplePayTomlConfig { #[serde_with::skip_serializing_none] #[derive(Debug, Clone, serde::Serialize, Deserialize)] - pub enum KlarnaEndpoint { Europe, NorthAmerica, diff --git a/crates/diesel_models/src/configs.rs b/crates/diesel_models/src/configs.rs index 2b30aa6a9724..37381961d967 100644 --- a/crates/diesel_models/src/configs.rs +++ b/crates/diesel_models/src/configs.rs @@ -7,7 +7,6 @@ use crate::schema::configs; #[derive(Default, Clone, Debug, Insertable, Serialize, Deserialize)] #[diesel(table_name = configs)] - pub struct ConfigNew { pub key: String, pub config: String, diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs index c7a3818d5fea..7e476662ea7a 100644 --- a/crates/diesel_models/src/lib.rs +++ b/crates/diesel_models/src/lib.rs @@ -75,7 +75,6 @@ pub use self::{ /// `Option` values. /// /// [diesel-2.0-array-nullability]: https://diesel.rs/guides/migration_guide.html#2-0-0-nullability-of-array-elements - #[doc(hidden)] pub(crate) mod diesel_impl { use diesel::{ diff --git a/crates/diesel_models/src/organization.rs b/crates/diesel_models/src/organization.rs index bd4fd1192017..6a3cad24e1c6 100644 --- a/crates/diesel_models/src/organization.rs +++ b/crates/diesel_models/src/organization.rs @@ -197,8 +197,8 @@ impl From for OrganizationUpdateInternal { } } } -#[cfg(feature = "v2")] +#[cfg(feature = "v2")] impl From for OrganizationUpdateInternal { fn from(value: OrganizationUpdate) -> Self { match value { diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 7760ea76c500..6ddc26d49bdb 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -6,7 +6,7 @@ use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; -use crate::enums::{self as storage_enums}; +use crate::enums as storage_enums; #[cfg(feature = "v1")] use crate::schema::payment_attempt; #[cfg(feature = "v2")] diff --git a/crates/diesel_models/src/reverse_lookup.rs b/crates/diesel_models/src/reverse_lookup.rs index 87fca3440784..974851f966a1 100644 --- a/crates/diesel_models/src/reverse_lookup.rs +++ b/crates/diesel_models/src/reverse_lookup.rs @@ -2,7 +2,6 @@ use diesel::{Identifiable, Insertable, Queryable, Selectable}; use crate::schema::reverse_lookup; -/// /// This reverse lookup table basically looks up id's and get result_id that you want. This is /// useful for KV where you can't lookup without key #[derive( diff --git a/crates/diesel_models/src/services/logger.rs b/crates/diesel_models/src/services/logger.rs deleted file mode 100644 index 9c1b20c9d280..000000000000 --- a/crates/diesel_models/src/services/logger.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! -//! Logger of the system. -//! - -pub use crate::env::logger::*; diff --git a/crates/drainer/src/services.rs b/crates/drainer/src/services.rs index 55ffe0c4e7fa..87057ebeff2d 100644 --- a/crates/drainer/src/services.rs +++ b/crates/drainer/src/services.rs @@ -29,7 +29,6 @@ impl Store { /// /// Panics if there is a failure while obtaining the HashiCorp client using the provided configuration. /// This panic indicates a critical failure in setting up external services, and the application cannot proceed without a valid HashiCorp client. - /// pub async fn new(config: &crate::Settings, test_transaction: bool, tenant: &Tenant) -> Self { let redis_conn = crate::connection::redis_connection(config).await; Self { diff --git a/crates/euclid/src/dssa/analyzer.rs b/crates/euclid/src/dssa/analyzer.rs index a81e7be351fb..7aeb850e1bb0 100644 --- a/crates/euclid/src/dssa/analyzer.rs +++ b/crates/euclid/src/dssa/analyzer.rs @@ -87,7 +87,7 @@ pub fn analyze_exhaustive_negations( .cloned() .unwrap_or_default() .iter() - .cloned() + .copied() .cloned() .collect(), }; @@ -121,12 +121,12 @@ fn analyze_negated_assertions( value: (*val).clone(), assertion_metadata: assertion_metadata .get(*val) - .cloned() + .copied() .cloned() .unwrap_or_default(), negation_metadata: negation_metadata .get(*val) - .cloned() + .copied() .cloned() .unwrap_or_default(), }; diff --git a/crates/euclid/src/dssa/graph.rs b/crates/euclid/src/dssa/graph.rs index 30d71090f365..7ef9bb244d98 100644 --- a/crates/euclid/src/dssa/graph.rs +++ b/crates/euclid/src/dssa/graph.rs @@ -421,7 +421,7 @@ impl CgraphExt for cgraph::ConstraintGraph { for (key, negation_set) in keywise_negation { let all_metadata = keywise_metadata.remove(&key).unwrap_or_default(); - let first_metadata = all_metadata.first().cloned().cloned().unwrap_or_default(); + let first_metadata = all_metadata.first().copied().cloned().unwrap_or_default(); self.key_analysis(key.clone(), analysis_ctx, memo, cycle_map, domains) .map_err(|e| AnalysisError::assertion_from_graph_error(&first_metadata, e))?; diff --git a/crates/euclid/src/dssa/types.rs b/crates/euclid/src/dssa/types.rs index df54de2dd998..f8340c315097 100644 --- a/crates/euclid/src/dssa/types.rs +++ b/crates/euclid/src/dssa/types.rs @@ -18,7 +18,7 @@ pub enum CtxValueKind<'a> { Negation(&'a [dir::DirValue]), } -impl<'a> CtxValueKind<'a> { +impl CtxValueKind<'_> { pub fn get_assertion(&self) -> Option<&dir::DirValue> { if let Self::Assertion(val) = self { Some(val) diff --git a/crates/euclid/src/frontend/ast.rs b/crates/euclid/src/frontend/ast.rs index 5a7b88acfbc2..7c75ad000bf8 100644 --- a/crates/euclid/src/frontend/ast.rs +++ b/crates/euclid/src/frontend/ast.rs @@ -135,7 +135,6 @@ pub struct IfStatement { /// } /// } /// ``` - #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] #[aliases(RuleConnectorSelection = Rule)] diff --git a/crates/euclid/src/frontend/ast/lowering.rs b/crates/euclid/src/frontend/ast/lowering.rs index 24fc1e80428b..ec629358d5fe 100644 --- a/crates/euclid/src/frontend/ast/lowering.rs +++ b/crates/euclid/src/frontend/ast/lowering.rs @@ -25,7 +25,6 @@ use crate::{ /// This serves for the purpose were we have the DirKey as an explicit Enum type and value as one /// of the member of the same Enum. /// So particularly it lowers a predefined Enum from DirKey to an Enum of DirValue. - macro_rules! lower_enum { ($key:ident, $value:ident) => { match $value { @@ -70,7 +69,6 @@ macro_rules! lower_enum { /// This is for the cases in which there are numerical values involved and they are lowered /// accordingly on basis of the supplied key, currently payment_amount is the only key having this /// use case - macro_rules! lower_number { ($key:ident, $value:ident, $comp:ident) => { match $value { @@ -117,7 +115,6 @@ macro_rules! lower_number { /// /// This serves for the purpose were we have the DirKey as Card_bin and value as an arbitrary string /// So particularly it lowers an arbitrary value to a predefined key. - macro_rules! lower_str { ($key:ident, $value:ident $(, $validation_closure:expr)?) => { match $value { @@ -155,7 +152,6 @@ macro_rules! lower_metadata { /// by throwing required errors for comparisons that can't be performed for a certain value type /// for example /// can't have greater/less than operations on enum types - fn lower_comparison_inner( comp: ast::Comparison, ) -> Result, AnalysisErrorType> { diff --git a/crates/euclid/src/frontend/ast/parser.rs b/crates/euclid/src/frontend/ast/parser.rs index 0c586e178e69..63a0ea08b8b6 100644 --- a/crates/euclid/src/frontend/ast/parser.rs +++ b/crates/euclid/src/frontend/ast/parser.rs @@ -51,9 +51,9 @@ impl EuclidParsable for DummyOutput { )(input) } } -pub fn skip_ws<'a, F, O>(inner: F) -> impl FnMut(&'a str) -> ParseResult<&str, O> +pub fn skip_ws<'a, F, O>(inner: F) -> impl FnMut(&'a str) -> ParseResult<&'a str, O> where - F: FnMut(&'a str) -> ParseResult<&str, O> + 'a, + F: FnMut(&'a str) -> ParseResult<&'a str, O> + 'a, { sequence::preceded(pchar::multispace0, inner) } diff --git a/crates/events/src/lib.rs b/crates/events/src/lib.rs index 3d333ec54b9d..38a597e030ba 100644 --- a/crates/events/src/lib.rs +++ b/crates/events/src/lib.rs @@ -2,14 +2,12 @@ #![cfg_attr(docsrs, doc(cfg_hide(doc)))] #![warn(missing_docs)] -//! //! A generic event handler system. //! This library consists of 4 parts: //! Event Sink: A trait that defines how events are published. This could be a simple logger, a message queue, or a database. //! EventContext: A struct that holds the event sink and metadata about the event. This is used to create events. This can be used to add metadata to all events, such as the user who triggered the event. //! EventInfo: A trait that defines the metadata that is sent with the event. It works with the EventContext to add metadata to all events. //! Event: A trait that defines the event itself. This trait is used to define the data that is sent with the event and defines the event's type & identifier. -//! mod actix; diff --git a/crates/external_services/src/file_storage.rs b/crates/external_services/src/file_storage.rs index e551cfee2af0..479b7c5f386c 100644 --- a/crates/external_services/src/file_storage.rs +++ b/crates/external_services/src/file_storage.rs @@ -1,6 +1,4 @@ -//! //! Module for managing file storage operations with support for multiple storage schemes. -//! use std::{ fmt::{Display, Formatter}, diff --git a/crates/external_services/src/file_storage/file_system.rs b/crates/external_services/src/file_storage/file_system.rs index e3986abf0f86..19b3185e81b4 100644 --- a/crates/external_services/src/file_storage/file_system.rs +++ b/crates/external_services/src/file_storage/file_system.rs @@ -1,6 +1,4 @@ -//! //! Module for local file system storage operations -//! use std::{ fs::{remove_file, File}, diff --git a/crates/external_services/src/hashicorp_vault/core.rs b/crates/external_services/src/hashicorp_vault/core.rs index 15edcb6418cd..3cc03b4330b1 100644 --- a/crates/external_services/src/hashicorp_vault/core.rs +++ b/crates/external_services/src/hashicorp_vault/core.rs @@ -103,7 +103,6 @@ impl HashiCorpVault { /// # Parameters /// /// - `config`: A reference to a `HashiCorpVaultConfig` containing the configuration details. - /// pub fn new(config: &HashiCorpVaultConfig) -> error_stack::Result { VaultClient::new( VaultClientSettingsBuilder::default() @@ -129,7 +128,6 @@ impl HashiCorpVault { /// /// - `En`: The engine type that implements the `Engine` trait. /// - `I`: The type that can be constructed from the retrieved encoded data. - /// pub async fn fetch(&self, data: String) -> error_stack::Result where for<'a> En: Engine< diff --git a/crates/external_services/src/managers/encryption_management.rs b/crates/external_services/src/managers/encryption_management.rs index 678239c60b1a..a0add638235c 100644 --- a/crates/external_services/src/managers/encryption_management.rs +++ b/crates/external_services/src/managers/encryption_management.rs @@ -1,6 +1,4 @@ -//! //! Encryption management util module -//! use std::sync::Arc; diff --git a/crates/external_services/src/managers/secrets_management.rs b/crates/external_services/src/managers/secrets_management.rs index b79046b4c75f..7b27e74bf34b 100644 --- a/crates/external_services/src/managers/secrets_management.rs +++ b/crates/external_services/src/managers/secrets_management.rs @@ -1,6 +1,4 @@ -//! //! Secrets management util module -//! use common_utils::errors::CustomResult; #[cfg(feature = "hashicorp-vault")] diff --git a/crates/external_services/src/no_encryption.rs b/crates/external_services/src/no_encryption.rs index 17c29618f89a..6f805fc7a10f 100644 --- a/crates/external_services/src/no_encryption.rs +++ b/crates/external_services/src/no_encryption.rs @@ -1,6 +1,4 @@ -//! //! No encryption functionalities -//! pub mod core; diff --git a/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs index 180db6268646..479abf13b13a 100644 --- a/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs @@ -916,7 +916,6 @@ pub struct Data { } #[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] - pub struct MultisafepayPaymentDetails { pub account_holder_name: Option>, pub account_id: Option>, diff --git a/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs index 6297b97ee98a..8d6dc42926ba 100644 --- a/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs @@ -122,7 +122,6 @@ pub struct NexinetsBankRedirects { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] - pub struct NexinetsAsyncDetails { pub success_url: Option, pub cancel_url: Option, diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index b2bf76944c0f..18783c64a016 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -80,7 +80,6 @@ pub struct NovalnetPaymentsRequestCustomer { no_nc: i64, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] - pub struct NovalnetCard { card_number: CardNumber, card_expiry_month: Secret, diff --git a/crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs index 3b2164dfc978..c799cf2594ee 100644 --- a/crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs @@ -58,7 +58,6 @@ pub struct RazorpayPaymentsRequest { #[derive(Default, Debug, Serialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] - pub struct SecondFactor { txn_id: String, id: String, @@ -981,7 +980,6 @@ pub struct RazorpaySyncResponse { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] - pub enum PsyncStatus { Charged, Pending, @@ -1054,7 +1052,6 @@ pub struct Refund { #[derive(Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] - pub enum RefundStatus { Success, Failure, @@ -1221,7 +1218,6 @@ impl From for enums::RefundStatus { #[derive(Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] - pub struct RefundResponse { txn_id: Option, refund: RefundRes, diff --git a/crates/hyperswitch_connectors/src/connectors/tsys/transformers.rs b/crates/hyperswitch_connectors/src/connectors/tsys/transformers.rs index cd9fe63e4a79..27b9daaa5798 100644 --- a/crates/hyperswitch_connectors/src/connectors/tsys/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/tsys/transformers.rs @@ -457,7 +457,6 @@ pub struct TsysCaptureRequest { #[derive(Debug, Serialize)] #[serde(rename_all = "PascalCase")] - pub struct TsysPaymentsCaptureRequest { capture: TsysCaptureRequest, } diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay.rs b/crates/hyperswitch_connectors/src/connectors/worldpay.rs index be8f9bca3527..266fcc7d659b 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay.rs @@ -54,8 +54,8 @@ use response::{ WP_CORRELATION_ID, }; use ring::hmac; -use transformers::{self as worldpay}; +use self::transformers as worldpay; use crate::{ constants::headers, types::ResponseRouterData, diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index a7fe1cc4cd95..181f46a20523 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -119,7 +119,7 @@ where pub(crate) fn missing_field_err( message: &'static str, -) -> Box error_stack::Report + '_> { +) -> Box error_stack::Report + 'static> { Box::new(move || { errors::ConnectorError::MissingRequiredField { field_name: message, diff --git a/crates/hyperswitch_constraint_graph/src/graph.rs b/crates/hyperswitch_constraint_graph/src/graph.rs index 7c435fee2909..8d56406d2a37 100644 --- a/crates/hyperswitch_constraint_graph/src/graph.rs +++ b/crates/hyperswitch_constraint_graph/src/graph.rs @@ -120,7 +120,7 @@ where already_memo .clone() .map_err(|err| GraphError::AnalysisError(Arc::downgrade(&err))) - } else if let Some((initial_strength, initial_relation)) = cycle_map.get(&node_id).cloned() + } else if let Some((initial_strength, initial_relation)) = cycle_map.get(&node_id).copied() { let strength_relation = Strength::get_resolved_strength(initial_strength, strength); let relation_resolve = @@ -197,7 +197,7 @@ where if !unsatisfied.is_empty() { let err = Arc::new(AnalysisTrace::AllAggregation { unsatisfied, - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), }); @@ -264,7 +264,7 @@ where } else { let err = Arc::new(AnalysisTrace::AnyAggregation { unsatisfied: unsatisfied.clone(), - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), }); @@ -305,7 +305,7 @@ where expected: expected.iter().cloned().collect(), found: None, relation: vald.relation, - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), }); @@ -324,7 +324,7 @@ where expected: expected.iter().cloned().collect(), found: Some(ctx_value.clone()), relation: vald.relation, - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), }); @@ -402,7 +402,7 @@ where let err = Arc::new(AnalysisTrace::Value { value: val.clone(), relation: vald.relation, - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), predecessors: Some(error::ValueTracePredecessor::Mandatory(Box::new( trace.get_analysis_trace()?, @@ -437,7 +437,7 @@ where let err = Arc::new(AnalysisTrace::Value { value: val.clone(), relation: vald.relation, - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), predecessors: Some(error::ValueTracePredecessor::OneOf(errors.clone())), }); @@ -469,7 +469,7 @@ where value: val.clone(), relation, predecessors: None, - info: self.node_info.get(node_id).cloned().flatten(), + info: self.node_info.get(node_id).copied().flatten(), metadata: self.node_metadata.get(node_id).cloned().flatten(), }); memo.insert((node_id, relation, strength), Err(Arc::clone(&err))); diff --git a/crates/hyperswitch_domain_models/src/merchant_account.rs b/crates/hyperswitch_domain_models/src/merchant_account.rs index 1e3302dab4a3..a6d2114f0fe9 100644 --- a/crates/hyperswitch_domain_models/src/merchant_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_account.rs @@ -255,7 +255,6 @@ pub enum MerchantAccountUpdate { } #[cfg(feature = "v1")] - impl From for MerchantAccountUpdateInternal { fn from(merchant_account_update: MerchantAccountUpdate) -> Self { let now = date_time::now(); diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index ea994946ca86..820959a59637 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -161,7 +161,6 @@ pub enum PayLaterData { } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - pub enum WalletData { AliPayQr(Box), AliPayRedirect(AliPayRedirection), @@ -234,7 +233,6 @@ pub struct SamsungPayTokenData { } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - pub struct GooglePayWalletData { /// The type of payment method pub pm_type: String, @@ -304,7 +302,6 @@ pub struct MobilePayRedirection {} pub struct MbWayRedirection {} #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - pub struct GooglePayPaymentMethodInfo { /// The name of the card network pub card_network: String, @@ -361,7 +358,6 @@ pub struct ApplepayPaymentMethod { } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] - pub enum RealTimePaymentData { DuitNow {}, Fps {}, @@ -370,7 +366,6 @@ pub enum RealTimePaymentData { } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] - pub enum BankRedirectData { BancontactCard { card_number: Option, diff --git a/crates/hyperswitch_interfaces/src/secrets_interface.rs b/crates/hyperswitch_interfaces/src/secrets_interface.rs index 761981bedda1..6944729191a7 100644 --- a/crates/hyperswitch_interfaces/src/secrets_interface.rs +++ b/crates/hyperswitch_interfaces/src/secrets_interface.rs @@ -10,11 +10,13 @@ use masking::Secret; /// Trait defining the interface for managing application secrets #[async_trait::async_trait] pub trait SecretManagementInterface: Send + Sync { + /* /// Given an input, encrypt/store the secret - // async fn store_secret( - // &self, - // input: Secret, - // ) -> CustomResult; + async fn store_secret( + &self, + input: Secret, + ) -> CustomResult; + */ /// Given an input, decrypt/retrieve the secret async fn get_secret( diff --git a/crates/hyperswitch_interfaces/src/secrets_interface/secret_state.rs b/crates/hyperswitch_interfaces/src/secrets_interface/secret_state.rs index d1da6a8c8b68..9573dfa12cb8 100644 --- a/crates/hyperswitch_interfaces/src/secrets_interface/secret_state.rs +++ b/crates/hyperswitch_interfaces/src/secrets_interface/secret_state.rs @@ -26,17 +26,13 @@ pub struct SecretStateContainer { } impl SecretStateContainer { - /// /// Get the inner data while consuming self - /// #[inline] pub fn into_inner(self) -> T { self.inner } - /// /// Get the reference to inner value - /// #[inline] pub fn get_inner(&self) -> &T { &self.inner diff --git a/crates/kgraph_utils/src/types.rs b/crates/kgraph_utils/src/types.rs index 26f27896e0a5..9ff55b68ab70 100644 --- a/crates/kgraph_utils/src/types.rs +++ b/crates/kgraph_utils/src/types.rs @@ -2,8 +2,8 @@ use std::collections::{HashMap, HashSet}; use api_models::enums as api_enums; use serde::Deserialize; -#[derive(Debug, Deserialize, Clone, Default)] +#[derive(Debug, Deserialize, Clone, Default)] pub struct CountryCurrencyFilter { pub connector_configs: HashMap, pub default_configs: Option, diff --git a/crates/masking/src/abs.rs b/crates/masking/src/abs.rs index f50725d9f234..6501f89c9db0 100644 --- a/crates/masking/src/abs.rs +++ b/crates/masking/src/abs.rs @@ -1,6 +1,4 @@ -//! //! Abstract data types. -//! use crate::Secret; diff --git a/crates/masking/src/boxed.rs b/crates/masking/src/boxed.rs index 67397ec3c07f..42dda0873a13 100644 --- a/crates/masking/src/boxed.rs +++ b/crates/masking/src/boxed.rs @@ -1,4 +1,3 @@ -//! //! `Box` types containing secrets //! //! There is not alias type by design. diff --git a/crates/masking/src/diesel.rs b/crates/masking/src/diesel.rs index ea60a861c9d5..148e50ed823a 100644 --- a/crates/masking/src/diesel.rs +++ b/crates/masking/src/diesel.rs @@ -1,6 +1,4 @@ -//! //! Diesel-related. -//! use diesel::{ backend::Backend, @@ -13,7 +11,7 @@ use diesel::{ use crate::{Secret, Strategy, StrongSecret, ZeroizableSecret}; -impl<'expr, S, I, T> AsExpression for &'expr Secret +impl AsExpression for &Secret where T: sql_types::SingleValue, I: Strategy, @@ -24,7 +22,7 @@ where } } -impl<'expr2, 'expr, S, I, T> AsExpression for &'expr2 &'expr Secret +impl AsExpression for &&Secret where T: sql_types::SingleValue, I: Strategy, @@ -81,7 +79,7 @@ where } } -impl<'expr, S, I, T> AsExpression for &'expr StrongSecret +impl AsExpression for &StrongSecret where T: sql_types::SingleValue, S: ZeroizableSecret, @@ -93,7 +91,7 @@ where } } -impl<'expr2, 'expr, S, I, T> AsExpression for &'expr2 &'expr StrongSecret +impl AsExpression for &&StrongSecret where T: sql_types::SingleValue, S: ZeroizableSecret, diff --git a/crates/masking/src/lib.rs b/crates/masking/src/lib.rs index d376e935bd6e..49e9cbf54f79 100644 --- a/crates/masking/src/lib.rs +++ b/crates/masking/src/lib.rs @@ -2,10 +2,8 @@ #![cfg_attr(docsrs, doc(cfg_hide(doc)))] #![warn(missing_docs)] -//! //! Personal Identifiable Information protection. Wrapper types and traits for secret management which help ensure they aren't accidentally copied, logged, or otherwise exposed (as much as possible), and also ensure secrets are securely wiped from memory when dropped. //! Secret-keeping library inspired by secrecy. -//! #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR" ), "/", "README.md"))] @@ -49,7 +47,6 @@ pub use crate::serde::{ /// This module should be included with asterisk. /// /// `use masking::prelude::*;` -/// pub mod prelude { pub use super::{ExposeInterface, ExposeOptionInterface, PeekInterface}; } diff --git a/crates/masking/src/maskable.rs b/crates/masking/src/maskable.rs index 7969c9ab8e42..e957e89b3512 100644 --- a/crates/masking/src/maskable.rs +++ b/crates/masking/src/maskable.rs @@ -1,13 +1,9 @@ -//! //! This module contains Masking objects and traits -//! use crate::{ExposeInterface, Secret}; -/// /// An Enum that allows us to optionally mask data, based on which enum variant that data is stored /// in. -/// #[derive(Clone, Eq, PartialEq)] pub enum Maskable { /// Variant which masks the data by wrapping in a Secret @@ -35,9 +31,7 @@ impl std::hash::Hash for Maskable Maskable { - /// /// Get the inner data while consuming self - /// pub fn into_inner(self) -> T { match self { Self::Masked(inner_secret) => inner_secret.expose(), @@ -45,49 +39,37 @@ impl Maskable { } } - /// /// Create a new Masked data - /// pub fn new_masked(item: Secret) -> Self { Self::Masked(item) } - /// /// Create a new non-masked data - /// pub fn new_normal(item: T) -> Self { Self::Normal(item) } - /// /// Checks whether the data is masked. /// Returns `true` if the data is wrapped in the `Masked` variant, /// returns `false` otherwise. - /// pub fn is_masked(&self) -> bool { matches!(self, Self::Masked(_)) } - /// /// Checks whether the data is normal (not masked). /// Returns `true` if the data is wrapped in the `Normal` variant, /// returns `false` otherwise. - /// pub fn is_normal(&self) -> bool { matches!(self, Self::Normal(_)) } } /// Trait for providing a method on custom types for constructing `Maskable` - pub trait Mask { /// The type returned by the `into_masked()` method. Must implement `PartialEq`, `Eq` and `Clone` - type Output: Eq + Clone + PartialEq; - /// /// Construct a `Maskable` instance that wraps `Self::Output` by consuming `self` - /// fn into_masked(self) -> Maskable; } diff --git a/crates/masking/src/secret.rs b/crates/masking/src/secret.rs index a813829d63de..0bd28c3af926 100644 --- a/crates/masking/src/secret.rs +++ b/crates/masking/src/secret.rs @@ -1,12 +1,9 @@ -//! //! Structure describing secret. -//! use std::{fmt, marker::PhantomData}; use crate::{strategy::Strategy, PeekInterface, StrongSecret}; -/// /// Secret thing. /// /// To get access to value use method `expose()` of trait [`crate::ExposeInterface`]. @@ -39,7 +36,6 @@ use crate::{strategy::Strategy, PeekInterface, StrongSecret}; /// /// assert_eq!("hello", &format!("{:?}", my_secret)); /// ``` -/// pub struct Secret where MaskingStrategy: Strategy, diff --git a/crates/masking/src/serde.rs b/crates/masking/src/serde.rs index 0c5782ae04d5..48514df8c6c0 100644 --- a/crates/masking/src/serde.rs +++ b/crates/masking/src/serde.rs @@ -1,6 +1,4 @@ -//! //! Serde-related. -//! pub use erased_serde::Serialize as ErasedSerialize; pub use serde::{de, Deserialize, Serialize, Serializer}; @@ -17,7 +15,6 @@ use crate::{Secret, Strategy, StrongSecret, ZeroizableSecret}; /// /// This is done deliberately to prevent accidental exfiltration of secrets /// via `serde` serialization. -/// #[cfg_attr(docsrs, cfg(feature = "serde"))] pub trait SerializableSecret: Serialize {} @@ -87,7 +84,6 @@ where } } -/// /// Masked serialization. /// /// the default behaviour for secrets is to serialize in exposed format since the common use cases @@ -99,7 +95,6 @@ pub fn masked_serialize(value: &T) -> Result ErasedMaskSerialize for T { } } -impl<'a> Serialize for dyn ErasedMaskSerialize + 'a { +impl Serialize for dyn ErasedMaskSerialize + '_ { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -127,7 +122,7 @@ impl<'a> Serialize for dyn ErasedMaskSerialize + 'a { } } -impl<'a> Serialize for dyn ErasedMaskSerialize + 'a + Send { +impl Serialize for dyn ErasedMaskSerialize + '_ + Send { fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/crates/masking/src/string.rs b/crates/masking/src/string.rs index 2638fbd282ef..be6e90a21555 100644 --- a/crates/masking/src/string.rs +++ b/crates/masking/src/string.rs @@ -1,4 +1,3 @@ -//! //! Secret strings //! //! There is not alias type by design. diff --git a/crates/masking/src/strong_secret.rs b/crates/masking/src/strong_secret.rs index 51c0f2cb3fef..300b5463d250 100644 --- a/crates/masking/src/strong_secret.rs +++ b/crates/masking/src/strong_secret.rs @@ -1,6 +1,4 @@ -//! //! Structure describing secret. -//! use std::{fmt, marker::PhantomData}; @@ -9,11 +7,9 @@ use zeroize::{self, Zeroize as ZeroizableSecret}; use crate::{strategy::Strategy, PeekInterface}; -/// /// Secret thing. /// /// To get access to value use method `expose()` of trait [`crate::ExposeInterface`]. -/// pub struct StrongSecret { /// Inner secret value pub(crate) inner_secret: Secret, diff --git a/crates/masking/src/vec.rs b/crates/masking/src/vec.rs index 1f8c1c671e0b..2a077be99439 100644 --- a/crates/masking/src/vec.rs +++ b/crates/masking/src/vec.rs @@ -1,4 +1,3 @@ -//! //! Secret `Vec` types //! //! There is not alias type by design. diff --git a/crates/openapi/src/routes/payments.rs b/crates/openapi/src/routes/payments.rs index 2ffd4f4cdc79..4c9d069a4586 100644 --- a/crates/openapi/src/routes/payments.rs +++ b/crates/openapi/src/routes/payments.rs @@ -549,8 +549,6 @@ pub fn payments_incremental_authorization() {} pub fn payments_external_authentication() {} /// Payments - Complete Authorize -/// -/// #[utoipa::path( post, path = "/{payment_id}/complete_authorize", @@ -569,8 +567,6 @@ pub fn payments_external_authentication() {} pub fn payments_complete_authorize() {} /// Dynamic Tax Calculation -/// -/// #[utoipa::path( post, path = "/payments/{payment_id}/calculate_tax", @@ -587,8 +583,6 @@ pub fn payments_complete_authorize() {} pub fn payments_dynamic_tax_calculation() {} /// Payments - Post Session Tokens -/// -/// #[utoipa::path( post, path = "/payments/{payment_id}/post_session_tokens", diff --git a/crates/openapi/src/routes/routing.rs b/crates/openapi/src/routes/routing.rs index b144fd046ad4..9b8f56aeb81a 100644 --- a/crates/openapi/src/routes/routing.rs +++ b/crates/openapi/src/routes/routing.rs @@ -68,7 +68,6 @@ pub async fn routing_link_config() {} /// Routing - Retrieve /// /// Retrieve a routing algorithm - #[utoipa::path( get, path = "/routing/{routing_algorithm_id}", @@ -91,7 +90,6 @@ pub async fn routing_retrieve_config() {} /// Routing - Retrieve /// /// Retrieve a routing algorithm with its algorithm id - #[utoipa::path( get, path = "/v2/routing-algorithm/{id}", diff --git a/crates/pm_auth/src/connector/plaid/transformers.rs b/crates/pm_auth/src/connector/plaid/transformers.rs index a91aa57a1e43..a10ff0d60e5c 100644 --- a/crates/pm_auth/src/connector/plaid/transformers.rs +++ b/crates/pm_auth/src/connector/plaid/transformers.rs @@ -20,7 +20,6 @@ pub struct PlaidLinkTokenRequest { } #[derive(Debug, Serialize, Eq, PartialEq)] - pub struct User { pub client_user_id: id_type::CustomerId, } @@ -94,7 +93,6 @@ pub struct PlaidExchangeTokenRequest { } #[derive(Debug, Deserialize, Eq, PartialEq)] - pub struct PlaidExchangeTokenResponse { pub access_token: String, } @@ -236,7 +234,6 @@ pub struct PlaidBankAccountCredentialsRequest { } #[derive(Debug, Deserialize, PartialEq)] - pub struct PlaidBankAccountCredentialsResponse { pub accounts: Vec, pub numbers: PlaidBankAccountCredentialsNumbers, @@ -251,7 +248,6 @@ pub struct BankAccountCredentialsOptions { } #[derive(Debug, Deserialize, PartialEq)] - pub struct PlaidBankAccountCredentialsAccounts { pub account_id: String, pub name: String, diff --git a/crates/redis_interface/src/commands.rs b/crates/redis_interface/src/commands.rs index 9fd07a473d4a..746b424abc88 100644 --- a/crates/redis_interface/src/commands.rs +++ b/crates/redis_interface/src/commands.rs @@ -3,8 +3,6 @@ //! The folder provides generic functions for providing serialization //! and deserialization while calling redis. //! It also includes instruments to provide tracing. -//! -//! use std::fmt::Debug; diff --git a/crates/redis_interface/src/errors.rs b/crates/redis_interface/src/errors.rs index 0e2a4b8d63b0..9e0fb4639a5d 100644 --- a/crates/redis_interface/src/errors.rs +++ b/crates/redis_interface/src/errors.rs @@ -1,6 +1,4 @@ -//! //! Errors specific to this custom redis interface -//! #[derive(Debug, thiserror::Error, PartialEq)] pub enum RedisError { diff --git a/crates/redis_interface/src/types.rs b/crates/redis_interface/src/types.rs index f40b81af68e2..92429f617d13 100644 --- a/crates/redis_interface/src/types.rs +++ b/crates/redis_interface/src/types.rs @@ -1,7 +1,5 @@ -//! //! Data types and type conversions //! from `fred`'s internal data-types to custom data-types -//! use common_utils::errors::CustomResult; use fred::types::RedisValue as FredRedisValue; diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 12cef3aa9f78..7daee6e73d74 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -1448,7 +1448,7 @@ pub enum OpenBankingUKIssuer { pub struct AdyenTestBankNames<'a>(&'a str); -impl<'a> TryFrom<&common_enums::BankNames> for AdyenTestBankNames<'a> { +impl TryFrom<&common_enums::BankNames> for AdyenTestBankNames<'_> { type Error = Error; fn try_from(bank: &common_enums::BankNames) -> Result { Ok(match bank { @@ -1501,7 +1501,7 @@ impl<'a> TryFrom<&common_enums::BankNames> for AdyenTestBankNames<'a> { pub struct AdyenBankNames<'a>(&'a str); -impl<'a> TryFrom<&common_enums::BankNames> for AdyenBankNames<'a> { +impl TryFrom<&common_enums::BankNames> for AdyenBankNames<'_> { type Error = Error; fn try_from(bank: &common_enums::BankNames) -> Result { Ok(match bank { @@ -1550,9 +1550,7 @@ impl TryFrom<&types::ConnectorAuthType> for AdyenAuthType { } } -impl<'a> TryFrom<&AdyenRouterData<&types::PaymentsAuthorizeRouterData>> - for AdyenPaymentRequest<'a> -{ +impl TryFrom<&AdyenRouterData<&types::PaymentsAuthorizeRouterData>> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( item: &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, @@ -1612,7 +1610,7 @@ impl<'a> TryFrom<&AdyenRouterData<&types::PaymentsAuthorizeRouterData>> } } -impl<'a> TryFrom<&types::PaymentsPreProcessingRouterData> for AdyenBalanceRequest<'a> { +impl TryFrom<&types::PaymentsPreProcessingRouterData> for AdyenBalanceRequest<'_> { type Error = Error; fn try_from(item: &types::PaymentsPreProcessingRouterData) -> Result { let payment_method = match &item.request.payment_method_data { @@ -1863,8 +1861,8 @@ fn build_shopper_reference( }) } -impl<'a> TryFrom<(&domain::BankDebitData, &types::PaymentsAuthorizeRouterData)> - for AdyenPaymentMethod<'a> +impl TryFrom<(&domain::BankDebitData, &types::PaymentsAuthorizeRouterData)> + for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -1911,8 +1909,8 @@ impl<'a> TryFrom<(&domain::BankDebitData, &types::PaymentsAuthorizeRouterData)> } } -impl<'a> TryFrom<(&domain::VoucherData, &types::PaymentsAuthorizeRouterData)> - for AdyenPaymentMethod<'a> +impl TryFrom<(&domain::VoucherData, &types::PaymentsAuthorizeRouterData)> + for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -1958,7 +1956,7 @@ impl<'a> TryFrom<(&domain::VoucherData, &types::PaymentsAuthorizeRouterData)> } } -impl<'a> TryFrom<&domain::GiftCardData> for AdyenPaymentMethod<'a> { +impl TryFrom<&domain::GiftCardData> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from(gift_card_data: &domain::GiftCardData) -> Result { match gift_card_data { @@ -1992,7 +1990,7 @@ fn get_adyen_card_network(card_network: common_enums::CardNetwork) -> Option TryFrom<(&domain::Card, Option>)> for AdyenPaymentMethod<'a> { +impl TryFrom<(&domain::Card, Option>)> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( (card, card_holder_name): (&domain::Card, Option>), @@ -2068,8 +2066,8 @@ impl TryFrom<&utils::CardIssuer> for CardBrand { } } -impl<'a> TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> - for AdyenPaymentMethod<'a> +impl TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> + for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -2206,7 +2204,7 @@ pub fn check_required_field<'a, T>( }) } -impl<'a> +impl TryFrom<( &domain::PayLaterData, &Option, @@ -2216,7 +2214,7 @@ impl<'a> &Option>, &Option

, &Option
, - )> for AdyenPaymentMethod<'a> + )> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -2329,12 +2327,12 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &domain::BankRedirectData, Option, &types::PaymentsAuthorizeRouterData, - )> for AdyenPaymentMethod<'a> + )> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -2492,11 +2490,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &domain::BankTransferData, &types::PaymentsAuthorizeRouterData, - )> for AdyenPaymentMethod<'a> + )> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -2564,7 +2562,7 @@ fn get_optional_shopper_email(item: &types::PaymentsAuthorizeRouterData) -> Opti } } -impl<'a> TryFrom<&domain::payments::CardRedirectData> for AdyenPaymentMethod<'a> { +impl TryFrom<&domain::payments::CardRedirectData> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( card_redirect_data: &domain::payments::CardRedirectData, @@ -2583,11 +2581,11 @@ impl<'a> TryFrom<&domain::payments::CardRedirectData> for AdyenPaymentMethod<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, payments::MandateReferenceId, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -2719,11 +2717,11 @@ impl<'a> }) } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::Card, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -2784,11 +2782,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::BankDebitData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; @@ -2842,11 +2840,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::VoucherData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; @@ -2903,11 +2901,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::BankTransferData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; @@ -2956,11 +2954,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::GiftCardData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; @@ -3009,11 +3007,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::BankRedirectData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -3117,11 +3115,11 @@ fn get_shopper_email( } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::WalletData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -3192,11 +3190,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::PayLaterData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -3268,11 +3266,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::payments::CardRedirectData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -5098,7 +5096,6 @@ impl TryFrom<&types::DefendDisputeRouterData> for AdyenDefendDisputeRequest { #[derive(Default, Debug, Serialize)] #[serde(rename_all = "camelCase")] - pub struct Evidence { defense_documents: Vec, merchant_account_code: Secret, @@ -5107,7 +5104,6 @@ pub struct Evidence { #[derive(Default, Debug, Serialize)] #[serde(rename_all = "camelCase")] - pub struct DefenseDocuments { content: Secret, content_type: Option, diff --git a/crates/router/src/connector/braintree/transformers.rs b/crates/router/src/connector/braintree/transformers.rs index 24c6118e44a8..00624ce0f9b1 100644 --- a/crates/router/src/connector/braintree/transformers.rs +++ b/crates/router/src/connector/braintree/transformers.rs @@ -67,13 +67,11 @@ pub struct GenericVariableInput { #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] - pub struct BraintreeApiErrorResponse { pub api_error_response: ApiErrorResponse, } #[derive(Debug, Deserialize, Serialize)] - pub struct ErrorsObject { pub errors: Vec, @@ -82,7 +80,6 @@ pub struct ErrorsObject { #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] - pub struct TransactionError { pub errors: Vec, pub credit_card: Option, @@ -122,7 +119,6 @@ pub struct BraintreeErrorResponse { #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[serde(untagged)] - pub enum ErrorResponses { BraintreeApiErrorResponse(Box), BraintreeErrorResponse(Box), @@ -907,7 +903,6 @@ pub struct BraintreeRefundResponseData { } #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct RefundResponse { pub data: BraintreeRefundResponseData, } diff --git a/crates/router/src/connector/datatrans.rs b/crates/router/src/connector/datatrans.rs index 9c7772c5b3ef..e6f0ebd09850 100644 --- a/crates/router/src/connector/datatrans.rs +++ b/crates/router/src/connector/datatrans.rs @@ -4,8 +4,8 @@ use base64::Engine; use common_utils::types::{AmountConvertor, MinorUnit, MinorUnitForConnector}; use error_stack::{report, ResultExt}; use masking::PeekInterface; -use transformers::{self as datatrans}; +use self::transformers as datatrans; use super::{utils as connector_utils, utils::RefundsRequestData}; use crate::{ configs::settings, diff --git a/crates/router/src/connector/globalpay/requests.rs b/crates/router/src/connector/globalpay/requests.rs index 9ccfcd90be38..223be54d3fd5 100644 --- a/crates/router/src/connector/globalpay/requests.rs +++ b/crates/router/src/connector/globalpay/requests.rs @@ -99,7 +99,6 @@ pub struct GlobalpayRefreshTokenRequest { } #[derive(Debug, Serialize, Deserialize)] - pub struct CurrencyConversion { /// A unique identifier generated by Global Payments to identify the currency conversion. It /// can be used to reference a currency conversion when processing a sale or a refund @@ -108,7 +107,6 @@ pub struct CurrencyConversion { } #[derive(Debug, Serialize, Deserialize)] - pub struct Device { pub capabilities: Option, @@ -128,7 +126,6 @@ pub struct Device { } #[derive(Debug, Serialize, Deserialize)] - pub struct Capabilities { pub authorization_modes: Option>, /// The number of lines that can be used to display information on the device. @@ -146,7 +143,6 @@ pub struct Capabilities { } #[derive(Debug, Serialize, Deserialize)] - pub struct Lodging { /// A reference that identifies the booking reference for a lodging stay. pub booking_reference: Option, @@ -163,7 +159,6 @@ pub struct Lodging { } #[derive(Debug, Serialize, Deserialize)] - pub struct LodgingChargeItem { pub payment_method_program_codes: Option>, /// A reference that identifies the charge item, such as a lodging folio number. @@ -195,7 +190,6 @@ pub struct Notifications { } #[derive(Debug, Serialize, Deserialize)] - pub struct Order { /// Merchant defined field common to all transactions that are part of the same order. pub reference: Option, @@ -239,7 +233,6 @@ pub struct PaymentMethod { } #[derive(Debug, Serialize, Deserialize)] - pub struct Apm { /// A string used to identify the payment method provider being used to execute this /// transaction. @@ -247,9 +240,7 @@ pub struct Apm { } /// Information outlining the degree of authentication executed related to a transaction. - #[derive(Debug, Serialize, Deserialize)] - pub struct Authentication { /// Information outlining the degree of 3D Secure authentication executed. pub three_ds: Option, @@ -259,9 +250,7 @@ pub struct Authentication { } /// Information outlining the degree of 3D Secure authentication executed. - #[derive(Debug, Serialize, Deserialize)] - pub struct ThreeDs { /// The reference created by the 3DSecure Directory Server to identify the specific /// authentication attempt. @@ -284,7 +273,6 @@ pub struct ThreeDs { } #[derive(Debug, Serialize, Deserialize)] - pub struct BankTransfer { /// The number or reference for the payer's bank account. pub account_number: Option>, @@ -298,7 +286,6 @@ pub struct BankTransfer { pub sec_code: Option, } #[derive(Debug, Serialize, Deserialize)] - pub struct Bank { pub address: Option
, /// The local identifier code for the bank. @@ -401,7 +388,6 @@ pub enum AuthorizationMode { /// requested amount. /// pub example: PARTIAL /// - /// /// Describes whether the device can process partial authorizations. Partial, } @@ -428,7 +414,6 @@ pub enum CaptureMode { /// Describes whether the transaction was processed in a face to face(CP) scenario or a /// Customer Not Present (CNP) scenario. - #[derive(Debug, Default, Serialize, Deserialize)] pub enum Channel { #[default] @@ -609,7 +594,6 @@ pub enum NumberType { } /// Indicates how the transaction was authorized by the merchant. - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum SecCode { @@ -766,7 +750,6 @@ pub enum CardStorageMode { /// Indicates the transaction processing model being executed when using stored /// credentials. - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum Model { diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 7f2d0d0e608e..c350f151d721 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -2970,7 +2970,6 @@ pub struct PaypalSourceVerificationRequest { } #[derive(Deserialize, Serialize, Debug)] - pub struct PaypalSourceVerificationResponse { pub verification_status: PaypalSourceVerificationStatus, } diff --git a/crates/router/src/connector/riskified/transformers/api.rs b/crates/router/src/connector/riskified/transformers/api.rs index c2da0193f996..2d519b3caa8d 100644 --- a/crates/router/src/connector/riskified/transformers/api.rs +++ b/crates/router/src/connector/riskified/transformers/api.rs @@ -657,7 +657,6 @@ fn get_fulfillment_status( } #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct RiskifiedWebhookBody { pub id: String, pub status: RiskifiedWebhookStatus, diff --git a/crates/router/src/connector/signifyd/transformers/api.rs b/crates/router/src/connector/signifyd/transformers/api.rs index 348ca3d099a6..028c0e825189 100644 --- a/crates/router/src/connector/signifyd/transformers/api.rs +++ b/crates/router/src/connector/signifyd/transformers/api.rs @@ -702,7 +702,6 @@ impl TryFrom<&frm_types::FrmRecordReturnRouterData> for SignifydPaymentsRecordRe #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] - pub struct SignifydWebhookBody { pub order_id: String, pub review_disposition: ReviewDisposition, diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index ed4b97e2a996..b5901f56d3b6 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -54,7 +54,7 @@ use crate::{ pub fn missing_field_err( message: &'static str, -) -> Box error_stack::Report + '_> { +) -> Box error_stack::Report + 'static> { Box::new(move || { errors::ConnectorError::MissingRequiredField { field_name: message, diff --git a/crates/router/src/connector/wellsfargopayout.rs b/crates/router/src/connector/wellsfargopayout.rs index 51b303494bbb..fe8f5dda25fe 100644 --- a/crates/router/src/connector/wellsfargopayout.rs +++ b/crates/router/src/connector/wellsfargopayout.rs @@ -3,9 +3,9 @@ pub mod transformers; use common_utils::types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}; use error_stack::{report, ResultExt}; use masking::ExposeInterface; -use transformers as wellsfargopayout; -use super::utils::{self as connector_utils}; +use self::transformers as wellsfargopayout; +use super::utils as connector_utils; use crate::{ configs::settings, core::errors::{self, CustomResult}, diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 6d4dde53082c..055c428b1fbc 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1199,7 +1199,7 @@ struct ConnectorAuthTypeAndMetadataValidation<'a> { connector_meta_data: &'a Option, } -impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { +impl ConnectorAuthTypeAndMetadataValidation<'_> { pub fn validate_auth_and_metadata_type( &self, ) -> Result<(), error_stack::Report> { @@ -1576,7 +1576,7 @@ struct ConnectorAuthTypeValidation<'a> { auth_type: &'a types::ConnectorAuthType, } -impl<'a> ConnectorAuthTypeValidation<'a> { +impl ConnectorAuthTypeValidation<'_> { fn validate_connector_auth_type( &self, ) -> Result<(), error_stack::Report> { @@ -1666,7 +1666,7 @@ struct ConnectorStatusAndDisabledValidation<'a> { current_status: &'a api_enums::ConnectorStatus, } -impl<'a> ConnectorStatusAndDisabledValidation<'a> { +impl ConnectorStatusAndDisabledValidation<'_> { fn validate_status_and_disabled( &self, ) -> RouterResult<(api_enums::ConnectorStatus, Option)> { @@ -1709,7 +1709,7 @@ struct PaymentMethodsEnabled<'a> { payment_methods_enabled: &'a Option>, } -impl<'a> PaymentMethodsEnabled<'a> { +impl PaymentMethodsEnabled<'_> { fn get_payment_methods_enabled(&self) -> RouterResult>> { let mut vec = Vec::new(); let payment_methods_enabled = match self.payment_methods_enabled.clone() { @@ -1735,7 +1735,7 @@ struct ConnectorMetadata<'a> { connector_metadata: &'a Option, } -impl<'a> ConnectorMetadata<'a> { +impl ConnectorMetadata<'_> { fn validate_apple_pay_certificates_in_mca_metadata(&self) -> RouterResult<()> { self.connector_metadata .clone() @@ -1826,7 +1826,7 @@ struct ConnectorTypeAndConnectorName<'a> { connector_name: &'a api_enums::Connector, } -impl<'a> ConnectorTypeAndConnectorName<'a> { +impl ConnectorTypeAndConnectorName<'_> { fn get_routable_connector(&self) -> RouterResult> { let mut routable_connector = api_enums::RoutableConnectors::from_str(&self.connector_name.to_string()).ok(); diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 9cfe6e6ec738..ad612ed6f3be 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3149,7 +3149,7 @@ pub fn get_banks( .iter() .skip(1) .fold(first_element.to_owned(), |acc, hs| { - acc.intersection(hs).cloned().collect() + acc.intersection(hs).copied().collect() }); } @@ -3617,7 +3617,7 @@ pub async fn list_payment_methods( .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Invalid PaymentRoutingInfo format found in payment attempt")? - .unwrap_or_else(|| storage::PaymentRoutingInfo { + .unwrap_or(storage::PaymentRoutingInfo { algorithm: None, pre_routing_results: None, }); diff --git a/crates/router/src/core/payment_methods/utils.rs b/crates/router/src/core/payment_methods/utils.rs index 1afdaaab0622..604e8c706262 100644 --- a/crates/router/src/core/payment_methods/utils.rs +++ b/crates/router/src/core/payment_methods/utils.rs @@ -737,7 +737,7 @@ fn compile_accepted_currency_for_mca( let config_currency: Vec = Vec::from_iter(config_currencies) .into_iter() - .cloned() + .copied() .collect(); let dir_currencies: Vec = config_currency @@ -767,7 +767,7 @@ fn compile_accepted_currency_for_mca( let config_currency: Vec = Vec::from_iter(config_currencies) .into_iter() - .cloned() + .copied() .collect(); let dir_currencies: Vec = config_currency diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index fd61fcacf5af..684293151ed5 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4312,7 +4312,7 @@ pub fn if_not_create_change_operation<'a, Op, F>( status: storage_enums::IntentStatus, confirm: Option, current: &'a Op, -) -> BoxedOperation<'_, F, api::PaymentsRequest, PaymentData> +) -> BoxedOperation<'a, F, api::PaymentsRequest, PaymentData> where F: Send + Clone, Op: Operation> + Send + Sync, @@ -4336,7 +4336,7 @@ where pub fn is_confirm<'a, F: Clone + Send, R, Op>( operation: &'a Op, confirm: Option, -) -> BoxedOperation<'_, F, R, PaymentData> +) -> BoxedOperation<'a, F, R, PaymentData> where PaymentConfirm: Operation>, &'a PaymentConfirm: Operation>, @@ -5044,7 +5044,7 @@ where .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Invalid straight through algorithm format found in payment attempt")? - .unwrap_or_else(|| storage::PaymentRoutingInfo { + .unwrap_or(storage::PaymentRoutingInfo { algorithm: None, pre_routing_results: None, }), diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 219a4d90519b..e7de03804d12 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -19,7 +19,7 @@ use common_utils::{ MinorUnit, }, }; -use diesel_models::enums::{self}; +use diesel_models::enums; // TODO : Evaluate all the helper functions () use error_stack::{report, ResultExt}; use futures::future::Either; diff --git a/crates/router/src/core/pm_auth/transformers.rs b/crates/router/src/core/pm_auth/transformers.rs index d516cab62fe7..b8a855f5332f 100644 --- a/crates/router/src/core/pm_auth/transformers.rs +++ b/crates/router/src/core/pm_auth/transformers.rs @@ -1,4 +1,4 @@ -use pm_auth::types::{self as pm_auth_types}; +use pm_auth::types as pm_auth_types; use crate::{core::errors, types, types::transformers::ForeignTryFrom}; diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index c706d8854af5..ebc4c926599a 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -914,7 +914,6 @@ pub async fn validate_and_create_refund( /// If payment-id is provided, lists all the refunds associated with that particular payment-id /// If payment-id is not provided, lists the refunds associated with that particular merchant - to the limit specified,if no limits given, it is 10 by default - #[instrument(skip_all)] #[cfg(feature = "olap")] pub async fn refund_list( diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 264328796c8b..c22a5021a264 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -356,7 +356,7 @@ impl MerchantConnectorAccounts { } #[cfg(feature = "v2")] -impl<'h> RoutingAlgorithmHelpers<'h> { +impl RoutingAlgorithmHelpers<'_> { fn connector_choice( &self, choice: &routing_types::RoutableConnectorChoice, diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 1168ea87bf5c..039e891e4225 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -216,12 +216,12 @@ pub async fn connect_account( .await; logger::info!(?send_email_result); - return Ok(ApplicationResponse::Json( + Ok(ApplicationResponse::Json( user_api::ConnectAccountResponse { is_email_sent: send_email_result.is_ok(), user_id: user_from_db.get_user_id().to_string(), }, - )); + )) } else if find_user .as_ref() .map_err(|e| e.current_context().is_db_not_found()) diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs index 0250415d4fda..c01d585feb76 100644 --- a/crates/router/src/core/user_role/role.rs +++ b/crates/router/src/core/user_role/role.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use api_models::user_role::role::{self as role_api}; +use api_models::user_role::role as role_api; use common_enums::{EntityType, ParentGroup, PermissionGroup, RoleScope}; use common_utils::generate_id_with_default_len; use diesel_models::role::{RoleNew, RoleUpdate}; diff --git a/crates/router/src/db/user_authentication_method.rs b/crates/router/src/db/user_authentication_method.rs index bcc2313d5dbd..a02e7bdb11ff 100644 --- a/crates/router/src/db/user_authentication_method.rs +++ b/crates/router/src/db/user_authentication_method.rs @@ -1,4 +1,4 @@ -use diesel_models::user_authentication_method::{self as storage}; +use diesel_models::user_authentication_method as storage; use error_stack::report; use router_env::{instrument, tracing}; diff --git a/crates/router/src/routes/payment_link.rs b/crates/router/src/routes/payment_link.rs index 783566335bc8..71f10fe73e91 100644 --- a/crates/router/src/routes/payment_link.rs +++ b/crates/router/src/routes/payment_link.rs @@ -26,7 +26,6 @@ use crate::{ security(("api_key" = []), ("publishable_key" = [])) )] #[instrument(skip(state, req), fields(flow = ?Flow::PaymentLinkRetrieve))] - pub async fn payment_link_retrieve( state: web::Data, req: actix_web::HttpRequest, diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 93f50f3ed9a2..27a5462c05ea 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -1977,7 +1977,7 @@ impl GetLockingInput for payment_types::PaymentsCaptureRequest { struct FPaymentsApproveRequest<'a>(&'a payment_types::PaymentsApproveRequest); #[cfg(feature = "oltp")] -impl<'a> GetLockingInput for FPaymentsApproveRequest<'a> { +impl GetLockingInput for FPaymentsApproveRequest<'_> { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where F: types::FlowMetric, @@ -1997,7 +1997,7 @@ impl<'a> GetLockingInput for FPaymentsApproveRequest<'a> { struct FPaymentsRejectRequest<'a>(&'a payment_types::PaymentsRejectRequest); #[cfg(feature = "oltp")] -impl<'a> GetLockingInput for FPaymentsRejectRequest<'a> { +impl GetLockingInput for FPaymentsRejectRequest<'_> { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where F: types::FlowMetric, diff --git a/crates/router/src/services/api/client.rs b/crates/router/src/services/api/client.rs index e1f113633a82..9a496c18d9a6 100644 --- a/crates/router/src/services/api/client.rs +++ b/crates/router/src/services/api/client.rs @@ -403,9 +403,7 @@ impl ApiClient for ProxyClient { fn add_flow_name(&mut self, _flow_name: String) {} } -/// /// Api client for testing sending request -/// #[derive(Clone)] pub struct MockApiClient; diff --git a/crates/router/src/services/authentication/decision.rs b/crates/router/src/services/authentication/decision.rs index c31a4696bc90..4ff9f8401baa 100644 --- a/crates/router/src/services/authentication/decision.rs +++ b/crates/router/src/services/authentication/decision.rs @@ -179,10 +179,7 @@ pub async fn revoke_api_key( call_decision_service(state, decision_config, rule, RULE_DELETE_METHOD).await } -/// -/// /// Safety: i64::MAX < u64::MAX -/// #[allow(clippy::as_conversions)] pub fn convert_expiry(expiry: time::PrimitiveDateTime) -> u64 { let now = common_utils::date_time::now(); diff --git a/crates/router/src/services/kafka.rs b/crates/router/src/services/kafka.rs index 51ae9456ecc9..91968510a6b6 100644 --- a/crates/router/src/services/kafka.rs +++ b/crates/router/src/services/kafka.rs @@ -94,7 +94,7 @@ impl<'a, T: KafkaMessage> KafkaEvent<'a, T> { } } -impl<'a, T: KafkaMessage> KafkaMessage for KafkaEvent<'a, T> { +impl KafkaMessage for KafkaEvent<'_, T> { fn key(&self) -> String { self.event.key() } @@ -130,7 +130,7 @@ impl<'a, T: KafkaMessage> KafkaConsolidatedEvent<'a, T> { } } -impl<'a, T: KafkaMessage> KafkaMessage for KafkaConsolidatedEvent<'a, T> { +impl KafkaMessage for KafkaConsolidatedEvent<'_, T> { fn key(&self) -> String { self.log.event.key() } diff --git a/crates/router/src/services/kafka/authentication.rs b/crates/router/src/services/kafka/authentication.rs index 67e488d6c8fb..5cf2d066ee2e 100644 --- a/crates/router/src/services/kafka/authentication.rs +++ b/crates/router/src/services/kafka/authentication.rs @@ -86,7 +86,7 @@ impl<'a> KafkaAuthentication<'a> { } } -impl<'a> super::KafkaMessage for KafkaAuthentication<'a> { +impl super::KafkaMessage for KafkaAuthentication<'_> { fn key(&self) -> String { format!( "{}_{}", diff --git a/crates/router/src/services/kafka/authentication_event.rs b/crates/router/src/services/kafka/authentication_event.rs index 7c8b77a38486..4169ff7b42a2 100644 --- a/crates/router/src/services/kafka/authentication_event.rs +++ b/crates/router/src/services/kafka/authentication_event.rs @@ -87,7 +87,7 @@ impl<'a> KafkaAuthenticationEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaAuthenticationEvent<'a> { +impl super::KafkaMessage for KafkaAuthenticationEvent<'_> { fn key(&self) -> String { format!( "{}_{}", diff --git a/crates/router/src/services/kafka/dispute.rs b/crates/router/src/services/kafka/dispute.rs index cc3a538851ef..4414af494f2f 100644 --- a/crates/router/src/services/kafka/dispute.rs +++ b/crates/router/src/services/kafka/dispute.rs @@ -71,7 +71,7 @@ impl<'a> KafkaDispute<'a> { } } -impl<'a> super::KafkaMessage for KafkaDispute<'a> { +impl super::KafkaMessage for KafkaDispute<'_> { fn key(&self) -> String { format!( "{}_{}_{}", diff --git a/crates/router/src/services/kafka/dispute_event.rs b/crates/router/src/services/kafka/dispute_event.rs index 64d91e8acaa6..92327c044d74 100644 --- a/crates/router/src/services/kafka/dispute_event.rs +++ b/crates/router/src/services/kafka/dispute_event.rs @@ -72,7 +72,7 @@ impl<'a> KafkaDisputeEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaDisputeEvent<'a> { +impl super::KafkaMessage for KafkaDisputeEvent<'_> { fn key(&self) -> String { format!( "{}_{}_{}", diff --git a/crates/router/src/services/kafka/fraud_check.rs b/crates/router/src/services/kafka/fraud_check.rs index 26fa9e6b4e26..3010f85753b9 100644 --- a/crates/router/src/services/kafka/fraud_check.rs +++ b/crates/router/src/services/kafka/fraud_check.rs @@ -53,7 +53,7 @@ impl<'a> KafkaFraudCheck<'a> { } } -impl<'a> super::KafkaMessage for KafkaFraudCheck<'a> { +impl super::KafkaMessage for KafkaFraudCheck<'_> { fn key(&self) -> String { format!( "{}_{}_{}_{}", diff --git a/crates/router/src/services/kafka/fraud_check_event.rs b/crates/router/src/services/kafka/fraud_check_event.rs index f35748cb1c8c..4fd711744188 100644 --- a/crates/router/src/services/kafka/fraud_check_event.rs +++ b/crates/router/src/services/kafka/fraud_check_event.rs @@ -52,7 +52,7 @@ impl<'a> KafkaFraudCheckEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaFraudCheckEvent<'a> { +impl super::KafkaMessage for KafkaFraudCheckEvent<'_> { fn key(&self) -> String { format!( "{}_{}_{}_{}", diff --git a/crates/router/src/services/kafka/payment_attempt.rs b/crates/router/src/services/kafka/payment_attempt.rs index e585b21d4b2b..adfff7450bc0 100644 --- a/crates/router/src/services/kafka/payment_attempt.rs +++ b/crates/router/src/services/kafka/payment_attempt.rs @@ -180,7 +180,7 @@ impl<'a> KafkaPaymentAttempt<'a> { } } -impl<'a> super::KafkaMessage for KafkaPaymentAttempt<'a> { +impl super::KafkaMessage for KafkaPaymentAttempt<'_> { fn key(&self) -> String { format!( "{}_{}_{}", diff --git a/crates/router/src/services/kafka/payment_attempt_event.rs b/crates/router/src/services/kafka/payment_attempt_event.rs index cbce46ad9c75..177b211ae897 100644 --- a/crates/router/src/services/kafka/payment_attempt_event.rs +++ b/crates/router/src/services/kafka/payment_attempt_event.rs @@ -181,7 +181,7 @@ impl<'a> KafkaPaymentAttemptEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaPaymentAttemptEvent<'a> { +impl super::KafkaMessage for KafkaPaymentAttemptEvent<'_> { fn key(&self) -> String { format!( "{}_{}_{}", diff --git a/crates/router/src/services/kafka/payment_intent.rs b/crates/router/src/services/kafka/payment_intent.rs index 82e33de8c609..378bddf193ba 100644 --- a/crates/router/src/services/kafka/payment_intent.rs +++ b/crates/router/src/services/kafka/payment_intent.rs @@ -180,7 +180,7 @@ impl KafkaPaymentIntent<'_> { } } -impl<'a> super::KafkaMessage for KafkaPaymentIntent<'a> { +impl super::KafkaMessage for KafkaPaymentIntent<'_> { fn key(&self) -> String { format!( "{}_{}", diff --git a/crates/router/src/services/kafka/payment_intent_event.rs b/crates/router/src/services/kafka/payment_intent_event.rs index 5657846ca4c7..7509f7116206 100644 --- a/crates/router/src/services/kafka/payment_intent_event.rs +++ b/crates/router/src/services/kafka/payment_intent_event.rs @@ -182,7 +182,7 @@ impl<'a> KafkaPaymentIntentEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaPaymentIntentEvent<'a> { +impl super::KafkaMessage for KafkaPaymentIntentEvent<'_> { fn key(&self) -> String { format!( "{}_{}", diff --git a/crates/router/src/services/kafka/payout.rs b/crates/router/src/services/kafka/payout.rs index 7a6254bd527d..babf7c1c5682 100644 --- a/crates/router/src/services/kafka/payout.rs +++ b/crates/router/src/services/kafka/payout.rs @@ -77,7 +77,7 @@ impl<'a> KafkaPayout<'a> { } } -impl<'a> super::KafkaMessage for KafkaPayout<'a> { +impl super::KafkaMessage for KafkaPayout<'_> { fn key(&self) -> String { format!( "{}_{}", diff --git a/crates/router/src/services/kafka/refund.rs b/crates/router/src/services/kafka/refund.rs index 7eff6f881baf..a7e37930cf2a 100644 --- a/crates/router/src/services/kafka/refund.rs +++ b/crates/router/src/services/kafka/refund.rs @@ -66,7 +66,7 @@ impl<'a> KafkaRefund<'a> { } } -impl<'a> super::KafkaMessage for KafkaRefund<'a> { +impl super::KafkaMessage for KafkaRefund<'_> { fn key(&self) -> String { format!( "{}_{}_{}_{}", diff --git a/crates/router/src/services/kafka/refund_event.rs b/crates/router/src/services/kafka/refund_event.rs index 8f9f77878a10..74278944b041 100644 --- a/crates/router/src/services/kafka/refund_event.rs +++ b/crates/router/src/services/kafka/refund_event.rs @@ -67,7 +67,7 @@ impl<'a> KafkaRefundEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaRefundEvent<'a> { +impl super::KafkaMessage for KafkaRefundEvent<'_> { fn key(&self) -> String { format!( "{}_{}_{}_{}", diff --git a/crates/router/src/services/logger.rs b/crates/router/src/services/logger.rs index 7dfd7abb8441..e88d4072f906 100644 --- a/crates/router/src/services/logger.rs +++ b/crates/router/src/services/logger.rs @@ -1,5 +1,3 @@ -//! //! Logger of the system. -//! pub use crate::logger::*; diff --git a/crates/router/src/types/storage/payment_link.rs b/crates/router/src/types/storage/payment_link.rs index 9a251d760bd6..ae9aa4173266 100644 --- a/crates/router/src/types/storage/payment_link.rs +++ b/crates/router/src/types/storage/payment_link.rs @@ -11,8 +11,8 @@ use crate::{ core::errors::{self, CustomResult}, logger, }; -#[async_trait::async_trait] +#[async_trait::async_trait] pub trait PaymentLinkDbExt: Sized { async fn filter_by_constraints( conn: &PgPooledConn, diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 4ae026689579..210ae715202d 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -701,7 +701,7 @@ impl ForeignFrom for api_types::Config { } } -impl<'a> ForeignFrom<&'a api_types::ConfigUpdate> for storage::ConfigUpdate { +impl ForeignFrom<&api_types::ConfigUpdate> for storage::ConfigUpdate { fn foreign_from(config: &api_types::ConfigUpdate) -> Self { Self::Update { config: Some(config.value.clone()), @@ -709,7 +709,7 @@ impl<'a> ForeignFrom<&'a api_types::ConfigUpdate> for storage::ConfigUpdate { } } -impl<'a> From<&'a domain::Address> for api_types::Address { +impl From<&domain::Address> for api_types::Address { fn from(address: &domain::Address) -> Self { // If all the fields of address are none, then pass the address as None let address_details = if address.city.is_none() diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index 0bd0e81149f5..8acab4364911 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -25,7 +25,7 @@ pub fn validate_role_groups(groups: &[PermissionGroup]) -> UserResult<()> { .attach_printable("Role groups cannot be empty"); } - let unique_groups: HashSet<_> = groups.iter().cloned().collect(); + let unique_groups: HashSet<_> = groups.iter().copied().collect(); if unique_groups.contains(&PermissionGroup::OrganizationManage) { return Err(report!(UserErrors::InvalidRoleOperation)) diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index b4fcf69241b8..04fa16484922 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -243,7 +243,6 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { /// `start_after`: The first psync should happen after 60 seconds /// /// `frequency` and `count`: The next 5 retries should have an interval of 300 seconds between them -/// pub async fn get_sync_process_schedule_time( db: &dyn StorageInterface, connector: &str, diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 92cba2eec3f0..751359c77634 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -205,7 +205,6 @@ fn construct_refund_router_data() -> types::RefundsRouterData { } #[actix_web::test] - async fn payments_create_success() { let conf = Settings::new().unwrap(); let tx: oneshot::Sender<()> = oneshot::channel().0; @@ -314,7 +313,6 @@ async fn payments_create_failure() { } #[actix_web::test] - async fn refund_for_successful_payments() { let conf = Settings::new().unwrap(); use router::connector::Aci; diff --git a/crates/router_derive/src/lib.rs b/crates/router_derive/src/lib.rs index 33edd5e215e0..d1fb543318f2 100644 --- a/crates/router_derive/src/lib.rs +++ b/crates/router_derive/src/lib.rs @@ -166,7 +166,6 @@ pub fn diesel_enum( /// } /// ``` /// - /// # Panics /// /// Panics if a struct without named fields is provided as input to the macro @@ -505,7 +504,6 @@ pub fn operation_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre /// payment_method: String, /// } /// ``` - #[proc_macro_derive( PolymorphicSchema, attributes(mandatory_in, generate_schemas, remove_in) @@ -620,6 +618,7 @@ pub fn try_get_enum_variant(input: proc_macro::TokenStream) -> proc_macro::Token /// /// Example /// +/// ``` /// #[derive(Default, Serialize, FlatStruct)] /// pub struct User { /// name: String, @@ -644,7 +643,7 @@ pub fn try_get_enum_variant(input: proc_macro::TokenStream) -> proc_macro::Token /// ("address.zip", "941222"), /// ("email", "test@example.com"), /// ] -/// +/// ``` #[proc_macro_derive(FlatStruct)] pub fn flat_struct_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as syn::DeriveInput); diff --git a/crates/router_derive/src/macros/operation.rs b/crates/router_derive/src/macros/operation.rs index 407e910b29d1..a096c236b2f2 100644 --- a/crates/router_derive/src/macros/operation.rs +++ b/crates/router_derive/src/macros/operation.rs @@ -5,7 +5,7 @@ use quote::{quote, ToTokens}; use strum::IntoEnumIterator; use syn::{self, parse::Parse, DeriveInput}; -use crate::macros::helpers::{self}; +use crate::macros::helpers; #[derive(Debug, Clone, Copy, strum::EnumString, strum::EnumIter, strum::Display)] #[strum(serialize_all = "snake_case")] diff --git a/crates/router_env/src/lib.rs b/crates/router_env/src/lib.rs index 0d46f8ac3325..9b3aec4c949b 100644 --- a/crates/router_env/src/lib.rs +++ b/crates/router_env/src/lib.rs @@ -1,8 +1,6 @@ #![warn(missing_debug_implementations)] -//! //! Environment of payment router: logger, basic config, its environment awareness. -//! #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR" ), "/", "README.md"))] diff --git a/crates/router_env/src/logger/mod.rs b/crates/router_env/src/logger.rs similarity index 98% rename from crates/router_env/src/logger/mod.rs rename to crates/router_env/src/logger.rs index a7aa0610e9b4..2b0ae49b1f65 100644 --- a/crates/router_env/src/logger/mod.rs +++ b/crates/router_env/src/logger.rs @@ -1,6 +1,4 @@ -//! //! Logger of the system. -//! pub use tracing::{debug, error, event as log, info, warn}; pub use tracing_attributes::instrument; diff --git a/crates/router_env/src/logger/config.rs b/crates/router_env/src/logger/config.rs index 03cc9ac17bc8..746c8ee9580a 100644 --- a/crates/router_env/src/logger/config.rs +++ b/crates/router_env/src/logger/config.rs @@ -1,6 +1,4 @@ -//! //! Logger-specific config. -//! use std::path::PathBuf; diff --git a/crates/router_env/src/logger/formatter.rs b/crates/router_env/src/logger/formatter.rs index 6bcf669e73a7..5c3341026c20 100644 --- a/crates/router_env/src/logger/formatter.rs +++ b/crates/router_env/src/logger/formatter.rs @@ -1,6 +1,4 @@ -//! //! Formatting [layer](https://docs.rs/tracing-subscriber/0.3.15/tracing_subscriber/layer/trait.Layer.html) for Router. -//! use std::{ collections::{HashMap, HashSet}, @@ -117,10 +115,8 @@ impl fmt::Display for RecordType { } } -/// /// Format log records. /// `FormattingLayer` relies on the `tracing_bunyan_formatter::JsonStorageLayer` which is storage of entries. -/// #[derive(Debug)] pub struct FormattingLayer where @@ -145,7 +141,6 @@ where W: for<'a> MakeWriter<'a> + 'static, F: Formatter + Clone, { - /// /// Constructor of `FormattingLayer`. /// /// A `name` will be attached to all records during formatting. @@ -155,7 +150,6 @@ where /// ```rust /// let formatting_layer = router_env::FormattingLayer::new("my_service", std::io::stdout, serde_json::ser::CompactFormatter); /// ``` - /// pub fn new( service: &str, dst_writer: W, @@ -296,11 +290,9 @@ where Ok(()) } - /// /// Flush memory buffer into an output stream trailing it with next line. /// /// Should be done by single `write_all` call to avoid fragmentation of log because of mutlithreading. - /// fn flush(&self, mut buffer: Vec) -> Result<(), std::io::Error> { buffer.write_all(b"\n")?; self.dst_writer.make_writer().write_all(&buffer) @@ -361,12 +353,9 @@ where Ok(buffer) } - /// /// Format message of a span. /// /// Example: "[FN_WITHOUT_COLON - START]" - /// - fn span_message(span: &SpanRef<'_, S>, ty: RecordType) -> String where S: Subscriber + for<'a> LookupSpan<'a>, @@ -374,12 +363,9 @@ where format!("[{} - {}]", span.metadata().name().to_uppercase(), ty) } - /// /// Format message of an event. /// /// Examples: "[FN_WITHOUT_COLON - EVENT] Message" - /// - fn event_message( span: &Option<&SpanRef<'_, S>>, event: &Event<'_>, diff --git a/crates/router_env/src/logger/setup.rs b/crates/router_env/src/logger/setup.rs index 428112fb37c3..6433172edf4d 100644 --- a/crates/router_env/src/logger/setup.rs +++ b/crates/router_env/src/logger/setup.rs @@ -181,9 +181,7 @@ struct TraceAssertion { } impl TraceAssertion { - /// /// Should the provided url be traced - /// fn should_trace_url(&self, url: &str) -> bool { match &self.clauses { Some(clauses) => clauses.iter().all(|cur| cur.compare_url(url)), @@ -192,9 +190,7 @@ impl TraceAssertion { } } -/// /// Conditional Sampler for providing control on url based tracing -/// #[derive(Clone, Debug)] struct ConditionalSampler(TraceAssertion, T); diff --git a/crates/router_env/src/logger/storage.rs b/crates/router_env/src/logger/storage.rs index ce220680bb15..cdaf06ecf93a 100644 --- a/crates/router_env/src/logger/storage.rs +++ b/crates/router_env/src/logger/storage.rs @@ -1,6 +1,4 @@ -//! //! Storing [layer](https://docs.rs/tracing-subscriber/0.3.15/tracing_subscriber/layer/trait.Layer.html) for Router. -//! use std::{collections::HashMap, fmt, time::Instant}; diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 5d59e7ddbac4..b1488b904bee 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -1,6 +1,4 @@ -//! //! Types. -//! use serde::Deserialize; use strum::{Display, EnumString}; @@ -9,12 +7,9 @@ pub use tracing::{ Level, Value, }; -/// /// Category and tag of log event. /// /// Don't hesitate to add your variant if it is missing here. -/// - #[derive(Debug, Default, Deserialize, Clone, Display, EnumString)] pub enum Tag { /// General. @@ -516,9 +511,7 @@ pub enum Flow { PaymentStartRedirection, } -/// /// Trait for providing generic behaviour to flow metric -/// pub trait FlowMetric: ToString + std::fmt::Debug + Clone {} impl FlowMetric for Flow {} diff --git a/crates/router_env/tests/logger.rs b/crates/router_env/tests/logger.rs index 46b5b9538cf7..1070938a8a10 100644 --- a/crates/router_env/tests/logger.rs +++ b/crates/router_env/tests/logger.rs @@ -1,10 +1,11 @@ #![allow(clippy::unwrap_used)] mod test_module; + use ::config::ConfigError; use router_env::TelemetryGuard; -use self::test_module::some_module::*; +use self::test_module::fn_with_colon; fn logger() -> error_stack::Result<&'static TelemetryGuard, ConfigError> { use once_cell::sync::OnceCell; diff --git a/crates/router_env/tests/test_module/some_module.rs b/crates/router_env/tests/test_module.rs similarity index 90% rename from crates/router_env/tests/test_module/some_module.rs rename to crates/router_env/tests/test_module.rs index 8c9dda2c08e6..f3c382706b4d 100644 --- a/crates/router_env/tests/test_module/some_module.rs +++ b/crates/router_env/tests/test_module.rs @@ -1,7 +1,6 @@ -use logger::instrument; use router_env as logger; -#[instrument(skip_all)] +#[tracing::instrument(skip_all)] pub async fn fn_with_colon(val: i32) { let a = 13; let b = 31; @@ -23,7 +22,7 @@ pub async fn fn_with_colon(val: i32) { fn_without_colon(131).await; } -#[instrument(fields(val3 = "abc"), skip_all)] +#[tracing::instrument(fields(val3 = "abc"), skip_all)] pub async fn fn_without_colon(val: i32) { let a = 13; let b = 31; diff --git a/crates/router_env/tests/test_module/mod.rs b/crates/router_env/tests/test_module/mod.rs deleted file mode 100644 index 7699174549da..000000000000 --- a/crates/router_env/tests/test_module/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod some_module; diff --git a/crates/scheduler/src/db/mod.rs b/crates/scheduler/src/db.rs similarity index 100% rename from crates/scheduler/src/db/mod.rs rename to crates/scheduler/src/db.rs diff --git a/crates/storage_impl/src/mock_db.rs b/crates/storage_impl/src/mock_db.rs index efcce5677270..b81676a6cc4f 100644 --- a/crates/storage_impl/src/mock_db.rs +++ b/crates/storage_impl/src/mock_db.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use diesel_models::{self as store}; +use diesel_models as store; use error_stack::ResultExt; use futures::lock::Mutex; use hyperswitch_domain_models::{ diff --git a/crates/storage_impl/src/redis/cache.rs b/crates/storage_impl/src/redis/cache.rs index 699310579594..b32c3d220445 100644 --- a/crates/storage_impl/src/redis/cache.rs +++ b/crates/storage_impl/src/redis/cache.rs @@ -117,7 +117,7 @@ impl<'a> TryFrom> for RedisValue { } } -impl<'a> TryFrom for CacheRedact<'a> { +impl TryFrom for CacheRedact<'_> { type Error = Report; fn try_from(v: RedisValue) -> Result { diff --git a/crates/storage_impl/src/redis/kv_store.rs b/crates/storage_impl/src/redis/kv_store.rs index 2fde935b670d..83d8de9c30ac 100644 --- a/crates/storage_impl/src/redis/kv_store.rs +++ b/crates/storage_impl/src/redis/kv_store.rs @@ -57,7 +57,7 @@ pub enum PartitionKey<'a> { }, } // PartitionKey::MerchantIdPaymentId {merchant_id, payment_id} -impl<'a> std::fmt::Display for PartitionKey<'a> { +impl std::fmt::Display for PartitionKey<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { PartitionKey::MerchantIdPaymentId { @@ -279,7 +279,7 @@ pub enum Op<'a> { Find, } -impl<'a> std::fmt::Display for Op<'a> { +impl std::fmt::Display for Op<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Op::Insert => f.write_str("insert"), From 933911eda11f32d72ffeddb948b86672cb08105b Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:03:12 +0530 Subject: [PATCH 49/51] fix(payment_link): add support for hide card nickname field for open payment links (#6700) Co-authored-by: Chikke Srujan --- .../payment_link_initiate/payment_link_initiator.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js index b79e2284a567..ab46223df177 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js @@ -39,7 +39,7 @@ function initializeSDK() { paymentDetails.sdk_layout === "accordion" ? "accordion" : paymentDetails.sdk_layout; - + var hideCardNicknameField = paymentDetails.hide_card_nickname_field; var unifiedCheckoutOptions = { displaySavedPaymentMethodsCheckbox: false, displaySavedPaymentMethods: false, @@ -57,7 +57,7 @@ function initializeSDK() { }, }, showCardFormByDefault: paymentDetails.show_card_form_by_default, - hideCardNicknameField: false, + hideCardNicknameField: hideCardNicknameField, }; // @ts-ignore unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions); @@ -83,5 +83,10 @@ function redirectToStatus() { arr.splice(0, 2); arr.unshift("status"); arr.unshift("payment_link"); - window.location.href = window.location.origin + "/" + arr.join("/") + "?locale=" + paymentDetails.locale; + window.location.href = + window.location.origin + + "/" + + arr.join("/") + + "?locale=" + + paymentDetails.locale; } From 063a1c636ce29ca8f76c3c272c6da4d32d356cda Mon Sep 17 00:00:00 2001 From: Sandeep Kumar <83278309+tsdk02@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:03:43 +0530 Subject: [PATCH 50/51] fix(opensearch): fix empty filter array query addition in globalsearch query (#6716) --- crates/analytics/src/opensearch.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/analytics/src/opensearch.rs b/crates/analytics/src/opensearch.rs index e8726840a2ea..246a0f4e8bb3 100644 --- a/crates/analytics/src/opensearch.rs +++ b/crates/analytics/src/opensearch.rs @@ -705,9 +705,7 @@ impl OpenSearchQueryBuilder { let should_array = self.build_auth_array(); - if !bool_obj.is_empty() { - query_obj.insert("bool".to_string(), Value::Object(bool_obj)); - } + query_obj.insert("bool".to_string(), Value::Object(bool_obj)); let mut sort_obj = Map::new(); sort_obj.insert( From f6dde13d6c2920761f236969a3862fe61f3e0e3d Mon Sep 17 00:00:00 2001 From: Prajjwal Kumar Date: Mon, 2 Dec 2024 20:13:37 +0530 Subject: [PATCH 51/51] feat(routing): elimination routing switch for toggling the feature (#6568) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 2 +- api-reference/openapi_spec.json | 64 ++-- crates/api_models/src/events/routing.rs | 6 +- crates/api_models/src/routing.rs | 184 +++++++++-- crates/openapi/src/openapi.rs | 6 +- crates/openapi/src/routes/routing.rs | 32 +- crates/router/src/core/payments/routing.rs | 2 +- crates/router/src/core/routing.rs | 215 ++++--------- crates/router/src/core/routing/helpers.rs | 339 +++++++++++++++++++-- crates/router/src/routes/app.rs | 14 +- crates/router/src/routes/routing.rs | 54 +++- crates/storage_impl/src/redis/cache.rs | 13 +- crates/storage_impl/src/redis/pub_sub.rs | 19 +- 13 files changed, 682 insertions(+), 268 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index d8ab5a7a20dd..6eab5ed2655c 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -1278,7 +1278,7 @@ ], "summary": "Routing - Create", "description": "Create a routing algorithm", - "operationId": "Create a routing algprithm", + "operationId": "Create a routing algorithm", "requestBody": { "content": { "application/json": { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index f903879c483c..56b77bff1aa9 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -3929,7 +3929,7 @@ ], "summary": "Routing - Toggle success based dynamic routing for profile", "description": "Create a success based dynamic routing algorithm", - "operationId": "Toggle success based dynamic routing algprithm", + "operationId": "Toggle success based dynamic routing algorithm", "parameters": [ { "name": "account_id", @@ -3955,7 +3955,7 @@ "description": "Feature to enable for success based routing", "required": true, "schema": { - "$ref": "#/components/schemas/SuccessBasedRoutingFeatures" + "$ref": "#/components/schemas/DynamicRoutingFeatures" } } ], @@ -10234,6 +10234,14 @@ } } }, + "DynamicRoutingFeatures": { + "type": "string", + "enum": [ + "metrics", + "dynamic_connector_selection", + "none" + ] + }, "EnabledPaymentMethod": { "type": "object", "description": "Object for EnabledPaymentMethod", @@ -23872,14 +23880,6 @@ "destination" ] }, - "SuccessBasedRoutingFeatures": { - "type": "string", - "enum": [ - "metrics", - "dynamic_connector_selection", - "none" - ] - }, "SurchargeDetailsResponse": { "type": "object", "required": [ @@ -24096,6 +24096,28 @@ } } }, + "ToggleDynamicRoutingPath": { + "type": "object", + "required": [ + "profile_id" + ], + "properties": { + "profile_id": { + "type": "string" + } + } + }, + "ToggleDynamicRoutingQuery": { + "type": "object", + "required": [ + "enable" + ], + "properties": { + "enable": { + "$ref": "#/components/schemas/DynamicRoutingFeatures" + } + } + }, "ToggleKVRequest": { "type": "object", "required": [ @@ -24129,28 +24151,6 @@ } } }, - "ToggleSuccessBasedRoutingPath": { - "type": "object", - "required": [ - "profile_id" - ], - "properties": { - "profile_id": { - "type": "string" - } - } - }, - "ToggleSuccessBasedRoutingQuery": { - "type": "object", - "required": [ - "enable" - ], - "properties": { - "enable": { - "$ref": "#/components/schemas/SuccessBasedRoutingFeatures" - } - } - }, "TouchNGoRedirection": { "type": "object" }, diff --git a/crates/api_models/src/events/routing.rs b/crates/api_models/src/events/routing.rs index ffa5e008f0a5..87e7fa278859 100644 --- a/crates/api_models/src/events/routing.rs +++ b/crates/api_models/src/events/routing.rs @@ -6,7 +6,7 @@ use crate::routing::{ RoutingLinkWrapper, RoutingPayloadWrapper, RoutingRetrieveLinkQuery, RoutingRetrieveLinkQueryWrapper, RoutingRetrieveQuery, SuccessBasedRoutingConfig, SuccessBasedRoutingPayloadWrapper, SuccessBasedRoutingUpdateConfigQuery, - ToggleSuccessBasedRoutingQuery, ToggleSuccessBasedRoutingWrapper, + ToggleDynamicRoutingQuery, ToggleDynamicRoutingWrapper, }; impl ApiEventMetric for RoutingKind { @@ -79,7 +79,7 @@ impl ApiEventMetric for RoutingRetrieveLinkQueryWrapper { } } -impl ApiEventMetric for ToggleSuccessBasedRoutingQuery { +impl ApiEventMetric for ToggleDynamicRoutingQuery { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Routing) } @@ -97,7 +97,7 @@ impl ApiEventMetric for SuccessBasedRoutingPayloadWrapper { } } -impl ApiEventMetric for ToggleSuccessBasedRoutingWrapper { +impl ApiEventMetric for ToggleDynamicRoutingWrapper { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Routing) } diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index dbfa770cbf1d..6cd5d5251a31 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -522,6 +522,92 @@ pub struct DynamicAlgorithmWithTimestamp { #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct DynamicRoutingAlgorithmRef { pub success_based_algorithm: Option, + pub elimination_routing_algorithm: Option, +} + +pub trait DynamicRoutingAlgoAccessor { + fn get_algorithm_id_with_timestamp( + self, + ) -> DynamicAlgorithmWithTimestamp; + fn get_enabled_features(&mut self) -> &mut DynamicRoutingFeatures; +} + +impl DynamicRoutingAlgoAccessor for SuccessBasedAlgorithm { + fn get_algorithm_id_with_timestamp( + self, + ) -> DynamicAlgorithmWithTimestamp { + self.algorithm_id_with_timestamp + } + fn get_enabled_features(&mut self) -> &mut DynamicRoutingFeatures { + &mut self.enabled_feature + } +} + +impl DynamicRoutingAlgoAccessor for EliminationRoutingAlgorithm { + fn get_algorithm_id_with_timestamp( + self, + ) -> DynamicAlgorithmWithTimestamp { + self.algorithm_id_with_timestamp + } + fn get_enabled_features(&mut self) -> &mut DynamicRoutingFeatures { + &mut self.enabled_feature + } +} + +impl EliminationRoutingAlgorithm { + pub fn new( + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp< + common_utils::id_type::RoutingId, + >, + ) -> Self { + Self { + algorithm_id_with_timestamp, + enabled_feature: DynamicRoutingFeatures::None, + } + } +} + +impl SuccessBasedAlgorithm { + pub fn new( + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp< + common_utils::id_type::RoutingId, + >, + ) -> Self { + Self { + algorithm_id_with_timestamp, + enabled_feature: DynamicRoutingFeatures::None, + } + } +} + +impl DynamicRoutingAlgorithmRef { + pub fn update(&mut self, new: Self) { + if let Some(elimination_routing_algorithm) = new.elimination_routing_algorithm { + self.elimination_routing_algorithm = Some(elimination_routing_algorithm) + } + if let Some(success_based_algorithm) = new.success_based_algorithm { + self.success_based_algorithm = Some(success_based_algorithm) + } + } + + pub fn update_specific_ref( + &mut self, + algo_type: DynamicRoutingType, + feature_to_enable: DynamicRoutingFeatures, + ) { + match algo_type { + DynamicRoutingType::SuccessRateBasedRouting => { + self.success_based_algorithm + .as_mut() + .map(|algo| algo.enabled_feature = feature_to_enable); + } + DynamicRoutingType::EliminationRouting => { + self.elimination_routing_algorithm + .as_mut() + .map(|algo| algo.enabled_feature = feature_to_enable); + } + } + } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -529,11 +615,25 @@ pub struct SuccessBasedAlgorithm { pub algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp, #[serde(default)] - pub enabled_feature: SuccessBasedRoutingFeatures, + pub enabled_feature: DynamicRoutingFeatures, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct EliminationRoutingAlgorithm { + pub algorithm_id_with_timestamp: + DynamicAlgorithmWithTimestamp, + #[serde(default)] + pub enabled_feature: DynamicRoutingFeatures, +} + +impl EliminationRoutingAlgorithm { + pub fn update_enabled_features(&mut self, feature_to_enable: DynamicRoutingFeatures) { + self.enabled_feature = feature_to_enable + } } impl SuccessBasedAlgorithm { - pub fn update_enabled_features(&mut self, feature_to_enable: SuccessBasedRoutingFeatures) { + pub fn update_enabled_features(&mut self, feature_to_enable: DynamicRoutingFeatures) { self.enabled_feature = feature_to_enable } } @@ -542,26 +642,40 @@ impl DynamicRoutingAlgorithmRef { pub fn update_algorithm_id( &mut self, new_id: common_utils::id_type::RoutingId, - enabled_feature: SuccessBasedRoutingFeatures, + enabled_feature: DynamicRoutingFeatures, + dynamic_routing_type: DynamicRoutingType, ) { - self.success_based_algorithm = Some(SuccessBasedAlgorithm { - algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp { - algorithm_id: Some(new_id), - timestamp: common_utils::date_time::now_unix_timestamp(), - }, - enabled_feature, - }) + match dynamic_routing_type { + DynamicRoutingType::SuccessRateBasedRouting => { + self.success_based_algorithm = Some(SuccessBasedAlgorithm { + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp { + algorithm_id: Some(new_id), + timestamp: common_utils::date_time::now_unix_timestamp(), + }, + enabled_feature, + }) + } + DynamicRoutingType::EliminationRouting => { + self.elimination_routing_algorithm = Some(EliminationRoutingAlgorithm { + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp { + algorithm_id: Some(new_id), + timestamp: common_utils::date_time::now_unix_timestamp(), + }, + enabled_feature, + }) + } + }; } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] -pub struct ToggleSuccessBasedRoutingQuery { - pub enable: SuccessBasedRoutingFeatures, +pub struct ToggleDynamicRoutingQuery { + pub enable: DynamicRoutingFeatures, } #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, ToSchema)] #[serde(rename_all = "snake_case")] -pub enum SuccessBasedRoutingFeatures { +pub enum DynamicRoutingFeatures { Metrics, DynamicConnectorSelection, #[default] @@ -577,26 +691,52 @@ pub struct SuccessBasedRoutingUpdateConfigQuery { } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct ToggleSuccessBasedRoutingWrapper { +pub struct ToggleDynamicRoutingWrapper { pub profile_id: common_utils::id_type::ProfileId, - pub feature_to_enable: SuccessBasedRoutingFeatures, + pub feature_to_enable: DynamicRoutingFeatures, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] -pub struct ToggleSuccessBasedRoutingPath { +pub struct ToggleDynamicRoutingPath { #[schema(value_type = String)] pub profile_id: common_utils::id_type::ProfileId, } + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] +pub struct EliminationRoutingConfig { + pub params: Option>, + // pub labels: Option>, + pub elimination_analyser_config: Option, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] +pub struct EliminationAnalyserConfig { + pub bucket_size: Option, + pub bucket_ttl_in_mins: Option, +} + +impl Default for EliminationRoutingConfig { + fn default() -> Self { + Self { + params: Some(vec![DynamicRoutingConfigParams::PaymentMethod]), + elimination_analyser_config: Some(EliminationAnalyserConfig { + bucket_size: Some(5), + bucket_ttl_in_mins: Some(2.0), + }), + } + } +} + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] pub struct SuccessBasedRoutingConfig { - pub params: Option>, + pub params: Option>, pub config: Option, } impl Default for SuccessBasedRoutingConfig { fn default() -> Self { Self { - params: Some(vec![SuccessBasedRoutingConfigParams::PaymentMethod]), + params: Some(vec![DynamicRoutingConfigParams::PaymentMethod]), config: Some(SuccessBasedRoutingConfigBody { min_aggregates_size: Some(2), default_success_rate: Some(100.0), @@ -611,7 +751,7 @@ impl Default for SuccessBasedRoutingConfig { } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema, strum::Display)] -pub enum SuccessBasedRoutingConfigParams { +pub enum DynamicRoutingConfigParams { PaymentMethod, PaymentMethodType, AuthenticationType, @@ -642,6 +782,12 @@ pub struct SuccessBasedRoutingPayloadWrapper { pub profile_id: common_utils::id_type::ProfileId, } +#[derive(Debug, Clone, strum::Display, serde::Serialize, serde::Deserialize)] +pub enum DynamicRoutingType { + SuccessRateBasedRouting, + EliminationRouting, +} + impl SuccessBasedRoutingConfig { pub fn update(&mut self, new: Self) { if let Some(params) = new.params { diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index ed6efea27f83..4ec1bb22b4b8 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -575,7 +575,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::routing::RoutingDictionaryRecord, api_models::routing::RoutingKind, api_models::routing::RoutableConnectorChoice, - api_models::routing::SuccessBasedRoutingFeatures, + api_models::routing::DynamicRoutingFeatures, api_models::routing::LinkedRoutingConfigRetrieveResponse, api_models::routing::RoutingRetrieveResponse, api_models::routing::ProfileDefaultRoutingConfig, @@ -586,8 +586,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::routing::StraightThroughAlgorithm, api_models::routing::ConnectorVolumeSplit, api_models::routing::ConnectorSelection, - api_models::routing::ToggleSuccessBasedRoutingQuery, - api_models::routing::ToggleSuccessBasedRoutingPath, + api_models::routing::ToggleDynamicRoutingQuery, + api_models::routing::ToggleDynamicRoutingPath, api_models::routing::ast::RoutableChoiceKind, api_models::enums::RoutableConnectors, api_models::routing::ast::ProgramConnectorSelection, diff --git a/crates/openapi/src/routes/routing.rs b/crates/openapi/src/routes/routing.rs index 9b8f56aeb81a..b0fcb75fa49b 100644 --- a/crates/openapi/src/routes/routing.rs +++ b/crates/openapi/src/routes/routing.rs @@ -37,7 +37,7 @@ pub async fn routing_create_config() {} (status = 403, description = "Forbidden"), ), tag = "Routing", - operation_id = "Create a routing algprithm", + operation_id = "Create a routing algorithm", security(("api_key" = []), ("jwt_key" = [])) )] pub async fn routing_create_config() {} @@ -264,7 +264,7 @@ pub async fn routing_update_default_config_for_profile() {} params( ("account_id" = String, Path, description = "Merchant id"), ("profile_id" = String, Path, description = "Profile id under which Dynamic routing needs to be toggled"), - ("enable" = SuccessBasedRoutingFeatures, Query, description = "Feature to enable for success based routing"), + ("enable" = DynamicRoutingFeatures, Query, description = "Feature to enable for success based routing"), ), responses( (status = 200, description = "Routing Algorithm created", body = RoutingDictionaryRecord), @@ -275,7 +275,33 @@ pub async fn routing_update_default_config_for_profile() {} (status = 403, description = "Forbidden"), ), tag = "Routing", - operation_id = "Toggle success based dynamic routing algprithm", + operation_id = "Toggle success based dynamic routing algorithm", security(("api_key" = []), ("jwt_key" = [])) )] pub async fn toggle_success_based_routing() {} + +#[cfg(feature = "v1")] +/// Routing - Toggle elimination routing for profile +/// +/// Create a elimination based dynamic routing algorithm +#[utoipa::path( + post, + path = "/account/:account_id/business_profile/:profile_id/dynamic_routing/elimination/toggle", + params( + ("account_id" = String, Path, description = "Merchant id"), + ("profile_id" = String, Path, description = "Profile id under which Dynamic routing needs to be toggled"), + ("enable" = DynamicRoutingFeatures, Query, description = "Feature to enable for success based routing"), + ), + responses( + (status = 200, description = "Routing Algorithm created", body = RoutingDictionaryRecord), + (status = 400, description = "Request body is malformed"), + (status = 500, description = "Internal server error"), + (status = 404, description = "Resource missing"), + (status = 422, description = "Unprocessable request"), + (status = 403, description = "Forbidden"), + ), + tag = "Routing", + operation_id = "Toggle elimination routing algorithm", + security(("api_key" = []), ("jwt_key" = [])) +)] +pub async fn toggle_elimination_routing() {} diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 28321529ef2d..2fb49c37d224 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -1263,7 +1263,7 @@ pub async fn perform_success_based_routing( )?; if success_based_algo_ref.enabled_feature - == api_routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection + == api_routing::DynamicRoutingFeatures::DynamicConnectorSelection { logger::debug!( "performing success_based_routing for profile {}", diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 348f5229b756..9318e0c5b9fa 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -452,6 +452,16 @@ pub async fn link_routing_config( }, enabled_feature: _ }) if id == &algorithm_id + ) || matches!( + dynamic_routing_ref.elimination_routing_algorithm, + Some(routing::EliminationRoutingAlgorithm { + algorithm_id_with_timestamp: + routing_types::DynamicAlgorithmWithTimestamp { + algorithm_id: Some(ref id), + timestamp: _ + }, + enabled_feature: _ + }) if id == &algorithm_id ), || { Err(errors::ApiErrorResponse::PreconditionFailed { @@ -470,7 +480,9 @@ pub async fn link_routing_config( "missing success_based_algorithm in dynamic_algorithm_ref from business_profile table", )? .enabled_feature, + routing_types::DynamicRoutingType::SuccessRateBasedRouting, ); + helpers::update_business_profile_active_dynamic_algorithm_ref( db, key_manager_state, @@ -1185,13 +1197,16 @@ pub async fn update_default_routing_config_for_profile( )) } +// Toggle the specific routing type as well as add the default configs in RoutingAlgorithm table +// and update the same in business profile table. #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -pub async fn toggle_success_based_routing( +pub async fn toggle_specific_dynamic_routing( state: SessionState, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, - feature_to_enable: routing::SuccessBasedRoutingFeatures, + feature_to_enable: routing::DynamicRoutingFeatures, profile_id: common_utils::id_type::ProfileId, + dynamic_routing_type: routing::DynamicRoutingType, ) -> RouterResponse { metrics::ROUTING_CREATE_REQUEST_RECEIVED.add( &metrics::CONTEXT, @@ -1214,170 +1229,44 @@ pub async fn toggle_success_based_routing( id: profile_id.get_string_repr().to_owned(), })?; - let mut success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef = - business_profile - .dynamic_routing_algorithm - .clone() - .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to deserialize dynamic routing algorithm ref from business profile", - )? - .unwrap_or_default(); + let dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef = business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to deserialize dynamic routing algorithm ref from business profile", + )? + .unwrap_or_default(); match feature_to_enable { - routing::SuccessBasedRoutingFeatures::Metrics - | routing::SuccessBasedRoutingFeatures::DynamicConnectorSelection => { - if let Some(ref mut algo_with_timestamp) = - success_based_dynamic_routing_algo_ref.success_based_algorithm - { - match algo_with_timestamp - .algorithm_id_with_timestamp - .algorithm_id - .clone() - { - Some(algorithm_id) => { - // algorithm is already present in profile - if algo_with_timestamp.enabled_feature == feature_to_enable { - // algorithm already has the required feature - Err(errors::ApiErrorResponse::PreconditionFailed { - message: "Success rate based routing is already enabled" - .to_string(), - })? - } else { - // enable the requested feature for the algorithm - algo_with_timestamp.update_enabled_features(feature_to_enable); - let record = db - .find_routing_algorithm_by_profile_id_algorithm_id( - business_profile.get_id(), - &algorithm_id, - ) - .await - .to_not_found_response( - errors::ApiErrorResponse::ResourceIdNotFound, - )?; - let response = record.foreign_into(); - helpers::update_business_profile_active_dynamic_algorithm_ref( - db, - key_manager_state, - &key_store, - business_profile, - success_based_dynamic_routing_algo_ref, - ) - .await?; - - metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( - &metrics::CONTEXT, - 1, - &add_attributes([( - "profile_id", - profile_id.get_string_repr().to_owned(), - )]), - ); - Ok(service_api::ApplicationResponse::Json(response)) - } - } - None => { - // algorithm isn't present in profile - helpers::default_success_based_routing_setup( - &state, - key_store, - business_profile, - feature_to_enable, - merchant_account.get_id().to_owned(), - success_based_dynamic_routing_algo_ref, - ) - .await - } - } - } else { - // algorithm isn't present in profile - helpers::default_success_based_routing_setup( - &state, - key_store, - business_profile, - feature_to_enable, - merchant_account.get_id().to_owned(), - success_based_dynamic_routing_algo_ref, - ) - .await - } + routing::DynamicRoutingFeatures::Metrics + | routing::DynamicRoutingFeatures::DynamicConnectorSelection => { + // occurs when algorithm is already present in the db + // 1. If present with same feature then return response as already enabled + // 2. Else update the feature and persist the same on db + // 3. If not present in db then create a new default entry + helpers::enable_dynamic_routing_algorithm( + &state, + key_store, + business_profile, + feature_to_enable, + dynamic_routing_algo_ref, + dynamic_routing_type, + ) + .await } - routing::SuccessBasedRoutingFeatures::None => { - // disable success based routing for the requested profile - let timestamp = common_utils::date_time::now_unix_timestamp(); - match success_based_dynamic_routing_algo_ref.success_based_algorithm { - Some(algorithm_ref) => { - if let Some(algorithm_id) = - algorithm_ref.algorithm_id_with_timestamp.algorithm_id - { - let dynamic_routing_algorithm = routing_types::DynamicRoutingAlgorithmRef { - success_based_algorithm: Some(routing::SuccessBasedAlgorithm { - algorithm_id_with_timestamp: - routing_types::DynamicAlgorithmWithTimestamp { - algorithm_id: None, - timestamp, - }, - enabled_feature: routing::SuccessBasedRoutingFeatures::None, - }), - }; - - // redact cache for success based routing configs - let cache_key = format!( - "{}_{}", - business_profile.get_id().get_string_repr(), - algorithm_id.get_string_repr() - ); - let cache_entries_to_redact = - vec![cache::CacheKind::SuccessBasedDynamicRoutingCache( - cache_key.into(), - )]; - let _ = cache::publish_into_redact_channel( - state.store.get_cache_store().as_ref(), - cache_entries_to_redact, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to publish into the redact channel for evicting the success based routing config cache")?; - - let record = db - .find_routing_algorithm_by_profile_id_algorithm_id( - business_profile.get_id(), - &algorithm_id, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?; - let response = record.foreign_into(); - helpers::update_business_profile_active_dynamic_algorithm_ref( - db, - key_manager_state, - &key_store, - business_profile, - dynamic_routing_algorithm, - ) - .await?; - - metrics::ROUTING_UNLINK_CONFIG_SUCCESS_RESPONSE.add( - &metrics::CONTEXT, - 1, - &add_attributes([( - "profile_id", - profile_id.get_string_repr().to_owned(), - )]), - ); - - Ok(service_api::ApplicationResponse::Json(response)) - } else { - Err(errors::ApiErrorResponse::PreconditionFailed { - message: "Algorithm is already inactive".to_string(), - })? - } - } - None => Err(errors::ApiErrorResponse::PreconditionFailed { - message: "Success rate based routing is already disabled".to_string(), - })?, - } + routing::DynamicRoutingFeatures::None => { + // disable specific dynamic routing for the requested profile + helpers::disable_dynamic_routing_algorithm( + &state, + key_store, + business_profile, + dynamic_routing_algo_ref, + dynamic_routing_type, + ) + .await } } } diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index c22a5021a264..9dc56b8bd0a7 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -26,6 +26,8 @@ use router_env::{instrument, metrics::add_attributes, tracing}; use rustc_hash::FxHashSet; use storage_impl::redis::cache; +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] +use crate::db::errors::StorageErrorExt; #[cfg(feature = "v2")] use crate::types::domain::MerchantConnectorAccount; use crate::{ @@ -39,6 +41,8 @@ use crate::{ use crate::{core::metrics as core_metrics, routes::metrics, types::transformers::ForeignInto}; pub const SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM: &str = "Success rate based dynamic routing algorithm"; +pub const ELIMINATION_BASED_DYNAMIC_ROUTING_ALGORITHM: &str = + "Elimination based dynamic routing algorithm"; /// Provides us with all the configured configs of the Merchant in the ascending time configured /// manner and chooses the first of them @@ -263,9 +267,9 @@ pub async fn update_business_profile_active_dynamic_algorithm_ref( key_manager_state: &KeyManagerState, merchant_key_store: &domain::MerchantKeyStore, current_business_profile: domain::Profile, - dynamic_routing_algorithm: routing_types::DynamicRoutingAlgorithmRef, + dynamic_routing_algorithm_ref: routing_types::DynamicRoutingAlgorithmRef, ) -> RouterResult<()> { - let ref_val = dynamic_routing_algorithm + let ref_val = dynamic_routing_algorithm_ref .encode_to_value() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to convert dynamic routing ref to value")?; @@ -662,7 +666,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("success_based_algorithm not found in dynamic_routing_algorithm from business_profile table")?; - if success_based_algo_ref.enabled_feature != routing_types::SuccessBasedRoutingFeatures::None { + if success_based_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None { let client = state .grpc_client .dynamic_routing @@ -912,34 +916,303 @@ pub fn generate_tenant_business_profile_id( format!("{}:{}", redis_key_prefix, business_profile_id) } -/// default config setup for success_based_routing +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +pub async fn disable_dynamic_routing_algorithm( + state: &SessionState, + key_store: domain::MerchantKeyStore, + business_profile: domain::Profile, + dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, + dynamic_routing_type: routing_types::DynamicRoutingType, +) -> RouterResult> { + let db = state.store.as_ref(); + let key_manager_state = &state.into(); + let timestamp = common_utils::date_time::now_unix_timestamp(); + let profile_id = business_profile + .get_id() + .clone() + .get_string_repr() + .to_owned(); + let (algorithm_id, dynamic_routing_algorithm, cache_entries_to_redact) = + match dynamic_routing_type { + routing_types::DynamicRoutingType::SuccessRateBasedRouting => { + let Some(algorithm_ref) = dynamic_routing_algo_ref.success_based_algorithm else { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Success rate based routing is already disabled".to_string(), + })? + }; + let Some(algorithm_id) = algorithm_ref.algorithm_id_with_timestamp.algorithm_id + else { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Algorithm is already inactive".to_string(), + })? + }; + + let cache_key = format!( + "{}_{}", + business_profile.get_id().get_string_repr(), + algorithm_id.get_string_repr() + ); + let cache_entries_to_redact = + vec![cache::CacheKind::SuccessBasedDynamicRoutingCache( + cache_key.into(), + )]; + ( + algorithm_id, + routing_types::DynamicRoutingAlgorithmRef { + success_based_algorithm: Some(routing_types::SuccessBasedAlgorithm { + algorithm_id_with_timestamp: + routing_types::DynamicAlgorithmWithTimestamp { + algorithm_id: None, + timestamp, + }, + enabled_feature: routing_types::DynamicRoutingFeatures::None, + }), + elimination_routing_algorithm: dynamic_routing_algo_ref + .elimination_routing_algorithm, + }, + cache_entries_to_redact, + ) + } + routing_types::DynamicRoutingType::EliminationRouting => { + let Some(algorithm_ref) = dynamic_routing_algo_ref.elimination_routing_algorithm + else { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Elimination routing is already disabled".to_string(), + })? + }; + let Some(algorithm_id) = algorithm_ref.algorithm_id_with_timestamp.algorithm_id + else { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Algorithm is already inactive".to_string(), + })? + }; + let cache_key = format!( + "{}_{}", + business_profile.get_id().get_string_repr(), + algorithm_id.get_string_repr() + ); + let cache_entries_to_redact = + vec![cache::CacheKind::EliminationBasedDynamicRoutingCache( + cache_key.into(), + )]; + ( + algorithm_id, + routing_types::DynamicRoutingAlgorithmRef { + success_based_algorithm: dynamic_routing_algo_ref.success_based_algorithm, + elimination_routing_algorithm: Some( + routing_types::EliminationRoutingAlgorithm { + algorithm_id_with_timestamp: + routing_types::DynamicAlgorithmWithTimestamp { + algorithm_id: None, + timestamp, + }, + enabled_feature: routing_types::DynamicRoutingFeatures::None, + }, + ), + }, + cache_entries_to_redact, + ) + } + }; + + // redact cache for dynamic routing config + let _ = cache::publish_into_redact_channel( + state.store.get_cache_store().as_ref(), + cache_entries_to_redact, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to publish into the redact channel for evicting the dynamic routing config cache", + )?; + + let record = db + .find_routing_algorithm_by_profile_id_algorithm_id(business_profile.get_id(), &algorithm_id) + .await + .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?; + let response = record.foreign_into(); + update_business_profile_active_dynamic_algorithm_ref( + db, + key_manager_state, + &key_store, + business_profile, + dynamic_routing_algorithm, + ) + .await?; + + core_metrics::ROUTING_UNLINK_CONFIG_SUCCESS_RESPONSE.add( + &metrics::CONTEXT, + 1, + &add_attributes([("profile_id", profile_id)]), + ); + + Ok(ApplicationResponse::Json(response)) +} + +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +pub async fn enable_dynamic_routing_algorithm( + state: &SessionState, + key_store: domain::MerchantKeyStore, + business_profile: domain::Profile, + feature_to_enable: routing_types::DynamicRoutingFeatures, + dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, + dynamic_routing_type: routing_types::DynamicRoutingType, +) -> RouterResult> { + let dynamic_routing = dynamic_routing_algo_ref.clone(); + match dynamic_routing_type { + routing_types::DynamicRoutingType::SuccessRateBasedRouting => { + enable_specific_routing_algorithm( + state, + key_store, + business_profile, + feature_to_enable, + dynamic_routing_algo_ref, + dynamic_routing_type, + dynamic_routing.success_based_algorithm, + ) + .await + } + routing_types::DynamicRoutingType::EliminationRouting => { + enable_specific_routing_algorithm( + state, + key_store, + business_profile, + feature_to_enable, + dynamic_routing_algo_ref, + dynamic_routing_type, + dynamic_routing.elimination_routing_algorithm, + ) + .await + } + } +} + +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +pub async fn enable_specific_routing_algorithm( + state: &SessionState, + key_store: domain::MerchantKeyStore, + business_profile: domain::Profile, + feature_to_enable: routing_types::DynamicRoutingFeatures, + mut dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, + dynamic_routing_type: routing_types::DynamicRoutingType, + algo_type: Option, +) -> RouterResult> +where + A: routing_types::DynamicRoutingAlgoAccessor + Clone + std::fmt::Debug, +{ + // Algorithm wasn't created yet + let Some(mut algo_type) = algo_type else { + return default_specific_dynamic_routing_setup( + state, + key_store, + business_profile, + feature_to_enable, + dynamic_routing_algo_ref, + dynamic_routing_type, + ) + .await; + }; + + // Algorithm was in disabled state + let Some(algo_type_algorithm_id) = algo_type + .clone() + .get_algorithm_id_with_timestamp() + .algorithm_id + else { + return default_specific_dynamic_routing_setup( + state, + key_store, + business_profile, + feature_to_enable, + dynamic_routing_algo_ref, + dynamic_routing_type, + ) + .await; + }; + let db = state.store.as_ref(); + let profile_id = business_profile.get_id().clone(); + let algo_type_enabled_features = algo_type.get_enabled_features(); + if *algo_type_enabled_features == feature_to_enable { + // algorithm already has the required feature + return Err(errors::ApiErrorResponse::PreconditionFailed { + message: format!("{} is already enabled", dynamic_routing_type), + } + .into()); + }; + *algo_type_enabled_features = feature_to_enable.clone(); + dynamic_routing_algo_ref + .update_specific_ref(dynamic_routing_type.clone(), feature_to_enable.clone()); + update_business_profile_active_dynamic_algorithm_ref( + db, + &state.into(), + &key_store, + business_profile, + dynamic_routing_algo_ref.clone(), + ) + .await?; + + let routing_algorithm = db + .find_routing_algorithm_by_profile_id_algorithm_id(&profile_id, &algo_type_algorithm_id) + .await + .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?; + let updated_routing_record = routing_algorithm.foreign_into(); + core_metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( + &metrics::CONTEXT, + 1, + &add_attributes([("profile_id", profile_id.get_string_repr().to_owned())]), + ); + Ok(ApplicationResponse::Json(updated_routing_record)) +} + #[cfg(feature = "v1")] #[instrument(skip_all)] -pub async fn default_success_based_routing_setup( +pub async fn default_specific_dynamic_routing_setup( state: &SessionState, key_store: domain::MerchantKeyStore, business_profile: domain::Profile, - feature_to_enable: routing_types::SuccessBasedRoutingFeatures, - merchant_id: id_type::MerchantId, - mut success_based_dynamic_routing_algo: routing_types::DynamicRoutingAlgorithmRef, + feature_to_enable: routing_types::DynamicRoutingFeatures, + mut dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, + dynamic_routing_type: routing_types::DynamicRoutingType, ) -> RouterResult> { let db = state.store.as_ref(); let key_manager_state = &state.into(); - let profile_id = business_profile.get_id().to_owned(); - let default_success_based_routing_config = routing_types::SuccessBasedRoutingConfig::default(); + let profile_id = business_profile.get_id().clone(); + let merchant_id = business_profile.merchant_id.clone(); let algorithm_id = common_utils::generate_routing_id_of_default_length(); let timestamp = common_utils::date_time::now(); - let algo = routing_algorithm::RoutingAlgorithm { - algorithm_id: algorithm_id.clone(), - profile_id: profile_id.clone(), - merchant_id, - name: SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM.to_string(), - description: None, - kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, - algorithm_data: serde_json::json!(default_success_based_routing_config), - created_at: timestamp, - modified_at: timestamp, - algorithm_for: common_enums::TransactionType::Payment, + let algo = match dynamic_routing_type { + routing_types::DynamicRoutingType::SuccessRateBasedRouting => { + let default_success_based_routing_config = + routing_types::SuccessBasedRoutingConfig::default(); + routing_algorithm::RoutingAlgorithm { + algorithm_id: algorithm_id.clone(), + profile_id: profile_id.clone(), + merchant_id, + name: SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM.to_string(), + description: None, + kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, + algorithm_data: serde_json::json!(default_success_based_routing_config), + created_at: timestamp, + modified_at: timestamp, + algorithm_for: common_enums::TransactionType::Payment, + } + } + routing_types::DynamicRoutingType::EliminationRouting => { + let default_elimination_routing_config = + routing_types::EliminationRoutingConfig::default(); + routing_algorithm::RoutingAlgorithm { + algorithm_id: algorithm_id.clone(), + profile_id: profile_id.clone(), + merchant_id, + name: ELIMINATION_BASED_DYNAMIC_ROUTING_ALGORITHM.to_string(), + description: None, + kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, + algorithm_data: serde_json::json!(default_elimination_routing_config), + created_at: timestamp, + modified_at: timestamp, + algorithm_for: common_enums::TransactionType::Payment, + } + } }; let record = db @@ -948,13 +1221,17 @@ pub async fn default_success_based_routing_setup( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to insert record in routing algorithm table")?; - success_based_dynamic_routing_algo.update_algorithm_id(algorithm_id, feature_to_enable); + dynamic_routing_algo_ref.update_algorithm_id( + algorithm_id, + feature_to_enable, + dynamic_routing_type, + ); update_business_profile_active_dynamic_algorithm_ref( db, key_manager_state, &key_store, business_profile, - success_based_dynamic_routing_algo, + dynamic_routing_algo_ref, ) .await?; @@ -1001,35 +1278,35 @@ impl SuccessBasedRoutingConfigParamsInterpolator { pub fn get_string_val( &self, - params: &Vec, + params: &Vec, ) -> String { let mut parts: Vec = Vec::new(); for param in params { let val = match param { - routing_types::SuccessBasedRoutingConfigParams::PaymentMethod => self + routing_types::DynamicRoutingConfigParams::PaymentMethod => self .payment_method .as_ref() .map_or(String::new(), |pm| pm.to_string()), - routing_types::SuccessBasedRoutingConfigParams::PaymentMethodType => self + routing_types::DynamicRoutingConfigParams::PaymentMethodType => self .payment_method_type .as_ref() .map_or(String::new(), |pmt| pmt.to_string()), - routing_types::SuccessBasedRoutingConfigParams::AuthenticationType => self + routing_types::DynamicRoutingConfigParams::AuthenticationType => self .authentication_type .as_ref() .map_or(String::new(), |at| at.to_string()), - routing_types::SuccessBasedRoutingConfigParams::Currency => self + routing_types::DynamicRoutingConfigParams::Currency => self .currency .as_ref() .map_or(String::new(), |cur| cur.to_string()), - routing_types::SuccessBasedRoutingConfigParams::Country => self + routing_types::DynamicRoutingConfigParams::Country => self .country .as_ref() .map_or(String::new(), |cn| cn.to_string()), - routing_types::SuccessBasedRoutingConfigParams::CardNetwork => { + routing_types::DynamicRoutingConfigParams::CardNetwork => { self.card_network.clone().unwrap_or_default() } - routing_types::SuccessBasedRoutingConfigParams::CardBin => { + routing_types::DynamicRoutingConfigParams::CardBin => { self.card_bin.clone().unwrap_or_default() } }; diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 1964c1cbfa5b..bb8d0d4f2bd6 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1755,9 +1755,9 @@ impl Profile { #[cfg(feature = "dynamic_routing")] { - route = - route.service( - web::scope("/{profile_id}/dynamic_routing").service( + route = route.service( + web::scope("/{profile_id}/dynamic_routing") + .service( web::scope("/success_based") .service( web::resource("/toggle") @@ -1770,8 +1770,14 @@ impl Profile { ) }), )), + ) + .service( + web::scope("/elimination").service( + web::resource("/toggle") + .route(web::post().to(routing::toggle_elimination_routing)), + ), ), - ); + ); } route = route.service( diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index cb7744f52009..d22c5e974906 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -1014,11 +1014,11 @@ pub async fn routing_update_default_config_for_profile( pub async fn toggle_success_based_routing( state: web::Data, req: HttpRequest, - query: web::Query, - path: web::Path, + query: web::Query, + path: web::Path, ) -> impl Responder { let flow = Flow::ToggleDynamicRouting; - let wrapper = routing_types::ToggleSuccessBasedRoutingWrapper { + let wrapper = routing_types::ToggleDynamicRoutingWrapper { feature_to_enable: query.into_inner().enable, profile_id: path.into_inner().profile_id, }; @@ -1029,14 +1029,15 @@ pub async fn toggle_success_based_routing( wrapper.clone(), |state, auth: auth::AuthenticationData, - wrapper: routing_types::ToggleSuccessBasedRoutingWrapper, + wrapper: routing_types::ToggleDynamicRoutingWrapper, _| { - routing::toggle_success_based_routing( + routing::toggle_specific_dynamic_routing( state, auth.merchant_account, auth.key_store, wrapper.feature_to_enable, wrapper.profile_id, + api_models::routing::DynamicRoutingType::SuccessRateBasedRouting, ) }, auth::auth_type( @@ -1085,3 +1086,46 @@ pub async fn success_based_routing_update_configs( )) .await } +#[cfg(all(feature = "olap", feature = "v1", feature = "dynamic_routing"))] +#[instrument(skip_all)] +pub async fn toggle_elimination_routing( + state: web::Data, + req: HttpRequest, + query: web::Query, + path: web::Path, +) -> impl Responder { + let flow = Flow::ToggleDynamicRouting; + let wrapper = routing_types::ToggleDynamicRoutingWrapper { + feature_to_enable: query.into_inner().enable, + profile_id: path.into_inner().profile_id, + }; + Box::pin(oss_api::server_wrap( + flow, + state, + &req, + wrapper.clone(), + |state, + auth: auth::AuthenticationData, + wrapper: routing_types::ToggleDynamicRoutingWrapper, + _| { + routing::toggle_specific_dynamic_routing( + state, + auth.merchant_account, + auth.key_store, + wrapper.feature_to_enable, + wrapper.profile_id, + api_models::routing::DynamicRoutingType::EliminationRouting, + ) + }, + auth::auth_type( + &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::JWTAuthProfileFromRoute { + profile_id: wrapper.profile_id, + required_permission: Permission::ProfileRoutingWrite, + }, + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/storage_impl/src/redis/cache.rs b/crates/storage_impl/src/redis/cache.rs index b32c3d220445..fff5435fc74d 100644 --- a/crates/storage_impl/src/redis/cache.rs +++ b/crates/storage_impl/src/redis/cache.rs @@ -72,7 +72,7 @@ pub static PM_FILTERS_CGRAPH_CACHE: Lazy = Lazy::new(|| { ) }); -/// Dynamic Algorithm Cache +/// Success based Dynamic Algorithm Cache pub static SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE: Lazy = Lazy::new(|| { Cache::new( "SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE", @@ -82,6 +82,16 @@ pub static SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE: Lazy = Lazy::new(|| { ) }); +/// Elimination based Dynamic Algorithm Cache +pub static ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE: Lazy = Lazy::new(|| { + Cache::new( + "ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE", + CACHE_TTL, + CACHE_TTI, + Some(MAX_CAPACITY), + ) +}); + /// Trait which defines the behaviour of types that's gonna be stored in Cache pub trait Cacheable: Any + Send + Sync + DynClone { fn as_any(&self) -> &dyn Any; @@ -102,6 +112,7 @@ pub enum CacheKind<'a> { Surcharge(Cow<'a, str>), CGraph(Cow<'a, str>), SuccessBasedDynamicRoutingCache(Cow<'a, str>), + EliminationBasedDynamicRoutingCache(Cow<'a, str>), PmFiltersCGraph(Cow<'a, str>), All(Cow<'a, str>), } diff --git a/crates/storage_impl/src/redis/pub_sub.rs b/crates/storage_impl/src/redis/pub_sub.rs index 6a2012f9b26f..42ad2ae0795a 100644 --- a/crates/storage_impl/src/redis/pub_sub.rs +++ b/crates/storage_impl/src/redis/pub_sub.rs @@ -6,8 +6,8 @@ use router_env::{logger, tracing::Instrument}; use crate::redis::cache::{ CacheKey, CacheKind, CacheRedact, ACCOUNTS_CACHE, CGRAPH_CACHE, CONFIG_CACHE, - DECISION_MANAGER_CACHE, PM_FILTERS_CGRAPH_CACHE, ROUTING_CACHE, - SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE, SURCHARGE_CACHE, + DECISION_MANAGER_CACHE, ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE, PM_FILTERS_CGRAPH_CACHE, + ROUTING_CACHE, SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE, SURCHARGE_CACHE, }; #[async_trait::async_trait] @@ -138,6 +138,15 @@ impl PubSubInterface for std::sync::Arc { .await; key } + CacheKind::EliminationBasedDynamicRoutingCache(key) => { + ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE + .remove(CacheKey { + key: key.to_string(), + prefix: message.tenant.clone(), + }) + .await; + key + } CacheKind::SuccessBasedDynamicRoutingCache(key) => { SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE .remove(CacheKey { @@ -205,6 +214,12 @@ impl PubSubInterface for std::sync::Arc { prefix: message.tenant.clone(), }) .await; + ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE + .remove(CacheKey { + key: key.to_string(), + prefix: message.tenant.clone(), + }) + .await; ROUTING_CACHE .remove(CacheKey { key: key.to_string(),