mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
390 lines
14 KiB
Rust
390 lines
14 KiB
Rust
use common_enums::Currency;
|
|
use common_utils::types::FloatMajorUnit;
|
|
use error_stack::ResultExt;
|
|
use masking::{PeekInterface, Secret};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
connector::utils::is_payment_failure,
|
|
core::errors,
|
|
types::{self, api, domain, storage::enums},
|
|
};
|
|
|
|
pub struct PlaidRouterData<T> {
|
|
pub amount: FloatMajorUnit,
|
|
pub router_data: T,
|
|
}
|
|
|
|
impl<T> From<(FloatMajorUnit, T)> for PlaidRouterData<T> {
|
|
fn from((amount, item): (FloatMajorUnit, T)) -> Self {
|
|
Self {
|
|
amount,
|
|
router_data: item,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Serialize)]
|
|
pub struct PlaidPaymentsRequest {
|
|
amount: PlaidAmount,
|
|
recipient_id: String,
|
|
reference: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
schedule: Option<PlaidSchedule>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
options: Option<PlaidOptions>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
|
pub struct PlaidAmount {
|
|
currency: Currency,
|
|
value: FloatMajorUnit,
|
|
}
|
|
|
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
|
pub struct PlaidSchedule {
|
|
interval: String,
|
|
interval_execution_day: String,
|
|
start_date: String,
|
|
end_date: Option<String>,
|
|
adjusted_start_date: Option<String>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
|
pub struct PlaidOptions {
|
|
request_refund_details: bool,
|
|
iban: Option<Secret<String>>,
|
|
bacs: Option<PlaidBacs>,
|
|
scheme: String,
|
|
}
|
|
|
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
|
pub struct PlaidBacs {
|
|
account: Secret<String>,
|
|
sort_code: Secret<String>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub struct PlaidLinkTokenRequest {
|
|
client_name: String,
|
|
country_codes: Vec<String>,
|
|
language: String,
|
|
products: Vec<String>,
|
|
user: User,
|
|
payment_initiation: PlaidPaymentInitiation,
|
|
}
|
|
|
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
|
pub struct User {
|
|
pub client_user_id: String,
|
|
}
|
|
|
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
|
pub struct PlaidPaymentInitiation {
|
|
payment_id: String,
|
|
}
|
|
|
|
impl TryFrom<&PlaidRouterData<&types::PaymentsAuthorizeRouterData>> for PlaidPaymentsRequest {
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
fn try_from(
|
|
item: &PlaidRouterData<&types::PaymentsAuthorizeRouterData>,
|
|
) -> Result<Self, Self::Error> {
|
|
match item.router_data.request.payment_method_data.clone() {
|
|
domain::PaymentMethodData::OpenBanking(ref data) => match data {
|
|
domain::OpenBankingData::OpenBankingPIS { .. } => {
|
|
let amount = item.amount;
|
|
let currency = item.router_data.request.currency;
|
|
let payment_id = item.router_data.payment_id.clone();
|
|
let id_len = payment_id.len();
|
|
let reference = if id_len > 18 {
|
|
payment_id.get(id_len - 18..id_len).map(|id| id.to_string())
|
|
} else {
|
|
Some(payment_id)
|
|
}
|
|
.ok_or(errors::ConnectorError::MissingRequiredField {
|
|
field_name: "payment_id",
|
|
})?;
|
|
let recipient_val = item
|
|
.router_data
|
|
.connector_meta_data
|
|
.as_ref()
|
|
.ok_or(errors::ConnectorError::MissingRequiredField {
|
|
field_name: "connector_customer",
|
|
})?
|
|
.peek()
|
|
.clone();
|
|
|
|
let recipient_type =
|
|
serde_json::from_value::<types::MerchantRecipientData>(recipient_val)
|
|
.change_context(errors::ConnectorError::ParsingFailed)?;
|
|
let recipient_id = match recipient_type {
|
|
types::MerchantRecipientData::ConnectorRecipientId(id) => {
|
|
Ok(id.peek().to_string())
|
|
}
|
|
_ => Err(errors::ConnectorError::MissingRequiredField {
|
|
field_name: "ConnectorRecipientId",
|
|
}),
|
|
}?;
|
|
|
|
Ok(Self {
|
|
amount: PlaidAmount {
|
|
currency,
|
|
value: amount,
|
|
},
|
|
reference,
|
|
recipient_id,
|
|
schedule: None,
|
|
options: None,
|
|
})
|
|
}
|
|
},
|
|
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&types::PaymentsSyncRouterData> for PlaidSyncRequest {
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
fn try_from(item: &types::PaymentsSyncRouterData) -> Result<Self, Self::Error> {
|
|
match item.request.connector_transaction_id {
|
|
types::ResponseId::ConnectorTransactionId(ref id) => Ok(Self {
|
|
payment_id: id.clone(),
|
|
}),
|
|
_ => Err((errors::ConnectorError::MissingConnectorTransactionID).into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&types::PaymentsPostProcessingRouterData> for PlaidLinkTokenRequest {
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
fn try_from(item: &types::PaymentsPostProcessingRouterData) -> Result<Self, Self::Error> {
|
|
match item.request.payment_method_data.clone() {
|
|
domain::PaymentMethodData::OpenBanking(ref data) => match data {
|
|
domain::OpenBankingData::OpenBankingPIS { .. } => Ok(Self {
|
|
client_name: "Hyperswitch".to_string(),
|
|
country_codes: item
|
|
.request
|
|
.country
|
|
.map(|code| vec![code.to_string()])
|
|
.ok_or(errors::ConnectorError::MissingRequiredField {
|
|
field_name: "billing.address.country",
|
|
})?,
|
|
language: "en".to_string(),
|
|
products: vec!["payment_initiation".to_string()],
|
|
user: User {
|
|
client_user_id: item
|
|
.request
|
|
.customer_id
|
|
.clone()
|
|
.map(|id| id.get_string_repr().to_string())
|
|
.unwrap_or("default cust".to_string()),
|
|
},
|
|
payment_initiation: PlaidPaymentInitiation {
|
|
payment_id: item
|
|
.request
|
|
.connector_transaction_id
|
|
.clone()
|
|
.ok_or(errors::ConnectorError::MissingConnectorTransactionID)?,
|
|
},
|
|
}),
|
|
},
|
|
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
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 { api_key, key1 } => Ok(Self {
|
|
client_id: api_key.to_owned(),
|
|
secret: key1.to_owned(),
|
|
}),
|
|
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
#[derive(strum::Display)]
|
|
pub enum PlaidPaymentStatus {
|
|
PaymentStatusInputNeeded,
|
|
PaymentStatusInitiated,
|
|
PaymentStatusInsufficientFunds,
|
|
PaymentStatusFailed,
|
|
PaymentStatusBlocked,
|
|
PaymentStatusCancelled,
|
|
PaymentStatusExecuted,
|
|
PaymentStatusSettled,
|
|
PaymentStatusEstablished,
|
|
PaymentStatusRejected,
|
|
PaymentStatusAuthorising,
|
|
}
|
|
|
|
impl From<PlaidPaymentStatus> for enums::AttemptStatus {
|
|
fn from(item: PlaidPaymentStatus) -> Self {
|
|
match item {
|
|
PlaidPaymentStatus::PaymentStatusAuthorising => Self::Authorizing,
|
|
PlaidPaymentStatus::PaymentStatusBlocked
|
|
| PlaidPaymentStatus::PaymentStatusInsufficientFunds
|
|
| PlaidPaymentStatus::PaymentStatusRejected => Self::AuthorizationFailed,
|
|
PlaidPaymentStatus::PaymentStatusCancelled => Self::Voided,
|
|
PlaidPaymentStatus::PaymentStatusEstablished => Self::Authorized,
|
|
PlaidPaymentStatus::PaymentStatusExecuted
|
|
| PlaidPaymentStatus::PaymentStatusSettled
|
|
| PlaidPaymentStatus::PaymentStatusInitiated => Self::Charged,
|
|
PlaidPaymentStatus::PaymentStatusFailed => Self::Failure,
|
|
PlaidPaymentStatus::PaymentStatusInputNeeded => Self::AuthenticationPending,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct PlaidPaymentsResponse {
|
|
status: PlaidPaymentStatus,
|
|
payment_id: String,
|
|
}
|
|
|
|
impl<F, T>
|
|
TryFrom<types::ResponseRouterData<F, PlaidPaymentsResponse, T, types::PaymentsResponseData>>
|
|
for types::RouterData<F, T, types::PaymentsResponseData>
|
|
{
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
fn try_from(
|
|
item: types::ResponseRouterData<F, PlaidPaymentsResponse, T, types::PaymentsResponseData>,
|
|
) -> Result<Self, Self::Error> {
|
|
let status = enums::AttemptStatus::from(item.response.status.clone());
|
|
Ok(Self {
|
|
status,
|
|
response: if is_payment_failure(status) {
|
|
Err(types::ErrorResponse {
|
|
// populating status everywhere as plaid only sends back a status
|
|
code: item.response.status.clone().to_string(),
|
|
message: item.response.status.clone().to_string(),
|
|
reason: Some(item.response.status.to_string()),
|
|
status_code: item.http_code,
|
|
attempt_status: None,
|
|
connector_transaction_id: Some(item.response.payment_id),
|
|
})
|
|
} else {
|
|
Ok(types::PaymentsResponseData::TransactionResponse {
|
|
resource_id: types::ResponseId::ConnectorTransactionId(
|
|
item.response.payment_id.clone(),
|
|
),
|
|
redirection_data: None,
|
|
mandate_reference: None,
|
|
connector_metadata: None,
|
|
network_txn_id: None,
|
|
connector_response_reference_id: Some(item.response.payment_id),
|
|
incremental_authorization_allowed: None,
|
|
charge_id: None,
|
|
})
|
|
},
|
|
..item.data
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
|
pub struct PlaidLinkTokenResponse {
|
|
link_token: String,
|
|
}
|
|
|
|
impl<F, T>
|
|
TryFrom<types::ResponseRouterData<F, PlaidLinkTokenResponse, T, types::PaymentsResponseData>>
|
|
for types::RouterData<F, T, types::PaymentsResponseData>
|
|
{
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
fn try_from(
|
|
item: types::ResponseRouterData<F, PlaidLinkTokenResponse, T, types::PaymentsResponseData>,
|
|
) -> Result<Self, Self::Error> {
|
|
let session_token = Some(api::OpenBankingSessionToken {
|
|
open_banking_session_token: item.response.link_token,
|
|
});
|
|
|
|
Ok(Self {
|
|
status: enums::AttemptStatus::AuthenticationPending,
|
|
response: Ok(types::PaymentsResponseData::PostProcessingResponse { session_token }),
|
|
..item.data
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
|
pub struct PlaidSyncRequest {
|
|
payment_id: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct PlaidSyncResponse {
|
|
payment_id: String,
|
|
amount: PlaidAmount,
|
|
status: PlaidPaymentStatus,
|
|
recipient_id: String,
|
|
reference: String,
|
|
last_status_update: String,
|
|
adjusted_reference: Option<String>,
|
|
schedule: Option<PlaidSchedule>,
|
|
iban: Option<Secret<String>>,
|
|
bacs: Option<PlaidBacs>,
|
|
scheme: Option<String>,
|
|
adjusted_scheme: Option<String>,
|
|
request_id: String,
|
|
}
|
|
|
|
impl<F, T> TryFrom<types::ResponseRouterData<F, PlaidSyncResponse, T, types::PaymentsResponseData>>
|
|
for types::RouterData<F, T, types::PaymentsResponseData>
|
|
{
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
fn try_from(
|
|
item: types::ResponseRouterData<F, PlaidSyncResponse, T, types::PaymentsResponseData>,
|
|
) -> Result<Self, Self::Error> {
|
|
let status = enums::AttemptStatus::from(item.response.status.clone());
|
|
Ok(Self {
|
|
status,
|
|
response: if is_payment_failure(status) {
|
|
Err(types::ErrorResponse {
|
|
// populating status everywhere as plaid only sends back a status
|
|
code: item.response.status.clone().to_string(),
|
|
message: item.response.status.clone().to_string(),
|
|
reason: Some(item.response.status.to_string()),
|
|
status_code: item.http_code,
|
|
attempt_status: None,
|
|
connector_transaction_id: Some(item.response.payment_id),
|
|
})
|
|
} else {
|
|
Ok(types::PaymentsResponseData::TransactionResponse {
|
|
resource_id: types::ResponseId::ConnectorTransactionId(
|
|
item.response.payment_id.clone(),
|
|
),
|
|
redirection_data: None,
|
|
mandate_reference: None,
|
|
connector_metadata: None,
|
|
network_txn_id: None,
|
|
connector_response_reference_id: Some(item.response.payment_id),
|
|
incremental_authorization_allowed: None,
|
|
charge_id: None,
|
|
})
|
|
},
|
|
..item.data
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, 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>,
|
|
}
|