Skip to content

Commit 771d20d

Browse files
sai-harsha-vardhanhyperswitch-bot[bot]
authored andcommitted
feat(router): add start_redirection api for three_ds flow in v2 (#6470)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
1 parent 299dbb5 commit 771d20d

File tree

19 files changed

+316
-28
lines changed

19 files changed

+316
-28
lines changed

api-reference-v2/openapi_spec.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13384,6 +13384,14 @@
1338413384
"payment_method_subtype": {
1338513385
"$ref": "#/components/schemas/PaymentMethodType"
1338613386
},
13387+
"next_action": {
13388+
"allOf": [
13389+
{
13390+
"$ref": "#/components/schemas/NextActionData"
13391+
}
13392+
],
13393+
"nullable": true
13394+
},
1338713395
"connector_transaction_id": {
1338813396
"type": "string",
1338913397
"description": "A unique identifier for a payment provided by the connector",

crates/api_models/src/events/payment.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};
22

33
#[cfg(feature = "v2")]
44
use super::{
5-
PaymentsConfirmIntentResponse, PaymentsCreateIntentRequest, PaymentsGetIntentRequest,
6-
PaymentsIntentResponse,
5+
PaymentStartRedirectionRequest, PaymentsConfirmIntentResponse, PaymentsCreateIntentRequest,
6+
PaymentsGetIntentRequest, PaymentsIntentResponse,
77
};
88
#[cfg(all(
99
any(feature = "v2", feature = "v1"),
@@ -365,3 +365,12 @@ impl ApiEventMetric for PaymentsSessionResponse {
365365
})
366366
}
367367
}
368+
369+
#[cfg(feature = "v2")]
370+
impl ApiEventMetric for PaymentStartRedirectionRequest {
371+
fn get_api_event_type(&self) -> Option<ApiEventsType> {
372+
Some(ApiEventsType::Payment {
373+
payment_id: self.id.clone(),
374+
})
375+
}
376+
}

crates/api_models/src/payments.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3876,9 +3876,16 @@ pub enum NextActionType {
38763876
#[serde(tag = "type", rename_all = "snake_case")]
38773877
pub enum NextActionData {
38783878
/// Contains the url for redirection flow
3879+
#[cfg(feature = "v1")]
38793880
RedirectToUrl {
38803881
redirect_to_url: String,
38813882
},
3883+
/// Contains the url for redirection flow
3884+
#[cfg(feature = "v2")]
3885+
RedirectToUrl {
3886+
#[schema(value_type = String)]
3887+
redirect_to_url: Url,
3888+
},
38823889
/// Informs the next steps for bank transfer and also contains the charges details (ex: amount received, amount charged etc)
38833890
DisplayBankTransferInformation {
38843891
bank_transfer_steps_and_charges_details: BankTransferNextStepsData,
@@ -4538,6 +4545,9 @@ pub struct PaymentsConfirmIntentResponse {
45384545
#[schema(value_type = PaymentMethodType, example = "apple_pay")]
45394546
pub payment_method_subtype: api_enums::PaymentMethodType,
45404547

4548+
/// Additional information required for redirection
4549+
pub next_action: Option<NextActionData>,
4550+
45414551
/// A unique identifier for a payment provided by the connector
45424552
#[schema(value_type = Option<String>, example = "993672945374576J")]
45434553
pub connector_transaction_id: Option<String>,
@@ -4558,6 +4568,22 @@ pub struct PaymentsConfirmIntentResponse {
45584568
pub error: Option<ErrorDetails>,
45594569
}
45604570

4571+
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
4572+
#[cfg(feature = "v2")]
4573+
pub struct PaymentStartRedirectionRequest {
4574+
/// Global Payment ID
4575+
pub id: id_type::GlobalPaymentId,
4576+
}
4577+
4578+
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
4579+
#[cfg(feature = "v2")]
4580+
pub struct PaymentStartRedirectionParams {
4581+
/// The identifier for the Merchant Account.
4582+
pub publishable_key: String,
4583+
/// The identifier for business profile
4584+
pub profile_id: id_type::ProfileId,
4585+
}
4586+
45614587
/// Fee information to be charged on the payment being collected
45624588
#[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)]
45634589
pub struct PaymentChargeResponse {

crates/diesel_models/src/payment_attempt.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,7 @@ pub struct PaymentAttemptUpdateInternal {
770770
pub updated_by: String,
771771
pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
772772
pub connector: Option<String>,
773-
// authentication_data: Option<serde_json::Value>,
773+
pub authentication_data: Option<pii::SecretSerdeValue>,
774774
// encoded_data: Option<String>,
775775
pub unified_code: Option<Option<String>>,
776776
pub unified_message: Option<Option<String>>,

crates/diesel_models/src/payment_intent.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub struct PaymentIntent {
3333
pub last_synced: Option<PrimitiveDateTime>,
3434
pub setup_future_usage: Option<storage_enums::FutureUsage>,
3535
pub client_secret: common_utils::types::ClientSecret,
36-
pub active_attempt_id: Option<String>,
36+
pub active_attempt_id: Option<common_utils::id_type::GlobalAttemptId>,
3737
#[diesel(deserialize_as = super::OptionalDieselArray<masking::Secret<OrderDetailsWithAmount>>)]
3838
pub order_details: Option<Vec<masking::Secret<OrderDetailsWithAmount>>>,
3939
pub allowed_payment_method_types: Option<pii::SecretSerdeValue>,
@@ -250,7 +250,7 @@ pub struct PaymentIntentNew {
250250
pub last_synced: Option<PrimitiveDateTime>,
251251
pub setup_future_usage: Option<storage_enums::FutureUsage>,
252252
pub client_secret: common_utils::types::ClientSecret,
253-
pub active_attempt_id: Option<String>,
253+
pub active_attempt_id: Option<common_utils::id_type::GlobalAttemptId>,
254254
#[diesel(deserialize_as = super::OptionalDieselArray<masking::Secret<OrderDetailsWithAmount>>)]
255255
pub order_details: Option<Vec<masking::Secret<OrderDetailsWithAmount>>>,
256256
pub allowed_payment_method_types: Option<pii::SecretSerdeValue>,
@@ -359,6 +359,7 @@ pub enum PaymentIntentUpdate {
359359
ConfirmIntent {
360360
status: storage_enums::IntentStatus,
361361
updated_by: String,
362+
active_attempt_id: common_utils::id_type::GlobalAttemptId,
362363
},
363364
/// Update the payment intent details on payment intent confirmation, after calling the connector
364365
ConfirmIntentPostUpdate {
@@ -520,7 +521,7 @@ pub struct PaymentIntentUpdateInternal {
520521
// pub setup_future_usage: Option<storage_enums::FutureUsage>,
521522
// pub metadata: Option<pii::SecretSerdeValue>,
522523
pub modified_at: PrimitiveDateTime,
523-
// pub active_attempt_id: Option<String>,
524+
pub active_attempt_id: Option<common_utils::id_type::GlobalAttemptId>,
524525
// pub description: Option<String>,
525526
// pub statement_descriptor: Option<String>,
526527
// #[diesel(deserialize_as = super::OptionalDieselArray<pii::SecretSerdeValue>)]
@@ -594,7 +595,7 @@ impl PaymentIntentUpdate {
594595
// setup_future_usage,
595596
// metadata,
596597
modified_at: _,
597-
// active_attempt_id,
598+
active_attempt_id,
598599
// description,
599600
// statement_descriptor,
600601
// order_details,
@@ -620,7 +621,7 @@ impl PaymentIntentUpdate {
620621
// setup_future_usage: setup_future_usage.or(source.setup_future_usage),
621622
// metadata: metadata.or(source.metadata),
622623
modified_at: common_utils::date_time::now(),
623-
// active_attempt_id: active_attempt_id.unwrap_or(source.active_attempt_id),
624+
active_attempt_id: active_attempt_id.or(source.active_attempt_id),
624625
// description: description.or(source.description),
625626
// statement_descriptor: statement_descriptor.or(source.statement_descriptor),
626627
// order_details: order_details.or(source.order_details),
@@ -735,15 +736,21 @@ impl PaymentIntentUpdate {
735736
impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
736737
fn from(payment_intent_update: PaymentIntentUpdate) -> Self {
737738
match payment_intent_update {
738-
PaymentIntentUpdate::ConfirmIntent { status, updated_by } => Self {
739+
PaymentIntentUpdate::ConfirmIntent {
740+
status,
741+
updated_by,
742+
active_attempt_id,
743+
} => Self {
739744
status: Some(status),
740745
modified_at: common_utils::date_time::now(),
741746
updated_by,
747+
active_attempt_id: Some(active_attempt_id),
742748
},
743749
PaymentIntentUpdate::ConfirmIntentPostUpdate { status, updated_by } => Self {
744750
status: Some(status),
745751
modified_at: common_utils::date_time::now(),
746752
updated_by,
753+
active_attempt_id: None,
747754
},
748755
}
749756
}

crates/hyperswitch_domain_models/src/payments.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,24 @@ impl PaymentIntent {
111111
pub fn get_id(&self) -> &id_type::GlobalPaymentId {
112112
&self.id
113113
}
114+
115+
#[cfg(feature = "v2")]
116+
pub fn create_start_redirection_url(
117+
&self,
118+
base_url: &str,
119+
publishable_key: String,
120+
) -> CustomResult<url::Url, errors::api_error_response::ApiErrorResponse> {
121+
let start_redirection_url = &format!(
122+
"{}/v2/payments/{}/start_redirection?publishable_key={}&profile_id={}",
123+
base_url,
124+
self.get_id().get_string_repr(),
125+
publishable_key,
126+
self.profile_id.get_string_repr()
127+
);
128+
url::Url::parse(start_redirection_url)
129+
.change_context(errors::api_error_response::ApiErrorResponse::InternalServerError)
130+
.attach_printable("Error creating start redirection url")
131+
}
114132
}
115133

116134
#[cfg(feature = "v2")]
@@ -272,8 +290,8 @@ pub struct PaymentIntent {
272290
pub setup_future_usage: storage_enums::FutureUsage,
273291
/// The client secret that is generated for the payment. This is used to authenticate the payment from client facing apis.
274292
pub client_secret: common_utils::types::ClientSecret,
275-
/// The active attempt for the payment intent. This is the payment attempt that is currently active for the payment intent.
276-
pub active_attempt: Option<RemoteStorageObject<PaymentAttempt>>,
293+
/// The active attempt id for the payment intent. This is the payment attempt that is currently active for the payment intent.
294+
pub active_attempt_id: Option<id_type::GlobalAttemptId>,
277295
/// The order details for the payment.
278296
pub order_details: Option<Vec<Secret<OrderDetailsWithAmount>>>,
279297
/// This is the list of payment method types that are allowed for the payment intent.
@@ -421,7 +439,7 @@ impl PaymentIntent {
421439
last_synced: None,
422440
setup_future_usage: request.setup_future_usage.unwrap_or_default(),
423441
client_secret,
424-
active_attempt: None,
442+
active_attempt_id: None,
425443
order_details,
426444
allowed_payment_method_types,
427445
connector_metadata,

crates/hyperswitch_domain_models/src/payments/payment_attempt.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,7 @@ pub enum PaymentAttemptUpdate {
12961296
status: storage_enums::AttemptStatus,
12971297
connector_payment_id: Option<String>,
12981298
updated_by: String,
1299+
authentication_data: Option<pii::SecretSerdeValue>,
12991300
},
13001301
/// Update the payment attempt on confirming the intent, after calling the connector on error response
13011302
ConfirmIntentError {
@@ -1923,6 +1924,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
19231924
unified_message: None,
19241925
connector_payment_id: None,
19251926
connector: Some(connector),
1927+
authentication_data: None,
19261928
},
19271929
PaymentAttemptUpdate::ConfirmIntentError {
19281930
status,
@@ -1941,11 +1943,13 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
19411943
unified_message: None,
19421944
connector_payment_id: None,
19431945
connector: None,
1946+
authentication_data: None,
19441947
},
19451948
PaymentAttemptUpdate::ConfirmIntentResponse {
19461949
status,
19471950
connector_payment_id,
19481951
updated_by,
1952+
authentication_data,
19491953
} => Self {
19501954
status: Some(status),
19511955
error_message: None,
@@ -1959,6 +1963,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
19591963
unified_message: None,
19601964
connector_payment_id,
19611965
connector: None,
1966+
authentication_data,
19621967
},
19631968
}
19641969
}

crates/hyperswitch_domain_models/src/payments/payment_intent.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ pub enum PaymentIntentUpdate {
274274
ConfirmIntent {
275275
status: storage_enums::IntentStatus,
276276
updated_by: String,
277+
active_attempt_id: id_type::GlobalAttemptId,
277278
},
278279
ConfirmIntentPostUpdate {
279280
status: storage_enums::IntentStatus,
@@ -363,9 +364,14 @@ pub struct PaymentIntentUpdateInternal {
363364
impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
364365
fn from(payment_intent_update: PaymentIntentUpdate) -> Self {
365366
match payment_intent_update {
366-
PaymentIntentUpdate::ConfirmIntent { status, updated_by } => Self {
367+
PaymentIntentUpdate::ConfirmIntent {
368+
status,
369+
updated_by,
370+
active_attempt_id,
371+
} => Self {
367372
status: Some(status),
368373
updated_by,
374+
active_attempt_id: Some(active_attempt_id),
369375
..Default::default()
370376
},
371377
PaymentIntentUpdate::ConfirmIntentPostUpdate { status, updated_by } => Self {
@@ -582,9 +588,15 @@ use diesel_models::{
582588
impl From<PaymentIntentUpdate> for DieselPaymentIntentUpdate {
583589
fn from(value: PaymentIntentUpdate) -> Self {
584590
match value {
585-
PaymentIntentUpdate::ConfirmIntent { status, updated_by } => {
586-
Self::ConfirmIntent { status, updated_by }
587-
}
591+
PaymentIntentUpdate::ConfirmIntent {
592+
status,
593+
updated_by,
594+
active_attempt_id,
595+
} => Self::ConfirmIntent {
596+
status,
597+
updated_by,
598+
active_attempt_id,
599+
},
588600
PaymentIntentUpdate::ConfirmIntentPostUpdate { status, updated_by } => {
589601
Self::ConfirmIntentPostUpdate { status, updated_by }
590602
}
@@ -1134,7 +1146,7 @@ impl behaviour::Conversion for PaymentIntent {
11341146
last_synced,
11351147
setup_future_usage,
11361148
client_secret,
1137-
active_attempt,
1149+
active_attempt_id,
11381150
order_details,
11391151
allowed_payment_method_types,
11401152
connector_metadata,
@@ -1182,7 +1194,7 @@ impl behaviour::Conversion for PaymentIntent {
11821194
last_synced,
11831195
setup_future_usage: Some(setup_future_usage),
11841196
client_secret,
1185-
active_attempt_id: active_attempt.map(|attempt| attempt.get_id()),
1197+
active_attempt_id,
11861198
order_details: order_details.map(|order_details| {
11871199
order_details
11881200
.into_iter()
@@ -1319,9 +1331,7 @@ impl behaviour::Conversion for PaymentIntent {
13191331
last_synced: storage_model.last_synced,
13201332
setup_future_usage: storage_model.setup_future_usage.unwrap_or_default(),
13211333
client_secret: storage_model.client_secret,
1322-
active_attempt: storage_model
1323-
.active_attempt_id
1324-
.map(RemoteStorageObject::ForeignID),
1334+
active_attempt_id: storage_model.active_attempt_id,
13251335
order_details: storage_model.order_details.map(|order_details| {
13261336
order_details
13271337
.into_iter()
@@ -1395,7 +1405,7 @@ impl behaviour::Conversion for PaymentIntent {
13951405
last_synced: self.last_synced,
13961406
setup_future_usage: Some(self.setup_future_usage),
13971407
client_secret: self.client_secret,
1398-
active_attempt_id: self.active_attempt.map(|attempt| attempt.get_id()),
1408+
active_attempt_id: self.active_attempt_id,
13991409
order_details: self.order_details,
14001410
allowed_payment_method_types: self
14011411
.allowed_payment_method_types

0 commit comments

Comments
 (0)