From a4f96daa6dd5110b6008dcf6b9e7e7f4ecac2950 Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Fri, 13 Dec 2024 17:14:09 +0530 Subject: [PATCH 01/14] Added Deutsche Bank Card 3DS Integration --- crates/diesel_models/src/payment_attempt.rs | 4 + .../src/connectors/deutschebank.rs | 73 +++- .../connectors/deutschebank/transformers.rs | 361 ++++++++++++++---- .../src/router_response_types.rs | 19 + crates/router/src/services/api.rs | 44 +++ 5 files changed, 418 insertions(+), 83 deletions(-) diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 6ddc26d49bdb..124b25b818dd 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -3426,6 +3426,10 @@ pub enum RedirectForm { access_token: String, step_up_url: String, }, + DeutschebankThreeDSChallengeFlow { + acs_url: String, + creq: String, + }, Payme, Braintree { client_token: String, diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index 28bcd8afca6d..d22fef496e0c 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -52,7 +52,7 @@ use crate::{ types::ResponseRouterData, utils::{ self, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, - RefundsRequestData, + RefundsRequestData, RouterData as OtherRouterData }, }; @@ -320,22 +320,38 @@ impl ConnectorIntegration CustomResult { - if req.request.connector_mandate_id().is_none() { - Ok(format!( - "{}/services/v2.1/managedmandate", - self.base_url(connectors) - )) - } else { - let event_id = req.connector_request_reference_id.clone(); + + let event_id = req.connector_request_reference_id.clone(); let tx_action = if req.request.is_auto_capture()? { "authorization" } else { "preauthorization" }; + + if req.is_three_ds() && req.request.is_card() { Ok(format!( - "{}/services/v2.1/payment/event/{event_id}/directdebit/{tx_action}", + "{}/services/v2.1/headless3DSecure/event/{event_id}/{tx_action}/initialize", + self.base_url(connectors) + )) + } + else if !req.is_three_ds() && req.request.is_card() { + Err(errors::ConnectorError::NotSupported { + message: "No three ds for credit card transactions".to_owned(), + connector: "deutschebank", + } + .into()) + } + else if req.request.connector_mandate_id().is_none() { + Ok(format!( + "{}/services/v2.1/managedmandate", self.base_url(connectors) )) + } + else { + Ok(format!( + "{}/services/v2.1/payment/event/{event_id}/directdebit/{tx_action}", + self.base_url(connectors) + )) } } @@ -384,7 +400,19 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult { - if data.request.connector_mandate_id().is_none() { + if data.is_three_ds() && data.request.is_card() { + let response: deutschebank::DeutschebankThreeDSInitializeResponse = res + .response + .parse_struct("DeutschebankPaymentsAuthorizeResponse") + .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, + })} + else if data.request.connector_mandate_id().is_none() { let response: deutschebank::DeutschebankMandatePostResponse = res .response .parse_struct("DeutschebankMandatePostResponse") @@ -396,7 +424,8 @@ impl ConnectorIntegration, + postal_code: Secret, + city: String, + state: Secret, + country: String +} + + impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> for DeutschebankPaymentsRequest { @@ -162,6 +231,51 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> last_name: billing_address.get_last_name()?.clone(), })) } + PaymentMethodData::Card(ccard) => { + + if !item.router_data.is_three_ds() { + Err(errors::ConnectorError::NotSupported { + message: "No three ds for credit card transactions".to_owned(), + connector: "deutschebank", + } + .into()) + } + else { + let billing_address = item.router_data.get_billing_address()?; + Ok(Self::CreditCard(DeutschebankThreeDSInitializeRequest { + means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment { + credit_card: DeutschebankThreeDSInitializeRequestCreditCard { + number: ccard.card_number.get_card_no(), + expiry_date: DeutschebankThreeDSInitializeRequestCreditCardExpiry { + year: ccard.get_expiry_year_4_digit().expose(), + month: ccard.card_exp_month.expose(), + }, + code: ccard.card_cvc.expose(), + cardholder: ccard.card_holder_name.unwrap().expose(), + }}, + amount_total: DeutschebankThreeDSInitializeRequestAmountTotal { + amount: item.amount.get_amount_as_i64(), + currency: item.router_data.request.currency.to_string(), + }, + tds_20_data: DeutschebankThreeDSInitializeRequestTds20Data { + communication_data: DeutschebankThreeDSInitializeRequestCommunicationData { + method_notification_url: item.router_data.request.get_complete_authorize_url()?, + cres_notification_url: item.router_data.request.get_complete_authorize_url()?, + }, + customer_data: DeutschebankThreeDSInitializeRequestCustomerData { + billing_address: DeutschebankThreeDSInitializeRequestCustomerBillingData { + street: billing_address.get_line1()?.clone(), + postal_code: billing_address.get_zip()?.clone(), + city: billing_address.get_city()?.to_string(), + state: billing_address.get_state()?.clone(), + country: item.router_data.get_billing_country()?.to_string(), + }, + cardholder_email: item.router_data.request.get_email()?, + } + } + })) + } + } _ => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("deutschebank"), ) @@ -209,6 +323,83 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> } } + + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct DeutschebankThreeDSInitializeResponse { + outcome: DeutschebankThreeDSInitializeResponseOutcome, + challenge_required: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum DeutschebankThreeDSInitializeResponseOutcome { + Processed, + ChallengeRequired, + MethodRequired +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct DeutschebankThreeDSInitializeResponseChallengeRequired { + acs_url: String, + creq: String, +} + +impl + TryFrom< + ResponseRouterData< + Authorize, + DeutschebankThreeDSInitializeResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + Authorize, + DeutschebankThreeDSInitializeResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + status: if item.response.outcome == DeutschebankThreeDSInitializeResponseOutcome::Processed { + match item.data.request.is_auto_capture()? { + true => common_enums::AttemptStatus::Charged, + false => common_enums::AttemptStatus::Authorized, + } + } + else { + common_enums::AttemptStatus::AuthenticationPending + }, + response: + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: match item.response.challenge_required { + Some(challenge) => { + Box::new(Some( + RedirectForm::DeutschebankThreeDSChallengeFlow { + acs_url: challenge.clone().acs_url.clone(), + creq: challenge.clone().creq.clone(), + }, + )) + }, + None => 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 + }) + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum DeutschebankSEPAMandateStatus { @@ -450,78 +641,118 @@ pub struct DeutschebankDirectDebitRequest { mandate: DeutschebankMandate, } +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum DeutschebankCompleteAuthorizeRequest { + DeutschebankDirectDebitRequest(DeutschebankDirectDebitRequest), + DeutschebankThreeDSCompleteAuthorizeRequest(DeutschebankThreeDSCompleteAuthorizeRequest), +} + +#[derive(Debug, Serialize, PartialEq)] +pub struct DeutschebankThreeDSCompleteAuthorizeRequest { + cres: String, +} + impl TryFrom<&DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>> - for DeutschebankDirectDebitRequest + for DeutschebankCompleteAuthorizeRequest { type Error = error_stack::Report; fn try_from( item: &DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>, ) -> Result { - let account_holder = item.router_data.get_billing_address()?.get_full_name()?; - let redirect_response = item.router_data.request.redirect_response.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "redirect_response", - }, - )?; - let queries_params = redirect_response - .params - .map(|param| { - let mut queries = HashMap::::new(); - let values = param.peek().split('&').collect::>(); - for value in values { - let pair = value.split('=').collect::>(); - queries.insert( - pair.first() - .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? - .to_string(), - pair.get(1) - .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? - .to_string(), - ); - } - Ok::<_, errors::ConnectorError>(queries) - }) - .transpose()? - .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; - let reference = Secret::from( - queries_params - .get("reference") - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "reference", - })? - .to_owned(), - ); - let signed_on = queries_params - .get("signed_on") - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "signed_on", - })? - .to_owned(); match item.router_data.request.payment_method_data.clone() { Some(PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. })) => { - Ok(Self { - amount_total: DeutschebankAmount { - amount: item.amount, - currency: item.router_data.request.currency, - }, - means_of_payment: DeutschebankMeansOfPayment { - bank_account: DeutschebankBankAccount { - account_holder, - iban: Secret::from(iban.peek().replace(" ", "")), - }, - }, - mandate: { - DeutschebankMandate { - reference, - signed_on, - } - }, + + let account_holder = item.router_data.get_billing_address()?.get_full_name()?; + let redirect_response = item.router_data.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + let queries_params = redirect_response + .params + .map(|param| { + let mut queries = HashMap::::new(); + let values = param.peek().split('&').collect::>(); + for value in values { + let pair = value.split('=').collect::>(); + queries.insert( + pair.first() + . ok_or(errors::ConnectorError::ResponseDeserializationFailed)? + . to_string(), + pair.get(1) + . ok_or(errors::ConnectorError::ResponseDeserializationFailed)? + . to_string(), + ); + } + Ok::<_, errors::ConnectorError>(queries) }) - } - _ => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("deutschebank"), - ) + .transpose()? + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; + let reference = Secret::from( + queries_params + .get("reference") + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "reference", + })? + .to_owned(), + ); + let signed_on = queries_params + .get("signed_on") + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "signed_on", + })? + .to_owned(); + Ok(Self::DeutschebankDirectDebitRequest(DeutschebankDirectDebitRequest { + amount_total: DeutschebankAmount { + amount: item.amount, + currency: item.router_data.request.currency, + }, + means_of_payment: DeutschebankMeansOfPayment { + bank_account: DeutschebankBankAccount { + account_holder, + iban: Secret::from(iban.peek().replace(" ", "")), + }, + }, + mandate: { + DeutschebankMandate { + reference, + signed_on, + } + }, + })) + } + Some(PaymentMethodData::Card(_ccard)) => { + let redirect_response = item.router_data.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + + let response_payload = redirect_response.payload.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "payload", + }, + )?.expose(); + + let deserialized: HashMap = serde_json::from_value(response_payload) + .map_err(|_| errors::ConnectorError::ResponseDeserializationFailed)?; + + let cres = deserialized + .get("cres") + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "cres", + })? + .to_owned(); + + Ok(Self::DeutschebankThreeDSCompleteAuthorizeRequest(DeutschebankThreeDSCompleteAuthorizeRequest{ + cres, + })) + } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) .into()), } } diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index 89eb7f9b3cdb..1f991cd121f0 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -236,6 +236,10 @@ pub enum RedirectForm { access_token: String, step_up_url: String, }, + DeutschebankThreeDSChallengeFlow { + acs_url: String, + creq: String, + }, Payme, Braintree { client_token: String, @@ -313,6 +317,13 @@ impl From for diesel_models::payment_attempt::RedirectForm { access_token, step_up_url, }, + RedirectForm::DeutschebankThreeDSChallengeFlow { + acs_url, + creq + } => Self::DeutschebankThreeDSChallengeFlow { + acs_url, + creq + }, RedirectForm::Payme => Self::Payme, RedirectForm::Braintree { client_token, @@ -352,6 +363,7 @@ impl From for diesel_models::payment_attempt::RedirectForm { form_fields, collection_id, }, + } } } @@ -392,6 +404,13 @@ impl From for RedirectForm { access_token, step_up_url, }, + diesel_models::RedirectForm::DeutschebankThreeDSChallengeFlow { + acs_url, + creq + } => Self::DeutschebankThreeDSChallengeFlow { + acs_url, + creq + }, diesel_models::payment_attempt::RedirectForm::Payme => Self::Payme, diesel_models::payment_attempt::RedirectForm::Braintree { client_token, diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index f746aa28fd5f..2748c66a9509 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1531,6 +1531,49 @@ pub fn build_redirection_form( "))) }} } + RedirectForm::DeutschebankThreeDSChallengeFlow { + acs_url, + creq + } => { + maud::html! { + (maud::DOCTYPE) + html { + head { + meta name="viewport" content="width=device-width, initial-scale=1"; + } + + body style="background-color: #ffffff; padding: 20px; font-family: Arial, Helvetica, Sans-Serif;" { + div id="loader1" class="lottie" style="height: 150px; display: block; position: relative; margin-top: 150px; margin-left: auto; margin-right: auto;" { "" } + + (PreEscaped(r#""#)) + + (PreEscaped(r#" + + "#)) + + h3 style="text-align: center;" { "Please wait while we process your payment..." } + } + (PreEscaped(format!("
+ +
"))) + (PreEscaped(format!(""))) + } + } + } RedirectForm::Payme => { maud::html! { (maud::DOCTYPE) @@ -1950,6 +1993,7 @@ pub fn build_redirection_form( } } }, + } } From a45dfa0b6ed36fc5cbe60c47ade04ff3bff68936 Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Fri, 13 Dec 2024 18:21:43 +0530 Subject: [PATCH 02/14] feat(connector): [deutschebank] Implement Cards 3ds Flow --- .../src/connectors/deutschebank/transformers.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs index d40757b82e03..20cced16b3ab 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs @@ -150,15 +150,15 @@ pub struct DeutschebankThreeDSInitializeRequestMeansOfPayment { pub struct DeutschebankThreeDSInitializeRequestCreditCard { number: String, expiry_date: DeutschebankThreeDSInitializeRequestCreditCardExpiry, - code: String, - cardholder: String, + code: Secret, + cardholder: Secret, } #[derive(Debug, Serialize)] #[serde(rename_all = "snake_case")] pub struct DeutschebankThreeDSInitializeRequestCreditCardExpiry { - year: String, - month: String + year: Secret, + month: Secret, } #[derive(Debug, Serialize)] @@ -247,11 +247,11 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> credit_card: DeutschebankThreeDSInitializeRequestCreditCard { number: ccard.card_number.get_card_no(), expiry_date: DeutschebankThreeDSInitializeRequestCreditCardExpiry { - year: ccard.get_expiry_year_4_digit().expose(), - month: ccard.card_exp_month.expose(), + year: ccard.get_expiry_year_4_digit(), + month: ccard.card_exp_month, }, - code: ccard.card_cvc.expose(), - cardholder: ccard.card_holder_name.unwrap().expose(), + code: ccard.card_cvc, + cardholder: ccard.card_holder_name.unwrap(), }}, amount_total: DeutschebankThreeDSInitializeRequestAmountTotal { amount: item.amount.get_amount_as_i64(), From 0bfb6016f5b58f589e25e57c020b541f1f55dbfb Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Mon, 16 Dec 2024 13:44:56 +0530 Subject: [PATCH 03/14] feat(connector): [deutschebank] Implement Cards 3ds Flow --- .../src/connectors/deutschebank.rs | 2 +- .../connectors/deutschebank/transformers.rs | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index d22fef496e0c..133c704198e6 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -52,7 +52,7 @@ use crate::{ types::ResponseRouterData, utils::{ self, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, - RefundsRequestData, RouterData as OtherRouterData + RefundsRequestData, RouterData as ConnectorsRouterData }, }; diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs index 20cced16b3ab..f04395d988b3 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs @@ -867,6 +867,8 @@ impl #[serde(rename_all = "UPPERCASE")] pub enum DeutschebankTransactionKind { Directdebit, + #[serde(rename = "CREDITCARD_3DS20")] + Creditcard3ds20, } #[derive(Debug, Serialize, PartialEq)] @@ -882,7 +884,10 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsCaptureRouterData>> for Deutscheba ) -> Result { Ok(Self { changed_amount: item.amount, - kind: DeutschebankTransactionKind::Directdebit, + kind: match item.router_data.is_three_ds() { + true => DeutschebankTransactionKind::Creditcard3ds20, + false => DeutschebankTransactionKind::Directdebit, + }, }) } } @@ -1003,9 +1008,12 @@ pub struct DeutschebankReversalRequest { impl TryFrom<&PaymentsCancelRouterData> for DeutschebankReversalRequest { type Error = error_stack::Report; - fn try_from(_item: &PaymentsCancelRouterData) -> Result { + fn try_from(item: &PaymentsCancelRouterData) -> Result { Ok(Self { - kind: DeutschebankTransactionKind::Directdebit, + kind: match item.is_three_ds() { + true => DeutschebankTransactionKind::Creditcard3ds20, + false => DeutschebankTransactionKind::Directdebit, + }, }) } } @@ -1048,7 +1056,10 @@ impl TryFrom<&DeutschebankRouterData<&RefundsRouterData>> for Deutschebank fn try_from(item: &DeutschebankRouterData<&RefundsRouterData>) -> Result { Ok(Self { changed_amount: item.amount.to_owned(), - kind: DeutschebankTransactionKind::Directdebit, + kind: match item.router_data.is_three_ds() { + true => DeutschebankTransactionKind::Creditcard3ds20, + false => DeutschebankTransactionKind::Directdebit, + } }) } } From e65d93f399070877ba914052d57d2c4efbf455fa Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Mon, 16 Dec 2024 14:48:40 +0530 Subject: [PATCH 04/14] feat(connector): [deutschebank] Implement Cards 3ds Flow --- crates/diesel_models/src/payment_attempt.rs | 6 +- .../src/connectors/deutschebank.rs | 47 ++-- .../connectors/deutschebank/transformers.rs | 247 +++++++++--------- .../src/router_response_types.rs | 21 +- crates/router/src/services/api.rs | 6 +- 5 files changed, 155 insertions(+), 172 deletions(-) diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 124b25b818dd..acc88c24be0c 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -3426,9 +3426,9 @@ pub enum RedirectForm { access_token: String, step_up_url: String, }, - DeutschebankThreeDSChallengeFlow { - acs_url: String, - creq: String, + DeutschebankThreeDSChallengeFlow { + acs_url: String, + creq: String, }, Payme, Braintree { diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index 133c704198e6..5199703057b5 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -52,7 +52,7 @@ use crate::{ types::ResponseRouterData, utils::{ self, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, - RefundsRequestData, RouterData as ConnectorsRouterData + RefundsRequestData, RouterData as ConnectorsRouterData, }, }; @@ -320,38 +320,34 @@ impl ConnectorIntegration CustomResult { - let event_id = req.connector_request_reference_id.clone(); - let tx_action = if req.request.is_auto_capture()? { - "authorization" - } else { - "preauthorization" - }; - + let tx_action = if req.request.is_auto_capture()? { + "authorization" + } else { + "preauthorization" + }; + if req.is_three_ds() && req.request.is_card() { Ok(format!( "{}/services/v2.1/headless3DSecure/event/{event_id}/{tx_action}/initialize", - self.base_url(connectors) + self.base_url(connectors) )) - } - else if !req.is_three_ds() && req.request.is_card() { + } else if !req.is_three_ds() && req.request.is_card() { Err(errors::ConnectorError::NotSupported { message: "No three ds for credit card transactions".to_owned(), connector: "deutschebank", } .into()) - } - else if req.request.connector_mandate_id().is_none() { + } else if req.request.connector_mandate_id().is_none() { Ok(format!( "{}/services/v2.1/managedmandate", self.base_url(connectors) )) - } - else { + } else { Ok(format!( "{}/services/v2.1/payment/event/{event_id}/directdebit/{tx_action}", self.base_url(connectors) - )) + )) } } @@ -411,8 +407,8 @@ impl ConnectorIntegration), } #[derive(Debug, Serialize)] @@ -136,13 +137,13 @@ pub enum DeutschebankPaymentsRequest { pub struct DeutschebankThreeDSInitializeRequest { means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment, tds_20_data: DeutschebankThreeDSInitializeRequestTds20Data, - amount_total: DeutschebankThreeDSInitializeRequestAmountTotal + amount_total: DeutschebankThreeDSInitializeRequestAmountTotal, } #[derive(Debug, Serialize)] #[serde(rename_all = "snake_case")] pub struct DeutschebankThreeDSInitializeRequestMeansOfPayment { - credit_card: DeutschebankThreeDSInitializeRequestCreditCard + credit_card: DeutschebankThreeDSInitializeRequestCreditCard, } #[derive(Debug, Serialize)] @@ -165,7 +166,7 @@ pub struct DeutschebankThreeDSInitializeRequestCreditCardExpiry { #[serde(rename_all = "snake_case")] pub struct DeutschebankThreeDSInitializeRequestAmountTotal { amount: i64, - currency: String + currency: String, } #[derive(Debug, Serialize)] @@ -179,7 +180,7 @@ pub struct DeutschebankThreeDSInitializeRequestTds20Data { #[serde(rename_all = "snake_case")] pub struct DeutschebankThreeDSInitializeRequestCommunicationData { method_notification_url: String, - cres_notification_url: String + cres_notification_url: String, } #[derive(Debug, Serialize)] @@ -196,10 +197,9 @@ pub struct DeutschebankThreeDSInitializeRequestCustomerBillingData { postal_code: Secret, city: String, state: Secret, - country: String + country: String, } - impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> for DeutschebankPaymentsRequest { @@ -232,18 +232,22 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> })) } PaymentMethodData::Card(ccard) => { - if !item.router_data.is_three_ds() { Err(errors::ConnectorError::NotSupported { message: "No three ds for credit card transactions".to_owned(), connector: "deutschebank", } .into()) - } - else { + } else { let billing_address = item.router_data.get_billing_address()?; - Ok(Self::CreditCard(DeutschebankThreeDSInitializeRequest { - means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment { + let cardholder = + ccard.clone().card_holder_name.ok_or_else(|| { + errors::ConnectorError::MissingRequiredField { + field_name: "card_holder_name", + } + })?; + Ok(Self::CreditCard(Box::new(DeutschebankThreeDSInitializeRequest { + means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment { credit_card: DeutschebankThreeDSInitializeRequestCreditCard { number: ccard.card_number.get_card_no(), expiry_date: DeutschebankThreeDSInitializeRequestCreditCardExpiry { @@ -251,7 +255,7 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> month: ccard.card_exp_month, }, code: ccard.card_cvc, - cardholder: ccard.card_holder_name.unwrap(), + cardholder, }}, amount_total: DeutschebankThreeDSInitializeRequestAmountTotal { amount: item.amount.get_amount_as_i64(), @@ -263,18 +267,18 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> cres_notification_url: item.router_data.request.get_complete_authorize_url()?, }, customer_data: DeutschebankThreeDSInitializeRequestCustomerData { - billing_address: DeutschebankThreeDSInitializeRequestCustomerBillingData { - street: billing_address.get_line1()?.clone(), - postal_code: billing_address.get_zip()?.clone(), - city: billing_address.get_city()?.to_string(), - state: billing_address.get_state()?.clone(), - country: item.router_data.get_billing_country()?.to_string(), + billing_address: DeutschebankThreeDSInitializeRequestCustomerBillingData { + street: billing_address.get_line1()?.clone(), + postal_code: billing_address.get_zip()?.clone(), + city: billing_address.get_city()?.to_string(), + state: billing_address.get_state()?.clone(), + country: item.router_data.get_billing_country()?.to_string(), }, cardholder_email: item.router_data.request.get_email()?, } } - })) - } + }))) + } } _ => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("deutschebank"), @@ -323,8 +327,6 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> } } - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct DeutschebankThreeDSInitializeResponse { outcome: DeutschebankThreeDSInitializeResponseOutcome, @@ -336,7 +338,7 @@ pub struct DeutschebankThreeDSInitializeResponse { pub enum DeutschebankThreeDSInitializeResponseOutcome { Processed, ChallengeRequired, - MethodRequired + MethodRequired, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -365,28 +367,26 @@ impl >, ) -> Result { Ok(Self { - status: if item.response.outcome == DeutschebankThreeDSInitializeResponseOutcome::Processed { + status: if item.response.outcome + == DeutschebankThreeDSInitializeResponseOutcome::Processed + { match item.data.request.is_auto_capture()? { true => common_enums::AttemptStatus::Charged, false => common_enums::AttemptStatus::Authorized, } - } - else { + } else { common_enums::AttemptStatus::AuthenticationPending }, - response: - Ok(PaymentsResponseData::TransactionResponse { + response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::NoResponseId, redirection_data: match item.response.challenge_required { Some(challenge) => { - Box::new(Some( - RedirectForm::DeutschebankThreeDSChallengeFlow { - acs_url: challenge.clone().acs_url.clone(), - creq: challenge.clone().creq.clone(), - }, - )) - }, - None => Box::new(None) + Box::new(Some(RedirectForm::DeutschebankThreeDSChallengeFlow { + acs_url: challenge.clone().acs_url.clone(), + creq: challenge.clone().creq.clone(), + })) + } + None => Box::new(None), }, mandate_reference: Box::new(None), connector_metadata: None, @@ -660,99 +660,100 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>> fn try_from( item: &DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>, ) -> Result { - match item.router_data.request.payment_method_data.clone() { Some(PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. })) => { - - let account_holder = item.router_data.get_billing_address()?.get_full_name()?; - let redirect_response = item.router_data.request.redirect_response.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "redirect_response", - }, - )?; - let queries_params = redirect_response - .params - .map(|param| { - let mut queries = HashMap::::new(); - let values = param.peek().split('&').collect::>(); - for value in values { - let pair = value.split('=').collect::>(); - queries.insert( - pair.first() - . ok_or(errors::ConnectorError::ResponseDeserializationFailed)? - . to_string(), - pair.get(1) - . ok_or(errors::ConnectorError::ResponseDeserializationFailed)? - . to_string(), - ); - } - Ok::<_, errors::ConnectorError>(queries) - }) - .transpose()? - .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; - let reference = Secret::from( - queries_params - .get("reference") + let account_holder = item.router_data.get_billing_address()?.get_full_name()?; + let redirect_response = item.router_data.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + let queries_params = redirect_response + .params + .map(|param| { + let mut queries = HashMap::::new(); + let values = param.peek().split('&').collect::>(); + for value in values { + let pair = value.split('=').collect::>(); + queries.insert( + pair.first() + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? + .to_string(), + pair.get(1) + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? + .to_string(), + ); + } + Ok::<_, errors::ConnectorError>(queries) + }) + .transpose()? + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; + let reference = Secret::from( + queries_params + .get("reference") + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "reference", + })? + .to_owned(), + ); + let signed_on = queries_params + .get("signed_on") .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "reference", + field_name: "signed_on", })? - .to_owned(), - ); - let signed_on = queries_params - .get("signed_on") - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "signed_on", - })? - .to_owned(); - Ok(Self::DeutschebankDirectDebitRequest(DeutschebankDirectDebitRequest { - amount_total: DeutschebankAmount { - amount: item.amount, - currency: item.router_data.request.currency, - }, - means_of_payment: DeutschebankMeansOfPayment { - bank_account: DeutschebankBankAccount { - account_holder, - iban: Secret::from(iban.peek().replace(" ", "")), + .to_owned(); + Ok(Self::DeutschebankDirectDebitRequest( + DeutschebankDirectDebitRequest { + amount_total: DeutschebankAmount { + amount: item.amount, + currency: item.router_data.request.currency, + }, + means_of_payment: DeutschebankMeansOfPayment { + bank_account: DeutschebankBankAccount { + account_holder, + iban: Secret::from(iban.peek().replace(" ", "")), + }, + }, + mandate: { + DeutschebankMandate { + reference, + signed_on, + } + }, }, - }, - mandate: { - DeutschebankMandate { - reference, - signed_on, - } - }, - })) - } - Some(PaymentMethodData::Card(_ccard)) => { - let redirect_response = item.router_data.request.redirect_response.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "redirect_response", - }, - )?; + )) + } + Some(PaymentMethodData::Card(_ccard)) => { + let redirect_response = item.router_data.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; - let response_payload = redirect_response.payload.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "payload", - }, - )?.expose(); + let response_payload = redirect_response + .payload + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "payload", + })? + .expose(); - let deserialized: HashMap = serde_json::from_value(response_payload) - .map_err(|_| errors::ConnectorError::ResponseDeserializationFailed)?; + let deserialized: HashMap = + serde_json::from_value(response_payload) + .map_err(|_| errors::ConnectorError::ResponseDeserializationFailed)?; - let cres = deserialized - .get("cres") - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "cres", - })? - .to_owned(); + let cres = deserialized + .get("cres") + .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "cres" })? + .to_owned(); - Ok(Self::DeutschebankThreeDSCompleteAuthorizeRequest(DeutschebankThreeDSCompleteAuthorizeRequest{ - cres, - })) - } - _ => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("deutschebank"), - ) + Ok(Self::DeutschebankThreeDSCompleteAuthorizeRequest( + DeutschebankThreeDSCompleteAuthorizeRequest { cres }, + )) + } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) .into()), } } @@ -1059,7 +1060,7 @@ impl TryFrom<&DeutschebankRouterData<&RefundsRouterData>> for Deutschebank kind: match item.router_data.is_three_ds() { true => DeutschebankTransactionKind::Creditcard3ds20, false => DeutschebankTransactionKind::Directdebit, - } + }, }) } } diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index 1f991cd121f0..0af93152aae6 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -317,13 +317,9 @@ impl From for diesel_models::payment_attempt::RedirectForm { access_token, step_up_url, }, - RedirectForm::DeutschebankThreeDSChallengeFlow { - acs_url, - creq - } => Self::DeutschebankThreeDSChallengeFlow { - acs_url, - creq - }, + RedirectForm::DeutschebankThreeDSChallengeFlow { acs_url, creq } => { + Self::DeutschebankThreeDSChallengeFlow { acs_url, creq } + } RedirectForm::Payme => Self::Payme, RedirectForm::Braintree { client_token, @@ -363,7 +359,6 @@ impl From for diesel_models::payment_attempt::RedirectForm { form_fields, collection_id, }, - } } } @@ -404,13 +399,9 @@ impl From for RedirectForm { access_token, step_up_url, }, - diesel_models::RedirectForm::DeutschebankThreeDSChallengeFlow { - acs_url, - creq - } => Self::DeutschebankThreeDSChallengeFlow { - acs_url, - creq - }, + diesel_models::RedirectForm::DeutschebankThreeDSChallengeFlow { acs_url, creq } => { + Self::DeutschebankThreeDSChallengeFlow { acs_url, creq } + } diesel_models::payment_attempt::RedirectForm::Payme => Self::Payme, diesel_models::payment_attempt::RedirectForm::Braintree { client_token, diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 2748c66a9509..97461449a3dd 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1531,10 +1531,7 @@ pub fn build_redirection_form( "))) }} } - RedirectForm::DeutschebankThreeDSChallengeFlow { - acs_url, - creq - } => { + RedirectForm::DeutschebankThreeDSChallengeFlow { acs_url, creq } => { maud::html! { (maud::DOCTYPE) html { @@ -1993,7 +1990,6 @@ pub fn build_redirection_form( } } }, - } } From 70da2d0cee7e9775094f7e57eea67db70bba89aa Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Mon, 16 Dec 2024 15:28:55 +0530 Subject: [PATCH 05/14] feat(connector): [deutschebank] Implement Cards 3ds Flow --- crates/hyperswitch_connectors/src/connectors/deutschebank.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index 5199703057b5..d2e2b8addb6d 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -52,7 +52,7 @@ use crate::{ types::ResponseRouterData, utils::{ self, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, - RefundsRequestData, RouterData as ConnectorsRouterData, + RefundsRequestData, RouterData as ConnectorRouterData, }, }; From 7bcd4a5c4685cc8e9d665ebb50d185b1bcfeefef Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Wed, 18 Dec 2024 09:53:54 +0530 Subject: [PATCH 06/14] feat(connector): [deutschebank] Implement Cards 3ds Flow --- .../connectors/deutschebank/transformers.rs | 81 ++++--- .../cypress/e2e/PaymentUtils/Deutschebank.js | 218 ++++++++++++++++++ .../cypress/e2e/PaymentUtils/Utils.js | 2 + 3 files changed, 274 insertions(+), 27 deletions(-) create mode 100644 cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs index 3d52249634f1..d4c0a23aef68 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs @@ -366,37 +366,64 @@ impl PaymentsResponseData, >, ) -> Result { - Ok(Self { - status: if item.response.outcome - == DeutschebankThreeDSInitializeResponseOutcome::Processed - { - match item.data.request.is_auto_capture()? { + match item.response.outcome { + DeutschebankThreeDSInitializeResponseOutcome::Processed => Ok(Self { + status: match item.data.request.is_auto_capture()? { true => common_enums::AttemptStatus::Charged, false => common_enums::AttemptStatus::Authorized, - } - } else { - common_enums::AttemptStatus::AuthenticationPending - }, - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::NoResponseId, - redirection_data: match item.response.challenge_required { - Some(challenge) => { - Box::new(Some(RedirectForm::DeutschebankThreeDSChallengeFlow { - acs_url: challenge.clone().acs_url.clone(), - creq: challenge.clone().creq.clone(), - })) - } - None => 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, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + 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 }), - ..item.data - }) + DeutschebankThreeDSInitializeResponseOutcome::ChallengeRequired => { + match item.response.challenge_required { + Some(challenge) => Ok(Self { + status: common_enums::AttemptStatus::AuthenticationPending, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(Some( + RedirectForm::DeutschebankThreeDSChallengeFlow { + acs_url: challenge.acs_url, + creq: challenge.creq, + }, + )), + 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 + }), + None => Err(errors::ConnectorError::MissingRequiredField { + field_name: "challenge_required", + } + .into()), + } + } + DeutschebankThreeDSInitializeResponseOutcome::MethodRequired => Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(ErrorResponse { + code: "500".to_owned(), + message: "Method_Required Flow not supported".to_owned(), + reason: None, + status_code: 500, + attempt_status: None, + connector_transaction_id: None, + }), + ..item.data + }), + } } } diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js b/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js new file mode 100644 index 000000000000..fbfb7d9b894d --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js @@ -0,0 +1,218 @@ +const successful3DSCardDetails = { + card_number: "4761739090000088", + card_exp_month: "12", + card_exp_year: "2034", + card_holder_name: "John Doe", + card_cvc: "123", + }; + + export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + "3DSManualCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + currency: "USD", + customer_acceptance: { + acceptance_type: "online" + }, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + + "3DSAutoCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + currency: "USD", + customer_acceptance: { + acceptance_type: "online" + }, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + No3DSManualCapture: { + Request: { + currency: "USD", + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + } + }, + }, + }, + No3DSAutoCapture: { + Request: { + currency: "USD", + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + } + }, + }, + }, + Capture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + 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: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PartialRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + }, + }; \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js index e432c37717bf..884f7bccf1c2 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js @@ -25,6 +25,7 @@ import { connectorDetails as stripeConnectorDetails } from "./Stripe.js"; import { connectorDetails as trustpayConnectorDetails } from "./Trustpay.js"; import { connectorDetails as wellsfargoConnectorDetails } from "./WellsFargo.js"; import { connectorDetails as worldpayConnectorDetails } from "./WorldPay.js"; +import { connectorDetails as deutschebankConnectorDetails } from "./Deutschebank.js"; const connectorDetails = { adyen: adyenConnectorDetails, @@ -33,6 +34,7 @@ const connectorDetails = { checkout: checkoutConnectorDetails, commons: CommonConnectorDetails, cybersource: cybersourceConnectorDetails, + deutschebank: deutschebankConnectorDetails, fiservemea: fiservemeaConnectorDetails, iatapay: iatapayConnectorDetails, itaubank: itaubankConnectorDetails, From ff8f7ee73ec1976fe165a2f4ea78fbd4b55ec423 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 04:24:44 +0000 Subject: [PATCH 07/14] chore(cypress): run formatter and address lints --- .../cypress/e2e/PaymentUtils/Deutschebank.js | 368 +++++++++--------- 1 file changed, 184 insertions(+), 184 deletions(-) diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js b/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js index fbfb7d9b894d..60f5f6666e2a 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js @@ -1,218 +1,218 @@ const successful3DSCardDetails = { - card_number: "4761739090000088", - card_exp_month: "12", - card_exp_year: "2034", - card_holder_name: "John Doe", - card_cvc: "123", - }; - - export const connectorDetails = { - card_pm: { - PaymentIntent: { - Request: { - currency: "USD", - customer_acceptance: null, - }, - Response: { - status: 200, - body: { - status: "requires_payment_method", - }, - }, + card_number: "4761739090000088", + card_exp_month: "12", + card_exp_year: "2034", + card_holder_name: "John Doe", + card_cvc: "123", +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + customer_acceptance: null, }, - "3DSManualCapture": { - Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - currency: "USD", - customer_acceptance: { - acceptance_type: "online" - }, - setup_future_usage: "on_session", - }, - Response: { - status: 200, - body: { - status: "requires_customer_action", - }, + Response: { + status: 200, + body: { + status: "requires_payment_method", }, }, - - "3DSAutoCapture": { - Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - currency: "USD", - customer_acceptance: { - acceptance_type: "online" - }, - setup_future_usage: "on_session", + }, + "3DSManualCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, }, - Response: { - status: 200, - body: { - status: "requires_customer_action", - }, + currency: "USD", + customer_acceptance: { + acceptance_type: "online", }, + setup_future_usage: "on_session", }, - No3DSManualCapture: { - Request: { - currency: "USD", - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - customer_acceptance: null, - setup_future_usage: "on_session", - }, - Response: { - status: 400, - body: { - error: { - type: "invalid_request", - message: "Payment method type not supported", - code: "IR_19", - } - }, + Response: { + status: 200, + body: { + status: "requires_customer_action", }, }, - No3DSAutoCapture: { - Request: { - currency: "USD", - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - customer_acceptance: null, - setup_future_usage: "on_session", - }, - Response: { - status: 400, - body: { - error: { - type: "invalid_request", - message: "Payment method type not supported", - code: "IR_19", - } - }, - }, - }, - Capture: { - Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - customer_acceptance: null, - }, - Response: { - status: 200, - body: { - status: "succeeded", - amount: 6500, - amount_capturable: 0, - amount_received: 6500, - }, + }, + + "3DSAutoCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + currency: "USD", + customer_acceptance: { + acceptance_type: "online", + }, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", }, }, - PartialCapture: { - Request: {}, - Response: { - status: 200, - body: { - status: "partially_captured", - amount: 6500, - amount_capturable: 0, - amount_received: 100, + }, + No3DSManualCapture: { + Request: { + currency: "USD", + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", }, }, }, - Refund: { - Configs: { - TRIGGER_SKIP: true, - }, - Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, + }, + No3DSAutoCapture: { + Request: { + currency: "USD", + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", }, - customer_acceptance: null, }, - Response: { - status: 200, - body: { - status: "succeeded", - }, + }, + }, + Capture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, }, + customer_acceptance: null, }, - manualPaymentRefund: { - Configs: { - TRIGGER_SKIP: true, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 6500, + amount_capturable: 0, + amount_received: 6500, }, - Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - customer_acceptance: null, + }, + }, + PartialCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 6500, + amount_capturable: 0, + amount_received: 100, }, - Response: { - status: 200, - body: { - status: "succeeded", - }, + }, + }, + Refund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, }, + customer_acceptance: null, }, - manualPaymentPartialRefund: { - Configs: { - TRIGGER_SKIP: true, + Response: { + status: 200, + body: { + status: "succeeded", }, - Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - customer_acceptance: null, + }, + }, + manualPaymentRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, }, - Response: { - status: 200, - body: { - status: "succeeded", - }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", }, }, - PartialRefund: { - Configs: { - TRIGGER_SKIP: true, + }, + manualPaymentPartialRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, }, - Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - customer_acceptance: null, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", }, - Response: { - status: 200, - body: { - status: "succeeded", - }, + }, + }, + PartialRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successful3DSCardDetails, }, + customer_acceptance: null, }, - SyncRefund: { - Configs: { - TRIGGER_SKIP: true, + Response: { + status: 200, + body: { + status: "succeeded", }, - Response: { - status: 200, - body: { - status: "succeeded", - }, + }, + }, + SyncRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Response: { + status: 200, + body: { + status: "succeeded", }, }, }, - }; \ No newline at end of file + }, +}; From e49115de12fbbac8252225363c900f728d80b296 Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Wed, 18 Dec 2024 16:44:06 +0530 Subject: [PATCH 08/14] chore: Resolved PR comments --- .../src/connectors/deutschebank.rs | 7 +- .../connectors/deutschebank/transformers.rs | 317 ++++++++++-------- .../cypress/e2e/PaymentUtils/Deutschebank.js | 8 +- 3 files changed, 175 insertions(+), 157 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index d2e2b8addb6d..10ab2f9f4812 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -4,7 +4,7 @@ use std::time::SystemTime; use actix_web::http::header::Date; use base64::Engine; -use common_enums::enums; +use common_enums::{enums, PaymentMethod}; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, @@ -124,7 +124,7 @@ impl ConnectorCommon for Deutschebank { } fn get_currency_unit(&self) -> api::CurrencyUnit { - api::CurrencyUnit::Base + api::CurrencyUnit::Minor } fn common_get_content_type(&self) -> &'static str { @@ -471,7 +471,7 @@ impl ConnectorIntegration, cardholder: Secret, @@ -165,7 +166,7 @@ pub struct DeutschebankThreeDSInitializeRequestCreditCardExpiry { #[derive(Debug, Serialize)] #[serde(rename_all = "snake_case")] pub struct DeutschebankThreeDSInitializeRequestAmountTotal { - amount: i64, + amount: MinorUnit, currency: String, } @@ -217,11 +218,9 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> None => { // To facilitate one-off payments via SEPA with Deutsche Bank, we are considering not storing the connector mandate ID in our system if future usage is on-session. // We will only check for customer acceptance to make a one-off payment. we will be storing the connector mandate details only when setup future usage is off-session. - if item.router_data.request.customer_acceptance.is_some() { - match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { - iban, .. - }) => { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. }) => { + if item.router_data.request.customer_acceptance.is_some() { let billing_address = item.router_data.get_billing_address()?; Ok(Self::MandatePost(DeutschebankMandatePostRequest { approval_by: DeutschebankSEPAApproval::Click, @@ -230,26 +229,31 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> first_name: billing_address.get_first_name()?.clone(), last_name: billing_address.get_last_name()?.clone(), })) + } else { + Err(errors::ConnectorError::MissingRequiredField { + field_name: "customer_acceptance", + } + .into()) } - PaymentMethodData::Card(ccard) => { - if !item.router_data.is_three_ds() { - Err(errors::ConnectorError::NotSupported { - message: "No three ds for credit card transactions".to_owned(), - connector: "deutschebank", + } + PaymentMethodData::Card(ccard) => { + if !item.router_data.clone().is_three_ds() { + Err(errors::ConnectorError::NotSupported { + message: "No three ds for credit card transactions".to_owned(), + connector: "deutschebank", + } + .into()) + } else { + let billing_address = item.router_data.get_billing_address()?; + let cardholder = ccard.clone().card_holder_name.ok_or_else(|| { + errors::ConnectorError::MissingRequiredField { + field_name: "card_holder_name", } - .into()) - } else { - let billing_address = item.router_data.get_billing_address()?; - let cardholder = - ccard.clone().card_holder_name.ok_or_else(|| { - errors::ConnectorError::MissingRequiredField { - field_name: "card_holder_name", - } - })?; - Ok(Self::CreditCard(Box::new(DeutschebankThreeDSInitializeRequest { + })?; + Ok(Self::CreditCard(Box::new(DeutschebankThreeDSInitializeRequest { means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment { credit_card: DeutschebankThreeDSInitializeRequestCreditCard { - number: ccard.card_number.get_card_no(), + number: ccard.clone().card_number, expiry_date: DeutschebankThreeDSInitializeRequestCreditCardExpiry { year: ccard.get_expiry_year_4_digit(), month: ccard.card_exp_month, @@ -258,8 +262,8 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> cardholder, }}, amount_total: DeutschebankThreeDSInitializeRequestAmountTotal { - amount: item.amount.get_amount_as_i64(), - currency: item.router_data.request.currency.to_string(), + amount: item.amount, + currency: item.router_data.request.currency.clone().to_string(), }, tds_20_data: DeutschebankThreeDSInitializeRequestTds20Data { communication_data: DeutschebankThreeDSInitializeRequestCommunicationData { @@ -278,18 +282,12 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> } } }))) - } } - _ => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("deutschebank"), - ) - .into()), } - } else { - Err(errors::ConnectorError::MissingRequiredField { - field_name: "customer_acceptance", - } - .into()) + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) + .into()), } } Some(api_models::payments::MandateReferenceId::ConnectorMandateId(mandate_data)) => { @@ -331,6 +329,14 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> pub struct DeutschebankThreeDSInitializeResponse { outcome: DeutschebankThreeDSInitializeResponseOutcome, challenge_required: Option, + processed: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct DeutschebankThreeDSInitializeResponseProcessed { + rc: String, + message: String, + tx_id: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -367,23 +373,33 @@ impl >, ) -> Result { match item.response.outcome { - DeutschebankThreeDSInitializeResponseOutcome::Processed => Ok(Self { - status: match item.data.request.is_auto_capture()? { - true => common_enums::AttemptStatus::Charged, - false => common_enums::AttemptStatus::Authorized, - }, - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::NoResponseId, - 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 - }), + DeutschebankThreeDSInitializeResponseOutcome::Processed => { + match item.response.processed { + Some(processed) => Ok(Self { + status: match item.data.request.is_auto_capture()? { + true => common_enums::AttemptStatus::Charged, + false => common_enums::AttemptStatus::Authorized, + }, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + processed.tx_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(processed.tx_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }), + None => Err(errors::ConnectorError::UnexpectedResponseError( + bytes::Bytes::from("processed".to_owned()), + ) + .into()), + } + } DeutschebankThreeDSInitializeResponseOutcome::ChallengeRequired => { match item.response.challenge_required { Some(challenge) => Ok(Self { @@ -405,9 +421,9 @@ impl }), ..item.data }), - None => Err(errors::ConnectorError::MissingRequiredField { - field_name: "challenge_required", - } + None => Err(errors::ConnectorError::UnexpectedResponseError( + bytes::Bytes::from("challenge_required".to_owned()), + ) .into()), } } @@ -416,7 +432,7 @@ impl response: Err(ErrorResponse { code: "500".to_owned(), message: "Method_Required Flow not supported".to_owned(), - reason: None, + reason: Some("Method_Required Flow is not currently supported".to_owned()), status_code: 500, attempt_status: None, connector_transaction_id: None, @@ -687,101 +703,108 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>> fn try_from( item: &DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>, ) -> Result { - match item.router_data.request.payment_method_data.clone() { - Some(PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. })) => { - let account_holder = item.router_data.get_billing_address()?.get_full_name()?; - let redirect_response = item.router_data.request.redirect_response.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "redirect_response", - }, - )?; - let queries_params = redirect_response - .params - .map(|param| { - let mut queries = HashMap::::new(); - let values = param.peek().split('&').collect::>(); - for value in values { - let pair = value.split('=').collect::>(); - queries.insert( - pair.first() - .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? - .to_string(), - pair.get(1) - .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? - .to_string(), - ); - } - Ok::<_, errors::ConnectorError>(queries) - }) - .transpose()? - .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; - let reference = Secret::from( - queries_params - .get("reference") + if matches!(item.router_data.payment_method, PaymentMethod::Card) { + let redirect_response = item.router_data.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + + let response_payload = redirect_response + .payload + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "payload", + })? + .expose(); + + let deserialized: HashMap = serde_json::from_value(response_payload) + .map_err(|_| errors::ConnectorError::ResponseDeserializationFailed)?; + + let cres = deserialized + .get("cres") + .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "cres" })? + .to_owned(); + + Ok(Self::DeutschebankThreeDSCompleteAuthorizeRequest( + DeutschebankThreeDSCompleteAuthorizeRequest { cres }, + )) + } else { + match item.router_data.request.payment_method_data.clone() { + Some(PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { + iban, .. + })) => { + let account_holder = item.router_data.get_billing_address()?.get_full_name()?; + let redirect_response = + item.router_data.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + let queries_params = redirect_response + .params + .map(|param| { + let mut queries = HashMap::::new(); + let values = param.peek().split('&').collect::>(); + for value in values { + let pair = value.split('=').collect::>(); + queries.insert( + pair.first() + .ok_or( + errors::ConnectorError::ResponseDeserializationFailed, + )? + .to_string(), + pair.get(1) + .ok_or( + errors::ConnectorError::ResponseDeserializationFailed, + )? + .to_string(), + ); + } + Ok::<_, errors::ConnectorError>(queries) + }) + .transpose()? + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; + let reference = Secret::from( + queries_params + .get("reference") + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "reference", + })? + .to_owned(), + ); + let signed_on = queries_params + .get("signed_on") .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "reference", + field_name: "signed_on", })? - .to_owned(), - ); - let signed_on = queries_params - .get("signed_on") - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "signed_on", - })? - .to_owned(); - Ok(Self::DeutschebankDirectDebitRequest( - DeutschebankDirectDebitRequest { - amount_total: DeutschebankAmount { - amount: item.amount, - currency: item.router_data.request.currency, - }, - means_of_payment: DeutschebankMeansOfPayment { - bank_account: DeutschebankBankAccount { - account_holder, - iban: Secret::from(iban.peek().replace(" ", "")), + .to_owned(); + Ok(Self::DeutschebankDirectDebitRequest( + DeutschebankDirectDebitRequest { + amount_total: DeutschebankAmount { + amount: item.amount, + currency: item.router_data.request.currency, + }, + means_of_payment: DeutschebankMeansOfPayment { + bank_account: DeutschebankBankAccount { + account_holder, + iban: Secret::from(iban.peek().replace(" ", "")), + }, + }, + mandate: { + DeutschebankMandate { + reference, + signed_on, + } }, }, - mandate: { - DeutschebankMandate { - reference, - signed_on, - } - }, - }, - )) - } - Some(PaymentMethodData::Card(_ccard)) => { - let redirect_response = item.router_data.request.redirect_response.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "redirect_response", - }, - )?; - - let response_payload = redirect_response - .payload - .clone() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "payload", - })? - .expose(); - - let deserialized: HashMap = - serde_json::from_value(response_payload) - .map_err(|_| errors::ConnectorError::ResponseDeserializationFailed)?; - - let cres = deserialized - .get("cres") - .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "cres" })? - .to_owned(); - - Ok(Self::DeutschebankThreeDSCompleteAuthorizeRequest( - DeutschebankThreeDSCompleteAuthorizeRequest { cres }, - )) + )) + } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) + .into()), } - _ => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("deutschebank"), - ) - .into()), } } } diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js b/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js index 60f5f6666e2a..591e6c8869e2 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Deutschebank.js @@ -27,9 +27,7 @@ export const connectorDetails = { card: successful3DSCardDetails, }, currency: "USD", - customer_acceptance: { - acceptance_type: "online", - }, + customer_acceptance: null, setup_future_usage: "on_session", }, Response: { @@ -47,9 +45,7 @@ export const connectorDetails = { card: successful3DSCardDetails, }, currency: "USD", - customer_acceptance: { - acceptance_type: "online", - }, + customer_acceptance: null, setup_future_usage: "on_session", }, Response: { From 2b61a02b8be5ec24e6a5e693be786d52a129c2d5 Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Wed, 18 Dec 2024 18:13:54 +0530 Subject: [PATCH 09/14] chore: Resolved PR comments --- .../src/connectors/deutschebank.rs | 2 +- .../src/connectors/deutschebank/transformers.rs | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index 10ab2f9f4812..d490c4bc5f70 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -334,7 +334,7 @@ impl ConnectorIntegration> PaymentMethodData::Card(ccard) => { if !item.router_data.clone().is_three_ds() { Err(errors::ConnectorError::NotSupported { - message: "No three ds for credit card transactions".to_owned(), + message: "Non-ThreeDs".to_owned(), connector: "deutschebank", } .into()) @@ -376,9 +376,13 @@ impl DeutschebankThreeDSInitializeResponseOutcome::Processed => { match item.response.processed { Some(processed) => Ok(Self { - status: match item.data.request.is_auto_capture()? { - true => common_enums::AttemptStatus::Charged, - false => common_enums::AttemptStatus::Authorized, + status: if is_response_success(&processed.rc) { + match item.data.request.is_auto_capture()? { + true => common_enums::AttemptStatus::Charged, + false => common_enums::AttemptStatus::Authorized, + } + } else { + common_enums::AttemptStatus::AuthenticationFailed }, response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId( @@ -395,7 +399,7 @@ impl ..item.data }), None => Err(errors::ConnectorError::UnexpectedResponseError( - bytes::Bytes::from("processed".to_owned()), + bytes::Bytes::from("missing processed".to_owned()), ) .into()), } @@ -422,7 +426,7 @@ impl ..item.data }), None => Err(errors::ConnectorError::UnexpectedResponseError( - bytes::Bytes::from("challenge_required".to_owned()), + bytes::Bytes::from("missing challenge_required".to_owned()), ) .into()), } From 7acd541dd61acff2c7022d98c904af65d73af649 Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Thu, 19 Dec 2024 12:28:54 +0530 Subject: [PATCH 10/14] chore: Resolve PR comments --- .../connectors/deutschebank/transformers.rs | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs index 20c9ec6ff32b..246030898b81 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs @@ -708,22 +708,15 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>> item: &DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>, ) -> Result { if matches!(item.router_data.payment_method, PaymentMethod::Card) { - let redirect_response = item.router_data.request.redirect_response.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "redirect_response", - }, - )?; - - let response_payload = redirect_response - .payload - .clone() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "payload", - })? + let redirect_response_payload = item + .router_data + .request + .get_redirect_response_payload()? .expose(); - let deserialized: HashMap = serde_json::from_value(response_payload) - .map_err(|_| errors::ConnectorError::ResponseDeserializationFailed)?; + let deserialized: HashMap = + serde_json::from_value(redirect_response_payload) + .map_err(|_| errors::ConnectorError::ResponseDeserializationFailed)?; let cres = deserialized .get("cres") From afba7444f19eee763da3d5196da6cf396e93b20a Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Thu, 19 Dec 2024 15:39:27 +0530 Subject: [PATCH 11/14] chore: Resolved PR comments --- .../connectors/deutschebank/transformers.rs | 60 ++-- .../payment_connector_required_fields.rs | 264 ++++++++++++++++++ 2 files changed, 297 insertions(+), 27 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs index 246030898b81..b95251b0e68f 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs @@ -167,7 +167,7 @@ pub struct DeutschebankThreeDSInitializeRequestCreditCardExpiry { #[serde(rename_all = "snake_case")] pub struct DeutschebankThreeDSInitializeRequestAmountTotal { amount: MinorUnit, - currency: String, + currency: api_models::enums::Currency, } #[derive(Debug, Serialize)] @@ -245,11 +245,6 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> .into()) } else { let billing_address = item.router_data.get_billing_address()?; - let cardholder = ccard.clone().card_holder_name.ok_or_else(|| { - errors::ConnectorError::MissingRequiredField { - field_name: "card_holder_name", - } - })?; Ok(Self::CreditCard(Box::new(DeutschebankThreeDSInitializeRequest { means_of_payment: DeutschebankThreeDSInitializeRequestMeansOfPayment { credit_card: DeutschebankThreeDSInitializeRequestCreditCard { @@ -259,11 +254,11 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> month: ccard.card_exp_month, }, code: ccard.card_cvc, - cardholder, + cardholder: item.router_data.get_billing_full_name()?, }}, amount_total: DeutschebankThreeDSInitializeRequestAmountTotal { amount: item.amount, - currency: item.router_data.request.currency.clone().to_string(), + currency: item.router_data.request.currency, }, tds_20_data: DeutschebankThreeDSInitializeRequestTds20Data { communication_data: DeutschebankThreeDSInitializeRequestCommunicationData { @@ -398,10 +393,15 @@ impl }), ..item.data }), - None => Err(errors::ConnectorError::UnexpectedResponseError( - bytes::Bytes::from("missing processed".to_owned()), - ) - .into()), + None => { + let response_string = format!("{:?}", item.response); + Err( + errors::ConnectorError::UnexpectedResponseError(bytes::Bytes::from( + response_string, + )) + .into(), + ) + } } } DeutschebankThreeDSInitializeResponseOutcome::ChallengeRequired => { @@ -425,16 +425,21 @@ impl }), ..item.data }), - None => Err(errors::ConnectorError::UnexpectedResponseError( - bytes::Bytes::from("missing challenge_required".to_owned()), - ) - .into()), + None => { + let response_string = format!("{:?}", item.response); + Err( + errors::ConnectorError::UnexpectedResponseError(bytes::Bytes::from( + response_string, + )) + .into(), + ) + } } } DeutschebankThreeDSInitializeResponseOutcome::MethodRequired => Ok(Self { status: common_enums::AttemptStatus::Failure, response: Err(ErrorResponse { - code: "500".to_owned(), + code: item.http_code.to_string(), message: "Method_Required Flow not supported".to_owned(), reason: Some("Method_Required Flow is not currently supported".to_owned()), status_code: 500, @@ -714,14 +719,11 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>> .get_redirect_response_payload()? .expose(); - let deserialized: HashMap = - serde_json::from_value(redirect_response_payload) - .map_err(|_| errors::ConnectorError::ResponseDeserializationFailed)?; - - let cres = deserialized + let cres = redirect_response_payload .get("cres") - .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "cres" })? - .to_owned(); + .and_then(|v| v.as_str()) + .map(String::from) + .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "cres" })?; Ok(Self::DeutschebankThreeDSCompleteAuthorizeRequest( DeutschebankThreeDSCompleteAuthorizeRequest { cres }, @@ -932,7 +934,9 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsCaptureRouterData>> for Deutscheba ) -> Result { Ok(Self { changed_amount: item.amount, - kind: match item.router_data.is_three_ds() { + kind: match item.router_data.is_three_ds() + && matches!(item.router_data.payment_method, PaymentMethod::Card) + { true => DeutschebankTransactionKind::Creditcard3ds20, false => DeutschebankTransactionKind::Directdebit, }, @@ -1058,7 +1062,7 @@ impl TryFrom<&PaymentsCancelRouterData> for DeutschebankReversalRequest { type Error = error_stack::Report; fn try_from(item: &PaymentsCancelRouterData) -> Result { Ok(Self { - kind: match item.is_three_ds() { + kind: match item.is_three_ds() && matches!(item.payment_method, PaymentMethod::Card) { true => DeutschebankTransactionKind::Creditcard3ds20, false => DeutschebankTransactionKind::Directdebit, }, @@ -1104,7 +1108,9 @@ impl TryFrom<&DeutschebankRouterData<&RefundsRouterData>> for Deutschebank fn try_from(item: &DeutschebankRouterData<&RefundsRouterData>) -> Result { Ok(Self { changed_amount: item.amount.to_owned(), - kind: match item.router_data.is_three_ds() { + kind: match item.router_data.is_three_ds() + && matches!(item.router_data.payment_method, PaymentMethod::Card) + { true => DeutschebankTransactionKind::Creditcard3ds20, false => DeutschebankTransactionKind::Directdebit, }, 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 0954ac51c7df..d3d2b83271e8 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -941,6 +941,138 @@ impl Default for settings::RequiredFields { ), } ), + ( + enums::Connector::Deutschebank, + 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, + } + ), + ( + "payment_method_data.card.card_holder_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_holder_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.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: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Dlocal, RequiredFieldFinal { @@ -4138,6 +4270,138 @@ impl Default for settings::RequiredFields { ), } ), + ( + enums::Connector::Deutschebank, + 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, + } + ), + ( + "payment_method_data.card.card_holder_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_holder_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.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: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Dlocal, RequiredFieldFinal { From 266af29bce692816d34d6903247f292f306d6ca1 Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Thu, 19 Dec 2024 17:59:12 +0530 Subject: [PATCH 12/14] chore: Resolved PR comments --- .../connectors/deutschebank/transformers.rs | 84 ++++++++++++------- .../payment_connector_required_fields.rs | 18 ---- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs index b95251b0e68f..8133be2fa794 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs @@ -23,7 +23,7 @@ use hyperswitch_domain_models::{ PaymentsCompleteAuthorizeRouterData, RefundsRouterData, }, }; -use hyperswitch_interfaces::errors; +use hyperswitch_interfaces::{consts, errors}; use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; @@ -439,10 +439,10 @@ impl DeutschebankThreeDSInitializeResponseOutcome::MethodRequired => Ok(Self { status: common_enums::AttemptStatus::Failure, response: Err(ErrorResponse { - code: item.http_code.to_string(), - message: "Method_Required Flow not supported".to_owned(), - reason: Some("Method_Required Flow is not currently supported".to_owned()), - status_code: 500, + code: consts::NO_ERROR_CODE.to_owned(), + message: "METHOD_REQUIRED Flow not supported for deutschebank 3ds payments".to_owned(), + reason: Some("METHOD_REQUIRED Flow is not currently supported for deutschebank 3ds payments".to_owned()), + status_code: item.http_code, attempt_status: None, connector_transaction_id: None, }), @@ -932,15 +932,24 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsCaptureRouterData>> for Deutscheba fn try_from( item: &DeutschebankRouterData<&PaymentsCaptureRouterData>, ) -> Result { - Ok(Self { - changed_amount: item.amount, - kind: match item.router_data.is_three_ds() - && matches!(item.router_data.payment_method, PaymentMethod::Card) - { - true => DeutschebankTransactionKind::Creditcard3ds20, - false => DeutschebankTransactionKind::Directdebit, - }, - }) + if matches!(item.router_data.payment_method, PaymentMethod::BankDebit) { + Ok(Self { + changed_amount: item.amount, + kind: DeutschebankTransactionKind::Directdebit, + }) + } else if item.router_data.is_three_ds() + && matches!(item.router_data.payment_method, PaymentMethod::Card) + { + Ok(Self { + changed_amount: item.amount, + kind: DeutschebankTransactionKind::Creditcard3ds20, + }) + } else { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) + .into()) + } } } @@ -1061,12 +1070,20 @@ pub struct DeutschebankReversalRequest { impl TryFrom<&PaymentsCancelRouterData> for DeutschebankReversalRequest { type Error = error_stack::Report; fn try_from(item: &PaymentsCancelRouterData) -> Result { - Ok(Self { - kind: match item.is_three_ds() && matches!(item.payment_method, PaymentMethod::Card) { - true => DeutschebankTransactionKind::Creditcard3ds20, - false => DeutschebankTransactionKind::Directdebit, - }, - }) + if matches!(item.payment_method, PaymentMethod::BankDebit) { + Ok(Self { + kind: DeutschebankTransactionKind::Directdebit, + }) + } else if item.is_three_ds() && matches!(item.payment_method, PaymentMethod::Card) { + Ok(Self { + kind: DeutschebankTransactionKind::Creditcard3ds20, + }) + } else { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) + .into()) + } } } @@ -1106,15 +1123,24 @@ pub struct DeutschebankRefundRequest { impl TryFrom<&DeutschebankRouterData<&RefundsRouterData>> for DeutschebankRefundRequest { type Error = error_stack::Report; fn try_from(item: &DeutschebankRouterData<&RefundsRouterData>) -> Result { - Ok(Self { - changed_amount: item.amount.to_owned(), - kind: match item.router_data.is_three_ds() - && matches!(item.router_data.payment_method, PaymentMethod::Card) - { - true => DeutschebankTransactionKind::Creditcard3ds20, - false => DeutschebankTransactionKind::Directdebit, - }, - }) + if matches!(item.router_data.payment_method, PaymentMethod::BankDebit) { + Ok(Self { + changed_amount: item.amount, + kind: DeutschebankTransactionKind::Directdebit, + }) + } else if item.router_data.is_three_ds() + && matches!(item.router_data.payment_method, PaymentMethod::Card) + { + Ok(Self { + changed_amount: item.amount, + kind: DeutschebankTransactionKind::Creditcard3ds20, + }) + } else { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("deutschebank"), + ) + .into()) + } } } 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 d3d2b83271e8..f531aa9eb9eb 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -983,15 +983,6 @@ impl Default for settings::RequiredFields { value: None, } ), - ( - "payment_method_data.card.card_holder_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_holder_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), ( "email".to_string(), RequiredFieldInfo { @@ -4312,15 +4303,6 @@ impl Default for settings::RequiredFields { value: None, } ), - ( - "payment_method_data.card.card_holder_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_holder_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), ( "email".to_string(), RequiredFieldInfo { From e1198bca5999a35e19faf0e6b8082616df2940d4 Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Fri, 27 Dec 2024 13:32:50 +0530 Subject: [PATCH 13/14] chore: Resolved Supported Payment Methods for Deutschebank --- .../src/connectors/deutschebank.rs | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index 9b00eccc6685..c75f08125deb 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -4,7 +4,7 @@ use std::time::SystemTime; use actix_web::http::header::Date; use base64::Engine; -use common_enums::{enums, PaymentMethod}; +use common_enums::enums; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, @@ -462,7 +462,7 @@ impl ConnectorIntegration Date: Fri, 27 Dec 2024 15:11:19 +0530 Subject: [PATCH 14/14] chore: Added supported payment methods for deutschebank --- .../src/connectors/deutschebank.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index c75f08125deb..f4d5fe5fdf15 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -984,17 +984,7 @@ lazy_static! { enums::PaymentMethod::BankDebit, enums::PaymentMethodType::Sepa, PaymentMethodDetails{ - mandates: enums::FeatureStatus::NotSupported, - refunds: enums::FeatureStatus::NotSupported, - supported_capture_methods: supported_capture_methods.clone(), - } - ); - - deutschebank_supported_payment_methods.add( - enums::PaymentMethod::Card, - enums::PaymentMethodType::Credit, - PaymentMethodDetails{ - mandates: enums::FeatureStatus::NotSupported, + mandates: enums::FeatureStatus::Supported, refunds: enums::FeatureStatus::Supported, supported_capture_methods: supported_capture_methods.clone(), } @@ -1002,7 +992,7 @@ lazy_static! { deutschebank_supported_payment_methods.add( enums::PaymentMethod::Card, - enums::PaymentMethodType::Debit, + enums::PaymentMethodType::Credit, PaymentMethodDetails{ mandates: enums::FeatureStatus::NotSupported, refunds: enums::FeatureStatus::Supported,