From eb418041cafc7df42da9ce1fb9a6db9dfa624f10 Mon Sep 17 00:00:00 2001 From: Sagnik Mitra Date: Thu, 5 Dec 2024 13:46:29 +0530 Subject: [PATCH] inespay sepa bank debit auth and psync flow --- crates/api_models/src/connector_enums.rs | 4 +- crates/common_enums/src/connector_enums.rs | 2 +- crates/connector_configs/src/connector.rs | 4 +- .../src/connectors/inespay.rs | 50 ++++-- .../src/connectors/inespay/transformers.rs | 154 +++++++++++++----- crates/router/src/core/admin.rs | 8 +- crates/router/src/types/api.rs | 6 +- crates/router/src/types/transformers.rs | 2 +- 8 files changed, 159 insertions(+), 71 deletions(-) diff --git a/crates/api_models/src/connector_enums.rs b/crates/api_models/src/connector_enums.rs index 4931b8dbd922..8e1f26f75c0f 100644 --- a/crates/api_models/src/connector_enums.rs +++ b/crates/api_models/src/connector_enums.rs @@ -84,7 +84,7 @@ pub enum Connector { Gocardless, Gpayments, Helcim, - // Inespay, + Inespay, Iatapay, Itaubank, //Jpmorgan, @@ -230,7 +230,7 @@ impl Connector { | Self::Gpayments | Self::Helcim | Self::Iatapay - // | Self::Inespay + | Self::Inespay | Self::Itaubank //| Self::Jpmorgan | Self::Klarna diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 421a51205dea..350ba1c88a1e 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -81,7 +81,7 @@ pub enum RoutableConnectors { Gocardless, Helcim, Iatapay, - // Inespay, + Inespay, Itaubank, //Jpmorgan, Klarna, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index c1f7cfc366c7..393530db0311 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -192,7 +192,7 @@ pub struct ConnectorConfig { pub gocardless: Option, pub gpayments: Option, pub helcim: Option, - // pub inespay: Option, + pub inespay: Option, pub klarna: Option, pub mifinity: Option, pub mollie: Option, @@ -358,7 +358,7 @@ impl ConnectorConfig { Connector::Gocardless => Ok(connector_data.gocardless), Connector::Gpayments => Ok(connector_data.gpayments), Connector::Helcim => Ok(connector_data.helcim), - // Connector::Inespay => Ok(connector_data.inespay), + Connector::Inespay => Ok(connector_data.inespay), Connector::Klarna => Ok(connector_data.klarna), Connector::Mifinity => Ok(connector_data.mifinity), Connector::Mollie => Ok(connector_data.mollie), diff --git a/crates/hyperswitch_connectors/src/connectors/inespay.rs b/crates/hyperswitch_connectors/src/connectors/inespay.rs index 89bf50c60ca9..ce924a6c2ff8 100644 --- a/crates/hyperswitch_connectors/src/connectors/inespay.rs +++ b/crates/hyperswitch_connectors/src/connectors/inespay.rs @@ -28,7 +28,7 @@ use hyperswitch_domain_models::{ use hyperswitch_interfaces::{ api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, configs::Connectors, - errors, + consts, errors, events::connector_api_logs::ConnectorEvent, types::{self, Response}, webhooks, @@ -83,8 +83,8 @@ where headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - header.append(&mut api_key); + let mut auth_headers = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut auth_headers); Ok(header) } } @@ -95,7 +95,7 @@ impl ConnectorCommon for Inespay { } fn get_currency_unit(&self) -> api::CurrencyUnit { - api::CurrencyUnit::Base + api::CurrencyUnit::Minor // TODO! Check connector documentation, on which unit they are processing the currency. // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base @@ -115,10 +115,16 @@ impl ConnectorCommon for Inespay { ) -> CustomResult)>, errors::ConnectorError> { let auth = inespay::InespayAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - Ok(vec![( - headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), - )]) + Ok(vec![ + ( + headers::AUTHORIZATION.to_string(), + auth.authorization.expose().into_masked(), + ), + ( + headers::X_API_KEY.to_string(), + auth.api_key.expose().into_masked(), + ), + ]) } fn build_error_response( @@ -136,9 +142,9 @@ impl ConnectorCommon for Inespay { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: consts::NO_ERROR_CODE.to_string(), + message: response.message.clone(), + reason: Some(response.message.clone()), attempt_status: None, connector_transaction_id: None, }) @@ -173,9 +179,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/payins/single/init", self.base_url(connectors))) } fn get_request_body( @@ -259,10 +265,20 @@ impl ConnectorIntegration for Ine fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}{}{}", + self.base_url(connectors), + "/payins/single/", + connector_payment_id, + )) } fn build_request( @@ -286,7 +302,7 @@ impl ConnectorIntegration for Ine event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: inespay::InespayPaymentsResponse = res + let response: inespay::InespayPSyncResponse = res .response .parse_struct("inespay PaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; diff --git a/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs index 296d76546c84..94b521c8d954 100644 --- a/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs @@ -1,20 +1,22 @@ use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::{request::Method, types::StringMinorUnit}; +use error_stack::ResultExt; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, + payment_method_data::{BankDebitData, PaymentMethodData}, router_data::{ConnectorAuthType, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, types::{PaymentsAuthorizeRouterData, RefundsRouterData}, }; use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; +use url::Url; use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + utils::RouterData as _, }; //TODO: Fill the struct with respective fields @@ -35,9 +37,12 @@ impl From<(StringMinorUnit, T)> for InespayRouterData { //TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct InespayPaymentsRequest { + description: String, amount: StringMinorUnit, - card: InespayCard, + reference: String, + debtor_account: Secret, } #[derive(Default, Debug, Serialize, Eq, PartialEq)] @@ -55,17 +60,13 @@ impl TryFrom<&InespayRouterData<&PaymentsAuthorizeRouterData>> for InespayPaymen item: &InespayRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = InespayCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; + PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. }) => { + let order_id = item.router_data.connector_request_reference_id.clone(); Ok(Self { + description: item.router_data.get_description()?, amount: item.amount.clone(), - card, + reference: order_id, + debtor_account: iban, }) } _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), @@ -77,59 +78,133 @@ impl TryFrom<&InespayRouterData<&PaymentsAuthorizeRouterData>> for InespayPaymen // Auth Struct pub struct InespayAuthType { pub(super) api_key: Secret, + pub authorization: Secret, } impl TryFrom<&ConnectorAuthType> for InespayAuthType { type Error = error_stack::Report; fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { api_key: api_key.to_owned(), + authorization: key1.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum InespayPaymentStatus { - Succeeded, + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct InespayPaymentsResponse { + status: String, + status_desc: String, + single_payin_id: String, + single_payin_link: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + let redirection_url = Url::parse(item.response.single_payin_link.as_str()) + .change_context(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let redirection_data = RedirectForm::from((redirection_url, Method::Get)); + let status = match item.response.status_desc.as_str() { + "Success" => common_enums::AttemptStatus::AuthenticationPending, + _ => common_enums::AttemptStatus::Failure, + }; + + Ok(Self { + status, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + item.response.single_payin_id.clone(), + ), + redirection_data: Box::new(Some(redirection_data)), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum InespayPSyncStatus { + Ok, + Created, + Opened, + BankSelected, + Initiated, + Pending, + Aborted, + Unfinished, + Rejected, + Cancelled, + PartiallyAccepted, Failed, - #[default] - Processing, + Settled, + PartRefunded, + Refunded, } -impl From for common_enums::AttemptStatus { - fn from(item: InespayPaymentStatus) -> Self { +impl From for common_enums::AttemptStatus { + fn from(item: InespayPSyncStatus) -> Self { match item { - InespayPaymentStatus::Succeeded => Self::Charged, - InespayPaymentStatus::Failed => Self::Failure, - InespayPaymentStatus::Processing => Self::Authorizing, + InespayPSyncStatus::Ok | InespayPSyncStatus::Settled => Self::Charged, + InespayPSyncStatus::Created + | InespayPSyncStatus::Opened + | InespayPSyncStatus::BankSelected + | InespayPSyncStatus::Initiated + | InespayPSyncStatus::Pending + | InespayPSyncStatus::Unfinished + | InespayPSyncStatus::PartiallyAccepted => Self::AuthenticationPending, + InespayPSyncStatus::Aborted + | InespayPSyncStatus::Rejected + | InespayPSyncStatus::Cancelled + | InespayPSyncStatus::Failed => Self::Failure, + InespayPSyncStatus::PartRefunded | InespayPSyncStatus::Refunded => Self::AutoRefunded, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct InespayPaymentsResponse { - status: InespayPaymentStatus, - id: String, +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct InespayPSyncResponse { + cod_status: InespayPSyncStatus, + status_desc: String, + single_payin_id: String, + single_payin_link: String, } -impl TryFrom> +impl TryFrom> for RouterData { type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, + item: ResponseRouterData, ) -> Result { + let redirection_url = Url::parse(item.response.single_payin_link.as_str()) + .change_context(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let redirection_data = RedirectForm::from((redirection_url, Method::Get)); + Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), + status: common_enums::AttemptStatus::from(item.response.cod_status), response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: Box::new(None), + resource_id: ResponseId::ConnectorTransactionId( + item.response.single_payin_id.clone(), + ), + redirection_data: Box::new(Some(redirection_data)), mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, @@ -221,8 +296,5 @@ impl TryFrom> for RefundsRouter //TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] pub struct InespayErrorResponse { - pub status_code: u16, - pub code: String, pub message: String, - pub reason: Option, } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 055c428b1fbc..411b4da44506 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1393,10 +1393,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { iatapay::transformers::IatapayAuthType::try_from(self.auth_type)?; Ok(()) } - // api_enums::Connector::Inespay => { - // inespay::transformers::InespayAuthType::try_from(self.auth_type)?; - // Ok(()) - // } + api_enums::Connector::Inespay => { + inespay::transformers::InespayAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Itaubank => { itaubank::transformers::ItaubankAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 975b219a26ca..80a6babad356 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -434,9 +434,9 @@ impl ConnectorData { enums::Connector::Iatapay => { Ok(ConnectorEnum::Old(Box::new(connector::Iatapay::new()))) } - // enums::Connector::Inespay => { - // Ok(ConnectorEnum::Old(Box::new(connector::Inespay::new()))) - // } + enums::Connector::Inespay => { + Ok(ConnectorEnum::Old(Box::new(connector::Inespay::new()))) + } enums::Connector::Itaubank => { //enums::Connector::Jpmorgan => Ok(ConnectorEnum::Old(Box::new(connector::Jpmorgan))), Ok(ConnectorEnum::Old(Box::new(connector::Itaubank::new()))) diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 55a523b528eb..64513bd0e2be 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -249,7 +249,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { } api_enums::Connector::Helcim => Self::Helcim, api_enums::Connector::Iatapay => Self::Iatapay, - // api_enums::Connector::Inespay => Self::Inespay, + api_enums::Connector::Inespay => Self::Inespay, api_enums::Connector::Itaubank => Self::Itaubank, //api_enums::Connector::Jpmorgan => Self::Jpmorgan, api_enums::Connector::Klarna => Self::Klarna,