Skip to content

Commit b70b28a

Browse files
Merge branch 'main' into partial_auth_lock
2 parents b3f795a + 1a99bf6 commit b70b28a

File tree

16 files changed

+152
-736
lines changed

16 files changed

+152
-736
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,31 @@ All notable changes to HyperSwitch will be documented here.
44

55
- - -
66

7+
## 2025.12.09.0
8+
9+
### Features
10+
11+
- **core:** Consume Card Holder Name in Payment Method Batch Migrations ([#10551](https://github.com/juspay/hyperswitch/pull/10551)) ([`a690a40`](https://github.com/juspay/hyperswitch/commit/a690a40bab3deae2f79ff9717b8eee81bec29462))
12+
- **platform:** Implement platform-connected setup for publishable key authentication ([#10475](https://github.com/juspay/hyperswitch/pull/10475)) ([`3831a52`](https://github.com/juspay/hyperswitch/commit/3831a526358bb79091bc51f7bfa7b4bd05518ccb))
13+
- **revenue_recovery:** Enable multiple retries for partial charged payments ([#9818](https://github.com/juspay/hyperswitch/pull/9818)) ([`e7fe480`](https://github.com/juspay/hyperswitch/commit/e7fe4804acd86ae0cc561cbe71d1ea227e4452cc))
14+
15+
### Bug Fixes
16+
17+
- Send customer_name to ucs during customer create ([#10561](https://github.com/juspay/hyperswitch/pull/10561)) ([`eb6080f`](https://github.com/juspay/hyperswitch/commit/eb6080fe1a6025acf631694278cadaad97c4ff2b))
18+
- Use proper masked deserialize method to deserialize ucs response ([#10566](https://github.com/juspay/hyperswitch/pull/10566)) ([`d42c447`](https://github.com/juspay/hyperswitch/commit/d42c447bd8d709dd451193540c140ce9fd2c5ce2))
19+
20+
### Miscellaneous Tasks
21+
22+
- **postman:** Update Postman collection files ([`f436d7b`](https://github.com/juspay/hyperswitch/commit/f436d7b80692588655f102eb3e02c1cabc534242))
23+
24+
### Revert
25+
26+
- **postman:** Postman tests for bluesnap and stripe failure scenarios ([#10527](https://github.com/juspay/hyperswitch/pull/10527)) ([`d84e66b`](https://github.com/juspay/hyperswitch/commit/d84e66b88cbb4ed9c4557b8fcb92ab6529769e9a))
27+
28+
**Full Changelog:** [`2025.12.08.0...2025.12.09.0`](https://github.com/juspay/hyperswitch/compare/2025.12.08.0...2025.12.09.0)
29+
30+
- - -
31+
732
## 2025.12.08.0
833

934
### Features

crates/api_models/src/payment_methods.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,6 +2758,7 @@ pub struct TokenizedBankRedirectValue2 {
27582758
pub struct PaymentMethodRecord {
27592759
pub customer_id: id_type::CustomerId,
27602760
pub name: Option<masking::Secret<String>>,
2761+
pub card_holder_name: Option<masking::Secret<String>>,
27612762
pub email: Option<pii::Email>,
27622763
pub phone: Option<masking::Secret<String>>,
27632764
pub phone_country_code: Option<String>,
@@ -3028,7 +3029,7 @@ impl
30283029
.unwrap_or_else(|| record.card_number_masked.clone()),
30293030
card_exp_month: record.card_expiry_month.clone(),
30303031
card_exp_year: record.card_expiry_year.clone(),
3031-
card_holder_name: record.name.clone(),
3032+
card_holder_name: record.card_holder_name.clone().or(record.name.clone()),
30323033
card_network: None,
30333034
card_type: None,
30343035
card_issuer: None,
@@ -3046,7 +3047,7 @@ impl
30463047
.network_token_expiry_year
30473048
.clone()
30483049
.unwrap_or_default(),
3049-
card_holder_name: record.name.clone(),
3050+
card_holder_name: record.card_holder_name.clone().or(record.name.clone()),
30503051
nick_name: record.nick_name.clone(),
30513052
card_issuing_country: None,
30523053
card_network: None,

crates/router/src/core/unified_connector_service.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1741,7 +1741,7 @@ where
17411741
.unwrap_or(200);
17421742

17431743
// Log the actual gRPC response
1744-
let grpc_response_body = serde_json::to_value(&grpc_response).unwrap_or_else(
1744+
let grpc_response_body = masking::masked_serialize(&grpc_response).unwrap_or_else(
17451745
|_| serde_json::json!({"error": "failed_to_serialize_grpc_response"}),
17461746
);
17471747

crates/router/src/routes/payments.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,10 @@ pub async fn payments_post_session_tokens(
857857
header_payload.clone(),
858858
)
859859
},
860-
&auth::PublishableKeyAuth,
860+
&auth::PublishableKeyAuth {
861+
is_connected_allowed: false,
862+
is_platform_allowed: false,
863+
},
861864
locking_action,
862865
))
863866
.await
@@ -1113,7 +1116,10 @@ pub async fn payments_dynamic_tax_calculation(
11131116
header_payload.clone(),
11141117
)
11151118
},
1116-
&auth::PublishableKeyAuth,
1119+
&auth::PublishableKeyAuth {
1120+
is_connected_allowed: false,
1121+
is_platform_allowed: false,
1122+
},
11171123
locking_action,
11181124
))
11191125
.await
@@ -1237,7 +1243,10 @@ pub async fn payments_connector_session(
12371243
header_payload.clone(),
12381244
)
12391245
},
1240-
&auth::HeaderAuth(auth::PublishableKeyAuth),
1246+
&auth::HeaderAuth(auth::PublishableKeyAuth {
1247+
is_connected_allowed: false,
1248+
is_platform_allowed: false,
1249+
}),
12411250
locking_action,
12421251
))
12431252
.await
@@ -2409,7 +2418,10 @@ pub async fn payments_external_authentication(
24092418
hyperswitch_domain_models::router_flow_types::Authenticate,
24102419
>(state, platform, req)
24112420
},
2412-
&auth::HeaderAuth(auth::PublishableKeyAuth),
2421+
&auth::HeaderAuth(auth::PublishableKeyAuth {
2422+
is_connected_allowed: false,
2423+
is_platform_allowed: false,
2424+
}),
24132425
locking_action,
24142426
))
24152427
.await

crates/router/src/routes/poll.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ pub async fn retrieve_poll_status(
4343
let platform = auth.into();
4444
poll::retrieve_poll_status(state, req, platform)
4545
},
46-
&auth::HeaderAuth(auth::PublishableKeyAuth),
46+
&auth::HeaderAuth(auth::PublishableKeyAuth {
47+
is_connected_allowed: false,
48+
is_platform_allowed: false,
49+
}),
4750
api_locking::LockAction::NotApplicable,
4851
))
4952
.await

crates/router/src/services/authentication.rs

Lines changed: 81 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2731,8 +2731,11 @@ where
27312731
api_auth
27322732
}
27332733
}
2734-
#[derive(Debug)]
2735-
pub struct PublishableKeyAuth;
2734+
#[derive(Debug, Default)]
2735+
pub struct PublishableKeyAuth {
2736+
pub is_connected_allowed: bool,
2737+
pub is_platform_allowed: bool,
2738+
}
27362739

27372740
#[cfg(feature = "partial-auth")]
27382741
impl GetAuthType for PublishableKeyAuth {
@@ -2744,10 +2747,10 @@ impl GetAuthType for PublishableKeyAuth {
27442747
#[cfg(feature = "partial-auth")]
27452748
impl GetMerchantAccessFlags for PublishableKeyAuth {
27462749
fn get_is_connected_allowed(&self) -> bool {
2747-
false // Publishable key doesn't support connected merchant operations currently
2750+
self.is_connected_allowed
27482751
}
27492752
fn get_is_platform_allowed(&self) -> bool {
2750-
false // Publishable key doesn't support platform merchant operations currently
2753+
self.is_platform_allowed
27512754
}
27522755
}
27532756

@@ -2762,29 +2765,45 @@ where
27622765
request_headers: &HeaderMap,
27632766
state: &A,
27642767
) -> RouterResult<(AuthenticationData, AuthenticationType)> {
2765-
if state.conf().platform.enabled {
2766-
throw_error_if_platform_merchant_authentication_required(request_headers)?;
2767-
}
2768-
27692768
let publishable_key =
27702769
get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?;
2771-
state
2770+
2771+
// Find initiator merchant and key store
2772+
let (initiator_merchant, key_store) = state
27722773
.store()
27732774
.find_merchant_account_by_publishable_key(publishable_key)
27742775
.await
2775-
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)
2776-
.map(|(merchant_account, key_store)| {
2777-
let merchant_id = merchant_account.get_id().clone();
2778-
(
2779-
AuthenticationData {
2780-
merchant_account,
2781-
platform_account_with_key_store: None,
2782-
key_store,
2783-
profile_id: None,
2784-
},
2785-
AuthenticationType::PublishableKey { merchant_id },
2786-
)
2787-
})
2776+
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
2777+
2778+
// Check access permissions using existing function
2779+
check_merchant_access(
2780+
state,
2781+
initiator_merchant.merchant_account_type,
2782+
self.is_connected_allowed,
2783+
self.is_platform_allowed,
2784+
)?;
2785+
2786+
// Resolve merchant relationships using existing function
2787+
let (merchant, key_store, platform_account_with_key_store) =
2788+
resolve_merchant_accounts_and_key_stores(
2789+
state,
2790+
request_headers,
2791+
initiator_merchant.clone(),
2792+
key_store,
2793+
)
2794+
.await?;
2795+
2796+
Ok((
2797+
AuthenticationData {
2798+
merchant_account: merchant,
2799+
platform_account_with_key_store,
2800+
key_store,
2801+
profile_id: None,
2802+
},
2803+
AuthenticationType::PublishableKey {
2804+
merchant_id: initiator_merchant.get_id().clone(),
2805+
},
2806+
))
27882807
}
27892808
}
27902809

@@ -2805,25 +2824,51 @@ where
28052824
get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)?
28062825
.get_required_value(headers::X_PROFILE_ID)?;
28072826

2808-
let (merchant_account, key_store) = state
2827+
// Find initiator merchant and key store
2828+
let (initiator_merchant, key_store) = state
28092829
.store()
28102830
.find_merchant_account_by_publishable_key(publishable_key)
28112831
.await
28122832
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
2813-
let merchant_id = merchant_account.get_id().clone();
2833+
2834+
// Check access permissions using existing function
2835+
check_merchant_access(
2836+
state,
2837+
initiator_merchant.merchant_account_type,
2838+
self.is_connected_allowed,
2839+
self.is_platform_allowed,
2840+
)?;
2841+
2842+
// Resolve merchant relationships using existing function
2843+
let (merchant, key_store, platform_account_with_key_store) =
2844+
resolve_merchant_accounts_and_key_stores(
2845+
state,
2846+
request_headers,
2847+
initiator_merchant.clone(),
2848+
key_store,
2849+
)
2850+
.await?;
2851+
2852+
// Find and validate profile after merchant resolution
28142853
let profile = state
28152854
.store()
2816-
.find_business_profile_by_merchant_id_profile_id(&key_store, &merchant_id, &profile_id)
2855+
.find_business_profile_by_merchant_id_profile_id(
2856+
&key_store,
2857+
merchant.get_id(),
2858+
&profile_id,
2859+
)
28172860
.await
28182861
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
28192862
Ok((
28202863
AuthenticationData {
2821-
merchant_account,
2864+
merchant_account: merchant,
2865+
platform_account_with_key_store,
28222866
key_store,
28232867
profile,
2824-
platform_account_with_key_store: None,
28252868
},
2826-
AuthenticationType::PublishableKey { merchant_id },
2869+
AuthenticationType::PublishableKey {
2870+
merchant_id: initiator_merchant.get_id().clone(),
2871+
},
28272872
))
28282873
}
28292874
}
@@ -4228,7 +4273,10 @@ pub fn get_auth_type_and_flow<A: SessionStateInfo + Sync + Send>(
42284273

42294274
if api_key.starts_with("pk_") {
42304275
return Ok((
4231-
Box::new(HeaderAuth(PublishableKeyAuth)),
4276+
Box::new(HeaderAuth(PublishableKeyAuth {
4277+
is_connected_allowed: api_auth.is_connected_allowed,
4278+
is_platform_allowed: api_auth.is_platform_allowed,
4279+
})),
42324280
api::AuthFlow::Client,
42334281
));
42344282
}
@@ -4257,7 +4305,10 @@ where
42574305
field_name: "client_secret",
42584306
})?;
42594307
return Ok((
4260-
Box::new(HeaderAuth(PublishableKeyAuth)),
4308+
Box::new(HeaderAuth(PublishableKeyAuth {
4309+
is_connected_allowed: api_auth.is_connected_allowed,
4310+
is_platform_allowed: api_auth.is_platform_allowed,
4311+
})),
42614312
api::AuthFlow::Client,
42624313
));
42634314
}

0 commit comments

Comments
 (0)