Skip to content

Commit 50e4d79

Browse files
sahkalhyperswitch-bot[bot]kashif-mhrithikesh026hrithikeshvm
authored
feat(payment_link): add status page for payment link (#3213)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Kashif <[email protected]> Co-authored-by: Kashif <[email protected]> Co-authored-by: hrithikeshvm <[email protected]> Co-authored-by: hrithikeshvm <[email protected]> Co-authored-by: Sahkal Poddar <[email protected]>
1 parent 8decbea commit 50e4d79

File tree

10 files changed

+642
-133
lines changed

10 files changed

+642
-133
lines changed

config/config.example.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE" #Merchant Cer
473473
apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" #Private key generate by RSA:2048 algorithm
474474

475475
[payment_link]
476-
sdk_url = "http://localhost:9090/dist/HyperLoader.js"
476+
sdk_url = "http://localhost:9090/0.16.7/v0/HyperLoader.js"
477477

478478
[payment_method_auth]
479479
redis_expiry = 900

config/development.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE"
494494
apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY"
495495

496496
[payment_link]
497-
sdk_url = "http://localhost:9090/dist/HyperLoader.js"
497+
sdk_url = "http://localhost:9050/HyperLoader.js"
498498

499499
[payment_method_auth]
500500
redis_expiry = 900

crates/api_models/src/payments.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3358,6 +3358,13 @@ pub struct PaymentLinkInitiateRequest {
33583358
pub payment_id: String,
33593359
}
33603360

3361+
#[derive(Debug, serde::Serialize)]
3362+
#[serde(untagged)]
3363+
pub enum PaymentLinkData {
3364+
PaymentLinkDetails(PaymentLinkDetails),
3365+
PaymentLinkStatusDetails(PaymentLinkStatusDetails),
3366+
}
3367+
33613368
#[derive(Debug, serde::Serialize)]
33623369
pub struct PaymentLinkDetails {
33633370
pub amount: String,
@@ -3376,6 +3383,21 @@ pub struct PaymentLinkDetails {
33763383
pub merchant_description: Option<String>,
33773384
}
33783385

3386+
#[derive(Debug, serde::Serialize)]
3387+
pub struct PaymentLinkStatusDetails {
3388+
pub amount: String,
3389+
pub currency: api_enums::Currency,
3390+
pub payment_id: String,
3391+
pub merchant_logo: String,
3392+
pub merchant_name: String,
3393+
#[serde(with = "common_utils::custom_serde::iso8601")]
3394+
pub created: PrimitiveDateTime,
3395+
pub intent_status: api_enums::IntentStatus,
3396+
pub payment_link_status: PaymentLinkStatus,
3397+
pub error_code: Option<String>,
3398+
pub error_message: Option<String>,
3399+
}
3400+
33793401
#[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]
33803402
#[serde(deny_unknown_fields)]
33813403

@@ -3451,7 +3473,8 @@ pub struct OrderDetailsWithStringAmount {
34513473
pub product_img_link: Option<String>,
34523474
}
34533475

3454-
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
3476+
#[derive(PartialEq, Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
3477+
#[serde(rename_all = "snake_case")]
34553478
pub enum PaymentLinkStatus {
34563479
Active,
34573480
Expired,

crates/router/src/compatibility/wrap.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,19 +133,34 @@ where
133133
.map_into_boxed_body()
134134
}
135135

136-
Ok(api::ApplicationResponse::PaymenkLinkForm(payment_link_data)) => {
137-
match api::build_payment_link_html(*payment_link_data) {
138-
Ok(rendered_html) => api::http_response_html_data(rendered_html),
139-
Err(_) => api::http_response_err(
140-
r#"{
141-
"error": {
142-
"message": "Error while rendering payment link html page"
143-
}
144-
}"#,
145-
),
136+
Ok(api::ApplicationResponse::PaymenkLinkForm(boxed_payment_link_data)) => {
137+
match *boxed_payment_link_data {
138+
api::PaymentLinkAction::PaymentLinkFormData(payment_link_data) => {
139+
match api::build_payment_link_html(payment_link_data) {
140+
Ok(rendered_html) => api::http_response_html_data(rendered_html),
141+
Err(_) => api::http_response_err(
142+
r#"{
143+
"error": {
144+
"message": "Error while rendering payment link html page"
145+
}
146+
}"#,
147+
),
148+
}
149+
}
150+
api::PaymentLinkAction::PaymentLinkStatus(payment_link_data) => {
151+
match api::get_payment_link_status(payment_link_data) {
152+
Ok(rendered_html) => api::http_response_html_data(rendered_html),
153+
Err(_) => api::http_response_err(
154+
r#"{
155+
"error": {
156+
"message": "Error while rendering payment link status page"
157+
}
158+
}"#,
159+
),
160+
}
161+
}
146162
}
147163
}
148-
149164
Err(error) => api::log_and_return_error_response(error),
150165
};
151166

crates/router/src/core/payment_link.rs

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use time::PrimitiveDateTime;
1313

1414
use super::errors::{self, RouterResult, StorageErrorExt};
1515
use crate::{
16-
core::payments::helpers,
1716
errors::RouterResponse,
1817
routes::AppState,
1918
services,
@@ -68,18 +67,6 @@ pub async fn intiate_payment_link_flow(
6867
.get_required_value("payment_link_id")
6968
.change_context(errors::ApiErrorResponse::PaymentLinkNotFound)?;
7069

71-
helpers::validate_payment_status_against_not_allowed_statuses(
72-
&payment_intent.status,
73-
&[
74-
storage_enums::IntentStatus::Cancelled,
75-
storage_enums::IntentStatus::Succeeded,
76-
storage_enums::IntentStatus::Processing,
77-
storage_enums::IntentStatus::RequiresCapture,
78-
storage_enums::IntentStatus::RequiresMerchantAction,
79-
],
80-
"use payment link for",
81-
)?;
82-
8370
let merchant_name_from_merchant_account = merchant_account
8471
.merchant_name
8572
.clone()
@@ -101,7 +88,7 @@ pub async fn intiate_payment_link_flow(
10188
}
10289
};
10390

104-
let return_url = if let Some(payment_create_return_url) = payment_intent.return_url {
91+
let return_url = if let Some(payment_create_return_url) = payment_intent.return_url.clone() {
10592
payment_create_return_url
10693
} else {
10794
merchant_account
@@ -114,23 +101,73 @@ pub async fn intiate_payment_link_flow(
114101
let (pub_key, currency, client_secret) = validate_sdk_requirements(
115102
merchant_account.publishable_key,
116103
payment_intent.currency,
117-
payment_intent.client_secret,
104+
payment_intent.client_secret.clone(),
118105
)?;
119-
let order_details = validate_order_details(payment_intent.order_details, currency)?;
106+
let amount = currency
107+
.to_currency_base_unit(payment_intent.amount)
108+
.into_report()
109+
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?;
110+
let order_details = validate_order_details(payment_intent.order_details.clone(), currency)?;
120111

121112
let session_expiry = payment_link.fulfilment_time.unwrap_or_else(|| {
122-
common_utils::date_time::now()
113+
payment_intent
114+
.created_at
123115
.saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY))
124116
});
125117

126118
// converting first letter of merchant name to upperCase
127119
let merchant_name = capitalize_first_char(&payment_link_config.seller_name);
120+
let css_script = get_color_scheme_css(payment_link_config.clone());
121+
let payment_link_status = check_payment_link_status(session_expiry);
122+
123+
if check_payment_link_invalid_conditions(
124+
&payment_intent.status,
125+
&[
126+
storage_enums::IntentStatus::Cancelled,
127+
storage_enums::IntentStatus::Failed,
128+
storage_enums::IntentStatus::Processing,
129+
storage_enums::IntentStatus::RequiresCapture,
130+
storage_enums::IntentStatus::RequiresMerchantAction,
131+
storage_enums::IntentStatus::Succeeded,
132+
],
133+
) || payment_link_status == api_models::payments::PaymentLinkStatus::Expired
134+
{
135+
let attempt_id = payment_intent.active_attempt.get_id().clone();
136+
let payment_attempt = db
137+
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
138+
&payment_intent.payment_id,
139+
&merchant_id,
140+
&attempt_id.clone(),
141+
merchant_account.storage_scheme,
142+
)
143+
.await
144+
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
145+
let payment_details = api_models::payments::PaymentLinkStatusDetails {
146+
amount,
147+
currency,
148+
payment_id: payment_intent.payment_id,
149+
merchant_name,
150+
merchant_logo: payment_link_config.clone().logo,
151+
created: payment_link.created_at,
152+
intent_status: payment_intent.status,
153+
payment_link_status,
154+
error_code: payment_attempt.error_code,
155+
error_message: payment_attempt.error_message,
156+
};
157+
let js_script = get_js_script(
158+
api_models::payments::PaymentLinkData::PaymentLinkStatusDetails(payment_details),
159+
)?;
160+
let payment_link_error_data = services::PaymentLinkStatusData {
161+
js_script,
162+
css_script,
163+
};
164+
return Ok(services::ApplicationResponse::PaymenkLinkForm(Box::new(
165+
services::api::PaymentLinkAction::PaymentLinkStatus(payment_link_error_data),
166+
)));
167+
};
128168

129169
let payment_details = api_models::payments::PaymentLinkDetails {
130-
amount: currency
131-
.to_currency_base_unit(payment_intent.amount)
132-
.into_report()
133-
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?,
170+
amount,
134171
currency,
135172
payment_id: payment_intent.payment_id,
136173
merchant_name,
@@ -145,29 +182,28 @@ pub async fn intiate_payment_link_flow(
145182
merchant_description: payment_intent.description,
146183
};
147184

148-
let js_script = get_js_script(payment_details)?;
149-
let css_script = get_color_scheme_css(payment_link_config.clone());
185+
let js_script = get_js_script(api_models::payments::PaymentLinkData::PaymentLinkDetails(
186+
payment_details,
187+
))?;
150188
let payment_link_data = services::PaymentLinkFormData {
151189
js_script,
152190
sdk_url: state.conf.payment_link.sdk_url.clone(),
153191
css_script,
154192
};
155193
Ok(services::ApplicationResponse::PaymenkLinkForm(Box::new(
156-
payment_link_data,
194+
services::api::PaymentLinkAction::PaymentLinkFormData(payment_link_data),
157195
)))
158196
}
159197

160198
/*
161199
The get_js_script function is used to inject dynamic value to payment_link sdk, which is unique to every payment.
162200
*/
163201

164-
fn get_js_script(
165-
payment_details: api_models::payments::PaymentLinkDetails,
166-
) -> RouterResult<String> {
202+
fn get_js_script(payment_details: api_models::payments::PaymentLinkData) -> RouterResult<String> {
167203
let payment_details_str = serde_json::to_string(&payment_details)
168204
.into_report()
169205
.change_context(errors::ApiErrorResponse::InternalServerError)
170-
.attach_printable("Failed to serialize PaymentLinkDetails")?;
206+
.attach_printable("Failed to serialize PaymentLinkData")?;
171207
Ok(format!("window.__PAYMENT_DETAILS = {payment_details_str};"))
172208
}
173209

@@ -218,11 +254,11 @@ pub async fn list_payment_link(
218254
}
219255

220256
pub fn check_payment_link_status(
221-
max_age: PrimitiveDateTime,
257+
payment_link_expiry: PrimitiveDateTime,
222258
) -> api_models::payments::PaymentLinkStatus {
223259
let curr_time = common_utils::date_time::now();
224260

225-
if curr_time > max_age {
261+
if curr_time > payment_link_expiry {
226262
api_models::payments::PaymentLinkStatus::Expired
227263
} else {
228264
api_models::payments::PaymentLinkStatus::Active
@@ -369,3 +405,10 @@ fn capitalize_first_char(s: &str) -> String {
369405
s.to_owned()
370406
}
371407
}
408+
409+
fn check_payment_link_invalid_conditions(
410+
intent_status: &storage_enums::IntentStatus,
411+
not_allowed_statuses: &[storage_enums::IntentStatus],
412+
) -> bool {
413+
not_allowed_statuses.contains(intent_status)
414+
}

0 commit comments

Comments
 (0)