mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
609 lines
20 KiB
Rust
609 lines
20 KiB
Rust
use error_stack::ResultExt;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
core::errors,
|
|
pii::PeekInterface,
|
|
types::{self, api, storage::enums},
|
|
};
|
|
|
|
#[derive(Debug, Serialize, PartialEq, Eq)]
|
|
pub enum TransactionType {
|
|
#[serde(rename = "authCaptureTransaction")]
|
|
Payment,
|
|
#[serde(rename = "refundTransaction")]
|
|
Refund,
|
|
#[serde(rename = "voidTransaction")]
|
|
Void,
|
|
}
|
|
#[derive(Debug, Serialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct MerchantAuthentication {
|
|
name: String,
|
|
transaction_key: String,
|
|
}
|
|
|
|
impl TryFrom<&types::ConnectorAuthType> for MerchantAuthentication {
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
|
|
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
|
|
if let types::ConnectorAuthType::BodyKey { api_key, key1 } = auth_type {
|
|
Ok(MerchantAuthentication {
|
|
name: api_key.clone(),
|
|
transaction_key: key1.clone(),
|
|
})
|
|
} else {
|
|
Err(errors::ConnectorError::FailedToObtainAuthType)?
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct CreditCardDetails {
|
|
card_number: String,
|
|
expiration_date: String,
|
|
card_code: String,
|
|
}
|
|
|
|
#[derive(Serialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct BankAccountDetails {
|
|
account_number: String,
|
|
}
|
|
|
|
#[derive(Serialize, PartialEq)]
|
|
enum PaymentDetails {
|
|
#[serde(rename = "creditCard")]
|
|
CreditCard(CreditCardDetails),
|
|
#[serde(rename = "bankAccount")]
|
|
BankAccount(BankAccountDetails),
|
|
Wallet,
|
|
Klarna,
|
|
Paypal,
|
|
}
|
|
|
|
#[derive(Serialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct TransactionRequest {
|
|
transaction_type: TransactionType,
|
|
amount: i32,
|
|
currency_code: String,
|
|
payment: PaymentDetails,
|
|
authorization_indicator_type: Option<AuthorizationIndicator>,
|
|
}
|
|
|
|
#[derive(Serialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct AuthorizationIndicator {
|
|
authorization_indicator: AuthorizationType,
|
|
}
|
|
|
|
#[derive(Serialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct TransactionVoidRequest {
|
|
transaction_type: TransactionType,
|
|
ref_trans_id: String,
|
|
}
|
|
|
|
#[derive(Serialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct AuthorizedotnetPaymentsRequest {
|
|
merchant_authentication: MerchantAuthentication,
|
|
transaction_request: TransactionRequest,
|
|
}
|
|
|
|
#[derive(Serialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct AuthorizedotnetPaymentCancelRequest {
|
|
merchant_authentication: MerchantAuthentication,
|
|
transaction_request: TransactionVoidRequest,
|
|
}
|
|
|
|
#[derive(Serialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
// The connector enforces field ordering, it expects fields to be in the same order as in their API documentation
|
|
pub struct CreateTransactionRequest {
|
|
create_transaction_request: AuthorizedotnetPaymentsRequest,
|
|
}
|
|
|
|
#[derive(Serialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct CancelTransactionRequest {
|
|
create_transaction_request: AuthorizedotnetPaymentCancelRequest,
|
|
}
|
|
|
|
#[derive(Serialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum AuthorizationType {
|
|
Final,
|
|
Pre,
|
|
}
|
|
|
|
impl From<enums::CaptureMethod> for AuthorizationType {
|
|
fn from(item: enums::CaptureMethod) -> Self {
|
|
match item {
|
|
enums::CaptureMethod::Manual => Self::Pre,
|
|
_ => Self::Final,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&types::PaymentsAuthorizeRouterData> for CreateTransactionRequest {
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
|
let payment_details = match item.request.payment_method_data {
|
|
api::PaymentMethod::Card(ref ccard) => {
|
|
let expiry_month = ccard.card_exp_month.peek().clone();
|
|
let expiry_year = ccard.card_exp_year.peek().clone();
|
|
|
|
PaymentDetails::CreditCard(CreditCardDetails {
|
|
card_number: ccard.card_number.peek().clone(),
|
|
expiration_date: format!("{expiry_year}-{expiry_month}"),
|
|
card_code: ccard.card_cvc.peek().clone(),
|
|
})
|
|
}
|
|
api::PaymentMethod::BankTransfer => PaymentDetails::BankAccount(BankAccountDetails {
|
|
account_number: "XXXXX".to_string(),
|
|
}),
|
|
api::PaymentMethod::PayLater(_) => PaymentDetails::Klarna,
|
|
api::PaymentMethod::Wallet(_) => PaymentDetails::Wallet,
|
|
api::PaymentMethod::Paypal => PaymentDetails::Paypal,
|
|
};
|
|
let authorization_indicator_type =
|
|
item.request.capture_method.map(|c| AuthorizationIndicator {
|
|
authorization_indicator: c.into(),
|
|
});
|
|
let transaction_request = TransactionRequest {
|
|
transaction_type: TransactionType::Payment,
|
|
amount: item.request.amount,
|
|
payment: payment_details,
|
|
currency_code: item.request.currency.to_string(),
|
|
authorization_indicator_type,
|
|
};
|
|
|
|
let merchant_authentication = MerchantAuthentication::try_from(&item.connector_auth_type)?;
|
|
|
|
Ok(CreateTransactionRequest {
|
|
create_transaction_request: AuthorizedotnetPaymentsRequest {
|
|
merchant_authentication,
|
|
transaction_request,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&types::PaymentsCancelRouterData> for CancelTransactionRequest {
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
|
|
let transaction_request = TransactionVoidRequest {
|
|
transaction_type: TransactionType::Void,
|
|
ref_trans_id: item.request.connector_transaction_id.to_string(),
|
|
};
|
|
|
|
let merchant_authentication = MerchantAuthentication::try_from(&item.connector_auth_type)?;
|
|
|
|
Ok(Self {
|
|
create_transaction_request: AuthorizedotnetPaymentCancelRequest {
|
|
merchant_authentication,
|
|
transaction_request,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub enum AuthorizedotnetPaymentStatus {
|
|
#[serde(rename = "1")]
|
|
Approved,
|
|
#[serde(rename = "2")]
|
|
Declined,
|
|
#[serde(rename = "3")]
|
|
Error,
|
|
#[default]
|
|
#[serde(rename = "4")]
|
|
HeldForReview,
|
|
}
|
|
|
|
pub type AuthorizedotnetRefundStatus = AuthorizedotnetPaymentStatus;
|
|
|
|
impl From<AuthorizedotnetPaymentStatus> for enums::AttemptStatus {
|
|
fn from(item: AuthorizedotnetPaymentStatus) -> Self {
|
|
match item {
|
|
AuthorizedotnetPaymentStatus::Approved => enums::AttemptStatus::Charged,
|
|
AuthorizedotnetPaymentStatus::Declined | AuthorizedotnetPaymentStatus::Error => {
|
|
enums::AttemptStatus::Failure
|
|
}
|
|
AuthorizedotnetPaymentStatus::HeldForReview => enums::AttemptStatus::Pending,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
|
struct ResponseMessage {
|
|
code: String,
|
|
text: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
|
enum ResultCode {
|
|
Ok,
|
|
Error,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ResponseMessages {
|
|
result_code: ResultCode,
|
|
message: Vec<ResponseMessage>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub(super) struct ErrorMessage {
|
|
pub(super) error_code: String,
|
|
pub(super) error_text: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct TransactionResponse {
|
|
response_code: AuthorizedotnetPaymentStatus,
|
|
auth_code: String,
|
|
#[serde(rename = "transId")]
|
|
transaction_id: String,
|
|
pub(super) errors: Option<Vec<ErrorMessage>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct AuthorizedotnetPaymentsResponse {
|
|
pub transaction_response: TransactionResponse,
|
|
pub messages: ResponseMessages,
|
|
}
|
|
|
|
impl<F, T>
|
|
TryFrom<
|
|
types::ResponseRouterData<
|
|
F,
|
|
AuthorizedotnetPaymentsResponse,
|
|
T,
|
|
types::PaymentsResponseData,
|
|
>,
|
|
> for types::RouterData<F, T, types::PaymentsResponseData>
|
|
{
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
fn try_from(
|
|
item: types::ResponseRouterData<
|
|
F,
|
|
AuthorizedotnetPaymentsResponse,
|
|
T,
|
|
types::PaymentsResponseData,
|
|
>,
|
|
) -> Result<Self, Self::Error> {
|
|
let status = enums::AttemptStatus::from(item.response.transaction_response.response_code);
|
|
let error = item
|
|
.response
|
|
.transaction_response
|
|
.errors
|
|
.and_then(|errors| {
|
|
errors.first().map(|error| types::ErrorResponse {
|
|
code: error.error_code.clone(),
|
|
message: error.error_text.clone(),
|
|
reason: None,
|
|
})
|
|
});
|
|
|
|
Ok(types::RouterData {
|
|
status,
|
|
response: match error {
|
|
Some(err) => Err(err),
|
|
None => {
|
|
Ok(types::PaymentsResponseData {
|
|
resource_id: types::ResponseId::ConnectorTransactionId(
|
|
item.response.transaction_response.transaction_id,
|
|
),
|
|
//TODO: Add redirection details here
|
|
redirection_data: None,
|
|
redirect: false,
|
|
})
|
|
}
|
|
},
|
|
..item.data
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct RefundTransactionRequest {
|
|
transaction_type: TransactionType,
|
|
amount: i32,
|
|
currency_code: String,
|
|
payment: PaymentDetails,
|
|
#[serde(rename = "refTransId")]
|
|
reference_transaction_id: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct AuthorizedotnetRefundRequest {
|
|
merchant_authentication: MerchantAuthentication,
|
|
transaction_request: RefundTransactionRequest,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
// The connector enforces field ordering, it expects fields to be in the same order as in their API documentation
|
|
pub struct CreateRefundRequest {
|
|
create_transaction_request: AuthorizedotnetRefundRequest,
|
|
}
|
|
|
|
impl<F> TryFrom<&types::RefundsRouterData<F>> for CreateRefundRequest {
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
|
let (payment_details, merchant_authentication, transaction_request);
|
|
payment_details = match item.request.payment_method_data {
|
|
api::PaymentMethod::Card(ref ccard) => {
|
|
let expiry_month = ccard.card_exp_month.peek().clone();
|
|
let expiry_year = ccard.card_exp_year.peek().clone();
|
|
|
|
PaymentDetails::CreditCard(CreditCardDetails {
|
|
card_number: ccard.card_number.peek().clone(),
|
|
expiration_date: format!("{expiry_year}-{expiry_month}"),
|
|
card_code: ccard.card_cvc.peek().clone(),
|
|
})
|
|
}
|
|
api::PaymentMethod::BankTransfer => PaymentDetails::BankAccount(BankAccountDetails {
|
|
account_number: "XXXXX".to_string(),
|
|
}),
|
|
api::PaymentMethod::PayLater(_) => PaymentDetails::Klarna,
|
|
api::PaymentMethod::Wallet(_) => PaymentDetails::Wallet,
|
|
api::PaymentMethod::Paypal => PaymentDetails::Paypal,
|
|
};
|
|
|
|
merchant_authentication = MerchantAuthentication::try_from(&item.connector_auth_type)?;
|
|
|
|
transaction_request = RefundTransactionRequest {
|
|
transaction_type: TransactionType::Refund,
|
|
amount: item.request.refund_amount,
|
|
payment: payment_details,
|
|
currency_code: item.request.currency.to_string(),
|
|
reference_transaction_id: item.request.connector_transaction_id.clone(),
|
|
};
|
|
|
|
Ok(CreateRefundRequest {
|
|
create_transaction_request: AuthorizedotnetRefundRequest {
|
|
merchant_authentication,
|
|
transaction_request,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
impl From<self::AuthorizedotnetPaymentStatus> for enums::RefundStatus {
|
|
fn from(item: self::AuthorizedotnetRefundStatus) -> Self {
|
|
match item {
|
|
AuthorizedotnetPaymentStatus::Approved => enums::RefundStatus::Success,
|
|
AuthorizedotnetPaymentStatus::Declined | AuthorizedotnetPaymentStatus::Error => {
|
|
enums::RefundStatus::Failure
|
|
}
|
|
AuthorizedotnetPaymentStatus::HeldForReview => enums::RefundStatus::Pending,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct AuthorizedotnetRefundResponse {
|
|
pub transaction_response: TransactionResponse,
|
|
pub messages: ResponseMessages,
|
|
}
|
|
|
|
impl<F> TryFrom<types::RefundsResponseRouterData<F, AuthorizedotnetRefundResponse>>
|
|
for types::RefundsRouterData<F>
|
|
{
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
fn try_from(
|
|
item: types::RefundsResponseRouterData<F, AuthorizedotnetRefundResponse>,
|
|
) -> Result<Self, Self::Error> {
|
|
let transaction_response = &item.response.transaction_response;
|
|
let refund_status = enums::RefundStatus::from(transaction_response.response_code.clone());
|
|
let error = transaction_response.errors.clone().and_then(|errors| {
|
|
errors.first().map(|error| types::ErrorResponse {
|
|
code: error.error_code.clone(),
|
|
message: error.error_text.clone(),
|
|
reason: None,
|
|
})
|
|
});
|
|
|
|
Ok(types::RouterData {
|
|
response: match error {
|
|
Some(err) => Err(err),
|
|
None => Ok(types::RefundsResponseData {
|
|
connector_refund_id: transaction_response.transaction_id.clone(),
|
|
refund_status,
|
|
}),
|
|
},
|
|
..item.data
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct TransactionDetails {
|
|
merchant_authentication: MerchantAuthentication,
|
|
#[serde(rename = "transId")]
|
|
transaction_id: Option<String>,
|
|
}
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct AuthorizedotnetCreateSyncRequest {
|
|
get_transaction_details_request: TransactionDetails,
|
|
}
|
|
|
|
impl<F> TryFrom<&types::RefundsRouterData<F>> for AuthorizedotnetCreateSyncRequest {
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
|
|
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
|
let transaction_id = item
|
|
.response
|
|
.as_ref()
|
|
.map(|refund_response_data| refund_response_data.connector_refund_id.clone())
|
|
.ok();
|
|
let merchant_authentication = MerchantAuthentication::try_from(&item.connector_auth_type)?;
|
|
|
|
let payload = AuthorizedotnetCreateSyncRequest {
|
|
get_transaction_details_request: TransactionDetails {
|
|
merchant_authentication,
|
|
transaction_id,
|
|
},
|
|
};
|
|
Ok(payload)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&types::PaymentsSyncRouterData> for AuthorizedotnetCreateSyncRequest {
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
|
|
fn try_from(item: &types::PaymentsSyncRouterData) -> Result<Self, Self::Error> {
|
|
let transaction_id = item
|
|
.response
|
|
.as_ref()
|
|
.ok()
|
|
.map(|payment_response_data| {
|
|
payment_response_data
|
|
.resource_id
|
|
.get_connector_transaction_id()
|
|
})
|
|
.transpose()
|
|
.change_context(errors::ConnectorError::ResponseHandlingFailed)?;
|
|
|
|
let merchant_authentication = MerchantAuthentication::try_from(&item.connector_auth_type)?;
|
|
|
|
let payload = AuthorizedotnetCreateSyncRequest {
|
|
get_transaction_details_request: TransactionDetails {
|
|
merchant_authentication,
|
|
transaction_id,
|
|
},
|
|
};
|
|
Ok(payload)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub enum SyncStatus {
|
|
RefundSettledSuccessfully,
|
|
RefundPendingSettlement,
|
|
AuthorizedPendingCapture,
|
|
CapturedPendingSettlement,
|
|
SettledSuccessfully,
|
|
Declined,
|
|
Voided,
|
|
CouldNotVoid,
|
|
GeneralError,
|
|
}
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct SyncTransactionResponse {
|
|
#[serde(rename = "transId")]
|
|
transaction_id: String,
|
|
transaction_status: SyncStatus,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct AuthorizedotnetSyncResponse {
|
|
transaction: SyncTransactionResponse,
|
|
}
|
|
|
|
impl From<SyncStatus> for enums::RefundStatus {
|
|
fn from(transaction_status: SyncStatus) -> Self {
|
|
match transaction_status {
|
|
SyncStatus::RefundSettledSuccessfully => enums::RefundStatus::Success,
|
|
SyncStatus::RefundPendingSettlement => enums::RefundStatus::Pending,
|
|
_ => enums::RefundStatus::Failure,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<SyncStatus> for enums::AttemptStatus {
|
|
fn from(transaction_status: SyncStatus) -> Self {
|
|
match transaction_status {
|
|
SyncStatus::SettledSuccessfully | SyncStatus::CapturedPendingSettlement => {
|
|
enums::AttemptStatus::Charged
|
|
}
|
|
SyncStatus::Declined => enums::AttemptStatus::AuthenticationFailed,
|
|
SyncStatus::Voided => enums::AttemptStatus::Voided,
|
|
SyncStatus::CouldNotVoid => enums::AttemptStatus::VoidFailed,
|
|
SyncStatus::GeneralError => enums::AttemptStatus::Failure,
|
|
_ => enums::AttemptStatus::Pending,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<types::RefundsResponseRouterData<api::RSync, AuthorizedotnetSyncResponse>>
|
|
for types::RefundsRouterData<api::RSync>
|
|
{
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
|
|
fn try_from(
|
|
item: types::RefundsResponseRouterData<api::RSync, AuthorizedotnetSyncResponse>,
|
|
) -> Result<Self, Self::Error> {
|
|
let refund_status = enums::RefundStatus::from(item.response.transaction.transaction_status);
|
|
Ok(types::RouterData {
|
|
response: Ok(types::RefundsResponseData {
|
|
connector_refund_id: item.response.transaction.transaction_id.clone(),
|
|
refund_status,
|
|
}),
|
|
..item.data
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<F, Req>
|
|
TryFrom<
|
|
types::ResponseRouterData<F, AuthorizedotnetSyncResponse, Req, types::PaymentsResponseData>,
|
|
> for types::RouterData<F, Req, types::PaymentsResponseData>
|
|
{
|
|
type Error = error_stack::Report<errors::ConnectorError>;
|
|
|
|
fn try_from(
|
|
item: types::ResponseRouterData<
|
|
F,
|
|
AuthorizedotnetSyncResponse,
|
|
Req,
|
|
types::PaymentsResponseData,
|
|
>,
|
|
) -> Result<Self, Self::Error> {
|
|
let payment_status =
|
|
enums::AttemptStatus::from(item.response.transaction.transaction_status);
|
|
Ok(types::RouterData {
|
|
response: Ok(types::PaymentsResponseData {
|
|
resource_id: types::ResponseId::ConnectorTransactionId(
|
|
item.response.transaction.transaction_id,
|
|
),
|
|
redirection_data: None,
|
|
redirect: false,
|
|
}),
|
|
status: payment_status,
|
|
..item.data
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Eq, PartialEq, Deserialize)]
|
|
pub struct ErrorDetails {
|
|
pub code: Option<String>,
|
|
#[serde(rename = "type")]
|
|
pub error_type: Option<String>,
|
|
pub message: Option<String>,
|
|
pub param: Option<String>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Deserialize, PartialEq, Eq)]
|
|
pub struct AuthorizedotnetErrorResponse {
|
|
pub error: ErrorDetails,
|
|
}
|