From b9685521735956659c50bc2e1c15b08cb9952aee Mon Sep 17 00:00:00 2001 From: Chethan Rao <70657455+Chethan-rao@users.noreply.github.com> Date: Wed, 11 Oct 2023 11:45:58 +0530 Subject: [PATCH 1/8] refactor: delete requires cvv config when merchant account is deleted (#2525) --- crates/router/src/core/admin.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index ec229bd8a564..226d77d598e6 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -491,6 +491,25 @@ pub async fn merchant_account_delete( .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; is_deleted = is_merchant_account_deleted && is_merchant_key_store_deleted; } + + match db + .delete_config_by_key(format!("{}_requires_cvv", merchant_id).as_str()) + .await + { + Ok(_) => Ok::<_, errors::ApiErrorResponse>(()), + Err(err) => { + if err.current_context().is_db_not_found() { + crate::logger::error!("requires_cvv config not found in db: {err:?}"); + Ok(()) + } else { + Err(err + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while deleting requires_cvv config"))? + } + } + } + .ok(); + let response = api::MerchantAccountDeleteResponse { merchant_id, deleted: is_deleted, From ee321bb82686559643d8c2725b0491997af717b2 Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:27:28 +0530 Subject: [PATCH 2/8] feat(connector): [Volt] Template generation (#2480) --- config/config.example.toml | 7 +- config/development.toml | 2 + config/docker_compose.toml | 8 +- crates/api_models/src/enums.rs | 2 + crates/router/src/configs/settings.rs | 1 + crates/router/src/connector.rs | 3 +- crates/router/src/connector/volt.rs | 533 ++++++++++++++++++ .../router/src/connector/volt/transformers.rs | 229 ++++++++ crates/router/src/core/payments/flows.rs | 21 +- crates/router/src/types/api.rs | 1 + crates/router/tests/connectors/main.rs | 1 + .../router/tests/connectors/sample_auth.toml | 4 + crates/router/tests/connectors/volt.rs | 419 ++++++++++++++ crates/test_utils/src/connector_auth.rs | 1 + loadtest/config/development.toml | 8 +- scripts/add_connector.sh | 2 +- 16 files changed, 1230 insertions(+), 12 deletions(-) create mode 100644 crates/router/src/connector/volt.rs create mode 100644 crates/router/src/connector/volt/transformers.rs create mode 100644 crates/router/tests/connectors/volt.rs diff --git a/config/config.example.toml b/config/config.example.toml index 56297fc20a44..bee61f2f6156 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -206,12 +206,13 @@ square.secondary_base_url = "https://pci-connect.squareupsandbox.com/" stax.base_url = "https://apiprod.fattlabs.com/" stripe.base_url = "https://api.stripe.com/" stripe.base_url_file_upload = "https://files.stripe.com/" +trustpay.base_url = "https://test-tpgw.trustpay.eu/" +trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" +tsys.base_url = "https://stagegw.transnox.com/" +volt.base_url = "https://api.sandbox.volt.io/" wise.base_url = "https://api.sandbox.transferwise.tech/" worldline.base_url = "https://eu.sandbox.api-ingenico.com/" worldpay.base_url = "https://try.access.worldpay.com/" -trustpay.base_url = "https://test-tpgw.trustpay.eu/" -tsys.base_url = "https://stagegw.transnox.com/" -trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" zen.base_url = "https://api.zen-test.com/" zen.secondary_base_url = "https://secure.zen-test.com/" diff --git a/config/development.toml b/config/development.toml index 6d5d02c3bb01..64bdd45f1c31 100644 --- a/config/development.toml +++ b/config/development.toml @@ -107,6 +107,7 @@ cards = [ "stripe", "trustpay", "tsys", + "volt", "wise", "worldline", "worldpay", @@ -182,6 +183,7 @@ worldline.base_url = "https://eu.sandbox.api-ingenico.com/" worldpay.base_url = "https://try.access.worldpay.com/" trustpay.base_url = "https://test-tpgw.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" +volt.base_url = "https://api.sandbox.volt.io/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" zen.base_url = "https://api.zen-test.com/" zen.secondary_base_url = "https://secure.zen-test.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 983abd7cc3c8..8504ec130e62 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -121,12 +121,13 @@ square.secondary_base_url = "https://pci-connect.squareupsandbox.com/" stax.base_url = "https://apiprod.fattlabs.com/" stripe.base_url = "https://api.stripe.com/" stripe.base_url_file_upload = "https://files.stripe.com/" +trustpay.base_url = "https://test-tpgw.trustpay.eu/" +trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" +tsys.base_url = "https://stagegw.transnox.com/" +volt.base_url = "https://api.sandbox.volt.io/" wise.base_url = "https://api.sandbox.transferwise.tech/" worldline.base_url = "https://eu.sandbox.api-ingenico.com/" worldpay.base_url = "https://try.access.worldpay.com/" -trustpay.base_url = "https://test-tpgw.trustpay.eu/" -tsys.base_url = "https://stagegw.transnox.com/" -trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" zen.base_url = "https://api.zen-test.com/" zen.secondary_base_url = "https://secure.zen-test.com/" @@ -179,6 +180,7 @@ cards = [ "stripe", "trustpay", "tsys", + "volt", "wise", "worldline", "worldpay", diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 33cde866eed1..fd6b92a91ceb 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -115,6 +115,7 @@ pub enum Connector { Trustpay, // Tsys, Tsys, + //Volt, added as template code for future usage, Wise, Worldline, Worldpay, @@ -232,6 +233,7 @@ pub enum RoutableConnectors { Trustpay, // Tsys, Tsys, + // Volt, added as template code for future usage Wise, Worldline, Worldpay, diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index a2feb55e86a2..9ee84e5baea7 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -558,6 +558,7 @@ pub struct Connectors { pub stripe: ConnectorParamsWithFileUploadUrl, pub trustpay: ConnectorParamsWithMoreUrls, pub tsys: ConnectorParams, + pub volt: ConnectorParams, pub wise: ConnectorParams, pub worldline: ConnectorParams, pub worldpay: ConnectorParams, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index 8be4a675ccce..689adb2490eb 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -44,6 +44,7 @@ pub mod stripe; pub mod trustpay; pub mod tsys; pub mod utils; +pub mod volt; pub mod wise; pub mod worldline; pub mod worldpay; @@ -60,6 +61,6 @@ pub use self::{ mollie::Mollie, multisafepay::Multisafepay, nexinets::Nexinets, nmi::Nmi, noon::Noon, nuvei::Nuvei, opayo::Opayo, opennode::Opennode, payeezy::Payeezy, payme::Payme, paypal::Paypal, payu::Payu, powertranz::Powertranz, rapyd::Rapyd, shift4::Shift4, square::Square, stax::Stax, - stripe::Stripe, trustpay::Trustpay, tsys::Tsys, wise::Wise, worldline::Worldline, + stripe::Stripe, trustpay::Trustpay, tsys::Tsys, volt::Volt, wise::Wise, worldline::Worldline, worldpay::Worldpay, zen::Zen, }; diff --git a/crates/router/src/connector/volt.rs b/crates/router/src/connector/volt.rs new file mode 100644 index 000000000000..92d2955a0e0d --- /dev/null +++ b/crates/router/src/connector/volt.rs @@ -0,0 +1,533 @@ +pub mod transformers; + +use std::fmt::Debug; + +use error_stack::{IntoReport, ResultExt}; +use masking::ExposeInterface; +use transformers as volt; + +use crate::{ + configs::settings, + core::errors::{self, CustomResult}, + headers, + services::{ + self, + request::{self, Mask}, + ConnectorIntegration, ConnectorValidation, + }, + types::{ + self, + api::{self, ConnectorCommon, ConnectorCommonExt}, + ErrorResponse, Response, + }, + utils::{self, BytesExt}, +}; + +#[derive(Debug, Clone)] +pub struct Volt; + +impl api::Payment for Volt {} +impl api::PaymentSession for Volt {} +impl api::ConnectorAccessToken for Volt {} +impl api::MandateSetup for Volt {} +impl api::PaymentAuthorize for Volt {} +impl api::PaymentSync for Volt {} +impl api::PaymentCapture for Volt {} +impl api::PaymentVoid for Volt {} +impl api::Refund for Volt {} +impl api::RefundExecute for Volt {} +impl api::RefundSync for Volt {} +impl api::PaymentToken for Volt {} + +impl + ConnectorIntegration< + api::PaymentMethodToken, + types::PaymentMethodTokenizationData, + types::PaymentsResponseData, + > for Volt +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Volt +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &types::RouterData, + _connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + types::PaymentsAuthorizeType::get_content_type(self) + .to_string() + .into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Volt { + fn id(&self) -> &'static str { + "volt" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + connectors.volt.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &types::ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = volt::VoltAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + ) -> CustomResult { + let response: volt::VoltErrorResponse = res + .response + .parse_struct("VoltErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + }) + } +} + +impl ConnectorValidation for Volt { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration + for Volt +{ + //TODO: implement sessions flow +} + +impl ConnectorIntegration + for Volt +{ +} + +impl + ConnectorIntegration< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + > for Volt +{ +} + +impl ConnectorIntegration + for Volt +{ + fn get_headers( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, 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::PaymentsAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &types::PaymentsAuthorizeRouterData, + ) -> CustomResult, errors::ConnectorError> { + let connector_router_data = volt::VoltRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let req_obj = volt::VoltPaymentsRequest::try_from(&connector_router_data)?; + let volt_req = types::RequestBody::log_and_get_request_body( + &req_obj, + utils::Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(volt_req)) + } + + fn build_request( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .body(types::PaymentsAuthorizeType::get_request_body(self, req)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsAuthorizeRouterData, + res: Response, + ) -> CustomResult { + let response: volt::VoltPaymentsResponse = res + .response + .parse_struct("Volt PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +impl ConnectorIntegration + for Volt +{ + fn get_headers( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, 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::PaymentsSyncRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsSyncRouterData, + res: Response, + ) -> CustomResult { + let response: volt::VoltPaymentsResponse = res + .response + .parse_struct("volt PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +impl ConnectorIntegration + for Volt +{ + fn get_headers( + &self, + req: &types::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, 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::PaymentsCaptureRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &types::PaymentsCaptureRouterData, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .body(types::PaymentsCaptureType::get_request_body(self, req)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsCaptureRouterData, + res: Response, + ) -> CustomResult { + let response: volt::VoltPaymentsResponse = res + .response + .parse_struct("Volt PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +impl ConnectorIntegration + for Volt +{ +} + +impl ConnectorIntegration for Volt { + fn get_headers( + &self, + req: &types::RefundsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, 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::RefundsRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &types::RefundsRouterData, + ) -> CustomResult, errors::ConnectorError> { + let connector_router_data = volt::VoltRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.refund_amount, + req, + ))?; + let req_obj = volt::VoltRefundRequest::try_from(&connector_router_data)?; + let volt_req = types::RequestBody::log_and_get_request_body( + &req_obj, + utils::Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(volt_req)) + } + + fn build_request( + &self, + req: &types::RefundsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .body(types::RefundExecuteType::get_request_body(self, req)?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &types::RefundsRouterData, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: volt::RefundResponse = res + .response + .parse_struct("volt RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +impl ConnectorIntegration for Volt { + fn get_headers( + &self, + req: &types::RefundSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, 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::RefundSyncRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::RefundSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .body(types::RefundSyncType::get_request_body(self, req)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::RefundSyncRouterData, + res: Response, + ) -> CustomResult { + let response: volt::RefundResponse = + res.response + .parse_struct("volt RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + +#[async_trait::async_trait] +impl api::IncomingWebhook for Volt { + fn get_webhook_object_reference_id( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_event_type( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_resource_object( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } +} diff --git a/crates/router/src/connector/volt/transformers.rs b/crates/router/src/connector/volt/transformers.rs new file mode 100644 index 000000000000..1bbbe5ff1eb4 --- /dev/null +++ b/crates/router/src/connector/volt/transformers.rs @@ -0,0 +1,229 @@ +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + connector::utils::PaymentsAuthorizeRequestData, + core::errors, + types::{self, api, storage::enums}, +}; + +pub struct VoltRouterData { + pub amount: i64, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for VoltRouterData +{ + type Error = error_stack::Report; + fn try_from( + (_currency_unit, _currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Ok(Self { + amount, + router_data: item, + }) + } +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct VoltPaymentsRequest { + amount: i64, + card: VoltCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct VoltCard { + name: Secret, + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&VoltRouterData<&types::PaymentsAuthorizeRouterData>> for VoltPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &VoltRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + api::PaymentMethodData::Card(req_card) => { + let card = VoltCard { + name: req_card.card_holder_name, + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.to_owned(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + } + } +} + +pub struct VoltAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&types::ConnectorAuthType> for VoltAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + match auth_type { + types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum VoltPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::AttemptStatus { + fn from(item: VoltPaymentStatus) -> Self { + match item { + VoltPaymentStatus::Succeeded => Self::Charged, + VoltPaymentStatus::Failed => Self::Failure, + VoltPaymentStatus::Processing => Self::Authorizing, + } + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct VoltPaymentsResponse { + status: VoltPaymentStatus, + id: String, +} + +impl + TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData, + ) -> Result { + Ok(Self { + status: enums::AttemptStatus::from(item.response.status), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + }), + ..item.data + }) + } +} + +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct VoltRefundRequest { + pub amount: i64, +} + +impl TryFrom<&VoltRouterData<&types::RefundsRouterData>> for VoltRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &VoltRouterData<&types::RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct VoltErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index b8919dab30a1..b54f1c6cd527 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -173,6 +173,7 @@ default_imp_for_complete_authorize!( connector::Stripe, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Wise, connector::Worldline, connector::Worldpay, @@ -247,6 +248,7 @@ default_imp_for_webhook_source_verification!( connector::Stripe, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Wise, connector::Worldline, connector::Worldpay, @@ -321,6 +323,7 @@ default_imp_for_create_customer!( connector::Square, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Wise, connector::Worldline, connector::Worldpay, @@ -386,6 +389,7 @@ default_imp_for_connector_redirect_response!( connector::Square, connector::Stax, connector::Tsys, + connector::Volt, connector::Wise, connector::Worldline, connector::Worldpay @@ -445,6 +449,7 @@ default_imp_for_connector_request_id!( connector::Stripe, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Wise, connector::Worldline, connector::Worldpay, @@ -511,6 +516,7 @@ default_imp_for_accept_dispute!( connector::Noon, connector::Nuvei, connector::Opayo, + connector::Opennode, connector::Payeezy, connector::Paypal, connector::Payme, @@ -523,7 +529,7 @@ default_imp_for_accept_dispute!( connector::Stripe, connector::Trustpay, connector::Tsys, - connector::Opennode, + connector::Volt, connector::Wise, connector::Worldline, connector::Worldpay, @@ -620,6 +626,7 @@ default_imp_for_file_upload!( connector::Stax, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Opennode, connector::Wise, connector::Worldline, @@ -695,6 +702,7 @@ default_imp_for_submit_evidence!( connector::Stax, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Opennode, connector::Wise, connector::Worldline, @@ -771,6 +779,7 @@ default_imp_for_defend_dispute!( connector::Stripe, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Opennode, connector::Wise, connector::Worldline, @@ -845,6 +854,7 @@ default_imp_for_pre_processing_steps!( connector::Square, connector::Stax, connector::Tsys, + connector::Volt, connector::Wise, connector::Worldline, connector::Worldpay, @@ -905,6 +915,7 @@ default_imp_for_payouts!( connector::Shift4, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Worldline, connector::Worldpay, connector::Zen @@ -981,6 +992,7 @@ default_imp_for_payouts_create!( connector::Shift4, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Worldline, connector::Worldpay, connector::Zen @@ -1060,6 +1072,7 @@ default_imp_for_payouts_eligibility!( connector::Shift4, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Worldline, connector::Worldpay, connector::Zen @@ -1136,6 +1149,7 @@ default_imp_for_payouts_fulfill!( connector::Shift4, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Worldline, connector::Worldpay, connector::Zen @@ -1212,6 +1226,7 @@ default_imp_for_payouts_cancel!( connector::Shift4, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Worldline, connector::Worldpay, connector::Zen @@ -1289,6 +1304,7 @@ default_imp_for_payouts_quote!( connector::Shift4, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Worldline, connector::Worldpay, connector::Zen @@ -1366,6 +1382,7 @@ default_imp_for_payouts_recipient!( connector::Shift4, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Worldline, connector::Worldpay, connector::Zen @@ -1442,6 +1459,7 @@ default_imp_for_approve!( connector::Shift4, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Wise, connector::Worldline, connector::Worldpay, @@ -1519,6 +1537,7 @@ default_imp_for_reject!( connector::Shift4, connector::Trustpay, connector::Tsys, + connector::Volt, connector::Wise, connector::Worldline, connector::Worldpay, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 27d86db831fe..6c1790d84841 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -355,6 +355,7 @@ impl ConnectorData { enums::Connector::Paypal => Ok(Box::new(&connector::Paypal)), enums::Connector::Trustpay => Ok(Box::new(&connector::Trustpay)), enums::Connector::Tsys => Ok(Box::new(&connector::Tsys)), + // enums::Connector::Volt => Ok(Box::new(&connector::Volt)), it is added as template code for future usage enums::Connector::Zen => Ok(Box::new(&connector::Zen)), enums::Connector::Signifyd | enums::Connector::Plaid => { Err(report!(errors::ConnectorError::InvalidConnectorName) diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index 11f23c82d895..9e117649e9a2 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -50,6 +50,7 @@ mod stripe; mod trustpay; mod tsys; mod utils; +mod volt; mod wise; mod worldline; mod worldpay; diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index 461ea099910c..b55aae498c70 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -179,3 +179,7 @@ api_key="API Key" [gocardless] api_key="API Key" + + +[volt] +api_key="API Key" diff --git a/crates/router/tests/connectors/volt.rs b/crates/router/tests/connectors/volt.rs new file mode 100644 index 000000000000..8435e39c6569 --- /dev/null +++ b/crates/router/tests/connectors/volt.rs @@ -0,0 +1,419 @@ +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct VoltTest; +impl ConnectorActions for VoltTest {} +impl utils::Connector for VoltTest { + fn get_data(&self) -> types::api::ConnectorData { + use router::connector::Volt; + types::api::ConnectorData { + connector: Box::new(&Volt), + connector_name: types::Connector::DummyConnector1, + get_token: types::api::GetToken::Connector, + } + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .volt + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "volt".to_string() + } +} + +static CONNECTOR: VoltTest = VoltTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenerios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index fced1fc55bc1..8edec7ee39c8 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -57,6 +57,7 @@ pub struct ConnectorAuthentication { pub stripe_uk: Option, pub trustpay: Option, pub tsys: Option, + pub volt: Option, pub wise: Option, pub worldpay: Option, pub worldline: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 380373049689..bee79de458d6 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -107,12 +107,13 @@ square.secondary_base_url = "https://pci-connect.squareupsandbox.com/" stax.base_url = "https://apiprod.fattlabs.com/" stripe.base_url = "https://api.stripe.com/" stripe.base_url_file_upload = "https://files.stripe.com/" -worldline.base_url = "https://eu.sandbox.api-ingenico.com/" -worldpay.base_url = "https://try.access.worldpay.com/" trustpay.base_url = "https://test-tpgw.trustpay.eu/" +trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" +volt.base_url = "https://api.sandbox.volt.io/" +worldline.base_url = "https://eu.sandbox.api-ingenico.com/" +worldpay.base_url = "https://try.access.worldpay.com/" wise.base_url = "https://api.sandbox.transferwise.tech/" -trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" zen.base_url = "https://api.zen-test.com/" zen.secondary_base_url = "https://secure.zen-test.com/" @@ -164,6 +165,7 @@ cards = [ "stripe", "trustpay", "tsys", + "volt", "wise", "worldline", "worldpay", diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 756e8213d97b..4327619dbb97 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen airwallex applepay authorizedotnet bambora bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource dlocal dummyconnector fiserv forte globalpay globepay gocardless helcim iatapay klarna mollie multisafepay nexinets noon nuvei opayo opennode payeezy payme paypal payu powertranz rapyd shift4 square stax stripe trustpay tsys wise worldline worldpay "$1") + connectors=(aci adyen airwallex applepay authorizedotnet bambora bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource dlocal dummyconnector fiserv forte globalpay globepay gocardless helcim iatapay klarna mollie multisafepay nexinets noon nuvei opayo opennode payeezy payme paypal payu powertranz rapyd shift4 square stax stripe trustpay tsys volt wise worldline worldpay "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res=`echo ${sorted[@]}` sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp From 2f6c00a1fd853876333608a7d1fa6b488c3001d3 Mon Sep 17 00:00:00 2001 From: Rutam Prita Mishra Date: Wed, 11 Oct 2023 14:38:04 +0530 Subject: [PATCH 3/8] feat(connector): [NexiNets] Update connector_response_reference_id as reference to merchant (#2537) --- crates/router/src/connector/nexinets/transformers.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/router/src/connector/nexinets/transformers.rs b/crates/router/src/connector/nexinets/transformers.rs index 5949e48ae18f..7b2a41cfb0be 100644 --- a/crates/router/src/connector/nexinets/transformers.rs +++ b/crates/router/src/connector/nexinets/transformers.rs @@ -364,7 +364,7 @@ impl mandate_reference, connector_metadata: Some(connector_metadata), network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.order_id), }), ..item.data }) @@ -425,7 +425,7 @@ impl let transaction_id = Some(item.response.transaction_id.clone()); let connector_metadata = serde_json::to_value(NexinetsPaymentsMetadata { transaction_id, - order_id: Some(item.response.order.order_id), + order_id: Some(item.response.order.order_id.clone()), psync_flow: item.response.transaction_type.clone(), }) .into_report() @@ -447,7 +447,7 @@ impl mandate_reference: None, connector_metadata: Some(connector_metadata), network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.order.order_id), }), ..item.data }) From c6384b4da493d7a4846f8349e773a98a58e8c95f Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:00:33 +0530 Subject: [PATCH 4/8] ci(hotfix-branch-creation): rename `hotfix-branch-creation` workflow to `create-hotfix-branch` (#2540) --- .../{hotfix-branch-creation.yml => create-hotfix-branch.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{hotfix-branch-creation.yml => create-hotfix-branch.yml} (100%) diff --git a/.github/workflows/hotfix-branch-creation.yml b/.github/workflows/create-hotfix-branch.yml similarity index 100% rename from .github/workflows/hotfix-branch-creation.yml rename to .github/workflows/create-hotfix-branch.yml From 2697765582154e3b5f706b2bf430212077adc64f Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:02:33 +0530 Subject: [PATCH 5/8] ci(postman): Update API creds before making payment (#2531) --- .../stripe/QuickStart/.meta.json | 2 + .../event.prerequest.js | 0 .../Payment Connector - Create/request.json | 28 +- .../.event.meta.json | 6 + .../event.prerequest.js | 0 .../Payment Connector - Update/event.test.js | 39 ++ .../Payment Connector - Update/request.json | 333 ++++++++++++++++++ .../Payment Connector - Update/response.json | 1 + .../Payments - Create-copy/.event.meta.json | 6 + .../event.prerequest.js | 0 .../Payments - Create-copy/event.test.js | 80 +++++ .../Payments - Create-copy/request.json | 103 ++++++ .../Payments - Create-copy/response.json | 1 + .../Payments - Create/event.test.js | 29 ++ 14 files changed, 622 insertions(+), 6 deletions(-) create mode 100644 postman/collection-dir/stripe/QuickStart/Payment Connector - Create/event.prerequest.js create mode 100644 postman/collection-dir/stripe/QuickStart/Payment Connector - Update/.event.meta.json create mode 100644 postman/collection-dir/stripe/QuickStart/Payment Connector - Update/event.prerequest.js create mode 100644 postman/collection-dir/stripe/QuickStart/Payment Connector - Update/event.test.js create mode 100644 postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json create mode 100644 postman/collection-dir/stripe/QuickStart/Payment Connector - Update/response.json create mode 100644 postman/collection-dir/stripe/QuickStart/Payments - Create-copy/.event.meta.json create mode 100644 postman/collection-dir/stripe/QuickStart/Payments - Create-copy/event.prerequest.js create mode 100644 postman/collection-dir/stripe/QuickStart/Payments - Create-copy/event.test.js create mode 100644 postman/collection-dir/stripe/QuickStart/Payments - Create-copy/request.json create mode 100644 postman/collection-dir/stripe/QuickStart/Payments - Create-copy/response.json diff --git a/postman/collection-dir/stripe/QuickStart/.meta.json b/postman/collection-dir/stripe/QuickStart/.meta.json index c4939d7ab913..afeb7c57049b 100644 --- a/postman/collection-dir/stripe/QuickStart/.meta.json +++ b/postman/collection-dir/stripe/QuickStart/.meta.json @@ -4,6 +4,8 @@ "API Key - Create", "Payment Connector - Create", "Payments - Create", + "Payment Connector - Update", + "Payments - Create-copy", "Payments - Retrieve", "Refunds - Create", "Refunds - Retrieve" diff --git a/postman/collection-dir/stripe/QuickStart/Payment Connector - Create/event.prerequest.js b/postman/collection-dir/stripe/QuickStart/Payment Connector - Create/event.prerequest.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/postman/collection-dir/stripe/QuickStart/Payment Connector - Create/request.json b/postman/collection-dir/stripe/QuickStart/Payment Connector - Create/request.json index ff1f8084a8d8..9d742acdf3c6 100644 --- a/postman/collection-dir/stripe/QuickStart/Payment Connector - Create/request.json +++ b/postman/collection-dir/stripe/QuickStart/Payment Connector - Create/request.json @@ -44,7 +44,7 @@ "business_label": "default", "connector_account_details": { "auth_type": "HeaderKey", - "api_key": "{{connector_api_key}}" + "api_key": "{{connector_api_key}}_invalid_values" }, "test_mode": false, "disabled": false, @@ -259,7 +259,10 @@ { "type": "CARD", "parameters": { - "allowed_auth_methods": ["PAN_ONLY", "CRYPTOGRAM_3DS"], + "allowed_auth_methods": [ + "PAN_ONLY", + "CRYPTOGRAM_3DS" + ], "allowed_card_networks": [ "AMEX", "DISCOVER", @@ -293,8 +296,15 @@ }, "payment_request_data": { "label": "applepay pvt.ltd", - "supported_networks": ["visa", "masterCard", "amex", "discover"], - "merchant_capabilities": ["supports3DS"] + "supported_networks": [ + "visa", + "masterCard", + "amex", + "discover" + ], + "merchant_capabilities": [ + "supports3DS" + ] } } } @@ -302,8 +312,14 @@ }, "url": { "raw": "{{baseUrl}}/account/:account_id/connectors", - "host": ["{{baseUrl}}"], - "path": ["account", ":account_id", "connectors"], + "host": [ + "{{baseUrl}}" + ], + "path": [ + "account", + ":account_id", + "connectors" + ], "variable": [ { "key": "account_id", diff --git a/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/.event.meta.json b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/.event.meta.json new file mode 100644 index 000000000000..4ac527d834af --- /dev/null +++ b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/event.prerequest.js b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/event.prerequest.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/event.test.js b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/event.test.js new file mode 100644 index 000000000000..d7259b6a840b --- /dev/null +++ b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/event.test.js @@ -0,0 +1,39 @@ +// Validate status 2xx +pm.test( + "[POST]::/account/:account_id/connectors/:connector_id - Status code is 2xx", + function () { + pm.response.to.be.success; + }, +); + +// Validate if response header has matching content-type +pm.test( + "[POST]::/account/:account_id/connectors/:connector_id - Content-Type is application/json", + function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); + }, +); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set merchant_connector_id as variable for jsonData.merchant_connector_id +if (jsonData?.merchant_connector_id) { + pm.collectionVariables.set( + "merchant_connector_id", + jsonData.merchant_connector_id, + ); + console.log( + "- use {{merchant_connector_id}} as collection variable for value", + jsonData.merchant_connector_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{merchant_connector_id}}, as jsonData.merchant_connector_id is undefined.", + ); +} diff --git a/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json new file mode 100644 index 000000000000..ef812ef5d172 --- /dev/null +++ b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/request.json @@ -0,0 +1,333 @@ +{ + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{admin_api_key}}", + "type": "string" + }, + { + "key": "key", + "value": "api-key", + "type": "string" + }, + { + "key": "in", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "connector_type": "fiz_operations", + "connector_account_details": { + "auth_type": "HeaderKey", + "api_key": "{{connector_api_key}}" + }, + "test_mode": false, + "disabled": false, + "payment_methods_enabled": [ + { + "payment_method": "pay_later", + "payment_method_types": [ + { + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true, + "payment_experience": "redirect_to_url", + "payment_method_type": "affirm" + } + ] + }, + { + "payment_method": "pay_later", + "payment_method_types": [ + { + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true, + "payment_experience": "redirect_to_url", + "payment_method_type": "afterpay_clearpay" + } + ] + }, + { + "payment_method": "pay_later", + "payment_method_types": [ + { + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true, + "payment_experience": "redirect_to_url", + "payment_method_type": "klarna" + } + ] + }, + { + "payment_method": "pay_later", + "payment_method_types": [ + { + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true, + "payment_experience": "invoke_sdk_client", + "payment_method_type": "klarna" + } + ] + }, + { + "payment_method": "bank_redirect", + "payment_method_types": [ + { + "payment_method_type": "ideal", + "payment_experience": null, + "card_networks": null, + "accepted_currencies": null, + "accepted_countries": null, + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "giropay", + "payment_experience": null, + "card_networks": null, + "accepted_currencies": null, + "accepted_countries": null, + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "sofort", + "payment_experience": null, + "card_networks": null, + "accepted_currencies": null, + "accepted_countries": null, + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "eps", + "payment_experience": null, + "card_networks": null, + "accepted_currencies": null, + "accepted_countries": null, + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + }, + { + "payment_method": "bank_debit", + "payment_method_types": [ + { + "payment_method_type": "ach", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "becs", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "sepa", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + }, + { + "payment_method": "bank_transfer", + "payment_method_types": [ + { + "payment_method_type": "ach", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "bacs", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + }, + { + "payment_method_type": "sepa", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + }, + { + "payment_method": "card", + "payment_method_types": [ + { + "payment_method_type": "credit", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + }, + { + "payment_method": "card", + "payment_method_types": [ + { + "payment_method_type": "debit", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + }, + { + "payment_method": "wallet", + "payment_method_types": [ + { + "payment_method_type": "apple_pay", + "payment_experience": "invoke_sdk_client", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + }, + { + "payment_method": "wallet", + "payment_method_types": [ + { + "payment_method_type": "google_pay", + "payment_experience": "invoke_sdk_client", + "minimum_amount": 1, + "maximum_amount": 68607706, + "recurring_enabled": true, + "installment_payment_enabled": true + } + ] + } + ], + "metadata": { + "google_pay": { + "allowed_payment_methods": [ + { + "type": "CARD", + "parameters": { + "allowed_auth_methods": [ + "PAN_ONLY", + "CRYPTOGRAM_3DS" + ], + "allowed_card_networks": [ + "AMEX", + "DISCOVER", + "INTERAC", + "JCB", + "MASTERCARD", + "VISA" + ] + }, + "tokenization_specification": { + "type": "PAYMENT_GATEWAY", + "parameters": { + "gateway": "example", + "gateway_merchant_id": "{{gateway_merchant_id}}" + } + } + } + ], + "merchant_info": { + "merchant_name": "Narayan Bhat" + } + }, + "apple_pay": { + "session_token_data": { + "initiative": "web", + "certificate": "{{certificate}}", + "display_name": "applepay", + "certificate_keys": "{{certificate_keys}}", + "initiative_context": "hyperswitch-sdk-test.netlify.app", + "merchant_identifier": "merchant.com.stripe.sang" + }, + "payment_request_data": { + "label": "applepay pvt.ltd", + "supported_networks": [ + "visa", + "masterCard", + "amex", + "discover" + ], + "merchant_capabilities": [ + "supports3DS" + ] + } + } + } + } + }, + "url": { + "raw": "{{baseUrl}}/account/:account_id/connectors/:connector_id", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "account", + ":account_id", + "connectors", + ":connector_id" + ], + "variable": [ + { + "key": "account_id", + "value": "{{merchant_id}}" + }, + { + "key": "connector_id", + "value": "{{merchant_connector_id}}" + } + ] + }, + "description": "To update an existing Payment Connector. Helpful in enabling / disabling different payment methods and other settings for the connector etc" +} diff --git a/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/response.json b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/stripe/QuickStart/Payment Connector - Update/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/.event.meta.json b/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/.event.meta.json new file mode 100644 index 000000000000..4ac527d834af --- /dev/null +++ b/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/.event.meta.json @@ -0,0 +1,6 @@ +{ + "eventOrder": [ + "event.test.js", + "event.prerequest.js" + ] +} diff --git a/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/event.prerequest.js b/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/event.prerequest.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/event.test.js b/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/event.test.js new file mode 100644 index 000000000000..ffcdd527d07c --- /dev/null +++ b/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/event.test.js @@ -0,0 +1,80 @@ +// Validate status 2xx +pm.test("[POST]::/payments - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/payments - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/payments - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id +if (jsonData?.payment_id) { + pm.collectionVariables.set("payment_id", jsonData.payment_id); + console.log( + "- use {{payment_id}} as collection variable for value", + jsonData.payment_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", + ); +} + +// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id +if (jsonData?.mandate_id) { + pm.collectionVariables.set("mandate_id", jsonData.mandate_id); + console.log( + "- use {{mandate_id}} as collection variable for value", + jsonData.mandate_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", + ); +} + +// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret +if (jsonData?.client_secret) { + pm.collectionVariables.set("client_secret", jsonData.client_secret); + console.log( + "- use {{client_secret}} as collection variable for value", + jsonData.client_secret, + ); +} else { + console.log( + "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", + ); +} + +// Response body should have value "succeeded" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", + function () { + pm.expect(jsonData.status).to.eql("succeeded"); + }, + ); +} + +// Response body should have "connector_transaction_id" +pm.test( + "[POST]::/payments - Content check if 'connector_transaction_id' exists", + function () { + pm.expect(typeof jsonData.connector_transaction_id !== "undefined").to.be + .true; + }, +); diff --git a/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/request.json b/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/request.json new file mode 100644 index 000000000000..da1e0039c81f --- /dev/null +++ b/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/request.json @@ -0,0 +1,103 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "amount": 6540, + "currency": "USD", + "confirm": true, + "capture_method": "automatic", + "capture_on": "2022-09-10T10:11:12Z", + "amount_to_capture": 6540, + "customer_id": "StripeCustomer", + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+1", + "description": "Its my first payment request", + "authentication_type": "no_three_ds", + "return_url": "https://duck.com", + "payment_method": "card", + "payment_method_type": "credit", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_cvc": "123" + } + }, + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "joseph", + "last_name": "Doe" + }, + "phone": { + "number": "8056594427", + "country_code": "+91" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "joseph", + "last_name": "Doe" + }, + "phone": { + "number": "8056594427", + "country_code": "+91" + } + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + }, + "routing": { + "type": "single", + "data": "stripe" + } + } + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + }, + "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" +} diff --git a/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/response.json b/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/QuickStart/Payments - Create/event.test.js b/postman/collection-dir/stripe/QuickStart/Payments - Create/event.test.js index a6947db94c0b..44aef3e03bb1 100644 --- a/postman/collection-dir/stripe/QuickStart/Payments - Create/event.test.js +++ b/postman/collection-dir/stripe/QuickStart/Payments - Create/event.test.js @@ -59,3 +59,32 @@ if (jsonData?.client_secret) { "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", ); } + +// Response body should have value "failed" for "status" +if (jsonData?.status) { + pm.test( + "[POST]::/payments - Content check if value for 'status' matches 'failed'", + function () { + pm.expect(jsonData.status).to.eql("failed"); + }, + ); +} + +// Response body should have an error message +if (jsonData?.error_message) { + pm.test( + "[POST]::/payments - Content check if value for 'error_message' is not 'null'", + function () { + pm.expect(jsonData.error_message).is.not.null; + }, + ); +} + +// Response body should have "connector_transaction_id" +pm.test( + "[POST]::/payments - Content check if 'connector_transaction_id' exists", + function () { + pm.expect(typeof jsonData.connector_transaction_id !== "undefined").to.be + .true; + }, +); From d9fb5d4a52f44809ab4a1576a99e97b4c8b8c41b Mon Sep 17 00:00:00 2001 From: Arjun Karthik Date: Wed, 11 Oct 2023 15:16:12 +0530 Subject: [PATCH 6/8] chore(CODEOWNERS): update CODEOWNERS (#2541) --- .github/CODEOWNERS | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9a45340c6f1a..98a33d5d0213 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -19,10 +19,13 @@ crates/router/src/services/ @juspay/hyperswitch-framework crates/router/src/db/ @juspay/hyperswitch-framework crates/router/src/routes/ @juspay/hyperswitch-framework migrations/ @juspay/hyperswitch-framework +openapi/ @juspay/hyperswitch-framework connector-template/ @juspay/hyperswitch-connector crates/router/src/connector/ @juspay/hyperswitch-connector -crates/router/tests/connectors @juspay/hyperswitch-connector +crates/router/tests/connectors/ @juspay/hyperswitch-connector +crates/test_utils/tests/connectors/ @juspay/hyperswitch-connector +crates/test_utils/tests/sample_auth.toml @juspay/hyperswitch-connector crates/router/src/compatibility/ @juspay/hyperswitch-compatibility From 62638c4230bfd149c43c2805cbad0ce9be5386b3 Mon Sep 17 00:00:00 2001 From: jurijsb <60851943+jurijsb@users.noreply.github.com> Date: Wed, 11 Oct 2023 13:54:21 +0300 Subject: [PATCH 7/8] feat(connector): [Authorizedotnet] use connector_response_reference_id as reference to merchant (#2497) Co-authored-by: Jurijs --- .../src/connector/authorizedotnet/transformers.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index a2faeddb50f5..1b7480f78e41 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -540,7 +540,9 @@ impl mandate_reference: None, connector_metadata: metadata, network_txn_id: transaction_response.network_trans_id.clone(), - connector_response_reference_id: None, + connector_response_reference_id: Some( + transaction_response.transaction_id.clone(), + ), }), }, ..item.data @@ -604,7 +606,9 @@ impl mandate_reference: None, connector_metadata: metadata, network_txn_id: transaction_response.network_trans_id.clone(), - connector_response_reference_id: None, + connector_response_reference_id: Some( + transaction_response.transaction_id.clone(), + ), }), }, ..item.data @@ -887,13 +891,13 @@ impl Ok(Self { response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId( - transaction.transaction_id, + transaction.transaction_id.clone(), ), redirection_data: None, mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(transaction.transaction_id.clone()), }), status: payment_status, ..item.data From 7994a1259c5852ba4ebabb906bef963c6cf81bc9 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Wed, 11 Oct 2023 21:48:02 +0530 Subject: [PATCH 8/8] test(postman): Add proper `customer_id` in payment method create api (#2548) --- .../P_Create Customer/.event.meta.json | 3 ++ .../P_Create Customer/event.test.js | 53 +++++++++++++++++++ .../P_Create Customer/request.json | 39 ++++++++++++++ .../P_Create Customer/response.json | 1 + .../PaymentMethods - Create/request.json | 2 +- 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 postman/collection-dir/stripe/PaymentMethods/P_Create Customer/.event.meta.json create mode 100644 postman/collection-dir/stripe/PaymentMethods/P_Create Customer/event.test.js create mode 100644 postman/collection-dir/stripe/PaymentMethods/P_Create Customer/request.json create mode 100644 postman/collection-dir/stripe/PaymentMethods/P_Create Customer/response.json diff --git a/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/.event.meta.json b/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/.event.meta.json new file mode 100644 index 000000000000..0731450e6b25 --- /dev/null +++ b/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/.event.meta.json @@ -0,0 +1,3 @@ +{ + "eventOrder": ["event.test.js"] +} diff --git a/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/event.test.js b/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/event.test.js new file mode 100644 index 000000000000..4ee1d3e8b2bb --- /dev/null +++ b/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/event.test.js @@ -0,0 +1,53 @@ +// Validate status 2xx +pm.test("[POST]::/customers - Status code is 2xx", function () { + pm.response.to.be.success; +}); + +// Validate if response header has matching content-type +pm.test("[POST]::/customers - Content-Type is application/json", function () { + pm.expect(pm.response.headers.get("Content-Type")).to.include( + "application/json", + ); +}); + +// Validate if response has JSON Body +pm.test("[POST]::/customers - Response has JSON Body", function () { + pm.response.to.have.jsonBody(); +}); + +// Set response object as internal variable +let jsonData = {}; +try { + jsonData = pm.response.json(); +} catch (e) {} + +// Response body should have "customer_id" +pm.test( + "[POST]::/customers - Content check if 'customer_id' exists", + function () { + pm.expect(typeof jsonData.customer_id !== "undefined").to.be.true; + }, +); + +// Response body should have a minimum length of "1" for "customer_id" +if (jsonData?.customer_id) { + pm.test( + "[POST]::/customers - Content check if value of 'customer_id' has a minimum length of '1'", + function () { + pm.expect(jsonData.customer_id.length).is.at.least(1); + }, + ); +} + +// pm.collectionVariables - Set customer_id as variable for jsonData.customer_id +if (jsonData?.customer_id) { + pm.collectionVariables.set("customer_id", jsonData.customer_id); + console.log( + "- use {{customer_id}} as collection variable for value", + jsonData.customer_id, + ); +} else { + console.log( + "INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.", + ); +} diff --git a/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/request.json b/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/request.json new file mode 100644 index 000000000000..be27f693d9c5 --- /dev/null +++ b/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/request.json @@ -0,0 +1,39 @@ +{ + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "options": { + "raw": { + "language": "json" + } + }, + "raw_json_formatted": { + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+65", + "description": "First customer", + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + } + } + }, + "url": { + "raw": "{{baseUrl}}/customers", + "host": ["{{baseUrl}}"], + "path": ["customers"] + }, + "description": "Create a customer object and store the customer details to be reused for future payments. Incase the customer already exists in the system, this API will respond with the customer details." +} diff --git a/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/response.json b/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/response.json new file mode 100644 index 000000000000..fe51488c7066 --- /dev/null +++ b/postman/collection-dir/stripe/PaymentMethods/P_Create Customer/response.json @@ -0,0 +1 @@ +[] diff --git a/postman/collection-dir/stripe/PaymentMethods/PaymentMethods - Create/request.json b/postman/collection-dir/stripe/PaymentMethods/PaymentMethods - Create/request.json index f9126f5e2e4a..2617b3e44a03 100644 --- a/postman/collection-dir/stripe/PaymentMethods/PaymentMethods - Create/request.json +++ b/postman/collection-dir/stripe/PaymentMethods/PaymentMethods - Create/request.json @@ -27,7 +27,7 @@ "card_exp_year": "25", "card_holder_name": "John Doe" }, - "customer_id": "cus_mnewerunwiuwiwqw", + "customer_id": "{{customer_id}}", "metadata": { "city": "NY", "unit": "245"