mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(Connector):[Adyen]Implement ACH Direct Debits for Adyen (#1033)
Co-authored-by: Jagan Elavarasan <jaganelavarasan@gmail.com>
This commit is contained in:
@ -488,6 +488,9 @@ pub enum BankDebitData {
|
|||||||
/// Routing number for ach bank debit payment
|
/// Routing number for ach bank debit payment
|
||||||
#[schema(value_type = String, example = "110000000")]
|
#[schema(value_type = String, example = "110000000")]
|
||||||
routing_number: Secret<String>,
|
routing_number: Secret<String>,
|
||||||
|
|
||||||
|
#[schema(value_type = String, example = "John Test")]
|
||||||
|
bank_account_holder_name: Option<Secret<String>>,
|
||||||
},
|
},
|
||||||
SepaBankDebit {
|
SepaBankDebit {
|
||||||
/// Billing details for bank debit
|
/// Billing details for bank debit
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
use api_models::{
|
use api_models::{enums, payments, webhooks};
|
||||||
enums::DisputeStage, payments::MandateReferenceId, webhooks::IncomingWebhookEvent,
|
|
||||||
};
|
|
||||||
use cards::CardNumber;
|
use cards::CardNumber;
|
||||||
use masking::PeekInterface;
|
use masking::PeekInterface;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
@ -280,6 +278,17 @@ pub enum AdyenPaymentMethod<'a> {
|
|||||||
Trustly(Box<BankRedirectionPMData>),
|
Trustly(Box<BankRedirectionPMData>),
|
||||||
Walley(Box<WalleyData>),
|
Walley(Box<WalleyData>),
|
||||||
WeChatPayWeb(Box<WeChatPayWebData>),
|
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)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
@ -652,6 +661,8 @@ pub enum PaymentType {
|
|||||||
Walley,
|
Walley,
|
||||||
#[serde(rename = "wechatpayWeb")]
|
#[serde(rename = "wechatpayWeb")]
|
||||||
WeChatPayWeb,
|
WeChatPayWeb,
|
||||||
|
#[serde(rename = "ach")]
|
||||||
|
AchDirectDebit,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AdyenTestBankNames<'a>(&'a str);
|
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) => {
|
api_models::payments::PaymentMethodData::BankRedirect(ref bank_redirect) => {
|
||||||
AdyenPaymentRequest::try_from((item, 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 {
|
_ => Err(errors::ConnectorError::NotSupported {
|
||||||
message: format!("{:?}", item.request.payment_method_type),
|
message: format!("{:?}", item.request.payment_method_type),
|
||||||
connector: "Adyen",
|
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))
|
.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> {
|
impl<'a> TryFrom<&api::Card> for AdyenPaymentMethod<'a> {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
fn try_from(card: &api::Card) -> Result<Self, Self::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)>
|
impl<'a>
|
||||||
for AdyenPaymentRequest<'a>
|
TryFrom<(
|
||||||
|
&types::PaymentsAuthorizeRouterData,
|
||||||
|
payments::MandateReferenceId,
|
||||||
|
)> for AdyenPaymentRequest<'a>
|
||||||
{
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
fn try_from(
|
fn try_from(
|
||||||
value: (&types::PaymentsAuthorizeRouterData, MandateReferenceId),
|
value: (
|
||||||
|
&types::PaymentsAuthorizeRouterData,
|
||||||
|
payments::MandateReferenceId,
|
||||||
|
),
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
let (item, mandate_ref_id) = value;
|
let (item, mandate_ref_id) = value;
|
||||||
let amount = get_amount_data(item);
|
let amount = get_amount_data(item);
|
||||||
@ -1124,7 +1173,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, MandateReferenceId)>
|
|||||||
let additional_data = get_additional_data(item);
|
let additional_data = get_additional_data(item);
|
||||||
let return_url = item.request.get_return_url()?;
|
let return_url = item.request.get_return_url()?;
|
||||||
let payment_method = match mandate_ref_id {
|
let payment_method = match mandate_ref_id {
|
||||||
MandateReferenceId::ConnectorMandateId(connector_mandate_ids) => {
|
payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids) => {
|
||||||
let adyen_mandate = AdyenMandate {
|
let adyen_mandate = AdyenMandate {
|
||||||
payment_type: PaymentType::Scheme,
|
payment_type: PaymentType::Scheme,
|
||||||
stored_payment_method_id: connector_mandate_ids.get_connector_mandate_id()?,
|
stored_payment_method_id: connector_mandate_ids.get_connector_mandate_id()?,
|
||||||
@ -1133,7 +1182,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, MandateReferenceId)>
|
|||||||
adyen_mandate,
|
adyen_mandate,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
MandateReferenceId::NetworkMandateId(network_mandate_id) => {
|
payments::MandateReferenceId::NetworkMandateId(network_mandate_id) => {
|
||||||
match item.request.payment_method_data {
|
match item.request.payment_method_data {
|
||||||
api::PaymentMethodData::Card(ref card) => {
|
api::PaymentMethodData::Card(ref card) => {
|
||||||
let card_issuer = card.get_card_issuer()?;
|
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>
|
impl<'a>
|
||||||
TryFrom<(
|
TryFrom<(
|
||||||
&types::PaymentsAuthorizeRouterData,
|
&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 {
|
fn foreign_from((code, status): (WebhookEventCode, Option<DisputeStatus>)) -> Self {
|
||||||
match (code, status) {
|
match (code, status) {
|
||||||
(WebhookEventCode::Authorisation, _) => Self::PaymentIntentSuccess,
|
(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 {
|
fn from(code: WebhookEventCode) -> Self {
|
||||||
match code {
|
match code {
|
||||||
WebhookEventCode::NotificationOfChargeback => Self::PreDispute,
|
WebhookEventCode::NotificationOfChargeback => Self::PreDispute,
|
||||||
|
|||||||
@ -668,6 +668,7 @@ fn get_bank_debit_data(
|
|||||||
billing_details,
|
billing_details,
|
||||||
account_number,
|
account_number,
|
||||||
routing_number,
|
routing_number,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let ach_data = BankDebitData::Ach {
|
let ach_data = BankDebitData::Ach {
|
||||||
account_holder_type: "individual".to_string(),
|
account_holder_type: "individual".to_string(),
|
||||||
|
|||||||
Reference in New Issue
Block a user