Skip to content

Commit 8dc594a

Browse files
Merge branch 'main' of https://github.com/juspay/hyperswitch into logging-wrapper-fix-ucs
2 parents 4be712d + d41e03c commit 8dc594a

File tree

9 files changed

+140
-49
lines changed

9 files changed

+140
-49
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/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
}

postman/collection-dir/bluesnap/Flow Testcases/Happy Cases/Scenario11-Pass Invalid CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/event.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ if (jsonData?.error_message) {
9797
pm.test(
9898
"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'Order creation failure due to problematic input. & There is a mismatch between the Credit Card American Express and Security Code. '",
9999
function () {
100-
pm.expect(jsonData.error_message).to.eql("VALIDATION_GENERAL_FAILURE");
100+
pm.expect(jsonData.error_message).to.eql("Order creation failure due to problematic input. & There is a mismatch between the Credit Card American Express and Security Code. ");
101101
},
102102
);
103103
}

postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Create/event.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,12 @@ if (jsonData?.error_code) {
8989
);
9090
}
9191

92-
// Response body should have value "Your card has insufficient funds." for "error_message"
92+
// Response body should have value "message - Your card has insufficient funds., decline_code - insufficient_funds" for "error_message"
9393
if (jsonData?.error_message) {
9494
pm.test(
95-
"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'Your card has insufficient funds.'",
95+
"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'",
9696
function () {
97-
pm.expect(jsonData.error_message).to.eql("Your card has insufficient funds.");
97+
pm.expect(jsonData.error_message).to.eql("message - Your card has insufficient funds., decline_code - insufficient_funds");
9898
},
9999
);
100100
}

postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario8-Create a failure card payment with confirm true/Payments - Retrieve/event.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,12 @@ if (jsonData?.error_code) {
8989
);
9090
}
9191

92-
// Response body should have value "Your card has insufficient funds." for "error_message"
92+
// Response body should have value "message - Your card has insufficient funds., decline_code - insufficient_funds" for "error_message"
9393
if (jsonData?.error_message) {
9494
pm.test(
95-
"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'Your card has insufficient funds.'",
95+
"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'",
9696
function () {
97-
pm.expect(jsonData.error_message).to.eql("Your card has insufficient funds.");
97+
pm.expect(jsonData.error_message).to.eql("message - Your card has insufficient funds., decline_code - insufficient_funds");
9898
},
9999
);
100100
}

postman/collection-json/bluesnap.postman_collection.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2230,7 +2230,7 @@
22302230
" pm.test(",
22312231
" \"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'Order creation failure due to problematic input. & There is a mismatch between the Credit Card American Express and Security Code. '\",",
22322232
" function () {",
2233-
" pm.expect(jsonData.error_message).to.eql(\"VALIDATION_GENERAL_FAILURE\");",
2233+
" pm.expect(jsonData.error_message).to.eql(\"Order creation failure due to problematic input. & There is a mismatch between the Credit Card American Express and Security Code. \");",
22342234
" },",
22352235
" );",
22362236
"}"

postman/collection-json/stripe.postman_collection.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10152,12 +10152,12 @@
1015210152
" );",
1015310153
"}",
1015410154
"",
10155-
"// Response body should have value \"Your card has insufficient funds.\" for \"error_message\"",
10155+
"// Response body should have value \"message - Your card has insufficient funds., decline_code - insufficient_funds\" for \"error_message\"",
1015610156
"if (jsonData?.error_message) {",
1015710157
" pm.test(",
10158-
" \"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'Your card has insufficient funds.'\",",
10158+
" \"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'\",",
1015910159
" function () {",
10160-
" pm.expect(jsonData.error_message).to.eql(\"Your card has insufficient funds.\");",
10160+
" pm.expect(jsonData.error_message).to.eql(\"message - Your card has insufficient funds., decline_code - insufficient_funds\");",
1016110161
" },",
1016210162
" );",
1016310163
"}",
@@ -10299,12 +10299,12 @@
1029910299
" );",
1030010300
"}",
1030110301
"",
10302-
"// Response body should have value \"Your card has insufficient funds.\" for \"error_message\"",
10302+
"// Response body should have value \"message - Your card has insufficient funds., decline_code - insufficient_funds\" for \"error_message\"",
1030310303
"if (jsonData?.error_message) {",
1030410304
" pm.test(",
10305-
" \"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'Your card has insufficient funds.'\",",
10305+
" \"[POST]::/payments/:id/confirm - Content check if value for 'error_message' matches 'message - Your card has insufficient funds., decline_code - insufficient_funds'\",",
1030610306
" function () {",
10307-
" pm.expect(jsonData.error_message).to.eql(\"Your card has insufficient funds.\");",
10307+
" pm.expect(jsonData.error_message).to.eql(\"message - Your card has insufficient funds., decline_code - insufficient_funds\");",
1030810308
" },",
1030910309
" );",
1031010310
"}",

0 commit comments

Comments
 (0)