feat(connector): [CYBERSOURCE] Refactor cybersource (#3215)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
DEEPANSHU BANSAL
2023-12-29 14:23:31 +05:30
committed by GitHub
parent 18eca7e9fb
commit e06ba148b6
2 changed files with 267 additions and 19 deletions

View File

@ -446,6 +446,27 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> { ) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
self.build_error_response(res) self.build_error_response(res)
} }
fn get_5xx_error_response(
&self,
res: types::Response,
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
let response: cybersource::CybersourceServerErrorResponse = res
.response
.parse_struct("CybersourceServerErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
Ok(types::ErrorResponse {
status_code: res.status_code,
reason: response.status.clone(),
code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()),
message: response
.message
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
attempt_status: None,
connector_transaction_id: None,
})
}
} }
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData> impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
@ -606,6 +627,33 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> { ) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
self.build_error_response(res) self.build_error_response(res)
} }
fn get_5xx_error_response(
&self,
res: types::Response,
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
let response: cybersource::CybersourceServerErrorResponse = res
.response
.parse_struct("CybersourceServerErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let attempt_status = match response.reason {
Some(reason) => match reason {
transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure),
transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None,
},
None => None,
};
Ok(types::ErrorResponse {
status_code: res.status_code,
reason: response.status.clone(),
code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()),
message: response
.message
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
attempt_status,
connector_transaction_id: None,
})
}
} }
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData> impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
@ -626,9 +674,8 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
) -> CustomResult<String, errors::ConnectorError> { ) -> CustomResult<String, errors::ConnectorError> {
let connector_payment_id = req.request.connector_transaction_id.clone(); let connector_payment_id = req.request.connector_transaction_id.clone();
Ok(format!( Ok(format!(
"{}pts/v2/payments/{}/voids", "{}pts/v2/payments/{connector_payment_id}/reversals",
self.base_url(connectors), self.base_url(connectors)
connector_payment_id
)) ))
} }
@ -638,10 +685,26 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
fn get_request_body( fn get_request_body(
&self, &self,
_req: &types::PaymentsCancelRouterData, req: &types::PaymentsCancelRouterData,
_connectors: &settings::Connectors, _connectors: &settings::Connectors,
) -> CustomResult<RequestContent, errors::ConnectorError> { ) -> CustomResult<RequestContent, errors::ConnectorError> {
Ok(RequestContent::Json(Box::new(serde_json::json!({})))) let connector_router_data = cybersource::CybersourceRouterData::try_from((
&self.get_currency_unit(),
req.request
.currency
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "Currency",
})?,
req.request
.amount
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "Amount",
})?,
req,
))?;
let connector_req = cybersource::CybersourceVoidRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
} }
fn build_request( fn build_request(
@ -682,6 +745,27 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> { ) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
self.build_error_response(res) self.build_error_response(res)
} }
fn get_5xx_error_response(
&self,
res: types::Response,
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
let response: cybersource::CybersourceServerErrorResponse = res
.response
.parse_struct("CybersourceServerErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
Ok(types::ErrorResponse {
status_code: res.status_code,
reason: response.status.clone(),
code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()),
message: response
.message
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
attempt_status: None,
connector_transaction_id: None,
})
}
} }
impl api::Refund for Cybersource {} impl api::Refund for Cybersource {}

View File

@ -1,8 +1,11 @@
use std::collections::HashMap;
use api_models::payments; use api_models::payments;
use base64::Engine; use base64::Engine;
use common_utils::pii; use common_utils::pii;
use masking::{PeekInterface, Secret}; use masking::{PeekInterface, Secret};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{ use crate::{
connector::utils::{ connector::utils::{
@ -134,6 +137,8 @@ pub struct CybersourcePaymentsRequest {
payment_information: PaymentInformation, payment_information: PaymentInformation,
order_information: OrderInformationWithBill, order_information: OrderInformationWithBill,
client_reference_information: ClientReferenceInformation, client_reference_information: ClientReferenceInformation,
#[serde(skip_serializing_if = "Option::is_none")]
merchant_defined_information: Option<Vec<MerchantDefinedInformation>>,
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
@ -148,6 +153,13 @@ pub struct ProcessingInformation {
payment_solution: Option<String>, payment_solution: Option<String>,
} }
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MerchantDefinedInformation {
key: u8,
value: String,
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CybersourceActionsList { pub enum CybersourceActionsList {
@ -218,6 +230,19 @@ pub struct TokenizedCard {
transaction_type: TransactionType, transaction_type: TransactionType,
} }
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayTokenizedCard {
transaction_type: TransactionType,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayTokenPaymentInformation {
fluid_data: FluidData,
tokenized_card: ApplePayTokenizedCard,
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ApplePayPaymentInformation { pub struct ApplePayPaymentInformation {
@ -242,6 +267,7 @@ pub enum PaymentInformation {
Cards(CardPaymentInformation), Cards(CardPaymentInformation),
GooglePay(GooglePayPaymentInformation), GooglePay(GooglePayPaymentInformation),
ApplePay(ApplePayPaymentInformation), ApplePay(ApplePayPaymentInformation),
ApplePayToken(ApplePayTokenPaymentInformation),
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -441,6 +467,23 @@ fn build_bill_to(
}) })
} }
impl ForeignFrom<Value> for Vec<MerchantDefinedInformation> {
fn foreign_from(metadata: Value) -> Self {
let hashmap: HashMap<String, Value> =
serde_json::from_str(&metadata.to_string()).unwrap_or(HashMap::new());
let mut vector: Self = Self::new();
let mut iter = 1;
for (key, value) in hashmap {
vector.push(MerchantDefinedInformation {
key: iter,
value: format!("{key}={value}"),
});
iter += 1;
}
vector
}
}
impl impl
TryFrom<( TryFrom<(
&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>,
@ -491,15 +534,19 @@ impl
card, card,
instrument_identifier, instrument_identifier,
}); });
let processing_information = ProcessingInformation::from((item, None)); let processing_information = ProcessingInformation::from((item, None));
let client_reference_information = ClientReferenceInformation::from(item); let client_reference_information = ClientReferenceInformation::from(item);
let merchant_defined_information =
item.router_data.request.metadata.clone().map(|metadata| {
Vec::<MerchantDefinedInformation>::foreign_from(metadata.peek().to_owned())
});
Ok(Self { Ok(Self {
processing_information, processing_information,
payment_information, payment_information,
order_information, order_information,
client_reference_information, client_reference_information,
merchant_defined_information,
}) })
} }
} }
@ -525,7 +572,6 @@ impl
let client_reference_information = ClientReferenceInformation::from(item); let client_reference_information = ClientReferenceInformation::from(item);
let expiration_month = apple_pay_data.get_expiry_month()?; let expiration_month = apple_pay_data.get_expiry_month()?;
let expiration_year = apple_pay_data.get_four_digit_expiry_year()?; let expiration_year = apple_pay_data.get_four_digit_expiry_year()?;
let payment_information = PaymentInformation::ApplePay(ApplePayPaymentInformation { let payment_information = PaymentInformation::ApplePay(ApplePayPaymentInformation {
tokenized_card: TokenizedCard { tokenized_card: TokenizedCard {
number: apple_pay_data.application_primary_account_number, number: apple_pay_data.application_primary_account_number,
@ -535,12 +581,17 @@ impl
expiration_month, expiration_month,
}, },
}); });
let merchant_defined_information =
item.router_data.request.metadata.clone().map(|metadata| {
Vec::<MerchantDefinedInformation>::foreign_from(metadata.peek().to_owned())
});
Ok(Self { Ok(Self {
processing_information, processing_information,
payment_information, payment_information,
order_information, order_information,
client_reference_information, client_reference_information,
merchant_defined_information,
}) })
} }
} }
@ -569,16 +620,20 @@ impl
), ),
}, },
}); });
let processing_information = let processing_information =
ProcessingInformation::from((item, Some(PaymentSolution::GooglePay))); ProcessingInformation::from((item, Some(PaymentSolution::GooglePay)));
let client_reference_information = ClientReferenceInformation::from(item); let client_reference_information = ClientReferenceInformation::from(item);
let merchant_defined_information =
item.router_data.request.metadata.clone().map(|metadata| {
Vec::<MerchantDefinedInformation>::foreign_from(metadata.peek().to_owned())
});
Ok(Self { Ok(Self {
processing_information, processing_information,
payment_information, payment_information,
order_information, order_information,
client_reference_information, client_reference_information,
merchant_defined_information,
}) })
} }
} }
@ -593,14 +648,50 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>>
match item.router_data.request.payment_method_data.clone() { match item.router_data.request.payment_method_data.clone() {
payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)),
payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data { payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data {
payments::WalletData::ApplePay(_) => { payments::WalletData::ApplePay(apple_pay_data) => {
let payment_method_token = item.router_data.get_payment_method_token()?; match item.router_data.payment_method_token.clone() {
match payment_method_token { Some(payment_method_token) => match payment_method_token {
types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => {
Self::try_from((item, decrypt_data)) Self::try_from((item, decrypt_data))
} }
types::PaymentMethodToken::Token(_) => { types::PaymentMethodToken::Token(_) => {
Err(errors::ConnectorError::InvalidWalletToken)? Err(errors::ConnectorError::InvalidWalletToken)?
}
},
None => {
let email = item.router_data.request.get_email()?;
let bill_to = build_bill_to(item.router_data.get_billing()?, email)?;
let order_information = OrderInformationWithBill::from((item, bill_to));
let processing_information = ProcessingInformation::from((
item,
Some(PaymentSolution::ApplePay),
));
let client_reference_information =
ClientReferenceInformation::from(item);
let payment_information = PaymentInformation::ApplePayToken(
ApplePayTokenPaymentInformation {
fluid_data: FluidData {
value: Secret::from(apple_pay_data.payment_data),
},
tokenized_card: ApplePayTokenizedCard {
transaction_type: TransactionType::ApplePay,
},
},
);
let merchant_defined_information =
item.router_data.request.metadata.clone().map(|metadata| {
Vec::<MerchantDefinedInformation>::foreign_from(
metadata.peek().to_owned(),
)
});
Ok(Self {
processing_information,
payment_information,
order_information,
client_reference_information,
merchant_defined_information,
})
} }
} }
} }
@ -737,6 +828,51 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsIncrementalAuthorizationRout
} }
} }
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CybersourceVoidRequest {
client_reference_information: ClientReferenceInformation,
reversal_information: ReversalInformation,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ReversalInformation {
amount_details: Amount,
reason: String,
}
impl TryFrom<&CybersourceRouterData<&types::PaymentsCancelRouterData>> for CybersourceVoidRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
value: &CybersourceRouterData<&types::PaymentsCancelRouterData>,
) -> Result<Self, Self::Error> {
Ok(Self {
client_reference_information: ClientReferenceInformation {
code: Some(value.router_data.connector_request_reference_id.clone()),
},
reversal_information: ReversalInformation {
amount_details: Amount {
total_amount: value.amount.to_owned(),
currency: value.router_data.request.currency.ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "Currency",
},
)?,
},
reason: value
.router_data
.request
.cancellation_reason
.clone()
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "Cancellation Reason",
})?,
},
})
}
}
pub struct CybersourceAuthType { pub struct CybersourceAuthType {
pub(super) api_key: Secret<String>, pub(super) api_key: Secret<String>,
pub(super) merchant_account: Secret<String>, pub(super) merchant_account: Secret<String>,
@ -1079,9 +1215,21 @@ impl<F>
..item.data ..item.data
}) })
} }
CybersourcePaymentsResponse::ErrorInformation(ref error_response) => { CybersourcePaymentsResponse::ErrorInformation(error_response) => Ok(Self {
Ok(Self::from((&error_response.clone(), item))) response: Err(types::ErrorResponse {
} code: consts::NO_ERROR_CODE.to_string(),
message: error_response
.error_information
.message
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
reason: error_response.error_information.reason,
status_code: item.http_code,
attempt_status: None,
connector_transaction_id: Some(error_response.id.clone()),
}),
status: enums::AttemptStatus::Failure,
..item.data
}),
} }
} }
} }
@ -1496,6 +1644,22 @@ pub struct CybersourceStandardErrorResponse {
pub details: Option<Vec<Details>>, pub details: Option<Vec<Details>>,
} }
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CybersourceServerErrorResponse {
pub status: Option<String>,
pub message: Option<String>,
pub reason: Option<Reason>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Reason {
SystemError,
ServerTimeout,
ServiceTimeout,
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct CybersourceAuthenticationErrorResponse { pub struct CybersourceAuthenticationErrorResponse {
pub response: AuthenticationErrorInformation, pub response: AuthenticationErrorInformation,