From 10562e60d390f34c3054a98624180cfd6a8841b9 Mon Sep 17 00:00:00 2001 From: Debarati Date: Thu, 21 Nov 2024 15:08:04 +0530 Subject: [PATCH 1/4] add zero auth mandates for novalnet --- .../src/connectors/novalnet.rs | 75 +++++- .../src/connectors/novalnet/transformers.rs | 239 ++++++++++++++++-- crates/hyperswitch_connectors/src/utils.rs | 18 ++ .../src/router_request_types.rs | 1 + .../router/src/core/payments/transformers.rs | 8 + 5 files changed, 325 insertions(+), 16 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet.rs b/crates/hyperswitch_connectors/src/connectors/novalnet.rs index 99da9c5caced..0a47c8ccfba8 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet.rs @@ -28,7 +28,7 @@ use hyperswitch_domain_models::{ router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData, }, }; use hyperswitch_interfaces::{ @@ -231,6 +231,79 @@ impl ConnectorIntegration impl ConnectorIntegration for Novalnet { + fn get_headers( + &self, + req: &SetupMandateRouterData, + 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: &SetupMandateRouterData, + connectors: &Connectors, + ) -> CustomResult { + let endpoint = self.base_url(connectors); + Ok(format!("{}/payment", endpoint)) + } + + fn get_request_body( + &self, + req: &SetupMandateRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = novalnet::NovalnetPaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &SetupMandateRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::SetupMandateType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::SetupMandateType::get_headers(self, req, connectors)?) + .set_body(types::SetupMandateType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &SetupMandateRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: novalnet::NovalnetPaymentsResponse = res + .response + .parse_struct("Novalnet 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 Novalnet { diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index 0a70393a13cd..d62d3d2a7554 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -17,7 +17,7 @@ use hyperswitch_domain_models::{ }, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData, }, }; use hyperswitch_interfaces::errors; @@ -28,8 +28,9 @@ use strum::Display; use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, utils::{ - self, ApplePay, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, - PaymentsCaptureRequestData, PaymentsSyncRequestData, RefundsRequestData, RouterData as _, + self, AddressDetailsData, ApplePay, PaymentsAuthorizeRequestData, + PaymentsCancelRequestData, PaymentsCaptureRequestData, PaymentsSetupMandateRequestData, + PaymentsSyncRequestData, RefundsRequestData, RouterData as _, }, }; @@ -47,6 +48,19 @@ impl From<(StringMinorUnit, T)> for NovalnetRouterData { } } +const MINIMAL_CUSTOMER_DATA_PASSED: i64 = 1; +const CREATE_TOKEN_REQUIRED: i8 = 1; + +const TEST_MODE_ENABLED: i8 = 1; +const TEST_MODE_DISABLED: i8 = 0; + +fn get_test_mode(item: Option) -> i8 { + match item { + Some(true) => TEST_MODE_ENABLED, + Some(false) | None => TEST_MODE_DISABLED, + } +} + #[derive(Debug, Copy, Serialize, Deserialize, Clone)] pub enum NovalNetPaymentTypes { CREDITCARD, @@ -117,11 +131,18 @@ pub struct NovalnetCustom { lang: String, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum NovalNetAmount { + StringMinor(StringMinorUnit), + Int(i64), +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct NovalnetPaymentsRequestTransaction { test_mode: i8, payment_type: NovalNetPaymentTypes, - amount: StringMinorUnit, + amount: NovalNetAmount, currency: common_enums::Currency, order_no: String, payment_data: Option, @@ -171,10 +192,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym enums::AuthenticationType::ThreeDs => Some(1), enums::AuthenticationType::NoThreeDs => None, }; - let test_mode = match item.router_data.test_mode { - Some(true) => 1, - Some(false) | None => 0, - }; + let test_mode = get_test_mode(item.router_data.test_mode); let billing = NovalnetPaymentsRequestBilling { house_no: item.router_data.get_optional_billing_line1(), @@ -197,7 +215,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym mobile: item.router_data.get_optional_billing_phone_number(), billing: Some(billing), // no_nc is used to indicate if minimal customer data is passed or not - no_nc: 1, + no_nc: MINIMAL_CUSTOMER_DATA_PASSED, }; let lang = item @@ -209,7 +227,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let hook_url = item.router_data.request.get_webhook_url()?; let return_url = item.router_data.request.get_router_return_url()?; let create_token = if item.router_data.request.is_mandate_payment() { - Some(1) + Some(CREATE_TOKEN_REQUIRED) } else { None }; @@ -234,7 +252,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let transaction = NovalnetPaymentsRequestTransaction { test_mode, payment_type: NovalNetPaymentTypes::CREDITCARD, - amount: item.amount.clone(), + amount: NovalNetAmount::StringMinor(item.amount.clone()), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), @@ -265,7 +283,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let transaction = NovalnetPaymentsRequestTransaction { test_mode, payment_type: NovalNetPaymentTypes::GOOGLEPAY, - amount: item.amount.clone(), + amount: NovalNetAmount::StringMinor(item.amount.clone()), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), @@ -287,7 +305,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let transaction = NovalnetPaymentsRequestTransaction { test_mode, payment_type: NovalNetPaymentTypes::APPLEPAY, - amount: item.amount.clone(), + amount: NovalNetAmount::StringMinor(item.amount.clone()), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), @@ -331,7 +349,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let transaction = NovalnetPaymentsRequestTransaction { test_mode, payment_type: NovalNetPaymentTypes::PAYPAL, - amount: item.amount.clone(), + amount: NovalNetAmount::StringMinor(item.amount.clone()), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), @@ -389,7 +407,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let transaction = NovalnetPaymentsRequestTransaction { test_mode, payment_type, - amount: item.amount.clone(), + amount: NovalNetAmount::StringMinor(item.amount.clone()), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), @@ -1380,3 +1398,194 @@ pub fn get_novalnet_dispute_status(status: WebhookEventType) -> WebhookDisputeSt pub fn option_to_result(opt: Option) -> Result { opt.ok_or(errors::ConnectorError::WebhookBodyDecodingFailed) } + +impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { + type Error = error_stack::Report; + fn try_from(item: &SetupMandateRouterData) -> Result { + let auth = NovalnetAuthType::try_from(&item.connector_auth_type)?; + + let merchant = NovalnetPaymentsRequestMerchant { + signature: auth.product_activation_key, + tariff: auth.tariff_id, + }; + + let enforce_3d = match item.auth_type { + enums::AuthenticationType::ThreeDs => Some(1), + enums::AuthenticationType::NoThreeDs => None, + }; + let test_mode = get_test_mode(item.test_mode); + let req_address = item.get_billing_address()?.to_owned(); + + let billing = NovalnetPaymentsRequestBilling { + house_no: item.get_optional_billing_line1(), + street: item.get_optional_billing_line2(), + city: item.get_optional_billing_city().map(Secret::new), + zip: item.get_optional_billing_zip(), + country_code: item.get_optional_billing_country(), + }; + + let customer = NovalnetPaymentsRequestCustomer { + first_name: req_address.get_first_name()?.clone(), + last_name: req_address.get_last_name()?.clone(), + email: item.request.get_email()?.clone(), + mobile: item.get_optional_billing_phone_number(), + billing: Some(billing), + // no_nc is used to indicate if minimal customer data is passed or not + no_nc: MINIMAL_CUSTOMER_DATA_PASSED, + }; + + let lang = item + .request + .get_optional_language_from_browser_info() + .unwrap_or(consts::DEFAULT_LOCALE.to_string().to_string()); + + let custom = NovalnetCustom { lang }; + let hook_url = item.request.get_webhook_url()?; + let return_url = item.request.get_return_url()?; + let create_token = Some(CREATE_TOKEN_REQUIRED); + + match item.request.payment_method_data { + PaymentMethodData::Card(ref req_card) => { + let novalnet_card = NovalNetPaymentData::Card(NovalnetCard { + card_number: req_card.card_number.clone(), + card_expiry_month: req_card.card_exp_month.clone(), + card_expiry_year: req_card.card_exp_year.clone(), + card_cvc: req_card.card_cvc.clone(), + card_holder: req_address.get_full_name()?.clone(), + }); + + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::CREDITCARD, + amount: NovalNetAmount::Int(0), + currency: item.request.currency, + order_no: item.connector_request_reference_id.clone(), + hook_url: Some(hook_url), + return_url: Some(return_url.clone()), + error_return_url: Some(return_url.clone()), + payment_data: Some(novalnet_card), + enforce_3d, + create_token, + }; + + Ok(Self { + merchant, + transaction, + customer, + custom, + }) + } + + PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { + WalletDataPaymentMethod::GooglePay(ref req_wallet) => { + let novalnet_google_pay: NovalNetPaymentData = + NovalNetPaymentData::GooglePay(NovalnetGooglePay { + wallet_data: Secret::new(req_wallet.tokenization_data.token.clone()), + }); + + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::GOOGLEPAY, + amount: NovalNetAmount::Int(0), + currency: item.request.currency, + order_no: item.connector_request_reference_id.clone(), + hook_url: Some(hook_url), + return_url: None, + error_return_url: None, + payment_data: Some(novalnet_google_pay), + enforce_3d, + create_token, + }; + + Ok(Self { + merchant, + transaction, + customer, + custom, + }) + } + WalletDataPaymentMethod::ApplePay(payment_method_data) => { + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::APPLEPAY, + amount: NovalNetAmount::Int(0), + currency: item.request.currency, + order_no: item.connector_request_reference_id.clone(), + hook_url: Some(hook_url), + return_url: None, + error_return_url: None, + payment_data: Some(NovalNetPaymentData::ApplePay(NovalnetApplePay { + wallet_data: Secret::new(payment_method_data.payment_data.clone()), + })), + enforce_3d: None, + create_token, + }; + + Ok(Self { + merchant, + transaction, + customer, + custom, + }) + } + WalletDataPaymentMethod::AliPayQr(_) + | WalletDataPaymentMethod::AliPayRedirect(_) + | WalletDataPaymentMethod::AliPayHkRedirect(_) + | WalletDataPaymentMethod::MomoRedirect(_) + | WalletDataPaymentMethod::KakaoPayRedirect(_) + | WalletDataPaymentMethod::GoPayRedirect(_) + | WalletDataPaymentMethod::GcashRedirect(_) + | WalletDataPaymentMethod::ApplePayRedirect(_) + | WalletDataPaymentMethod::ApplePayThirdPartySdk(_) + | WalletDataPaymentMethod::DanaRedirect {} + | WalletDataPaymentMethod::GooglePayRedirect(_) + | WalletDataPaymentMethod::GooglePayThirdPartySdk(_) + | WalletDataPaymentMethod::MbWayRedirect(_) + | WalletDataPaymentMethod::MobilePayRedirect(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("novalnet"), + ))? + } + WalletDataPaymentMethod::PaypalRedirect(_) => { + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::PAYPAL, + amount: NovalNetAmount::Int(0), + currency: item.request.currency, + order_no: item.connector_request_reference_id.clone(), + hook_url: Some(hook_url), + return_url: Some(return_url.clone()), + error_return_url: Some(return_url.clone()), + payment_data: None, + enforce_3d: None, + create_token, + }; + Ok(Self { + merchant, + transaction, + customer, + custom, + }) + } + WalletDataPaymentMethod::PaypalSdk(_) + | WalletDataPaymentMethod::Paze(_) + | WalletDataPaymentMethod::SamsungPay(_) + | WalletDataPaymentMethod::TwintRedirect {} + | WalletDataPaymentMethod::VippsRedirect {} + | WalletDataPaymentMethod::TouchNGoRedirect(_) + | WalletDataPaymentMethod::WeChatPayRedirect(_) + | WalletDataPaymentMethod::CashappQr(_) + | WalletDataPaymentMethod::SwishQr(_) + | WalletDataPaymentMethod::WeChatPayQr(_) + | WalletDataPaymentMethod::Mifinity(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("novalnet"), + ))? + } + }, + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("novalnet"), + ))?, + } + } +} diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 9061a758beab..169e5ae146e4 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1595,6 +1595,9 @@ pub trait PaymentsSetupMandateRequestData { fn get_email(&self) -> Result; fn get_router_return_url(&self) -> Result; fn is_card(&self) -> bool; + fn get_return_url(&self) -> Result; + fn get_webhook_url(&self) -> Result; + fn get_optional_language_from_browser_info(&self) -> Option; } impl PaymentsSetupMandateRequestData for SetupMandateRequestData { @@ -1614,6 +1617,21 @@ impl PaymentsSetupMandateRequestData for SetupMandateRequestData { fn is_card(&self) -> bool { matches!(self.payment_method_data, PaymentMethodData::Card(_)) } + fn get_return_url(&self) -> Result { + self.router_return_url + .clone() + .ok_or_else(missing_field_err("return_url")) + } + fn get_webhook_url(&self) -> Result { + self.webhook_url + .clone() + .ok_or_else(missing_field_err("webhook_url")) + } + fn get_optional_language_from_browser_info(&self) -> Option { + self.browser_info + .clone() + .and_then(|browser_info| browser_info.language) + } } pub trait PaymentMethodTokenizationRequestData { diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index aa9fb2b5f1f0..2517559e2fce 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -871,6 +871,7 @@ pub struct SetupMandateRequestData { pub off_session: Option, pub setup_mandate_details: Option, pub router_return_url: Option, + pub webhook_url: Option, pub browser_info: Option, pub email: Option, pub customer_name: Option>, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 79bcfb6a497d..20997f6edefa 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -3569,6 +3569,13 @@ impl TryFrom> for types::SetupMandateRequ .map(|customer| customer.clone().into_inner()) }); let amount = payment_data.payment_attempt.get_total_amount(); + + let webhook_url = Some(helpers::create_webhook_url( + router_base_url, + &attempt.merchant_id, + connector_name, + )); + Ok(Self { currency: payment_data.currency, confirm: true, @@ -3598,6 +3605,7 @@ impl TryFrom> for types::SetupMandateRequ ), metadata: payment_data.payment_intent.metadata.clone().map(Into::into), shipping_cost: payment_data.payment_intent.shipping_cost, + webhook_url, }) } } From 39a645b634de17c5fb07a1e13cf7a73f26a48e7a Mon Sep 17 00:00:00 2001 From: Debarati Date: Thu, 28 Nov 2024 13:52:06 +0530 Subject: [PATCH 2/4] modify create_webhook_url fn to take merchant_connector_id as input --- crates/router/src/core/payments.rs | 8 ++++- crates/router/src/core/payments/helpers.rs | 5 +-- .../router/src/core/payments/transformers.rs | 32 +++++++++++++++---- crates/router/src/core/utils.rs | 9 ++++-- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index cbf2cdea5384..c9085c29dcaf 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -6564,8 +6564,14 @@ pub async fn payment_external_authentication( &payment_attempt.clone(), payment_connector_name, )); + let merchant_connector_account_id = payment_attempt + .merchant_connector_id + .clone() + .get_required_value("merchant_connector_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Merchant connector id is not present in payment_attempt")?; let webhook_url = - helpers::create_webhook_url(&state.base_url, merchant_id, &authentication_connector); + helpers::create_webhook_url(&state.base_url, merchant_id, &merchant_connector_account_id); let authentication_details = business_profile .authentication_connector_details diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index b2c90e03b589..2d8b2c1b7e60 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1243,15 +1243,16 @@ pub fn create_authorize_url( pub fn create_webhook_url( router_base_url: &String, merchant_id: &id_type::MerchantId, - connector_name: impl std::fmt::Display, + merchant_connector_id: &id_type::MerchantConnectorAccountId, ) -> String { format!( "{}/webhooks/{}/{}", router_base_url, merchant_id.get_string_repr(), - connector_name + merchant_connector_id.get_string_repr(), ) } + pub fn create_complete_authorize_url( router_base_url: &String, payment_attempt: &PaymentAttempt, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 20997f6edefa..072564d466e5 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -224,7 +224,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - connector_id, + &merchant_connector_account.get_id(), )); let router_return_url = payment_data @@ -2745,11 +2745,17 @@ impl TryFrom> for types::PaymentsAuthoriz attempt, connector_name, )); - + let merchant_connector_account_id = payment_data + .payment_attempt + .merchant_connector_id + .clone() + .get_required_value("merchant_connector_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Merchant connector id is not present in payment_attempt")?; let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - connector_name, + &merchant_connector_account_id, )); let router_return_url = Some(helpers::create_redirect_url( router_base_url, @@ -3569,11 +3575,17 @@ impl TryFrom> for types::SetupMandateRequ .map(|customer| customer.clone().into_inner()) }); let amount = payment_data.payment_attempt.get_total_amount(); - + let merchant_connector_account_id = payment_data + .payment_attempt + .merchant_connector_id + .clone() + .get_required_value("merchant_connector_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Merchant connector id is not present in payment_attempt")?; let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - connector_name, + &merchant_connector_account_id, )); Ok(Self { @@ -3772,11 +3784,17 @@ impl TryFrom> for types::PaymentsPreProce .collect::, _>>() }) .transpose()?; - + let merchant_connector_account_id = payment_data + .payment_attempt + .merchant_connector_id + .clone() + .get_required_value("merchant_connector_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Merchant connector id is not present in payment_attempt")?; let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - connector_name, + &merchant_connector_account_id, )); let router_return_url = Some(helpers::create_redirect_url( router_base_url, diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index c441e32581e8..23eb269a02ca 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -285,11 +285,16 @@ pub async fn construct_refund_router_data<'a, F>( .payment_method .get_required_value("payment_method_type") .change_context(errors::ApiErrorResponse::InternalServerError)?; - + let merchant_connector_account_id = payment_attempt + .merchant_connector_id + .clone() + .get_required_value("merchant_connector_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Merchant connector id is not present in payment_attempt")?; let webhook_url = Some(helpers::create_webhook_url( &state.base_url.clone(), merchant_account.get_id(), - connector_id, + &merchant_connector_account_id, )); let test_mode: Option = merchant_connector_account.is_test_mode_on(); From c595f10dfe9820891247190df2049736d56be2ee Mon Sep 17 00:00:00 2001 From: Debarati Date: Wed, 4 Dec 2024 16:26:41 +0530 Subject: [PATCH 3/4] add cypress test trigger skip for no3ds zero auth mandate config --- crates/router/src/core/payments.rs | 18 +-- crates/router/src/core/payments/helpers.rs | 4 +- .../router/src/core/payments/transformers.rs | 14 +-- crates/router/src/core/relay/utils.rs | 2 +- crates/router/src/core/utils.rs | 2 +- .../cypress/e2e/PaymentUtils/Novalnet.js | 106 ++++++++++++++++++ 6 files changed, 127 insertions(+), 19 deletions(-) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index c9085c29dcaf..9632fc67abc3 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -6564,14 +6564,16 @@ pub async fn payment_external_authentication( &payment_attempt.clone(), payment_connector_name, )); - let merchant_connector_account_id = payment_attempt - .merchant_connector_id - .clone() - .get_required_value("merchant_connector_id") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Merchant connector id is not present in payment_attempt")?; - let webhook_url = - helpers::create_webhook_url(&state.base_url, merchant_id, &merchant_connector_account_id); + let merchant_connector_account_id = merchant_connector_account + .get_mca_id() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while finding mca_id from merchant_connector_account")?; + + let webhook_url = helpers::create_webhook_url( + &state.base_url, + merchant_id, + &merchant_connector_account_id.get_string_repr().to_string(), + ); let authentication_details = business_profile .authentication_connector_details diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 2d8b2c1b7e60..4b6952310f83 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1243,13 +1243,13 @@ pub fn create_authorize_url( pub fn create_webhook_url( router_base_url: &String, merchant_id: &id_type::MerchantId, - merchant_connector_id: &id_type::MerchantConnectorAccountId, + merchant_connector_id_or_connector_name: &String, ) -> String { format!( "{}/webhooks/{}/{}", router_base_url, merchant_id.get_string_repr(), - merchant_connector_id.get_string_repr(), + merchant_connector_id_or_connector_name, ) } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 072564d466e5..90c3775256bc 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -224,7 +224,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - &merchant_connector_account.get_id(), + &merchant_connector_account.get_id().get_string_repr().to_string(), )); let router_return_url = payment_data @@ -2747,11 +2747,11 @@ impl TryFrom> for types::PaymentsAuthoriz )); let merchant_connector_account_id = payment_data .payment_attempt - .merchant_connector_id .clone() - .get_required_value("merchant_connector_id") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Merchant connector id is not present in payment_attempt")?; + .merchant_connector_id + .map(|mca_id| mca_id.get_string_repr().to_string()) + .unwrap_or(connector_name.to_string()); + let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, @@ -3585,7 +3585,7 @@ impl TryFrom> for types::SetupMandateRequ let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - &merchant_connector_account_id, + &merchant_connector_account_id.get_string_repr().to_string(), )); Ok(Self { @@ -3794,7 +3794,7 @@ impl TryFrom> for types::PaymentsPreProce let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - &merchant_connector_account_id, + &merchant_connector_account_id.get_string_repr().to_string(), )); let router_return_url = Some(helpers::create_redirect_url( router_base_url, diff --git a/crates/router/src/core/relay/utils.rs b/crates/router/src/core/relay/utils.rs index 946f8df7d64e..d59e2b37ee61 100644 --- a/crates/router/src/core/relay/utils.rs +++ b/crates/router/src/core/relay/utils.rs @@ -32,7 +32,7 @@ pub async fn construct_relay_refund_router_data<'a, F>( let webhook_url = Some(payments::helpers::create_webhook_url( &state.base_url.clone(), merchant_id, - connector_name, + &connector_account.get_id().get_string_repr().to_string(), )); let supported_connector = &state diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 23eb269a02ca..adaf3030e8eb 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -294,7 +294,7 @@ pub async fn construct_refund_router_data<'a, F>( let webhook_url = Some(helpers::create_webhook_url( &state.base_url.clone(), merchant_account.get_id(), - &merchant_connector_account_id, + &merchant_connector_account_id.get_string_repr().to_string(), )); let test_mode: Option = merchant_connector_account.is_test_mode_on(); diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js index 0a8235141dcc..c34d3a3df65d 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js @@ -6,6 +6,31 @@ const successfulThreeDSTestCardDetails = { card_cvc: "123", }; +const successfulNo3DSCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "50", + 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: "EUR", + }, + }, +}; + export const connectorDetails = { card_pm: { PaymentIntent: { @@ -206,5 +231,86 @@ export const connectorDetails = { }, }, }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentIntentOffSession: { + Request: { + currency: "EUR", + amount: 6500, + authentication_type: "no_three_ds", + customer_acceptance: null, + setup_future_usage: "off_session", + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "EUR", + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthMandate: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "succeeded", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Novalnet is not implemented", + code: "IR_00", + }, + }, + }, + }, }, }; From 48a0de4739c0773ca81d3297aa49514d0859aee0 Mon Sep 17 00:00:00 2001 From: Debarati Date: Mon, 6 Jan 2025 13:40:33 +0530 Subject: [PATCH 4/4] add create_webhook_url fn changes --- crates/router/src/core/payments.rs | 11 +++--- crates/router/src/core/payments/helpers.rs | 4 +-- .../router/src/core/payments/transformers.rs | 34 +++++++++---------- crates/router/src/core/relay/utils.rs | 2 +- crates/router/src/core/utils.rs | 12 +++---- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 9632fc67abc3..1ba6209cf52f 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -6564,15 +6564,16 @@ pub async fn payment_external_authentication( &payment_attempt.clone(), payment_connector_name, )); - let merchant_connector_account_id = merchant_connector_account - .get_mca_id() - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Error while finding mca_id from merchant_connector_account")?; + let mca_id_option = merchant_connector_account.get_mca_id(); // Bind temporary value + let merchant_connector_account_id_or_connector_name = mca_id_option + .as_ref() + .map(|mca_id| mca_id.get_string_repr()) + .unwrap_or(&authentication_connector); let webhook_url = helpers::create_webhook_url( &state.base_url, merchant_id, - &merchant_connector_account_id.get_string_repr().to_string(), + merchant_connector_account_id_or_connector_name, ); let authentication_details = business_profile diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 4b6952310f83..e589ca637047 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1241,9 +1241,9 @@ pub fn create_authorize_url( } pub fn create_webhook_url( - router_base_url: &String, + router_base_url: &str, merchant_id: &id_type::MerchantId, - merchant_connector_id_or_connector_name: &String, + merchant_connector_id_or_connector_name: &str, ) -> String { format!( "{}/webhooks/{}/{}", diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 90c3775256bc..707d8b9d2be9 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -224,7 +224,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - &merchant_connector_account.get_id().get_string_repr().to_string(), + merchant_connector_account.get_id().get_string_repr(), )); let router_return_url = payment_data @@ -2745,17 +2745,17 @@ impl TryFrom> for types::PaymentsAuthoriz attempt, connector_name, )); - let merchant_connector_account_id = payment_data + let merchant_connector_account_id_or_connector_name = payment_data .payment_attempt - .clone() .merchant_connector_id - .map(|mca_id| mca_id.get_string_repr().to_string()) - .unwrap_or(connector_name.to_string()); + .as_ref() + .map(|mca_id| mca_id.get_string_repr()) + .unwrap_or(connector_name); let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - &merchant_connector_account_id, + merchant_connector_account_id_or_connector_name, )); let router_return_url = Some(helpers::create_redirect_url( router_base_url, @@ -3575,17 +3575,16 @@ impl TryFrom> for types::SetupMandateRequ .map(|customer| customer.clone().into_inner()) }); let amount = payment_data.payment_attempt.get_total_amount(); - let merchant_connector_account_id = payment_data + let merchant_connector_account_id_or_connector_name = payment_data .payment_attempt .merchant_connector_id - .clone() - .get_required_value("merchant_connector_id") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Merchant connector id is not present in payment_attempt")?; + .as_ref() + .map(|mca_id| mca_id.get_string_repr()) + .unwrap_or(connector_name); let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - &merchant_connector_account_id.get_string_repr().to_string(), + merchant_connector_account_id_or_connector_name, )); Ok(Self { @@ -3784,17 +3783,16 @@ impl TryFrom> for types::PaymentsPreProce .collect::, _>>() }) .transpose()?; - let merchant_connector_account_id = payment_data + let merchant_connector_account_id_or_connector_name = payment_data .payment_attempt .merchant_connector_id - .clone() - .get_required_value("merchant_connector_id") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Merchant connector id is not present in payment_attempt")?; + .as_ref() + .map(|mca_id| mca_id.get_string_repr()) + .unwrap_or(connector_name); let webhook_url = Some(helpers::create_webhook_url( router_base_url, &attempt.merchant_id, - &merchant_connector_account_id.get_string_repr().to_string(), + merchant_connector_account_id_or_connector_name, )); let router_return_url = Some(helpers::create_redirect_url( router_base_url, diff --git a/crates/router/src/core/relay/utils.rs b/crates/router/src/core/relay/utils.rs index d59e2b37ee61..25c0a65e06dc 100644 --- a/crates/router/src/core/relay/utils.rs +++ b/crates/router/src/core/relay/utils.rs @@ -32,7 +32,7 @@ pub async fn construct_relay_refund_router_data<'a, F>( let webhook_url = Some(payments::helpers::create_webhook_url( &state.base_url.clone(), merchant_id, - &connector_account.get_id().get_string_repr().to_string(), + connector_account.get_id().get_string_repr(), )); let supported_connector = &state diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index adaf3030e8eb..cb7e81811310 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -285,16 +285,16 @@ pub async fn construct_refund_router_data<'a, F>( .payment_method .get_required_value("payment_method_type") .change_context(errors::ApiErrorResponse::InternalServerError)?; - let merchant_connector_account_id = payment_attempt + let merchant_connector_account_id_or_connector_name = payment_attempt .merchant_connector_id - .clone() - .get_required_value("merchant_connector_id") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Merchant connector id is not present in payment_attempt")?; + .as_ref() + .map(|mca_id| mca_id.get_string_repr()) + .unwrap_or(connector_id); + let webhook_url = Some(helpers::create_webhook_url( &state.base_url.clone(), merchant_account.get_id(), - &merchant_connector_account_id.get_string_repr().to_string(), + merchant_connector_account_id_or_connector_name, )); let test_mode: Option = merchant_connector_account.is_test_mode_on();