feat(pm_auth): pm_auth service migration (#3047)

Co-authored-by: Sarthak Soni <76486416+Sarthak1799@users.noreply.github.com>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Sarthak Soni <sarthak.soni@juspay.in>
This commit is contained in:
Chethan Rao
2023-12-06 20:48:41 +05:30
committed by GitHub
parent 294b04bcdd
commit 9c1c44a706
40 changed files with 2492 additions and 25 deletions

View File

@ -0,0 +1,353 @@
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, BankAccountCredentials, ExchangeToken, LinkToken},
ConnectorCommon, ConnectorCommonExt, ConnectorIntegration,
},
},
};
#[derive(Debug, Clone)]
pub struct Plaid;
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Plaid
where
Self: ConnectorIntegration<Flow, Request, Response>,
{
fn build_headers(
&self,
req: &auth_types::PaymentAuthRouterData<Flow, Request, Response>,
_connectors: &auth_types::PaymentMethodAuthConnectors,
) -> errors::CustomResult<Vec<(String, Maskable<String>)>, 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<Vec<(String, Maskable<String>)>, 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<auth_types::ErrorResponse, errors::ConnectorError> {
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<LinkToken, auth_types::LinkTokenRequest, auth_types::LinkTokenResponse>
for Plaid
{
fn get_headers(
&self,
req: &auth_types::LinkTokenRouterData,
connectors: &auth_types::PaymentMethodAuthConnectors,
) -> errors::CustomResult<Vec<(String, Maskable<String>)>, 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<String, errors::ConnectorError> {
Ok(format!(
"{}{}",
self.base_url(connectors),
"/link/token/create"
))
}
fn get_request_body(
&self,
req: &auth_types::LinkTokenRouterData,
) -> errors::CustomResult<Option<RequestBody>, errors::ConnectorError> {
let req_obj = plaid::PlaidLinkTokenRequest::try_from(req)?;
let plaid_req = RequestBody::log_and_get_request_body(
&req_obj,
Encode::<plaid::PlaidLinkTokenRequest>::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<Option<Request>, 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<auth_types::LinkTokenRouterData, errors::ConnectorError> {
let response: plaid::PlaidLinkTokenResponse = res
.response
.parse_struct("PlaidLinkTokenResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
<auth_types::LinkTokenRouterData>::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<auth_types::ErrorResponse, errors::ConnectorError> {
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<Vec<(String, Maskable<String>)>, 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<String, errors::ConnectorError> {
Ok(format!(
"{}{}",
self.base_url(connectors),
"/item/public_token/exchange"
))
}
fn get_request_body(
&self,
req: &auth_types::ExchangeTokenRouterData,
) -> errors::CustomResult<Option<RequestBody>, errors::ConnectorError> {
let req_obj = plaid::PlaidExchangeTokenRequest::try_from(req)?;
let plaid_req = RequestBody::log_and_get_request_body(
&req_obj,
Encode::<plaid::PlaidExchangeTokenRequest>::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<Option<Request>, 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<auth_types::ExchangeTokenRouterData, errors::ConnectorError> {
let response: plaid::PlaidExchangeTokenResponse = res
.response
.parse_struct("PlaidExchangeTokenResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
<auth_types::ExchangeTokenRouterData>::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<auth_types::ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl auth_service::AuthServiceBankAccountCredentials for Plaid {}
impl
ConnectorIntegration<
BankAccountCredentials,
auth_types::BankAccountCredentialsRequest,
auth_types::BankAccountCredentialsResponse,
> for Plaid
{
fn get_headers(
&self,
req: &auth_types::BankDetailsRouterData,
connectors: &auth_types::PaymentMethodAuthConnectors,
) -> errors::CustomResult<Vec<(String, Maskable<String>)>, 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::BankDetailsRouterData,
connectors: &auth_types::PaymentMethodAuthConnectors,
) -> errors::CustomResult<String, errors::ConnectorError> {
Ok(format!("{}{}", self.base_url(connectors), "/auth/get"))
}
fn get_request_body(
&self,
req: &auth_types::BankDetailsRouterData,
) -> errors::CustomResult<Option<RequestBody>, errors::ConnectorError> {
let req_obj = plaid::PlaidBankAccountCredentialsRequest::try_from(req)?;
let plaid_req = RequestBody::log_and_get_request_body(
&req_obj,
Encode::<plaid::PlaidBankAccountCredentialsRequest>::encode_to_string_of_json,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(plaid_req))
}
fn build_request(
&self,
req: &auth_types::BankDetailsRouterData,
connectors: &auth_types::PaymentMethodAuthConnectors,
) -> errors::CustomResult<Option<Request>, errors::ConnectorError> {
Ok(Some(
RequestBuilder::new()
.method(Method::Post)
.url(&auth_types::PaymentAuthBankAccountDetailsType::get_url(
self, req, connectors,
)?)
.attach_default_headers()
.headers(auth_types::PaymentAuthBankAccountDetailsType::get_headers(
self, req, connectors,
)?)
.body(auth_types::PaymentAuthBankAccountDetailsType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &auth_types::BankDetailsRouterData,
res: auth_types::Response,
) -> errors::CustomResult<auth_types::BankDetailsRouterData, errors::ConnectorError> {
let response: plaid::PlaidBankAccountCredentialsResponse = res
.response
.parse_struct("PlaidBankAccountCredentialsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
<auth_types::BankDetailsRouterData>::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<auth_types::ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}

View File

@ -0,0 +1,294 @@
use std::collections::HashMap;
use common_enums::PaymentMethodType;
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<String>,
language: String,
products: Vec<String>,
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<errors::ConnectorError>;
fn try_from(item: &types::LinkTokenRouterData) -> Result<Self, Self::Error> {
Ok(Self {
client_name: item.request.client_name.clone(),
country_codes: item.request.country_codes.clone().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "country_codes",
},
)?,
language: item.request.language.clone().unwrap_or("en".to_string()),
products: vec!["auth".to_string()],
user: User {
client_user_id: item.request.user_info.clone().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "country_codes",
},
)?,
},
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct PlaidLinkTokenResponse {
link_token: String,
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, PlaidLinkTokenResponse, T, types::LinkTokenResponse>>
for types::PaymentAuthRouterData<F, T, types::LinkTokenResponse>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<F, PlaidLinkTokenResponse, T, types::LinkTokenResponse>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::LinkTokenResponse {
link_token: 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,
}
impl<F, T>
TryFrom<
types::ResponseRouterData<F, PlaidExchangeTokenResponse, T, types::ExchangeTokenResponse>,
> for types::PaymentAuthRouterData<F, T, types::ExchangeTokenResponse>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
PlaidExchangeTokenResponse,
T,
types::ExchangeTokenResponse,
>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::ExchangeTokenResponse {
access_token: item.response.access_token,
}),
..item.data
})
}
}
impl TryFrom<&types::ExchangeTokenRouterData> for PlaidExchangeTokenRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::ExchangeTokenRouterData) -> Result<Self, Self::Error> {
Ok(Self {
public_token: item.request.public_token.clone(),
})
}
}
#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct PlaidBankAccountCredentialsRequest {
access_token: String,
options: Option<BankAccountCredentialsOptions>,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsResponse {
pub accounts: Vec<PlaidBankAccountCredentialsAccounts>,
pub numbers: PlaidBankAccountCredentialsNumbers,
// pub item: PlaidBankAccountCredentialsItem,
pub request_id: String,
}
#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct BankAccountCredentialsOptions {
account_ids: Vec<String>,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsAccounts {
pub account_id: String,
pub name: String,
pub subtype: Option<String>,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsBalances {
pub available: Option<i32>,
pub current: Option<i32>,
pub limit: Option<i32>,
pub iso_currency_code: Option<String>,
pub unofficial_currency_code: Option<String>,
pub last_updated_datetime: Option<String>,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsNumbers {
pub ach: Vec<PlaidBankAccountCredentialsACH>,
pub eft: Vec<PlaidBankAccountCredentialsEFT>,
pub international: Vec<PlaidBankAccountCredentialsInternational>,
pub bacs: Vec<PlaidBankAccountCredentialsBacs>,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsItem {
pub item_id: String,
pub institution_id: Option<String>,
pub webhook: Option<String>,
pub error: Option<PlaidErrorResponse>,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsACH {
pub account_id: String,
pub account: String,
pub routing: String,
pub wire_routing: Option<String>,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsEFT {
pub account_id: String,
pub account: String,
pub institution: String,
pub branch: String,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsInternational {
pub account_id: String,
pub iban: String,
pub bic: String,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsBacs {
pub account_id: String,
pub account: String,
pub sort_code: String,
}
impl TryFrom<&types::BankDetailsRouterData> for PlaidBankAccountCredentialsRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::BankDetailsRouterData) -> Result<Self, Self::Error> {
Ok(Self {
access_token: item.request.access_token.clone(),
options: item.request.optional_ids.as_ref().map(|bank_account_ids| {
BankAccountCredentialsOptions {
account_ids: bank_account_ids.ids.clone(),
}
}),
})
}
}
impl<F, T>
TryFrom<
types::ResponseRouterData<
F,
PlaidBankAccountCredentialsResponse,
T,
types::BankAccountCredentialsResponse,
>,
> for types::PaymentAuthRouterData<F, T, types::BankAccountCredentialsResponse>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
PlaidBankAccountCredentialsResponse,
T,
types::BankAccountCredentialsResponse,
>,
) -> Result<Self, Self::Error> {
let (account_numbers, accounts_info) = (item.response.numbers, item.response.accounts);
let mut bank_account_vec = Vec::new();
let mut id_to_suptype = HashMap::new();
accounts_info.into_iter().for_each(|acc| {
id_to_suptype.insert(acc.account_id, (acc.subtype, acc.name));
});
account_numbers.ach.into_iter().for_each(|ach| {
let (acc_type, acc_name) =
if let Some((_type, name)) = id_to_suptype.get(&ach.account_id) {
(_type.to_owned(), Some(name.clone()))
} else {
(None, None)
};
let bank_details_new = types::BankAccountDetails {
account_name: acc_name,
account_number: ach.account,
routing_number: ach.routing,
payment_method_type: PaymentMethodType::Ach,
account_id: ach.account_id,
account_type: acc_type,
};
bank_account_vec.push(bank_details_new);
});
Ok(Self {
response: Ok(types::BankAccountCredentialsResponse {
credentials: bank_account_vec,
}),
..item.data
})
}
}
pub struct PlaidAuthType {
pub client_id: Secret<String>,
pub secret: Secret<String>,
}
impl TryFrom<&types::ConnectorAuthType> for PlaidAuthType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
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, Eq)]
#[serde(rename_all = "snake_case")]
pub struct PlaidErrorResponse {
pub display_message: Option<String>,
pub error_code: Option<String>,
pub error_message: String,
pub error_type: Option<String>,
}