From 2ff93ff972453ce30ffeff667cb406d35286f60a Mon Sep 17 00:00:00 2001 From: sweta-sharma <77436883+swetasharma03@users.noreply.github.com> Date: Tue, 1 Jul 2025 19:34:18 +0530 Subject: [PATCH] refactor(connector): update add connector script with new connector features (#8213) --- connector-template/mod.rs | 76 +++++++++- connector-template/test.rs | 21 +-- connector-template/transformers.rs | 29 ++-- .../src/default_implementations.rs | 4 +- scripts/add_connector.sh | 136 +++++++++++++++--- 5 files changed, 209 insertions(+), 57 deletions(-) diff --git a/connector-template/mod.rs b/connector-template/mod.rs index ee5f4872d6..09ccdf9544 100644 --- a/connector-template/mod.rs +++ b/connector-template/mod.rs @@ -39,11 +39,17 @@ use hyperswitch_interfaces::{ types::{self, Response}, webhooks, }; +use std::sync::LazyLock; + +use common_enums::enums; +use hyperswitch_interfaces::api::ConnectorSpecifications; +use hyperswitch_domain_models::router_response_types::{ConnectorInfo, SupportedPaymentMethods}; use crate::{ constants::headers, types::ResponseRouterData, utils, }; +use hyperswitch_domain_models::payment_method_data::PaymentMethodData; use transformers as {{project-name | downcase}}; @@ -147,13 +153,39 @@ impl ConnectorCommon for {{project-name | downcase | pascal_case}} { reason: response.reason, attempt_status: None, connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, }) } } + impl ConnectorValidation for {{project-name | downcase | pascal_case}} { - //TODO: implement functions when support enabled + fn validate_mandate_payment( + &self, + _pm_type: Option, + pm_data: PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + match pm_data { + PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented( + "validate_mandate_payment does not support cards".to_string(), + ) + .into()), + _ => Ok(()), + } + } + + fn validate_psync_reference_id( + &self, + _data: &PaymentsSyncData, + _is_three_ds: bool, + _status: enums::AttemptStatus, + _connector_meta_data: Option, + ) -> CustomResult<(), errors::ConnectorError> { + Ok(()) + } } impl @@ -194,7 +226,10 @@ impl self.common_get_content_type() } - fn get_url(&self, _req: &PaymentsAuthorizeRouterData, _connectors: &Connectors,) -> CustomResult { + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors,) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } @@ -425,7 +460,10 @@ impl self.common_get_content_type() } - fn get_url(&self, _req: &RefundsRouterData, _connectors: &Connectors,) -> CustomResult { + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors,) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } @@ -487,7 +525,9 @@ impl self.common_get_content_type() } - fn get_url(&self, _req: &RefundSyncRouterData,_connectors: &Connectors,) -> CustomResult { + fn get_url( + &self, + _req: &RefundSyncRouterData,_connectors: &Connectors,) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } @@ -552,5 +592,29 @@ impl webhooks::IncomingWebhook for {{project-name | downcase | pascal_case}} { } } -impl ConnectorSpecifications for {{project-name | downcase | pascal_case}} {} - +static {{project-name | upcase}}_SUPPORTED_PAYMENT_METHODS: LazyLock = + LazyLock::new(|| { + SupportedPaymentMethods::new() + }); + +static {{project-name | upcase}}_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + display_name: "{{project-name | downcase | pascal_case}}", + description: "{{project-name | downcase | pascal_case}} connector", + connector_type: enums::PaymentConnectorCategory::PaymentGateway, +}; + +static {{project-name | upcase}}_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = []; + +impl ConnectorSpecifications for {{project-name | downcase | pascal_case}} { + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + Some(&{{project-name | upcase}}_CONNECTOR_INFO) + } + + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + Some(&*{{project-name | upcase}}_SUPPORTED_PAYMENT_METHODS) + } + + fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { + Some(&{{project-name | upcase}}_SUPPORTED_WEBHOOK_FLOWS) + } +} diff --git a/connector-template/test.rs b/connector-template/test.rs index 5b5ba6a138..126f6cdd2d 100644 --- a/connector-template/test.rs +++ b/connector-template/test.rs @@ -1,3 +1,4 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; use masking::Secret; use router::{ types::{self, api, storage::enums, @@ -12,13 +13,13 @@ impl ConnectorActions for {{project-name | downcase | pascal_case}}Test {} impl utils::Connector for {{project-name | downcase | pascal_case}}Test { fn get_data(&self) -> api::ConnectorData { use router::connector::{{project-name | downcase | pascal_case}}; - api::ConnectorData { - connector: Box::new({{project-name | downcase | pascal_case}}::new()), - connector_name: types::Connector::{{project-name | downcase | pascal_case}}, - get_token: types::api::GetToken::Connector, - merchant_connector_id: None, - } - } + utils::construct_connector_data_old( + Box::new({{project-name | downcase | pascal_case}}::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } fn get_auth_token(&self) -> types::ConnectorAuthType { utils::to_connector_auth_type( @@ -287,7 +288,7 @@ 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 { + payment_method_data: PaymentMethodData::Card(Card { card_cvc: Secret::new("12345".to_string()), ..utils::CCardType::default().0 }), @@ -309,7 +310,7 @@ 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 { + payment_method_data: PaymentMethodData::Card(Card { card_exp_month: Secret::new("20".to_string()), ..utils::CCardType::default().0 }), @@ -331,7 +332,7 @@ 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 { + payment_method_data: PaymentMethodData::Card(Card { card_exp_year: Secret::new("2000".to_string()), ..utils::CCardType::default().0 }), diff --git a/connector-template/transformers.rs b/connector-template/transformers.rs index 73e167d89f..49424238f3 100644 --- a/connector-template/transformers.rs +++ b/connector-template/transformers.rs @@ -11,10 +11,7 @@ use hyperswitch_domain_models::{ types::{PaymentsAuthorizeRouterData, RefundsRouterData}, }; use hyperswitch_interfaces::errors; -use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, -}; +use crate::types::{RefundsResponseRouterData, ResponseRouterData}; //TODO: Fill the struct with respective fields pub struct {{project-name | downcase | pascal_case}}RouterData { @@ -62,21 +59,12 @@ impl TryFrom<&{{project-name | downcase | pascal_case}}RouterData<&PaymentsAutho type Error = error_stack::Report; fn try_from(item: &{{project-name | downcase | pascal_case}}RouterData<&PaymentsAuthorizeRouterData>) -> Result { match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = {{project-name | downcase | pascal_case}}Card { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; - Ok(Self { - amount: item.amount.clone(), - card, - }) - } + PaymentMethodData::Card(_) => { + Err(errors::ConnectorError::NotImplemented("Card payment method not implemented".to_string()).into()) + }, _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), } + } } @@ -99,7 +87,7 @@ impl TryFrom<&ConnectorAuthType> for {{project-name | downcase | pascal_case}}Au } // PaymentsResponse //TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum {{project-name | downcase | pascal_case}}PaymentStatus { Succeeded, @@ -165,7 +153,7 @@ impl TryFrom<&{{project-name | downcase | pascal_case}}RouterData<&RefundsRou // Type definition for Refund Response #[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] +#[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)] pub enum RefundStatus { Succeeded, Failed, @@ -229,4 +217,7 @@ pub struct {{project-name | downcase | pascal_case}}ErrorResponse { pub code: String, pub message: String, pub reason: Option, + pub network_advice_code: Option, + pub network_decline_code: Option, + pub network_error_message: Option, } diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 04871a1751..6717a25d2a 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -5132,7 +5132,7 @@ macro_rules! default_imp_for_revenue_recovery { }; } -default_imp_for_revenue_recovery! { +default_imp_for_revenue_recovery!( connectors::Vgs, connectors::Aci, connectors::Adyen, @@ -5240,7 +5240,7 @@ default_imp_for_revenue_recovery! { connectors::Xendit, connectors::Zen, connectors::Zsl -} +); #[cfg(all(feature = "v2", feature = "revenue_recovery"))] macro_rules! default_imp_for_billing_connector_payment_sync { diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index e0a2391051..a8fbbb15af 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -54,40 +54,134 @@ previous_connector_camelcase="$(tr '[:lower:]' '[:upper:]' <<< ${previous_connec sed -i'' -e "s|pub mod $previous_connector;|pub mod $previous_connector;\npub mod ${payment_gateway};|" $conn.rs sed -i'' -e "s/};/ ${payment_gateway}::${payment_gateway_camelcase},\n};/" $conn.rs sed -i'' -e "/pub use hyperswitch_connectors::connectors::{/ s/{/{\n ${payment_gateway}, ${payment_gateway}::${payment_gateway_camelcase},/" $src/connector.rs -sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tenums::Connector::${payment_gateway_camelcase} => Ok(ConnectorEnum::Old(\Box::new(\connector::${payment_gateway_camelcase}))),|" $src/types/api.rs sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tRoutableConnectors::${payment_gateway_camelcase} => euclid_enums::Connector::${payment_gateway_camelcase},|" crates/api_models/src/routing.rs sed -i'' -e "s/pub $previous_connector: \(.*\)/pub $previous_connector: \1\n\tpub ${payment_gateway}: ConnectorParams,/" crates/hyperswitch_interfaces/src/configs.rs sed -i'' -e "s|$previous_connector.base_url \(.*\)|$previous_connector.base_url \1\n${payment_gateway}.base_url = \"$base_url\"|" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml config/deployments/integration_test.toml config/deployments/production.toml config/deployments/sandbox.toml -sed -r -i'' -e "s/\"$previous_connector\",/\"$previous_connector\",\n \"${payment_gateway}\",/" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml sed -i '' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/api_models/src/connector_enums.rs sed -i '' -e "/\/\/ Add Separate authentication support for connectors/{N;s/\(.*\)\n/\1\n\t\t\t| Self::${payment_gateway_camelcase}\n/;}" crates/api_models/src/connector_enums.rs sed -i '' -e "s/\(match connector_name {\)/\1\n\t\tapi_enums::Connector::${payment_gateway_camelcase} => {${payment_gateway}::transformers::${payment_gateway_camelcase}AuthType::try_from(val)?;Ok(())}/" $src/core/admin.rs -sed -i'' -e "s/\(pub enum RoutableConnectors {\)/\1\n\t${payment_gateway_camelcase},/" crates/common_enums/src/connector_enums.rs sed -i '' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/euclid/src/enums.rs -sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tapi_enums::Connector::${payment_gateway_camelcase} => Self::${payment_gateway_camelcase},|" $src/types/transformers.rs -sed -i'' -e "s/^default_imp_for_\(.*\)/default_imp_for_\1\n\tconnectors::${payment_gateway_camelcase},/" crates/hyperswitch_connectors/src/default_implementations.rs -sed -i'' -e "s/^default_imp_for_\(.*\)/default_imp_for_\1\n\tconnectors::${payment_gateway_camelcase},/" crates/hyperswitch_connectors/src/default_implementations_v2.rs + +default_impl_files=( + "crates/hyperswitch_connectors/src/default_implementations.rs" + "crates/hyperswitch_connectors/src/default_implementations_v2.rs" +) + +# Inserts the new connector into macro blocks in default_implementations files. +# - If previous_connector exists in a macro, new_connector is added after it (maintaining logical order). +# - If previous_connector is missing, new_connector is added at the top of the macro block. +# - Ensures no duplicate entries and handles all default_imp macro variants. +# Iterate through all files where default implementations are defined +for file in "${default_impl_files[@]}"; do + tmpfile="${file}.tmp" + + # Use AWK to parse and update macro blocks for connector registration + awk -v prev="$previous_connector_camelcase" -v new="$payment_gateway_camelcase" ' + BEGIN { in_macro = 0 } + + { + if ($0 ~ /^default_imp_for_.*!\s*[\({]$/) { + in_macro = 1 + inserted = 0 + found_prev = 0 + found_new = 0 + macro_lines_count = 0 + delete macro_lines + + macro_header = $0 + macro_open = ($0 ~ /\{$/) ? "{" : "(" + macro_close = (macro_open == "{") ? "}" : ");" + next + } + + if (in_macro) { + if ((macro_close == "}" && $0 ~ /^[[:space:]]*}[[:space:]]*$/) || + (macro_close == ");" && $0 ~ /^[[:space:]]*\);[[:space:]]*$/)) { + + for (i = 1; i <= macro_lines_count; i++) { + line = macro_lines[i] + clean = line + gsub(/^[ \t]+/, "", clean) + gsub(/[ \t]+$/, "", clean) + if (clean == "connectors::" prev ",") found_prev = 1 + if (clean == "connectors::" new ",") found_new = 1 + } + + print macro_header + + if (!found_prev && !found_new) { + print " connectors::" new "," + inserted = 1 + } + + for (i = 1; i <= macro_lines_count; i++) { + line = macro_lines[i] + clean = line + gsub(/^[ \t]+/, "", clean) + gsub(/[ \t]+$/, "", clean) + + print " " clean + + if (!inserted && clean == "connectors::" prev ",") { + if (!found_new) { + print " connectors::" new "," + inserted = 1 + } + } + } + + print $0 + in_macro = 0 + next + } + + macro_lines[++macro_lines_count] = $0 + next + } + + print $0 + }' "$file" > "$tmpfile" && mv "$tmpfile" "$file" +done + + +sed -i '' -e "\$a\\ +\\ +[${payment_gateway}]\\ +[${payment_gateway}.connector_auth.HeaderKey]\\ +api_key = \\\"API Key\\\"" crates/connector_configs/toml/sandbox.toml + +sed -i '' -e "\$a\\ +\\ +[${payment_gateway}]\\ +[${payment_gateway}.connector_auth.HeaderKey]\\ +api_key = \\\"API Key\\\"" crates/connector_configs/toml/development.toml + +sed -i '' -e "\$a\\ +\\ +[${payment_gateway}]\\ +[${payment_gateway}.connector_auth.HeaderKey]\\ +api_key = \\\"API Key\\\"" crates/connector_configs/toml/production.toml + sed -i'' -e "s/^default_imp_for_connector_request_id!(/default_imp_for_connector_request_id!(\n connectors::${payment_gateway_camelcase},/" $src/core/payments/flows.rs sed -i'' -e "s/^default_imp_for_fraud_check!(/default_imp_for_fraud_check!(\n connectors::${payment_gateway_camelcase},/" $src/core/payments/flows.rs sed -i'' -e "s/^default_imp_for_connector_authentication!(/default_imp_for_connector_authentication!(\n connectors::${payment_gateway_camelcase},/" $src/core/payments/flows.rs -sed -i'' -e "/pub struct ConnectorConfig {/ s/{/{\n pub ${payment_gateway}: Option,/" crates/connector_configs/src/connector.rs +sed -i'' -e "/pub ${previous_connector}: Option,/a\\ + pub ${payment_gateway}: Option, +" crates/connector_configs/src/connector.rs + sed -i'' -e "/mod utils;/ s/mod utils;/mod ${payment_gateway};\nmod utils;/" crates/router/tests/connectors/main.rs sed -i'' -e "s/^default_imp_for_new_connector_integration_payouts!(/default_imp_for_new_connector_integration_payouts!(\n connector::${payment_gateway_camelcase},/" crates/router/src/core/payments/connector_integration_v2_impls.rs sed -i'' -e "s/^default_imp_for_new_connector_integration_frm!(/default_imp_for_new_connector_integration_frm!(\n connector::${payment_gateway_camelcase},/" crates/router/src/core/payments/connector_integration_v2_impls.rs sed -i'' -e "s/^default_imp_for_new_connector_integration_connector_authentication!(/default_imp_for_new_connector_integration_connector_authentication!(\n connector::${payment_gateway_camelcase},/" crates/router/src/core/payments/connector_integration_v2_impls.rs -sed -i'' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/common_enums/src/connector_enums.rs -sed -i'' -e "/match self {/ s/match self {/match self {\n | Self::${payment_gateway_camelcase}/" crates/common_enums/src/connector_enums.rs -sed -i'' -e "/match routable_connector {/ s/match routable_connector {/match routable_connector {\n RoutableConnectors::${payment_gateway_camelcase} => Self::${payment_gateway_camelcase},/" crates/common_enums/src/connector_enums.rs -sed -i'' -e "/match self.connector_name {/a\\ - api_enums::Connector::${payment_gateway_camelcase} => {\\ - ${payment_gateway}::transformers::${payment_gateway_camelcase}AuthType::try_from(self.auth_type)?;\\ - Ok(())\\ - },\\ -" crates/router/src/core/admin.rs + +sed -i'' -e "/pub ${previous_connector}: ConnectorParams,/a\\ + pub ${payment_gateway}: ConnectorParams, +" crates/hyperswitch_domain_models/src/configs.rs # Remove temporary files created in above step -rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/development.toml-e config/docker_compose.toml-e config/config.example.toml-e loadtest/config/development.toml-e crates/api_models/src/connector_enums.rs-e crates/euclid/src/enums.rs-e crates/api_models/src/routing.rs-e $src/core/payments/flows.rs-e crates/common_enums/src/connector_enums.rs-e $src/types/transformers.rs-e $src/core/admin.rs-e crates/hyperswitch_connectors/src/default_implementations.rs-e crates/hyperswitch_connectors/src/default_implementations_v2.rs-e crates/hyperswitch_interfaces/src/configs.rs-e $src/connector.rs-e config/deployments/integration_test.toml-e config/deployments/production.toml-e config/deployments/sandbox.toml-e temp crates/connector_configs/src/connector.rs-e crates/router/tests/connectors/main.rs-e crates/router/src/core/payments/connector_integration_v2_impls.rs-e +rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/development.toml-e config/docker_compose.toml-e config/config.example.toml-e loadtest/config/development.toml-e crates/api_models/src/connector_enums.rs-e crates/euclid/src/enums.rs-e crates/api_models/src/routing.rs-e $src/core/payments/flows.rs-e crates/common_enums/src/connector_enums.rs-e $src/types/transformers.rs-e $src/core/admin.rs-e crates/hyperswitch_connectors/src/default_implementations.rs-e crates/hyperswitch_connectors/src/default_implementations_v2.rs-e crates/hyperswitch_interfaces/src/configs.rs-e $src/connector.rs-e config/deployments/integration_test.toml-e config/deployments/production.toml-e config/deployments/sandbox.toml-e temp crates/connector_configs/src/connector.rs-e crates/router/tests/connectors/main.rs-e crates/router/src/core/payments/connector_integration_v2_impls.rs-e crates/hyperswitch_domain_models/src/configs.rs-e + cd $conn/ # Generate template files for the connector @@ -96,7 +190,9 @@ cargo generate --path ../../../../connector-template -n $payment_gateway # Move sub files and test files to appropriate folder mv $payment_gateway/mod.rs $payment_gateway.rs -mv $payment_gateway/test.rs ${tests}/$payment_gateway.rs +mkdir -p ../../../router/tests/connectors +mv "$payment_gateway/test.rs" ../../../router/tests/connectors/$payment_gateway.rs + # Remove changes from tests if already done for this connector git checkout ${tests}/main.rs ${test_utils}/connector_auth.rs ${tests}/sample_auth.toml @@ -109,9 +205,9 @@ echo "\n\n[${payment_gateway}]\napi_key=\"API Key\"" >> ${tests}/sample_auth.tom # Remove temporary files created in above step rm ${tests}/main.rs-e ${test_utils}/connector_auth.rs-e cargo +nightly fmt --all -cargo check +cargo check --features v1 echo "${GREEN}Successfully created connector. Running the tests of $payment_gateway.rs" # Runs tests for the new connector cargo test --package router --test connectors -- $payment_gateway -echo "${ORANGE}Update your credentials for $payment_gateway connector in crates/router/tests/connectors/sample_auth.toml" +echo "${ORANGE}Update your credentials for $payment_gateway connector in crates/router/tests/connectors/sample_auth.toml" \ No newline at end of file