mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +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,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, | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 DEEPANSHU BANSAL
					DEEPANSHU BANSAL