feat(core): Added support for unified_connector_service CardNumber and Secret<String> Type (#9044)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Debarshi Gupta
2025-08-27 21:07:24 +05:30
committed by GitHub
parent 4b7f2093db
commit cf64d2a9dc
5 changed files with 186 additions and 91 deletions

132
Cargo.lock generated
View File

@ -357,7 +357,7 @@ dependencies = [
"error-stack 0.4.1",
"futures 0.3.31",
"hyperswitch_interfaces",
"masking",
"masking 0.1.0",
"opensearch",
"reqwest 0.11.27",
"router_env",
@ -462,7 +462,7 @@ dependencies = [
"deserialize_form_style_query_parameter",
"error-stack 0.4.1",
"euclid",
"masking",
"masking 0.1.0",
"mime",
"nutype",
"reqwest 0.11.27",
@ -1620,7 +1620,7 @@ version = "0.1.0"
dependencies = [
"common_utils",
"error-stack 0.4.1",
"masking",
"masking 0.1.0",
"regex",
"router_env",
"serde",
@ -1846,7 +1846,7 @@ name = "common_enums"
version = "0.1.0"
dependencies = [
"diesel",
"masking",
"masking 0.1.0",
"router_derive",
"serde",
"serde_json",
@ -1865,7 +1865,7 @@ dependencies = [
"diesel",
"error-stack 0.4.1",
"euclid",
"masking",
"masking 0.1.0",
"serde",
"serde_json",
"time",
@ -1889,7 +1889,7 @@ dependencies = [
"globset",
"hex",
"http 0.2.12",
"masking",
"masking 0.1.0",
"md5",
"nanoid",
"nutype",
@ -2633,7 +2633,7 @@ dependencies = [
"common_utils",
"diesel",
"error-stack 0.4.1",
"masking",
"masking 0.1.0",
"router_derive",
"router_env",
"serde",
@ -2721,7 +2721,7 @@ dependencies = [
"error-stack 0.4.1",
"external_services",
"hyperswitch_interfaces",
"masking",
"masking 0.1.0",
"mime",
"redis_interface",
"reqwest 0.11.27",
@ -3020,7 +3020,7 @@ name = "events"
version = "0.1.0"
dependencies = [
"error-stack 0.4.1",
"masking",
"masking 0.1.0",
"router_env",
"serde",
"serde_json",
@ -3053,7 +3053,7 @@ dependencies = [
"hyper-util",
"hyperswitch_interfaces",
"lettre",
"masking",
"masking 0.1.0",
"once_cell",
"prost",
"prost-types",
@ -3377,8 +3377,9 @@ dependencies = [
[[package]]
name = "g2h"
version = "0.4.0"
source = "git+https://github.com/NishantJoshi00/g2h?branch=fixing-response-serializing#fd2c856b2c6c88a85d6fe51d95b4d3342b788d31"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d843f7c70cdae8a2452e0b2f463b7e483a57cece7687da89371d952d44e0445d"
dependencies = [
"cargo_metadata 0.19.2",
"heck 0.5.0",
@ -3540,19 +3541,21 @@ dependencies = [
[[package]]
name = "grpc-api-types"
version = "0.1.0"
source = "git+https://github.com/juspay/connector-service?rev=aae51574fed25b0a7849e3832cff8904bc310cf5#aae51574fed25b0a7849e3832cff8904bc310cf5"
source = "git+https://github.com/juspay/connector-service?rev=a56de0a4ee393af5c401f02b6b4344cb263f4cac#a56de0a4ee393af5c401f02b6b4344cb263f4cac"
dependencies = [
"axum 0.8.4",
"error-stack 0.5.0",
"g2h",
"heck 0.5.0",
"http 1.3.1",
"masking 0.1.0 (git+https://github.com/juspay/hyperswitch?tag=v1.116.0)",
"prost",
"prost-build",
"prost-types",
"serde",
"tonic 0.13.1",
"tonic-build",
"ucs_cards",
]
[[package]]
@ -4046,7 +4049,7 @@ dependencies = [
"isocountry",
"josekit",
"lazy_static",
"masking",
"masking 0.1.0",
"mime",
"nanoid",
"num-traits",
@ -4104,7 +4107,7 @@ dependencies = [
"error-stack 0.4.1",
"futures 0.3.31",
"http 0.2.12",
"masking",
"masking 0.1.0",
"mime",
"router_derive",
"router_env",
@ -4131,7 +4134,7 @@ dependencies = [
"error-stack 0.4.1",
"http 0.2.12",
"hyperswitch_domain_models",
"masking",
"masking 0.1.0",
"mime",
"reqwest 0.11.27",
"router_env",
@ -4392,7 +4395,7 @@ dependencies = [
"async-trait",
"common_utils",
"error-stack 0.4.1",
"masking",
"masking 0.1.0",
"nom 7.1.3",
"reqwest 0.12.7",
"router_env",
@ -4603,7 +4606,7 @@ dependencies = [
"criterion",
"euclid",
"hyperswitch_constraint_graph",
"masking",
"masking 0.1.0",
"serde",
"serde_json",
"strum 0.26.3",
@ -4843,6 +4846,23 @@ dependencies = [
"zeroize",
]
[[package]]
name = "masking"
version = "0.1.0"
source = "git+https://github.com/juspay/hyperswitch?tag=v1.116.0#672d749e20bec7800613878e36a0ab3885177326"
dependencies = [
"bytes 1.10.1",
"diesel",
"erased-serde 0.4.6",
"prost",
"serde",
"serde_json",
"subtle",
"time",
"url",
"zeroize",
]
[[package]]
name = "matchers"
version = "0.1.0"
@ -5702,7 +5722,7 @@ dependencies = [
"error-stack 0.4.1",
"hyperswitch_domain_models",
"hyperswitch_interfaces",
"masking",
"masking 0.1.0",
"rdkafka",
"router_env",
"scheduler",
@ -5958,7 +5978,7 @@ dependencies = [
"common_utils",
"error-stack 0.4.1",
"http 0.2.12",
"masking",
"masking 0.1.0",
"mime",
"serde",
"serde_json",
@ -6810,7 +6830,7 @@ dependencies = [
"josekit",
"jsonwebtoken",
"kgraph_utils",
"masking",
"masking 0.1.0",
"maud",
"mimalloc",
"mime",
@ -6855,6 +6875,7 @@ dependencies = [
"tokio 1.45.1",
"totp-rs",
"tracing-futures",
"ucs_cards",
"unicode-segmentation",
"url",
"urlencoding",
@ -6940,7 +6961,7 @@ dependencies = [
[[package]]
name = "rust-grpc-client"
version = "0.1.0"
source = "git+https://github.com/juspay/connector-service?rev=aae51574fed25b0a7849e3832cff8904bc310cf5#aae51574fed25b0a7849e3832cff8904bc310cf5"
source = "git+https://github.com/juspay/connector-service?rev=a56de0a4ee393af5c401f02b6b4344cb263f4cac#a56de0a4ee393af5c401f02b6b4344cb263f4cac"
dependencies = [
"grpc-api-types",
]
@ -8146,7 +8167,7 @@ dependencies = [
"error-stack 0.4.1",
"futures 0.3.31",
"hyperswitch_domain_models",
"masking",
"masking 0.1.0",
"moka",
"redis_interface",
"router_derive",
@ -8442,7 +8463,7 @@ dependencies = [
"base64 0.22.1",
"clap",
"common_enums",
"masking",
"masking 0.1.0",
"rand 0.8.5",
"regex",
"reqwest 0.11.27",
@ -9324,6 +9345,69 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "ucs_cards"
version = "0.1.0"
source = "git+https://github.com/juspay/connector-service?rev=a56de0a4ee393af5c401f02b6b4344cb263f4cac#a56de0a4ee393af5c401f02b6b4344cb263f4cac"
dependencies = [
"bytes 1.10.1",
"error-stack 0.4.1",
"masking 0.1.0 (git+https://github.com/juspay/hyperswitch?tag=v1.116.0)",
"prost",
"regex",
"serde",
"thiserror 1.0.69",
"time",
"ucs_common_utils",
]
[[package]]
name = "ucs_common_enums"
version = "0.1.0"
source = "git+https://github.com/juspay/connector-service?rev=a56de0a4ee393af5c401f02b6b4344cb263f4cac#a56de0a4ee393af5c401f02b6b4344cb263f4cac"
dependencies = [
"serde",
"strum 0.26.3",
"thiserror 1.0.69",
"utoipa",
]
[[package]]
name = "ucs_common_utils"
version = "0.1.0"
source = "git+https://github.com/juspay/connector-service?rev=a56de0a4ee393af5c401f02b6b4344cb263f4cac#a56de0a4ee393af5c401f02b6b4344cb263f4cac"
dependencies = [
"anyhow",
"blake3",
"bytes 1.10.1",
"chrono",
"error-stack 0.4.1",
"hex",
"http 1.3.1",
"masking 0.1.0 (git+https://github.com/juspay/hyperswitch?tag=v1.116.0)",
"md5",
"nanoid",
"once_cell",
"quick-xml",
"rand 0.8.5",
"regex",
"reqwest 0.11.27",
"ring 0.17.14",
"rust_decimal",
"semver 1.0.26",
"serde",
"serde_json",
"serde_urlencoded",
"strum 0.26.3",
"thiserror 1.0.69",
"time",
"tracing",
"ucs_common_enums",
"url",
"utoipa",
"uuid",
]
[[package]]
name = "unarray"
version = "0.1.4"

View File

@ -66,7 +66,7 @@ reqwest = { version = "0.11.27", features = ["rustls-tls"] }
http = "0.2.12"
url = { version = "2.5.4", features = ["serde"] }
quick-xml = { version = "0.31.0", features = ["serialize"] }
unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "aae51574fed25b0a7849e3832cff8904bc310cf5", package = "rust-grpc-client" }
unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "a56de0a4ee393af5c401f02b6b4344cb263f4cac", package = "rust-grpc-client" }
# First party crates

View File

@ -89,7 +89,8 @@ reqwest = { version = "0.11.27", features = ["json", "rustls-tls", "gzip", "mult
ring = "0.17.14"
rust_decimal = { version = "1.37.1", features = ["serde-with-float", "serde-with-str"] }
rust-i18n = { git = "https://github.com/kashif-m/rust-i18n", rev = "f2d8096aaaff7a87a847c35a5394c269f75e077a" }
unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "aae51574fed25b0a7849e3832cff8904bc310cf5", package = "rust-grpc-client" }
unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "a56de0a4ee393af5c401f02b6b4344cb263f4cac", package = "rust-grpc-client" }
unified-connector-service-cards = { git = "https://github.com/juspay/connector-service", rev = "a56de0a4ee393af5c401f02b6b4344cb263f4cac", package = "ucs_cards" }
rustc-hash = "1.1.0"
rustls = "0.22"
rustls-pemfile = "2"

View File

@ -18,6 +18,7 @@ use hyperswitch_domain_models::{
};
use masking::{ExposeInterface, PeekInterface, Secret};
use router_env::logger;
use unified_connector_service_cards::CardNumber;
use unified_connector_service_client::payments::{
self as payments_grpc, payment_method::PaymentMethod, CardDetails, CardPaymentMethodType,
PaymentServiceAuthorizeResponse, RewardPaymentMethodType,
@ -277,11 +278,17 @@ pub fn build_unified_connector_service_payment_method(
.transpose()?;
let card_details = CardDetails {
card_number: card.card_number.get_card_no(),
card_exp_month,
card_exp_year: card.get_expiry_year_4_digit().peek().to_string(),
card_cvc: card.card_cvc.peek().to_string(),
card_holder_name: card.card_holder_name.map(|name| name.expose()),
card_number: Some(
CardNumber::from_str(&card.card_number.get_card_no()).change_context(
UnifiedConnectorServiceError::RequestEncodingFailedWithReason(
"Failed to parse card number".to_string(),
),
)?,
),
card_exp_month: Some(card_exp_month.into()),
card_exp_year: Some(card.get_expiry_year_4_digit().expose().into()),
card_cvc: Some(card.card_cvc.expose().into()),
card_holder_name: card.card_holder_name.map(|name| name.expose().into()),
card_issuer: card.card_issuer.clone(),
card_network: card_network.map(|card_network| card_network.into()),
card_type: card.card_type.clone(),
@ -316,12 +323,13 @@ pub fn build_unified_connector_service_payment_method(
hyperswitch_domain_models::payment_method_data::UpiData::UpiCollect(
upi_collect_data,
) => {
let vpa_id = upi_collect_data.vpa_id.map(|vpa| vpa.expose());
let upi_details = payments_grpc::UpiCollect { vpa_id };
let upi_details = payments_grpc::UpiCollect {
vpa_id: upi_collect_data.vpa_id.map(|vpa| vpa.expose().into()),
};
PaymentMethod::UpiCollect(upi_details)
}
hyperswitch_domain_models::payment_method_data::UpiData::UpiIntent(_) => {
let upi_details = payments_grpc::UpiIntent {};
let upi_details = payments_grpc::UpiIntent { app_name: None };
PaymentMethod::UpiIntent(upi_details)
}
};
@ -369,13 +377,13 @@ pub fn build_unified_connector_service_payment_method_for_external_proxy(
.map(payments_grpc::CardNetwork::foreign_try_from)
.transpose()?;
let card_details = CardDetails {
card_number: external_vault_card.card_number.peek().to_string(),
card_exp_month: external_vault_card.card_exp_month.peek().to_string(),
card_exp_year: external_vault_card.card_exp_year.peek().to_string(),
card_cvc: external_vault_card.card_cvc.peek().to_string(),
card_holder_name: external_vault_card
.card_holder_name
.map(|name| name.expose()),
card_number: Some(CardNumber::from_str(external_vault_card.card_number.peek()).change_context(
UnifiedConnectorServiceError::RequestEncodingFailedWithReason("Failed to parse card number".to_string())
)?),
card_exp_month: Some(external_vault_card.card_exp_month.expose().into()),
card_exp_year: Some(external_vault_card.card_exp_year.expose().into()),
card_cvc: Some(external_vault_card.card_cvc.expose().into()),
card_holder_name: external_vault_card.card_holder_name.map(|name| name.expose().into()),
card_issuer: external_vault_card.card_issuer.clone(),
card_network: card_network.map(|card_network| card_network.into()),
card_type: external_vault_card.card_type.clone(),

View File

@ -141,7 +141,7 @@ impl ForeignTryFrom<&RouterData<Authorize, PaymentsAuthorizeData, PaymentsRespon
.request
.email
.clone()
.map(|e| e.expose().expose()),
.map(|e| e.expose().expose().into()),
browser_info,
access_token: None,
session_token: None,
@ -193,6 +193,7 @@ impl ForeignTryFrom<&RouterData<Authorize, PaymentsAuthorizeData, PaymentsRespon
.collect::<HashMap<String, String>>()
})
.unwrap_or_default(),
test_mode: None,
})
}
}
@ -264,7 +265,7 @@ impl
.request
.email
.clone()
.map(|e| e.expose().expose()),
.map(|e| e.expose().expose().into()),
browser_info,
access_token: None,
session_token: None,
@ -316,6 +317,7 @@ impl
.collect::<HashMap<String, String>>()
})
.unwrap_or_default(),
test_mode: None,
})
}
}
@ -372,7 +374,7 @@ impl ForeignTryFrom<&RouterData<SetupMandate, SetupMandateRequestData, PaymentsR
.request
.email
.clone()
.map(|e| e.expose().expose()),
.map(|e| e.expose().expose().into()),
customer_name: router_data
.request
.customer_name
@ -495,8 +497,10 @@ impl ForeignTryFrom<&RouterData<Authorize, PaymentsAuthorizeData, PaymentsRespon
.request
.email
.clone()
.map(|e| e.expose().expose()),
.map(|e| e.expose().expose().into()),
browser_info,
test_mode: None,
payment_method_type: None,
})
}
}
@ -861,11 +865,6 @@ impl ForeignTryFrom<hyperswitch_domain_models::payment_address::PaymentAddress>
let shipping = payment_address.get_shipping().map(|address| {
let details = address.address.as_ref();
let get_str =
|opt: &Option<masking::Secret<String>>| opt.as_ref().map(|s| s.peek().to_owned());
let get_plain = |opt: &Option<String>| opt.clone();
let country = details.and_then(|details| {
details
.country
@ -875,20 +874,23 @@ impl ForeignTryFrom<hyperswitch_domain_models::payment_address::PaymentAddress>
});
payments_grpc::Address {
first_name: get_str(&details.and_then(|d| d.first_name.clone())),
last_name: get_str(&details.and_then(|d| d.last_name.clone())),
line1: get_str(&details.and_then(|d| d.line1.clone())),
line2: get_str(&details.and_then(|d| d.line2.clone())),
line3: get_str(&details.and_then(|d| d.line3.clone())),
city: get_plain(&details.and_then(|d| d.city.clone())),
state: get_str(&details.and_then(|d| d.state.clone())),
zip_code: get_str(&details.and_then(|d| d.zip.clone())),
first_name: details.and_then(|d| d.first_name.as_ref().map(|s| s.clone().expose())),
last_name: details.and_then(|d| d.last_name.as_ref().map(|s| s.clone().expose())),
line1: details.and_then(|d| d.line1.as_ref().map(|s| s.clone().expose().into())),
line2: details.and_then(|d| d.line2.as_ref().map(|s| s.clone().expose().into())),
line3: details.and_then(|d| d.line3.as_ref().map(|s| s.clone().expose().into())),
city: details.and_then(|d| d.city.as_ref().map(|s| s.clone().into())),
state: details.and_then(|d| d.state.as_ref().map(|s| s.clone().expose().into())),
zip_code: details.and_then(|d| d.zip.as_ref().map(|s| s.clone().expose().into())),
country_alpha2_code: country,
email: address.email.as_ref().map(|e| e.peek().to_string()),
email: address
.email
.as_ref()
.map(|e| e.clone().expose().expose().into()),
phone_number: address
.phone
.as_ref()
.and_then(|phone| phone.number.as_ref().map(|n| n.peek().to_string())),
.and_then(|phone| phone.number.as_ref().map(|n| n.clone().expose().into())),
phone_country_code: address.phone.as_ref().and_then(|p| p.country_code.clone()),
}
});
@ -896,11 +898,6 @@ impl ForeignTryFrom<hyperswitch_domain_models::payment_address::PaymentAddress>
let billing = payment_address.get_payment_billing().map(|address| {
let details = address.address.as_ref();
let get_str =
|opt: &Option<masking::Secret<String>>| opt.as_ref().map(|s| s.peek().to_owned());
let get_plain = |opt: &Option<String>| opt.clone();
let country = details.and_then(|details| {
details
.country
@ -910,20 +907,21 @@ impl ForeignTryFrom<hyperswitch_domain_models::payment_address::PaymentAddress>
});
payments_grpc::Address {
first_name: get_str(&details.and_then(|d| d.first_name.clone())),
last_name: get_str(&details.and_then(|d| d.last_name.clone())),
line1: get_str(&details.and_then(|d| d.line1.clone())),
line2: get_str(&details.and_then(|d| d.line2.clone())),
line3: get_str(&details.and_then(|d| d.line3.clone())),
city: get_plain(&details.and_then(|d| d.city.clone())),
state: get_str(&details.and_then(|d| d.state.clone())),
zip_code: get_str(&details.and_then(|d| d.zip.clone())),
first_name: details
.and_then(|d| d.first_name.as_ref().map(|s| s.peek().to_string())),
last_name: details.and_then(|d| d.last_name.as_ref().map(|s| s.peek().to_string())),
line1: details.and_then(|d| d.line1.as_ref().map(|s| s.peek().to_string().into())),
line2: details.and_then(|d| d.line2.as_ref().map(|s| s.peek().to_string().into())),
line3: details.and_then(|d| d.line3.as_ref().map(|s| s.peek().to_string().into())),
city: details.and_then(|d| d.city.as_ref().map(|s| s.clone().into())),
state: details.and_then(|d| d.state.as_ref().map(|s| s.peek().to_string().into())),
zip_code: details.and_then(|d| d.zip.as_ref().map(|s| s.peek().to_string().into())),
country_alpha2_code: country,
email: address.email.as_ref().map(|e| e.peek().to_string()),
email: address.email.as_ref().map(|e| e.peek().to_string().into()),
phone_number: address
.phone
.as_ref()
.and_then(|phone| phone.number.as_ref().map(|n| n.peek().to_string())),
.and_then(|phone| phone.number.as_ref().map(|n| n.peek().to_string().into())),
phone_country_code: address.phone.as_ref().and_then(|p| p.country_code.clone()),
}
});
@ -932,12 +930,6 @@ impl ForeignTryFrom<hyperswitch_domain_models::payment_address::PaymentAddress>
payment_address.get_payment_method_billing().map(|address| {
let details = address.address.as_ref();
let get_str = |opt: &Option<masking::Secret<String>>| {
opt.as_ref().map(|s| s.peek().to_owned())
};
let get_plain = |opt: &Option<String>| opt.clone();
let country = details.and_then(|details| {
details
.country
@ -947,20 +939,30 @@ impl ForeignTryFrom<hyperswitch_domain_models::payment_address::PaymentAddress>
});
payments_grpc::Address {
first_name: get_str(&details.and_then(|d| d.first_name.clone())),
last_name: get_str(&details.and_then(|d| d.last_name.clone())),
line1: get_str(&details.and_then(|d| d.line1.clone())),
line2: get_str(&details.and_then(|d| d.line2.clone())),
line3: get_str(&details.and_then(|d| d.line3.clone())),
city: get_plain(&details.and_then(|d| d.city.clone())),
state: get_str(&details.and_then(|d| d.state.clone())),
zip_code: get_str(&details.and_then(|d| d.zip.clone())),
first_name: details
.and_then(|d| d.first_name.as_ref().map(|s| s.peek().to_string())),
last_name: details
.and_then(|d| d.last_name.as_ref().map(|s| s.peek().to_string())),
line1: details
.and_then(|d| d.line1.as_ref().map(|s| s.peek().to_string().into())),
line2: details
.and_then(|d| d.line2.as_ref().map(|s| s.peek().to_string().into())),
line3: details
.and_then(|d| d.line3.as_ref().map(|s| s.peek().to_string().into())),
city: details.and_then(|d| d.city.as_ref().map(|s| s.clone().into())),
state: details
.and_then(|d| d.state.as_ref().map(|s| s.peek().to_string().into())),
zip_code: details
.and_then(|d| d.zip.as_ref().map(|s| s.peek().to_string().into())),
country_alpha2_code: country,
email: address.email.as_ref().map(|e| e.peek().to_string()),
email: address
.email
.as_ref()
.map(|e| e.clone().expose().expose().into()),
phone_number: address
.phone
.as_ref()
.and_then(|phone| phone.number.as_ref().map(|n| n.peek().to_string())),
.and_then(|phone| phone.number.as_ref().map(|n| n.clone().expose().into())),
phone_country_code: address.phone.as_ref().and_then(|p| p.country_code.clone()),
}
});