diff --git a/config/Development.toml b/config/Development.toml index 33fe4f74c7..29ed380b7c 100644 --- a/config/Development.toml +++ b/config/Development.toml @@ -43,7 +43,7 @@ locker_decryption_key2 = "" [connectors.supported] wallets = ["klarna","braintree","applepay"] -cards = ["stripe","adyen","authorizedotnet","checkout","braintree","aci","shift4","cybersource", "worldpay", "globalpay", "fiserv", "worldline"] +cards = ["stripe","adyen","authorizedotnet","checkout","braintree","aci","shift4","cybersource", "worldpay", "globalpay", "fiserv", "payu", "worldline"] [refund] max_attempts = 10 @@ -92,7 +92,7 @@ base_url = "https://cert.api.fiservapps.com/" base_url = "http://localhost:9090/" [connectors.payu] -base_url = "https://secure.snd.payu.com/api/" +base_url = "https://secure.snd.payu.com/" [connectors.globalpay] base_url = "https://apis.sandbox.globalpay.com/ucp/" diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 1bd75a634f..dc8666b10b 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -529,7 +529,7 @@ pub enum Connector { impl Connector { pub fn supports_access_token(&self) -> bool { - matches!(self, Self::Globalpay) + matches!(self, Self::Globalpay | Self::Payu) } } diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index 930ae3ab51..bf553f3639 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -19,7 +19,7 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, }, - utils::{self, BytesExt}, + utils::{self, BytesExt, OptionExt}, }; #[derive(Debug, Clone)] @@ -633,10 +633,17 @@ impl ConnectorIntegration CustomResult { + let refund_id = req + .response + .clone() + .ok() + .get_required_value("response") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)? + .connector_refund_id; Ok(format!( "{}tss/v2/transactions/{}", self.base_url(connectors), - req.request.connector_transaction_id + refund_id )) } fn build_request( diff --git a/crates/router/src/connector/fiserv/transformers.rs b/crates/router/src/connector/fiserv/transformers.rs index a16fdea007..b998db5d70 100644 --- a/crates/router/src/connector/fiserv/transformers.rs +++ b/crates/router/src/connector/fiserv/transformers.rs @@ -197,7 +197,7 @@ pub struct FiservPaymentsResponse { #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct GatewayResponse { - gateway_transaction_id: String, + gateway_transaction_id: Option, transaction_state: FiservPaymentStatus, transaction_processing_details: TransactionProcessingDetails, } diff --git a/crates/router/src/connector/globalpay.rs b/crates/router/src/connector/globalpay.rs index 6d2b2be63a..25b5a0fb06 100644 --- a/crates/router/src/connector/globalpay.rs +++ b/crates/router/src/connector/globalpay.rs @@ -26,7 +26,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, }, - utils::{self, BytesExt}, + utils::{self, BytesExt, OptionExt}, }; #[derive(Debug, Clone)] @@ -633,10 +633,17 @@ impl ConnectorIntegration CustomResult { + let refund_id = req + .response + .clone() + .ok() + .get_required_value("response") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)? + .connector_refund_id; Ok(format!( "{}transactions/{}", self.base_url(connectors), - req.request.connector_transaction_id + refund_id )) } diff --git a/crates/router/src/connector/payu.rs b/crates/router/src/connector/payu.rs index f7ffc31b71..2354679918 100644 --- a/crates/router/src/connector/payu.rs +++ b/crates/router/src/connector/payu.rs @@ -11,7 +11,8 @@ use crate::{ errors::{self, CustomResult}, payments, }, - headers, logger, services, + headers, logger, + services::{self, ConnectorIntegration}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -25,20 +26,29 @@ pub struct Payu; impl ConnectorCommonExt for Payu where - Self: services::ConnectorIntegration, + Self: ConnectorIntegration, { fn build_headers( &self, req: &types::RouterData, _connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { - let mut header = vec![( + let mut headers = vec![( headers::CONTENT_TYPE.to_string(), - types::PaymentsAuthorizeType::get_content_type(self).to_string(), + self.get_content_type().to_string(), )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - header.append(&mut api_key); - Ok(header) + let access_token = req + .access_token + .clone() + .ok_or(errors::ConnectorError::FailedToObtainAuthType)?; + + let auth_header = ( + headers::AUTHORIZATION.to_string(), + format!("Bearer {}", access_token.token), + ); + + headers.push(auth_header); + Ok(headers) } } @@ -64,10 +74,12 @@ impl ConnectorCommon for Payu { .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)]) } + fn build_error_response( &self, res: types::Response, ) -> CustomResult { + logger::debug!(payu_error_response=?res); let response: payu::PayuErrorResponse = res .response .parse_struct("Payu ErrorResponse") @@ -85,23 +97,15 @@ impl ConnectorCommon for Payu { impl api::Payment for Payu {} impl api::PreVerify for Payu {} -impl - services::ConnectorIntegration< - api::Verify, - types::VerifyRequestData, - types::PaymentsResponseData, - > for Payu +impl ConnectorIntegration + for Payu { } impl api::PaymentVoid for Payu {} -impl - services::ConnectorIntegration< - api::Void, - types::PaymentsCancelData, - types::PaymentsResponseData, - > for Payu +impl ConnectorIntegration + for Payu { fn get_headers( &self, @@ -124,7 +128,7 @@ impl Ok(format!( "{}{}{}", self.base_url(connectors), - "v2_1/orders/", + "api/v2_1/orders/", connector_payment_id )) } @@ -167,18 +171,106 @@ impl impl api::ConnectorAccessToken for Payu {} -impl - services::ConnectorIntegration< - api::AccessTokenAuth, - types::AccessTokenRequestData, - types::AccessToken, - > for Payu +impl ConnectorIntegration + for Payu { + fn get_url( + &self, + _req: &types::RefreshTokenRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "pl/standard/user/oauth/authorize" + )) + } + + fn get_content_type(&self) -> &'static str { + "application/x-www-form-urlencoded" + } + + fn get_headers( + &self, + _req: &types::RefreshTokenRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(vec![( + headers::CONTENT_TYPE.to_string(), + types::RefreshTokenType::get_content_type(self).to_string(), + )]) + } + + fn get_request_body( + &self, + req: &types::RefreshTokenRouterData, + ) -> CustomResult, errors::ConnectorError> { + let payu_req = utils::Encode::::convert_and_url_encode(req) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + + logger::debug!(payu_access_token_request=?payu_req); + Ok(Some(payu_req)) + } + + fn build_request( + &self, + req: &types::RefreshTokenRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req = Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .headers(types::RefreshTokenType::get_headers(self, req, connectors)?) + .url(&types::RefreshTokenType::get_url(self, req, connectors)?) + .body(types::RefreshTokenType::get_request_body(self, req)?) + .build(), + ); + + logger::debug!(payu_access_token_request=?req); + + Ok(req) + } + fn handle_response( + &self, + data: &types::RefreshTokenRouterData, + res: types::Response, + ) -> CustomResult { + logger::debug!(access_token_response=?res); + let response: payu::PayuAuthUpdateResponse = res + .response + .parse_struct("payu PayuAuthUpdateResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + } + .try_into() + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: types::Response, + ) -> CustomResult { + logger::debug!(access_token_error_response=?res); + let response: payu::PayuAccessTokenErrorResponse = res + .response + .parse_struct("Payu AccessTokenErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.error, + message: response.error_description, + reason: None, + }) + } } impl api::PaymentSync for Payu {} -impl - services::ConnectorIntegration +impl ConnectorIntegration for Payu { fn get_headers( @@ -206,7 +298,7 @@ impl Ok(format!( "{}{}{}", self.base_url(connectors), - "v2_1/orders/", + "api/v2_1/orders/", connector_payment_id )) } @@ -253,12 +345,8 @@ impl } impl api::PaymentCapture for Payu {} -impl - services::ConnectorIntegration< - api::Capture, - types::PaymentsCaptureData, - types::PaymentsResponseData, - > for Payu +impl ConnectorIntegration + for Payu { fn get_headers( &self, @@ -280,7 +368,7 @@ impl Ok(format!( "{}{}{}{}", self.base_url(connectors), - "v2_1/orders/", + "api/v2_1/orders/", req.request.connector_transaction_id, "/status" )) @@ -343,24 +431,16 @@ impl impl api::PaymentSession for Payu {} -impl - services::ConnectorIntegration< - api::Session, - types::PaymentsSessionData, - types::PaymentsResponseData, - > for Payu +impl ConnectorIntegration + for Payu { //TODO: implement sessions flow } impl api::PaymentAuthorize for Payu {} -impl - services::ConnectorIntegration< - api::Authorize, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - > for Payu +impl ConnectorIntegration + for Payu { fn get_headers( &self, @@ -379,7 +459,11 @@ impl _req: &types::PaymentsAuthorizeRouterData, connectors: &settings::Connectors, ) -> CustomResult { - Ok(format!("{}{}", self.base_url(connectors), "v2_1/orders")) + Ok(format!( + "{}{}", + self.base_url(connectors), + "api/v2_1/orders" + )) } fn get_request_body( @@ -447,9 +531,7 @@ impl api::Refund for Payu {} impl api::RefundExecute for Payu {} impl api::RefundSync for Payu {} -impl services::ConnectorIntegration - for Payu -{ +impl ConnectorIntegration for Payu { fn get_headers( &self, req: &types::RefundsRouterData, @@ -470,7 +552,7 @@ impl services::ConnectorIntegration - for Payu -{ +impl ConnectorIntegration for Payu { fn get_headers( &self, req: &types::RefundSyncRouterData, @@ -553,7 +633,7 @@ impl services::ConnectorIntegration } } +#[derive(Debug, Clone, Serialize, PartialEq)] +pub struct PayuAuthUpdateRequest { + grant_type: String, + client_id: String, + client_secret: String, +} + +impl TryFrom<&types::RefreshTokenRouterData> for PayuAuthUpdateRequest { + type Error = error_stack::Report; + fn try_from(item: &types::RefreshTokenRouterData) -> Result { + Ok(Self { + grant_type: "client_credentials".to_string(), + client_id: item.get_request_id()?, + client_secret: item.request.app_id.clone(), + }) + } +} +#[derive(Default, Debug, Clone, Deserialize, PartialEq)] +pub struct PayuAuthUpdateResponse { + pub access_token: String, + pub token_type: String, + pub expires_in: i64, + pub grant_type: String, +} + +impl TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::AccessToken { + token: item.response.access_token, + expires: item.response.expires_in, + }), + ..item.data + }) + } +} + #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct PayuPaymentsCancelResponse { @@ -456,9 +499,9 @@ impl TryFrom<&types::RefundsRouterData> for PayuRefundRequest { fn try_from(item: &types::RefundsRouterData) -> Result { Ok(Self { refund: PayuRefundRequestData { - description: item.description.clone().ok_or( + description: item.request.reason.clone().ok_or( errors::ConnectorError::MissingRequiredField { - field_name: "item.description".to_string(), + field_name: "item.request.reason".to_string(), }, )?, amount: None, @@ -474,6 +517,7 @@ impl TryFrom<&types::RefundsRouterData> for PayuRefundRequest { #[serde(rename_all = "UPPERCASE")] pub enum RefundStatus { Finalized, + Completed, Canceled, #[default] Pending, @@ -482,7 +526,7 @@ pub enum RefundStatus { impl From for enums::RefundStatus { fn from(item: RefundStatus) -> Self { match item { - RefundStatus::Finalized => Self::Success, + RefundStatus::Finalized | RefundStatus::Completed => Self::Success, RefundStatus::Canceled => Self::Failure, RefundStatus::Pending => Self::Pending, } @@ -563,3 +607,9 @@ pub struct PayuErrorData { pub struct PayuErrorResponse { pub status: PayuErrorData, } + +#[derive(Deserialize, Debug)] +pub struct PayuAccessTokenErrorResponse { + pub error: String, + pub error_description: String, +} diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 4b8c095cd8..952e788da6 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -18,6 +18,20 @@ pub fn missing_field_err( } type Error = error_stack::Report; + +pub trait AccessTokenRequestInfo { + fn get_request_id(&self) -> Result; +} + +impl AccessTokenRequestInfo for types::RefreshTokenRouterData { + fn get_request_id(&self) -> Result { + self.request + .id + .clone() + .ok_or_else(missing_field_err("request.id")) + } +} + pub trait PaymentsRequestData { fn get_attempt_id(&self) -> Result; fn get_billing(&self) -> Result<&api::Address, Error>; diff --git a/crates/router/tests/connectors/fiserv.rs b/crates/router/tests/connectors/fiserv.rs index 3a183ba830..fb7a4ce5b8 100644 --- a/crates/router/tests/connectors/fiserv.rs +++ b/crates/router/tests/connectors/fiserv.rs @@ -1,3 +1,4 @@ +use futures::future::OptionFuture; use masking::Secret; use router::types::{self, api, storage::enums}; use serde_json::json; @@ -79,52 +80,78 @@ async fn should_authorize_and_capture_payment() { assert_eq!(response.unwrap().status, enums::AttemptStatus::Charged); } -// You get a service declined for Payment Capture, look into it from merchant dashboard -/* #[actix_web::test] async fn should_capture_already_authorized_payment() { let connector = Fiserv {}; - let authorize_response = connector.authorize_payment(None, None).await; + let authorize_response = connector + .authorize_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethod::Card(api::CCard { + card_number: Secret::new("4005550000000019".to_string()), + card_exp_month: Secret::new("02".to_string()), + card_exp_year: Secret::new("2035".to_string()), + card_holder_name: Secret::new("John Doe".to_string()), + card_cvc: Secret::new("123".to_string()), + }), + capture_method: Some(storage_models::enums::CaptureMethod::Manual), + ..utils::PaymentAuthorizeType::default().0 + }), + None, + ) + .await + .unwrap(); assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized); let txn_id = utils::get_connector_transaction_id(authorize_response); let response: OptionFuture<_> = txn_id .map(|transaction_id| async move { - connector.capture_payment(transaction_id, None, None).await.status + connector + .capture_payment(transaction_id, None, None) + .await + .unwrap() + .status }) .into(); assert_eq!(response.await, Some(enums::AttemptStatus::Charged)); } #[actix_web::test] -async fn should_fail_payment_for_incorrect_cvc() { - let response = Fiserv {}.make_payment(Some(types::PaymentsAuthorizeData { - payment_method_data: types::api::PaymentMethod::Card(api::CCard { - card_number: Secret::new("4024007134364842".to_string()), - ..utils::CCardType::default().0 +async fn should_fail_payment_for_missing_cvc() { + let response = Fiserv {} + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethod::Card(api::CCard { + card_number: Secret::new("4005550000000019".to_string()), + card_exp_month: Secret::new("02".to_string()), + card_exp_year: Secret::new("2035".to_string()), + card_holder_name: Secret::new("John Doe".to_string()), + card_cvc: Secret::new("".to_string()), + }), + ..utils::PaymentAuthorizeType::default().0 }), - ..utils::PaymentAuthorizeType::default().0 - }), None) - .await; + None, + ) + .await + .unwrap(); let x = response.response.unwrap_err(); - assert_eq!( - x.message, - "The card's security code failed verification.".to_string(), - ); + assert_eq!(x.message, "Invalid or Missing Field Data".to_string(),); } +#[ignore] #[actix_web::test] async fn should_refund_succeeded_payment() { let connector = Fiserv {}; //make a successful payment - let response = connector.make_payment(None, None).await; + let response = connector.make_payment(None, None).await.unwrap(); //try refund for previous payment if let Some(transaction_id) = utils::get_connector_transaction_id(response) { - let response = connector.refund_payment(transaction_id, None, None).await; + let response = connector + .refund_payment(transaction_id, None, None) + .await + .unwrap(); assert_eq!( response.response.unwrap().refund_status, enums::RefundStatus::Success, ); } } -*/ diff --git a/crates/router/tests/connectors/globalpay.rs b/crates/router/tests/connectors/globalpay.rs index d2b1f3387d..31104d9aeb 100644 --- a/crates/router/tests/connectors/globalpay.rs +++ b/crates/router/tests/connectors/globalpay.rs @@ -56,6 +56,7 @@ fn get_default_payment_info() -> Option { ..Default::default() }), auth_type: None, + access_token: None, }) } diff --git a/crates/router/tests/connectors/payu.rs b/crates/router/tests/connectors/payu.rs index f13bdf9f11..d610119a65 100644 --- a/crates/router/tests/connectors/payu.rs +++ b/crates/router/tests/connectors/payu.rs @@ -1,13 +1,12 @@ -use router::types::{self, api, storage::enums}; +use router::types::{self, api, storage::enums, AccessToken, ConnectorAuthType}; use crate::{ connector_auth, - utils::{self, ConnectorActions, PaymentAuthorizeType}, + utils::{self, Connector, ConnectorActions, PaymentAuthorizeType}, }; - struct Payu; impl ConnectorActions for Payu {} -impl utils::Connector for Payu { +impl Connector for Payu { fn get_data(&self) -> types::api::ConnectorData { use router::connector::Payu; types::api::ConnectorData { @@ -17,7 +16,7 @@ impl utils::Connector for Payu { } } - fn get_auth_token(&self) -> types::ConnectorAuthType { + fn get_auth_token(&self) -> ConnectorAuthType { types::ConnectorAuthType::from( connector_auth::ConnectorAuthentication::new() .payu @@ -30,7 +29,26 @@ impl utils::Connector for Payu { } } +fn get_access_token() -> Option { + let connector = Payu {}; + match connector.get_auth_token() { + ConnectorAuthType::BodyKey { api_key, key1 } => Some(AccessToken { + token: api_key, + expires: key1.parse::().unwrap(), + }), + _ => None, + } +} +fn get_default_payment_info() -> Option { + Some(utils::PaymentInfo { + address: None, + auth_type: None, + access_token: get_access_token(), + }) +} + #[actix_web::test] +#[ignore] async fn should_authorize_card_payment() { //Authorize Card Payment in PLN currency let authorize_response = Payu {} @@ -39,7 +57,7 @@ async fn should_authorize_card_payment() { currency: enums::Currency::PLN, ..PaymentAuthorizeType::default().0 }), - None, + get_default_payment_info(), ) .await .unwrap(); @@ -47,7 +65,8 @@ async fn should_authorize_card_payment() { assert_eq!(authorize_response.status, enums::AttemptStatus::Pending); if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) { let sync_response = Payu {} - .sync_payment( + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, Some(types::PaymentsSyncData { connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( transaction_id.clone(), @@ -55,7 +74,7 @@ async fn should_authorize_card_payment() { encoded_data: None, capture_method: None, }), - None, + get_default_payment_info(), ) .await .unwrap(); @@ -74,7 +93,7 @@ async fn should_authorize_gpay_payment() { }), currency: enums::Currency::PLN, ..PaymentAuthorizeType::default().0 - }), None).await.unwrap(); + }), get_default_payment_info()).await.unwrap(); assert_eq!(authorize_response.status, enums::AttemptStatus::Pending); if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) { let sync_response = Payu {} @@ -86,7 +105,7 @@ async fn should_authorize_gpay_payment() { encoded_data: None, capture_method: None, }), - None, + get_default_payment_info(), ) .await .unwrap(); @@ -95,6 +114,7 @@ async fn should_authorize_gpay_payment() { } #[actix_web::test] +#[ignore] async fn should_capture_already_authorized_payment() { let connector = Payu {}; let authorize_response = connector @@ -103,15 +123,15 @@ async fn should_capture_already_authorized_payment() { currency: enums::Currency::PLN, ..PaymentAuthorizeType::default().0 }), - None, + get_default_payment_info(), ) .await .unwrap(); assert_eq!(authorize_response.status, enums::AttemptStatus::Pending); - if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) { let sync_response = connector - .sync_payment( + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, Some(types::PaymentsSyncData { connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( transaction_id.clone(), @@ -119,18 +139,19 @@ async fn should_capture_already_authorized_payment() { encoded_data: None, capture_method: None, }), - None, + get_default_payment_info(), ) .await .unwrap(); assert_eq!(sync_response.status, enums::AttemptStatus::Authorized); let capture_response = connector - .capture_payment(transaction_id.clone(), None, None) + .capture_payment(transaction_id.clone(), None, get_default_payment_info()) .await .unwrap(); assert_eq!(capture_response.status, enums::AttemptStatus::Pending); let response = connector - .sync_payment( + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, Some(types::PaymentsSyncData { connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( transaction_id, @@ -138,7 +159,7 @@ async fn should_capture_already_authorized_payment() { encoded_data: None, capture_method: None, }), - None, + get_default_payment_info(), ) .await .unwrap(); @@ -147,6 +168,7 @@ async fn should_capture_already_authorized_payment() { } #[actix_web::test] +#[ignore] async fn should_sync_payment() { let connector = Payu {}; // Authorize the payment for manual capture @@ -156,7 +178,7 @@ async fn should_sync_payment() { currency: enums::Currency::PLN, ..PaymentAuthorizeType::default().0 }), - None, + get_default_payment_info(), ) .await .unwrap(); @@ -165,7 +187,8 @@ async fn should_sync_payment() { if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) { // Sync the Payment Data let response = connector - .sync_payment( + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, Some(types::PaymentsSyncData { connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( transaction_id, @@ -173,7 +196,7 @@ async fn should_sync_payment() { encoded_data: None, capture_method: None, }), - None, + get_default_payment_info(), ) .await .unwrap(); @@ -183,6 +206,7 @@ async fn should_sync_payment() { } #[actix_web::test] +#[ignore] async fn should_void_already_authorized_payment() { let connector = Payu {}; //make a successful payment @@ -192,7 +216,7 @@ async fn should_void_already_authorized_payment() { currency: enums::Currency::PLN, ..PaymentAuthorizeType::default().0 }), - None, + get_default_payment_info(), ) .await .unwrap(); @@ -201,13 +225,14 @@ async fn should_void_already_authorized_payment() { //try CANCEL for previous payment if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) { let void_response = connector - .void_payment(transaction_id.clone(), None, None) + .void_payment(transaction_id.clone(), None, get_default_payment_info()) .await .unwrap(); assert_eq!(void_response.status, enums::AttemptStatus::Pending); let sync_response = connector - .sync_payment( + .psync_retry_till_status_matches( + enums::AttemptStatus::Voided, Some(types::PaymentsSyncData { connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( transaction_id, @@ -215,7 +240,7 @@ async fn should_void_already_authorized_payment() { encoded_data: None, capture_method: None, }), - None, + get_default_payment_info(), ) .await .unwrap(); @@ -224,31 +249,25 @@ async fn should_void_already_authorized_payment() { } #[actix_web::test] +#[ignore] async fn should_refund_succeeded_payment() { let connector = Payu {}; - //make a successful payment let authorize_response = connector - .make_payment( + .authorize_payment( Some(types::PaymentsAuthorizeData { currency: enums::Currency::PLN, ..PaymentAuthorizeType::default().0 }), - None, + get_default_payment_info(), ) .await .unwrap(); assert_eq!(authorize_response.status, enums::AttemptStatus::Pending); if let Some(transaction_id) = utils::get_connector_transaction_id(authorize_response) { - //Capture the payment in case of Manual Capture - let capture_response = connector - .capture_payment(transaction_id.clone(), None, None) - .await - .unwrap(); - assert_eq!(capture_response.status, enums::AttemptStatus::Pending); - let sync_response = connector - .sync_payment( + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, Some(types::PaymentsSyncData { connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( transaction_id.clone(), @@ -256,15 +275,36 @@ async fn should_refund_succeeded_payment() { encoded_data: None, capture_method: None, }), - None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(sync_response.status, enums::AttemptStatus::Authorized); + //Capture the payment in case of Manual Capture + let capture_response = connector + .capture_payment(transaction_id.clone(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!(capture_response.status, enums::AttemptStatus::Pending); + + let sync_response = connector + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + transaction_id.clone(), + ), + encoded_data: None, + capture_method: None, + }), + get_default_payment_info(), ) .await .unwrap(); assert_eq!(sync_response.status, enums::AttemptStatus::Charged); - //Refund the payment let refund_response = connector - .refund_payment(transaction_id.clone(), None, None) + .refund_payment(transaction_id.clone(), None, get_default_payment_info()) .await .unwrap(); assert_eq!( @@ -280,7 +320,11 @@ async fn should_sync_succeeded_refund_payment() { //Currently hardcoding the order_id because RSync is not instant, change it accordingly let sync_refund_response = connector - .sync_refund("6DHQQN3T57230110GUEST000P01".to_string(), None, None) + .sync_refund( + "6DHQQN3T57230110GUEST000P01".to_string(), + None, + get_default_payment_info(), + ) .await .unwrap(); assert_eq!( @@ -294,7 +338,11 @@ async fn should_fail_already_refunded_payment() { let connector = Payu {}; //Currently hardcoding the order_id, change it accordingly let response = connector - .refund_payment("5H1SVX6P7W230112GUEST000P01".to_string(), None, None) + .refund_payment( + "5H1SVX6P7W230112GUEST000P01".to_string(), + None, + get_default_payment_info(), + ) .await .unwrap(); let x = response.response.unwrap_err(); diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 0fd79e47c2..9b442f770c 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -1,13 +1,13 @@ -use std::{fmt::Debug, marker::PhantomData}; +use std::{fmt::Debug, marker::PhantomData, thread::sleep, time::Duration}; use async_trait::async_trait; use error_stack::Report; use masking::Secret; use router::{ - core::{errors::ConnectorError, payments}, + core::{errors, errors::ConnectorError, payments}, db::StorageImpl, routes, services, - types::{self, api, storage::enums, PaymentAddress}, + types::{self, api, storage::enums, AccessToken, PaymentAddress}, }; use wiremock::{Mock, MockServer}; @@ -24,6 +24,7 @@ pub trait Connector { pub struct PaymentInfo { pub address: Option, pub auth_type: Option, + pub access_token: Option, } #[async_trait] @@ -69,6 +70,29 @@ pub trait ConnectorActions: Connector { call_connector(request, integration).await } + /// will retry the psync till the given status matches or retry max 3 times in a 10secs interval + async fn psync_retry_till_status_matches( + &self, + status: enums::AttemptStatus, + payment_data: Option, + payment_info: Option, + ) -> Result> { + let max_try = 3; + let mut curr_try = 1; + while curr_try <= max_try { + let sync_res = self + .sync_payment(payment_data.clone(), payment_info.clone()) + .await + .unwrap(); + if (sync_res.status == status) || (curr_try == max_try) { + return Ok(sync_res); + } + sleep(Duration::from_secs(10)); + curr_try += 1; + } + Err(errors::ConnectorError::ProcessingStepFailed(None).into()) + } + async fn capture_payment( &self, transaction_id: String, @@ -120,7 +144,7 @@ pub trait ConnectorActions: Connector { connector_transaction_id: transaction_id, refund_amount: 100, connector_metadata: None, - reason: None, + reason: Some("Customer returned product".to_string()), }), payment_info, ); @@ -175,10 +199,14 @@ pub trait ConnectorActions: Connector { request: req, response: Err(types::ErrorResponse::default()), payment_method_id: None, - address: info.map_or(PaymentAddress::default(), |a| a.address.unwrap()), + address: info + .clone() + .and_then(|a| a.address) + .or_else(|| Some(PaymentAddress::default())) + .unwrap(), connector_meta_data: self.get_connector_meta(), amount_captured: None, - access_token: None, + access_token: info.and_then(|a| a.access_token), } } } diff --git a/crates/router/tests/connectors/worldline.rs b/crates/router/tests/connectors/worldline.rs index 7aad7366a5..1509201881 100644 --- a/crates/router/tests/connectors/worldline.rs +++ b/crates/router/tests/connectors/worldline.rs @@ -50,6 +50,7 @@ impl WorldlineTest { ..Default::default() }), auth_type: None, + access_token: None, }) }