mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
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:
@ -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 {}
|
||||
|
||||
@ -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,15 +648,51 @@ 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 {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
payments::WalletData::GooglePay(google_pay_data) => {
|
||||
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user