diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 26cde7c6c2b..87cd5fb66d4 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 } } }, @@ -24325,6 +24369,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": [ @@ -24350,6 +24427,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 bf997d7feda..826f778a069 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 c46f23dab4a..344caa4b04b 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -225,6 +225,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" @@ -347,6 +348,7 @@ cards = [ "gpayments", "helcim", "hipay", + "hyperswitch_vault", "mollie", "moneris", "paypal", @@ -817,7 +819,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 812dca12752..30c99e9f1bc 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 72ca305629f..33a787d86cf 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 f0229e1e734..06123d9bf41 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 c686a93994f..4c19fd7f4aa 100644 --- a/config/development.toml +++ b/config/development.toml @@ -133,6 +133,7 @@ cards = [ "gpayments", "helcim", "hipay", + "hyperswitch_vault", "iatapay", "inespay", "itaubank", @@ -258,6 +259,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" @@ -863,7 +865,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 f217c2df4a8..4d15a43f9c7 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 f4435b08fcd..213691ef4fb 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 5550d7e23e9..57f8b523554 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 7ab8bc19361..f722d75dd40 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 147e2111f1e..83a19b7bfae 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 d6b021fd4d2..38fb0d3455a 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 cf1ef060e01..f05ca7b5f49 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 0bc52b33189..10e71ce1f35 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 ab586e00904..b603923d985 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 a123b4796cf..23347e31edb 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::() - }); - - let document_type = convert_to_document_type("cpf")?; - (document_type, document_number) + 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) + } + _ => { + 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 00000000000..d7e0e93c592 --- /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 00000000000..220e2e55440 --- /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 b4bf6eb520f..f30a455ef4d 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 faeb9e2c0a5..223d7a37f71 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 a71f1cb4130..4195b155d14 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 93ce14d4e8f..952de0b231b 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 992bd58784f..80070aa9e50 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 46a100af455..4c170d5c50c 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 42d1ef1a426..8d95ae17bc0 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 9195125f745..8468431b881 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 5f362e0f85b..8250f919c54 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 bdf04e03283..516682889ce 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 be44f8e8d7e..78e32f7b57c 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 ExternalVaultDelete +pub trait ExternalVaultCreate: + ConnectorIntegration +{ +} + /// trait ExternalVault pub trait ExternalVault: - ConnectorCommon + ExternalVaultInsert + ExternalVaultRetrieve + ExternalVaultDelete + 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 5509de5db86..069c20ed3e9 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 ExternalVaultDeleteV2 +pub trait ExternalVaultCreateV2: + ConnectorIntegrationV2< + ExternalVaultCreateFlow, + VaultConnectorFlowData, + VaultRequestData, + VaultResponseData, +> +{ +} + /// trait ExternalVaultV2 pub trait ExternalVaultV2: - ConnectorCommon + ExternalVaultInsertV2 + ExternalVaultRetrieveV2 + ExternalVaultDeleteV2 + ConnectorCommon + + ExternalVaultInsertV2 + + ExternalVaultRetrieveV2 + + ExternalVaultDeleteV2 + + ExternalVaultCreateV2 { } diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index e41e8cd9bcf..e19580a85b4 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 f43712782f6..deb966797dd 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 748a2bcf83c..a5af15325fc 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 0dc5d5fdd37..0a8af98dcc1 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 96ac030f57f..d3d8b31cb0e 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 9a2c096d66a..3c231ad3f5b 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 aad07256fd2..8c21739cde4 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 58cd601558b..a639805cde7 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 dcf424b4c88..6e9ed9e2e62 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 a166c54139d..94a02248aa2 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 1cb95a2b92e..10076585a98 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 c9bd018a4a8..9003bd73088 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 e4b85f83c5d..77cc6dc1a92 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 b791dab22c1..9a397e248cf 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 d018ac19107..3ed015309ea 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 4dff52acb4c..fee5440a609 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 00000000000..bb9b714a9bd --- /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 712d092fc77..b357465bc64 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 c4bc55bb83d..c947bcd2651 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 325956acb81..7379794aaa2 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 e1607c3b6b2..66e9c0963d6 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 00000000000..8f8fd5f169b --- /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 3b855b82a44..1d499d5829b 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 543d2451140..3cfd3b277e5 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 ff454ddd554..7e5616e6a2c 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 22036f653b2..4e18ce3e43d 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 e0bfbdffc49..8199bcc5015 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