From d32c61a25653d46a3d40adb8a6eff2fbbdbb608d Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Fri, 6 Jun 2025 17:31:08 +0530 Subject: [PATCH] feat(payment_methods): add `external_vault_details` for payments v2 sdk session call (#8003) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 94 ++++ api-reference/openapi_spec.json | 1 + config/config.example.toml | 4 +- config/deployments/integration_test.toml | 3 +- config/deployments/production.toml | 3 +- config/deployments/sandbox.toml | 3 +- config/development.toml | 4 +- config/docker_compose.toml | 4 +- crates/api_models/src/admin.rs | 4 + crates/api_models/src/enums.rs | 2 + crates/api_models/src/payments.rs | 34 ++ crates/common_enums/src/connector_enums.rs | 3 + crates/common_enums/src/enums.rs | 20 + crates/connector_configs/src/connector.rs | 3 + crates/diesel_models/src/business_profile.rs | 3 +- .../hyperswitch_connectors/src/connectors.rs | 13 +- .../connectors/facilitapay/transformers.rs | 40 +- .../src/connectors/hyperswitch_vault.rs | 491 ++++++++++++++++++ .../hyperswitch_vault/transformers.rs | 130 +++++ .../src/connectors/vgs/transformers.rs | 2 +- .../hyperswitch_connectors/src/constants.rs | 1 + .../src/default_implementations.rs | 177 ++++++- .../src/default_implementations_v2.rs | 40 +- .../src/business_profile.rs | 5 + .../hyperswitch_domain_models/src/configs.rs | 1 + .../hyperswitch_domain_models/src/payments.rs | 4 +- .../src/router_flow_types/vault.rs | 3 + .../src/router_request_types.rs | 31 +- .../src/router_response_types.rs | 4 + .../hyperswitch_interfaces/src/api/vault.rs | 19 +- .../src/api/vault_v2.rs | 24 +- crates/hyperswitch_interfaces/src/types.rs | 22 +- crates/openapi/src/openapi_v2.rs | 4 + crates/router/src/connector.rs | 43 +- crates/router/src/core/admin.rs | 6 + crates/router/src/core/payment_methods.rs | 4 +- .../router/src/core/payment_methods/vault.rs | 8 +- crates/router/src/core/payments.rs | 77 ++- crates/router/src/core/payments/flows.rs | 18 +- .../src/core/payments/flows/session_flow.rs | 16 +- crates/router/src/core/payments/helpers.rs | 121 +++++ .../operations/payment_create_intent.rs | 2 + .../payments/operations/payment_get_intent.rs | 2 + .../operations/payment_session_intent.rs | 6 +- .../operations/payment_update_intent.rs | 2 + .../src/core/payments/session_operation.rs | 43 +- .../router/src/core/payments/transformers.rs | 26 +- .../router/src/core/payments/vault_session.rs | 385 ++++++++++++++ crates/router/src/core/utils.rs | 4 + crates/router/src/types/api.rs | 3 + crates/router/src/types/api/payments.rs | 10 +- crates/router/src/types/transformers.rs | 7 + .../tests/connectors/hyperswitch_vault.rs | 421 +++++++++++++++ crates/router/tests/connectors/main.rs | 1 + crates/router/tests/connectors/utils.rs | 4 +- crates/test_utils/src/connector_auth.rs | 3 +- loadtest/config/development.toml | 4 +- scripts/add_connector.sh | 2 +- 58 files changed, 2311 insertions(+), 103 deletions(-) create mode 100644 crates/hyperswitch_connectors/src/connectors/hyperswitch_vault.rs create mode 100644 crates/hyperswitch_connectors/src/connectors/hyperswitch_vault/transformers.rs create mode 100644 crates/router/src/core/payments/vault_session.rs create mode 100644 crates/router/tests/connectors/hyperswitch_vault.rs diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index c68771b717..26d482bba9 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -8110,6 +8110,7 @@ "gpayments", "hipay", "helcim", + "hyperswitch_vault", "inespay", "iatapay", "itaubank", @@ -10712,6 +10713,14 @@ "type": "string", "description": "Merchant Connector id to be stored for vault connector", "nullable": true + }, + "vault_sdk": { + "allOf": [ + { + "$ref": "#/components/schemas/VaultSdk" + } + ], + "nullable": true } } }, @@ -12210,6 +12219,33 @@ } } }, + "HyperswitchVaultSessionDetails": { + "type": "object", + "required": [ + "payment_method_session_id", + "client_secret", + "publishable_key", + "profile_id" + ], + "properties": { + "payment_method_session_id": { + "type": "string", + "description": "Session ID for Hyperswitch Vault" + }, + "client_secret": { + "type": "string", + "description": "Client secret for Hyperswitch Vault" + }, + "publishable_key": { + "type": "string", + "description": "Publishable key for Hyperswitch Vault" + }, + "profile_id": { + "type": "string", + "description": "Profile ID for Hyperswitch Vault" + } + } + }, "IfStatement": { "type": "object", "description": "Represents an IF statement with conditions and optional nested IF statements\n\n```text\npayment.method = card {\npayment.method.cardtype = (credit, debit) {\npayment.method.network = (amex, rupay, diners)\n}\n}\n```", @@ -18824,6 +18860,14 @@ "$ref": "#/components/schemas/SessionToken" }, "description": "The list of session token object" + }, + "vault_details": { + "allOf": [ + { + "$ref": "#/components/schemas/VaultSessionDetails" + } + ], + "nullable": true } } }, @@ -24333,6 +24377,39 @@ "propertyName": "type" } }, + "VaultSdk": { + "type": "string", + "enum": [ + "vgs_sdk", + "hyperswitch_sdk" + ] + }, + "VaultSessionDetails": { + "oneOf": [ + { + "type": "object", + "required": [ + "vgs" + ], + "properties": { + "vgs": { + "$ref": "#/components/schemas/VgsSessionDetails" + } + } + }, + { + "type": "object", + "required": [ + "hyperswitch_vault" + ], + "properties": { + "hyperswitch_vault": { + "$ref": "#/components/schemas/HyperswitchVaultSessionDetails" + } + } + } + ] + }, "Venmo": { "type": "object", "required": [ @@ -24358,6 +24435,23 @@ } } }, + "VgsSessionDetails": { + "type": "object", + "required": [ + "external_vault_id", + "sdk_env" + ], + "properties": { + "external_vault_id": { + "type": "string", + "description": "The identifier of the external vault" + }, + "sdk_env": { + "type": "string", + "description": "The environment for the external vault initiation" + } + } + }, "VoucherData": { "oneOf": [ { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index bf997d7fed..826f778a06 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9973,6 +9973,7 @@ "gpayments", "hipay", "helcim", + "hyperswitch_vault", "inespay", "iatapay", "itaubank", diff --git a/config/config.example.toml b/config/config.example.toml index 3ce1e02f00..10a9e0e052 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -230,6 +230,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" hipay.base_url = "https://stage-secure-gateway.hipay-tpp.com/rest/" +hyperswitch_vault.base_url = "https://integ-api.hyperswitch.io" hipay.secondary_base_url = "https://stage-secure2-vault.hipay-tpp.com/rest/" hipay.third_base_url = "https://stage-api-gateway.hipay.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" @@ -352,6 +353,7 @@ cards = [ "gpayments", "helcim", "hipay", + "hyperswitch_vault", "mollie", "moneris", "paypal", @@ -822,7 +824,7 @@ credit = {country = "AU,NZ,CN,HK,IN,LK,KR,MY,SG,GB,BE,FR,DE,IT,ME,NL,PL,ES,ZA,AR debit = {country = "AU,NZ,CN,HK,IN,LK,KR,MY,SG,GB,BE,FR,DE,IT,ME,NL,PL,ES,ZA,AR,BR,CO,MX,PA,UY,US,CA", currency = "AFN,ALL,DZD,AOA,ARS,AMD,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BYN,BZD,BMD,BTN,BOB,VES,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XAF,CLP,CNY,COP,KMF,CDF,CRC,HRK,CUP,CZK,DKK,DJF,DOP,XCD,EGP,ERN,ETB,EUR,FKP,FJD,XPF,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KGS,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,ANG,NZD,NIO,NGN,VUV,KPW,NOK,OMR,PKR,PAB,PGK,PYG,PEN,PHP,PLN,GBP,QAR,RON,RUB,RWF,SHP,SVC,WST,STN,SAR,RSD,SCR,SLL,SGD,SBD,SOS,ZAR,KRW,SSP,LKR,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,UGX,UAH,AED,USD,UYU,UZS,VND,XOF,YER,ZMW,ZWL"} [connector_customer] -connector_list = "gocardless,stax,stripe,facilitapay" +connector_list = "gocardless,stax,stripe,facilitapay,hyperswitch_vault" payout_connector_list = "nomupay,stripe,wise" [bank_config.online_banking_fpx] diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 812dca1275..30c99e9f1b 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -69,6 +69,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" hipay.base_url = "https://stage-secure-gateway.hipay-tpp.com/rest/" +hyperswitch_vault.base_url = "https://integ-api.hyperswitch.io" hipay.secondary_base_url = "https://stage-secure2-vault.hipay-tpp.com/rest/" hipay.third_base_url = "https://stage-api-gateway.hipay.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" @@ -173,7 +174,7 @@ force_cookies = true enabled = true [connector_customer] -connector_list = "gocardless,stax,stripe,facilitapay" +connector_list = "gocardless,stax,stripe,facilitapay,hyperswitch_vault" payout_connector_list = "nomupay,stripe,wise" [delayed_session_response] diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 72ca305629..33a787d86c 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -15,7 +15,7 @@ open_banking_uk.adyen.banks = "aib,bank_of_scotland,danske_bank,first_direct,fir przelewy24.stripe.banks = "alior_bank,bank_millennium,bank_nowy_bfg_sa,bank_pekao_sa,banki_spbdzielcze,blik,bnp_paribas,boz,citi,credit_agricole,e_transfer_pocztowy24,getin_bank,idea_bank,inteligo,mbank_mtransfer,nest_przelew,noble_pay,pbac_z_ipko,plus_bank,santander_przelew24,toyota_bank,volkswagen_bank" [connector_customer] -connector_list = "stax,stripe,gocardless,facilitapay" +connector_list = "stax,stripe,gocardless,facilitapay,hyperswitch_vault" payout_connector_list = "nomupay,stripe,wise" # Connector configuration, provided attributes will be used to fulfill API requests. @@ -73,6 +73,7 @@ gocardless.base_url = "https://api.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" hipay.base_url = "https://secure-gateway.hipay-tpp.com/rest/" +hyperswitch_vault.base_url = "https://sandbox.hyperswitch.io" hipay.secondary_base_url = "https://secure2-vault.hipay-tpp.com/rest/" hipay.third_base_url = "https://api-gateway.hipay.com/" iatapay.base_url = "https://iata-pay.iata.org/api/v1" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index f0229e1e73..06123d9bf4 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -15,7 +15,7 @@ open_banking_uk.adyen.banks = "aib,bank_of_scotland,danske_bank,first_direct,fir przelewy24.stripe.banks = "alior_bank,bank_millennium,bank_nowy_bfg_sa,bank_pekao_sa,banki_spbdzielcze,blik,bnp_paribas,boz,citi,credit_agricole,e_transfer_pocztowy24,getin_bank,idea_bank,inteligo,mbank_mtransfer,nest_przelew,noble_pay,pbac_z_ipko,plus_bank,santander_przelew24,toyota_bank,volkswagen_bank" [connector_customer] -connector_list = "stax,stripe,gocardless,facilitapay" +connector_list = "stax,stripe,gocardless,facilitapay,hyperswitch_vault" payout_connector_list = "nomupay,stripe,wise" # Connector configuration, provided attributes will be used to fulfill API requests. @@ -73,6 +73,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" hipay.base_url = "https://stage-secure-gateway.hipay-tpp.com/rest/" +hyperswitch_vault.base_url = "https://sandbox.hyperswitch.io" hipay.secondary_base_url = "https://stage-secure2-vault.hipay-tpp.com/rest/" hipay.third_base_url = "https://stage-api-gateway.hipay.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" diff --git a/config/development.toml b/config/development.toml index 03862761e4..e2c1f7952e 100644 --- a/config/development.toml +++ b/config/development.toml @@ -133,6 +133,7 @@ cards = [ "gpayments", "helcim", "hipay", + "hyperswitch_vault", "iatapay", "inespay", "itaubank", @@ -261,6 +262,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" hipay.base_url = "https://stage-secure-gateway.hipay-tpp.com/rest/" +hyperswitch_vault.base_url = "https://integ-api.hyperswitch.io" hipay.secondary_base_url = "https://stage-secure2-vault.hipay-tpp.com/rest/" hipay.third_base_url = "https://stage-api-gateway.hipay.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" @@ -866,7 +868,7 @@ nexixpay = { payment_method = "card" } redsys = { payment_method = "card" } [connector_customer] -connector_list = "gocardless,stax,stripe,facilitapay" +connector_list = "gocardless,stax,stripe,facilitapay,hyperswitch_vault" payout_connector_list = "nomupay,stripe,wise" [dummy_connector] diff --git a/config/docker_compose.toml b/config/docker_compose.toml index e68dcea836..5e64233dce 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -156,6 +156,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" hipay.base_url = "https://stage-secure-gateway.hipay-tpp.com/rest/" +hyperswitch_vault.base_url = "https://integ-api.hyperswitch.io" hipay.secondary_base_url = "https://stage-secure2-vault.hipay-tpp.com/rest/" hipay.third_base_url = "https://stage-api-gateway.hipay.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" @@ -278,6 +279,7 @@ cards = [ "gpayments", "helcim", "hipay", + "hyperswitch_vault", "iatapay", "inespay", "itaubank", @@ -877,7 +879,7 @@ card.debit = { connector_list = "cybersource" } connector_list = "adyen,archipel,cybersource,novalnet,stripe,worldpay" [connector_customer] -connector_list = "gocardless,stax,stripe,facilitapay" +connector_list = "gocardless,stax,stripe,facilitapay,hyperswitch_vault" payout_connector_list = "nomupay,stripe,wise" diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index f4435b08fc..213691ef4f 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -287,6 +287,10 @@ pub struct ExternalVaultConnectorDetails { /// Merchant Connector id to be stored for vault connector #[schema(value_type = Option)] pub vault_connector_id: id_type::MerchantConnectorAccountId, + + /// External vault to be used for storing payment method information + #[schema(value_type = Option)] + pub vault_sdk: Option, } #[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 5550d7e23e..57f8b52355 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -175,12 +175,14 @@ pub enum BillingConnectors { #[strum(serialize_all = "snake_case")] pub enum VaultConnectors { Vgs, + HyperswitchVault, } impl From for Connector { fn from(value: VaultConnectors) -> Self { match value { VaultConnectors::Vgs => Self::Vgs, + VaultConnectors::HyperswitchVault => Self::HyperswitchVault, } } } diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index adbfb56c0f..0ff988fc9b 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -7004,6 +7004,38 @@ pub enum SessionToken { NoSessionTokenReceived, } +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum VaultSessionDetails { + Vgs(VgsSessionDetails), + HyperswitchVault(HyperswitchVaultSessionDetails), +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +pub struct VgsSessionDetails { + /// The identifier of the external vault + #[schema(value_type = String)] + pub external_vault_id: Secret, + /// The environment for the external vault initiation + pub sdk_env: String, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +pub struct HyperswitchVaultSessionDetails { + /// Session ID for Hyperswitch Vault + #[schema(value_type = String)] + pub payment_method_session_id: Secret, + /// Client secret for Hyperswitch Vault + #[schema(value_type = String)] + pub client_secret: Secret, + /// Publishable key for Hyperswitch Vault + #[schema(value_type = String)] + pub publishable_key: Secret, + /// Profile ID for Hyperswitch Vault + #[schema(value_type = String)] + pub profile_id: Secret, +} + #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] #[serde(rename_all = "lowercase")] pub struct PazeSessionTokenResponse { @@ -7392,6 +7424,8 @@ pub struct PaymentsSessionResponse { pub payment_id: id_type::GlobalPaymentId, /// The list of session token object pub session_token: Vec, + /// External vault session details + pub vault_details: Option, } #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 147e2111f1..83a19b7bfa 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -254,6 +254,7 @@ pub enum Connector { Gpayments, Hipay, Helcim, + HyperswitchVault, Inespay, Iatapay, Itaubank, @@ -425,6 +426,7 @@ impl Connector { | Self::Gpayments | Self::Hipay | Self::Helcim + | Self::HyperswitchVault | Self::Iatapay | Self::Inespay | Self::Itaubank @@ -754,6 +756,7 @@ impl TryFrom for RoutableConnectors { Connector::Redsys => Ok(Self::Redsys), Connector::CtpMastercard | Connector::Gpayments + | Connector::HyperswitchVault | Connector::Juspaythreedsserver | Connector::Netcetera | Connector::Taxjar diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index d6b021fd4d..38fb0d3455 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -7103,6 +7103,26 @@ impl AuthenticationConnectors { } } +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + PartialEq, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum VaultSdk { + VgsSdk, + HyperswitchSdk, +} + #[derive( Clone, Debug, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index cf1ef060e0..f05ca7b5f4 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -125,6 +125,7 @@ pub struct ConfigMetadata { pub tenant_id: Option, pub platform_url: Option, pub report_group: Option, + pub proxy_url: Option, } #[serde_with::skip_serializing_none] @@ -213,6 +214,7 @@ pub struct ConnectorConfig { pub gpayments: Option, pub hipay: Option, pub helcim: Option, + pub hyperswitch_vault: Option, pub inespay: Option, pub jpmorgan: Option, pub klarna: Option, @@ -398,6 +400,7 @@ impl ConnectorConfig { Connector::Gocardless => Ok(connector_data.gocardless), Connector::Gpayments => Ok(connector_data.gpayments), Connector::Hipay => Ok(connector_data.hipay), + Connector::HyperswitchVault => Ok(connector_data.hyperswitch_vault), Connector::Helcim => Ok(connector_data.helcim), Connector::Inespay => Ok(connector_data.inespay), Connector::Jpmorgan => Ok(connector_data.jpmorgan), diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index 0bc52b3318..10e71ce1f3 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -1,6 +1,6 @@ use std::collections::{HashMap, HashSet}; -use common_enums::{AuthenticationConnectors, UIWidgetFormLayout}; +use common_enums::{AuthenticationConnectors, UIWidgetFormLayout, VaultSdk}; use common_types::primitive_wrappers; use common_utils::{encryption::Encryption, pii}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; @@ -683,6 +683,7 @@ common_utils::impl_to_sql_from_sql_json!(AuthenticationConnectorDetails); #[diesel(sql_type = diesel::sql_types::Jsonb)] pub struct ExternalVaultConnectorDetails { pub vault_connector_id: common_utils::id_type::MerchantConnectorAccountId, + pub vault_sdk: Option, } common_utils::impl_to_sql_from_sql_json!(ExternalVaultConnectorDetails); diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index ab586e0090..b603923d98 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -40,6 +40,7 @@ pub mod gocardless; pub mod gpayments; pub mod helcim; pub mod hipay; +pub mod hyperswitch_vault; pub mod iatapay; pub mod inespay; pub mod itaubank; @@ -113,12 +114,12 @@ pub use self::{ digitalvirgo::Digitalvirgo, dlocal::Dlocal, 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, iatapay::Iatapay, inespay::Inespay, - itaubank::Itaubank, jpmorgan::Jpmorgan, juspaythreedsserver::Juspaythreedsserver, - klarna::Klarna, mifinity::Mifinity, mollie::Mollie, moneris::Moneris, - multisafepay::Multisafepay, netcetera::Netcetera, nexinets::Nexinets, nexixpay::Nexixpay, - nmi::Nmi, nomupay::Nomupay, noon::Noon, nordea::Nordea, novalnet::Novalnet, nuvei::Nuvei, - opayo::Opayo, opennode::Opennode, paybox::Paybox, payeezy::Payeezy, payme::Payme, + 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, netcetera::Netcetera, nexinets::Nexinets, + nexixpay::Nexixpay, nmi::Nmi, nomupay::Nomupay, noon::Noon, nordea::Nordea, novalnet::Novalnet, + nuvei::Nuvei, opayo::Opayo, opennode::Opennode, paybox::Paybox, payeezy::Payeezy, payme::Payme, payone::Payone, paypal::Paypal, paystack::Paystack, payu::Payu, placetopay::Placetopay, plaid::Plaid, powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, razorpay::Razorpay, recurly::Recurly, redsys::Redsys, riskified::Riskified, shift4::Shift4, signifyd::Signifyd, diff --git a/crates/hyperswitch_connectors/src/connectors/facilitapay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/facilitapay/transformers.rs index a123b4796c..23347e31ed 100644 --- a/crates/hyperswitch_connectors/src/connectors/facilitapay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/facilitapay/transformers.rs @@ -281,27 +281,29 @@ impl TryFrom<&types::ConnectorCustomerRouterData> for FacilitapayCustomerRequest let social_name = item.get_billing_full_name()?; let (document_type, document_number) = match item.request.payment_method_data.clone() { - PaymentMethodData::BankTransfer(bank_transfer_data) => match *bank_transfer_data { - BankTransferData::Pix { cpf, .. } => { - // Extract only digits from the CPF string - let document_number = - cpf.ok_or_else(missing_field_err("cpf"))?.map(|cpf_number| { - cpf_number - .chars() - .filter(|chars| chars.is_ascii_digit()) - .collect::() - }); + Some(PaymentMethodData::BankTransfer(bank_transfer_data)) => { + match *bank_transfer_data { + BankTransferData::Pix { cpf, .. } => { + // Extract only digits from the CPF string + let document_number = + cpf.ok_or_else(missing_field_err("cpf"))?.map(|cpf_number| { + cpf_number + .chars() + .filter(|chars| chars.is_ascii_digit()) + .collect::() + }); - let document_type = convert_to_document_type("cpf")?; - (document_type, document_number) + let document_type = convert_to_document_type("cpf")?; + (document_type, document_number) + } + _ => { + return Err(errors::ConnectorError::NotImplemented( + "Selected payment method through Facilitapay".to_string(), + ) + .into()) + } } - _ => { - return Err(errors::ConnectorError::NotImplemented( - "Selected payment method through Facilitapay".to_string(), - ) - .into()) - } - }, + } _ => { return Err(errors::ConnectorError::NotImplemented( "Selected payment method through Facilitapay".to_string(), diff --git a/crates/hyperswitch_connectors/src/connectors/hyperswitch_vault.rs b/crates/hyperswitch_connectors/src/connectors/hyperswitch_vault.rs new file mode 100644 index 0000000000..d7e0e93c59 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/hyperswitch_vault.rs @@ -0,0 +1,491 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, +}; +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, CreateConnectorCustomer, PSync, PaymentMethodToken, Session, + SetupMandate, Void, + }, + refunds::{Execute, RSync}, + vault::ExternalVaultCreateFlow, + }, + router_request_types::{ + AccessTokenRequestData, ConnectorCustomerData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, VaultRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData, VaultResponseData}, + types::{ + ConnectorCustomerRouterData, PaymentsAuthorizeRouterData, PaymentsCancelRouterData, + PaymentsCaptureRouterData, PaymentsSessionRouterData, PaymentsSyncRouterData, + RefreshTokenRouterData, RefundsRouterData, SetupMandateRouterData, TokenizationRouterData, + VaultRouterData, + }, +}; +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}; +use transformers as hyperswitch_vault; + +use crate::{constants::headers, types::ResponseRouterData}; + +#[derive(Clone)] +pub struct HyperswitchVault; + +impl api::Payment for HyperswitchVault {} +impl api::PaymentSession for HyperswitchVault {} +impl api::ConnectorAccessToken for HyperswitchVault {} +impl api::MandateSetup for HyperswitchVault {} +impl api::PaymentAuthorize for HyperswitchVault {} +impl api::PaymentSync for HyperswitchVault {} +impl api::PaymentCapture for HyperswitchVault {} +impl api::PaymentVoid for HyperswitchVault {} +impl api::Refund for HyperswitchVault {} +impl api::RefundExecute for HyperswitchVault {} +impl api::RefundSync for HyperswitchVault {} +impl api::PaymentToken for HyperswitchVault {} +impl api::ExternalVaultCreate for HyperswitchVault {} +impl api::ConnectorCustomer for HyperswitchVault {} + +impl ConnectorIntegration + for HyperswitchVault +{ + fn get_headers( + &self, + req: &VaultRouterData, + 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: &VaultRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!( + "{}/v2/payment-methods-session", + self.base_url(connectors) + )) + } + + fn get_request_body( + &self, + req: &VaultRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = hyperswitch_vault::HyperswitchVaultCreateRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &VaultRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::ExternalVaultCreateType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::ExternalVaultCreateType::get_headers( + self, req, connectors, + )?) + .set_body(types::ExternalVaultCreateType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &VaultRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: hyperswitch_vault::HyperswitchVaultCreateResponse = res + .response + .parse_struct("HyperswitchVault HyperswitchVaultCreateResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?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 HyperswitchVault +{ + fn build_request( + &self, + _req: &TokenizationRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "PaymentMethodTokenization".to_string(), + connector: self.id().to_string(), + } + .into()) + } +} + +impl ConnectorCommonExt for HyperswitchVault +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = hyperswitch_vault::HyperswitchVaultAuthType::try_from(&req.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let api_key = auth.api_key.expose(); + + let header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + headers::AUTHORIZATION.to_string(), + format!("api-key={api_key}").into_masked(), + ), + ( + headers::X_PROFILE_ID.to_string(), + auth.profile_id.expose().into_masked(), + ), + ]; + Ok(header) + } +} + +impl ConnectorCommon for HyperswitchVault { + fn id(&self) -> &'static str { + "hyperswitch_vault" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.hyperswitch_vault.base_url.as_ref() + } + + fn get_auth_header( + &self, + _auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + Ok(vec![]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: hyperswitch_vault::HyperswitchVaultErrorResponse = res + .response + .parse_struct("HyperswitchVaultErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.error.code, + message: response.error.error_type, + reason: response.error.message, + attempt_status: None, + connector_transaction_id: None, + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + }) + } +} + +impl ConnectorIntegration + for HyperswitchVault +{ + fn get_headers( + &self, + req: &ConnectorCustomerRouterData, + 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: &ConnectorCustomerRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/v2/customers", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &ConnectorCustomerRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = + hyperswitch_vault::HyperswitchVaultCustomerCreateRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &ConnectorCustomerRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::ConnectorCustomerType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::ConnectorCustomerType::get_headers( + self, req, connectors, + )?) + .set_body(types::ConnectorCustomerType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &ConnectorCustomerRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: hyperswitch_vault::HyperswitchVaultCustomerCreateResponse = res + .response + .parse_struct("HyperswitchVault HyperswitchVaultCustomerCreateResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?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 ConnectorValidation for HyperswitchVault {} + +impl ConnectorIntegration for HyperswitchVault { + fn build_request( + &self, + _req: &PaymentsSessionRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "PaymentsSession".to_string(), + connector: self.id().to_string(), + } + .into()) + } +} + +impl ConnectorIntegration + for HyperswitchVault +{ + fn build_request( + &self, + _req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "AccessTokenAuthorize".to_string(), + connector: self.id().to_string(), + } + .into()) + } +} + +impl ConnectorIntegration + for HyperswitchVault +{ + fn build_request( + &self, + _req: &SetupMandateRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "SetupMandate".to_string(), + connector: self.id().to_string(), + } + .into()) + } +} + +impl ConnectorIntegration + for HyperswitchVault +{ + fn build_request( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "PaymentsAuthorize".to_string(), + connector: self.id().to_string(), + } + .into()) + } +} + +impl ConnectorIntegration for HyperswitchVault { + fn build_request( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "PaymentsSync".to_string(), + connector: self.id().to_string(), + } + .into()) + } +} + +impl ConnectorIntegration for HyperswitchVault { + fn build_request( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "PaymentsCapture".to_string(), + connector: self.id().to_string(), + } + .into()) + } +} + +impl ConnectorIntegration for HyperswitchVault { + fn build_request( + &self, + _req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "PaymentsCapture".to_string(), + connector: self.id().to_string(), + } + .into()) + } +} + +impl ConnectorIntegration for HyperswitchVault { + fn build_request( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "Refunds".to_string(), + connector: self.id().to_string(), + } + .into()) + } +} + +impl ConnectorIntegration for HyperswitchVault { + fn build_request( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "RefundsSync".to_string(), + connector: self.id().to_string(), + } + .into()) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for HyperswitchVault { + 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)) + } +} + +impl ConnectorSpecifications for HyperswitchVault {} diff --git a/crates/hyperswitch_connectors/src/connectors/hyperswitch_vault/transformers.rs b/crates/hyperswitch_connectors/src/connectors/hyperswitch_vault/transformers.rs new file mode 100644 index 0000000000..220e2e5544 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/hyperswitch_vault/transformers.rs @@ -0,0 +1,130 @@ +use common_utils::pii::Email; +use hyperswitch_domain_models::{ + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::vault::ExternalVaultCreateFlow, + router_response_types::{PaymentsResponseData, VaultResponseData}, + types::{ConnectorCustomerRouterData, VaultRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{types::ResponseRouterData, utils}; + +#[derive(Default, Debug, Serialize)] +pub struct HyperswitchVaultCreateRequest { + customer_id: String, +} + +impl TryFrom<&VaultRouterData> for HyperswitchVaultCreateRequest { + type Error = error_stack::Report; + fn try_from(item: &VaultRouterData) -> Result { + let customer_id = item + .request + .connector_customer_id + .clone() + .ok_or_else(utils::missing_field_err("connector_customer"))?; + Ok(Self { customer_id }) + } +} + +pub struct HyperswitchVaultAuthType { + pub(super) api_key: Secret, + pub(super) profile_id: Secret, +} + +impl TryFrom<&ConnectorAuthType> for HyperswitchVaultAuthType { + 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(), + profile_id: api_secret.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct HyperswitchVaultCreateResponse { + id: Secret, + client_secret: Secret, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(VaultResponseData::ExternalVaultCreateResponse { + session_id: item.response.id, + client_secret: item.response.client_secret, + }), + ..item.data + }) + } +} + +#[derive(Default, Debug, Serialize)] +pub struct HyperswitchVaultCustomerCreateRequest { + name: Option>, + email: Option, +} + +impl TryFrom<&ConnectorCustomerRouterData> for HyperswitchVaultCustomerCreateRequest { + type Error = error_stack::Report; + fn try_from(item: &ConnectorCustomerRouterData) -> Result { + Ok(Self { + name: item.request.name.clone(), + email: item.request.email.clone(), + }) + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct HyperswitchVaultCustomerCreateResponse { + id: String, +} + +impl + TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + HyperswitchVaultCustomerCreateResponse, + T, + PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(PaymentsResponseData::ConnectorCustomerResponse { + connector_customer_id: item.response.id, + }), + ..item.data + }) + } +} + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct HyperswitchVaultErrorResponse { + pub error: HyperswitchVaultErrorDetails, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct HyperswitchVaultErrorDetails { + #[serde(alias = "type")] + pub error_type: String, + pub message: Option, + pub code: String, +} diff --git a/crates/hyperswitch_connectors/src/connectors/vgs/transformers.rs b/crates/hyperswitch_connectors/src/connectors/vgs/transformers.rs index b4bf6eb520..f30a455ef4 100644 --- a/crates/hyperswitch_connectors/src/connectors/vgs/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/vgs/transformers.rs @@ -83,7 +83,7 @@ impl TryFrom<&ConnectorAuthType> for VgsAuthType { type Error = error_stack::Report; fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + ConnectorAuthType::SignatureKey { api_key, key1, .. } => Ok(Self { username: api_key.to_owned(), password: key1.to_owned(), }), diff --git a/crates/hyperswitch_connectors/src/constants.rs b/crates/hyperswitch_connectors/src/constants.rs index faeb9e2c0a..223d7a37f7 100644 --- a/crates/hyperswitch_connectors/src/constants.rs +++ b/crates/hyperswitch_connectors/src/constants.rs @@ -33,6 +33,7 @@ pub(crate) mod headers { pub(crate) const KEY: &str = "key"; pub(crate) const X_SIGNATURE: &str = "X-Signature"; pub(crate) const SOAP_ACTION: &str = "SOAPAction"; + pub(crate) const X_PROFILE_ID: &str = "X-Profile-Id"; } /// Unsupported response type error message diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index a71f1cb413..4195b155d1 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -42,8 +42,8 @@ use hyperswitch_domain_models::{ PreProcessing, Reject, SdkSessionUpdate, UpdateMetadata, }, webhooks::VerifyWebhookSource, - Authenticate, AuthenticationConfirmation, ExternalVaultDeleteFlow, ExternalVaultInsertFlow, - ExternalVaultRetrieveFlow, PostAuthenticate, PreAuthenticate, + Authenticate, AuthenticationConfirmation, ExternalVaultCreateFlow, ExternalVaultDeleteFlow, + ExternalVaultInsertFlow, ExternalVaultRetrieveFlow, PostAuthenticate, PreAuthenticate, }, router_request_types::{ authentication, @@ -95,7 +95,10 @@ use hyperswitch_interfaces::{ PaymentsPostProcessing, PaymentsPreProcessing, TaxCalculation, }, revenue_recovery::RevenueRecovery, - vault::{ExternalVault, ExternalVaultDelete, ExternalVaultInsert, ExternalVaultRetrieve}, + vault::{ + ExternalVault, ExternalVaultCreate, ExternalVaultDelete, ExternalVaultInsert, + ExternalVaultRetrieve, + }, ConnectorIntegration, ConnectorMandateRevoke, ConnectorRedirectResponse, ConnectorTransactionId, UasAuthentication, UasAuthenticationConfirmation, UasPostAuthentication, UasPreAuthentication, UnifiedAuthenticationService, @@ -160,6 +163,7 @@ default_imp_for_authorize_session_token!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -279,6 +283,7 @@ default_imp_for_calculate_tax!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -392,6 +397,7 @@ default_imp_for_session_update!( connectors::Forte, connectors::Getnet, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -512,6 +518,7 @@ default_imp_for_post_session_tokens!( connectors::Forte, connectors::Getnet, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -631,6 +638,7 @@ default_imp_for_update_metadata!( connectors::Forte, connectors::Getnet, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -752,6 +760,7 @@ default_imp_for_complete_authorize!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -860,6 +869,7 @@ default_imp_for_incremental_authorization!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1093,6 +1103,7 @@ default_imp_for_connector_redirect_response!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1197,6 +1208,7 @@ default_imp_for_pre_processing_steps!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1310,6 +1322,7 @@ default_imp_for_post_processing_steps!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1431,6 +1444,7 @@ default_imp_for_approve!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1553,6 +1567,7 @@ default_imp_for_reject!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1675,6 +1690,7 @@ default_imp_for_webhook_source_verification!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1795,6 +1811,7 @@ default_imp_for_accept_dispute!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1915,6 +1932,7 @@ default_imp_for_submit_evidence!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2033,6 +2051,7 @@ default_imp_for_defend_dispute!( connectors::Gocardless, connectors::Gpayments, connectors::Hipay, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2163,6 +2182,7 @@ default_imp_for_file_upload!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2273,6 +2293,7 @@ default_imp_for_payouts!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2389,6 +2410,7 @@ default_imp_for_payouts_create!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2509,6 +2531,7 @@ default_imp_for_payouts_retrieve!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2629,6 +2652,7 @@ default_imp_for_payouts_eligibility!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2747,6 +2771,7 @@ default_imp_for_payouts_fulfill!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2864,6 +2889,7 @@ default_imp_for_payouts_cancel!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2984,6 +3010,7 @@ default_imp_for_payouts_quote!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -3105,6 +3132,7 @@ default_imp_for_payouts_recipient!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -3226,6 +3254,7 @@ default_imp_for_payouts_recipient_account!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -3348,6 +3377,7 @@ default_imp_for_frm_sale!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -3470,6 +3500,7 @@ default_imp_for_frm_checkout!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -3592,6 +3623,7 @@ default_imp_for_frm_transaction!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -3714,6 +3746,7 @@ default_imp_for_frm_fulfillment!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -3836,6 +3869,7 @@ default_imp_for_frm_record_return!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -3953,6 +3987,7 @@ default_imp_for_revoking_mandates!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -4073,6 +4108,7 @@ default_imp_for_uas_pre_authentication!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -4192,6 +4228,7 @@ default_imp_for_uas_post_authentication!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -4312,6 +4349,7 @@ default_imp_for_uas_authentication_confirmation!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -4423,6 +4461,7 @@ default_imp_for_connector_request_id!( connectors::Gocardless, connectors::Gpayments, connectors::Hipay, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -4538,6 +4577,7 @@ default_imp_for_fraud_check!( connectors::Gpayments, connectors::Helcim, connectors::Hipay, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -4681,6 +4721,7 @@ default_imp_for_connector_authentication!( connectors::Gocardless, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -4799,6 +4840,7 @@ default_imp_for_uas_authentication!( connectors::Gpayments, connectors::Helcim, connectors::Hipay, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -4911,6 +4953,7 @@ default_imp_for_revenue_recovery! { connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -5034,6 +5077,7 @@ default_imp_for_billing_connector_payment_sync!( connectors::Gpayments, connectors::Helcim, connectors::Hipay, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -5155,6 +5199,7 @@ default_imp_for_revenue_recovery_record_back!( connectors::Gpayments, connectors::Helcim, connectors::Hipay, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -5276,6 +5321,7 @@ default_imp_for_billing_connector_invoice_sync!( connectors::Gpayments, connectors::Helcim, connectors::Hipay, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -5391,6 +5437,7 @@ default_imp_for_external_vault!( connectors::Gpayments, connectors::Helcim, connectors::Hipay, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -5512,6 +5559,7 @@ default_imp_for_external_vault_insert!( connectors::Gpayments, connectors::Helcim, connectors::Hipay, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -5633,6 +5681,7 @@ default_imp_for_external_vault_retrieve!( connectors::Gpayments, connectors::Helcim, connectors::Hipay, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -5712,6 +5761,128 @@ macro_rules! default_imp_for_external_vault_delete { } default_imp_for_external_vault_delete!( + connectors::Aci, + connectors::Adyen, + connectors::Adyenplatform, + connectors::Airwallex, + connectors::Amazonpay, + connectors::Archipel, + connectors::Authorizedotnet, + connectors::Barclaycard, + connectors::Bambora, + connectors::Bamboraapac, + connectors::Bankofamerica, + connectors::Billwerk, + connectors::Bluesnap, + connectors::Bitpay, + connectors::Braintree, + connectors::Boku, + connectors::Cashtocode, + connectors::Chargebee, + connectors::Checkout, + connectors::Coinbase, + connectors::Coingate, + connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Cybersource, + connectors::Datatrans, + connectors::Deutschebank, + connectors::Digitalvirgo, + connectors::Dlocal, + connectors::Ebanx, + connectors::Elavon, + connectors::Facilitapay, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Fiuu, + connectors::Forte, + connectors::Getnet, + connectors::Globalpay, + connectors::Globepay, + connectors::Gocardless, + connectors::Gpayments, + connectors::Helcim, + connectors::Hipay, + connectors::HyperswitchVault, + connectors::Iatapay, + connectors::Inespay, + connectors::Itaubank, + connectors::Juspaythreedsserver, + connectors::Jpmorgan, + connectors::Klarna, + connectors::Netcetera, + connectors::Nordea, + connectors::Nomupay, + connectors::Nmi, + connectors::Noon, + connectors::Novalnet, + connectors::Nexinets, + connectors::Nexixpay, + connectors::Nuvei, + connectors::Opayo, + connectors::Opennode, + connectors::Payeezy, + connectors::Paystack, + connectors::Payu, + connectors::Paypal, + connectors::Plaid, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Mifinity, + connectors::Mollie, + connectors::Moneris, + connectors::Multisafepay, + connectors::Paybox, + connectors::Payme, + connectors::Payone, + connectors::Placetopay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Recurly, + connectors::Redsys, + connectors::Riskified, + connectors::Signifyd, + connectors::Shift4, + connectors::Stax, + connectors::Stripe, + connectors::Stripebilling, + connectors::Square, + connectors::Taxjar, + connectors::Threedsecureio, + connectors::Thunes, + connectors::Tokenio, + connectors::Trustpay, + connectors::Tsys, + connectors::UnifiedAuthenticationService, + connectors::Wise, + connectors::Worldline, + connectors::Worldpay, + connectors::Worldpayvantiv, + connectors::Worldpayxml, + connectors::Wellsfargo, + connectors::Vgs, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl +); + +macro_rules! default_imp_for_external_vault_create { + ($($path:ident::$connector:ident),*) => { + $( + impl ExternalVaultCreate for $path::$connector {} + impl + ConnectorIntegration< + ExternalVaultCreateFlow, + VaultRequestData, + VaultResponseData, + > for $path::$connector + {} + )* + }; +} + +default_imp_for_external_vault_create!( connectors::Aci, connectors::Adyen, connectors::Adyenplatform, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 93ce14d4e8..952de0b231 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -26,7 +26,7 @@ use hyperswitch_domain_models::{ BillingConnectorInvoiceSync, BillingConnectorPaymentsSync, RecoveryRecordBack, }, webhooks::VerifyWebhookSource, - AccessTokenAuth, ExternalVaultDeleteFlow, ExternalVaultInsertFlow, + AccessTokenAuth, ExternalVaultCreateFlow, ExternalVaultDeleteFlow, ExternalVaultInsertFlow, ExternalVaultRetrieveFlow, }, router_request_types::{ @@ -109,7 +109,8 @@ use hyperswitch_interfaces::{ RevenueRecoveryRecordBackV2, RevenueRecoveryV2, }, vault_v2::{ - ExternalVaultDeleteV2, ExternalVaultInsertV2, ExternalVaultRetrieveV2, ExternalVaultV2, + ExternalVaultCreateV2, ExternalVaultDeleteV2, ExternalVaultInsertV2, + ExternalVaultRetrieveV2, ExternalVaultV2, }, ConnectorAccessTokenV2, ConnectorMandateRevokeV2, ConnectorVerifyWebhookSourceV2, }, @@ -285,6 +286,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -407,6 +409,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -524,6 +527,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -646,6 +650,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -767,6 +772,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -889,6 +895,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1021,6 +1028,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1145,6 +1153,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1269,6 +1278,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1393,6 +1403,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1517,6 +1528,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1641,6 +1653,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1765,6 +1778,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -1889,6 +1903,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2013,6 +2028,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2135,6 +2151,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2259,6 +2276,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2383,6 +2401,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2507,6 +2526,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2631,6 +2651,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2755,6 +2776,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2876,6 +2898,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, @@ -2979,6 +3002,7 @@ default_imp_for_new_connector_integration_frm!( connectors::Gocardless, connectors::Gpayments, connectors::Helcim, + connectors::HyperswitchVault, connectors::Inespay, connectors::Jpmorgan, connectors::Juspaythreedsserver, @@ -3100,6 +3124,7 @@ default_imp_for_new_connector_integration_connector_authentication!( connectors::Gpayments, connectors::Helcim, connectors::Hipay, + connectors::HyperswitchVault, connectors::Inespay, connectors::Jpmorgan, connectors::Juspaythreedsserver, @@ -3210,6 +3235,7 @@ default_imp_for_new_connector_integration_revenue_recovery!( connectors::Gpayments, connectors::Helcim, connectors::Hipay, + connectors::HyperswitchVault, connectors::Inespay, connectors::Jpmorgan, connectors::Juspaythreedsserver, @@ -3262,6 +3288,7 @@ macro_rules! default_imp_for_new_connector_integration_external_vault { impl ExternalVaultInsertV2 for $path::$connector {} impl ExternalVaultRetrieveV2 for $path::$connector {} impl ExternalVaultDeleteV2 for $path::$connector {} + impl ExternalVaultCreateV2 for $path::$connector {} impl ConnectorIntegrationV2< ExternalVaultInsertFlow, @@ -3286,6 +3313,14 @@ macro_rules! default_imp_for_new_connector_integration_external_vault { VaultResponseData, > for $path::$connector {} + impl + ConnectorIntegrationV2< + ExternalVaultCreateFlow, + VaultConnectorFlowData, + VaultRequestData, + VaultResponseData, + > for $path::$connector + {} )* }; } @@ -3333,6 +3368,7 @@ default_imp_for_new_connector_integration_external_vault!( connectors::Gpayments, connectors::Hipay, connectors::Helcim, + connectors::HyperswitchVault, connectors::Iatapay, connectors::Inespay, connectors::Itaubank, diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index 992bd58784..80070aa9e5 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -1106,6 +1106,11 @@ impl Profile { pub fn is_external_vault_enabled(&self) -> bool { self.is_external_vault_enabled.unwrap_or(false) } + + #[cfg(feature = "v2")] + pub fn is_vault_sdk_enabled(&self) -> bool { + self.external_vault_connector_details.is_some() + } } #[cfg(feature = "v2")] diff --git a/crates/hyperswitch_domain_models/src/configs.rs b/crates/hyperswitch_domain_models/src/configs.rs index 46a100af45..4c170d5c50 100644 --- a/crates/hyperswitch_domain_models/src/configs.rs +++ b/crates/hyperswitch_domain_models/src/configs.rs @@ -57,6 +57,7 @@ pub struct Connectors { pub gpayments: ConnectorParams, pub helcim: ConnectorParams, pub hipay: ConnectorParamsWithThreeUrls, + pub hyperswitch_vault: ConnectorParams, pub iatapay: ConnectorParams, pub inespay: ConnectorParams, pub itaubank: ConnectorParams, diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 42d1ef1a42..8d95ae17bc 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; #[cfg(feature = "v2")] -use api_models::payments::SessionToken; +use api_models::payments::{SessionToken, VaultSessionDetails}; use common_types::primitive_wrappers::{ AlwaysRequestExtendedAuthorization, RequestExtendedAuthorizationBool, }; @@ -838,6 +838,8 @@ where pub payment_intent: PaymentIntent, pub sessions_token: Vec, pub client_secret: Option>, + pub vault_session_details: Option, + pub connector_customer_id: Option, } // TODO: Check if this can be merged with existing payment data diff --git a/crates/hyperswitch_domain_models/src/router_flow_types/vault.rs b/crates/hyperswitch_domain_models/src/router_flow_types/vault.rs index 9195125f74..8468431b88 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/vault.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/vault.rs @@ -6,3 +6,6 @@ pub struct ExternalVaultRetrieveFlow; #[derive(Debug, Clone)] pub struct ExternalVaultDeleteFlow; + +#[derive(Debug, Clone)] +pub struct ExternalVaultCreateFlow; diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 5f362e0f85..8250f919c5 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -176,7 +176,7 @@ pub struct ConnectorCustomerData { pub phone: Option>, pub name: Option>, pub preprocessing_id: Option, - pub payment_method_data: PaymentMethodData, + pub payment_method_data: Option, } impl TryFrom for ConnectorCustomerData { @@ -184,7 +184,7 @@ impl TryFrom for ConnectorCustomerData { fn try_from(data: SetupMandateRequestData) -> Result { Ok(Self { email: data.email, - payment_method_data: data.payment_method_data, + payment_method_data: Some(data.payment_method_data), description: None, phone: None, name: None, @@ -208,7 +208,30 @@ impl ) -> Result { Ok(Self { email: data.request.email.clone(), - payment_method_data: data.request.payment_method_data.clone(), + payment_method_data: Some(data.request.payment_method_data.clone()), + description: None, + phone: None, + name: data.request.customer_name.clone(), + preprocessing_id: data.preprocessing_id.clone(), + }) + } +} + +impl TryFrom<&RouterData> + for ConnectorCustomerData +{ + type Error = error_stack::Report; + + fn try_from( + data: &RouterData< + flows::Session, + PaymentsSessionData, + response_types::PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + email: data.request.email.clone(), + payment_method_data: None, description: None, phone: None, name: data.request.customer_name.clone(), @@ -888,6 +911,7 @@ pub struct PaymentsSessionData { // Minor Unit amount for amount frame work pub minor_amount: MinorUnit, pub apple_pay_recurring_details: Option, + pub customer_name: Option>, } #[derive(Debug, Clone, Default)] @@ -945,4 +969,5 @@ pub struct SetupMandateRequestData { pub struct VaultRequestData { pub payment_method_vaulting_data: Option, pub connector_vault_id: Option, + pub connector_customer_id: Option, } diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index bdf04e0328..516682889c 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -615,6 +615,10 @@ impl SupportedPaymentMethodsExt for SupportedPaymentMethods { #[derive(Debug, Clone)] pub enum VaultResponseData { + ExternalVaultCreateResponse { + session_id: masking::Secret, + client_secret: masking::Secret, + }, ExternalVaultInsertResponse { connector_vault_id: String, fingerprint_id: String, diff --git a/crates/hyperswitch_interfaces/src/api/vault.rs b/crates/hyperswitch_interfaces/src/api/vault.rs index be44f8e8d7..78e32f7b57 100644 --- a/crates/hyperswitch_interfaces/src/api/vault.rs +++ b/crates/hyperswitch_interfaces/src/api/vault.rs @@ -2,7 +2,8 @@ use hyperswitch_domain_models::{ router_flow_types::vault::{ - ExternalVaultDeleteFlow, ExternalVaultInsertFlow, ExternalVaultRetrieveFlow, + ExternalVaultCreateFlow, ExternalVaultDeleteFlow, ExternalVaultInsertFlow, + ExternalVaultRetrieveFlow, }, router_request_types::VaultRequestData, router_response_types::VaultResponseData, @@ -29,8 +30,18 @@ pub trait ExternalVaultDelete: { } -/// trait ExternalVault -pub trait ExternalVault: - ConnectorCommon + ExternalVaultInsert + ExternalVaultRetrieve + ExternalVaultDelete +/// trait ExternalVaultDelete +pub trait ExternalVaultCreate: + ConnectorIntegration +{ +} + +/// trait ExternalVault +pub trait ExternalVault: + ConnectorCommon + + ExternalVaultInsert + + ExternalVaultRetrieve + + ExternalVaultDelete + + ExternalVaultCreate { } diff --git a/crates/hyperswitch_interfaces/src/api/vault_v2.rs b/crates/hyperswitch_interfaces/src/api/vault_v2.rs index 5509de5db8..069c20ed3e 100644 --- a/crates/hyperswitch_interfaces/src/api/vault_v2.rs +++ b/crates/hyperswitch_interfaces/src/api/vault_v2.rs @@ -2,7 +2,8 @@ use hyperswitch_domain_models::{ router_data_v2::flow_common_types::VaultConnectorFlowData, router_flow_types::vault::{ - ExternalVaultDeleteFlow, ExternalVaultInsertFlow, ExternalVaultRetrieveFlow, + ExternalVaultCreateFlow, ExternalVaultDeleteFlow, ExternalVaultInsertFlow, + ExternalVaultRetrieveFlow, }, router_request_types::VaultRequestData, router_response_types::VaultResponseData, @@ -43,8 +44,23 @@ pub trait ExternalVaultDeleteV2: { } -/// trait ExternalVaultV2 -pub trait ExternalVaultV2: - ConnectorCommon + ExternalVaultInsertV2 + ExternalVaultRetrieveV2 + ExternalVaultDeleteV2 +/// trait ExternalVaultDeleteV2 +pub trait ExternalVaultCreateV2: + ConnectorIntegrationV2< + ExternalVaultCreateFlow, + VaultConnectorFlowData, + VaultRequestData, + VaultResponseData, +> +{ +} + +/// trait ExternalVaultV2 +pub trait ExternalVaultV2: + ConnectorCommon + + ExternalVaultInsertV2 + + ExternalVaultRetrieveV2 + + ExternalVaultDeleteV2 + + ExternalVaultCreateV2 { } diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index e41e8cd9bc..e19580a85b 100644 --- a/crates/hyperswitch_interfaces/src/types.rs +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -19,6 +19,10 @@ use hyperswitch_domain_models::{ unified_authentication_service::{ Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, }, + vault::{ + ExternalVaultCreateFlow, ExternalVaultDeleteFlow, ExternalVaultInsertFlow, + ExternalVaultRetrieveFlow, + }, webhooks::VerifyWebhookSource, BillingConnectorInvoiceSync, }, @@ -40,7 +44,7 @@ use hyperswitch_domain_models::{ PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, PaymentsUpdateMetadataData, RefundsData, RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, SubmitEvidenceRequestData, - UploadFileRequestData, VerifyWebhookSourceRequestData, + UploadFileRequestData, VaultRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ revenue_recovery::{ @@ -49,7 +53,8 @@ use hyperswitch_domain_models::{ }, AcceptDisputeResponse, DefendDisputeResponse, MandateRevokeResponseData, PaymentsResponseData, RefundsResponseData, RetrieveFileResponse, SubmitEvidenceResponse, - TaxCalculationResponseData, UploadFileResponse, VerifyWebhookSourceResponseData, + TaxCalculationResponseData, UploadFileResponse, VaultResponseData, + VerifyWebhookSourceResponseData, }, }; #[cfg(feature = "payouts")] @@ -283,6 +288,19 @@ pub type BillingConnectorInvoiceSyncTypeV2 = dyn ConnectorIntegrationV2< BillingConnectorInvoiceSyncResponse, >; +/// Type alias for `ConnectorIntegration` +pub type ExternalVaultInsertType = + dyn ConnectorIntegration; +/// Type alias for `ConnectorIntegration` +pub type ExternalVaultRetrieveType = + dyn ConnectorIntegration; +/// Type alias for `ConnectorIntegration` +pub type ExternalVaultDeleteType = + dyn ConnectorIntegration; +/// Type alias for `ConnectorIntegration` +pub type ExternalVaultCreateType = + dyn ConnectorIntegration; + /// Proxy configuration structure #[derive(Debug, serde::Deserialize, Clone)] #[serde(default)] diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index f43712782f..deb966797d 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -268,6 +268,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::ConnectorType, api_models::enums::PayoutConnectors, api_models::enums::AuthenticationConnectors, + api_models::enums::VaultSdk, api_models::enums::Currency, api_models::enums::IntentStatus, api_models::enums::CaptureMethod, @@ -429,6 +430,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::AmountDetails, api_models::payments::AmountDetailsUpdate, api_models::payments::SessionToken, + api_models::payments::VaultSessionDetails, + api_models::payments::VgsSessionDetails, + api_models::payments::HyperswitchVaultSessionDetails, api_models::payments::ApplePaySessionResponse, api_models::payments::ThirdPartySdkSessionResponse, api_models::payments::NoThirdPartySdkSessionResponse, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index 748a2bcf83..a5af15325f 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -18,27 +18,28 @@ pub use hyperswitch_connectors::connectors::{ fiservemea::Fiservemea, fiuu, fiuu::Fiuu, forte, forte::Forte, getnet, getnet::Getnet, globalpay, globalpay::Globalpay, globepay, globepay::Globepay, gocardless, gocardless::Gocardless, gpayments, gpayments::Gpayments, helcim, helcim::Helcim, hipay, - hipay::Hipay, iatapay, iatapay::Iatapay, inespay, inespay::Inespay, itaubank, - itaubank::Itaubank, jpmorgan, jpmorgan::Jpmorgan, juspaythreedsserver, - juspaythreedsserver::Juspaythreedsserver, klarna, klarna::Klarna, mifinity, mifinity::Mifinity, - mollie, mollie::Mollie, moneris, moneris::Moneris, multisafepay, multisafepay::Multisafepay, - netcetera, netcetera::Netcetera, nexinets, nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, - nmi, nmi::Nmi, nomupay, nomupay::Nomupay, noon, noon::Noon, nordea, nordea::Nordea, novalnet, - novalnet::Novalnet, nuvei, nuvei::Nuvei, opayo, opayo::Opayo, opennode, opennode::Opennode, - paybox, paybox::Paybox, payeezy, payeezy::Payeezy, payme, payme::Payme, payone, payone::Payone, - paypal, paypal::Paypal, paystack, paystack::Paystack, payu, payu::Payu, placetopay, - placetopay::Placetopay, plaid, plaid::Plaid, powertranz, powertranz::Powertranz, prophetpay, - prophetpay::Prophetpay, rapyd, rapyd::Rapyd, razorpay, razorpay::Razorpay, recurly, - recurly::Recurly, redsys, redsys::Redsys, riskified, riskified::Riskified, shift4, - shift4::Shift4, signifyd, signifyd::Signifyd, square, square::Square, stax, stax::Stax, stripe, - stripe::Stripe, stripebilling, stripebilling::Stripebilling, taxjar, taxjar::Taxjar, - threedsecureio, threedsecureio::Threedsecureio, thunes, thunes::Thunes, tokenio, - tokenio::Tokenio, trustpay, trustpay::Trustpay, tsys, tsys::Tsys, - unified_authentication_service, unified_authentication_service::UnifiedAuthenticationService, - vgs, vgs::Vgs, volt, volt::Volt, wellsfargo, wellsfargo::Wellsfargo, wellsfargopayout, - wellsfargopayout::Wellsfargopayout, wise, wise::Wise, worldline, worldline::Worldline, - worldpay, worldpay::Worldpay, worldpayvantiv, worldpayvantiv::Worldpayvantiv, worldpayxml, - worldpayxml::Worldpayxml, xendit, xendit::Xendit, zen, zen::Zen, zsl, zsl::Zsl, + hipay::Hipay, hyperswitch_vault, hyperswitch_vault::HyperswitchVault, iatapay, + iatapay::Iatapay, inespay, inespay::Inespay, itaubank, itaubank::Itaubank, jpmorgan, + jpmorgan::Jpmorgan, juspaythreedsserver, juspaythreedsserver::Juspaythreedsserver, klarna, + klarna::Klarna, mifinity, mifinity::Mifinity, mollie, mollie::Mollie, moneris, + moneris::Moneris, multisafepay, multisafepay::Multisafepay, netcetera, netcetera::Netcetera, + nexinets, nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, nmi, nmi::Nmi, nomupay, + nomupay::Nomupay, noon, noon::Noon, nordea, nordea::Nordea, novalnet, novalnet::Novalnet, + nuvei, nuvei::Nuvei, opayo, opayo::Opayo, opennode, opennode::Opennode, paybox, paybox::Paybox, + payeezy, payeezy::Payeezy, payme, payme::Payme, payone, payone::Payone, paypal, paypal::Paypal, + paystack, paystack::Paystack, payu, payu::Payu, placetopay, placetopay::Placetopay, plaid, + plaid::Plaid, powertranz, powertranz::Powertranz, prophetpay, prophetpay::Prophetpay, rapyd, + rapyd::Rapyd, razorpay, razorpay::Razorpay, recurly, recurly::Recurly, redsys, redsys::Redsys, + riskified, riskified::Riskified, shift4, shift4::Shift4, signifyd, signifyd::Signifyd, square, + square::Square, stax, stax::Stax, stripe, stripe::Stripe, stripebilling, + stripebilling::Stripebilling, taxjar, taxjar::Taxjar, threedsecureio, + threedsecureio::Threedsecureio, thunes, thunes::Thunes, tokenio, tokenio::Tokenio, trustpay, + trustpay::Trustpay, tsys, tsys::Tsys, unified_authentication_service, + unified_authentication_service::UnifiedAuthenticationService, vgs, vgs::Vgs, volt, volt::Volt, + wellsfargo, wellsfargo::Wellsfargo, wellsfargopayout, wellsfargopayout::Wellsfargopayout, wise, + wise::Wise, worldline, worldline::Worldline, worldpay, worldpay::Worldpay, worldpayvantiv, + worldpayvantiv::Worldpayvantiv, worldpayxml, worldpayxml::Worldpayxml, xendit, xendit::Xendit, + zen, zen::Zen, zsl, zsl::Zsl, }; #[cfg(feature = "dummy_connector")] diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 0dc5d5fdd3..0a8af98dcc 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1538,6 +1538,12 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { helcim::transformers::HelcimAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::HyperswitchVault => { + hyperswitch_vault::transformers::HyperswitchVaultAuthType::try_from( + self.auth_type, + )?; + Ok(()) + } api_enums::Connector::Iatapay => { iatapay::transformers::IatapayAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 96ac030f57..d3d8b31cb0 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -1878,6 +1878,7 @@ pub async fn vault_payment_method_external( &merchant_connector_account, Some(pmd.clone()), None, + None, ) .await?; @@ -1936,7 +1937,8 @@ pub fn get_vault_response_for_insert_payment_method_data( entity_id: None, }), types::VaultResponseData::ExternalVaultRetrieveResponse { .. } - | types::VaultResponseData::ExternalVaultDeleteResponse { .. } => { + | types::VaultResponseData::ExternalVaultDeleteResponse { .. } + | types::VaultResponseData::ExternalVaultCreateResponse { .. } => { Err(report!(errors::ApiErrorResponse::InternalServerError) .attach_printable("Invalid Vault Response")) } diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index 9a2c096d66..3c231ad3f5 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -1398,6 +1398,7 @@ pub async fn retrieve_payment_method_from_vault_external( &merchant_connector_account, None, connector_vault_id, + None, ) .await?; @@ -1453,7 +1454,8 @@ pub fn get_vault_response_for_retrieve_payment_method_data( Ok(pm_types::VaultRetrieveResponse { data: vault_data }) } types::VaultResponseData::ExternalVaultInsertResponse { .. } - | types::VaultResponseData::ExternalVaultDeleteResponse { .. } => { + | types::VaultResponseData::ExternalVaultDeleteResponse { .. } + | types::VaultResponseData::ExternalVaultCreateResponse { .. } => { Err(report!(errors::ApiErrorResponse::InternalServerError) .attach_printable("Invalid Vault Response")) } @@ -1559,6 +1561,7 @@ pub async fn delete_payment_method_data_from_vault_external( &merchant_connector_account, None, Some(connector_vault_id), + None, ) .await?; @@ -1619,7 +1622,8 @@ pub fn get_vault_response_for_delete_payment_method_data( }) } types::VaultResponseData::ExternalVaultInsertResponse { .. } - | types::VaultResponseData::ExternalVaultRetrieveResponse { .. } => { + | types::VaultResponseData::ExternalVaultRetrieveResponse { .. } + | types::VaultResponseData::ExternalVaultCreateResponse { .. } => { Err(report!(errors::ApiErrorResponse::InternalServerError) .attach_printable("Invalid Vault Response")) } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index aad07256fd..8c21739cde 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -13,6 +13,8 @@ pub mod session_operation; pub mod tokenization; pub mod transformers; pub mod types; +#[cfg(feature = "v2")] +pub mod vault_session; #[cfg(feature = "olap")] use std::collections::HashMap; use std::{ @@ -8501,7 +8503,6 @@ pub trait OperationSessionGetters { fn get_capture_method(&self) -> Option; fn get_merchant_connector_id_in_attempt(&self) -> Option; - #[cfg(feature = "v1")] fn get_connector_customer_id(&self) -> Option; fn get_whole_connector_response(&self) -> Option; @@ -8515,6 +8516,9 @@ pub trait OperationSessionGetters { fn get_pre_routing_result( &self, ) -> Option>; + + #[cfg(feature = "v2")] + fn get_optional_external_vault_session_details(&self) -> Option; } pub trait OperationSessionSetters { @@ -8574,6 +8578,12 @@ pub trait OperationSessionSetters { #[cfg(feature = "v1")] fn set_vault_operation(&mut self, vault_operation: domain_payments::VaultOperation); + + #[cfg(feature = "v2")] + fn set_vault_session_details( + &mut self, + external_vault_session_details: Option, + ); } #[cfg(feature = "v1")] @@ -8724,7 +8734,6 @@ impl OperationSessionGetters for PaymentData { self.vault_operation.as_ref() } - #[cfg(feature = "v1")] fn get_connector_customer_id(&self) -> Option { self.connector_customer_id.clone() } @@ -8982,6 +8991,10 @@ impl OperationSessionGetters for PaymentIntentData { todo!() } + fn get_connector_customer_id(&self) -> Option { + self.connector_customer_id.clone() + } + fn get_billing_address(&self) -> Option { todo!() } @@ -9027,6 +9040,10 @@ impl OperationSessionGetters for PaymentIntentData { ) -> Option> { None } + + fn get_optional_external_vault_session_details(&self) -> Option { + self.vault_session_details.clone() + } } #[cfg(feature = "v2")] @@ -9075,8 +9092,8 @@ impl OperationSessionSetters for PaymentIntentData { todo!() } - fn set_connector_customer_id(&mut self, _customer_id: Option) { - todo!() + fn set_connector_customer_id(&mut self, customer_id: Option) { + self.connector_customer_id = customer_id; } fn push_sessions_token(&mut self, token: api::SessionToken) { @@ -9131,6 +9148,13 @@ impl OperationSessionSetters for PaymentIntentData { fn set_connector_in_payment_attempt(&mut self, _connector: Option) { todo!() } + + fn set_vault_session_details( + &mut self, + vault_session_details: Option, + ) { + self.vault_session_details = vault_session_details; + } } #[cfg(feature = "v2")] @@ -9237,6 +9261,10 @@ impl OperationSessionGetters for PaymentConfirmData { self.payment_attempt.merchant_connector_id.clone() } + fn get_connector_customer_id(&self) -> Option { + todo!() + } + fn get_billing_address(&self) -> Option { todo!() } @@ -9285,6 +9313,10 @@ impl OperationSessionGetters for PaymentConfirmData { .clone() .and_then(|pre_routing_algorithm| pre_routing_algorithm.pre_routing_results) } + + fn get_optional_external_vault_session_details(&self) -> Option { + todo!() + } } #[cfg(feature = "v2")] @@ -9391,6 +9423,13 @@ impl OperationSessionSetters for PaymentConfirmData { fn set_connector_in_payment_attempt(&mut self, connector: Option) { self.payment_attempt.connector = connector; } + + fn set_vault_session_details( + &mut self, + external_vault_session_details: Option, + ) { + todo!() + } } #[cfg(feature = "v2")] @@ -9497,6 +9536,10 @@ impl OperationSessionGetters for PaymentStatusData { todo!() } + fn get_connector_customer_id(&self) -> Option { + todo!() + } + fn get_billing_address(&self) -> Option { todo!() } @@ -9542,6 +9585,10 @@ impl OperationSessionGetters for PaymentStatusData { ) -> Option> { None } + + fn get_optional_external_vault_session_details(&self) -> Option { + todo!() + } } #[cfg(feature = "v2")] @@ -9647,6 +9694,13 @@ impl OperationSessionSetters for PaymentStatusData { fn set_connector_in_payment_attempt(&mut self, connector: Option) { todo!() } + + fn set_vault_session_details( + &mut self, + external_vault_session_details: Option, + ) { + todo!() + } } #[cfg(feature = "v2")] @@ -9754,6 +9808,10 @@ impl OperationSessionGetters for PaymentCaptureData { todo!() } + fn get_connector_customer_id(&self) -> Option { + todo!() + } + fn get_billing_address(&self) -> Option { todo!() } @@ -9799,6 +9857,10 @@ impl OperationSessionGetters for PaymentCaptureData { ) -> Option> { None } + + fn get_optional_external_vault_session_details(&self) -> Option { + todo!() + } } #[cfg(feature = "v2")] @@ -9904,4 +9966,11 @@ impl OperationSessionSetters for PaymentCaptureData { fn set_connector_in_payment_attempt(&mut self, connector: Option) { todo!() } + + fn set_vault_session_details( + &mut self, + external_vault_session_details: Option, + ) { + todo!() + } } diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 58cd601558..a639805cde 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -19,7 +19,8 @@ use hyperswitch_domain_models::router_flow_types::{ }; #[cfg(feature = "dummy_connector")] use hyperswitch_domain_models::router_flow_types::{ - ExternalVaultDeleteFlow, ExternalVaultInsertFlow, ExternalVaultRetrieveFlow, + ExternalVaultCreateFlow, ExternalVaultDeleteFlow, ExternalVaultInsertFlow, + ExternalVaultRetrieveFlow, }; use hyperswitch_domain_models::{ mandates::CustomerAcceptance, @@ -30,7 +31,8 @@ use hyperswitch_domain_models::{ }; #[cfg(feature = "dummy_connector")] use hyperswitch_interfaces::api::vault::{ - ExternalVault, ExternalVaultDelete, ExternalVaultInsert, ExternalVaultRetrieve, + ExternalVault, ExternalVaultCreate, ExternalVaultDelete, ExternalVaultInsert, + ExternalVaultRetrieve, }; use hyperswitch_interfaces::api::{ payouts::Payouts, UasAuthentication, UasAuthenticationConfirmation, UasPostAuthentication, @@ -897,3 +899,15 @@ impl > for connector::DummyConnector { } + +#[cfg(feature = "dummy_connector")] +impl ExternalVaultCreate for connector::DummyConnector {} +#[cfg(feature = "dummy_connector")] +impl + services::ConnectorIntegration< + ExternalVaultCreateFlow, + types::VaultRequestData, + types::VaultResponseData, + > for connector::DummyConnector +{ +} diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index dcf424b4c8..6e9ed9e2e6 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -15,7 +15,7 @@ use crate::{ consts::PROTOCOL, core::{ errors::{self, ConnectorErrorExt, RouterResult}, - payments::{self, access_token, helpers, transformers, PaymentData}, + payments::{self, access_token, customers, helpers, transformers, PaymentData}, }, headers, logger, routes::{self, app::settings, metrics}, @@ -148,6 +148,20 @@ impl Feature for types::PaymentsSessio access_token::add_access_token(state, connector, merchant_context, self, creds_identifier) .await } + + async fn create_connector_customer<'a>( + &self, + state: &routes::SessionState, + connector: &api::ConnectorData, + ) -> RouterResult> { + customers::create_connector_customer( + state, + connector, + self, + types::ConnectorCustomerData::try_from(self)?, + ) + .await + } } /// This function checks if for a given connector, payment_method and payment_method_type, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index a166c54139..94a02248aa 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -3973,6 +3973,15 @@ impl MerchantConnectorAccountType { Self::CacheVal(_) => None, } } + + pub fn get_inner_db_merchant_connector_account( + &self, + ) -> Option<&domain::MerchantConnectorAccount> { + match self { + Self::DbVal(db_val) => Some(db_val), + Self::CacheVal(_) => None, + } + } } /// Query for merchant connector account either by business label or profile id @@ -7313,3 +7322,115 @@ pub async fn allow_payment_update_enabled_for_client_auth( services::AuthFlow::Merchant => Ok(()), } } + +/// Query for merchant connector account either by business label +#[cfg(feature = "v2")] +#[instrument(skip_all)] +pub async fn get_merchant_connector_account_v2( + state: &SessionState, + merchant_id: &id_type::MerchantId, + creds_identifier: Option<&str>, + key_store: &domain::MerchantKeyStore, + merchant_connector_id: Option<&id_type::MerchantConnectorAccountId>, +) -> RouterResult { + let db = &*state.store; + let key_manager_state: &KeyManagerState = &state.into(); + match creds_identifier { + Some(creds_identifier) => { + let key = merchant_id.get_creds_identifier_key(creds_identifier); + let cloned_key = key.clone(); + let redis_fetch = || async { + db.get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection") + .async_and_then(|redis| async move { + redis + .get_and_deserialize_key(&key.as_str().into(), "String") + .await + .change_context( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: key.clone(), + }, + ) + .attach_printable(key.clone() + ": Not found in Redis") + }) + .await + }; + + let db_fetch = || async { + db.find_config_by_key(cloned_key.as_str()) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: cloned_key.to_owned(), + }, + ) + }; + + let mca_config: String = redis_fetch() + .await + .map_or_else( + |_| { + Either::Left(async { + match db_fetch().await { + Ok(config_entry) => Ok(config_entry.config), + Err(e) => Err(e), + } + }) + }, + |result| Either::Right(async { Ok(result) }), + ) + .await?; + + let private_key = state + .conf + .jwekey + .get_inner() + .tunnel_private_key + .peek() + .as_bytes(); + + let decrypted_mca = services::decrypt_jwe(mca_config.as_str(), services::KeyIdCheck::SkipKeyIdCheck, private_key, jwe::RSA_OAEP_256) + .await + .change_context(errors::ApiErrorResponse::UnprocessableEntity{ + message: "decoding merchant_connector_details failed due to invalid data format!".into()}) + .attach_printable( + "Failed to decrypt merchant_connector_details sent in request and then put in cache", + )?; + + let res = String::into_bytes(decrypted_mca) + .parse_struct("MerchantConnectorDetails") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Failed to parse merchant_connector_details sent in request and then put in cache", + )?; + + Ok(MerchantConnectorAccountType::CacheVal(res)) + } + None => { + let mca: RouterResult = if let Some( + merchant_connector_id, + ) = merchant_connector_id + { + db.find_merchant_connector_account_by_id( + &state.into(), + merchant_connector_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_connector_id.get_string_repr().to_string(), + }, + ) + } else { + Err(errors::ApiErrorResponse::MissingRequiredField { + field_name: "merchant_connector_id", + }).attach_printable( + "merchant_connector_id is required when creds_identifier is not provided for get_merchant_connector_account_v2", + ) + }; + mca.map(Box::new).map(MerchantConnectorAccountType::DbVal) + } + } +} diff --git a/crates/router/src/core/payments/operations/payment_create_intent.rs b/crates/router/src/core/payments/operations/payment_create_intent.rs index 1cb95a2b92..10076585a9 100644 --- a/crates/router/src/core/payments/operations/payment_create_intent.rs +++ b/crates/router/src/core/payments/operations/payment_create_intent.rs @@ -176,6 +176,8 @@ impl payment_intent, client_secret: Some(client_secret.secret), sessions_token: vec![], + vault_session_details: None, + connector_customer_id: None, }; let get_trackers_response = operations::GetTrackerResponse { payment_data }; diff --git a/crates/router/src/core/payments/operations/payment_get_intent.rs b/crates/router/src/core/payments/operations/payment_get_intent.rs index c9bd018a4a..9003bd7308 100644 --- a/crates/router/src/core/payments/operations/payment_get_intent.rs +++ b/crates/router/src/core/payments/operations/payment_get_intent.rs @@ -108,6 +108,8 @@ impl GetTracker, Payme // todo : add a way to fetch client secret if required client_secret: None, sessions_token: vec![], + vault_session_details: None, + connector_customer_id: None, }; let get_trackers_response = operations::GetTrackerResponse { payment_data }; diff --git a/crates/router/src/core/payments/operations/payment_session_intent.rs b/crates/router/src/core/payments/operations/payment_session_intent.rs index e4b85f83c5..77cc6dc1a9 100644 --- a/crates/router/src/core/payments/operations/payment_session_intent.rs +++ b/crates/router/src/core/payments/operations/payment_session_intent.rs @@ -146,6 +146,8 @@ impl GetTracker, Payme payment_intent, client_secret: None, sessions_token: vec![], + vault_session_details: None, + connector_customer_id: None, }; let get_trackers_response = operations::GetTrackerResponse { payment_data }; @@ -164,9 +166,9 @@ impl UpdateTracker, PaymentsS state: &'b SessionState, _req_state: ReqState, mut payment_data: payments::PaymentIntentData, - _customer: Option, + customer: Option, storage_scheme: enums::MerchantStorageScheme, - _updated_customer: Option, + updated_customer: Option, key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: hyperswitch_domain_models::payments::HeaderPayload, diff --git a/crates/router/src/core/payments/operations/payment_update_intent.rs b/crates/router/src/core/payments/operations/payment_update_intent.rs index b791dab22c..9a397e248c 100644 --- a/crates/router/src/core/payments/operations/payment_update_intent.rs +++ b/crates/router/src/core/payments/operations/payment_update_intent.rs @@ -300,6 +300,8 @@ impl GetTracker, PaymentsUpda payment_intent, client_secret: None, sessions_token: vec![], + vault_session_details: None, + connector_customer_id: None, }; let get_trackers_response = operations::GetTrackerResponse { payment_data }; diff --git a/crates/router/src/core/payments/session_operation.rs b/crates/router/src/core/payments/session_operation.rs index d018ac1910..3ed015309e 100644 --- a/crates/router/src/core/payments/session_operation.rs +++ b/crates/router/src/core/payments/session_operation.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::{fmt::Debug, str::FromStr}; pub use common_enums::enums::CallConnectorAction; use common_utils::id_type; @@ -6,27 +6,42 @@ use error_stack::ResultExt; pub use hyperswitch_domain_models::{ mandates::{CustomerAcceptance, MandateData}, payment_address::PaymentAddress, - payments::HeaderPayload, + payments::{HeaderPayload, PaymentIntentData}, router_data::{PaymentMethodToken, RouterData}, + router_data_v2::{flow_common_types::VaultConnectorFlowData, RouterDataV2}, + router_flow_types::ExternalVaultCreateFlow, router_request_types::CustomerDetails, + types::{VaultRouterData, VaultRouterDataV2}, }; -use router_env::{instrument, tracing}; +use hyperswitch_interfaces::{ + api::Connector as ConnectorTrait, + connector_integration_v2::{ConnectorIntegrationV2, ConnectorV2}, +}; +use masking::ExposeInterface; +use router_env::{env::Env, instrument, tracing}; use crate::{ core::{ errors::{self, utils::StorageErrorExt, RouterResult}, payments::{ - call_multiple_connectors_service, + self as payments_core, call_multiple_connectors_service, flows::{ConstructFlowSpecificData, Feature}, - operations, + helpers, helpers as payment_helpers, operations, operations::{BoxedOperation, Operation}, - transformers, OperationSessionGetters, OperationSessionSetters, + transformers, vault_session, OperationSessionGetters, OperationSessionSetters, }, + utils as core_utils, }, + db::errors::ConnectorErrorExt, errors::RouterResponse, routes::{app::ReqState, SessionState}, - services, - types::{self as router_types, api, domain}, + services::{self, connector_integration_interface::RouterDataConversion}, + types::{ + self as router_types, + api::{self, enums as api_enums, ConnectorCommon}, + domain, storage, + }, + utils::{OptionExt, ValueExt}, }; #[cfg(feature = "v2")] @@ -143,6 +158,18 @@ where .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound) .attach_printable("Failed while fetching/creating customer")?; + vault_session::populate_vault_session_details( + state, + req_state.clone(), + &customer, + &merchant_context, + &operation, + &profile, + &mut payment_data, + header_payload.clone(), + ) + .await?; + let connector = operation .to_domain()? .perform_routing( diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 4dff52acb4..fee5440a60 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -713,10 +713,30 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>( .attach_printable( "Invalid global customer generated, not able to convert to reference id", )?; + let billing_address = payment_data + .payment_intent + .billing_address + .as_ref() + .map(|billing_address| billing_address.clone().into_inner()); + // fetch email from customer or billing address (fallback) let email = customer .as_ref() .and_then(|customer| customer.email.clone()) - .map(pii::Email::from); + .map(pii::Email::from) + .or(billing_address + .as_ref() + .and_then(|address| address.email.clone())); + // fetch customer name from customer or billing address (fallback) + let customer_name = customer + .as_ref() + .and_then(|customer| customer.name.clone()) + .map(|name| name.into_inner()) + .or(billing_address.and_then(|address| { + address + .address + .as_ref() + .and_then(|address_details| address_details.get_optional_full_name()) + })); let order_details = payment_data .payment_intent .order_details @@ -770,6 +790,7 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>( email, minor_amount: payment_data.payment_intent.amount_details.order_amount, apple_pay_recurring_details, + customer_name, }; // TODO: evaluate the fields in router data, if they are required or not @@ -1636,6 +1657,7 @@ where Self { session_token: payment_data.get_sessions_token(), payment_id: payment_data.get_payment_intent().id.clone(), + vault_details: payment_data.get_optional_external_vault_session_details(), }, vec![], ))) @@ -4087,6 +4109,7 @@ impl TryFrom> for types::PaymentsSessionD surcharge_details: payment_data.surcharge_details, email: payment_data.email, apple_pay_recurring_details, + customer_name: None, }) } } @@ -4172,6 +4195,7 @@ impl TryFrom> for types::PaymentsSessionD email: payment_data.email, surcharge_details: payment_data.surcharge_details, apple_pay_recurring_details, + customer_name: None, }) } } diff --git a/crates/router/src/core/payments/vault_session.rs b/crates/router/src/core/payments/vault_session.rs new file mode 100644 index 0000000000..bb9b714a9b --- /dev/null +++ b/crates/router/src/core/payments/vault_session.rs @@ -0,0 +1,385 @@ +use std::{fmt::Debug, str::FromStr}; + +pub use common_enums::enums::CallConnectorAction; +use common_utils::id_type; +use error_stack::ResultExt; +pub use hyperswitch_domain_models::{ + mandates::{CustomerAcceptance, MandateData}, + payment_address::PaymentAddress, + payments::{HeaderPayload, PaymentIntentData}, + router_data::{PaymentMethodToken, RouterData}, + router_data_v2::{flow_common_types::VaultConnectorFlowData, RouterDataV2}, + router_flow_types::ExternalVaultCreateFlow, + router_request_types::CustomerDetails, + types::{VaultRouterData, VaultRouterDataV2}, +}; +use hyperswitch_interfaces::{ + api::Connector as ConnectorTrait, + connector_integration_v2::{ConnectorIntegrationV2, ConnectorV2}, +}; +use masking::ExposeInterface; +use router_env::{env::Env, instrument, tracing}; + +use crate::{ + core::{ + errors::{self, utils::StorageErrorExt, RouterResult}, + payments::{ + self as payments_core, call_multiple_connectors_service, customers, + flows::{ConstructFlowSpecificData, Feature}, + helpers, helpers as payment_helpers, operations, + operations::{BoxedOperation, Operation}, + transformers, OperationSessionGetters, OperationSessionSetters, + }, + utils as core_utils, + }, + db::errors::ConnectorErrorExt, + errors::RouterResponse, + routes::{app::ReqState, SessionState}, + services::{self, connector_integration_interface::RouterDataConversion}, + types::{ + self as router_types, + api::{self, enums as api_enums, ConnectorCommon}, + domain, storage, + }, + utils::{OptionExt, ValueExt}, +}; + +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +pub async fn populate_vault_session_details( + state: &SessionState, + req_state: ReqState, + customer: &Option, + merchant_context: &domain::MerchantContext, + operation: &BoxedOperation<'_, F, ApiRequest, D>, + profile: &domain::Profile, + payment_data: &mut D, + header_payload: HeaderPayload, +) -> RouterResult<()> +where + F: Send + Clone + Sync, + RouterDReq: Send + Sync, + + // To create connector flow specific interface data + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature + Send, + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + let is_external_vault_sdk_enabled = profile.is_vault_sdk_enabled(); + + if is_external_vault_sdk_enabled { + let external_vault_source = profile + .external_vault_connector_details + .as_ref() + .map(|details| &details.vault_connector_id); + + let merchant_connector_account = helpers::get_merchant_connector_account_v2( + state, + merchant_context.get_merchant_account().get_id(), + None, + merchant_context.get_merchant_key_store(), + external_vault_source, + ) + .await?; + + let updated_customer = call_create_connector_customer_if_required( + state, + customer, + merchant_context, + &merchant_connector_account, + payment_data, + ) + .await?; + + if let Some((customer, updated_customer)) = customer.clone().zip(updated_customer) { + let db = &*state.store; + let customer_id = customer.get_id().clone(); + let customer_merchant_id = customer.merchant_id.clone(); + + let _updated_customer = db + .update_customer_by_global_id( + &state.into(), + &customer_id, + customer, + &customer_merchant_id, + updated_customer, + merchant_context.get_merchant_key_store(), + merchant_context.get_merchant_account().storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to update customer during Vault session")?; + }; + + let vault_session_details = generate_vault_session_details( + state, + merchant_context, + &merchant_connector_account, + payment_data.get_connector_customer_id(), + ) + .await?; + + payment_data.set_vault_session_details(vault_session_details); + } + Ok(()) +} + +#[cfg(feature = "v2")] +pub async fn call_create_connector_customer_if_required( + state: &SessionState, + customer: &Option, + merchant_context: &domain::MerchantContext, + merchant_connector_account_type: &payment_helpers::MerchantConnectorAccountType, + payment_data: &mut D, +) -> RouterResult> +where + F: Send + Clone + Sync, + Req: Send + Sync, + + // To create connector flow specific interface data + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature + Send, + + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + let db_merchant_connector_account = + merchant_connector_account_type.get_inner_db_merchant_connector_account(); + + match db_merchant_connector_account { + Some(merchant_connector_account) => { + let connector_name = merchant_connector_account.get_connector_name_as_string(); + let merchant_connector_id = merchant_connector_account.get_id(); + + let connector = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + &connector_name, + api::GetToken::Connector, + Some(merchant_connector_id.clone()), + )?; + + let (should_call_connector, existing_connector_customer_id) = + customers::should_call_connector_create_customer( + state, + &connector, + customer, + &merchant_connector_id, + ); + + if should_call_connector { + // Create customer at connector and update the customer table to store this data + let router_data = payment_data + .construct_router_data( + state, + connector.connector.id(), + merchant_context, + customer, + merchant_connector_account, + None, + None, + ) + .await?; + + let connector_customer_id = router_data + .create_connector_customer(state, &connector) + .await?; + + let customer_update = customers::update_connector_customer_in_customers( + merchant_connector_id, + customer.as_ref(), + connector_customer_id.clone(), + ) + .await; + + payment_data.set_connector_customer_id(connector_customer_id); + Ok(customer_update) + } else { + // Customer already created in previous calls use the same value, no need to update + payment_data.set_connector_customer_id( + existing_connector_customer_id.map(ToOwned::to_owned), + ); + Ok(None) + } + } + None => { + router_env::logger::error!( + "Merchant connector account is missing, cannot create customer for vault session" + ); + Err(errors::ApiErrorResponse::InternalServerError.into()) + } + } +} + +#[cfg(feature = "v2")] +pub async fn generate_vault_session_details( + state: &SessionState, + merchant_context: &domain::MerchantContext, + merchant_connector_account_type: &payment_helpers::MerchantConnectorAccountType, + connector_customer_id: Option, +) -> RouterResult> { + let connector_name = merchant_connector_account_type + .get_connector_name() + .map(|name| name.to_string()) + .ok_or(errors::ApiErrorResponse::InternalServerError)?; // should not panic since we should always have a connector name + let connector = api_enums::VaultConnectors::from_str(&connector_name) + .change_context(errors::ApiErrorResponse::InternalServerError)?; + let connector_auth_type: router_types::ConnectorAuthType = merchant_connector_account_type + .get_connector_account_details() + .parse_value("ConnectorAuthType") + .map_err(|err| { + err.change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse connector auth type") + })?; + + match (connector, connector_auth_type) { + // create session for vgs vault + ( + api_enums::VaultConnectors::Vgs, + router_types::ConnectorAuthType::SignatureKey { api_secret, .. }, + ) => { + let sdk_env = match state.conf.env { + Env::Sandbox | Env::Development => "sandbox", + Env::Production => "live", + } + .to_string(); + Ok(Some(api::VaultSessionDetails::Vgs( + api::VgsSessionDetails { + external_vault_id: api_secret, + sdk_env, + }, + ))) + } + // create session for hyperswitch vault + ( + api_enums::VaultConnectors::HyperswitchVault, + router_types::ConnectorAuthType::SignatureKey { + key1, api_secret, .. + }, + ) => { + generate_hyperswitch_vault_session_details( + state, + merchant_context, + merchant_connector_account_type, + connector_customer_id, + connector_name, + key1, + api_secret, + ) + .await + } + _ => { + router_env::logger::warn!( + "External vault session creation is not supported for connector: {}", + connector_name + ); + Ok(None) + } + } +} + +async fn generate_hyperswitch_vault_session_details( + state: &SessionState, + merchant_context: &domain::MerchantContext, + merchant_connector_account_type: &payment_helpers::MerchantConnectorAccountType, + connector_customer_id: Option, + connector_name: String, + vault_publishable_key: masking::Secret, + vault_profile_id: masking::Secret, +) -> RouterResult> { + let connector_response = call_external_vault_create( + state, + merchant_context, + connector_name, + merchant_connector_account_type, + connector_customer_id, + ) + .await?; + + match connector_response.response { + Ok(router_types::VaultResponseData::ExternalVaultCreateResponse { + session_id, + client_secret, + }) => Ok(Some(api::VaultSessionDetails::HyperswitchVault( + api::HyperswitchVaultSessionDetails { + payment_method_session_id: session_id, + client_secret, + publishable_key: vault_publishable_key, + profile_id: vault_profile_id, + }, + ))), + Ok(_) => { + router_env::logger::warn!("Unexpected response from external vault create API"); + Err(errors::ApiErrorResponse::InternalServerError.into()) + } + Err(err) => { + router_env::logger::error!(error_response_from_external_vault_create=?err); + Err(errors::ApiErrorResponse::InternalServerError.into()) + } + } +} + +#[cfg(feature = "v2")] +async fn call_external_vault_create( + state: &SessionState, + merchant_context: &domain::MerchantContext, + connector_name: String, + merchant_connector_account_type: &payment_helpers::MerchantConnectorAccountType, + connector_customer_id: Option, +) -> RouterResult> +where + dyn ConnectorTrait + Sync: services::api::ConnectorIntegration< + ExternalVaultCreateFlow, + router_types::VaultRequestData, + router_types::VaultResponseData, + >, + dyn ConnectorV2 + Sync: ConnectorIntegrationV2< + ExternalVaultCreateFlow, + VaultConnectorFlowData, + router_types::VaultRequestData, + router_types::VaultResponseData, + >, +{ + let connector_data: api::ConnectorData = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + &connector_name, + api::GetToken::Connector, + merchant_connector_account_type.get_mca_id(), + )?; + + let mut router_data = core_utils::construct_vault_router_data( + state, + merchant_context.get_merchant_account(), + merchant_connector_account_type, + None, + None, + connector_customer_id, + ) + .await?; + + let mut old_router_data = VaultConnectorFlowData::to_old_router_data(router_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Cannot construct router data for making the external vault create api call", + )?; + + let connector_integration: services::BoxedVaultConnectorIntegrationInterface< + ExternalVaultCreateFlow, + router_types::VaultRequestData, + router_types::VaultResponseData, + > = connector_data.connector.get_connector_integration(); + services::execute_connector_processing_step( + state, + connector_integration, + &old_router_data, + CallConnectorAction::Trigger, + None, + None, + ) + .await + .to_vault_failed_response() +} diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 712d092fc7..b357465bc6 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -15,6 +15,8 @@ use common_utils::{ types::{keymanager::KeyManagerState, ConnectorTransactionIdTrait, MinorUnit}, }; use error_stack::{report, ResultExt}; +#[cfg(feature = "v2")] +use hyperswitch_domain_models::types::VaultRouterData; use hyperswitch_domain_models::{ merchant_connector_account::MerchantConnectorAccount, payment_address::PaymentAddress, router_data::ErrorResponse, router_request_types, types::OrderDetailsWithAmount, @@ -2041,6 +2043,7 @@ pub async fn construct_vault_router_data( merchant_connector_account: &payment_helpers::MerchantConnectorAccountType, payment_method_vaulting_data: Option, connector_vault_id: Option, + connector_customer_id: Option, ) -> RouterResult> { let connector_name = merchant_connector_account .get_connector_name() @@ -2063,6 +2066,7 @@ pub async fn construct_vault_router_data( request: types::VaultRequestData { payment_method_vaulting_data, connector_vault_id, + connector_customer_id, }, response: Ok(types::VaultResponseData::default()), }; diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index c4bc55bb83..c947bcd265 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -478,6 +478,9 @@ impl ConnectorData { enums::Connector::Helcim => { Ok(ConnectorEnum::Old(Box::new(connector::Helcim::new()))) } + enums::Connector::HyperswitchVault => { + Ok(ConnectorEnum::Old(Box::new(&connector::HyperswitchVault))) + } enums::Connector::Iatapay => { Ok(ConnectorEnum::Old(Box::new(connector::Iatapay::new()))) } diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 325956acb8..7379794aaa 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -14,10 +14,10 @@ pub use api_models::{ payments::{ AcceptanceType, Address, AddressDetails, Amount, AuthenticationForStartResponse, Card, CryptoData, CustomerAcceptance, CustomerDetails, CustomerDetailsResponse, - MandateAmountData, MandateData, MandateTransactionType, MandateType, - MandateValidationFields, NextActionType, OnlineMandate, OpenBankingSessionToken, - PayLaterData, PaymentIdType, PaymentListConstraints, PaymentListFilters, - PaymentListFiltersV2, PaymentMethodData, PaymentMethodDataRequest, + HyperswitchVaultSessionDetails, MandateAmountData, MandateData, MandateTransactionType, + MandateType, MandateValidationFields, NextActionType, OnlineMandate, + OpenBankingSessionToken, PayLaterData, PaymentIdType, PaymentListConstraints, + PaymentListFilters, PaymentListFiltersV2, PaymentMethodData, PaymentMethodDataRequest, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, @@ -29,7 +29,7 @@ pub use api_models::{ PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, PaymentsStartRequest, PaymentsUpdateMetadataRequest, PaymentsUpdateMetadataResponse, PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, UrlDetails, - VerifyRequest, VerifyResponse, WalletData, + VaultSessionDetails, VerifyRequest, VerifyResponse, VgsSessionDetails, WalletData, }, }; use error_stack::ResultExt; diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index e1607c3b6b..66e9c0963d 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -264,6 +264,11 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { } api_enums::Connector::Hipay => Self::Hipay, api_enums::Connector::Helcim => Self::Helcim, + api_enums::Connector::HyperswitchVault => { + Err(common_utils::errors::ValidationError::InvalidValue { + message: "Hyperswitch Vault is not a routable connector".to_string(), + })? + } api_enums::Connector::Iatapay => Self::Iatapay, api_enums::Connector::Inespay => Self::Inespay, api_enums::Connector::Itaubank => Self::Itaubank, @@ -2073,6 +2078,7 @@ impl ForeignFrom fn foreign_from(item: api_models::admin::ExternalVaultConnectorDetails) -> Self { Self { vault_connector_id: item.vault_connector_id, + vault_sdk: item.vault_sdk, } } } @@ -2083,6 +2089,7 @@ impl ForeignFrom fn foreign_from(item: diesel_models::business_profile::ExternalVaultConnectorDetails) -> Self { Self { vault_connector_id: item.vault_connector_id, + vault_sdk: item.vault_sdk, } } } diff --git a/crates/router/tests/connectors/hyperswitch_vault.rs b/crates/router/tests/connectors/hyperswitch_vault.rs new file mode 100644 index 0000000000..8f8fd5f169 --- /dev/null +++ b/crates/router/tests/connectors/hyperswitch_vault.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct HyperswitchVaultTest; +impl ConnectorActions for HyperswitchVaultTest {} +impl utils::Connector for HyperswitchVaultTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::HyperswitchVault; + utils::construct_connector_data_old( + Box::new(&HyperswitchVault), + types::Connector::HyperswitchVault, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .hyperswitch_vault + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "hyperswitch_vault".to_string() + } +} + +static CONNECTOR: HyperswitchVaultTest = HyperswitchVaultTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: 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: PaymentMethodData::Card(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: PaymentMethodData::Card(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: PaymentMethodData::Card(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/main.rs b/crates/router/tests/connectors/main.rs index 3b855b82a4..1d499d5829 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -47,6 +47,7 @@ mod gocardless; mod gpayments; mod helcim; mod hipay; +mod hyperswitch_vault; mod iatapay; mod inespay; mod itaubank; diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 543d245114..3cfd3b277e 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -1099,7 +1099,9 @@ impl Default for PaymentRefundType { impl Default for CustomerType { fn default() -> Self { let data = types::ConnectorCustomerData { - payment_method_data: types::domain::PaymentMethodData::Card(CCardType::default().0), + payment_method_data: Some(types::domain::PaymentMethodData::Card( + CCardType::default().0, + )), description: None, email: Email::from_str("test@juspay.in").ok(), phone: None, diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index ff454ddd55..7e5616e6a2 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -55,6 +55,7 @@ pub struct ConnectorAuthentication { pub gpayments: Option, pub helcim: Option, pub hipay: Option, + pub hyperswitch_vault: Option, pub iatapay: Option, pub inespay: Option, pub itaubank: Option, @@ -104,7 +105,7 @@ pub struct ConnectorAuthentication { pub trustpay: Option, pub tsys: Option, pub unified_authentication_service: Option, - pub vgs: Option, + pub vgs: Option, pub volt: Option, pub wellsfargo: Option, // pub wellsfargopayout: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 22036f653b..4e18ce3e43 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -123,6 +123,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" hipay.base_url = "https://stage-secure-gateway.hipay-tpp.com/rest/" +hyperswitch_vault.base_url = "https://integ-api.hyperswitch.io" hipay.secondary_base_url = "https://stage-secure2-vault.hipay-tpp.com/rest/" hipay.third_base_url = "https://stage-api-gateway.hipay.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" @@ -244,6 +245,7 @@ cards = [ "gpayments", "helcim", "hipay", + "hyperswitch_vault", "iatapay", "inespay", "itaubank", @@ -548,7 +550,7 @@ gocardless = { long_lived_token = true, payment_method = "bank_debit" } billwerk = { long_lived_token = false, payment_method = "card" } [connector_customer] -connector_list = "gocardless,stax,stripe,facilitapay" +connector_list = "gocardless,stax,stripe,facilitapay,hyperswitch_vault" payout_connector_list = "nomupay,wise" [dummy_connector] diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index e0bfbdffc4..8199bcc501 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 chargebee checkout coinbase cryptopay ctp_visa cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon facilitapay fiserv fiservemea fiuu forte getnet globalpay globepay gocardless gpayments helcim hipay iatapay inespay itaubank jpmorgan juspaythreedsserver klarna mifinity mollie moneris multisafepay netcetera nexinets nexixpay nomupay noon nordea novalnet nuvei opayo opennode paybox payeezy payme payone paypal paystack payu placetopay plaid powertranz prophetpay rapyd razorpay recurly redsys shift4 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 authorizedotnet bambora bamboraapac bankofamerica barclaycard billwerk bitpay bluesnap boku braintree cashtocode chargebee checkout coinbase cryptopay ctp_visa cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector 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 payme payone paypal paystack payu placetopay plaid powertranz prophetpay rapyd razorpay recurly redsys shift4 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[@]})" sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp