feat(Connector):[Adyen]Implement ACH Direct Debits for Adyen (#1033)

Co-authored-by: Jagan Elavarasan <jaganelavarasan@gmail.com>
This commit is contained in:
Swangi Kumari
2023-05-09 23:55:26 +05:30
committed by GitHub
parent 8c34114138
commit eee55bdfbe
3 changed files with 112 additions and 10 deletions

View File

@ -488,6 +488,9 @@ pub enum BankDebitData {
/// Routing number for ach bank debit payment
#[schema(value_type = String, example = "110000000")]
routing_number: Secret<String>,
#[schema(value_type = String, example = "John Test")]
bank_account_holder_name: Option<Secret<String>>,
},
SepaBankDebit {
/// Billing details for bank debit

View File

@ -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<BankRedirectionPMData>),
Walley(Box<WalleyData>),
WeChatPayWeb(Box<WeChatPayWebData>),
AchDirectDebit(Box<AchDirectDebitData>),
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AchDirectDebitData {
#[serde(rename = "type")]
payment_type: PaymentType,
bank_account_number: Secret<String>,
bank_location_id: Secret<String>,
owner_name: Secret<String>,
}
#[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<api_enu
.and_then(|billing| billing.address.as_ref().and_then(|address| address.country))
}
impl<'a> TryFrom<&api_models::payments::BankDebitData> for AdyenPaymentMethod<'a> {
type Error = Error;
fn try_from(
bank_debit_data: &api_models::payments::BankDebitData,
) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
@ -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<Self, Self::Error> {
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<Self, Self::Error> {
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<DisputeStatus>)> for IncomingWebhookEvent {
impl ForeignFrom<(WebhookEventCode, Option<DisputeStatus>)> for webhooks::IncomingWebhookEvent {
fn foreign_from((code, status): (WebhookEventCode, Option<DisputeStatus>)) -> Self {
match (code, status) {
(WebhookEventCode::Authorisation, _) => Self::PaymentIntentSuccess,
@ -1816,7 +1914,7 @@ impl ForeignFrom<(WebhookEventCode, Option<DisputeStatus>)> for IncomingWebhookE
}
}
impl From<WebhookEventCode> for DisputeStage {
impl From<WebhookEventCode> for enums::DisputeStage {
fn from(code: WebhookEventCode) -> Self {
match code {
WebhookEventCode::NotificationOfChargeback => Self::PreDispute,

View File

@ -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(),