From 50e4d797da31b570b5920b33d77c24a21d9871e2 Mon Sep 17 00:00:00 2001 From: Sahkal Poddar Date: Wed, 10 Jan 2024 13:52:37 +0530 Subject: [PATCH] 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 Co-authored-by: Kashif Co-authored-by: hrithikeshvm Co-authored-by: hrithikeshvm Co-authored-by: Sahkal Poddar --- config/config.example.toml | 2 +- config/development.toml | 2 +- crates/api_models/src/payments.rs | 25 +- crates/router/src/compatibility/wrap.rs | 37 +- crates/router/src/core/payment_link.rs | 103 +++-- .../src/core/payment_link/payment_link.html | 174 +++++---- .../router/src/core/payment_link/status.html | 355 ++++++++++++++++++ crates/router/src/services/api.rs | 70 +++- crates/router/src/types/api/payment_link.rs | 3 +- openapi/openapi_spec.json | 4 +- 10 files changed, 642 insertions(+), 133 deletions(-) create mode 100644 crates/router/src/core/payment_link/status.html diff --git a/config/config.example.toml b/config/config.example.toml index 9749a01a8a..4cb2bc085b 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -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 [payment_link] -sdk_url = "http://localhost:9090/dist/HyperLoader.js" +sdk_url = "http://localhost:9090/0.16.7/v0/HyperLoader.js" [payment_method_auth] redis_expiry = 900 diff --git a/config/development.toml b/config/development.toml index 65ec470d19..23917cec3a 100644 --- a/config/development.toml +++ b/config/development.toml @@ -494,7 +494,7 @@ apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE" apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" [payment_link] -sdk_url = "http://localhost:9090/dist/HyperLoader.js" +sdk_url = "http://localhost:9050/HyperLoader.js" [payment_method_auth] redis_expiry = 900 diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 76d62605a4..4ef0c540b5 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -3358,6 +3358,13 @@ pub struct PaymentLinkInitiateRequest { pub payment_id: String, } +#[derive(Debug, serde::Serialize)] +#[serde(untagged)] +pub enum PaymentLinkData { + PaymentLinkDetails(PaymentLinkDetails), + PaymentLinkStatusDetails(PaymentLinkStatusDetails), +} + #[derive(Debug, serde::Serialize)] pub struct PaymentLinkDetails { pub amount: String, @@ -3376,6 +3383,21 @@ pub struct PaymentLinkDetails { pub merchant_description: Option, } +#[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, + pub error_message: Option, +} + #[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)] #[serde(deny_unknown_fields)] @@ -3451,7 +3473,8 @@ pub struct OrderDetailsWithStringAmount { pub product_img_link: Option, } -#[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 { Active, Expired, diff --git a/crates/router/src/compatibility/wrap.rs b/crates/router/src/compatibility/wrap.rs index 1ab156d32a..d3ca0172f2 100644 --- a/crates/router/src/compatibility/wrap.rs +++ b/crates/router/src/compatibility/wrap.rs @@ -133,19 +133,34 @@ where .map_into_boxed_body() } - Ok(api::ApplicationResponse::PaymenkLinkForm(payment_link_data)) => { - match api::build_payment_link_html(*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 html page" - } - }"#, - ), + Ok(api::ApplicationResponse::PaymenkLinkForm(boxed_payment_link_data)) => { + match *boxed_payment_link_data { + api::PaymentLinkAction::PaymentLinkFormData(payment_link_data) => { + match api::build_payment_link_html(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 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), }; diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index f2043d392a..9adf903179 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -13,7 +13,6 @@ use time::PrimitiveDateTime; use super::errors::{self, RouterResult, StorageErrorExt}; use crate::{ - core::payments::helpers, errors::RouterResponse, routes::AppState, services, @@ -68,18 +67,6 @@ pub async fn intiate_payment_link_flow( .get_required_value("payment_link_id") .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 .merchant_name .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 } else { merchant_account @@ -114,23 +101,73 @@ pub async fn intiate_payment_link_flow( let (pub_key, currency, client_secret) = validate_sdk_requirements( merchant_account.publishable_key, 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(|| { - common_utils::date_time::now() + payment_intent + .created_at .saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY)) }); // converting first letter of merchant name to upperCase 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 { - amount: currency - .to_currency_base_unit(payment_intent.amount) - .into_report() - .change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?, + amount, currency, payment_id: payment_intent.payment_id, merchant_name, @@ -145,15 +182,16 @@ pub async fn intiate_payment_link_flow( merchant_description: payment_intent.description, }; - let js_script = get_js_script(payment_details)?; - let css_script = get_color_scheme_css(payment_link_config.clone()); + let js_script = get_js_script(api_models::payments::PaymentLinkData::PaymentLinkDetails( + payment_details, + ))?; let payment_link_data = services::PaymentLinkFormData { js_script, sdk_url: state.conf.payment_link.sdk_url.clone(), css_script, }; 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. */ -fn get_js_script( - payment_details: api_models::payments::PaymentLinkDetails, -) -> RouterResult { +fn get_js_script(payment_details: api_models::payments::PaymentLinkData) -> RouterResult { let payment_details_str = serde_json::to_string(&payment_details) .into_report() .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};")) } @@ -218,11 +254,11 @@ pub async fn list_payment_link( } pub fn check_payment_link_status( - max_age: PrimitiveDateTime, + payment_link_expiry: PrimitiveDateTime, ) -> api_models::payments::PaymentLinkStatus { let curr_time = common_utils::date_time::now(); - if curr_time > max_age { + if curr_time > payment_link_expiry { api_models::payments::PaymentLinkStatus::Expired } else { api_models::payments::PaymentLinkStatus::Active @@ -369,3 +405,10 @@ fn capitalize_first_char(s: &str) -> String { 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) +} diff --git a/crates/router/src/core/payment_link/payment_link.html b/crates/router/src/core/payment_link/payment_link.html index 4fb5bb98ef..3a3ed4fffe 100644 --- a/crates/router/src/core/payment_link/payment_link.html +++ b/crates/router/src/core/payment_link/payment_link.html @@ -63,7 +63,6 @@ width: 100vw; display: flex; justify-content: center; - background-color: white; padding: 20px 0; } @@ -133,7 +132,6 @@ #hyper-checkout-cart-image { height: 64px; width: 64px; - border: 1px solid #e6e6e6; border-radius: 4px; display: flex; align-self: flex-start; @@ -344,10 +342,6 @@ font-size: 25px; } - .payNow { - margin-top: 10px; - } - .page-spinner { position: absolute; width: 100vw; @@ -605,9 +599,38 @@ 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) { body { - overflow: scroll; + overflow-y: scroll; } .hyper-checkout { @@ -720,6 +743,7 @@ background-color: transparent; width: auto; min-width: 300px; + box-shadow: none; } #payment-form-wrap { @@ -748,7 +772,7 @@ href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800" /> - + @@ -920,7 +944,7 @@
-
+
-
+
+
@@ -1060,7 +1088,7 @@ window.state = { prevHeight: window.innerHeight, prevWidth: window.innerWidth, - isMobileView: window.innerWidth <= 1200, + isMobileView: window.innerWidth <= 1400, currentScreen: "payment_link", }; @@ -1088,9 +1116,9 @@ } // Render UI - renderPaymentDetails(); - renderSDKHeader(); - renderCart(); + renderPaymentDetails(paymentDetails); + renderSDKHeader(paymentDetails); + renderCart(paymentDetails); // Deal w loaders show("#sdk-spinner"); @@ -1098,7 +1126,7 @@ hide("#unified-checkout"); // Add event listeners - initializeEventListeners(); + initializeEventListeners(paymentDetails); // Initialize SDK if (window.Hyper) { @@ -1115,30 +1143,46 @@ } boot(); - function initializeEventListeners() { - var primaryColor = window - .getComputedStyle(document.documentElement) - .getPropertyValue("--primary-color"); + function initializeEventListeners(paymentDetails) { + var primaryColor = paymentDetails.theme; var lighterColor = adjustLightness(primaryColor, 1.4); var darkerColor = adjustLightness(primaryColor, 0.8); var contrastBWColor = invert(primaryColor, true); + var contrastingTone = + Array.isArray(a) && a.length > 4 ? darkerColor : lighterColor; var hyperCheckoutNode = document.getElementById( "hyper-checkout-payment" ); + var hyperCheckoutCartImageNode = document.getElementById( + "hyper-checkout-cart-image" + ); var hyperCheckoutFooterNode = document.getElementById( "hyper-checkout-payment-footer" ); var statusRedirectTextNode = document.getElementById( "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"; hyperCheckoutNode.style.color = contrastBWColor; var a = lighterColor.match(/[fF]/gi); - hyperCheckoutFooterNode.style.backgroundColor = - Array.isArray(a) && a.length > 4 ? darkerColor : lighterColor; - } else if (window.innerWidth > 1200) { + hyperCheckoutFooterNode.style.backgroundColor = contrastingTone; + } else if (window.innerWidth > 1400) { statusRedirectTextNode.style.color = contrastBWColor; hyperCheckoutNode.style.color = "#333333"; hyperCheckoutFooterNode.style.backgroundColor = "#F5F5F5"; @@ -1147,7 +1191,7 @@ window.addEventListener("resize", function (event) { var currentHeight = window.innerHeight; var currentWidth = window.innerWidth; - if (currentWidth <= 1200 && window.state.prevWidth > 1200) { + if (currentWidth <= 1400 && window.state.prevWidth > 1400) { hide("#hyper-checkout-cart"); if (window.state.currentScreen === "payment_link") { show("#hyper-footer"); @@ -1162,7 +1206,7 @@ error ); } - } else if (currentWidth > 1200 && window.state.prevWidth <= 1200) { + } else if (currentWidth > 1400 && window.state.prevWidth <= 1400) { if (window.state.currentScreen === "payment_link") { hide("#hyper-footer"); } @@ -1178,16 +1222,17 @@ window.state.prevHeight = currentHeight; window.state.prevWidth = currentWidth; - window.state.isMobileView = currentWidth <= 1200; + window.state.isMobileView = currentWidth <= 1400; }); } - function showSDK() { - checkStatus() + function showSDK(paymentDetails) { + checkStatus(paymentDetails) .then(function (res) { if (res.showSdk) { show("#hyper-checkout-sdk"); show("#hyper-checkout-details"); + show("#submit"); } else { hide("#hyper-checkout-details"); hide("#hyper-checkout-sdk"); @@ -1195,6 +1240,8 @@ hide("#hyper-footer"); window.state.currentScreen = "status"; } + show("#unified-checkout"); + hide("#sdk-spinner"); }) .catch(function (err) { console.error("Failed to check status", err); @@ -1217,14 +1264,13 @@ colorBackground: "rgb(255, 255, 255)", }, }; - hyper = window.Hyper(pub_key); + hyper = window.Hyper(pub_key, { isPreloadEnabled: false }); widgets = hyper.widgets({ appearance: appearance, clientSecret: client_secret, }); var unifiedCheckoutOptions = { layout: "tabs", - sdkHandleConfirmPayment: true, branding: "never", wallets: { walletReturnUrl: paymentDetails.return_url, @@ -1237,35 +1283,7 @@ }; unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions); mountUnifiedCheckout("#unified-checkout"); - - // 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(); + showSDK(paymentDetails); } // Util functions @@ -1277,6 +1295,14 @@ function handleSubmit(e) { 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 .confirmPayment({ widgets: widgets, @@ -1293,9 +1319,6 @@ } else { showMessage("An unexpected error occurred."); } - - // Re-initialize SDK - mountUnifiedCheckout("#unified-checkout"); } 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'. // 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) { 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 - function checkStatus() { + function checkStatus(paymentDetails) { return new window.Promise(function (resolve, reject) { - var paymentDetails = window.__PAYMENT_DETAILS; var res = { showSdk: true, }; @@ -1669,9 +1697,7 @@ return formatted; } - function renderPaymentDetails() { - var paymentDetails = window.__PAYMENT_DETAILS; - + function renderPaymentDetails(paymentDetails) { // Create price node var priceNode = document.createElement("div"); priceNode.className = "hyper-checkout-payment-price"; @@ -1720,8 +1746,7 @@ footerNode.append(paymentExpiryNode); } - function renderCart() { - var paymentDetails = window.__PAYMENT_DETAILS; + function renderCart(paymentDetails) { var orderDetails = paymentDetails.order_details; // Cart items @@ -1749,7 +1774,9 @@ if (totalItems > MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) { var expandButtonNode = document.createElement("div"); expandButtonNode.className = "hyper-checkout-cart-button"; - expandButtonNode.onclick = handleCartView; + expandButtonNode.onclick = () => { + handleCartView(paymentDetails); + }; var buttonImageNode = document.createElement("svg"); buttonImageNode.id = "hyper-checkout-cart-button-arrow"; buttonImageNode.innerHTML = @@ -1822,8 +1849,7 @@ cartItemsNode.append(itemWrapperNode); } - function handleCartView() { - var paymentDetails = window.__PAYMENT_DETAILS; + function handleCartView(paymentDetails) { var orderDetails = paymentDetails.order_details; var MAX_ITEMS_VISIBLE_AFTER_COLLAPSE = paymentDetails.max_items_visible_after_collapse; @@ -1911,9 +1937,7 @@ show("#hyper-checkout-cart"); } - function renderSDKHeader() { - var paymentDetails = window.__PAYMENT_DETAILS; - + function renderSDKHeader(paymentDetails) { // SDK headers' items var sdkHeaderItemNode = document.createElement("div"); sdkHeaderItemNode.className = "hyper-checkout-sdk-items"; diff --git a/crates/router/src/core/payment_link/status.html b/crates/router/src/core/payment_link/status.html new file mode 100644 index 0000000000..d3bb97d294 --- /dev/null +++ b/crates/router/src/core/payment_link/status.html @@ -0,0 +1,355 @@ + + + + + 404 Not Found + + + + + + +
+
+
+
+
+
+ + diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 54df028551..fdaaa87bf4 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -733,11 +733,17 @@ pub enum ApplicationResponse { TextPlain(String), JsonForRedirection(api::RedirectionResponse), Form(Box), - PaymenkLinkForm(Box), + PaymenkLinkForm(Box), FileData((Vec, mime::Mime)), 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)] pub struct PaymentLinkFormData { pub js_script: String, @@ -745,6 +751,12 @@ pub struct PaymentLinkFormData { 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)] pub struct RedirectionFormData { pub redirect_form: RedirectForm, @@ -1051,16 +1063,32 @@ where .map_into_boxed_body() } - Ok(ApplicationResponse::PaymenkLinkForm(payment_link_data)) => { - match build_payment_link_html(*payment_link_data) { - Ok(rendered_html) => http_response_html_data(rendered_html), - Err(_) => http_response_err( - r#"{ - "error": { - "message": "Error while rendering payment link html page" - } - }"#, - ), + Ok(ApplicationResponse::PaymenkLinkForm(boxed_payment_link_data)) => { + match *boxed_payment_link_data { + PaymentLinkAction::PaymentLinkFormData(payment_link_data) => { + match build_payment_link_html(payment_link_data) { + Ok(rendered_html) => http_response_html_data(rendered_html), + Err(_) => http_response_err( + 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!("") } +pub fn get_payment_link_status( + payment_link_data: PaymentLinkStatusData, +) -> CustomResult { + 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)] mod tests { #[test] diff --git a/crates/router/src/types/api/payment_link.rs b/crates/router/src/types/api/payment_link.rs index d0ce8c043b..85cb539d41 100644 --- a/crates/router/src/types/api/payment_link.rs +++ b/crates/router/src/types/api/payment_link.rs @@ -15,7 +15,8 @@ pub(crate) trait PaymentLinkResponseExt: Sized { impl PaymentLinkResponseExt for RetrievePaymentLinkResponse { async fn from_db_payment_link(payment_link: storage::PaymentLink) -> RouterResult { 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)) }); let status = payment_link::check_payment_link_status(session_expiry); diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index bf0df4dc4b..4e6c69b2eb 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -8760,8 +8760,8 @@ "PaymentLinkStatus": { "type": "string", "enum": [ - "Active", - "Expired" + "active", + "expired" ] }, "PaymentListConstraints": {