diff --git a/.github/workflows/CI-pr.yml b/.github/workflows/CI-pr.yml index ecb13f3c1a85..79cb352acbb8 100644 --- a/.github/workflows/CI-pr.yml +++ b/.github/workflows/CI-pr.yml @@ -203,6 +203,11 @@ jobs: else echo "test_utils_changes_exist=true" >> $GITHUB_ENV fi + if git diff --submodule=diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/pm_auth/; then + echo "pm_auth_changes_exist=false" >> $GITHUB_ENV + else + echo "pm_auth_changes_exist=true" >> $GITHUB_ENV + fi - name: Cargo hack api_models if: env.api_models_changes_exist == 'true' @@ -249,6 +254,11 @@ jobs: shell: bash run: cargo hack check --each-feature --no-dev-deps -p redis_interface + - name: Cargo hack pm_auth + if: env.pm_auth_changes_exist == 'true' + shell: bash + run: cargo hack check --each-feature --no-dev-deps -p pm_auth + - name: Cargo hack router if: env.router_changes_exist == 'true' shell: bash @@ -456,6 +466,11 @@ jobs: else echo "test_utils_changes_exist=true" >> $GITHUB_ENV fi + if git diff --submodule=diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/pm_auth/; then + echo "pm_auth_changes_exist=false" >> $GITHUB_ENV + else + echo "pm_auth_changes_exist=true" >> $GITHUB_ENV + fi - name: Cargo hack api_models if: env.api_models_changes_exist == 'true' @@ -502,6 +517,11 @@ jobs: shell: bash run: cargo hack check --each-feature --no-dev-deps -p redis_interface + - name: Cargo hack pm_auth + if: env.pm_auth_changes_exist == 'true' + shell: bash + run: cargo hack check --each-feature --no-dev-deps -p pm_auth + - name: Cargo hack router if: env.router_changes_exist == 'true' shell: bash diff --git a/Cargo.lock b/Cargo.lock index e8719b29f51d..dd1478c0459b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4435,6 +4435,27 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "pm_auth" +version = "0.1.0" +dependencies = [ + "api_models", + "async-trait", + "bytes 1.5.0", + "common_enums", + "common_utils", + "error-stack", + "http", + "masking", + "mime", + "router_derive", + "router_env", + "serde", + "serde_json", + "strum 0.24.1", + "thiserror", +] + [[package]] name = "png" version = "0.16.8" diff --git a/crates/pm_auth/Cargo.toml b/crates/pm_auth/Cargo.toml new file mode 100644 index 000000000000..a9aebc5b540a --- /dev/null +++ b/crates/pm_auth/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "pm_auth" +description = "Open banking services" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +readme = "README.md" + +[dependencies] +# First party crates +api_models = { version = "0.1.0", path = "../api_models" } +common_enums = { version = "0.1.0", path = "../common_enums" } +common_utils = { version = "0.1.0", path = "../common_utils" } +masking = { version = "0.1.0", path = "../masking" } +router_derive = { version = "0.1.0", path = "../router_derive" } +router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } + +# Third party crates +async-trait = "0.1.66" +bytes = "1.4.0" +error-stack = "0.3.1" +http = "0.2.9" +mime = "0.3.17" +serde = "1.0.159" +serde_json = "1.0.91" +strum = { version = "0.24.1", features = ["derive"] } +thiserror = "1.0.43" diff --git a/crates/pm_auth/README.md b/crates/pm_auth/README.md new file mode 100644 index 000000000000..c630a7fe6761 --- /dev/null +++ b/crates/pm_auth/README.md @@ -0,0 +1,3 @@ +# Payment Method Auth Services + +An open banking services for payment method auth validation diff --git a/crates/pm_auth/src/connector.rs b/crates/pm_auth/src/connector.rs new file mode 100644 index 000000000000..56aad846e248 --- /dev/null +++ b/crates/pm_auth/src/connector.rs @@ -0,0 +1,3 @@ +pub mod plaid; + +pub use self::plaid::Plaid; diff --git a/crates/pm_auth/src/connector/plaid.rs b/crates/pm_auth/src/connector/plaid.rs new file mode 100644 index 000000000000..86953315afe8 --- /dev/null +++ b/crates/pm_auth/src/connector/plaid.rs @@ -0,0 +1,268 @@ +pub mod transformers; + +use std::fmt::Debug; + +use common_utils::{ + ext_traits::{BytesExt, Encode}, + request::{Method, Request, RequestBody, RequestBuilder}, +}; +use error_stack::ResultExt; +use masking::{Mask, Maskable}; +use transformers as plaid; + +use crate::{ + core::errors, + types::{ + self as auth_types, + api::{ + auth_service::{self, ExchangeToken, LinkToken}, + ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, + }, + }, +}; + +#[derive(Debug, Clone)] +pub struct Plaid; + +impl ConnectorCommonExt for Plaid +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &auth_types::PaymentAuthRouterData, + _connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + "Content-Type".to_string(), + self.get_content_type().to_string().into(), + )]; + + let mut auth = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut auth); + Ok(header) + } +} + +impl ConnectorCommon for Plaid { + fn id(&self) -> &'static str { + "plaid" + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + fn base_url<'a>(&self, _connectors: &'a auth_types::PaymentMethodAuthConnectors) -> &'a str { + "https://sandbox.plaid.com" + } + + fn get_auth_header( + &self, + auth_type: &auth_types::ConnectorAuthType, + ) -> errors::CustomResult)>, errors::ConnectorError> { + let auth = plaid::PlaidAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let client_id = auth.client_id.into_masked(); + let secret = auth.secret.into_masked(); + + Ok(vec![ + ("PLAID-CLIENT-ID".to_string(), client_id), + ("PLAID-SECRET".to_string(), secret), + ]) + } + + fn build_error_response( + &self, + res: auth_types::Response, + ) -> errors::CustomResult { + let response: plaid::PlaidErrorResponse = + res.response + .parse_struct("PlaidErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + Ok(auth_types::ErrorResponse { + status_code: res.status_code, + code: crate::consts::NO_ERROR_CODE.to_string(), + message: response.error_message, + reason: response.display_message, + }) + } +} + +impl auth_service::AuthService for Plaid {} +impl auth_service::AuthServiceLinkToken for Plaid {} + +impl ConnectorIntegration + for Plaid +{ + fn get_headers( + &self, + req: &auth_types::LinkTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &auth_types::LinkTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "/link/token/create" + )) + } + + fn get_request_body( + &self, + req: &auth_types::LinkTokenRouterData, + ) -> errors::CustomResult, errors::ConnectorError> { + let req_obj = plaid::PlaidLinkTokenRequest::try_from(req)?; + let plaid_req = RequestBody::log_and_get_request_body( + &req_obj, + Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(plaid_req)) + } + + fn build_request( + &self, + req: &auth_types::LinkTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&auth_types::PaymentAuthLinkTokenType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(auth_types::PaymentAuthLinkTokenType::get_headers( + self, req, connectors, + )?) + .body(auth_types::PaymentAuthLinkTokenType::get_request_body( + self, req, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &auth_types::LinkTokenRouterData, + res: auth_types::Response, + ) -> errors::CustomResult { + let response: plaid::PlaidLinkTokenResponse = res + .response + .parse_struct("PlaidLinkTokenResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + ::try_from(auth_types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + fn get_error_response( + &self, + res: auth_types::Response, + ) -> errors::CustomResult { + self.build_error_response(res) + } +} + +impl auth_service::AuthServiceExchangeToken for Plaid {} + +impl + ConnectorIntegration< + ExchangeToken, + auth_types::ExchangeTokenRequest, + auth_types::ExchangeTokenResponse, + > for Plaid +{ + fn get_headers( + &self, + req: &auth_types::ExchangeTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &auth_types::ExchangeTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "/item/public_token/exchange" + )) + } + + fn get_request_body( + &self, + req: &auth_types::ExchangeTokenRouterData, + ) -> errors::CustomResult, errors::ConnectorError> { + let req_obj = plaid::PlaidExchangeTokenRequest::try_from(req)?; + let plaid_req = RequestBody::log_and_get_request_body( + &req_obj, + Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(plaid_req)) + } + + fn build_request( + &self, + req: &auth_types::ExchangeTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&auth_types::PaymentAuthExchangeTokenType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(auth_types::PaymentAuthExchangeTokenType::get_headers( + self, req, connectors, + )?) + .body(auth_types::PaymentAuthExchangeTokenType::get_request_body( + self, req, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &auth_types::ExchangeTokenRouterData, + res: auth_types::Response, + ) -> errors::CustomResult { + let response: plaid::PlaidExchangeTokenResponse = res + .response + .parse_struct("PlaidExchangeTokenResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + ::try_from(auth_types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + fn get_error_response( + &self, + res: auth_types::Response, + ) -> errors::CustomResult { + self.build_error_response(res) + } +} diff --git a/crates/pm_auth/src/connector/plaid/transformers.rs b/crates/pm_auth/src/connector/plaid/transformers.rs new file mode 100644 index 000000000000..370be89fd111 --- /dev/null +++ b/crates/pm_auth/src/connector/plaid/transformers.rs @@ -0,0 +1,151 @@ +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{core::errors, types}; + +#[derive(Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct PlaidLinkTokenRequest { + client_name: String, + country_codes: Vec, + language: String, + products: Vec, + user: User, +} + +#[derive(Debug, Serialize, Eq, PartialEq)] + +pub struct User { + pub client_user_id: String, +} + +impl TryFrom<&types::LinkTokenRouterData> for PlaidLinkTokenRequest { + type Error = error_stack::Report; + fn try_from(item: &types::LinkTokenRouterData) -> Result { + Ok(Self { + client_name: item.request.client_name.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "client_name", + }, + )?, + country_codes: item.request.country_codes.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "country_codes", + }, + )?, + language: item.request.language.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "language", + }, + )?, + products: vec!["auth".to_string()], + user: User { + client_user_id: item.request.user_info.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "user.client_user_id", + }, + )?, + }, + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct PlaidLinkTokenResponse { + expiration: String, + request_id: String, + link_token: String, +} + +impl + TryFrom> + for types::PaymentAuthRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::LinkTokenResponse { + expiration: Some(item.response.expiration), + request_id: Some(item.response.request_id), + link_token: Some(item.response.link_token), + }), + ..item.data + }) + } +} + +#[derive(Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct PlaidExchangeTokenRequest { + public_token: String, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] + +pub struct PlaidExchangeTokenResponse { + pub access_token: String, + pub request_id: String, +} + +impl + TryFrom< + types::ResponseRouterData, + > for types::PaymentAuthRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + PlaidExchangeTokenResponse, + T, + types::ExchangeTokenResponse, + >, + ) -> Result { + Ok(Self { + response: Ok(types::ExchangeTokenResponse { + access_token: Some(item.response.access_token), + request_id: Some(item.response.request_id), + }), + ..item.data + }) + } +} + +impl TryFrom<&types::ExchangeTokenRouterData> for PlaidExchangeTokenRequest { + type Error = error_stack::Report; + fn try_from(item: &types::ExchangeTokenRouterData) -> Result { + Ok(Self { + public_token: item.request.public_token.clone(), + }) + } +} + +pub struct PlaidAuthType { + pub client_id: Secret, + pub secret: Secret, +} + +impl TryFrom<&types::ConnectorAuthType> for PlaidAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + match auth_type { + types::ConnectorAuthType::BodyKey { client_id, secret } => Ok(Self { + client_id: client_id.to_owned(), + secret: secret.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct PlaidErrorResponse { + pub display_message: Option, + pub error_code: Option, + pub error_message: String, + pub error_type: Option, +} diff --git a/crates/pm_auth/src/consts.rs b/crates/pm_auth/src/consts.rs new file mode 100644 index 000000000000..f3e535c6d7b0 --- /dev/null +++ b/crates/pm_auth/src/consts.rs @@ -0,0 +1,2 @@ +pub(crate) const NO_ERROR_CODE: &str = "No error code"; +pub(crate) const NO_ERROR_MESSAGE: &str = "No error message"; diff --git a/crates/pm_auth/src/core.rs b/crates/pm_auth/src/core.rs new file mode 100644 index 000000000000..629e98fbf874 --- /dev/null +++ b/crates/pm_auth/src/core.rs @@ -0,0 +1 @@ +pub mod errors; diff --git a/crates/pm_auth/src/core/errors.rs b/crates/pm_auth/src/core/errors.rs new file mode 100644 index 000000000000..597455ecbf51 --- /dev/null +++ b/crates/pm_auth/src/core/errors.rs @@ -0,0 +1,25 @@ +#[derive(Debug, thiserror::Error, PartialEq)] +pub enum ConnectorError { + #[error("Failed to obtain authentication type")] + FailedToObtainAuthType, + #[error("Missing required field: {field_name}")] + MissingRequiredField { field_name: &'static str }, + #[error("Failed to deserialize connector response")] + ResponseDeserializationFailed, + #[error("Failed to encode connector request")] + RequestEncodingFailed, +} + +pub type CustomResult = error_stack::Result; + +#[derive(Debug, thiserror::Error)] +pub enum ParsingError { + #[error("Failed to parse enum: {0}")] + EnumParseFailure(&'static str), + #[error("Failed to parse struct: {0}")] + StructParseFailure(&'static str), + #[error("Failed to serialize to {0} format")] + EncodeError(&'static str), + #[error("Unknown error while parsing")] + UnknownError, +} diff --git a/crates/pm_auth/src/lib.rs b/crates/pm_auth/src/lib.rs new file mode 100644 index 000000000000..60d0e06a1e00 --- /dev/null +++ b/crates/pm_auth/src/lib.rs @@ -0,0 +1,4 @@ +pub mod connector; +pub mod consts; +pub mod core; +pub mod types; diff --git a/crates/pm_auth/src/types.rs b/crates/pm_auth/src/types.rs new file mode 100644 index 000000000000..1cccb11cee0f --- /dev/null +++ b/crates/pm_auth/src/types.rs @@ -0,0 +1,132 @@ +pub mod api; + +use std::marker::PhantomData; + +use api::auth_service::{BankAccountCredentials, ExchangeToken, LinkToken}; +use masking::Secret; +#[derive(Debug, Clone)] +pub struct PaymentAuthRouterData { + pub flow: PhantomData, + pub merchant_id: Option, + pub connector: Option, + pub request: Request, + pub response: Result, + pub connector_auth_type: ConnectorAuthType, +} + +#[derive(Debug, Clone)] +pub struct LinkTokenRequest { + pub client_name: Option, + pub country_codes: Option>, + pub language: Option, + pub user_info: Option, +} + +#[derive(Debug, Clone)] +pub struct LinkTokenResponse { + pub expiration: Option, + pub link_token: Option, + pub request_id: Option, +} + +pub type LinkTokenRouterData = + PaymentAuthRouterData; + +#[derive(Debug, Clone)] +pub struct ExchangeTokenRequest { + pub public_token: String, +} + +#[derive(Debug, Clone)] +pub struct ExchangeTokenResponse { + pub access_token: Option, + pub request_id: Option, +} + +pub type ExchangeTokenRouterData = + PaymentAuthRouterData; + +#[derive(Debug, Clone)] +pub struct BankAccountCredentialsRequest { + pub access_token: String, +} + +#[derive(Debug, Clone)] +pub struct BankAccountCredentialsResponse { + pub credentials: Vec, +} + +#[derive(Debug, Clone)] +pub struct BankAccountDetails { + pub account_id: String, + pub account_name: String, + pub account_number: String, + pub routing_number: String, +} + +pub type BankDetailsRouterData = PaymentAuthRouterData< + BankAccountCredentials, + BankAccountCredentialsRequest, + BankAccountCredentialsResponse, +>; + +pub type PaymentAuthLinkTokenType = + dyn self::api::ConnectorIntegration; + +pub type PaymentAuthExchangeTokenType = + dyn self::api::ConnectorIntegration; + +pub type PaymentAuthBankAccountDetailsType = dyn self::api::ConnectorIntegration< + BankAccountCredentials, + BankAccountCredentialsRequest, + BankAccountCredentialsResponse, +>; + +#[derive(Clone, Debug, strum::EnumString)] +#[strum(serialize_all = "snake_case")] +pub enum PaymentMethodAuthConnectors { + Plaid, +} + +#[derive(Debug, Clone)] +pub struct ResponseRouterData { + pub response: R, + pub data: PaymentAuthRouterData, + pub http_code: u16, +} + +#[derive(Clone, Debug, serde::Serialize)] +pub struct ErrorResponse { + pub code: String, + pub message: String, + pub reason: Option, + pub status_code: u16, +} + +impl ErrorResponse { + fn get_not_implemented() -> Self { + Self { + code: "IR_00".to_string(), + message: "This API is under development and will be made available soon.".to_string(), + reason: None, + status_code: http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + } + } +} + +#[derive(Default, Debug, Clone, serde::Deserialize)] +pub enum ConnectorAuthType { + BodyKey { + client_id: Secret, + secret: Secret, + }, + #[default] + NoKey, +} + +#[derive(Clone, Debug)] +pub struct Response { + pub headers: Option, + pub response: bytes::Bytes, + pub status_code: u16, +} diff --git a/crates/pm_auth/src/types/api.rs b/crates/pm_auth/src/types/api.rs new file mode 100644 index 000000000000..6f083dc42bdd --- /dev/null +++ b/crates/pm_auth/src/types/api.rs @@ -0,0 +1,167 @@ +pub mod auth_service; + +use std::fmt::Debug; + +use common_utils::{ + errors::CustomResult, + request::{Request, RequestBody}, +}; +use masking::Maskable; + +use crate::{ + core::errors::ConnectorError, + types::{self as auth_types, api::auth_service::AuthService}, +}; + +#[async_trait::async_trait] +pub trait ConnectorIntegration: ConnectorIntegrationAny + Sync { + fn get_headers( + &self, + _req: &super::PaymentAuthRouterData, + _connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> CustomResult)>, ConnectorError> { + Ok(vec![]) + } + + fn get_content_type(&self) -> &'static str { + mime::APPLICATION_JSON.essence_str() + } + + fn get_url( + &self, + _req: &super::PaymentAuthRouterData, + _connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> CustomResult { + Ok(String::new()) + } + + fn get_request_body( + &self, + _req: &super::PaymentAuthRouterData, + ) -> CustomResult, ConnectorError> { + Ok(None) + } + + fn build_request( + &self, + _req: &super::PaymentAuthRouterData, + _connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> CustomResult, ConnectorError> { + Ok(None) + } + + fn handle_response( + &self, + data: &super::PaymentAuthRouterData, + _res: auth_types::Response, + ) -> CustomResult, ConnectorError> + where + T: Clone, + Req: Clone, + Resp: Clone, + { + Ok(data.clone()) + } + + fn get_error_response( + &self, + _res: auth_types::Response, + ) -> CustomResult { + Ok(auth_types::ErrorResponse::get_not_implemented()) + } + + fn get_5xx_error_response( + &self, + res: auth_types::Response, + ) -> CustomResult { + let error_message = match res.status_code { + 500 => "internal_server_error", + 501 => "not_implemented", + 502 => "bad_gateway", + 503 => "service_unavailable", + 504 => "gateway_timeout", + 505 => "http_version_not_supported", + 506 => "variant_also_negotiates", + 507 => "insufficient_storage", + 508 => "loop_detected", + 510 => "not_extended", + 511 => "network_authentication_required", + _ => "unknown_error", + }; + Ok(auth_types::ErrorResponse { + code: res.status_code.to_string(), + message: error_message.to_string(), + reason: String::from_utf8(res.response.to_vec()).ok(), + status_code: res.status_code, + }) + } +} + +pub trait ConnectorCommonExt: + ConnectorCommon + ConnectorIntegration +{ + fn build_headers( + &self, + _req: &auth_types::PaymentAuthRouterData, + _connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> CustomResult)>, ConnectorError> { + Ok(Vec::new()) + } +} + +pub type BoxedConnectorIntegration<'a, T, Req, Resp> = + Box<&'a (dyn ConnectorIntegration + Send + Sync)>; + +pub trait ConnectorIntegrationAny: Send + Sync + 'static { + fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp>; +} + +impl ConnectorIntegrationAny for S +where + S: ConnectorIntegration, +{ + fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp> { + Box::new(self) + } +} + +pub trait AuthServiceConnector: AuthService + Send + Debug {} + +impl AuthServiceConnector for T {} + +type BoxedPaymentAuthConnector = Box<&'static (dyn AuthServiceConnector + Sync)>; + +#[derive(Clone, Debug)] +pub struct PaymentAuthConnectorData { + pub connector: BoxedPaymentAuthConnector, + pub connector_name: super::PaymentMethodAuthConnectors, +} + +pub trait ConnectorCommon { + fn id(&self) -> &'static str; + + fn get_auth_header( + &self, + _auth_type: &auth_types::ConnectorAuthType, + ) -> CustomResult)>, ConnectorError> { + Ok(Vec::new()) + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a auth_types::PaymentMethodAuthConnectors) -> &'a str; + + fn build_error_response( + &self, + res: auth_types::Response, + ) -> CustomResult { + Ok(auth_types::ErrorResponse { + status_code: res.status_code, + code: crate::consts::NO_ERROR_CODE.to_string(), + message: crate::consts::NO_ERROR_MESSAGE.to_string(), + reason: None, + }) + } +} diff --git a/crates/pm_auth/src/types/api/auth_service.rs b/crates/pm_auth/src/types/api/auth_service.rs new file mode 100644 index 000000000000..8cee37b4b9b7 --- /dev/null +++ b/crates/pm_auth/src/types/api/auth_service.rs @@ -0,0 +1,37 @@ +use crate::types::{ + BankAccountCredentialsRequest, BankAccountCredentialsResponse, ExchangeTokenRequest, + ExchangeTokenResponse, LinkTokenRequest, LinkTokenResponse, +}; + +pub trait AuthService: + super::ConnectorCommon + AuthServiceLinkToken + AuthServiceExchangeToken +{ +} + +#[derive(Debug, Clone)] +pub struct LinkToken; + +pub trait AuthServiceLinkToken: + super::ConnectorIntegration +{ +} + +#[derive(Debug, Clone)] +pub struct ExchangeToken; + +pub trait AuthServiceExchangeToken: + super::ConnectorIntegration +{ +} + +#[derive(Debug, Clone)] +pub struct BankAccountCredentials; + +pub trait AuthServiceBankAccountCredentials: + super::ConnectorIntegration< + BankAccountCredentials, + BankAccountCredentialsRequest, + BankAccountCredentialsResponse, +> +{ +}