diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index 940922743ca..d748f65261a 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -10916,6 +10916,7 @@ "Connector": { "type": "string", "enum": [ + "authipay", "adyenplatform", "stripe_billing_test", "phonypay", @@ -27926,6 +27927,7 @@ "type": "string", "description": "RoutableConnectors are the subset of Connectors that are eligible for payments routing", "enum": [ + "authipay", "adyenplatform", "stripe_billing_test", "phonypay", diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index 12340ea5dcb..53bbace7dfb 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -8123,6 +8123,7 @@ "Connector": { "type": "string", "enum": [ + "authipay", "adyenplatform", "stripe_billing_test", "phonypay", @@ -22643,6 +22644,7 @@ "type": "string", "description": "RoutableConnectors are the subset of Connectors that are eligible for payments routing", "enum": [ + "authipay", "adyenplatform", "stripe_billing_test", "phonypay", diff --git a/config/config.example.toml b/config/config.example.toml index 10f796128f7..2b1916d41c2 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -189,6 +189,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" +authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" @@ -343,6 +344,7 @@ cards = [ "adyen", "adyenplatform", "archipel", + "authipay", "authorizedotnet", "celero", "coinbase", diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 803047dca2e..f839aabbf60 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -28,6 +28,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" +authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 94d8f18d44d..36f80248f9b 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -32,6 +32,7 @@ airwallex.base_url = "https://api.airwallex.com/" amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" +authipay.base_url = "https://prod.emea.api.fiservapps.com/ipp/payments-gateway/v2/" authorizedotnet.base_url = "https://api.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://www.bambora.co.nz/interface/api/dts.asmx" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 99daaab178d..3018fb176d0 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -32,6 +32,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" +authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" diff --git a/config/development.toml b/config/development.toml index b72f7bcbaee..8808aabb0ef 100644 --- a/config/development.toml +++ b/config/development.toml @@ -99,6 +99,7 @@ cards = [ "airwallex", "amazonpay", "archipel", + "authipay", "authorizedotnet", "bambora", "bamboraapac", @@ -227,6 +228,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" +authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index ef3ad0b1b05..b232636421f 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -115,6 +115,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" +authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" @@ -251,6 +252,7 @@ cards = [ "airwallex", "amazonpay", "archipel", + "authipay", "authorizedotnet", "bambora", "bamboraapac", diff --git a/config/payment_required_fields_v2.toml b/config/payment_required_fields_v2.toml index cc9ffee29e2..fe1e6e050d2 100644 --- a/config/payment_required_fields_v2.toml +++ b/config/payment_required_fields_v2.toml @@ -47,6 +47,14 @@ common = [ { required_field = "payment_method_data.billing.address.last_name", display_name = "card_holder_name", field_type = "user_full_name" } ] +[required_fields.Card.Debit.fields.Authipay] +common = [ + { required_field = "payment_method_data.card.card_number", display_name = "card_number", field_type = "user_card_number" }, + { required_field = "payment_method_data.card.card_exp_month", display_name = "card_exp_month", field_type = "user_card_expiry_month" }, + { required_field = "payment_method_data.card.card_exp_year", display_name = "card_exp_year", field_type = "user_card_expiry_year" }, + { required_field = "payment_method_data.card.card_cvc", display_name = "card_cvc", field_type = "user_card_cvc" } +] + [required_fields.Card.Debit.fields.Bambora] non_mandate = [ { required_field = "payment_method_data.card.card_number", display_name = "card_number", field_type = "user_card_number" }, @@ -546,6 +554,14 @@ common = [ { required_field = "payment_method_data.card.card_cvc", display_name = "card_cvc", field_type = "user_card_cvc" } ] +[required_fields.Card.Credit.fields.Authipay] +common = [ + { required_field = "payment_method_data.card.card_number", display_name = "card_number", field_type = "user_card_number" }, + { required_field = "payment_method_data.card.card_exp_month", display_name = "card_exp_month", field_type = "user_card_expiry_month" }, + { required_field = "payment_method_data.card.card_exp_year", display_name = "card_exp_year", field_type = "user_card_expiry_year" }, + { required_field = "payment_method_data.card.card_cvc", display_name = "card_cvc", field_type = "user_card_cvc" } +] + [required_fields.Card.Credit.fields.Bambora] non_mandate = [ { required_field = "payment_method_data.card.card_number", display_name = "card_number", field_type = "user_card_number" }, @@ -2341,4 +2357,4 @@ non_mandate = [ [required_fields.card_redirect.momo_atm.fields.Adyen] common = [] mandate = [] -non_mandate = [] \ No newline at end of file +non_mandate = [] diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 5251b9b3177..41563d56f27 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -25,6 +25,7 @@ pub use crate::PaymentMethodType; #[strum(serialize_all = "snake_case")] /// RoutableConnectors are the subset of Connectors that are eligible for payments routing pub enum RoutableConnectors { + Authipay, Adyenplatform, #[cfg(feature = "dummy_connector")] #[serde(rename = "stripe_billing_test")] @@ -184,6 +185,7 @@ pub enum RoutableConnectors { #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum Connector { + Authipay, Adyenplatform, #[cfg(feature = "dummy_connector")] #[serde(rename = "stripe_billing_test")] @@ -404,6 +406,7 @@ impl Connector { | Self::DummyConnector7 => false, Self::Aci // Add Separate authentication support for connectors + | Self::Authipay | Self::Adyen | Self::Adyenplatform | Self::Airwallex @@ -551,6 +554,7 @@ impl Connector { impl From for Connector { fn from(routable_connector: RoutableConnectors) -> Self { match routable_connector { + RoutableConnectors::Authipay => Self::Authipay, RoutableConnectors::Adyenplatform => Self::Adyenplatform, #[cfg(feature = "dummy_connector")] RoutableConnectors::DummyBillingConnector => Self::DummyBillingConnector, @@ -673,6 +677,7 @@ impl TryFrom for RoutableConnectors { fn try_from(connector: Connector) -> Result { match connector { + Connector::Authipay => Ok(Self::Authipay), Connector::Adyenplatform => Ok(Self::Adyenplatform), #[cfg(feature = "dummy_connector")] Connector::DummyBillingConnector => Ok(Self::DummyBillingConnector), diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index f092b543f8c..67e340fa155 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -171,6 +171,7 @@ pub struct ConnectorTomlConfig { #[serde_with::skip_serializing_none] #[derive(Debug, Deserialize, serde::Serialize, Clone)] pub struct ConnectorConfig { + pub authipay: Option, pub juspaythreedsserver: Option, pub aci: Option, pub adyen: Option, @@ -369,6 +370,7 @@ impl ConnectorConfig { let connector_data = Self::new()?; match connector { Connector::Aci => Ok(connector_data.aci), + Connector::Authipay => Ok(connector_data.authipay), Connector::Adyen => Ok(connector_data.adyen), Connector::Adyenplatform => Err("Use get_payout_connector_config".to_string()), Connector::Airwallex => Ok(connector_data.airwallex), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index f8919cba957..a8a0baa2661 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -502,7 +502,21 @@ required=true type="MultiSelect" options=["PAN_ONLY", "CRYPTOGRAM_3DS"] - +[authipay] +[[authipay.credit]] + payment_method_type = "Mastercard" +[[authipay.credit]] + payment_method_type = "Visa" +[[authipay.debit]] + payment_method_type = "Mastercard" +[[authipay.debit]] + payment_method_type = "Visa" +[authipay.connector_auth.SignatureKey] +api_key="API Key" +api_secret="API Secret" +key1="Merchant ID" +[authipay.connector_webhook_details] +merchant_secret="Source verification key" [authorizedotnet] [[authorizedotnet.credit]] diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 667c5028c95..a22b50a9b4f 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -305,6 +305,22 @@ key1="Client ID" [airwallex.connector_webhook_details] merchant_secret="Source verification key" +[authipay] +[[authipay.credit]] + payment_method_type = "Mastercard" +[[authipay.credit]] + payment_method_type = "Visa" +[[authipay.debit]] + payment_method_type = "Mastercard" +[[authipay.debit]] + payment_method_type = "Visa" +[authipay.connector_auth.SignatureKey] +api_key="API Key" +api_secret="API Secret" +key1="Merchant ID" +[authipay.connector_webhook_details] +merchant_secret="Source verification key" + [authorizedotnet] [[authorizedotnet.credit]] payment_method_type = "Mastercard" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 20b7cdf09bf..d7026129699 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -503,7 +503,21 @@ required=true type="MultiSelect" options=["PAN_ONLY", "CRYPTOGRAM_3DS"] - +[authipay] +[[authipay.credit]] + payment_method_type = "Mastercard" +[[authipay.credit]] + payment_method_type = "Visa" +[[authipay.debit]] + payment_method_type = "Mastercard" +[[authipay.debit]] + payment_method_type = "Visa" +[authipay.connector_auth.SignatureKey] +api_key="API Key" +api_secret="API Secret" +key1="Merchant ID" +[authipay.connector_webhook_details] +merchant_secret="Source verification key" [authorizedotnet] [[authorizedotnet.credit]] diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index 6583e2ce5b9..84b53c97143 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -4,6 +4,7 @@ pub mod adyenplatform; pub mod airwallex; pub mod amazonpay; pub mod archipel; +pub mod authipay; pub mod authorizedotnet; pub mod bambora; pub mod bamboraapac; @@ -115,16 +116,16 @@ pub mod zsl; pub use self::dummyconnector::DummyConnector; pub use self::{ aci::Aci, adyen::Adyen, adyenplatform::Adyenplatform, airwallex::Airwallex, - amazonpay::Amazonpay, archipel::Archipel, authorizedotnet::Authorizedotnet, bambora::Bambora, - bamboraapac::Bamboraapac, bankofamerica::Bankofamerica, barclaycard::Barclaycard, - billwerk::Billwerk, bitpay::Bitpay, bluesnap::Bluesnap, boku::Boku, braintree::Braintree, - cashtocode::Cashtocode, celero::Celero, chargebee::Chargebee, checkbook::Checkbook, - checkout::Checkout, coinbase::Coinbase, coingate::Coingate, cryptopay::Cryptopay, - ctp_mastercard::CtpMastercard, cybersource::Cybersource, datatrans::Datatrans, - deutschebank::Deutschebank, digitalvirgo::Digitalvirgo, dlocal::Dlocal, dwolla::Dwolla, - ebanx::Ebanx, elavon::Elavon, facilitapay::Facilitapay, fiserv::Fiserv, fiservemea::Fiservemea, - fiuu::Fiuu, forte::Forte, getnet::Getnet, globalpay::Globalpay, globepay::Globepay, - gocardless::Gocardless, gpayments::Gpayments, helcim::Helcim, hipay::Hipay, + amazonpay::Amazonpay, archipel::Archipel, authipay::Authipay, authorizedotnet::Authorizedotnet, + bambora::Bambora, bamboraapac::Bamboraapac, bankofamerica::Bankofamerica, + barclaycard::Barclaycard, billwerk::Billwerk, bitpay::Bitpay, bluesnap::Bluesnap, boku::Boku, + braintree::Braintree, cashtocode::Cashtocode, celero::Celero, chargebee::Chargebee, + checkbook::Checkbook, checkout::Checkout, coinbase::Coinbase, coingate::Coingate, + cryptopay::Cryptopay, ctp_mastercard::CtpMastercard, cybersource::Cybersource, + datatrans::Datatrans, deutschebank::Deutschebank, digitalvirgo::Digitalvirgo, dlocal::Dlocal, + dwolla::Dwolla, ebanx::Ebanx, elavon::Elavon, facilitapay::Facilitapay, fiserv::Fiserv, + fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, getnet::Getnet, globalpay::Globalpay, + globepay::Globepay, gocardless::Gocardless, gpayments::Gpayments, helcim::Helcim, hipay::Hipay, hyperswitch_vault::HyperswitchVault, iatapay::Iatapay, inespay::Inespay, itaubank::Itaubank, jpmorgan::Jpmorgan, juspaythreedsserver::Juspaythreedsserver, klarna::Klarna, mifinity::Mifinity, mollie::Mollie, moneris::Moneris, multisafepay::Multisafepay, diff --git a/crates/hyperswitch_connectors/src/connectors/authipay.rs b/crates/hyperswitch_connectors/src/connectors/authipay.rs new file mode 100644 index 00000000000..5a5179ef5f1 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/authipay.rs @@ -0,0 +1,849 @@ +pub mod transformers; + +use std::sync::LazyLock; + +use base64::Engine; +use common_enums::enums; +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{ + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, + }, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask, PeekInterface}; +use transformers as authipay; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Authipay { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Authipay { + pub fn new() -> &'static Self { + &Self { + amount_converter: &FloatMajorUnitForConnector, + } + } + + pub fn generate_authorization_signature( + &self, + auth: authipay::AuthipayAuthType, + request_id: &str, + payload: &str, + timestamp: i128, + ) -> CustomResult { + let authipay::AuthipayAuthType { + api_key, + api_secret, + } = auth; + let raw_signature = format!("{}{request_id}{timestamp}{payload}", api_key.peek()); + + let key = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, api_secret.expose().as_bytes()); + let signature_value = common_utils::consts::BASE64_ENGINE + .encode(ring::hmac::sign(&key, raw_signature.as_bytes()).as_ref()); + Ok(signature_value) + } +} + +impl api::Payment for Authipay {} +impl api::PaymentSession for Authipay {} +impl api::ConnectorAccessToken for Authipay {} +impl api::MandateSetup for Authipay {} +impl api::PaymentAuthorize for Authipay {} +impl api::PaymentSync for Authipay {} +impl api::PaymentCapture for Authipay {} +impl api::PaymentVoid for Authipay {} +impl api::Refund for Authipay {} +impl api::RefundExecute for Authipay {} +impl api::RefundSync for Authipay {} +impl api::PaymentToken for Authipay {} + +impl ConnectorIntegration + for Authipay +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Authipay +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let timestamp = time::OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000; + let auth: authipay::AuthipayAuthType = + authipay::AuthipayAuthType::try_from(&req.connector_auth_type)?; + let mut auth_header = self.get_auth_header(&req.connector_auth_type)?; + + let authipay_req = self.get_request_body(req, connectors)?; + + let client_request_id = uuid::Uuid::new_v4().to_string(); + let hmac = self + .generate_authorization_signature( + auth, + &client_request_id, + authipay_req.get_inner_value().peek(), + timestamp, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let mut headers = vec![ + ( + headers::CONTENT_TYPE.to_string(), + types::PaymentsAuthorizeType::get_content_type(self) + .to_string() + .into(), + ), + ("Client-Request-Id".to_string(), client_request_id.into()), + ("Auth-Token-Type".to_string(), "HMAC".to_string().into()), + (headers::TIMESTAMP.to_string(), timestamp.to_string().into()), + ("Message-Signature".to_string(), hmac.into_masked()), + ]; + headers.append(&mut auth_header); + Ok(headers) + } +} + +impl ConnectorCommon for Authipay { + fn id(&self) -> &'static str { + "authipay" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.authipay.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = authipay::AuthipayAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::API_KEY.to_string(), + auth.api_key.into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: authipay::AuthipayErrorResponse = res + .response + .parse_struct("AuthipayErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_error_response_body(&response)); + + let mut error_response = ErrorResponse::from(&response); + + // Set status code from the response, or 400 if error code is a "404" + if let Some(error_code) = &response.error.code { + if error_code == "404" { + error_response.status_code = 404; + } else { + error_response.status_code = res.status_code; + } + } else { + error_response.status_code = res.status_code; + } + + Ok(error_response) + } +} + +impl ConnectorValidation for Authipay { + fn validate_connector_against_payment_request( + &self, + capture_method: Option, + _payment_method: enums::PaymentMethod, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), + enums::CaptureMethod::Scheduled | enums::CaptureMethod::ManualMultiple => Err( + utils::construct_not_implemented_error_report(capture_method, self.id()), + ), + } + } +} + +impl ConnectorIntegration for Authipay { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Authipay {} + +impl ConnectorIntegration + for Authipay +{ +} + +impl ConnectorIntegration for Authipay { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &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: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}payments", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = authipay::AuthipayRouterData::from((amount, req)); + let connector_req = authipay::AuthipayPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: authipay::AuthipayPaymentsResponse = res + .response + .parse_struct("Authipay PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Authipay { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &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: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_transaction_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(format!( + "{}payments/{}", + self.base_url(connectors), + connector_transaction_id + )) + } + + fn get_request_body( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Ok(RequestContent::RawBytes(Vec::new())) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(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: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: authipay::AuthipayPaymentsResponse = res + .response + .parse_struct("authipay PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Authipay { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &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: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_transaction_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}payments/{}", + self.base_url(connectors), + connector_transaction_id + )) + } + + fn get_request_body( + &self, + req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + + let connector_router_data = authipay::AuthipayRouterData::from((amount, req)); + let connector_req = authipay::AuthipayCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: authipay::AuthipayPaymentsResponse = res + .response + .parse_struct("Authipay PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Authipay { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + connectors: &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: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + // For void operations, Authipay requires using the /orders/{orderId} endpoint + // The orderId should be stored in connector_meta from the authorization response + let order_id = req + .request + .connector_meta + .as_ref() + .and_then(|meta| meta.get("order_id")) + .and_then(|v| v.as_str()) + .ok_or(errors::ConnectorError::RequestEncodingFailed)?; + + Ok(format!("{}orders/{}", self.base_url(connectors), order_id)) + } + + fn get_request_body( + &self, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult { + // For void, we don't need amount conversion since it's always full amount + let connector_router_data = + authipay::AuthipayRouterData::from((FloatMajorUnit::zero(), req)); + let connector_req = authipay::AuthipayVoidRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: authipay::AuthipayPaymentsResponse = res + .response + .parse_struct("Authipay PaymentsVoidResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Authipay { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &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: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}payments/{}", + self.base_url(connectors), + connector_payment_id + )) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = authipay::AuthipayRouterData::from((refund_amount, req)); + let connector_req = authipay::AuthipayRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: authipay::RefundResponse = res + .response + .parse_struct("authipay RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Authipay { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &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: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult { + let refund_id = req + .request + .connector_refund_id + .clone() + .ok_or(errors::ConnectorError::RequestEncodingFailed)?; + Ok(format!( + "{}payments/{}", + self.base_url(connectors), + refund_id + )) + } + + fn get_request_body( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Ok(RequestContent::RawBytes(Vec::new())) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: authipay::RefundResponse = res + .response + .parse_struct("authipay RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Authipay { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +static AUTHIPAY_SUPPORTED_PAYMENT_METHODS: LazyLock = + LazyLock::new(|| { + let supported_capture_methods = vec![ + enums::CaptureMethod::Automatic, + enums::CaptureMethod::SequentialAutomatic, + enums::CaptureMethod::Manual, + ]; + + let supported_card_network = vec![ + common_enums::CardNetwork::Visa, + common_enums::CardNetwork::Mastercard, + ]; + + let mut authipay_supported_payment_methods = SupportedPaymentMethods::new(); + + authipay_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Credit, + PaymentMethodDetails { + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::Supported, + supported_capture_methods: supported_capture_methods.clone(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::Supported, + supported_card_networks: supported_card_network.clone(), + } + }), + ), + }, + ); + + authipay_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Debit, + PaymentMethodDetails { + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::Supported, + supported_capture_methods: supported_capture_methods.clone(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::Supported, + supported_card_networks: supported_card_network.clone(), + } + }), + ), + }, + ); + + authipay_supported_payment_methods + }); + +static AUTHIPAY_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + display_name: "Authipay", + description: "Authipay is a Fiserv-powered payment gateway for the EMEA region supporting Visa and Mastercard transactions. Features include flexible capture methods (automatic, manual, sequential), partial captures/refunds, payment tokenization, and secure HMAC SHA256 authentication.", + connector_type: enums::PaymentConnectorCategory::PaymentGateway, +}; + +static AUTHIPAY_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = []; + +impl ConnectorSpecifications for Authipay { + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + Some(&AUTHIPAY_CONNECTOR_INFO) + } + + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + Some(&*AUTHIPAY_SUPPORTED_PAYMENT_METHODS) + } + + fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { + Some(&AUTHIPAY_SUPPORTED_WEBHOOK_FLOWS) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/authipay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/authipay/transformers.rs new file mode 100644 index 00000000000..c7c21404752 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/authipay/transformers.rs @@ -0,0 +1,624 @@ +use cards; +use common_enums::enums; +use common_utils::types::FloatMajorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + RefundsRouterData, + }, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils, +}; + +// Type definition for router data with amount +pub struct AuthipayRouterData { + pub amount: FloatMajorUnit, // Amount in major units (e.g., dollars instead of cents) + pub router_data: T, +} + +impl From<(FloatMajorUnit, T)> for AuthipayRouterData { + fn from((amount, item): (FloatMajorUnit, T)) -> Self { + Self { + amount, + router_data: item, + } + } +} + +// Basic request/response structs used across multiple operations + +#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Amount { + total: FloatMajorUnit, + currency: String, + #[serde(skip_serializing_if = "Option::is_none")] + components: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AmountComponents { + subtotal: FloatMajorUnit, +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ExpiryDate { + month: Secret, + year: Secret, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Card { + number: cards::CardNumber, + security_code: Secret, + expiry_date: ExpiryDate, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentMethod { + payment_card: Card, +} + +#[derive(Default, Debug, Serialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SplitShipment { + total_count: i32, + final_shipment: bool, +} + +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AuthipayPaymentsRequest { + request_type: &'static str, + transaction_amount: Amount, + payment_method: PaymentMethod, + // split_shipment: Option, + // incremental_flag: Option, +} + +impl TryFrom<&AuthipayRouterData<&PaymentsAuthorizeRouterData>> for AuthipayPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &AuthipayRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + // Check if 3DS is being requested - Authipay doesn't support 3DS + if matches!( + item.router_data.auth_type, + enums::AuthenticationType::ThreeDs + ) { + return Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("authipay"), + ) + .into()); + } + + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let expiry_date = ExpiryDate { + month: req_card.card_exp_month.clone(), + year: req_card.card_exp_year.clone(), + }; + + let card = Card { + number: req_card.card_number.clone(), + security_code: req_card.card_cvc.clone(), + expiry_date, + }; + + let payment_method = PaymentMethod { payment_card: card }; + + let transaction_amount = Amount { + total: item.amount, + currency: item.router_data.request.currency.to_string(), + components: None, + }; + + // Determine request type based on capture method + let request_type = match item.router_data.request.capture_method { + Some(enums::CaptureMethod::Manual) => "PaymentCardPreAuthTransaction", + Some(enums::CaptureMethod::Automatic) => "PaymentCardSaleTransaction", + Some(enums::CaptureMethod::SequentialAutomatic) => "PaymentCardSaleTransaction", + Some(enums::CaptureMethod::ManualMultiple) + | Some(enums::CaptureMethod::Scheduled) => { + return Err(errors::ConnectorError::NotSupported { + message: "Capture method not supported by Authipay".to_string(), + connector: "Authipay", + } + .into()); + } + None => "PaymentCardSaleTransaction", // Default when not specified + }; + + let request = Self { + request_type, + transaction_amount, + payment_method, + }; + + Ok(request) + } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("authipay"), + ) + .into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct AuthipayAuthType { + pub(super) api_key: Secret, + pub(super) api_secret: Secret, +} + +impl TryFrom<&ConnectorAuthType> for AuthipayAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::SignatureKey { + api_key, + api_secret, + .. + } => Ok(Self { + api_key: api_key.to_owned(), + api_secret: api_secret.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// Transaction Status enum (like Fiserv's FiservPaymentStatus) +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "UPPERCASE")] +pub enum AuthipayTransactionStatus { + Authorized, + Captured, + Voided, + Declined, + Failed, + #[default] + Processing, +} + +impl From for enums::AttemptStatus { + fn from(item: AuthipayTransactionStatus) -> Self { + match item { + AuthipayTransactionStatus::Captured => Self::Charged, + AuthipayTransactionStatus::Declined | AuthipayTransactionStatus::Failed => { + Self::Failure + } + AuthipayTransactionStatus::Processing => Self::Pending, + AuthipayTransactionStatus::Authorized => Self::Authorized, + AuthipayTransactionStatus::Voided => Self::Voided, + } + } +} + +// Transaction Processing Details (like Fiserv's TransactionProcessingDetails) +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AuthipayTransactionProcessingDetails { + pub order_id: String, + pub transaction_id: String, +} + +// Gateway Response (like Fiserv's GatewayResponse) +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AuthipayGatewayResponse { + pub transaction_state: AuthipayTransactionStatus, + pub transaction_processing_details: AuthipayTransactionProcessingDetails, +} + +// Payment Receipt (like Fiserv's PaymentReceipt) +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AuthipayPaymentReceipt { + pub approved_amount: Amount, + pub processor_response_details: Option, +} + +// Main Response (like Fiserv's FiservPaymentsResponse) - but flat for JSON deserialization +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AuthipayPaymentsResponse { + #[serde(rename = "type")] + response_type: Option, + client_request_id: String, + api_trace_id: String, + ipg_transaction_id: String, + order_id: String, + transaction_type: String, + payment_token: Option, + transaction_origin: Option, + payment_method_details: Option, + country: Option, + terminal_id: Option, + merchant_id: Option, + transaction_time: i64, + approved_amount: Amount, + transaction_amount: Amount, + // For payment transactions (SALE) + transaction_status: Option, + // For refund transactions (RETURN) + transaction_result: Option, + transaction_state: Option, + approval_code: String, + scheme_transaction_id: Option, + processor: Processor, +} + +impl AuthipayPaymentsResponse { + /// Get gateway response (like Fiserv's gateway_response) + pub fn gateway_response(&self) -> AuthipayGatewayResponse { + AuthipayGatewayResponse { + transaction_state: self.get_transaction_status(), + transaction_processing_details: AuthipayTransactionProcessingDetails { + order_id: self.order_id.clone(), + transaction_id: self.ipg_transaction_id.clone(), + }, + } + } + + /// Get payment receipt (like Fiserv's payment_receipt) + pub fn payment_receipt(&self) -> AuthipayPaymentReceipt { + AuthipayPaymentReceipt { + approved_amount: self.approved_amount.clone(), + processor_response_details: Some(self.processor.clone()), + } + } + + /// Determine the transaction status based on transaction type and various status fields (like Fiserv) + fn get_transaction_status(&self) -> AuthipayTransactionStatus { + match self.transaction_type.as_str() { + "RETURN" => { + // Refund transaction - use transaction_result + match self.transaction_result.as_deref() { + Some("APPROVED") => AuthipayTransactionStatus::Captured, + Some("DECLINED") | Some("FAILED") => AuthipayTransactionStatus::Failed, + _ => AuthipayTransactionStatus::Processing, + } + } + "VOID" => { + // Void transaction - use transaction_result, fallback to transaction_state + match self.transaction_result.as_deref() { + Some("APPROVED") => AuthipayTransactionStatus::Voided, + Some("DECLINED") | Some("FAILED") => AuthipayTransactionStatus::Failed, + Some("PENDING") | Some("PROCESSING") => AuthipayTransactionStatus::Processing, + _ => { + // Fallback to transaction_state for void operations + match self.transaction_state.as_deref() { + Some("VOIDED") => AuthipayTransactionStatus::Voided, + Some("FAILED") | Some("DECLINED") => AuthipayTransactionStatus::Failed, + _ => AuthipayTransactionStatus::Voided, // Default assumption for void requests + } + } + } + } + _ => { + // Payment transaction - prioritize transaction_state over transaction_status + match self.transaction_state.as_deref() { + Some("AUTHORIZED") => AuthipayTransactionStatus::Authorized, + Some("CAPTURED") => AuthipayTransactionStatus::Captured, + Some("VOIDED") => AuthipayTransactionStatus::Voided, + Some("DECLINED") | Some("FAILED") => AuthipayTransactionStatus::Failed, + _ => { + // Fallback to transaction_status with transaction_type context + match ( + self.transaction_type.as_str(), + self.transaction_status.as_deref(), + ) { + // For PREAUTH transactions, "APPROVED" means authorized and awaiting capture + ("PREAUTH", Some("APPROVED")) => AuthipayTransactionStatus::Authorized, + // For POSTAUTH transactions, "APPROVED" means successfully captured + ("POSTAUTH", Some("APPROVED")) => AuthipayTransactionStatus::Captured, + // For SALE transactions, "APPROVED" means completed payment + ("SALE", Some("APPROVED")) => AuthipayTransactionStatus::Captured, + // For VOID transactions, "APPROVED" means successfully voided + ("VOID", Some("APPROVED")) => AuthipayTransactionStatus::Voided, + // Generic status mappings for other cases + (_, Some("APPROVED")) => AuthipayTransactionStatus::Captured, + (_, Some("AUTHORIZED")) => AuthipayTransactionStatus::Authorized, + (_, Some("DECLINED") | Some("FAILED")) => { + AuthipayTransactionStatus::Failed + } + _ => AuthipayTransactionStatus::Processing, + } + } + } + } + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentToken { + reusable: Option, + decline_duplicates: Option, + brand: Option, + #[serde(rename = "type")] + token_type: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentMethodDetails { + payment_card: Option, + payment_method_type: Option, + payment_method_brand: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentCardDetails { + expiry_date: ExpiryDate, + bin: String, + last4: String, + brand: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Processor { + reference_number: Option, + authorization_code: Option, + response_code: String, + response_message: String, + avs_response: Option, + security_code_response: Option, + tax_refund_data: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AvsResponse { + street_match: Option, + postal_code_match: Option, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + // Get gateway response (like Fiserv pattern) + let gateway_resp = item.response.gateway_response(); + + // Store order_id in connector_metadata for void operations (like Fiserv) + let mut metadata = std::collections::HashMap::new(); + metadata.insert( + "order_id".to_string(), + serde_json::Value::String(gateway_resp.transaction_processing_details.order_id.clone()), + ); + let connector_metadata = Some(serde_json::Value::Object(serde_json::Map::from_iter( + metadata, + ))); + + Ok(Self { + status: enums::AttemptStatus::from(gateway_resp.transaction_state.clone()), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + gateway_resp + .transaction_processing_details + .transaction_id + .clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata, + network_txn_id: None, + connector_response_reference_id: Some( + gateway_resp.transaction_processing_details.order_id.clone(), + ), + incremental_authorization_allowed: None, + charges: None, + }), + ..item.data + }) + } +} + +// Type definition for CaptureRequest +#[derive(Debug, Serialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AuthipayCaptureRequest { + request_type: &'static str, + transaction_amount: Amount, +} + +impl TryFrom<&AuthipayRouterData<&PaymentsCaptureRouterData>> for AuthipayCaptureRequest { + type Error = error_stack::Report; + + fn try_from( + item: &AuthipayRouterData<&PaymentsCaptureRouterData>, + ) -> Result { + Ok(Self { + request_type: "PostAuthTransaction", + transaction_amount: Amount { + total: item.amount, + currency: item.router_data.request.currency.to_string(), + components: None, + }, + }) + } +} + +// Type definition for VoidRequest +#[derive(Debug, Serialize, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AuthipayVoidRequest { + request_type: &'static str, +} + +impl TryFrom<&AuthipayRouterData<&PaymentsCancelRouterData>> for AuthipayVoidRequest { + type Error = error_stack::Report; + + fn try_from( + _item: &AuthipayRouterData<&PaymentsCancelRouterData>, + ) -> Result { + Ok(Self { + request_type: "VoidTransaction", + }) + } +} + +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthipayRefundRequest { + request_type: &'static str, + transaction_amount: Amount, +} + +impl TryFrom<&AuthipayRouterData<&RefundsRouterData>> for AuthipayRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &AuthipayRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + request_type: "ReturnTransaction", + transaction_amount: Amount { + total: item.amount.to_owned(), + currency: item.router_data.request.currency.to_string(), + components: None, + }, + }) + } +} + +// 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 + } + } +} + +// Reusing the payments response structure for refunds +// because Authipay uses the same endpoint and response format +pub type RefundResponse = AuthipayPaymentsResponse; + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + let refund_status = if item.response.transaction_type == "RETURN" { + match item.response.transaction_result.as_deref() { + Some("APPROVED") => RefundStatus::Succeeded, + Some("DECLINED") | Some("FAILED") => RefundStatus::Failed, + _ => RefundStatus::Processing, + } + } else { + RefundStatus::Processing + }; + + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.ipg_transaction_id.to_string(), + refund_status: enums::RefundStatus::from(refund_status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + let refund_status = if item.response.transaction_type == "RETURN" { + match item.response.transaction_result.as_deref() { + Some("APPROVED") => RefundStatus::Succeeded, + Some("DECLINED") | Some("FAILED") => RefundStatus::Failed, + _ => RefundStatus::Processing, + } + } else { + RefundStatus::Processing + }; + + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.ipg_transaction_id.to_string(), + refund_status: enums::RefundStatus::from(refund_status), + }), + ..item.data + }) + } +} + +// Error Response structs +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ErrorDetailItem { + pub field: String, + pub message: String, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ErrorDetails { + pub code: Option, + pub message: String, + pub details: Option>, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthipayErrorResponse { + pub client_request_id: Option, + pub api_trace_id: Option, + pub response_type: Option, + #[serde(rename = "type")] + pub response_object_type: Option, + pub error: ErrorDetails, + pub decline_reason_code: Option, +} + +impl From<&AuthipayErrorResponse> for ErrorResponse { + fn from(item: &AuthipayErrorResponse) -> Self { + Self { + status_code: 500, // Default to Internal Server Error, will be overridden by actual HTTP status + code: item.error.code.clone().unwrap_or_default(), + message: item.error.message.clone(), + reason: None, + attempt_status: None, + connector_transaction_id: None, + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + } + } +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 9cc798d57cd..38e63124029 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -148,6 +148,7 @@ default_imp_for_authorize_session_token!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -274,6 +275,7 @@ default_imp_for_calculate_tax!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -401,6 +403,7 @@ default_imp_for_session_update!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -528,6 +531,7 @@ default_imp_for_post_session_tokens!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -654,6 +658,7 @@ default_imp_for_create_order!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -781,6 +786,7 @@ default_imp_for_update_metadata!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -911,6 +917,7 @@ default_imp_for_complete_authorize!( connectors::Adyenplatform, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Bamboraapac, connectors::Bankofamerica, connectors::Barclaycard, @@ -1018,6 +1025,7 @@ default_imp_for_incremental_authorization!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1145,6 +1153,7 @@ default_imp_for_create_customer!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1270,6 +1279,7 @@ default_imp_for_connector_redirect_response!( connectors::Adyenplatform, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Billwerk, connectors::Bitpay, connectors::Bamboraapac, @@ -1377,6 +1387,7 @@ default_imp_for_pre_processing_steps!( connectors::Adyenplatform, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1494,6 +1505,7 @@ default_imp_for_post_processing_steps!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1622,6 +1634,7 @@ default_imp_for_approve!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1751,6 +1764,7 @@ default_imp_for_reject!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1880,6 +1894,7 @@ default_imp_for_webhook_source_verification!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2008,6 +2023,7 @@ default_imp_for_accept_dispute!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2135,6 +2151,7 @@ default_imp_for_submit_evidence!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2261,6 +2278,7 @@ default_imp_for_defend_dispute!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2397,6 +2415,7 @@ default_imp_for_file_upload!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2515,6 +2534,7 @@ default_imp_for_payouts!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2637,6 +2657,7 @@ default_imp_for_payouts_create!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2763,6 +2784,7 @@ default_imp_for_payouts_retrieve!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2891,6 +2913,7 @@ default_imp_for_payouts_eligibility!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -3017,6 +3040,7 @@ default_imp_for_payouts_fulfill!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -3140,6 +3164,7 @@ default_imp_for_payouts_cancel!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -3267,6 +3292,7 @@ default_imp_for_payouts_quote!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -3395,6 +3421,7 @@ default_imp_for_payouts_recipient!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -3522,6 +3549,7 @@ default_imp_for_payouts_recipient_account!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -3651,6 +3679,7 @@ default_imp_for_frm_sale!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -3780,6 +3809,7 @@ default_imp_for_frm_checkout!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -3909,6 +3939,7 @@ default_imp_for_frm_transaction!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -4038,6 +4069,7 @@ default_imp_for_frm_fulfillment!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -4167,6 +4199,7 @@ default_imp_for_frm_record_return!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -4293,6 +4326,7 @@ default_imp_for_revoking_mandates!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -4417,6 +4451,7 @@ default_imp_for_uas_pre_authentication!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -4543,6 +4578,7 @@ default_imp_for_uas_post_authentication!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -4670,6 +4706,7 @@ default_imp_for_uas_authentication_confirmation!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -4788,6 +4825,7 @@ default_imp_for_connector_request_id!( connectors::Adyenplatform, connectors::Airwallex, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Amazonpay, connectors::Bambora, @@ -4910,6 +4948,7 @@ default_imp_for_fraud_check!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -5061,6 +5100,7 @@ default_imp_for_connector_authentication!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -5185,6 +5225,7 @@ default_imp_for_uas_authentication!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -5304,6 +5345,7 @@ default_imp_for_revenue_recovery!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -5434,6 +5476,7 @@ default_imp_for_billing_connector_payment_sync!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -5563,6 +5606,7 @@ default_imp_for_revenue_recovery_record_back!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -5690,6 +5734,7 @@ default_imp_for_billing_connector_invoice_sync!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -5812,6 +5857,7 @@ default_imp_for_external_vault!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Barclaycard, connectors::Bambora, @@ -5940,6 +5986,7 @@ default_imp_for_external_vault_insert!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Barclaycard, connectors::Bambora, @@ -6068,6 +6115,7 @@ default_imp_for_external_vault_retrieve!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Barclaycard, connectors::Bambora, @@ -6196,6 +6244,7 @@ default_imp_for_external_vault_delete!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Barclaycard, connectors::Bambora, @@ -6324,6 +6373,7 @@ default_imp_for_external_vault_create!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Barclaycard, connectors::Bambora, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 7bc66a1e6cf..89a7b3de2ac 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -253,6 +253,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Airwallex, connectors::Amazonpay, connectors::Adyenplatform, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -382,6 +383,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -506,6 +508,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -636,6 +639,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -764,6 +768,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -892,6 +897,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1031,6 +1037,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1162,6 +1169,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1293,6 +1301,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1424,6 +1433,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1555,6 +1565,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1686,6 +1697,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1817,6 +1829,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -1948,6 +1961,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2079,6 +2093,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2208,6 +2223,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2339,6 +2355,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2470,6 +2487,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2601,6 +2619,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2732,6 +2751,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2863,6 +2883,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -2991,6 +3012,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Adyenplatform, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Authorizedotnet, connectors::Bambora, connectors::Bamboraapac, @@ -3110,6 +3132,7 @@ default_imp_for_new_connector_integration_frm!( connectors::Vgs, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Bambora, connectors::Bamboraapac, connectors::Barclaycard, @@ -3238,6 +3261,7 @@ default_imp_for_new_connector_integration_connector_authentication!( connectors::Vgs, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Bambora, connectors::Bamboraapac, connectors::Barclaycard, @@ -3355,6 +3379,7 @@ default_imp_for_new_connector_integration_revenue_recovery!( connectors::Vgs, connectors::Airwallex, connectors::Amazonpay, + connectors::Authipay, connectors::Bambora, connectors::Bamboraapac, connectors::Barclaycard, @@ -3485,6 +3510,7 @@ default_imp_for_new_connector_integration_external_vault!( connectors::Airwallex, connectors::Amazonpay, connectors::Archipel, + connectors::Authipay, connectors::Authorizedotnet, connectors::Barclaycard, connectors::Bambora, diff --git a/crates/hyperswitch_domain_models/src/configs.rs b/crates/hyperswitch_domain_models/src/configs.rs index 9e64262f78f..9e4b94f9465 100644 --- a/crates/hyperswitch_domain_models/src/configs.rs +++ b/crates/hyperswitch_domain_models/src/configs.rs @@ -12,6 +12,7 @@ use crate::errors::api_error_response; #[serde(default)] pub struct Connectors { pub aci: ConnectorParams, + pub authipay: ConnectorParams, pub adyen: AdyenParamsWithThreeBaseUrls, pub adyenplatform: ConnectorParams, pub airwallex: ConnectorParams, diff --git a/crates/payment_methods/src/configs/payment_connector_required_fields.rs b/crates/payment_methods/src/configs/payment_connector_required_fields.rs index 08baca7e35f..ec3ea46ccd2 100644 --- a/crates/payment_methods/src/configs/payment_connector_required_fields.rs +++ b/crates/payment_methods/src/configs/payment_connector_required_fields.rs @@ -1195,6 +1195,7 @@ impl Default for RequiredFields { fn get_cards_required_fields() -> HashMap { HashMap::from([ (Connector::Aci, fields(vec![], vec![], card_with_name())), + (Connector::Authipay, fields(vec![], vec![], card_basic())), (Connector::Adyen, fields(vec![], vec![], card_with_name())), (Connector::Airwallex, fields(vec![], card_basic(), vec![])), ( diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index 619608a95de..d50086f5481 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -4,16 +4,16 @@ pub mod utils; pub use hyperswitch_connectors::connectors::DummyConnector; pub use hyperswitch_connectors::connectors::{ aci, aci::Aci, adyen, adyen::Adyen, adyenplatform, adyenplatform::Adyenplatform, airwallex, - airwallex::Airwallex, amazonpay, amazonpay::Amazonpay, archipel, archipel::Archipel, - authorizedotnet, authorizedotnet::Authorizedotnet, bambora, bambora::Bambora, bamboraapac, - bamboraapac::Bamboraapac, bankofamerica, bankofamerica::Bankofamerica, barclaycard, - barclaycard::Barclaycard, billwerk, billwerk::Billwerk, bitpay, bitpay::Bitpay, bluesnap, - bluesnap::Bluesnap, boku, boku::Boku, braintree, braintree::Braintree, cashtocode, - cashtocode::Cashtocode, celero, celero::Celero, chargebee, chargebee::Chargebee, checkbook, - checkbook::Checkbook, checkout, checkout::Checkout, coinbase, coinbase::Coinbase, coingate, - coingate::Coingate, cryptopay, cryptopay::Cryptopay, ctp_mastercard, - ctp_mastercard::CtpMastercard, cybersource, cybersource::Cybersource, datatrans, - datatrans::Datatrans, deutschebank, deutschebank::Deutschebank, digitalvirgo, + airwallex::Airwallex, amazonpay, amazonpay::Amazonpay, archipel, archipel::Archipel, authipay, + authipay::Authipay, authorizedotnet, authorizedotnet::Authorizedotnet, bambora, + bambora::Bambora, bamboraapac, bamboraapac::Bamboraapac, bankofamerica, + bankofamerica::Bankofamerica, barclaycard, barclaycard::Barclaycard, billwerk, + billwerk::Billwerk, bitpay, bitpay::Bitpay, bluesnap, bluesnap::Bluesnap, boku, boku::Boku, + braintree, braintree::Braintree, cashtocode, cashtocode::Cashtocode, celero, celero::Celero, + chargebee, chargebee::Chargebee, checkbook, checkbook::Checkbook, checkout, checkout::Checkout, + coinbase, coinbase::Coinbase, coingate, coingate::Coingate, cryptopay, cryptopay::Cryptopay, + ctp_mastercard, ctp_mastercard::CtpMastercard, cybersource, cybersource::Cybersource, + datatrans, datatrans::Datatrans, deutschebank, deutschebank::Deutschebank, digitalvirgo, digitalvirgo::Digitalvirgo, dlocal, dlocal::Dlocal, dwolla, dwolla::Dwolla, ebanx, ebanx::Ebanx, elavon, elavon::Elavon, facilitapay, facilitapay::Facilitapay, fiserv, fiserv::Fiserv, fiservemea, fiservemea::Fiservemea, fiuu, fiuu::Fiuu, forte, forte::Forte, diff --git a/crates/router/src/core/connector_validation.rs b/crates/router/src/core/connector_validation.rs index 655636197d8..6fb5a340ba2 100644 --- a/crates/router/src/core/connector_validation.rs +++ b/crates/router/src/core/connector_validation.rs @@ -89,6 +89,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { archipel::transformers::ArchipelConfigData::try_from(self.connector_meta_data)?; Ok(()) } + api_enums::Connector::Authipay => { + authipay::transformers::AuthipayAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Authorizedotnet => { authorizedotnet::transformers::AuthorizedotnetAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 45d6dd0d75e..eab04a9f0cc 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -328,6 +328,10 @@ impl ConnectorData { match enums::Connector::from_str(connector_name) { Ok(name) => match name { enums::Connector::Aci => Ok(ConnectorEnum::Old(Box::new(connector::Aci::new()))), + + enums::Connector::Authipay => { + Ok(ConnectorEnum::Old(Box::new(connector::Authipay::new()))) + } enums::Connector::Adyen => { Ok(ConnectorEnum::Old(Box::new(connector::Adyen::new()))) } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 713293568b3..35ba30da6a7 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -207,6 +207,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { fn foreign_try_from(from: api_enums::Connector) -> Result { Ok(match from { api_enums::Connector::Aci => Self::Aci, + api_enums::Connector::Authipay => Self::Authipay, api_enums::Connector::Adyen => Self::Adyen, api_enums::Connector::Adyenplatform => Self::Adyenplatform, api_enums::Connector::Airwallex => Self::Airwallex, diff --git a/crates/router/tests/connectors/authipay.rs b/crates/router/tests/connectors/authipay.rs new file mode 100644 index 00000000000..aa362166fdf --- /dev/null +++ b/crates/router/tests/connectors/authipay.rs @@ -0,0 +1,454 @@ +use std::str::FromStr; +use masking::Secret; +use router::{ + types::{self, api, storage::enums}, + core::errors, +}; +use cards; + +use crate::utils::{self, ConnectorActions}; +use test_utils::connector_auth; + +#[derive(Clone, Copy)] +struct AuthipayTest; +impl ConnectorActions for AuthipayTest {} +impl utils::Connector for AuthipayTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Authipay; + api::ConnectorData { + connector: Box::new(Authipay::new()), + connector_name: types::Connector::Authipay, + get_token: types::api::GetToken::Connector, + merchant_connector_id: None, + } + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .authipay + .expect("Missing connector authentication configuration").into(), + ) + } + + fn get_name(&self) -> String { + "authipay".to_string() + } +} + +static CONNECTOR: AuthipayTest = AuthipayTest {}; + +fn get_default_payment_info() -> Option { + Some(utils::PaymentInfo { + payment_id: utils::get_payment_id(), + payment_method: enums::PaymentMethod::Card, + payment_method_type: Some(enums::PaymentMethodType::Credit), + amount: 100, // $1.00 + currency: enums::Currency::USD, + capture_method: Some(enums::CaptureMethod::Manual), + mandate_id: None, + setup_future_usage: None, + off_session: None, + merchant_connector_id: None, + browser_info: None, + recurring_mandate_payment: None, + router_return_url: None, + connector_meta: None, + allowed_payment_method_types: None, + pm_token: None, + payment_experience: None, + webhook_url: None, + customer_id: None, + surcharge_details: None, + connector_request_reference_id: None, + request_incremental_authorization: None, + metadata: None, + payment_type: None, + session_token: None, + terminal_id: None, + card_network: None, + capture_on: None, + incremental_authorization_allowed: None, + }) +} + +fn payment_method_details() -> Option { + Some(types::PaymentsAuthorizeData { + payment_method_data: api::PaymentMethodData::Card(api::Card { + card_number: cards::CardNumber::from_str("4035874000424977").unwrap(), // Visa test card from Authipay specs + card_exp_month: Secret::new("12".to_string()), + card_exp_year: Secret::new("2024".to_string()), + card_cvc: Secret::new("123".to_string()), + card_holder_name: Secret::new("John Doe".to_string()), + card_issuer: None, + card_network: None, + card_type: None, + card_issuing_country: None, + bank_code: None, + nick_name: Some("Default Card".to_string()), + }), + currency: enums::Currency::USD, + amount: 100, // $1.00 + router_return_url: String::from("http://localhost:8080/"), + payment_method_type: Some(enums::PaymentMethodType::Credit), + ..utils::PaymentAuthorizeType::default().0 + }) +} + +// 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: 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: 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 scenarios +// 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: 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: 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/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index 7d5a742aaf5..a7f18afcb78 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -341,10 +341,16 @@ api_key = "MyApiKey" key1 = "Merchant id" api_secret = "Secret key" + +[authipay] +api_key = "MyApiKey" +api_secret = "MySecretKey" + [checkbook] api_key="Client ID" key1 ="Client Secret" + [santander] api_key="Client ID" key1 ="Client Secret" @@ -354,4 +360,5 @@ api_key="Client ID" key1="Client Secret" [payload] -api_key="API Key" \ No newline at end of file +api_key="API Key" + diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 25fffd09b0f..14e08be1623 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -19,6 +19,7 @@ pub struct ConnectorAuthentication { pub airwallex: Option, pub amazonpay: Option, pub archipel: Option, + pub authipay: Option, pub authorizedotnet: Option, pub bambora: Option, pub bamboraapac: Option, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Authipay.js b/cypress-tests/cypress/e2e/configs/Payment/Authipay.js new file mode 100644 index 00000000000..616e41d8ca4 --- /dev/null +++ b/cypress-tests/cypress/e2e/configs/Payment/Authipay.js @@ -0,0 +1,967 @@ +import { cardRequiredField, customerAcceptance } from "./Commons"; +import { getCustomExchange } from "./Modifiers"; + +// ============================================================================ +// AUTHIPAY CYPRESS TEST CONFIGURATION (SIMPLIFIED) +// ============================================================================ +// Core functionality only: +// - Non-3DS card payments (auto/manual capture) +// - Refund operations (full/partial/sync) +// - Basic failure scenarios +// ============================================================================ + +// ===== TEST CARD DATA ===== + +const successfulNo3DSCardDetails = { + card_number: "4147463011110083", // Authipay Mastercard test card + card_exp_month: "10", + card_exp_year: "30", + card_holder_name: "Test User", + card_cvc: "123", +}; + +const failedCardDetails = { + card_number: "4000000000000002", // Generic declined card + card_exp_month: "10", + card_exp_year: "30", + card_holder_name: "Test User", + card_cvc: "123", +}; + +// ===== MANDATE DATA ===== + +const singleUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + single_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + multi_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +// ===== BILLING INFORMATION ===== + +const billingAddress = { + address: { + line1: "1467", + line2: "Harrison Street", + city: "San Francisco", + state: "California", + zip: "94122", + country: "US", + first_name: "Test", + last_name: "User", + }, + phone: { + number: "9123456789", + country_code: "+1", + }, +}; + +// ===== PAYMENT METHOD DATA ===== + +const payment_method_data = { + card: { + last4: "0083", + card_type: null, + card_network: null, + card_issuer: null, + card_issuing_country: null, + card_isin: "414746", + card_extended_bin: null, + card_exp_month: "10", + card_exp_year: "30", + card_holder_name: "Test User", + payment_checks: null, + authentication_data: null, + }, + billing: null, +}; + +const payment_method_data_failed = { + card: { + last4: "0002", + card_type: null, + card_network: null, + card_issuer: null, + card_issuing_country: null, + card_isin: "400000", + card_extended_bin: null, + card_exp_month: "10", + card_exp_year: "30", + card_holder_name: "Test User", + payment_checks: null, + authentication_data: null, + }, + billing: null, +}; + +// ===== REQUIRED FIELDS ===== + +const requiredFields = { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["authipay"], + }, + ], + required_fields: cardRequiredField, + }, + ], + }, + ], +}; + +// ===== MAIN CONNECTOR DETAILS ===== + +export const connectorDetails = { + card_pm: { + // Basic payment intent + PaymentIntent: { + Request: { + currency: "USD", + customer_acceptance: null, + setup_future_usage: null, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: null, + }, + }, + }, + + // Successful automatic capture + No3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + billing: billingAddress, + currency: "USD", + customer_acceptance: null, + setup_future_usage: null, + capture_method: "automatic", + }, + Response: { + status: 200, + body: { + status: "succeeded", + payment_method: "card", + attempt_count: 1, + payment_method_data: payment_method_data, + }, + }, + }, + + // Successful manual capture (requires capture) + No3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + billing: billingAddress, + currency: "USD", + customer_acceptance: null, + setup_future_usage: null, + capture_method: "manual", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + payment_method: "card", + attempt_count: 1, + payment_method_data: payment_method_data, + }, + }, + }, + + // Failed payment + No3DSFailPayment: { + Request: { + payment_method: "card", + payment_method_data: { + card: failedCardDetails, + }, + billing: billingAddress, + customer_acceptance: null, + setup_future_usage: null, + }, + //Response as successful payment due to Authipay's failed payment cards not getting declined on connector side + Response: { + status: 200, + body: { + status: "succeeded", + payment_method: "card", + attempt_count: 1, + payment_method_data: payment_method_data_failed, + }, + }, + }, + + // Capture operation + Capture: { + Request: { + amount_to_capture: 6000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 6000, + amount_capturable: 0, + amount_received: 6000, + }, + }, + }, + + // Partial capture + PartialCapture: { + Request: { + amount_to_capture: 3000, + }, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 6000, + amount_capturable: 0, + amount_received: 3000, + }, + }, + }, + + // Full refund + Refund: { + Request: { + amount: 6000, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + + // Partial refund + PartialRefund: { + Request: { + amount: 2000, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + + // Refund sync + SyncRefund: { + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + + // Manual payment refund + manualPaymentRefund: { + Request: { + amount: 6000, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + + // Manual payment partial refund + manualPaymentPartialRefund: { + Request: { + amount: 3000, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + + // Void payment failure (trying to void already captured payment) + VoidPaymentFailure: { + Request: { + cancellation_reason: "merchant_request", + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request_error", + code: "processing_error", + message: + "You cannot cancel this PaymentIntent because it has a status of succeeded.", + }, + }, + }, + }, + + // Void after confirm (manual capture scenario) + // This should work for payments in "requires_capture" state + VoidAfterConfirm: { + Request: { + cancellation_reason: "customer_request", + }, + Response: { + status: 200, + body: { + status: "cancelled", + payment_method: "card", + attempt_count: 1, + }, + }, + }, + + // Void payment in early state (before payment confirmation) + Void: { + Request: { + cancellation_reason: "duplicate_transaction", + }, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + + // Payment sync + SyncPayment: { + Response: { + status: 200, + body: { + status: "succeeded", + payment_method: "card", + payment_method_data: payment_method_data, + }, + }, + }, + + // Payment with shipping cost + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6000, + }, + }, + }, + + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + billing: billingAddress, + customer_acceptance: null, + setup_future_usage: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + payment_method: "card", + payment_method_data: payment_method_data, + shipping_cost: 50, + }, + }, + }, + + // ===== MANDATE SCENARIOS ===== + // Note: Authipay may not implement mandates, marked as TRIGGER_SKIP + + MandateSingleUseNo3DSAutoCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support mandates + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 400, + body: {}, + }, + }), + + MandateSingleUseNo3DSManualCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support mandates + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }), + + MandateMultiUseNo3DSAutoCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support mandates + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }), + + MandateMultiUseNo3DSManualCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support mandates + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }), + + // ===== SAVE CARD SCENARIOS ===== + // Note: Authipay may not support save card, marked as TRIGGER_SKIP + + SaveCardUseNo3DSAutoCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support save card + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 400, + body: { + status: "succeeded", + }, + }, + }), + + SaveCardUseNo3DSManualCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support save card + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }), + + // ===== ZERO AUTH SCENARIOS ===== + // Note: Authipay may not support zero auth, marked as TRIGGER_SKIP + + ZeroAuthPaymentIntent: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support zero auth + }, + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + payment_type: "setup_mandate", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }), + + ZeroAuthConfirmPayment: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support zero auth + }, + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }), + + // ===== MIT (MERCHANT INITIATED TRANSACTION) SCENARIOS ===== + // Note: Authipay may not support MIT, marked as TRIGGER_SKIP + + MITAutoCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support MIT + }, + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }), + + MITWithoutBillingAddress: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support MIT + }, + Request: { + billing: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }), + + MandateSingleUse3DSAutoCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip - 3DS not implemented + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }), + + MandateSingleUse3DSManualCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip - 3DS not implemented + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }), + + MandateMultiUse3DSAutoCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip - 3DS not implemented + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }), + + MandateMultiUse3DSManualCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip - 3DS not implemented + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }), + + SaveCardUse3DSAutoCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip - 3DS not implemented + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }), + + SaveCardUse3DSManualCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip - 3DS not implemented + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }), + + // ===== OFF-SESSION SCENARIOS ===== + // Note: Authipay may not support off-session, marked as TRIGGER_SKIP + + PaymentIntentOffSession: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support off-session + }, + Request: { + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }), + + SaveCardUseNo3DSAutoCaptureOffSession: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support off-session + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }), + + SaveCardUse3DSAutoCaptureOffSession: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip - 3DS not implemented and off-session may not be supported + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }), + + SaveCardUseNo3DSManualCaptureOffSession: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support off-session + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }), + + SaveCardConfirmAutoCaptureOffSession: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support off-session + }, + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }), + + SaveCardConfirmManualCaptureOffSession: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support off-session + }, + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }), + + SaveCardConfirmAutoCaptureOffSessionWithoutBilling: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support off-session + }, + Request: { + setup_future_usage: "off_session", + billing: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + billing: null, + }, + }, + }), + + // ===== PAYMENT METHOD SCENARIOS ===== + // Note: These may not be supported by Authipay, marked as TRIGGER_SKIP + + PaymentMethod: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support payment method creation + }, + Request: { + payment_method: "card", + payment_method_type: "credit", + payment_method_issuer: "Gpay", + payment_method_issuer_code: "jp_hdfc", + card: successfulNo3DSCardDetails, + }, + Response: { + status: 200, + body: {}, + }, + }), + + PaymentMethodIdMandateNo3DSAutoCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support payment method ID mandates + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }), + + PaymentMethodIdMandateNo3DSManualCapture: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support payment method ID mandates + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }), + // ===== ZERO AUTH MANDATE SCENARIO ===== + // Note: User specifically requested this to be skipped + + ZeroAuthMandate: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip as requested by user + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }), + + // ===== SESSION TOKEN SCENARIO ===== + // Note: Authipay may not support session tokens, marked as TRIGGER_SKIP + + SessionToken: getCustomExchange({ + Configs: { + TRIGGER_SKIP: true, // Skip if Authipay doesn't support session tokens + }, + Response: { + status: 200, + body: { + session_token: [], + }, + }, + }), + }, + + pm_list: { + PmListResponse: { + PmListNull: { + payment_methods: [], + }, + pmListDynamicFieldWithoutBilling: requiredFields, + pmListDynamicFieldWithBilling: requiredFields, + pmListDynamicFieldWithNames: requiredFields, + pmListDynamicFieldWithEmail: requiredFields, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/configs/Payment/Utils.js b/cypress-tests/cypress/e2e/configs/Payment/Utils.js index a39d336bc51..84ae4c998b9 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Utils.js @@ -6,6 +6,7 @@ import { connectorDetails as aciConnectorDetails } from "./Aci.js"; import { connectorDetails as adyenConnectorDetails } from "./Adyen.js"; import { connectorDetails as airwallexConnectorDetails } from "./Airwallex.js"; import { connectorDetails as archipelConnectorDetails } from "./Archipel.js"; +import { connectorDetails as authipayConnectorDetails } from "./Authipay.js"; import { connectorDetails as bamboraConnectorDetails } from "./Bambora.js"; import { connectorDetails as bamboraapacConnectorDetails } from "./Bamboraapac.js"; import { connectorDetails as bankOfAmericaConnectorDetails } from "./BankOfAmerica.js"; @@ -58,6 +59,7 @@ const connectorDetails = { adyen: adyenConnectorDetails, airwallex: airwallexConnectorDetails, archipel: archipelConnectorDetails, + authipay: authipayConnectorDetails, bambora: bamboraConnectorDetails, bamboraapac: bamboraapacConnectorDetails, bankofamerica: bankOfAmericaConnectorDetails, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index b1c7a7581c4..4711699bdf9 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -82,6 +82,7 @@ airwallex.base_url = "https://api-demo.airwallex.com/" amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" archipel.base_url = "https://{{merchant_endpoint_prefix}}/ArchiPEL/Transaction/v1" +authipay.base_url = "https://prod.emea.api.fiservapps.com/sandbox/ipp/payments-gateway/v2/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" bamboraapac.base_url = "https://demo.ippayments.com.au/interface/api" @@ -217,6 +218,7 @@ cards = [ "airwallex", "amazonpay", "archipel", + "authipay", "authorizedotnet", "bambora", "bamboraapac", diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 90424011957..98490f12324 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 adyenplatform airwallex amazonpay applepay archipel authorizedotnet bambora bamboraapac bankofamerica barclaycard billwerk bitpay bluesnap boku braintree cashtocode celero chargebee checkbook checkout coinbase cryptopay ctp_visa cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector dwolla ebanx elavon facilitapay fiserv fiservemea fiuu forte getnet globalpay globepay gocardless gpayments helcim hipay hyperswitch_vault iatapay inespay itaubank jpmorgan juspaythreedsserver klarna mifinity mollie moneris multisafepay netcetera nexinets nexixpay nomupay noon nordea novalnet nuvei opayo opennode paybox payeezy payload payme payone paypal paystack payu placetopay plaid powertranz prophetpay rapyd razorpay recurly redsys santander shift4 silverflow square stax stripe stripebilling taxjar threedsecureio thunes tokenio trustpay tsys unified_authentication_service vgs volt wellsfargo wellsfargopayout wise worldline worldpay worldpayvantiv worldpayxml xendit zsl "$1") + connectors=(aci adyen adyenplatform airwallex amazonpay applepay archipel authipay authorizedotnet bambora bamboraapac bankofamerica barclaycard billwerk bitpay bluesnap boku braintree cashtocode celero chargebee checkbook checkout coinbase cryptopay ctp_visa cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector dwolla ebanx elavon facilitapay fiserv fiservemea fiuu forte getnet globalpay globepay gocardless gpayments helcim hipay hyperswitch_vault iatapay inespay itaubank jpmorgan juspaythreedsserver klarna mifinity mollie moneris multisafepay netcetera nexinets nexixpay nomupay noon nordea novalnet nuvei opayo opennode paybox payeezy payload payme payone paypal paystack payu placetopay plaid powertranz prophetpay rapyd razorpay recurly redsys santander shift4 silverflow square stax stripe stripebilling taxjar threedsecureio thunes tokenio trustpay tsys unified_authentication_service vgs volt wellsfargo wellsfargopayout wise worldline worldpay worldpayvantiv worldpayxml xendit zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res="$(echo ${sorted[@]})"