From eee55bdfbe67e5f4be7ed7e388f5ed93e70165ff Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Tue, 9 May 2023 23:55:26 +0530 Subject: [PATCH] feat(Connector):[Adyen]Implement ACH Direct Debits for Adyen (#1033) Co-authored-by: Jagan Elavarasan --- crates/api_models/src/payments.rs | 3 + .../src/connector/adyen/transformers.rs | 118 ++++++++++++++++-- .../src/connector/stripe/transformers.rs | 1 + 3 files changed, 112 insertions(+), 10 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 57dd2b0076..bffeb088c7 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -488,6 +488,9 @@ pub enum BankDebitData { /// Routing number for ach bank debit payment #[schema(value_type = String, example = "110000000")] routing_number: Secret, + + #[schema(value_type = String, example = "John Test")] + bank_account_holder_name: Option>, }, SepaBankDebit { /// Billing details for bank debit diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 5dc6f7d5ba..e06f0a99b6 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -1,6 +1,4 @@ -use api_models::{ - enums::DisputeStage, payments::MandateReferenceId, webhooks::IncomingWebhookEvent, -}; +use api_models::{enums, payments, webhooks}; use cards::CardNumber; use masking::PeekInterface; use reqwest::Url; @@ -280,6 +278,17 @@ pub enum AdyenPaymentMethod<'a> { Trustly(Box), Walley(Box), WeChatPayWeb(Box), + AchDirectDebit(Box), +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AchDirectDebitData { + #[serde(rename = "type")] + payment_type: PaymentType, + bank_account_number: Secret, + bank_location_id: Secret, + owner_name: Secret, } #[derive(Debug, Clone, Serialize)] @@ -652,6 +661,8 @@ pub enum PaymentType { Walley, #[serde(rename = "wechatpayWeb")] WeChatPayWeb, + #[serde(rename = "ach")] + AchDirectDebit, } pub struct AdyenTestBankNames<'a>(&'a str); @@ -755,6 +766,9 @@ impl<'a> TryFrom<&types::PaymentsAuthorizeRouterData> for AdyenPaymentRequest<'a api_models::payments::PaymentMethodData::BankRedirect(ref bank_redirect) => { AdyenPaymentRequest::try_from((item, bank_redirect)) } + api_models::payments::PaymentMethodData::BankDebit(ref bank_debit) => { + AdyenPaymentRequest::try_from((item, bank_debit)) + } _ => Err(errors::ConnectorError::NotSupported { message: format!("{:?}", item.request.payment_method_type), connector: "Adyen", @@ -905,6 +919,35 @@ fn get_country_code(item: &types::PaymentsAuthorizeRouterData) -> Option TryFrom<&api_models::payments::BankDebitData> for AdyenPaymentMethod<'a> { + type Error = Error; + fn try_from( + bank_debit_data: &api_models::payments::BankDebitData, + ) -> Result { + match bank_debit_data { + payments::BankDebitData::AchBankDebit { + account_number, + routing_number, + billing_details: _, + bank_account_holder_name, + } => Ok(AdyenPaymentMethod::AchDirectDebit(Box::new( + AchDirectDebitData { + payment_type: PaymentType::AchDirectDebit, + bank_account_number: account_number.clone(), + bank_location_id: routing_number.clone(), + owner_name: bank_account_holder_name.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "bank_account_holder_name", + }, + )?, + }, + ))), + + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + impl<'a> TryFrom<&api::Card> for AdyenPaymentMethod<'a> { type Error = Error; fn try_from(card: &api::Card) -> Result { @@ -1107,12 +1150,18 @@ impl<'a> TryFrom<&api_models::payments::BankRedirectData> for AdyenPaymentMethod } } -impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, MandateReferenceId)> - for AdyenPaymentRequest<'a> +impl<'a> + TryFrom<( + &types::PaymentsAuthorizeRouterData, + payments::MandateReferenceId, + )> for AdyenPaymentRequest<'a> { type Error = Error; fn try_from( - value: (&types::PaymentsAuthorizeRouterData, MandateReferenceId), + value: ( + &types::PaymentsAuthorizeRouterData, + payments::MandateReferenceId, + ), ) -> Result { let (item, mandate_ref_id) = value; let amount = get_amount_data(item); @@ -1124,7 +1173,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, MandateReferenceId)> let additional_data = get_additional_data(item); let return_url = item.request.get_return_url()?; let payment_method = match mandate_ref_id { - MandateReferenceId::ConnectorMandateId(connector_mandate_ids) => { + payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids) => { let adyen_mandate = AdyenMandate { payment_type: PaymentType::Scheme, stored_payment_method_id: connector_mandate_ids.get_connector_mandate_id()?, @@ -1133,7 +1182,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, MandateReferenceId)> adyen_mandate, ))) } - MandateReferenceId::NetworkMandateId(network_mandate_id) => { + payments::MandateReferenceId::NetworkMandateId(network_mandate_id) => { match item.request.payment_method_data { api::PaymentMethodData::Card(ref card) => { let card_issuer = card.get_card_issuer()?; @@ -1220,6 +1269,55 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::Card)> for AdyenPay } } +impl<'a> + TryFrom<( + &types::PaymentsAuthorizeRouterData, + &api_models::payments::BankDebitData, + )> for AdyenPaymentRequest<'a> +{ + type Error = Error; + + fn try_from( + value: ( + &types::PaymentsAuthorizeRouterData, + &api_models::payments::BankDebitData, + ), + ) -> Result { + let (item, bank_debit_data) = value; + let amount = get_amount_data(item); + let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?; + let shopper_interaction = AdyenShopperInteraction::from(item); + let recurring_processing_model = get_recurring_processing_model(item)?.0; + let browser_info = get_browser_info(item); + let additional_data = get_additional_data(item); + let return_url = item.request.get_return_url()?; + let payment_method = AdyenPaymentMethod::try_from(bank_debit_data)?; + let country_code = get_country_code(item); + let request = AdyenPaymentRequest { + amount, + merchant_account: auth_type.merchant_account, + payment_method, + reference: item.payment_id.to_string(), + return_url, + browser_info, + shopper_interaction, + recurring_processing_model, + additional_data, + shopper_name: None, + shopper_locale: None, + shopper_email: item.request.email.clone(), + telephone_number: None, + billing_address: None, + delivery_address: None, + country_code, + line_items: None, + shopper_reference: None, + store_payment_method: None, + }; + Ok(request) + } +} + impl<'a> TryFrom<( &types::PaymentsAuthorizeRouterData, @@ -1790,7 +1888,7 @@ pub fn is_chargeback_event(event_code: &WebhookEventCode) -> bool { ) } -impl ForeignFrom<(WebhookEventCode, Option)> for IncomingWebhookEvent { +impl ForeignFrom<(WebhookEventCode, Option)> for webhooks::IncomingWebhookEvent { fn foreign_from((code, status): (WebhookEventCode, Option)) -> Self { match (code, status) { (WebhookEventCode::Authorisation, _) => Self::PaymentIntentSuccess, @@ -1816,7 +1914,7 @@ impl ForeignFrom<(WebhookEventCode, Option)> for IncomingWebhookE } } -impl From for DisputeStage { +impl From for enums::DisputeStage { fn from(code: WebhookEventCode) -> Self { match code { WebhookEventCode::NotificationOfChargeback => Self::PreDispute, diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index f50337403e..82c440dae7 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -668,6 +668,7 @@ fn get_bank_debit_data( billing_details, account_number, routing_number, + .. } => { let ach_data = BankDebitData::Ach { account_holder_type: "individual".to_string(),