feat(connector): [Loonio] implement payouts (#9718)

This commit is contained in:
Swangi Kumari
2025-10-08 21:37:14 +05:30
committed by GitHub
parent 3727995a0a
commit abcc70be07
41 changed files with 915 additions and 96 deletions

View File

@ -244,6 +244,24 @@ fn get_connector_payment_method_type_fields(
)
}
// Bank Redirect
PaymentMethodType::Interac => {
common_fields.extend(get_interac_fields());
(
payment_method_type,
ConnectorFields {
fields: HashMap::from([(
connector.into(),
RequiredFieldFinal {
mandate: HashMap::new(),
non_mandate: HashMap::new(),
common: common_fields,
},
)]),
},
)
}
_ => (
payment_method_type,
ConnectorFields {
@ -375,6 +393,38 @@ fn get_paypal_fields() -> HashMap<String, RequiredFieldInfo> {
)])
}
fn get_interac_fields() -> HashMap<String, RequiredFieldInfo> {
HashMap::from([
(
"payout_method_data.bank_redirect.interac.email".to_string(),
RequiredFieldInfo {
required_field: "payout_method_data.bank_redirect.interac.email".to_string(),
display_name: "email".to_string(),
field_type: FieldType::Text,
value: None,
},
),
(
"billing.address.first_name".to_string(),
RequiredFieldInfo {
required_field: "billing.address.first_name".to_string(),
display_name: "billing_address_first_name".to_string(),
field_type: FieldType::Text,
value: None,
},
),
(
"billing.address.last_name".to_string(),
RequiredFieldInfo {
required_field: "billing.address.last_name".to_string(),
display_name: "billing_address_last_name".to_string(),
field_type: FieldType::Text,
value: None,
},
),
])
}
fn get_countries_for_connector(connector: PayoutConnectors) -> Vec<CountryAlpha2> {
match connector {
PayoutConnectors::Adyenplatform => vec![

View File

@ -4803,6 +4803,12 @@ pub async fn get_bank_from_hs_locker(
message: "Expected bank details, found wallet details instead".to_string(),
}
.into()),
api::PayoutMethodData::BankRedirect(_) => {
Err(errors::ApiErrorResponse::InvalidRequestData {
message: "Expected bank details, found bank redirect details instead".to_string(),
}
.into())
}
}
}

View File

@ -824,6 +824,7 @@ pub enum VaultPayoutMethod {
Card(String),
Bank(String),
Wallet(String),
BankRedirect(String),
}
#[cfg(feature = "payouts")]
@ -836,6 +837,9 @@ impl Vaultable for api::PayoutMethodData {
Self::Card(card) => VaultPayoutMethod::Card(card.get_value1(customer_id)?),
Self::Bank(bank) => VaultPayoutMethod::Bank(bank.get_value1(customer_id)?),
Self::Wallet(wallet) => VaultPayoutMethod::Wallet(wallet.get_value1(customer_id)?),
Self::BankRedirect(bank_redirect) => {
VaultPayoutMethod::BankRedirect(bank_redirect.get_value1(customer_id)?)
}
};
value1
@ -852,6 +856,9 @@ impl Vaultable for api::PayoutMethodData {
Self::Card(card) => VaultPayoutMethod::Card(card.get_value2(customer_id)?),
Self::Bank(bank) => VaultPayoutMethod::Bank(bank.get_value2(customer_id)?),
Self::Wallet(wallet) => VaultPayoutMethod::Wallet(wallet.get_value2(customer_id)?),
Self::BankRedirect(bank_redirect) => {
VaultPayoutMethod::BankRedirect(bank_redirect.get_value2(customer_id)?)
}
};
value2
@ -887,12 +894,95 @@ impl Vaultable for api::PayoutMethodData {
let (wallet, supp_data) = api::WalletPayout::from_values(mvalue1, mvalue2)?;
Ok((Self::Wallet(wallet), supp_data))
}
(
VaultPayoutMethod::BankRedirect(mvalue1),
VaultPayoutMethod::BankRedirect(mvalue2),
) => {
let (bank_redirect, supp_data) =
api::BankRedirectPayout::from_values(mvalue1, mvalue2)?;
Ok((Self::BankRedirect(bank_redirect), supp_data))
}
_ => Err(errors::VaultError::PayoutMethodNotSupported)
.attach_printable("Payout method not supported"),
}
}
}
#[cfg(feature = "payouts")]
impl Vaultable for api::BankRedirectPayout {
fn get_value1(
&self,
_customer_id: Option<id_type::CustomerId>,
) -> CustomResult<String, errors::VaultError> {
let value1 = match self {
Self::Interac(interac_data) => TokenizedBankRedirectSensitiveValues {
email: interac_data.email.clone(),
bank_redirect_type: PaymentMethodType::Interac,
},
};
value1
.encode_to_string_of_json()
.change_context(errors::VaultError::RequestEncodingFailed)
.attach_printable(
"Failed to encode bank redirect data - TokenizedBankRedirectSensitiveValues",
)
}
fn get_value2(
&self,
customer_id: Option<id_type::CustomerId>,
) -> CustomResult<String, errors::VaultError> {
let value2 = TokenizedBankRedirectInsensitiveValues { customer_id };
value2
.encode_to_string_of_json()
.change_context(errors::VaultError::RequestEncodingFailed)
.attach_printable("Failed to encode wallet data value2")
}
fn from_values(
value1: String,
value2: String,
) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
let value1: TokenizedBankRedirectSensitiveValues = value1
.parse_struct("TokenizedBankRedirectSensitiveValues")
.change_context(errors::VaultError::ResponseDeserializationFailed)
.attach_printable("Could not deserialize into wallet data value1")?;
let value2: TokenizedBankRedirectInsensitiveValues = value2
.parse_struct("TokenizedBankRedirectInsensitiveValues")
.change_context(errors::VaultError::ResponseDeserializationFailed)
.attach_printable("Could not deserialize into wallet data value2")?;
let bank_redirect = match value1.bank_redirect_type {
PaymentMethodType::Interac => Self::Interac(api_models::payouts::Interac {
email: value1.email,
}),
_ => Err(errors::VaultError::PayoutMethodNotSupported)
.attach_printable("Payout method not supported")?,
};
let supp_data = SupplementaryVaultData {
customer_id: value2.customer_id,
payment_method_id: None,
};
Ok((bank_redirect, supp_data))
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizedBankRedirectSensitiveValues {
pub email: Email,
pub bank_redirect_type: PaymentMethodType,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizedBankRedirectInsensitiveValues {
pub customer_id: Option<id_type::CustomerId>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MockTokenizeDBValue {
pub value1: String,

View File

@ -377,7 +377,8 @@ pub async fn save_payout_data_to_locker(
Some(wallet.to_owned()),
api_enums::PaymentMethodType::foreign_from(wallet),
),
payouts::PayoutMethodData::Card(_) => {
payouts::PayoutMethodData::Card(_)
| payouts::PayoutMethodData::BankRedirect(_) => {
Err(errors::ApiErrorResponse::InternalServerError)?
}
}
@ -1533,6 +1534,11 @@ pub async fn get_additional_payout_data(
Box::new(wallet_data.to_owned().into()),
))
}
api::PayoutMethodData::BankRedirect(bank_redirect_data) => {
Some(payout_additional::AdditionalPayoutMethodData::BankRedirect(
Box::new(bank_redirect_data.to_owned().into()),
))
}
}
}

View File

@ -1,10 +1,11 @@
pub use api_models::payouts::{
AchBankTransfer, BacsBankTransfer, Bank as BankPayout, CardPayout, PaymentMethodTypeInfo,
PayoutActionRequest, PayoutAttemptResponse, PayoutCreateRequest, PayoutCreateResponse,
PayoutEnabledPaymentMethodsInfo, PayoutLinkResponse, PayoutListConstraints,
PayoutListFilterConstraints, PayoutListFilters, PayoutListResponse, PayoutMethodData,
PayoutMethodDataResponse, PayoutRequest, PayoutRetrieveBody, PayoutRetrieveRequest,
PixBankTransfer, RequiredFieldsOverrideRequest, SepaBankTransfer, Wallet as WalletPayout,
AchBankTransfer, BacsBankTransfer, Bank as BankPayout, BankRedirect as BankRedirectPayout,
CardPayout, PaymentMethodTypeInfo, PayoutActionRequest, PayoutAttemptResponse,
PayoutCreateRequest, PayoutCreateResponse, PayoutEnabledPaymentMethodsInfo, PayoutLinkResponse,
PayoutListConstraints, PayoutListFilterConstraints, PayoutListFilters, PayoutListResponse,
PayoutMethodData, PayoutMethodDataResponse, PayoutRequest, PayoutRetrieveBody,
PayoutRetrieveRequest, PixBankTransfer, RequiredFieldsOverrideRequest, SepaBankTransfer,
Wallet as WalletPayout,
};
pub use hyperswitch_domain_models::router_flow_types::payouts::{
PoCancel, PoCreate, PoEligibility, PoFulfill, PoQuote, PoRecipient, PoRecipientAccount, PoSync,

View File

@ -1195,6 +1195,9 @@ impl ForeignFrom<&api_models::payouts::PayoutMethodData> for api_enums::PaymentM
api_models::payouts::PayoutMethodData::Bank(bank) => Self::foreign_from(bank),
api_models::payouts::PayoutMethodData::Card(_) => Self::Debit,
api_models::payouts::PayoutMethodData::Wallet(wallet) => Self::foreign_from(wallet),
api_models::payouts::PayoutMethodData::BankRedirect(bank_redirect) => {
Self::foreign_from(bank_redirect)
}
}
}
}
@ -1221,6 +1224,15 @@ impl ForeignFrom<&api_models::payouts::Wallet> for api_enums::PaymentMethodType
}
}
#[cfg(feature = "payouts")]
impl ForeignFrom<&api_models::payouts::BankRedirect> for api_enums::PaymentMethodType {
fn foreign_from(value: &api_models::payouts::BankRedirect) -> Self {
match value {
api_models::payouts::BankRedirect::Interac(_) => Self::Interac,
}
}
}
#[cfg(feature = "payouts")]
impl ForeignFrom<&api_models::payouts::PayoutMethodData> for api_enums::PaymentMethod {
fn foreign_from(value: &api_models::payouts::PayoutMethodData) -> Self {
@ -1228,6 +1240,7 @@ impl ForeignFrom<&api_models::payouts::PayoutMethodData> for api_enums::PaymentM
api_models::payouts::PayoutMethodData::Bank(_) => Self::BankTransfer,
api_models::payouts::PayoutMethodData::Card(_) => Self::Card,
api_models::payouts::PayoutMethodData::Wallet(_) => Self::Wallet,
api_models::payouts::PayoutMethodData::BankRedirect(_) => Self::BankRedirect,
}
}
}
@ -1239,6 +1252,7 @@ impl ForeignFrom<&api_models::payouts::PayoutMethodData> for api_models::enums::
api_models::payouts::PayoutMethodData::Bank(_) => Self::Bank,
api_models::payouts::PayoutMethodData::Card(_) => Self::Card,
api_models::payouts::PayoutMethodData::Wallet(_) => Self::Wallet,
api_models::payouts::PayoutMethodData::BankRedirect(_) => Self::BankRedirect,
}
}
}
@ -1250,6 +1264,7 @@ impl ForeignFrom<api_models::enums::PayoutType> for api_enums::PaymentMethod {
api_models::enums::PayoutType::Bank => Self::BankTransfer,
api_models::enums::PayoutType::Card => Self::Card,
api_models::enums::PayoutType::Wallet => Self::Wallet,
api_models::enums::PayoutType::BankRedirect => Self::BankRedirect,
}
}
}

View File

@ -128,6 +128,16 @@ impl AdyenTest {
paypal_id: None,
}),
)),
enums::PayoutType::BankRedirect => {
Some(types::api::PayoutMethodData::BankRedirect(
types::api::payouts::BankRedirectPayout::Interac(
api_models::payouts::Interac {
email: Email::from_str("EmailUsedForPayPalAccount@example.com")
.ok()?,
},
),
))
}
},
..Default::default()
})