mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(connector): add 3ds for Bambora and Support Html 3ds response (#817)
Co-authored-by: Narayan Bhat <narayan.bhat@juspay.in> Co-authored-by: shankar singh <shankar.singh@shankar.singh-MacBookPro> Co-authored-by: Jagan Elavarasan <jaganelavarasan@gmail.com> Co-authored-by: Arun Raj M <jarnura47@gmail.com>
This commit is contained in:
		| @ -1324,7 +1324,7 @@ pub fn get_redirection_response( | ||||
|                     .map(|(key, value)| (key.to_string(), value.to_string())), | ||||
|             ) | ||||
|         }); | ||||
|         services::RedirectForm { | ||||
|         services::RedirectForm::Form { | ||||
|             endpoint: url.to_string(), | ||||
|             method: response.action.method.unwrap_or(services::Method::Get), | ||||
|             form_fields, | ||||
|  | ||||
| @ -150,7 +150,7 @@ pub struct AirwallexCompleteRequest { | ||||
|  | ||||
| #[derive(Default, Debug, Serialize, Eq, PartialEq)] | ||||
| pub struct AirwallexThreeDsData { | ||||
|     acs_response: Option<common_utils::pii::SecretSerdeValue>, | ||||
|     acs_response: Option<Secret<String>>, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Serialize, Eq, PartialEq)] | ||||
| @ -166,7 +166,11 @@ impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for AirwallexCompleteR | ||||
|         Ok(Self { | ||||
|             request_id: Uuid::new_v4().to_string(), | ||||
|             three_ds: AirwallexThreeDsData { | ||||
|                 acs_response: item.request.payload.clone().map(Secret::new), | ||||
|                 acs_response: item | ||||
|                     .request | ||||
|                     .payload | ||||
|                     .as_ref() | ||||
|                     .map(|data| Secret::new(serde_json::Value::to_string(data))), | ||||
|             }, | ||||
|             three_ds_type: AirwallexThreeDsType::ThreeDSContinue, | ||||
|         }) | ||||
| @ -285,7 +289,7 @@ pub struct AirwallexPaymentsResponse { | ||||
| fn get_redirection_form( | ||||
|     response_url_data: AirwallexPaymentsNextAction, | ||||
| ) -> Option<services::RedirectForm> { | ||||
|     Some(services::RedirectForm { | ||||
|     Some(services::RedirectForm::Form { | ||||
|         endpoint: response_url_data.url.to_string(), | ||||
|         method: response_url_data.method, | ||||
|         form_fields: std::collections::HashMap::from([ | ||||
|  | ||||
| @ -8,8 +8,11 @@ use transformers as bambora; | ||||
| use super::utils::RefundsRequestData; | ||||
| use crate::{ | ||||
|     configs::settings, | ||||
|     connector::utils::{PaymentsAuthorizeRequestData, PaymentsSyncRequestData}, | ||||
|     core::errors::{self, CustomResult}, | ||||
|     connector::utils::{to_connector_meta, PaymentsAuthorizeRequestData, PaymentsSyncRequestData}, | ||||
|     core::{ | ||||
|         errors::{self, CustomResult}, | ||||
|         payments, | ||||
|     }, | ||||
|     headers, logger, | ||||
|     services::{self, ConnectorIntegration}, | ||||
|     types::{ | ||||
| @ -166,7 +169,7 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR | ||||
|         data: &types::PaymentsCancelRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> { | ||||
|         let response: bambora::BamboraPaymentsResponse = res | ||||
|         let response: bambora::BamboraResponse = res | ||||
|             .response | ||||
|             .parse_struct("bambora PaymentsResponse") | ||||
|             .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||
| @ -257,7 +260,7 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe | ||||
|         data: &types::PaymentsSyncRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> { | ||||
|         let response: bambora::BamboraPaymentsResponse = res | ||||
|         let response: bambora::BamboraResponse = res | ||||
|             .response | ||||
|             .parse_struct("bambora PaymentsResponse") | ||||
|             .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||
| @ -336,7 +339,7 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme | ||||
|         data: &types::PaymentsCaptureRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> { | ||||
|         let response: bambora::BamboraPaymentsResponse = res | ||||
|         let response: bambora::BamboraResponse = res | ||||
|             .response | ||||
|             .parse_struct("Bambora PaymentsResponse") | ||||
|             .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||
| @ -388,9 +391,9 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         _req: &types::PaymentsAuthorizeRouterData, | ||||
|         _connectors: &settings::Connectors, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Ok(format!("{}{}", self.base_url(_connectors), "/v1/payments")) | ||||
|         Ok(format!("{}{}", self.base_url(connectors), "/v1/payments")) | ||||
|     } | ||||
|  | ||||
|     fn get_request_body( | ||||
| @ -429,7 +432,7 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P | ||||
|         data: &types::PaymentsAuthorizeRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> { | ||||
|         let response: bambora::BamboraPaymentsResponse = res | ||||
|         let response: bambora::BamboraResponse = res | ||||
|             .response | ||||
|             .parse_struct("PaymentIntentResponse") | ||||
|             .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||
| @ -638,3 +641,111 @@ pub fn get_payment_flow(is_auto_capture: bool) -> bambora::PaymentFlow { | ||||
|         bambora::PaymentFlow::Authorize | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl services::ConnectorRedirectResponse for Bambora { | ||||
|     fn get_flow_type( | ||||
|         &self, | ||||
|         _query_params: &str, | ||||
|         _json_payload: Option<serde_json::Value>, | ||||
|         _action: services::PaymentAction, | ||||
|     ) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> { | ||||
|         Ok(payments::CallConnectorAction::Trigger) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl api::PaymentsCompleteAuthorize for Bambora {} | ||||
|  | ||||
| impl | ||||
|     ConnectorIntegration< | ||||
|         api::CompleteAuthorize, | ||||
|         types::CompleteAuthorizeData, | ||||
|         types::PaymentsResponseData, | ||||
|     > for Bambora | ||||
| { | ||||
|     fn get_headers( | ||||
|         &self, | ||||
|         req: &types::PaymentsCompleteAuthorizeRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> { | ||||
|         self.build_headers(req, connectors) | ||||
|     } | ||||
|  | ||||
|     fn get_content_type(&self) -> &'static str { | ||||
|         self.common_get_content_type() | ||||
|     } | ||||
|  | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         req: &types::PaymentsCompleteAuthorizeRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         let meta: bambora::BamboraMeta = to_connector_meta(req.request.connector_meta.clone())?; | ||||
|         Ok(format!( | ||||
|             "{}/v1/payments/{}{}", | ||||
|             self.base_url(connectors), | ||||
|             meta.three_d_session_data, | ||||
|             "/continue" | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn get_request_body( | ||||
|         &self, | ||||
|         req: &types::PaymentsCompleteAuthorizeRouterData, | ||||
|     ) -> CustomResult<Option<String>, errors::ConnectorError> { | ||||
|         let request = bambora::BamboraThreedsContinueRequest::try_from(&req.request)?; | ||||
|         let bambora_req = | ||||
|             utils::Encode::<bambora::BamboraThreedsContinueRequest>::encode_to_string_of_json( | ||||
|                 &request, | ||||
|             ) | ||||
|             .change_context(errors::ConnectorError::RequestEncodingFailed)?; | ||||
|         Ok(Some(bambora_req)) | ||||
|     } | ||||
|  | ||||
|     fn build_request( | ||||
|         &self, | ||||
|         req: &types::PaymentsCompleteAuthorizeRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         let request = services::RequestBuilder::new() | ||||
|             .method(services::Method::Post) | ||||
|             .url(&types::PaymentsCompleteAuthorizeType::get_url( | ||||
|                 self, req, connectors, | ||||
|             )?) | ||||
|             .headers(types::PaymentsCompleteAuthorizeType::get_headers( | ||||
|                 self, req, connectors, | ||||
|             )?) | ||||
|             .body(types::PaymentsCompleteAuthorizeType::get_request_body( | ||||
|                 self, req, | ||||
|             )?) | ||||
|             .build(); | ||||
|         Ok(Some(request)) | ||||
|     } | ||||
|  | ||||
|     fn handle_response( | ||||
|         &self, | ||||
|         data: &types::PaymentsCompleteAuthorizeRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::PaymentsCompleteAuthorizeRouterData, errors::ConnectorError> { | ||||
|         let response: bambora::BamboraResponse = res | ||||
|             .response | ||||
|             .parse_struct("Bambora PaymentsResponse") | ||||
|             .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||
|         logger::debug!(bamborapayments_create_response=?response); | ||||
|         types::RouterData::try_from(( | ||||
|             types::ResponseRouterData { | ||||
|                 response, | ||||
|                 data: data.clone(), | ||||
|                 http_code: res.status_code, | ||||
|             }, | ||||
|             bambora::PaymentFlow::Capture, | ||||
|         )) | ||||
|         .change_context(errors::ConnectorError::ResponseHandlingFailed) | ||||
|     } | ||||
|  | ||||
|     fn get_error_response( | ||||
|         &self, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<ErrorResponse, errors::ConnectorError> { | ||||
|         self.build_error_response(res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,4 +1,6 @@ | ||||
| use base64::Engine; | ||||
| use common_utils::ext_traits::ValueExt; | ||||
| use error_stack::{IntoReport, ResultExt}; | ||||
| use masking::Secret; | ||||
| use serde::{Deserialize, Deserializer, Serialize}; | ||||
|  | ||||
| @ -6,6 +8,7 @@ use crate::{ | ||||
|     connector::utils::PaymentsAuthorizeRequestData, | ||||
|     consts, | ||||
|     core::errors, | ||||
|     services, | ||||
|     types::{self, api, storage::enums}, | ||||
| }; | ||||
|  | ||||
| @ -24,19 +27,21 @@ pub struct BamboraCard { | ||||
|  | ||||
| #[derive(Default, Debug, Serialize, Eq, PartialEq)] | ||||
| pub struct ThreeDSecure { | ||||
|     // browser: Option<Browser>, //Needed only in case of 3Ds 2.0. Need to update request for this. | ||||
|     browser: Option<BamboraBrowserInfo>, //Needed only in case of 3Ds 2.0. Need to update request for this. | ||||
|     enabled: bool, | ||||
|     version: Option<i64>, | ||||
|     auth_required: Option<bool>, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Serialize, Eq, PartialEq)] | ||||
| pub struct Browser { | ||||
| pub struct BamboraBrowserInfo { | ||||
|     accept_header: String, | ||||
|     java_enabled: String, | ||||
|     java_enabled: bool, | ||||
|     language: String, | ||||
|     color_depth: String, | ||||
|     screen_height: i64, | ||||
|     screen_width: i64, | ||||
|     time_zone: i64, | ||||
|     color_depth: u8, | ||||
|     screen_height: u32, | ||||
|     screen_width: u32, | ||||
|     time_zone: i32, | ||||
|     user_agent: String, | ||||
|     javascript_enabled: bool, | ||||
| } | ||||
| @ -45,16 +50,63 @@ pub struct Browser { | ||||
| pub struct BamboraPaymentsRequest { | ||||
|     amount: i64, | ||||
|     payment_method: PaymentMethod, | ||||
|     customer_ip: Option<std::net::IpAddr>, | ||||
|     term_url: Option<String>, | ||||
|     card: BamboraCard, | ||||
| } | ||||
|  | ||||
| fn get_browser_info(item: &types::PaymentsAuthorizeRouterData) -> Option<BamboraBrowserInfo> { | ||||
|     if matches!(item.auth_type, enums::AuthenticationType::ThreeDs) { | ||||
|         item.request | ||||
|             .browser_info | ||||
|             .as_ref() | ||||
|             .map(|info| BamboraBrowserInfo { | ||||
|                 accept_header: info.accept_header.clone(), | ||||
|                 java_enabled: info.java_enabled, | ||||
|                 language: info.language.clone(), | ||||
|                 color_depth: info.color_depth, | ||||
|                 screen_height: info.screen_height, | ||||
|                 screen_width: info.screen_width, | ||||
|                 time_zone: info.time_zone, | ||||
|                 user_agent: info.user_agent.clone(), | ||||
|                 javascript_enabled: info.java_script_enabled, | ||||
|             }) | ||||
|     } else { | ||||
|         None | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<&types::CompleteAuthorizeData> for BamboraThreedsContinueRequest { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(value: &types::CompleteAuthorizeData) -> Result<Self, Self::Error> { | ||||
|         let card_response: CardResponse = value | ||||
|             .payload | ||||
|             .clone() | ||||
|             .ok_or(errors::ConnectorError::MissingRequiredField { | ||||
|                 field_name: "payload", | ||||
|             })? | ||||
|             .parse_value("CardResponse") | ||||
|             .change_context(errors::ConnectorError::ParsingFailed)?; | ||||
|         let bambora_req = Self { | ||||
|             payment_method: "credit_card".to_string(), | ||||
|             card_response, | ||||
|         }; | ||||
|         Ok(bambora_req) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<&types::PaymentsAuthorizeRouterData> for BamboraPaymentsRequest { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> { | ||||
|         match item.request.payment_method_data.clone() { | ||||
|             api::PaymentMethodData::Card(req_card) => { | ||||
|                 let three_ds = match item.auth_type { | ||||
|                     enums::AuthenticationType::ThreeDs => Some(ThreeDSecure { enabled: true }), | ||||
|                     enums::AuthenticationType::ThreeDs => Some(ThreeDSecure { | ||||
|                         enabled: true, | ||||
|                         browser: get_browser_info(item), | ||||
|                         version: Some(2), | ||||
|                         auth_required: Some(true), | ||||
|                     }), | ||||
|                     enums::AuthenticationType::NoThreeDs => None, | ||||
|                 }; | ||||
|                 let bambora_card = BamboraCard { | ||||
| @ -66,10 +118,13 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for BamboraPaymentsRequest { | ||||
|                     three_d_secure: three_ds, | ||||
|                     complete: item.request.is_auto_capture()?, | ||||
|                 }; | ||||
|                 let browser_info = item.request.get_browser_info()?; | ||||
|                 Ok(Self { | ||||
|                     amount: item.request.amount, | ||||
|                     payment_method: PaymentMethod::Card, | ||||
|                     card: bambora_card, | ||||
|                     customer_ip: browser_info.ip_address, | ||||
|                     term_url: item.request.complete_authorize_url.clone(), | ||||
|                 }) | ||||
|             } | ||||
|             _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), | ||||
| @ -115,42 +170,68 @@ pub enum PaymentFlow { | ||||
| // PaymentsResponse | ||||
| impl<F, T> | ||||
|     TryFrom<( | ||||
|         types::ResponseRouterData<F, BamboraPaymentsResponse, T, types::PaymentsResponseData>, | ||||
|         types::ResponseRouterData<F, BamboraResponse, T, types::PaymentsResponseData>, | ||||
|         PaymentFlow, | ||||
|     )> for types::RouterData<F, T, types::PaymentsResponseData> | ||||
| { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from( | ||||
|         data: ( | ||||
|             types::ResponseRouterData<F, BamboraPaymentsResponse, T, types::PaymentsResponseData>, | ||||
|             types::ResponseRouterData<F, BamboraResponse, T, types::PaymentsResponseData>, | ||||
|             PaymentFlow, | ||||
|         ), | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         let flow = data.1; | ||||
|         let item = data.0; | ||||
|         let pg_response = item.response; | ||||
|         Ok(Self { | ||||
|             status: match pg_response.approved.as_str() { | ||||
|                 "0" => match flow { | ||||
|                     PaymentFlow::Authorize => enums::AttemptStatus::AuthorizationFailed, | ||||
|                     PaymentFlow::Capture => enums::AttemptStatus::Failure, | ||||
|                     PaymentFlow::Void => enums::AttemptStatus::VoidFailed, | ||||
|         match item.response { | ||||
|             BamboraResponse::NormalTransaction(pg_response) => Ok(Self { | ||||
|                 status: match pg_response.approved.as_str() { | ||||
|                     "0" => match flow { | ||||
|                         PaymentFlow::Authorize => enums::AttemptStatus::AuthorizationFailed, | ||||
|                         PaymentFlow::Capture => enums::AttemptStatus::Failure, | ||||
|                         PaymentFlow::Void => enums::AttemptStatus::VoidFailed, | ||||
|                     }, | ||||
|                     "1" => match flow { | ||||
|                         PaymentFlow::Authorize => enums::AttemptStatus::Authorized, | ||||
|                         PaymentFlow::Capture => enums::AttemptStatus::Charged, | ||||
|                         PaymentFlow::Void => enums::AttemptStatus::Voided, | ||||
|                     }, | ||||
|                     &_ => Err(errors::ConnectorError::ResponseDeserializationFailed)?, | ||||
|                 }, | ||||
|                 "1" => match flow { | ||||
|                     PaymentFlow::Authorize => enums::AttemptStatus::Authorized, | ||||
|                     PaymentFlow::Capture => enums::AttemptStatus::Charged, | ||||
|                     PaymentFlow::Void => enums::AttemptStatus::Voided, | ||||
|                 }, | ||||
|                 &_ => Err(errors::ConnectorError::ResponseDeserializationFailed)?, | ||||
|             }, | ||||
|             response: Ok(types::PaymentsResponseData::TransactionResponse { | ||||
|                 resource_id: types::ResponseId::ConnectorTransactionId(pg_response.id.to_string()), | ||||
|                 redirection_data: None, | ||||
|                 mandate_reference: None, | ||||
|                 connector_metadata: None, | ||||
|                 response: Ok(types::PaymentsResponseData::TransactionResponse { | ||||
|                     resource_id: types::ResponseId::ConnectorTransactionId( | ||||
|                         pg_response.id.to_string(), | ||||
|                     ), | ||||
|                     redirection_data: None, | ||||
|                     mandate_reference: None, | ||||
|                     connector_metadata: None, | ||||
|                 }), | ||||
|                 ..item.data | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|  | ||||
|             BamboraResponse::ThreeDsResponse(response) => { | ||||
|                 let value = url::form_urlencoded::parse(response.contents.as_bytes()) | ||||
|                     .map(|(key, val)| [key, val].concat()) | ||||
|                     .collect(); | ||||
|                 let redirection_data = Some(services::RedirectForm::Html { html_data: value }); | ||||
|                 Ok(Self { | ||||
|                     status: enums::AttemptStatus::AuthenticationPending, | ||||
|                     response: Ok(types::PaymentsResponseData::TransactionResponse { | ||||
|                         resource_id: types::ResponseId::NoResponseId, | ||||
|                         redirection_data, | ||||
|                         mandate_reference: None, | ||||
|                         connector_metadata: Some( | ||||
|                             serde_json::to_value(BamboraMeta { | ||||
|                                 three_d_session_data: response.three_d_session_data, | ||||
|                             }) | ||||
|                             .into_report() | ||||
|                             .change_context(errors::ConnectorError::ResponseHandlingFailed)?, | ||||
|                         ), | ||||
|                     }), | ||||
|                     ..item.data | ||||
|                 }) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -173,7 +254,14 @@ where | ||||
|     Ok(res) | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] | ||||
| #[derive(Debug, Clone, Deserialize)] | ||||
| #[serde(untagged)] | ||||
| pub enum BamboraResponse { | ||||
|     NormalTransaction(Box<BamboraPaymentsResponse>), | ||||
|     ThreeDsResponse(Box<Bambora3DsResponse>), | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Clone, Deserialize, PartialEq)] | ||||
| pub struct BamboraPaymentsResponse { | ||||
|     #[serde(deserialize_with = "str_or_i32")] | ||||
|     id: String, | ||||
| @ -203,7 +291,30 @@ pub struct BamboraPaymentsResponse { | ||||
|     risk_score: Option<f32>, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] | ||||
| #[derive(Debug, Clone, Deserialize)] | ||||
| pub struct Bambora3DsResponse { | ||||
|     #[serde(rename = "3d_session_data")] | ||||
|     three_d_session_data: String, | ||||
|     contents: String, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize, Default, Deserialize)] | ||||
| pub struct BamboraMeta { | ||||
|     pub three_d_session_data: String, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Serialize, Eq, PartialEq)] | ||||
| pub struct BamboraThreedsContinueRequest { | ||||
|     pub(crate) payment_method: String, | ||||
|     pub card_response: CardResponse, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Deserialize, Serialize, Eq, PartialEq)] | ||||
| pub struct CardResponse { | ||||
|     pub(crate) cres: Option<common_utils::pii::SecretSerdeValue>, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Clone, Deserialize, PartialEq)] | ||||
| pub struct CardData { | ||||
|     name: Option<String>, | ||||
|     expiry_month: Option<String>, | ||||
| @ -337,34 +448,34 @@ impl From<RefundStatus> for enums::RefundStatus { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Clone, Serialize, Deserialize)] | ||||
| #[derive(Default, Debug, Clone, Deserialize)] | ||||
| pub struct RefundResponse { | ||||
|     #[serde(deserialize_with = "str_or_i32")] | ||||
|     id: String, | ||||
|     authorizing_merchant_id: i32, | ||||
|     pub id: String, | ||||
|     pub authorizing_merchant_id: i32, | ||||
|     #[serde(deserialize_with = "str_or_i32")] | ||||
|     approved: String, | ||||
|     pub approved: String, | ||||
|     #[serde(deserialize_with = "str_or_i32")] | ||||
|     message_id: String, | ||||
|     message: String, | ||||
|     auth_code: String, | ||||
|     created: String, | ||||
|     amount: f32, | ||||
|     order_number: String, | ||||
|     pub message_id: String, | ||||
|     pub message: String, | ||||
|     pub auth_code: String, | ||||
|     pub created: String, | ||||
|     pub amount: f32, | ||||
|     pub order_number: String, | ||||
|     #[serde(rename = "type")] | ||||
|     payment_type: String, | ||||
|     comments: Option<String>, | ||||
|     batch_number: Option<String>, | ||||
|     total_refunds: Option<f32>, | ||||
|     total_completions: Option<f32>, | ||||
|     payment_method: String, | ||||
|     card: CardData, | ||||
|     billing: Option<AddressData>, | ||||
|     shipping: Option<AddressData>, | ||||
|     custom: CustomData, | ||||
|     adjusted_by: Option<Vec<AdjustedBy>>, | ||||
|     links: Vec<Links>, | ||||
|     risk_score: Option<f32>, | ||||
|     pub payment_type: String, | ||||
|     pub comments: Option<String>, | ||||
|     pub batch_number: Option<String>, | ||||
|     pub total_refunds: Option<f32>, | ||||
|     pub total_completions: Option<f32>, | ||||
|     pub payment_method: String, | ||||
|     pub card: CardData, | ||||
|     pub billing: Option<AddressData>, | ||||
|     pub shipping: Option<AddressData>, | ||||
|     pub custom: CustomData, | ||||
|     pub adjusted_by: Option<Vec<AdjustedBy>>, | ||||
|     pub links: Vec<Links>, | ||||
|     pub risk_score: Option<f32>, | ||||
| } | ||||
|  | ||||
| impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>> | ||||
|  | ||||
| @ -124,7 +124,7 @@ impl<F, T> | ||||
|         >, | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         let form_fields = HashMap::new(); | ||||
|         let redirection_data = services::RedirectForm { | ||||
|         let redirection_data = services::RedirectForm::Form { | ||||
|             endpoint: item.response.data.hosted_url.to_string(), | ||||
|             method: services::Method::Get, | ||||
|             form_fields, | ||||
|  | ||||
| @ -1121,7 +1121,7 @@ where | ||||
|                 .and_then(|o| o.card.clone()) | ||||
|                 .and_then(|card| card.three_d) | ||||
|                 .and_then(|three_ds| three_ds.acs_url.zip(three_ds.c_req)) | ||||
|                 .map(|(base_url, creq)| services::RedirectForm { | ||||
|                 .map(|(base_url, creq)| services::RedirectForm::Form { | ||||
|                     endpoint: base_url, | ||||
|                     method: services::Method::Post, | ||||
|                     form_fields: std::collections::HashMap::from([("creq".to_string(), creq)]), | ||||
|  | ||||
| @ -97,7 +97,7 @@ impl<F, T> | ||||
|         >, | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         let form_fields = HashMap::new(); | ||||
|         let redirection_data = services::RedirectForm { | ||||
|         let redirection_data = services::RedirectForm::Form { | ||||
|             endpoint: item.response.data.hosted_checkout_url.to_string(), | ||||
|             method: services::Method::Get, | ||||
|             form_fields, | ||||
|  | ||||
| @ -558,11 +558,13 @@ fn handle_cards_response( | ||||
|         response.redirect_url.clone(), | ||||
|     )?; | ||||
|     let form_fields = response.redirect_params.unwrap_or_default(); | ||||
|     let redirection_data = response.redirect_url.map(|url| services::RedirectForm { | ||||
|         endpoint: url.to_string(), | ||||
|         method: services::Method::Post, | ||||
|         form_fields, | ||||
|     }); | ||||
|     let redirection_data = response | ||||
|         .redirect_url | ||||
|         .map(|url| services::RedirectForm::Form { | ||||
|             endpoint: url.to_string(), | ||||
|             method: services::Method::Post, | ||||
|             form_fields, | ||||
|         }); | ||||
|     let error = if msg.is_some() { | ||||
|         Some(types::ErrorResponse { | ||||
|             code: response.payment_status, | ||||
|  | ||||
| @ -89,7 +89,6 @@ default_imp_for_complete_authorize!( | ||||
|     connector::Aci, | ||||
|     connector::Adyen, | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bambora, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Checkout, | ||||
| @ -132,7 +131,6 @@ default_imp_for_connector_redirect_response!( | ||||
|     connector::Aci, | ||||
|     connector::Adyen, | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bambora, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Coinbase, | ||||
|  | ||||
| @ -701,7 +701,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::CompleteAuthoriz | ||||
|         let json_payload = payment_data | ||||
|             .connector_response | ||||
|             .encoded_data | ||||
|             .map(serde_json::to_value) | ||||
|             .map(|s| serde_json::from_str::<serde_json::Value>(&s)) | ||||
|             .transpose() | ||||
|             .into_report() | ||||
|             .change_context(errors::ApiErrorResponse::InternalServerError)?; | ||||
|  | ||||
| @ -467,10 +467,15 @@ pub struct ApplicationRedirectResponse { | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] | ||||
| pub struct RedirectForm { | ||||
|     pub endpoint: String, | ||||
|     pub method: Method, | ||||
|     pub form_fields: HashMap<String, String>, | ||||
| pub enum RedirectForm { | ||||
|     Form { | ||||
|         endpoint: String, | ||||
|         method: Method, | ||||
|         form_fields: HashMap<String, String>, | ||||
|     }, | ||||
|     Html { | ||||
|         html_data: String, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| impl From<(url::Url, Method)> for RedirectForm { | ||||
| @ -484,7 +489,7 @@ impl From<(url::Url, Method)> for RedirectForm { | ||||
|         // Do not include query params in the endpoint | ||||
|         redirect_url.set_query(None); | ||||
|  | ||||
|         Self { | ||||
|         Self::Form { | ||||
|             endpoint: redirect_url.to_string(), | ||||
|             method, | ||||
|             form_fields, | ||||
| @ -681,7 +686,12 @@ impl Authenticate for api_models::payment_methods::PaymentMethodListRequest { | ||||
| pub fn build_redirection_form(form: &RedirectForm) -> maud::Markup { | ||||
|     use maud::PreEscaped; | ||||
|  | ||||
|     maud::html! { | ||||
|     match form { | ||||
|         RedirectForm::Form { | ||||
|             endpoint, | ||||
|             method, | ||||
|             form_fields, | ||||
|         } => maud::html! { | ||||
|         (maud::DOCTYPE) | ||||
|         html { | ||||
|             meta name="viewport" content="width=device-width, initial-scale=1"; | ||||
| @ -726,8 +736,8 @@ pub fn build_redirection_form(form: &RedirectForm) -> maud::Markup { | ||||
|  | ||||
|  | ||||
|                 h3 style="text-align: center;" { "Please wait while we process your payment..." } | ||||
|                 form action=(PreEscaped(&form.endpoint)) method=(form.method.to_string()) #payment_form { | ||||
|                     @for (field, value) in &form.form_fields { | ||||
|                     form action=(PreEscaped(endpoint)) method=(method.to_string()) #payment_form { | ||||
|                         @for (field, value) in form_fields { | ||||
|                         input type="hidden" name=(field) value=(value); | ||||
|                     } | ||||
|                 } | ||||
| @ -735,6 +745,8 @@ pub fn build_redirection_form(form: &RedirectForm) -> maud::Markup { | ||||
|                 (PreEscaped(r#"<script type="text/javascript"> var frm = document.getElementById("payment_form"); window.setTimeout(function () { frm.submit(); }, 300); </script>"#)) | ||||
|             } | ||||
|         } | ||||
|         }, | ||||
|         RedirectForm::Html { html_data } => PreEscaped(html_data.to_string()), | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Shankar Singh C
					Shankar Singh C