mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 09:07:09 +08:00
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 <mohammed.kashif@juspay.in> Co-authored-by: Kashif <kashif.dev@protonmail.com> Co-authored-by: hrithikeshvm <hrithikeshmylatty@gmail.com> Co-authored-by: hrithikeshvm <vmhrithikesh@gmail.com> Co-authored-by: Sahkal Poddar <sahkalpoddar@Sahkals-MacBook-Air.local>
This commit is contained in:
@ -473,7 +473,7 @@ apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE" #Merchant Cer
|
|||||||
apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" #Private key generate by RSA:2048 algorithm
|
apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" #Private key generate by RSA:2048 algorithm
|
||||||
|
|
||||||
[payment_link]
|
[payment_link]
|
||||||
sdk_url = "http://localhost:9090/dist/HyperLoader.js"
|
sdk_url = "http://localhost:9090/0.16.7/v0/HyperLoader.js"
|
||||||
|
|
||||||
[payment_method_auth]
|
[payment_method_auth]
|
||||||
redis_expiry = 900
|
redis_expiry = 900
|
||||||
|
|||||||
@ -494,7 +494,7 @@ apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE"
|
|||||||
apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY"
|
apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY"
|
||||||
|
|
||||||
[payment_link]
|
[payment_link]
|
||||||
sdk_url = "http://localhost:9090/dist/HyperLoader.js"
|
sdk_url = "http://localhost:9050/HyperLoader.js"
|
||||||
|
|
||||||
[payment_method_auth]
|
[payment_method_auth]
|
||||||
redis_expiry = 900
|
redis_expiry = 900
|
||||||
|
|||||||
@ -3358,6 +3358,13 @@ pub struct PaymentLinkInitiateRequest {
|
|||||||
pub payment_id: String,
|
pub payment_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum PaymentLinkData {
|
||||||
|
PaymentLinkDetails(PaymentLinkDetails),
|
||||||
|
PaymentLinkStatusDetails(PaymentLinkStatusDetails),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
pub struct PaymentLinkDetails {
|
pub struct PaymentLinkDetails {
|
||||||
pub amount: String,
|
pub amount: String,
|
||||||
@ -3376,6 +3383,21 @@ pub struct PaymentLinkDetails {
|
|||||||
pub merchant_description: Option<String>,
|
pub merchant_description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
pub struct PaymentLinkStatusDetails {
|
||||||
|
pub amount: String,
|
||||||
|
pub currency: api_enums::Currency,
|
||||||
|
pub payment_id: String,
|
||||||
|
pub merchant_logo: String,
|
||||||
|
pub merchant_name: String,
|
||||||
|
#[serde(with = "common_utils::custom_serde::iso8601")]
|
||||||
|
pub created: PrimitiveDateTime,
|
||||||
|
pub intent_status: api_enums::IntentStatus,
|
||||||
|
pub payment_link_status: PaymentLinkStatus,
|
||||||
|
pub error_code: Option<String>,
|
||||||
|
pub error_message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
|
|
||||||
@ -3451,7 +3473,8 @@ pub struct OrderDetailsWithStringAmount {
|
|||||||
pub product_img_link: Option<String>,
|
pub product_img_link: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
#[derive(PartialEq, Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PaymentLinkStatus {
|
pub enum PaymentLinkStatus {
|
||||||
Active,
|
Active,
|
||||||
Expired,
|
Expired,
|
||||||
|
|||||||
@ -133,19 +133,34 @@ where
|
|||||||
.map_into_boxed_body()
|
.map_into_boxed_body()
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(api::ApplicationResponse::PaymenkLinkForm(payment_link_data)) => {
|
Ok(api::ApplicationResponse::PaymenkLinkForm(boxed_payment_link_data)) => {
|
||||||
match api::build_payment_link_html(*payment_link_data) {
|
match *boxed_payment_link_data {
|
||||||
Ok(rendered_html) => api::http_response_html_data(rendered_html),
|
api::PaymentLinkAction::PaymentLinkFormData(payment_link_data) => {
|
||||||
Err(_) => api::http_response_err(
|
match api::build_payment_link_html(payment_link_data) {
|
||||||
r#"{
|
Ok(rendered_html) => api::http_response_html_data(rendered_html),
|
||||||
"error": {
|
Err(_) => api::http_response_err(
|
||||||
"message": "Error while rendering payment link html page"
|
r#"{
|
||||||
}
|
"error": {
|
||||||
}"#,
|
"message": "Error while rendering payment link html page"
|
||||||
),
|
}
|
||||||
|
}"#,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
api::PaymentLinkAction::PaymentLinkStatus(payment_link_data) => {
|
||||||
|
match api::get_payment_link_status(payment_link_data) {
|
||||||
|
Ok(rendered_html) => api::http_response_html_data(rendered_html),
|
||||||
|
Err(_) => api::http_response_err(
|
||||||
|
r#"{
|
||||||
|
"error": {
|
||||||
|
"message": "Error while rendering payment link status page"
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(error) => api::log_and_return_error_response(error),
|
Err(error) => api::log_and_return_error_response(error),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,6 @@ use time::PrimitiveDateTime;
|
|||||||
|
|
||||||
use super::errors::{self, RouterResult, StorageErrorExt};
|
use super::errors::{self, RouterResult, StorageErrorExt};
|
||||||
use crate::{
|
use crate::{
|
||||||
core::payments::helpers,
|
|
||||||
errors::RouterResponse,
|
errors::RouterResponse,
|
||||||
routes::AppState,
|
routes::AppState,
|
||||||
services,
|
services,
|
||||||
@ -68,18 +67,6 @@ pub async fn intiate_payment_link_flow(
|
|||||||
.get_required_value("payment_link_id")
|
.get_required_value("payment_link_id")
|
||||||
.change_context(errors::ApiErrorResponse::PaymentLinkNotFound)?;
|
.change_context(errors::ApiErrorResponse::PaymentLinkNotFound)?;
|
||||||
|
|
||||||
helpers::validate_payment_status_against_not_allowed_statuses(
|
|
||||||
&payment_intent.status,
|
|
||||||
&[
|
|
||||||
storage_enums::IntentStatus::Cancelled,
|
|
||||||
storage_enums::IntentStatus::Succeeded,
|
|
||||||
storage_enums::IntentStatus::Processing,
|
|
||||||
storage_enums::IntentStatus::RequiresCapture,
|
|
||||||
storage_enums::IntentStatus::RequiresMerchantAction,
|
|
||||||
],
|
|
||||||
"use payment link for",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let merchant_name_from_merchant_account = merchant_account
|
let merchant_name_from_merchant_account = merchant_account
|
||||||
.merchant_name
|
.merchant_name
|
||||||
.clone()
|
.clone()
|
||||||
@ -101,7 +88,7 @@ pub async fn intiate_payment_link_flow(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let return_url = if let Some(payment_create_return_url) = payment_intent.return_url {
|
let return_url = if let Some(payment_create_return_url) = payment_intent.return_url.clone() {
|
||||||
payment_create_return_url
|
payment_create_return_url
|
||||||
} else {
|
} else {
|
||||||
merchant_account
|
merchant_account
|
||||||
@ -114,23 +101,73 @@ pub async fn intiate_payment_link_flow(
|
|||||||
let (pub_key, currency, client_secret) = validate_sdk_requirements(
|
let (pub_key, currency, client_secret) = validate_sdk_requirements(
|
||||||
merchant_account.publishable_key,
|
merchant_account.publishable_key,
|
||||||
payment_intent.currency,
|
payment_intent.currency,
|
||||||
payment_intent.client_secret,
|
payment_intent.client_secret.clone(),
|
||||||
)?;
|
)?;
|
||||||
let order_details = validate_order_details(payment_intent.order_details, currency)?;
|
let amount = currency
|
||||||
|
.to_currency_base_unit(payment_intent.amount)
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?;
|
||||||
|
let order_details = validate_order_details(payment_intent.order_details.clone(), currency)?;
|
||||||
|
|
||||||
let session_expiry = payment_link.fulfilment_time.unwrap_or_else(|| {
|
let session_expiry = payment_link.fulfilment_time.unwrap_or_else(|| {
|
||||||
common_utils::date_time::now()
|
payment_intent
|
||||||
|
.created_at
|
||||||
.saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY))
|
.saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY))
|
||||||
});
|
});
|
||||||
|
|
||||||
// converting first letter of merchant name to upperCase
|
// converting first letter of merchant name to upperCase
|
||||||
let merchant_name = capitalize_first_char(&payment_link_config.seller_name);
|
let merchant_name = capitalize_first_char(&payment_link_config.seller_name);
|
||||||
|
let css_script = get_color_scheme_css(payment_link_config.clone());
|
||||||
|
let payment_link_status = check_payment_link_status(session_expiry);
|
||||||
|
|
||||||
|
if check_payment_link_invalid_conditions(
|
||||||
|
&payment_intent.status,
|
||||||
|
&[
|
||||||
|
storage_enums::IntentStatus::Cancelled,
|
||||||
|
storage_enums::IntentStatus::Failed,
|
||||||
|
storage_enums::IntentStatus::Processing,
|
||||||
|
storage_enums::IntentStatus::RequiresCapture,
|
||||||
|
storage_enums::IntentStatus::RequiresMerchantAction,
|
||||||
|
storage_enums::IntentStatus::Succeeded,
|
||||||
|
],
|
||||||
|
) || payment_link_status == api_models::payments::PaymentLinkStatus::Expired
|
||||||
|
{
|
||||||
|
let attempt_id = payment_intent.active_attempt.get_id().clone();
|
||||||
|
let payment_attempt = db
|
||||||
|
.find_payment_attempt_by_payment_id_merchant_id_attempt_id(
|
||||||
|
&payment_intent.payment_id,
|
||||||
|
&merchant_id,
|
||||||
|
&attempt_id.clone(),
|
||||||
|
merchant_account.storage_scheme,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
|
||||||
|
let payment_details = api_models::payments::PaymentLinkStatusDetails {
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
payment_id: payment_intent.payment_id,
|
||||||
|
merchant_name,
|
||||||
|
merchant_logo: payment_link_config.clone().logo,
|
||||||
|
created: payment_link.created_at,
|
||||||
|
intent_status: payment_intent.status,
|
||||||
|
payment_link_status,
|
||||||
|
error_code: payment_attempt.error_code,
|
||||||
|
error_message: payment_attempt.error_message,
|
||||||
|
};
|
||||||
|
let js_script = get_js_script(
|
||||||
|
api_models::payments::PaymentLinkData::PaymentLinkStatusDetails(payment_details),
|
||||||
|
)?;
|
||||||
|
let payment_link_error_data = services::PaymentLinkStatusData {
|
||||||
|
js_script,
|
||||||
|
css_script,
|
||||||
|
};
|
||||||
|
return Ok(services::ApplicationResponse::PaymenkLinkForm(Box::new(
|
||||||
|
services::api::PaymentLinkAction::PaymentLinkStatus(payment_link_error_data),
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
let payment_details = api_models::payments::PaymentLinkDetails {
|
let payment_details = api_models::payments::PaymentLinkDetails {
|
||||||
amount: currency
|
amount,
|
||||||
.to_currency_base_unit(payment_intent.amount)
|
|
||||||
.into_report()
|
|
||||||
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?,
|
|
||||||
currency,
|
currency,
|
||||||
payment_id: payment_intent.payment_id,
|
payment_id: payment_intent.payment_id,
|
||||||
merchant_name,
|
merchant_name,
|
||||||
@ -145,15 +182,16 @@ pub async fn intiate_payment_link_flow(
|
|||||||
merchant_description: payment_intent.description,
|
merchant_description: payment_intent.description,
|
||||||
};
|
};
|
||||||
|
|
||||||
let js_script = get_js_script(payment_details)?;
|
let js_script = get_js_script(api_models::payments::PaymentLinkData::PaymentLinkDetails(
|
||||||
let css_script = get_color_scheme_css(payment_link_config.clone());
|
payment_details,
|
||||||
|
))?;
|
||||||
let payment_link_data = services::PaymentLinkFormData {
|
let payment_link_data = services::PaymentLinkFormData {
|
||||||
js_script,
|
js_script,
|
||||||
sdk_url: state.conf.payment_link.sdk_url.clone(),
|
sdk_url: state.conf.payment_link.sdk_url.clone(),
|
||||||
css_script,
|
css_script,
|
||||||
};
|
};
|
||||||
Ok(services::ApplicationResponse::PaymenkLinkForm(Box::new(
|
Ok(services::ApplicationResponse::PaymenkLinkForm(Box::new(
|
||||||
payment_link_data,
|
services::api::PaymentLinkAction::PaymentLinkFormData(payment_link_data),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,13 +199,11 @@ pub async fn intiate_payment_link_flow(
|
|||||||
The get_js_script function is used to inject dynamic value to payment_link sdk, which is unique to every payment.
|
The get_js_script function is used to inject dynamic value to payment_link sdk, which is unique to every payment.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fn get_js_script(
|
fn get_js_script(payment_details: api_models::payments::PaymentLinkData) -> RouterResult<String> {
|
||||||
payment_details: api_models::payments::PaymentLinkDetails,
|
|
||||||
) -> RouterResult<String> {
|
|
||||||
let payment_details_str = serde_json::to_string(&payment_details)
|
let payment_details_str = serde_json::to_string(&payment_details)
|
||||||
.into_report()
|
.into_report()
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
.attach_printable("Failed to serialize PaymentLinkDetails")?;
|
.attach_printable("Failed to serialize PaymentLinkData")?;
|
||||||
Ok(format!("window.__PAYMENT_DETAILS = {payment_details_str};"))
|
Ok(format!("window.__PAYMENT_DETAILS = {payment_details_str};"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,11 +254,11 @@ pub async fn list_payment_link(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_payment_link_status(
|
pub fn check_payment_link_status(
|
||||||
max_age: PrimitiveDateTime,
|
payment_link_expiry: PrimitiveDateTime,
|
||||||
) -> api_models::payments::PaymentLinkStatus {
|
) -> api_models::payments::PaymentLinkStatus {
|
||||||
let curr_time = common_utils::date_time::now();
|
let curr_time = common_utils::date_time::now();
|
||||||
|
|
||||||
if curr_time > max_age {
|
if curr_time > payment_link_expiry {
|
||||||
api_models::payments::PaymentLinkStatus::Expired
|
api_models::payments::PaymentLinkStatus::Expired
|
||||||
} else {
|
} else {
|
||||||
api_models::payments::PaymentLinkStatus::Active
|
api_models::payments::PaymentLinkStatus::Active
|
||||||
@ -369,3 +405,10 @@ fn capitalize_first_char(s: &str) -> String {
|
|||||||
s.to_owned()
|
s.to_owned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_payment_link_invalid_conditions(
|
||||||
|
intent_status: &storage_enums::IntentStatus,
|
||||||
|
not_allowed_statuses: &[storage_enums::IntentStatus],
|
||||||
|
) -> bool {
|
||||||
|
not_allowed_statuses.contains(intent_status)
|
||||||
|
}
|
||||||
|
|||||||
@ -63,7 +63,6 @@
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: white;
|
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +132,6 @@
|
|||||||
#hyper-checkout-cart-image {
|
#hyper-checkout-cart-image {
|
||||||
height: 64px;
|
height: 64px;
|
||||||
width: 64px;
|
width: 64px;
|
||||||
border: 1px solid #e6e6e6;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
@ -344,10 +342,6 @@
|
|||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.payNow {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-spinner {
|
.page-spinner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
@ -605,9 +599,38 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#submit {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
height: 38px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 18px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#submit.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#submit-spinner {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border: 4px solid #fff;
|
||||||
|
border-bottom-color: #ff3d00;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
animation: loading 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 1400px) {
|
@media only screen and (max-width: 1400px) {
|
||||||
body {
|
body {
|
||||||
overflow: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hyper-checkout {
|
.hyper-checkout {
|
||||||
@ -720,6 +743,7 @@
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
width: auto;
|
width: auto;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#payment-form-wrap {
|
#payment-form-wrap {
|
||||||
@ -748,7 +772,7 @@
|
|||||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800"
|
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="hide-scrollbar">
|
||||||
<!-- SVG ICONS -->
|
<!-- SVG ICONS -->
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" display="none">
|
<svg xmlns="http://www.w3.org/2000/svg" display="none">
|
||||||
<defs>
|
<defs>
|
||||||
@ -920,7 +944,7 @@
|
|||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hyper-checkout">
|
<div class="hyper-checkout hide-scrollbar">
|
||||||
<div class="main hidden" id="hyper-checkout-status-canvas">
|
<div class="main hidden" id="hyper-checkout-status-canvas">
|
||||||
<div class="hyper-checkout-status-wrap">
|
<div class="hyper-checkout-status-wrap">
|
||||||
<div id="hyper-checkout-status-header"></div>
|
<div id="hyper-checkout-status-header"></div>
|
||||||
@ -1035,8 +1059,12 @@
|
|||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form id="payment-form">
|
<form id="payment-form" onclick="handleSubmit(); return false;">
|
||||||
<div id="unified-checkout"></div>
|
<div id="unified-checkout"></div>
|
||||||
|
<button id="submit" class="hidden">
|
||||||
|
<span id="submit-spinner" class="hidden"></span>
|
||||||
|
<span id="submit-button-text">Pay now</span>
|
||||||
|
</button>
|
||||||
<div id="payment-message" class="hidden"></div>
|
<div id="payment-message" class="hidden"></div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -1060,7 +1088,7 @@
|
|||||||
window.state = {
|
window.state = {
|
||||||
prevHeight: window.innerHeight,
|
prevHeight: window.innerHeight,
|
||||||
prevWidth: window.innerWidth,
|
prevWidth: window.innerWidth,
|
||||||
isMobileView: window.innerWidth <= 1200,
|
isMobileView: window.innerWidth <= 1400,
|
||||||
currentScreen: "payment_link",
|
currentScreen: "payment_link",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1088,9 +1116,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render UI
|
// Render UI
|
||||||
renderPaymentDetails();
|
renderPaymentDetails(paymentDetails);
|
||||||
renderSDKHeader();
|
renderSDKHeader(paymentDetails);
|
||||||
renderCart();
|
renderCart(paymentDetails);
|
||||||
|
|
||||||
// Deal w loaders
|
// Deal w loaders
|
||||||
show("#sdk-spinner");
|
show("#sdk-spinner");
|
||||||
@ -1098,7 +1126,7 @@
|
|||||||
hide("#unified-checkout");
|
hide("#unified-checkout");
|
||||||
|
|
||||||
// Add event listeners
|
// Add event listeners
|
||||||
initializeEventListeners();
|
initializeEventListeners(paymentDetails);
|
||||||
|
|
||||||
// Initialize SDK
|
// Initialize SDK
|
||||||
if (window.Hyper) {
|
if (window.Hyper) {
|
||||||
@ -1115,30 +1143,46 @@
|
|||||||
}
|
}
|
||||||
boot();
|
boot();
|
||||||
|
|
||||||
function initializeEventListeners() {
|
function initializeEventListeners(paymentDetails) {
|
||||||
var primaryColor = window
|
var primaryColor = paymentDetails.theme;
|
||||||
.getComputedStyle(document.documentElement)
|
|
||||||
.getPropertyValue("--primary-color");
|
|
||||||
var lighterColor = adjustLightness(primaryColor, 1.4);
|
var lighterColor = adjustLightness(primaryColor, 1.4);
|
||||||
var darkerColor = adjustLightness(primaryColor, 0.8);
|
var darkerColor = adjustLightness(primaryColor, 0.8);
|
||||||
var contrastBWColor = invert(primaryColor, true);
|
var contrastBWColor = invert(primaryColor, true);
|
||||||
|
var contrastingTone =
|
||||||
|
Array.isArray(a) && a.length > 4 ? darkerColor : lighterColor;
|
||||||
var hyperCheckoutNode = document.getElementById(
|
var hyperCheckoutNode = document.getElementById(
|
||||||
"hyper-checkout-payment"
|
"hyper-checkout-payment"
|
||||||
);
|
);
|
||||||
|
var hyperCheckoutCartImageNode = document.getElementById(
|
||||||
|
"hyper-checkout-cart-image"
|
||||||
|
);
|
||||||
var hyperCheckoutFooterNode = document.getElementById(
|
var hyperCheckoutFooterNode = document.getElementById(
|
||||||
"hyper-checkout-payment-footer"
|
"hyper-checkout-payment-footer"
|
||||||
);
|
);
|
||||||
var statusRedirectTextNode = document.getElementById(
|
var statusRedirectTextNode = document.getElementById(
|
||||||
"hyper-checkout-status-redirect-message"
|
"hyper-checkout-status-redirect-message"
|
||||||
);
|
);
|
||||||
|
var submitButtonNode = document.getElementById("submit");
|
||||||
|
var submitButtonLoaderNode = document.getElementById("submit-spinner");
|
||||||
|
|
||||||
if (window.innerWidth <= 1200) {
|
if (submitButtonLoaderNode instanceof HTMLSpanElement) {
|
||||||
|
submitButtonLoaderNode.style.borderBottomColor = contrastingTone;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (submitButtonNode instanceof HTMLButtonElement) {
|
||||||
|
submitButtonNode.style.color = contrastBWColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hyperCheckoutCartImageNode instanceof HTMLDivElement) {
|
||||||
|
hyperCheckoutCartImageNode.style.backgroundColor = contrastingTone;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.innerWidth <= 1400) {
|
||||||
statusRedirectTextNode.style.color = "#333333";
|
statusRedirectTextNode.style.color = "#333333";
|
||||||
hyperCheckoutNode.style.color = contrastBWColor;
|
hyperCheckoutNode.style.color = contrastBWColor;
|
||||||
var a = lighterColor.match(/[fF]/gi);
|
var a = lighterColor.match(/[fF]/gi);
|
||||||
hyperCheckoutFooterNode.style.backgroundColor =
|
hyperCheckoutFooterNode.style.backgroundColor = contrastingTone;
|
||||||
Array.isArray(a) && a.length > 4 ? darkerColor : lighterColor;
|
} else if (window.innerWidth > 1400) {
|
||||||
} else if (window.innerWidth > 1200) {
|
|
||||||
statusRedirectTextNode.style.color = contrastBWColor;
|
statusRedirectTextNode.style.color = contrastBWColor;
|
||||||
hyperCheckoutNode.style.color = "#333333";
|
hyperCheckoutNode.style.color = "#333333";
|
||||||
hyperCheckoutFooterNode.style.backgroundColor = "#F5F5F5";
|
hyperCheckoutFooterNode.style.backgroundColor = "#F5F5F5";
|
||||||
@ -1147,7 +1191,7 @@
|
|||||||
window.addEventListener("resize", function (event) {
|
window.addEventListener("resize", function (event) {
|
||||||
var currentHeight = window.innerHeight;
|
var currentHeight = window.innerHeight;
|
||||||
var currentWidth = window.innerWidth;
|
var currentWidth = window.innerWidth;
|
||||||
if (currentWidth <= 1200 && window.state.prevWidth > 1200) {
|
if (currentWidth <= 1400 && window.state.prevWidth > 1400) {
|
||||||
hide("#hyper-checkout-cart");
|
hide("#hyper-checkout-cart");
|
||||||
if (window.state.currentScreen === "payment_link") {
|
if (window.state.currentScreen === "payment_link") {
|
||||||
show("#hyper-footer");
|
show("#hyper-footer");
|
||||||
@ -1162,7 +1206,7 @@
|
|||||||
error
|
error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (currentWidth > 1200 && window.state.prevWidth <= 1200) {
|
} else if (currentWidth > 1400 && window.state.prevWidth <= 1400) {
|
||||||
if (window.state.currentScreen === "payment_link") {
|
if (window.state.currentScreen === "payment_link") {
|
||||||
hide("#hyper-footer");
|
hide("#hyper-footer");
|
||||||
}
|
}
|
||||||
@ -1178,16 +1222,17 @@
|
|||||||
|
|
||||||
window.state.prevHeight = currentHeight;
|
window.state.prevHeight = currentHeight;
|
||||||
window.state.prevWidth = currentWidth;
|
window.state.prevWidth = currentWidth;
|
||||||
window.state.isMobileView = currentWidth <= 1200;
|
window.state.isMobileView = currentWidth <= 1400;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSDK() {
|
function showSDK(paymentDetails) {
|
||||||
checkStatus()
|
checkStatus(paymentDetails)
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (res.showSdk) {
|
if (res.showSdk) {
|
||||||
show("#hyper-checkout-sdk");
|
show("#hyper-checkout-sdk");
|
||||||
show("#hyper-checkout-details");
|
show("#hyper-checkout-details");
|
||||||
|
show("#submit");
|
||||||
} else {
|
} else {
|
||||||
hide("#hyper-checkout-details");
|
hide("#hyper-checkout-details");
|
||||||
hide("#hyper-checkout-sdk");
|
hide("#hyper-checkout-sdk");
|
||||||
@ -1195,6 +1240,8 @@
|
|||||||
hide("#hyper-footer");
|
hide("#hyper-footer");
|
||||||
window.state.currentScreen = "status";
|
window.state.currentScreen = "status";
|
||||||
}
|
}
|
||||||
|
show("#unified-checkout");
|
||||||
|
hide("#sdk-spinner");
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
console.error("Failed to check status", err);
|
console.error("Failed to check status", err);
|
||||||
@ -1217,14 +1264,13 @@
|
|||||||
colorBackground: "rgb(255, 255, 255)",
|
colorBackground: "rgb(255, 255, 255)",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
hyper = window.Hyper(pub_key);
|
hyper = window.Hyper(pub_key, { isPreloadEnabled: false });
|
||||||
widgets = hyper.widgets({
|
widgets = hyper.widgets({
|
||||||
appearance: appearance,
|
appearance: appearance,
|
||||||
clientSecret: client_secret,
|
clientSecret: client_secret,
|
||||||
});
|
});
|
||||||
var unifiedCheckoutOptions = {
|
var unifiedCheckoutOptions = {
|
||||||
layout: "tabs",
|
layout: "tabs",
|
||||||
sdkHandleConfirmPayment: true,
|
|
||||||
branding: "never",
|
branding: "never",
|
||||||
wallets: {
|
wallets: {
|
||||||
walletReturnUrl: paymentDetails.return_url,
|
walletReturnUrl: paymentDetails.return_url,
|
||||||
@ -1237,35 +1283,7 @@
|
|||||||
};
|
};
|
||||||
unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions);
|
unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions);
|
||||||
mountUnifiedCheckout("#unified-checkout");
|
mountUnifiedCheckout("#unified-checkout");
|
||||||
|
showSDK(paymentDetails);
|
||||||
// Add event listener for SDK iframe mutations
|
|
||||||
var orcaIFrame = document.getElementById(
|
|
||||||
"orca-payment-element-iframeRef-unified-checkout"
|
|
||||||
);
|
|
||||||
var callback = function (mutationList, observer) {
|
|
||||||
for (var i = 0; i < mutationList.length; i++) {
|
|
||||||
var mutation = mutationList[i];
|
|
||||||
|
|
||||||
if (
|
|
||||||
mutation.type === "attributes" &&
|
|
||||||
mutation.attributeName === "style"
|
|
||||||
) {
|
|
||||||
show("#unified-checkout");
|
|
||||||
hide("#sdk-spinner");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var observer = new MutationObserver(callback);
|
|
||||||
observer.observe(orcaIFrame, { attributes: true });
|
|
||||||
|
|
||||||
// Handle button press callback
|
|
||||||
var paymentElement = widgets.getElement("payment");
|
|
||||||
if (paymentElement) {
|
|
||||||
paymentElement.on("confirmTriggered", function (event) {
|
|
||||||
handleSubmit(event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
showSDK();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Util functions
|
// Util functions
|
||||||
@ -1277,6 +1295,14 @@
|
|||||||
|
|
||||||
function handleSubmit(e) {
|
function handleSubmit(e) {
|
||||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
var paymentDetails = window.__PAYMENT_DETAILS;
|
||||||
|
|
||||||
|
// Update button loader
|
||||||
|
hide("#submit-button-text");
|
||||||
|
show("#submit-spinner");
|
||||||
|
var submitButtonNode = document.getElementById("submit");
|
||||||
|
submitButtonNode.disabled = true;
|
||||||
|
submitButtonNode.classList.add("disabled");
|
||||||
|
|
||||||
hyper
|
hyper
|
||||||
.confirmPayment({
|
.confirmPayment({
|
||||||
widgets: widgets,
|
widgets: widgets,
|
||||||
@ -1293,9 +1319,6 @@
|
|||||||
} else {
|
} else {
|
||||||
showMessage("An unexpected error occurred.");
|
showMessage("An unexpected error occurred.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-initialize SDK
|
|
||||||
mountUnifiedCheckout("#unified-checkout");
|
|
||||||
} else {
|
} else {
|
||||||
// This point will only be reached if there is an immediate error occurring while confirming the payment. Otherwise, your customer will be redirected to your 'return_url'.
|
// This point will only be reached if there is an immediate error occurring while confirming the payment. Otherwise, your customer will be redirected to your 'return_url'.
|
||||||
// For some payment flows such as Sofort, iDEAL, your customer will be redirected to an intermediate page to complete authorization of the payment, and then redirected to the 'return_url'.
|
// For some payment flows such as Sofort, iDEAL, your customer will be redirected to an intermediate page to complete authorization of the payment, and then redirected to the 'return_url'.
|
||||||
@ -1320,13 +1343,18 @@
|
|||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
console.error("Error confirming payment_intent", error);
|
console.error("Error confirming payment_intent", error);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide("#submit-spinner");
|
||||||
|
show("#submit-button-text");
|
||||||
|
submitButtonNode.disabled = false;
|
||||||
|
submitButtonNode.classList.remove("disabled");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the payment status after payment submission
|
// Fetches the payment status after payment submission
|
||||||
function checkStatus() {
|
function checkStatus(paymentDetails) {
|
||||||
return new window.Promise(function (resolve, reject) {
|
return new window.Promise(function (resolve, reject) {
|
||||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
|
||||||
var res = {
|
var res = {
|
||||||
showSdk: true,
|
showSdk: true,
|
||||||
};
|
};
|
||||||
@ -1669,9 +1697,7 @@
|
|||||||
return formatted;
|
return formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPaymentDetails() {
|
function renderPaymentDetails(paymentDetails) {
|
||||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
|
||||||
|
|
||||||
// Create price node
|
// Create price node
|
||||||
var priceNode = document.createElement("div");
|
var priceNode = document.createElement("div");
|
||||||
priceNode.className = "hyper-checkout-payment-price";
|
priceNode.className = "hyper-checkout-payment-price";
|
||||||
@ -1720,8 +1746,7 @@
|
|||||||
footerNode.append(paymentExpiryNode);
|
footerNode.append(paymentExpiryNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCart() {
|
function renderCart(paymentDetails) {
|
||||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
|
||||||
var orderDetails = paymentDetails.order_details;
|
var orderDetails = paymentDetails.order_details;
|
||||||
|
|
||||||
// Cart items
|
// Cart items
|
||||||
@ -1749,7 +1774,9 @@
|
|||||||
if (totalItems > MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) {
|
if (totalItems > MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) {
|
||||||
var expandButtonNode = document.createElement("div");
|
var expandButtonNode = document.createElement("div");
|
||||||
expandButtonNode.className = "hyper-checkout-cart-button";
|
expandButtonNode.className = "hyper-checkout-cart-button";
|
||||||
expandButtonNode.onclick = handleCartView;
|
expandButtonNode.onclick = () => {
|
||||||
|
handleCartView(paymentDetails);
|
||||||
|
};
|
||||||
var buttonImageNode = document.createElement("svg");
|
var buttonImageNode = document.createElement("svg");
|
||||||
buttonImageNode.id = "hyper-checkout-cart-button-arrow";
|
buttonImageNode.id = "hyper-checkout-cart-button-arrow";
|
||||||
buttonImageNode.innerHTML =
|
buttonImageNode.innerHTML =
|
||||||
@ -1822,8 +1849,7 @@
|
|||||||
cartItemsNode.append(itemWrapperNode);
|
cartItemsNode.append(itemWrapperNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCartView() {
|
function handleCartView(paymentDetails) {
|
||||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
|
||||||
var orderDetails = paymentDetails.order_details;
|
var orderDetails = paymentDetails.order_details;
|
||||||
var MAX_ITEMS_VISIBLE_AFTER_COLLAPSE =
|
var MAX_ITEMS_VISIBLE_AFTER_COLLAPSE =
|
||||||
paymentDetails.max_items_visible_after_collapse;
|
paymentDetails.max_items_visible_after_collapse;
|
||||||
@ -1911,9 +1937,7 @@
|
|||||||
show("#hyper-checkout-cart");
|
show("#hyper-checkout-cart");
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSDKHeader() {
|
function renderSDKHeader(paymentDetails) {
|
||||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
|
||||||
|
|
||||||
// SDK headers' items
|
// SDK headers' items
|
||||||
var sdkHeaderItemNode = document.createElement("div");
|
var sdkHeaderItemNode = document.createElement("div");
|
||||||
sdkHeaderItemNode.className = "hyper-checkout-sdk-items";
|
sdkHeaderItemNode.className = "hyper-checkout-sdk-items";
|
||||||
|
|||||||
355
crates/router/src/core/payment_link/status.html
Normal file
355
crates/router/src/core/payment_link/status.html
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>404 Not Found</title>
|
||||||
|
<style>
|
||||||
|
{{ css_color_scheme }}
|
||||||
|
|
||||||
|
body,
|
||||||
|
body > div {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Montserrat";
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > div {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
overflow: scroll;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper-checkout-status-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
font-family: "Montserrat";
|
||||||
|
width: auto;
|
||||||
|
min-width: 400px;
|
||||||
|
max-width: 800px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hyper-checkout-status-header {
|
||||||
|
max-width: 1200px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border-bottom: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hyper-checkout-status-header,
|
||||||
|
#hyper-checkout-status-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper-checkout-status-amount {
|
||||||
|
font-family: "Montserrat";
|
||||||
|
font-size: 35px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper-checkout-status-merchant-logo {
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 9px;
|
||||||
|
height: 48px;
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hyper-checkout-status-content {
|
||||||
|
height: 100%;
|
||||||
|
flex-flow: column;
|
||||||
|
min-height: 500px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper-checkout-status-image {
|
||||||
|
height: 200px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper-checkout-status-text {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 21px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper-checkout-status-message {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px !important;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper-checkout-status-details {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
max-width: calc(100vw - 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper-checkout-status-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-bottom: 1px solid #e6e6e6;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper-checkout-status-item:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper-checkout-item-header {
|
||||||
|
min-width: 13ch;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper-checkout-item-value {
|
||||||
|
font-size: 12px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ellipsis-container-2 {
|
||||||
|
height: 2.5em;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1136px) {
|
||||||
|
.info {
|
||||||
|
flex-flow: column;
|
||||||
|
align-self: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800"
|
||||||
|
/>
|
||||||
|
<script>
|
||||||
|
{{ payment_details_js_script }}
|
||||||
|
|
||||||
|
function boot() {
|
||||||
|
var paymentDetails = window.__PAYMENT_DETAILS;
|
||||||
|
|
||||||
|
// Attach document icon
|
||||||
|
if (paymentDetails.merchant_logo) {
|
||||||
|
var link = document.createElement("link");
|
||||||
|
link.rel = "icon";
|
||||||
|
link.href = paymentDetails.merchant_logo;
|
||||||
|
link.type = "image/x-icon";
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusDetails = {
|
||||||
|
imageSource: "",
|
||||||
|
message: "",
|
||||||
|
status: "",
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
var paymentId = createItem("Ref Id", paymentDetails.payment_id);
|
||||||
|
statusDetails.items.push(paymentId);
|
||||||
|
|
||||||
|
// Decide screen to render
|
||||||
|
switch (paymentDetails.payment_link_status) {
|
||||||
|
case "expired": {
|
||||||
|
statusDetails.imageSource = "https://i.imgur.com/UD8CEuY.png";
|
||||||
|
statusDetails.status = "Payment Link Expired!";
|
||||||
|
statusDetails.message = "This payment link is expired.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
statusDetails.status = paymentDetails.intent_status;
|
||||||
|
// Render status screen
|
||||||
|
switch (paymentDetails.intent_status) {
|
||||||
|
case "succeeded": {
|
||||||
|
statusDetails.imageSource = "https://i.imgur.com/5BOmYVl.png";
|
||||||
|
statusDetails.message =
|
||||||
|
"We have successfully received your payment";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "processing": {
|
||||||
|
statusDetails.imageSource = "https://i.imgur.com/Yb79Qt4.png";
|
||||||
|
statusDetails.message =
|
||||||
|
"Sorry! Your payment is taking longer than expected. Please check back again in sometime.";
|
||||||
|
statusDetails.status = "Payment Pending";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "failed": {
|
||||||
|
statusDetails.imageSource = "https://i.imgur.com/UD8CEuY.png";
|
||||||
|
statusDetails.status = "Payment Failed!";
|
||||||
|
var errorCodeNode = createItem(
|
||||||
|
"Error code",
|
||||||
|
paymentDetails.error_code
|
||||||
|
);
|
||||||
|
var errorMessageNode = createItem(
|
||||||
|
"Error message",
|
||||||
|
paymentDetails.error_message
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
statusDetails.items.push(errorMessageNode, errorCodeNode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "cancelled": {
|
||||||
|
statusDetails.imageSource = "https://i.imgur.com/UD8CEuY.png";
|
||||||
|
statusDetails.status = "Payment Cancelled";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "requires_merchant_action": {
|
||||||
|
statusDetails.imageSource = "https://i.imgur.com/Yb79Qt4.png";
|
||||||
|
statusDetails.status = "Payment under review";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "requires_capture": {
|
||||||
|
statusDetails.imageSource = "https://i.imgur.com/Yb79Qt4.png";
|
||||||
|
statusDetails.status = "Payment Pending";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "partially_captured": {
|
||||||
|
statusDetails.imageSource = "https://i.imgur.com/Yb79Qt4.png";
|
||||||
|
statusDetails.message = "Partial payment was captured.";
|
||||||
|
statusDetails.status = "Partial Payment Pending";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
statusDetails.imageSource = "https://i.imgur.com/UD8CEuY.png";
|
||||||
|
statusDetails.status = "Something went wrong";
|
||||||
|
// Error details
|
||||||
|
if (typeof paymentDetails.error === "object") {
|
||||||
|
var errorCodeNode = createItem(
|
||||||
|
"Error Code",
|
||||||
|
paymentDetails.error.code
|
||||||
|
);
|
||||||
|
var errorMessageNode = createItem(
|
||||||
|
"Error Message",
|
||||||
|
paymentDetails.error.message
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
statusDetails.items.push(errorMessageNode, errorCodeNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form header
|
||||||
|
var hyperCheckoutImageNode = document.createElement("img");
|
||||||
|
var hyperCheckoutAmountNode = document.createElement("div");
|
||||||
|
|
||||||
|
hyperCheckoutImageNode.src = paymentDetails.merchant_logo;
|
||||||
|
hyperCheckoutImageNode.className =
|
||||||
|
"hyper-checkout-status-merchant-logo";
|
||||||
|
hyperCheckoutAmountNode.innerText =
|
||||||
|
paymentDetails.currency + " " + paymentDetails.amount;
|
||||||
|
hyperCheckoutAmountNode.className = "hyper-checkout-status-amount";
|
||||||
|
var hyperCheckoutHeaderNode = document.getElementById(
|
||||||
|
"hyper-checkout-status-header"
|
||||||
|
);
|
||||||
|
if (hyperCheckoutHeaderNode instanceof HTMLDivElement) {
|
||||||
|
hyperCheckoutHeaderNode.append(
|
||||||
|
hyperCheckoutAmountNode,
|
||||||
|
hyperCheckoutImageNode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form and append items
|
||||||
|
var hyperCheckoutStatusTextNode = document.createElement("div");
|
||||||
|
hyperCheckoutStatusTextNode.innerText = statusDetails.status;
|
||||||
|
hyperCheckoutStatusTextNode.className = "hyper-checkout-status-text";
|
||||||
|
|
||||||
|
var merchantLogoNode = document.createElement("img");
|
||||||
|
merchantLogoNode.src = statusDetails.imageSource;
|
||||||
|
merchantLogoNode.className = "hyper-checkout-status-image";
|
||||||
|
|
||||||
|
var hyperCheckoutStatusMessageNode = document.createElement("div");
|
||||||
|
hyperCheckoutStatusMessageNode.innerText = statusDetails.message;
|
||||||
|
|
||||||
|
var hyperCheckoutDetailsNode = document.createElement("div");
|
||||||
|
hyperCheckoutDetailsNode.className = "hyper-checkout-status-details";
|
||||||
|
if (hyperCheckoutDetailsNode instanceof HTMLDivElement) {
|
||||||
|
hyperCheckoutDetailsNode.append(...statusDetails.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hyperCheckoutContentNode = document.getElementById(
|
||||||
|
"hyper-checkout-status-content"
|
||||||
|
);
|
||||||
|
if (hyperCheckoutContentNode instanceof HTMLDivElement) {
|
||||||
|
hyperCheckoutContentNode.prepend(
|
||||||
|
merchantLogoNode,
|
||||||
|
hyperCheckoutStatusTextNode,
|
||||||
|
hyperCheckoutDetailsNode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createItem(heading, value) {
|
||||||
|
var itemNode = document.createElement("div");
|
||||||
|
itemNode.className = "hyper-checkout-status-item";
|
||||||
|
var headerNode = document.createElement("div");
|
||||||
|
headerNode.className = "hyper-checkout-item-header";
|
||||||
|
headerNode.innerText = heading;
|
||||||
|
var valueNode = document.createElement("div");
|
||||||
|
valueNode.classList.add("hyper-checkout-item-value");
|
||||||
|
// valueNode.classList.add("ellipsis-container-2");
|
||||||
|
valueNode.innerText = value;
|
||||||
|
itemNode.append(headerNode);
|
||||||
|
itemNode.append(valueNode);
|
||||||
|
return itemNode;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="boot()">
|
||||||
|
<div>
|
||||||
|
<div class="hyper-checkout-status-wrap">
|
||||||
|
<div id="hyper-checkout-status-header"></div>
|
||||||
|
<div id="hyper-checkout-status-content"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -733,11 +733,17 @@ pub enum ApplicationResponse<R> {
|
|||||||
TextPlain(String),
|
TextPlain(String),
|
||||||
JsonForRedirection(api::RedirectionResponse),
|
JsonForRedirection(api::RedirectionResponse),
|
||||||
Form(Box<RedirectionFormData>),
|
Form(Box<RedirectionFormData>),
|
||||||
PaymenkLinkForm(Box<PaymentLinkFormData>),
|
PaymenkLinkForm(Box<PaymentLinkAction>),
|
||||||
FileData((Vec<u8>, mime::Mime)),
|
FileData((Vec<u8>, mime::Mime)),
|
||||||
JsonWithHeaders((R, Vec<(String, String)>)),
|
JsonWithHeaders((R, Vec<(String, String)>)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub enum PaymentLinkAction {
|
||||||
|
PaymentLinkFormData(PaymentLinkFormData),
|
||||||
|
PaymentLinkStatus(PaymentLinkStatusData),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct PaymentLinkFormData {
|
pub struct PaymentLinkFormData {
|
||||||
pub js_script: String,
|
pub js_script: String,
|
||||||
@ -745,6 +751,12 @@ pub struct PaymentLinkFormData {
|
|||||||
pub sdk_url: String,
|
pub sdk_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct PaymentLinkStatusData {
|
||||||
|
pub js_script: String,
|
||||||
|
pub css_script: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub struct RedirectionFormData {
|
pub struct RedirectionFormData {
|
||||||
pub redirect_form: RedirectForm,
|
pub redirect_form: RedirectForm,
|
||||||
@ -1051,16 +1063,32 @@ where
|
|||||||
.map_into_boxed_body()
|
.map_into_boxed_body()
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ApplicationResponse::PaymenkLinkForm(payment_link_data)) => {
|
Ok(ApplicationResponse::PaymenkLinkForm(boxed_payment_link_data)) => {
|
||||||
match build_payment_link_html(*payment_link_data) {
|
match *boxed_payment_link_data {
|
||||||
Ok(rendered_html) => http_response_html_data(rendered_html),
|
PaymentLinkAction::PaymentLinkFormData(payment_link_data) => {
|
||||||
Err(_) => http_response_err(
|
match build_payment_link_html(payment_link_data) {
|
||||||
r#"{
|
Ok(rendered_html) => http_response_html_data(rendered_html),
|
||||||
"error": {
|
Err(_) => http_response_err(
|
||||||
"message": "Error while rendering payment link html page"
|
r#"{
|
||||||
}
|
"error": {
|
||||||
}"#,
|
"message": "Error while rendering payment link html page"
|
||||||
),
|
}
|
||||||
|
}"#,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PaymentLinkAction::PaymentLinkStatus(payment_link_data) => {
|
||||||
|
match get_payment_link_status(payment_link_data) {
|
||||||
|
Ok(rendered_html) => http_response_html_data(rendered_html),
|
||||||
|
Err(_) => http_response_err(
|
||||||
|
r#"{
|
||||||
|
"error": {
|
||||||
|
"message": "Error while rendering payment link status page"
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1634,6 +1662,26 @@ fn get_hyper_loader_sdk(sdk_url: &str) -> String {
|
|||||||
format!("<script src=\"{sdk_url}\" onload=\"initializeSDK()\"></script>")
|
format!("<script src=\"{sdk_url}\" onload=\"initializeSDK()\"></script>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_payment_link_status(
|
||||||
|
payment_link_data: PaymentLinkStatusData,
|
||||||
|
) -> CustomResult<String, errors::ApiErrorResponse> {
|
||||||
|
let html_template = include_str!("../core/payment_link/status.html").to_string();
|
||||||
|
let mut tera = Tera::default();
|
||||||
|
let _ = tera.add_raw_template("payment_link_status", &html_template);
|
||||||
|
|
||||||
|
let mut context = Context::new();
|
||||||
|
context.insert("css_color_scheme", &payment_link_data.css_script);
|
||||||
|
context.insert("payment_details_js_script", &payment_link_data.js_script);
|
||||||
|
|
||||||
|
match tera.render("payment_link_status", &context) {
|
||||||
|
Ok(rendered_html) => Ok(rendered_html),
|
||||||
|
Err(tera_error) => {
|
||||||
|
crate::logger::warn!("{tera_error}");
|
||||||
|
Err(errors::ApiErrorResponse::InternalServerError)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -15,7 +15,8 @@ pub(crate) trait PaymentLinkResponseExt: Sized {
|
|||||||
impl PaymentLinkResponseExt for RetrievePaymentLinkResponse {
|
impl PaymentLinkResponseExt for RetrievePaymentLinkResponse {
|
||||||
async fn from_db_payment_link(payment_link: storage::PaymentLink) -> RouterResult<Self> {
|
async fn from_db_payment_link(payment_link: storage::PaymentLink) -> RouterResult<Self> {
|
||||||
let session_expiry = payment_link.fulfilment_time.unwrap_or_else(|| {
|
let session_expiry = payment_link.fulfilment_time.unwrap_or_else(|| {
|
||||||
common_utils::date_time::now()
|
payment_link
|
||||||
|
.created_at
|
||||||
.saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY))
|
.saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY))
|
||||||
});
|
});
|
||||||
let status = payment_link::check_payment_link_status(session_expiry);
|
let status = payment_link::check_payment_link_status(session_expiry);
|
||||||
|
|||||||
@ -8760,8 +8760,8 @@
|
|||||||
"PaymentLinkStatus": {
|
"PaymentLinkStatus": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"Active",
|
"active",
|
||||||
"Expired"
|
"expired"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"PaymentListConstraints": {
|
"PaymentListConstraints": {
|
||||||
|
|||||||
Reference in New Issue
Block a user