mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +08:00 
			
		
		
		
	feat(connector): [Stripe] implement Multibanco Bank Transfer for stripe (#1420)
Co-authored-by: Jagan <jaganelavarasan@gmail.com> Co-authored-by: Arjun Karthik <m.arjunkarthik@gmail.com>
This commit is contained in:
		| @ -130,12 +130,13 @@ impl | ||||
|         &self, | ||||
|         req: &types::PaymentsPreProcessingRouterData, | ||||
|     ) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> { | ||||
|         let req = stripe::StripeAchSourceRequest::try_from(req)?; | ||||
|         let req = stripe::StripeCreditTransferSourceRequest::try_from(req)?; | ||||
|         let pre_processing_request = types::RequestBody::log_and_get_request_body( | ||||
|             &req, | ||||
|             utils::Encode::<stripe::StripeAchSourceRequest>::url_encode, | ||||
|             utils::Encode::<stripe::StripeCreditTransferSourceRequest>::url_encode, | ||||
|         ) | ||||
|         .change_context(errors::ConnectorError::RequestEncodingFailed)?; | ||||
|  | ||||
|         Ok(Some(pre_processing_request)) | ||||
|     } | ||||
|  | ||||
| @ -721,7 +722,8 @@ impl | ||||
|         match &req.request.payment_method_data { | ||||
|             api_models::payments::PaymentMethodData::BankTransfer(bank_transfer_data) => { | ||||
|                 match bank_transfer_data.deref() { | ||||
|                     api_models::payments::BankTransferData::AchBankTransfer { .. } => { | ||||
|                     api_models::payments::BankTransferData::AchBankTransfer { .. } | ||||
|                     | api_models::payments::BankTransferData::MultibancoBankTransfer { .. } => { | ||||
|                         Ok(format!("{}{}", self.base_url(connectors), "v1/charges")) | ||||
|                     } | ||||
|                     _ => Ok(format!( | ||||
| @ -1772,7 +1774,9 @@ impl api::IncomingWebhook for Stripe { | ||||
|             } | ||||
|             stripe::WebhookEventType::ChargeSucceeded => { | ||||
|                 if let Some(stripe::WebhookPaymentMethodDetails { | ||||
|                     payment_method: stripe::WebhookPaymentMethodType::AchCreditTransfer, | ||||
|                     payment_method: | ||||
|                         stripe::WebhookPaymentMethodType::AchCreditTransfer | ||||
|                         | stripe::WebhookPaymentMethodType::MultibancoBankTransfers, | ||||
|                 }) = details.event_data.event_object.payment_method_details | ||||
|                 { | ||||
|                     api::IncomingWebhookEvent::PaymentIntentSuccess | ||||
|  | ||||
| @ -15,10 +15,7 @@ use uuid::Uuid; | ||||
|  | ||||
| use crate::{ | ||||
|     collect_missing_value_keys, | ||||
|     connector::{ | ||||
|         self, | ||||
|         utils::{ApplePay, RouterData}, | ||||
|     }, | ||||
|     connector::utils::{ApplePay, PaymentsPreProcessingData, RouterData}, | ||||
|     core::errors, | ||||
|     services, | ||||
|     types::{self, api, storage::enums, transformers::ForeignFrom}, | ||||
| @ -292,7 +289,13 @@ pub struct StripeBankRedirectData { | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Eq, PartialEq, Serialize)] | ||||
| pub struct AchBankTransferData { | ||||
| pub struct AchTransferData { | ||||
|     #[serde(rename = "owner[email]")] | ||||
|     pub email: Email, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Eq, PartialEq, Serialize)] | ||||
| pub struct MultibancoTransferData { | ||||
|     #[serde(rename = "owner[email]")] | ||||
|     pub email: Email, | ||||
| } | ||||
| @ -326,12 +329,31 @@ pub struct SepaBankTransferData { | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Eq, PartialEq, Serialize)] | ||||
| pub struct StripeAchSourceRequest { | ||||
| #[serde(untagged)] | ||||
| pub enum StripeCreditTransferSourceRequest { | ||||
|     AchBankTansfer(AchCreditTransferSourceRequest), | ||||
|     MultibancoBankTansfer(MultibancoCreditTransferSourceRequest), | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Eq, PartialEq, Serialize)] | ||||
| pub struct AchCreditTransferSourceRequest { | ||||
|     #[serde(rename = "type")] | ||||
|     pub transfer_type: StripePaymentMethodType, | ||||
|     #[serde(flatten)] | ||||
|     pub payment_method_data: AchBankTransferData, | ||||
|     pub currency: String, | ||||
|     pub payment_method_data: AchTransferData, | ||||
|     pub currency: enums::Currency, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Eq, PartialEq, Serialize)] | ||||
| pub struct MultibancoCreditTransferSourceRequest { | ||||
|     #[serde(rename = "type")] | ||||
|     pub transfer_type: StripePaymentMethodType, | ||||
|     #[serde(flatten)] | ||||
|     pub payment_method_data: MultibancoTransferData, | ||||
|     pub currency: enums::Currency, | ||||
|     pub amount: Option<i64>, | ||||
|     #[serde(rename = "redirect[return_url]")] | ||||
|     pub return_url: Option<String>, | ||||
| } | ||||
|  | ||||
| // Remove untagged when Deserialize is added | ||||
| @ -395,9 +417,10 @@ pub struct BankTransferData { | ||||
| #[derive(Debug, Eq, PartialEq, Serialize)] | ||||
| #[serde(untagged)] | ||||
| pub enum StripeBankTransferData { | ||||
|     AchBankTransfer(Box<AchBankTransferData>), | ||||
|     AchBankTransfer(Box<AchTransferData>), | ||||
|     SepaBankTransfer(Box<SepaBankTransferData>), | ||||
|     BacsBankTransfers(Box<BacsBankTransferData>), | ||||
|     MultibancoBankTransfers(Box<MultibancoTransferData>), | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Eq, PartialEq, Serialize)] | ||||
| @ -494,6 +517,7 @@ pub enum StripePaymentMethodType { | ||||
|     #[serde(rename = "p24")] | ||||
|     Przelewy24, | ||||
|     CustomerBalance, | ||||
|     Multibanco, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Eq, PartialEq, Serialize, Clone)] | ||||
| @ -1078,13 +1102,24 @@ fn create_stripe_payment_method( | ||||
|             match bank_transfer_data.deref() { | ||||
|                 payments::BankTransferData::AchBankTransfer { billing_details } => Ok(( | ||||
|                     StripePaymentMethodData::BankTransfer(StripeBankTransferData::AchBankTransfer( | ||||
|                         Box::new(AchBankTransferData { | ||||
|                         Box::new(AchTransferData { | ||||
|                             email: billing_details.email.to_owned(), | ||||
|                         }), | ||||
|                     )), | ||||
|                     StripePaymentMethodType::AchCreditTransfer, | ||||
|                     StripeBillingAddress::default(), | ||||
|                 )), | ||||
|                 payments::BankTransferData::MultibancoBankTransfer { billing_details } => Ok(( | ||||
|                     StripePaymentMethodData::BankTransfer( | ||||
|                         StripeBankTransferData::MultibancoBankTransfers(Box::new( | ||||
|                             MultibancoTransferData { | ||||
|                                 email: billing_details.email.to_owned(), | ||||
|                             }, | ||||
|                         )), | ||||
|                     ), | ||||
|                     StripePaymentMethodType::Multibanco, | ||||
|                     StripeBillingAddress::default(), | ||||
|                 )), | ||||
|                 payments::BankTransferData::SepaBankTransfer { | ||||
|                     billing_details, | ||||
|                     country, | ||||
| @ -1444,7 +1479,10 @@ pub struct PaymentIntentResponse { | ||||
| #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] | ||||
| pub struct StripeSourceResponse { | ||||
|     pub id: String, | ||||
|     pub ach_credit_transfer: AchCreditTransferResponse, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub ach_credit_transfer: Option<AchCreditTransferResponse>, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub multibanco: Option<MultibancoCreditTansferResponse>, | ||||
|     pub receiver: AchReceiverDetails, | ||||
|     pub status: StripePaymentStatus, | ||||
| } | ||||
| @ -1457,6 +1495,12 @@ pub struct AchCreditTransferResponse { | ||||
|     pub swift_code: Secret<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] | ||||
| pub struct MultibancoCreditTansferResponse { | ||||
|     pub reference: Secret<String>, | ||||
|     pub entity: Secret<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] | ||||
| pub struct AchReceiverDetails { | ||||
|     pub amount_received: i64, | ||||
| @ -1597,7 +1641,8 @@ impl ForeignFrom<(Option<StripePaymentMethodOptions>, String)> for types::Mandat | ||||
|                 | StripePaymentMethodOptions::Sepa {} | ||||
|                 | StripePaymentMethodOptions::Bancontact {} | ||||
|                 | StripePaymentMethodOptions::Przelewy24 {} | ||||
|                 | StripePaymentMethodOptions::CustomerBalance {} => None, | ||||
|                 | StripePaymentMethodOptions::CustomerBalance {} | ||||
|                 | StripePaymentMethodOptions::Multibanco {} => None, | ||||
|             }), | ||||
|             payment_method_id: Some(payment_method_id), | ||||
|         } | ||||
| @ -2088,6 +2133,7 @@ pub enum StripePaymentMethodOptions { | ||||
|     #[serde(rename = "p24")] | ||||
|     Przelewy24 {}, | ||||
|     CustomerBalance {}, | ||||
|     Multibanco {}, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] | ||||
| @ -2127,23 +2173,42 @@ impl TryFrom<&types::PaymentsCaptureRouterData> for CaptureRequest { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<&types::PaymentsPreProcessingRouterData> for StripeAchSourceRequest { | ||||
| impl TryFrom<&types::PaymentsPreProcessingRouterData> for StripeCreditTransferSourceRequest { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(item: &types::PaymentsPreProcessingRouterData) -> Result<Self, Self::Error> { | ||||
|         Ok(Self { | ||||
|             transfer_type: StripePaymentMethodType::AchCreditTransfer, | ||||
|             payment_method_data: AchBankTransferData { | ||||
|                 email: connector::utils::PaymentsPreProcessingData::get_email(&item.request)?, | ||||
|             }, | ||||
|             currency: item | ||||
|                 .request | ||||
|                 .currency | ||||
|                 .get_required_value("currency") | ||||
|                 .change_context(errors::ConnectorError::MissingRequiredField { | ||||
|                     field_name: "currency", | ||||
|                 })? | ||||
|                 .to_string(), | ||||
|         }) | ||||
|         let currency = item.request.get_currency()?; | ||||
|  | ||||
|         match &item.request.payment_method_data { | ||||
|             Some(payments::PaymentMethodData::BankTransfer(bank_transfer_data)) => { | ||||
|                 match **bank_transfer_data { | ||||
|                     payments::BankTransferData::MultibancoBankTransfer { .. } => Ok( | ||||
|                         Self::MultibancoBankTansfer(MultibancoCreditTransferSourceRequest { | ||||
|                             transfer_type: StripePaymentMethodType::Multibanco, | ||||
|                             currency, | ||||
|                             payment_method_data: MultibancoTransferData { | ||||
|                                 email: item.request.get_email()?, | ||||
|                             }, | ||||
|                             amount: Some(item.request.get_amount()?), | ||||
|                             return_url: Some(item.get_return_url()?), | ||||
|                         }), | ||||
|                     ), | ||||
|                     payments::BankTransferData::AchBankTransfer { .. } => { | ||||
|                         Ok(Self::AchBankTansfer(AchCreditTransferSourceRequest { | ||||
|                             transfer_type: StripePaymentMethodType::AchCreditTransfer, | ||||
|                             payment_method_data: AchTransferData { | ||||
|                                 email: item.request.get_email()?, | ||||
|                             }, | ||||
|                             currency, | ||||
|                         })) | ||||
|                     } | ||||
|                     _ => Err(errors::ConnectorError::NotImplemented( | ||||
|                         "Bank Transfer Method".to_string(), | ||||
|                     ) | ||||
|                     .into()), | ||||
|                 } | ||||
|             } | ||||
|             _ => Err(errors::ConnectorError::NotImplemented("Payment Method".to_string()).into()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2342,6 +2407,7 @@ pub struct WebhookStatusObjectData { | ||||
| #[serde(rename_all = "snake_case")] | ||||
| pub enum WebhookPaymentMethodType { | ||||
|     AchCreditTransfer, | ||||
|     MultibancoBankTransfers, | ||||
|     #[serde(other)] | ||||
|     Unknown, | ||||
| } | ||||
| @ -2546,11 +2612,18 @@ impl | ||||
|                 match bank_transfer_data.deref() { | ||||
|                     payments::BankTransferData::AchBankTransfer { billing_details } => { | ||||
|                         Ok(Self::BankTransfer(StripeBankTransferData::AchBankTransfer( | ||||
|                             Box::new(AchBankTransferData { | ||||
|                             Box::new(AchTransferData { | ||||
|                                 email: billing_details.email.to_owned(), | ||||
|                             }), | ||||
|                         ))) | ||||
|                     } | ||||
|                     payments::BankTransferData::MultibancoBankTransfer { billing_details } => Ok( | ||||
|                         Self::BankTransfer(StripeBankTransferData::MultibancoBankTransfers( | ||||
|                             Box::new(MultibancoTransferData { | ||||
|                                 email: billing_details.email.to_owned(), | ||||
|                             }), | ||||
|                         )), | ||||
|                     ), | ||||
|                     payments::BankTransferData::SepaBankTransfer { country, .. } => Ok( | ||||
|                         Self::BankTransfer(StripeBankTransferData::SepaBankTransfer(Box::new( | ||||
|                             SepaBankTransferData { | ||||
| @ -2594,7 +2667,8 @@ pub fn get_bank_transfer_request_data( | ||||
|     bank_transfer_data: &api_models::payments::BankTransferData, | ||||
| ) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> { | ||||
|     match bank_transfer_data { | ||||
|         api_models::payments::BankTransferData::AchBankTransfer { .. } => { | ||||
|         api_models::payments::BankTransferData::AchBankTransfer { .. } | ||||
|         | api_models::payments::BankTransferData::MultibancoBankTransfer { .. } => { | ||||
|             let req = ChargesRequest::try_from(req)?; | ||||
|             let request = types::RequestBody::log_and_get_request_body( | ||||
|                 &req, | ||||
| @ -2621,7 +2695,8 @@ pub fn get_bank_transfer_authorize_response( | ||||
|     bank_transfer_data: &api_models::payments::BankTransferData, | ||||
| ) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> { | ||||
|     match bank_transfer_data { | ||||
|         api_models::payments::BankTransferData::AchBankTransfer { .. } => { | ||||
|         api_models::payments::BankTransferData::AchBankTransfer { .. } | ||||
|         | api_models::payments::BankTransferData::MultibancoBankTransfer { .. } => { | ||||
|             let response: ChargesResponse = res | ||||
|                 .response | ||||
|                 .parse_struct("ChargesResponse") | ||||
|  | ||||
| @ -171,6 +171,8 @@ impl<Flow, Request, Response> RouterData for types::RouterData<Flow, Request, Re | ||||
| pub trait PaymentsPreProcessingData { | ||||
|     fn get_email(&self) -> Result<Email, Error>; | ||||
|     fn get_payment_method_type(&self) -> Result<storage_models::enums::PaymentMethodType, Error>; | ||||
|     fn get_currency(&self) -> Result<storage_models::enums::Currency, Error>; | ||||
|     fn get_amount(&self) -> Result<i64, Error>; | ||||
| } | ||||
|  | ||||
| impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { | ||||
| @ -182,6 +184,12 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { | ||||
|             .to_owned() | ||||
|             .ok_or_else(missing_field_err("payment_method_type")) | ||||
|     } | ||||
|     fn get_currency(&self) -> Result<storage_models::enums::Currency, Error> { | ||||
|         self.currency.ok_or_else(missing_field_err("currency")) | ||||
|     } | ||||
|     fn get_amount(&self) -> Result<i64, Error> { | ||||
|         self.amount.ok_or_else(missing_field_err("amount")) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub trait PaymentsAuthorizeRequestData { | ||||
|  | ||||
| @ -838,7 +838,8 @@ where | ||||
|     //TODO: For ACH transfers, if preprocessing_step is not required for connectors encountered in future, add the check | ||||
|     let router_data_and_should_continue_payment = match payment_data.payment_method_data.clone() { | ||||
|         Some(api_models::payments::PaymentMethodData::BankTransfer(data)) => match data.deref() { | ||||
|             api_models::payments::BankTransferData::AchBankTransfer { .. } => { | ||||
|             api_models::payments::BankTransferData::AchBankTransfer { .. } | ||||
|             | api_models::payments::BankTransferData::MultibancoBankTransfer { .. } => { | ||||
|                 if payment_data.payment_attempt.preprocessing_step_id.is_none() { | ||||
|                     ( | ||||
|                         router_data.preprocessing_steps(state, connector).await?, | ||||
|  | ||||
| @ -342,9 +342,10 @@ impl TryFrom<types::PaymentsAuthorizeData> for types::PaymentsPreProcessingData | ||||
|  | ||||
|     fn try_from(data: types::PaymentsAuthorizeData) -> Result<Self, Self::Error> { | ||||
|         Ok(Self { | ||||
|             payment_method_data: Some(data.payment_method_data), | ||||
|             amount: Some(data.amount), | ||||
|             email: data.email, | ||||
|             currency: Some(data.currency), | ||||
|             amount: Some(data.amount), | ||||
|             payment_method_type: data.payment_method_type, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
| @ -951,7 +951,10 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsPreProce | ||||
|  | ||||
|     fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> { | ||||
|         let payment_data = additional_data.payment_data; | ||||
|         let payment_method_data = payment_data.payment_method_data; | ||||
|  | ||||
|         Ok(Self { | ||||
|             payment_method_data, | ||||
|             email: payment_data.email, | ||||
|             currency: Some(payment_data.currency), | ||||
|             amount: Some(payment_data.amount.into()), | ||||
|  | ||||
| @ -244,9 +244,11 @@ Never share your secret api keys. Keep them guarded and secure. | ||||
|         api_models::payments::BankTransferNextStepsData, | ||||
|         api_models::payments::SepaAndBacsBillingDetails, | ||||
|         api_models::payments::AchBillingDetails, | ||||
|         api_models::payments::MultibancoBillingDetails, | ||||
|         api_models::payments::BankTransferInstructions, | ||||
|         api_models::payments::ReceiverDetails, | ||||
|         api_models::payments::AchTransfer, | ||||
|         api_models::payments::MultibancoTransferInstructions, | ||||
|         api_models::payments::ApplePayRedirectData, | ||||
|         api_models::payments::ApplePayThirdPartySdkData, | ||||
|         api_models::payments::GooglePayRedirectData, | ||||
|  | ||||
| @ -268,9 +268,10 @@ pub struct PaymentMethodTokenizationData { | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct PaymentsPreProcessingData { | ||||
|     pub payment_method_data: Option<payments::PaymentMethodData>, | ||||
|     pub amount: Option<i64>, | ||||
|     pub email: Option<Email>, | ||||
|     pub currency: Option<storage_enums::Currency>, | ||||
|     pub amount: Option<i64>, | ||||
|     pub payment_method_type: Option<storage_enums::PaymentMethodType>, | ||||
| } | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 AkshayaFoiger
					AkshayaFoiger