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> {
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>
@ -606,6 +627,33 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
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>
@ -626,9 +674,8 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
) -> CustomResult<String, errors::ConnectorError> {
let connector_payment_id = req.request.connector_transaction_id.clone();
Ok(format!(
"{}pts/v2/payments/{}/voids",
self.base_url(connectors),
connector_payment_id
"{}pts/v2/payments/{connector_payment_id}/reversals",
self.base_url(connectors)
))
}
@ -638,10 +685,26 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
fn get_request_body(
&self,
_req: &types::PaymentsCancelRouterData,
req: &types::PaymentsCancelRouterData,
_connectors: &settings::Connectors,
) -> 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(
@ -682,6 +745,27 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
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 {}

View File

@ -1,8 +1,11 @@
use std::collections::HashMap;
use api_models::payments;
use base64::Engine;
use common_utils::pii;
use masking::{PeekInterface, Secret};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{
connector::utils::{
@ -134,6 +137,8 @@ pub struct CybersourcePaymentsRequest {
payment_information: PaymentInformation,
order_information: OrderInformationWithBill,
client_reference_information: ClientReferenceInformation,
#[serde(skip_serializing_if = "Option::is_none")]
merchant_defined_information: Option<Vec<MerchantDefinedInformation>>,
}
#[derive(Debug, Serialize)]
@ -148,6 +153,13 @@ pub struct ProcessingInformation {
payment_solution: Option<String>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MerchantDefinedInformation {
key: u8,
value: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CybersourceActionsList {
@ -218,6 +230,19 @@ pub struct TokenizedCard {
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)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayPaymentInformation {
@ -242,6 +267,7 @@ pub enum PaymentInformation {
Cards(CardPaymentInformation),
GooglePay(GooglePayPaymentInformation),
ApplePay(ApplePayPaymentInformation),
ApplePayToken(ApplePayTokenPaymentInformation),
}
#[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
TryFrom<(
&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>,
@ -491,15 +534,19 @@ impl
card,
instrument_identifier,
});
let processing_information = ProcessingInformation::from((item, None));
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 {
processing_information,
payment_information,
order_information,
client_reference_information,
merchant_defined_information,
})
}
}
@ -525,7 +572,6 @@ impl
let client_reference_information = ClientReferenceInformation::from(item);
let expiration_month = apple_pay_data.get_expiry_month()?;
let expiration_year = apple_pay_data.get_four_digit_expiry_year()?;
let payment_information = PaymentInformation::ApplePay(ApplePayPaymentInformation {
tokenized_card: TokenizedCard {
number: apple_pay_data.application_primary_account_number,
@ -535,12 +581,17 @@ impl
expiration_month,
},
});
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,
})
}
}
@ -569,16 +620,20 @@ impl
),
},
});
let processing_information =
ProcessingInformation::from((item, Some(PaymentSolution::GooglePay)));
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 {
processing_information,
payment_information,
order_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() {
payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)),
payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data {
payments::WalletData::ApplePay(_) => {
let payment_method_token = item.router_data.get_payment_method_token()?;
match payment_method_token {
types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => {
Self::try_from((item, decrypt_data))
}
types::PaymentMethodToken::Token(_) => {
Err(errors::ConnectorError::InvalidWalletToken)?
payments::WalletData::ApplePay(apple_pay_data) => {
match item.router_data.payment_method_token.clone() {
Some(payment_method_token) => match payment_method_token {
types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => {
Self::try_from((item, decrypt_data))
}
types::PaymentMethodToken::Token(_) => {
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(super) api_key: Secret<String>,
pub(super) merchant_account: Secret<String>,
@ -1079,9 +1215,21 @@ impl<F>
..item.data
})
}
CybersourcePaymentsResponse::ErrorInformation(ref error_response) => {
Ok(Self::from((&error_response.clone(), item)))
}
CybersourcePaymentsResponse::ErrorInformation(error_response) => Ok(Self {
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>>,
}
#[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)]
pub struct CybersourceAuthenticationErrorResponse {
pub response: AuthenticationErrorInformation,