mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 21:07:58 +08:00
refactor(payment_link): segregated payment link in html css js files, sdk over flow issue, surcharge bug, block SPM customer call for payment link (#3410)
Co-authored-by: Kashif <mohammed.kashif@juspay.in> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Kashif <kashif.dev@protonmail.com> Co-authored-by: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com>
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
use api_models::admin as admin_types;
|
||||
use api_models::{admin as admin_types, payments::PaymentLinkStatusWrap};
|
||||
use common_utils::{
|
||||
consts::{
|
||||
DEFAULT_BACKGROUND_COLOR, DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT,
|
||||
@ -89,10 +89,24 @@ pub async fn intiate_payment_link_flow(
|
||||
}
|
||||
};
|
||||
|
||||
let profile_id = payment_link
|
||||
.profile_id
|
||||
.or(payment_intent.profile_id)
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.into_report()
|
||||
.attach_printable("Profile id missing in payment link and payment intent")?;
|
||||
|
||||
let business_profile = db
|
||||
.find_business_profile_by_profile_id(&profile_id)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound {
|
||||
id: profile_id.to_string(),
|
||||
})?;
|
||||
|
||||
let return_url = if let Some(payment_create_return_url) = payment_intent.return_url.clone() {
|
||||
payment_create_return_url
|
||||
} else {
|
||||
merchant_account
|
||||
business_profile
|
||||
.return_url
|
||||
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "return_url",
|
||||
@ -121,7 +135,7 @@ pub async fn intiate_payment_link_flow(
|
||||
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(
|
||||
let is_terminal_state = check_payment_link_invalid_conditions(
|
||||
&payment_intent.status,
|
||||
&[
|
||||
storage_enums::IntentStatus::Cancelled,
|
||||
@ -130,9 +144,26 @@ pub async fn intiate_payment_link_flow(
|
||||
storage_enums::IntentStatus::RequiresCapture,
|
||||
storage_enums::IntentStatus::RequiresMerchantAction,
|
||||
storage_enums::IntentStatus::Succeeded,
|
||||
storage_enums::IntentStatus::PartiallyCaptured,
|
||||
],
|
||||
) || payment_link_status == api_models::payments::PaymentLinkStatus::Expired
|
||||
);
|
||||
if is_terminal_state || payment_link_status == api_models::payments::PaymentLinkStatus::Expired
|
||||
{
|
||||
let status = match payment_link_status {
|
||||
api_models::payments::PaymentLinkStatus::Active => {
|
||||
PaymentLinkStatusWrap::IntentStatus(payment_intent.status)
|
||||
}
|
||||
api_models::payments::PaymentLinkStatus::Expired => {
|
||||
if is_terminal_state {
|
||||
PaymentLinkStatusWrap::IntentStatus(payment_intent.status)
|
||||
} else {
|
||||
PaymentLinkStatusWrap::PaymentLinkStatus(
|
||||
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(
|
||||
@ -148,12 +179,14 @@ pub async fn intiate_payment_link_flow(
|
||||
currency,
|
||||
payment_id: payment_intent.payment_id,
|
||||
merchant_name,
|
||||
merchant_logo: payment_link_config.clone().logo,
|
||||
merchant_logo: payment_link_config.logo.clone(),
|
||||
created: payment_link.created_at,
|
||||
intent_status: payment_intent.status,
|
||||
payment_link_status,
|
||||
status,
|
||||
error_code: payment_attempt.error_code,
|
||||
error_message: payment_attempt.error_message,
|
||||
redirect: false,
|
||||
theme: payment_link_config.theme.clone(),
|
||||
return_url: return_url.clone(),
|
||||
};
|
||||
let js_script = get_js_script(
|
||||
api_models::payments::PaymentLinkData::PaymentLinkStatusDetails(payment_details),
|
||||
@ -177,11 +210,11 @@ pub async fn intiate_payment_link_flow(
|
||||
session_expiry,
|
||||
pub_key,
|
||||
client_secret,
|
||||
merchant_logo: payment_link_config.clone().logo,
|
||||
merchant_logo: payment_link_config.logo.clone(),
|
||||
max_items_visible_after_collapse: 3,
|
||||
theme: payment_link_config.clone().theme,
|
||||
theme: payment_link_config.theme.clone(),
|
||||
merchant_description: payment_intent.description,
|
||||
sdk_layout: payment_link_config.clone().sdk_layout,
|
||||
sdk_layout: payment_link_config.sdk_layout.clone(),
|
||||
};
|
||||
|
||||
let js_script = get_js_script(api_models::payments::PaymentLinkData::PaymentLinkDetails(
|
||||
@ -425,3 +458,123 @@ fn check_payment_link_invalid_conditions(
|
||||
) -> bool {
|
||||
not_allowed_statuses.contains(intent_status)
|
||||
}
|
||||
|
||||
pub async fn get_payment_link_status(
|
||||
state: AppState,
|
||||
merchant_account: domain::MerchantAccount,
|
||||
merchant_id: String,
|
||||
payment_id: String,
|
||||
) -> RouterResponse<services::PaymentLinkFormData> {
|
||||
let db = &*state.store;
|
||||
let payment_intent = db
|
||||
.find_payment_intent_by_payment_id_merchant_id(
|
||||
&payment_id,
|
||||
&merchant_id,
|
||||
merchant_account.storage_scheme,
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
|
||||
|
||||
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_link_id = payment_intent
|
||||
.payment_link_id
|
||||
.get_required_value("payment_link_id")
|
||||
.change_context(errors::ApiErrorResponse::PaymentLinkNotFound)?;
|
||||
|
||||
let merchant_name_from_merchant_account = merchant_account
|
||||
.merchant_name
|
||||
.clone()
|
||||
.map(|merchant_name| merchant_name.into_inner().peek().to_owned())
|
||||
.unwrap_or_default();
|
||||
|
||||
let payment_link = db
|
||||
.find_payment_link_by_payment_link_id(&payment_link_id)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::PaymentLinkNotFound)?;
|
||||
|
||||
let payment_link_config = if let Some(pl_config_value) = payment_link.payment_link_config {
|
||||
extract_payment_link_config(pl_config_value)?
|
||||
} else {
|
||||
admin_types::PaymentLinkConfig {
|
||||
theme: DEFAULT_BACKGROUND_COLOR.to_string(),
|
||||
logo: DEFAULT_MERCHANT_LOGO.to_string(),
|
||||
seller_name: merchant_name_from_merchant_account,
|
||||
sdk_layout: DEFAULT_SDK_LAYOUT.to_owned(),
|
||||
}
|
||||
};
|
||||
|
||||
let currency =
|
||||
payment_intent
|
||||
.currency
|
||||
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "currency",
|
||||
})?;
|
||||
|
||||
let amount = currency
|
||||
.to_currency_base_unit(payment_attempt.net_amount)
|
||||
.into_report()
|
||||
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?;
|
||||
|
||||
// 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 profile_id = payment_link
|
||||
.profile_id
|
||||
.or(payment_intent.profile_id)
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.into_report()
|
||||
.attach_printable("Profile id missing in payment link and payment intent")?;
|
||||
|
||||
let business_profile = db
|
||||
.find_business_profile_by_profile_id(&profile_id)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound {
|
||||
id: profile_id.to_string(),
|
||||
})?;
|
||||
|
||||
let return_url = if let Some(payment_create_return_url) = payment_intent.return_url.clone() {
|
||||
payment_create_return_url
|
||||
} else {
|
||||
business_profile
|
||||
.return_url
|
||||
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "return_url",
|
||||
})?
|
||||
};
|
||||
|
||||
let payment_details = api_models::payments::PaymentLinkStatusDetails {
|
||||
amount,
|
||||
currency,
|
||||
payment_id: payment_intent.payment_id,
|
||||
merchant_name,
|
||||
merchant_logo: payment_link_config.logo.clone(),
|
||||
created: payment_link.created_at,
|
||||
status: PaymentLinkStatusWrap::IntentStatus(payment_intent.status),
|
||||
error_code: payment_attempt.error_code,
|
||||
error_message: payment_attempt.error_message,
|
||||
redirect: true,
|
||||
theme: payment_link_config.theme.clone(),
|
||||
return_url,
|
||||
};
|
||||
let js_script = get_js_script(
|
||||
api_models::payments::PaymentLinkData::PaymentLinkStatusDetails(payment_details),
|
||||
)?;
|
||||
let payment_link_status_data = services::PaymentLinkStatusData {
|
||||
js_script,
|
||||
css_script,
|
||||
};
|
||||
Ok(services::ApplicationResponse::PaymenkLinkForm(Box::new(
|
||||
services::api::PaymentLinkAction::PaymentLinkStatus(payment_link_status_data),
|
||||
)))
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,785 @@
|
||||
{{ css_color_scheme }}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
margin: 0;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.hide-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.hide-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
/* IE and Edge */
|
||||
scrollbar-width: none;
|
||||
/* Firefox */
|
||||
}
|
||||
|
||||
/* For ellipsis on text lines */
|
||||
.ellipsis-container-3 {
|
||||
height: 4em;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.hyper-checkout {
|
||||
display: flex;
|
||||
background-color: #f8f9fb;
|
||||
color: #333333;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#hyper-footer {
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-width: 600px;
|
||||
width: 50vw;
|
||||
}
|
||||
|
||||
#hyper-checkout-details {
|
||||
font-family: "Montserrat";
|
||||
}
|
||||
|
||||
.hyper-checkout-payment {
|
||||
min-width: 600px;
|
||||
box-shadow: 0px 0px 5px #d1d1d1;
|
||||
border-radius: 8px;
|
||||
background-color: #fefefe;
|
||||
}
|
||||
|
||||
.hyper-checkout-payment-content-details {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: space-between;
|
||||
align-content: space-between;
|
||||
}
|
||||
|
||||
.content-details-wrap {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
margin: 20px 20px 30px 20px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hyper-checkout-payment-price {
|
||||
font-weight: 700;
|
||||
font-size: 40px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#hyper-checkout-payment-merchant-details {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.hyper-checkout-payment-merchant-name {
|
||||
font-weight: 600;
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.hyper-checkout-payment-ref {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.hyper-checkout-image-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#hyper-checkout-merchant-image,
|
||||
#hyper-checkout-cart-image {
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#hyper-checkout-merchant-image > img {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
#hyper-checkout-cart-image {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
border-radius: 100px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
#hyper-checkout-payment-footer {
|
||||
margin-top: 20px;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
padding: 12px 20px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
#hyper-checkout-cart {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
min-width: 600px;
|
||||
margin-top: 40px;
|
||||
max-height: 60vh;
|
||||
}
|
||||
|
||||
#hyper-checkout-cart-items {
|
||||
max-height: 291px;
|
||||
overflow: scroll;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-header {
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-header > span {
|
||||
margin-left: 5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cart-close {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-item {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
padding: 20px 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-product-image {
|
||||
height: 56px;
|
||||
width: 56px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.hyper-checkout-card-item-name {
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.hyper-checkout-card-item-quantity {
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 3px;
|
||||
width: max-content;
|
||||
padding: 5px 12px;
|
||||
background-color: #fafafa;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-product-details {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin-left: 15px;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hyper-checkout-card-item-price {
|
||||
justify-self: flex-end;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
padding-left: 30px;
|
||||
text-align: end;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-item-divider {
|
||||
height: 1px;
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-button {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
align-self: flex-start;
|
||||
display: flex;
|
||||
align-content: flex-end;
|
||||
gap: 3px;
|
||||
text-decoration: none;
|
||||
transition: text-decoration 0.3s;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#hyper-checkout-merchant-description {
|
||||
font-size: 13px;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.powered-by-hyper {
|
||||
margin-top: 40px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.hyper-checkout-sdk {
|
||||
width: 50vw;
|
||||
min-width: 584px;
|
||||
z-index: 2;
|
||||
background-color: var(--primary-color);
|
||||
box-shadow: 0px 1px 10px #f2f2f2;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#payment-form-wrap {
|
||||
min-width: 300px;
|
||||
width: 30vw;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#hyper-checkout-sdk-header {
|
||||
padding: 10px 10px 10px 22px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
.hyper-checkout-sdk-header-logo {
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.hyper-checkout-sdk-header-logo > img {
|
||||
height: 56px;
|
||||
width: 56px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.hyper-checkout-sdk-header-items {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hyper-checkout-sdk-items {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.hyper-checkout-sdk-header-brand-name,
|
||||
.hyper-checkout-sdk-header-amount {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: "Montserrat";
|
||||
justify-self: flex-start;
|
||||
}
|
||||
|
||||
.hyper-checkout-sdk-header-amount {
|
||||
font-weight: 800;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.page-spinner {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 3;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sdk-spinner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 3;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.spinner div {
|
||||
transform-origin: 30px 30px;
|
||||
animation: spinner 1.2s linear infinite;
|
||||
}
|
||||
|
||||
.spinner div:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 28px;
|
||||
width: 4px;
|
||||
height: 15px;
|
||||
border-radius: 20%;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.spinner div:nth-child(1) {
|
||||
transform: rotate(0deg);
|
||||
animation-delay: -1.1s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(2) {
|
||||
transform: rotate(30deg);
|
||||
animation-delay: -1s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(3) {
|
||||
transform: rotate(60deg);
|
||||
animation-delay: -0.9s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(4) {
|
||||
transform: rotate(90deg);
|
||||
animation-delay: -0.8s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(5) {
|
||||
transform: rotate(120deg);
|
||||
animation-delay: -0.7s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(6) {
|
||||
transform: rotate(150deg);
|
||||
animation-delay: -0.6s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(7) {
|
||||
transform: rotate(180deg);
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(8) {
|
||||
transform: rotate(210deg);
|
||||
animation-delay: -0.4s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(9) {
|
||||
transform: rotate(240deg);
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(10) {
|
||||
transform: rotate(270deg);
|
||||
animation-delay: -0.2s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(11) {
|
||||
transform: rotate(300deg);
|
||||
animation-delay: -0.1s;
|
||||
}
|
||||
|
||||
.spinner div:nth-child(12) {
|
||||
transform: rotate(330deg);
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
@keyframes spinner {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#hyper-checkout-status-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.hyper-checkout-status-wrap {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
font-family: "Montserrat";
|
||||
width: auto;
|
||||
min-width: 400px;
|
||||
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;
|
||||
}
|
||||
|
||||
#hyper-checkout-status-redirect-message {
|
||||
margin-top: 20px;
|
||||
font-family: "Montserrat";
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-from-right {
|
||||
from {
|
||||
right: -582px;
|
||||
}
|
||||
|
||||
to {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-to-right {
|
||||
from {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
right: -582px;
|
||||
}
|
||||
}
|
||||
|
||||
#payment-message {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
padding: 2%;
|
||||
color: #ff0000;
|
||||
font-family: "Montserrat";
|
||||
}
|
||||
|
||||
#payment-form {
|
||||
max-width: 560px;
|
||||
width: 100%;
|
||||
min-height: 500px;
|
||||
max-height: 90vh;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
margin: 0 auto;
|
||||
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-y: scroll;
|
||||
}
|
||||
|
||||
.hyper-checkout {
|
||||
flex-flow: column;
|
||||
margin: 0;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#hyper-checkout-payment-merchant-details {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: auto;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.hyper-checkout-payment {
|
||||
min-width: 300px;
|
||||
width: calc(100vw - 50px);
|
||||
margin: 0;
|
||||
padding: 25px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background-color: var(--primary-color);
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-self: flex-start;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.hyper-checkout-payment-content-details {
|
||||
max-width: 520px;
|
||||
width: 100%;
|
||||
align-self: center;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.content-details-wrap {
|
||||
flex-flow: column;
|
||||
flex-direction: column-reverse;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#hyper-checkout-merchant-image {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#hyper-checkout-cart-image {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.hyper-checkout-payment-price {
|
||||
font-size: 48px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.hyper-checkout-payment-merchant-name {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#hyper-checkout-payment-footer {
|
||||
border-radius: 50px;
|
||||
width: max-content;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
#hyper-checkout-cart {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
margin: 0;
|
||||
min-width: 300px;
|
||||
max-width: 582px;
|
||||
max-height: 100vh;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
box-shadow: 0px 10px 10px #aeaeae;
|
||||
right: 0px;
|
||||
animation: slide-from-right 0.3s linear;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-header {
|
||||
margin: 10px 0 0 10px;
|
||||
}
|
||||
|
||||
.cart-close {
|
||||
margin: 0 10px 0 auto;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#hyper-checkout-cart-items {
|
||||
margin: 20px 20px 0 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-button {
|
||||
margin: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.powered-by-hyper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#hyper-checkout-sdk {
|
||||
background-color: transparent;
|
||||
width: auto;
|
||||
min-width: 300px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#payment-form-wrap {
|
||||
min-width: 300px;
|
||||
width: calc(100vw - 40px);
|
||||
margin: 0;
|
||||
padding: 25px 20px;
|
||||
}
|
||||
|
||||
#hyper-checkout-status-canvas {
|
||||
background-color: #fefefe;
|
||||
}
|
||||
|
||||
.hyper-checkout-status-wrap {
|
||||
min-width: 100vw;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
#hyper-checkout-status-header {
|
||||
max-width: calc(100% - 40px);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,197 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Payments requested by HyperSwitch</title>
|
||||
<style>
|
||||
{{rendered_css}}
|
||||
</style>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800"
|
||||
/>
|
||||
</head>
|
||||
<body class="hide-scrollbar">
|
||||
<!-- SVG ICONS -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" display="none">
|
||||
<defs>
|
||||
<symbol id="cart-icon-small">
|
||||
<image href="https://live.hyperswitch.io/payment-link-assets/icons/cart-small.svg"/>
|
||||
</symbol>
|
||||
<symbol id="cart-icon-big">
|
||||
<image href="https://live.hyperswitch.io/payment-link-assets/icons/cart-big.svg"/>
|
||||
</symbol>
|
||||
<symbol id="cart-close">
|
||||
<image href="https://live.hyperswitch.io/payment-link-assets/icons/close.svg"/>
|
||||
</symbol>
|
||||
<symbol id="hyperswitch-brand">
|
||||
<image href="https://live.hyperswitch.io/payment-link-assets/icons/powered-by-hyperswitch.svg" opacity="0.4"/>
|
||||
</symbol>
|
||||
<symbol id="arrow-down">
|
||||
<image href="https://live.hyperswitch.io/payment-link-assets/icons/arrow-down.svg"/>
|
||||
</symbol>
|
||||
<symbol id="arrow-up">
|
||||
<image href="https://live.hyperswitch.io/payment-link-assets/icons/arrow-up.svg"/>
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
||||
<div id="page-spinner" class="page-spinner hidden">
|
||||
<div class="spinner">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hyper-checkout hide-scrollbar">
|
||||
<div class="main hidden" id="hyper-checkout-status-canvas">
|
||||
<div class="hyper-checkout-status-wrap">
|
||||
<div id="hyper-checkout-status-header"></div>
|
||||
<div id="hyper-checkout-status-content"></div>
|
||||
</div>
|
||||
<div id="hyper-checkout-status-redirect-message"></div>
|
||||
</div>
|
||||
<div class="main" id="hyper-checkout-details">
|
||||
<div id="hyper-checkout-payment" class="hyper-checkout-payment">
|
||||
<div class="hyper-checkout-payment-content-details">
|
||||
<div class="content-details-wrap">
|
||||
<div id="hyper-checkout-payment-context">
|
||||
<div id="hyper-checkout-payment-merchant-details"></div>
|
||||
</div>
|
||||
<div class="hyper-checkout-image-header">
|
||||
<div id="hyper-checkout-merchant-image"></div>
|
||||
<div
|
||||
id="hyper-checkout-cart-image"
|
||||
onclick="viewCartInMobileView()"
|
||||
>
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="cart-icon"
|
||||
>
|
||||
<use
|
||||
xlink:href="#cart-icon-big"
|
||||
x="0"
|
||||
y="0"
|
||||
width="30"
|
||||
height="30"
|
||||
></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hyper-checkout-payment-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hyper-checkout-cart" class="">
|
||||
<div
|
||||
id="hyper-checkout-cart-header"
|
||||
class="hyper-checkout-cart-header"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="cart-icon"
|
||||
>
|
||||
<use
|
||||
xlink:href="#cart-icon-small"
|
||||
x="0"
|
||||
y="0"
|
||||
width="16"
|
||||
height="16"
|
||||
></use>
|
||||
</svg>
|
||||
<span>Your Cart</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 50 50"
|
||||
width="25"
|
||||
height="25"
|
||||
class="cart-close"
|
||||
onclick="hideCartInMobileView()"
|
||||
>
|
||||
<use
|
||||
xlink:href="#cart-close"
|
||||
x="0"
|
||||
y="0"
|
||||
width="50"
|
||||
height="50"
|
||||
></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div id="hyper-checkout-cart-items" class="hide-scrollbar"></div>
|
||||
<div id="hyper-checkout-merchant-description"></div>
|
||||
<div class="powered-by-hyper">
|
||||
<svg class="fill-current" height="18" width="130">
|
||||
<use
|
||||
xlink:href="#hyperswitch-brand"
|
||||
x="0"
|
||||
y="0"
|
||||
height="18"
|
||||
width="130"
|
||||
></use>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hyper-checkout-sdk" id="hyper-checkout-sdk">
|
||||
<div id="payment-form-wrap">
|
||||
<div id="sdk-spinner" class="sdk-spinner">
|
||||
<div class="spinner">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<form id="payment-form" onclick="handleSubmit(); return false;">
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hyper-footer" class="hidden">
|
||||
<svg class="fill-current" height="18" width="130">
|
||||
<use
|
||||
xlink:href="#hyperswitch-brand"
|
||||
x="0"
|
||||
y="0"
|
||||
height="18"
|
||||
width="130"
|
||||
></use>
|
||||
</svg>
|
||||
</div>
|
||||
<script>
|
||||
{{rendered_js}}
|
||||
</script>
|
||||
{{ hyperloader_sdk_link }}
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,906 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* UTIL FUNCTIONS
|
||||
*/
|
||||
|
||||
function adjustLightness(hexColor, factor) {
|
||||
// Convert hex to RGB
|
||||
var r = parseInt(hexColor.slice(1, 3), 16);
|
||||
var g = parseInt(hexColor.slice(3, 5), 16);
|
||||
var b = parseInt(hexColor.slice(5, 7), 16);
|
||||
|
||||
// Convert RGB to HSL
|
||||
var hsl = rgbToHsl(r, g, b);
|
||||
|
||||
// Adjust lightness
|
||||
hsl[2] = Math.max(0, Math.min(100, hsl[2] * factor));
|
||||
|
||||
// Convert HSL back to RGB
|
||||
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
|
||||
|
||||
// Convert RGB to hex
|
||||
var newHexColor = rgbToHex(rgb[0], rgb[1], rgb[2]);
|
||||
|
||||
return newHexColor;
|
||||
}
|
||||
function rgbToHsl(r, g, b) {
|
||||
r /= 255;
|
||||
g /= 255;
|
||||
b /= 255;
|
||||
var max = Math.max(r, g, b),
|
||||
min = Math.min(r, g, b);
|
||||
var h = 1,
|
||||
s,
|
||||
l = (max + min) / 2;
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0;
|
||||
} else {
|
||||
var d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0);
|
||||
break;
|
||||
case g:
|
||||
h = (b - r) / d + 2;
|
||||
break;
|
||||
case b:
|
||||
h = (r - g) / d + 4;
|
||||
break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return [h * 360, s * 100, l * 100];
|
||||
}
|
||||
function hslToRgb(h, s, l) {
|
||||
h /= 360;
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
var r, g, b;
|
||||
|
||||
if (s === 0) {
|
||||
r = g = b = l;
|
||||
} else {
|
||||
var hue2rgb = function (p, q, t) {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||
if (t < 1 / 2) return q;
|
||||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||
return p;
|
||||
};
|
||||
|
||||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
var p = 2 * l - q;
|
||||
|
||||
r = hue2rgb(p, q, h + 1 / 3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1 / 3);
|
||||
}
|
||||
|
||||
return [r * 255, g * 255, b * 255];
|
||||
}
|
||||
function rgbToHex(r, g, b) {
|
||||
var toHex = function (c) {
|
||||
var hex = Math.round(c).toString(16);
|
||||
return hex.length === 1 ? "0" + hex : hex;
|
||||
};
|
||||
return "#" + toHex(r) + toHex(g) + toHex(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ref - https://github.com/onury/invert-color/blob/master/lib/cjs/invert.js
|
||||
*/
|
||||
function padz(str, len) {
|
||||
if (len === void 0) {
|
||||
len = 2;
|
||||
}
|
||||
return (new Array(len).join("0") + str).slice(-len);
|
||||
}
|
||||
function hexToRgbArray(hex) {
|
||||
if (hex.slice(0, 1) === "#") hex = hex.slice(1);
|
||||
var RE_HEX = /^(?:[0-9a-f]{3}){1,2}$/i;
|
||||
if (!RE_HEX.test(hex)) throw new Error('Invalid HEX color: "' + hex + '"');
|
||||
if (hex.length === 3) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
||||
}
|
||||
return [
|
||||
parseInt(hex.slice(0, 2), 16),
|
||||
parseInt(hex.slice(2, 4), 16),
|
||||
parseInt(hex.slice(4, 6), 16),
|
||||
];
|
||||
}
|
||||
function toRgbArray(c) {
|
||||
if (!c) throw new Error("Invalid color value");
|
||||
if (Array.isArray(c)) return c;
|
||||
return typeof c === "string" ? hexToRgbArray(c) : [c.r, c.g, c.b];
|
||||
}
|
||||
function getLuminance(c) {
|
||||
var i, x;
|
||||
var a = [];
|
||||
for (i = 0; i < c.length; i++) {
|
||||
x = c[i] / 255;
|
||||
a[i] = x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
|
||||
}
|
||||
function invertToBW(color, bw, asArr) {
|
||||
var DEFAULT_BW = {
|
||||
black: "#090302",
|
||||
white: "#FFFFFC",
|
||||
threshold: Math.sqrt(1.05 * 0.05) - 0.05,
|
||||
};
|
||||
var options = bw === true ? DEFAULT_BW : Object.assign({}, DEFAULT_BW, bw);
|
||||
return getLuminance(color) > options.threshold
|
||||
? asArr
|
||||
? hexToRgbArray(options.black)
|
||||
: options.black
|
||||
: asArr
|
||||
? hexToRgbArray(options.white)
|
||||
: options.white;
|
||||
}
|
||||
function invert(color, bw) {
|
||||
if (bw === void 0) {
|
||||
bw = false;
|
||||
}
|
||||
color = toRgbArray(color);
|
||||
if (bw) return invertToBW(color, bw);
|
||||
return (
|
||||
"#" +
|
||||
color
|
||||
.map(function (c) {
|
||||
return padz((255 - c).toString(16));
|
||||
})
|
||||
.join("")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* UTIL FUNCTIONS END HERE
|
||||
*/
|
||||
|
||||
{{ payment_details_js_script }}
|
||||
|
||||
// @ts-ignore
|
||||
window.state = {
|
||||
prevHeight: window.innerHeight,
|
||||
prevWidth: window.innerWidth,
|
||||
isMobileView: window.innerWidth <= 1400,
|
||||
currentScreen: "payment_link",
|
||||
};
|
||||
|
||||
var widgets = null;
|
||||
var unifiedCheckout = null;
|
||||
// @ts-ignore
|
||||
var pub_key = window.__PAYMENT_DETAILS.pub_key;
|
||||
var hyper = null;
|
||||
|
||||
/**
|
||||
* Trigger - init function invoked once the script tag is loaded
|
||||
* Use
|
||||
* - Update document's title
|
||||
* - Update document's icon
|
||||
* - Render and populate document with payment details and cart
|
||||
* - Initialize event listeners for updating UI on screen size changes
|
||||
* - Initialize SDK
|
||||
**/
|
||||
function boot() {
|
||||
// @ts-ignore
|
||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
||||
|
||||
if (paymentDetails.merchant_name) {
|
||||
document.title = "Payment requested by " + paymentDetails.merchant_name;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Render UI
|
||||
renderPaymentDetails(paymentDetails);
|
||||
renderSDKHeader(paymentDetails);
|
||||
renderCart(paymentDetails);
|
||||
|
||||
// Deal w loaders
|
||||
show("#sdk-spinner");
|
||||
hide("#page-spinner");
|
||||
hide("#unified-checkout");
|
||||
|
||||
// Add event listeners
|
||||
initializeEventListeners(paymentDetails);
|
||||
|
||||
// Initialize SDK
|
||||
// @ts-ignore
|
||||
if (window.Hyper) {
|
||||
initializeSDK();
|
||||
}
|
||||
|
||||
// State specific functions
|
||||
// @ts-ignore
|
||||
if (window.state.isMobileView) {
|
||||
show("#hyper-footer");
|
||||
hide("#hyper-checkout-cart");
|
||||
} else {
|
||||
show("#hyper-checkout-cart");
|
||||
}
|
||||
}
|
||||
boot();
|
||||
|
||||
/**
|
||||
* Use - add event listeners for changing UI on screen resize
|
||||
* @param {PaymentDetails} paymentDetails
|
||||
*/
|
||||
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 a = lighterColor.match(/[fF]/gi);
|
||||
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 submitButtonNode = document.getElementById("submit");
|
||||
var submitButtonLoaderNode = document.getElementById("submit-spinner");
|
||||
|
||||
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) {
|
||||
if (hyperCheckoutNode instanceof HTMLDivElement) {
|
||||
hyperCheckoutNode.style.color = contrastBWColor;
|
||||
}
|
||||
if (hyperCheckoutFooterNode instanceof HTMLDivElement) {
|
||||
hyperCheckoutFooterNode.style.backgroundColor = contrastingTone;
|
||||
}
|
||||
} else if (window.innerWidth > 1400) {
|
||||
if (hyperCheckoutNode instanceof HTMLDivElement) {
|
||||
hyperCheckoutNode.style.color = "#333333";
|
||||
}
|
||||
if (hyperCheckoutFooterNode instanceof HTMLDivElement) {
|
||||
hyperCheckoutFooterNode.style.backgroundColor = "#F5F5F5";
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("resize", function (event) {
|
||||
var currentHeight = window.innerHeight;
|
||||
var currentWidth = window.innerWidth;
|
||||
// @ts-ignore
|
||||
if (currentWidth <= 1400 && window.state.prevWidth > 1400) {
|
||||
hide("#hyper-checkout-cart");
|
||||
// @ts-ignore
|
||||
if (window.state.currentScreen === "payment_link") {
|
||||
show("#hyper-footer");
|
||||
}
|
||||
try {
|
||||
if (hyperCheckoutNode instanceof HTMLDivElement) {
|
||||
hyperCheckoutNode.style.color = contrastBWColor;
|
||||
}
|
||||
if (hyperCheckoutFooterNode instanceof HTMLDivElement) {
|
||||
hyperCheckoutFooterNode.style.backgroundColor = lighterColor;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch primary-color, using default", error);
|
||||
}
|
||||
// @ts-ignore
|
||||
} else if (currentWidth > 1400 && window.state.prevWidth <= 1400) {
|
||||
// @ts-ignore
|
||||
if (window.state.currentScreen === "payment_link") {
|
||||
hide("#hyper-footer");
|
||||
}
|
||||
show("#hyper-checkout-cart");
|
||||
try {
|
||||
if (hyperCheckoutNode instanceof HTMLDivElement) {
|
||||
hyperCheckoutNode.style.color = "#333333";
|
||||
}
|
||||
if (hyperCheckoutFooterNode instanceof HTMLDivElement) {
|
||||
hyperCheckoutFooterNode.style.backgroundColor = "#F5F5F5";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to revert back to default colors", error);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
window.state.prevHeight = currentHeight;
|
||||
// @ts-ignore
|
||||
window.state.prevWidth = currentWidth;
|
||||
// @ts-ignore
|
||||
window.state.isMobileView = currentWidth <= 1400;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger - post mounting SDK
|
||||
* Use - set relevant classes to elements in the doc for showing SDK
|
||||
**/
|
||||
function showSDK() {
|
||||
show("#hyper-checkout-sdk");
|
||||
show("#hyper-checkout-details");
|
||||
show("#submit");
|
||||
show("#unified-checkout");
|
||||
hide("#sdk-spinner");
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger - post downloading SDK
|
||||
* Uses
|
||||
* - Instantiate SDK
|
||||
* - Create a payment widget
|
||||
* - Decide whether or not to show SDK (based on status)
|
||||
**/
|
||||
function initializeSDK() {
|
||||
// @ts-ignore
|
||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
||||
var client_secret = paymentDetails.client_secret;
|
||||
var appearance = {
|
||||
variables: {
|
||||
colorPrimary: paymentDetails.theme || "rgb(0, 109, 249)",
|
||||
fontFamily: "Work Sans, sans-serif",
|
||||
fontSizeBase: "16px",
|
||||
colorText: "rgb(51, 65, 85)",
|
||||
colorTextSecondary: "#334155B3",
|
||||
colorPrimaryText: "rgb(51, 65, 85)",
|
||||
colorTextPlaceholder: "#33415550",
|
||||
borderColor: "#33415550",
|
||||
colorBackground: "rgb(255, 255, 255)",
|
||||
},
|
||||
};
|
||||
// @ts-ignore
|
||||
hyper = window.Hyper(pub_key, {
|
||||
isPreloadEnabled: false,
|
||||
});
|
||||
widgets = hyper.widgets({
|
||||
appearance: appearance,
|
||||
clientSecret: client_secret,
|
||||
});
|
||||
var type =
|
||||
paymentDetails.sdk_layout === "spaced_accordion" ||
|
||||
paymentDetails.sdk_layout === "accordion"
|
||||
? "accordion"
|
||||
: paymentDetails.sdk_layout;
|
||||
|
||||
var unifiedCheckoutOptions = {
|
||||
disableSaveCards: true,
|
||||
layout: {
|
||||
type: type, //accordion , tabs, spaced accordion
|
||||
spacedAccordionItems: paymentDetails.sdk_layout === "spaced_accordion",
|
||||
},
|
||||
branding: "never",
|
||||
wallets: {
|
||||
walletReturnUrl: paymentDetails.return_url,
|
||||
style: {
|
||||
theme: "dark",
|
||||
type: "default",
|
||||
height: 55,
|
||||
},
|
||||
},
|
||||
};
|
||||
unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions);
|
||||
mountUnifiedCheckout("#unified-checkout");
|
||||
showSDK();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use - mount payment widget on the passed element
|
||||
* @param {String} id
|
||||
**/
|
||||
function mountUnifiedCheckout(id) {
|
||||
if (unifiedCheckout !== null) {
|
||||
unifiedCheckout.mount(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger - on clicking submit button
|
||||
* Uses
|
||||
* - Trigger /payment/confirm through SDK
|
||||
* - Toggle UI loaders appropriately
|
||||
* - Handle errors and redirect to status page
|
||||
* @param {Event} e
|
||||
*/
|
||||
function handleSubmit(e) {
|
||||
// @ts-ignore
|
||||
var paymentDetails = window.__PAYMENT_DETAILS;
|
||||
|
||||
// Update button loader
|
||||
hide("#submit-button-text");
|
||||
show("#submit-spinner");
|
||||
var submitButtonNode = document.getElementById("submit");
|
||||
if (submitButtonNode instanceof HTMLButtonElement) {
|
||||
submitButtonNode.disabled = true;
|
||||
submitButtonNode.classList.add("disabled");
|
||||
}
|
||||
|
||||
hyper
|
||||
.confirmPayment({
|
||||
widgets: widgets,
|
||||
confirmParams: {
|
||||
// Make sure to change this to your payment completion page
|
||||
return_url: paymentDetails.return_url,
|
||||
},
|
||||
})
|
||||
.then(function (result) {
|
||||
var error = result.error;
|
||||
if (error) {
|
||||
if (error.type === "validation_error") {
|
||||
showMessage(error.message);
|
||||
} else {
|
||||
showMessage("An unexpected error occurred.");
|
||||
}
|
||||
} else {
|
||||
redirectToStatus();
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error("Error confirming payment_intent", error);
|
||||
})
|
||||
.finally(() => {
|
||||
hide("#submit-spinner");
|
||||
show("#submit-button-text");
|
||||
if (submitButtonNode instanceof HTMLButtonElement) {
|
||||
submitButtonNode.disabled = false;
|
||||
submitButtonNode.classList.remove("disabled");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function show(id) {
|
||||
removeClass(id, "hidden");
|
||||
}
|
||||
function hide(id) {
|
||||
addClass(id, "hidden");
|
||||
}
|
||||
|
||||
function showMessage(msg) {
|
||||
show("#payment-message");
|
||||
addText("#payment-message", msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use - redirect to /payment_link/status
|
||||
*/
|
||||
function redirectToStatus() {
|
||||
var arr = window.location.pathname.split("/");
|
||||
arr.splice(0, 2);
|
||||
arr.unshift("status");
|
||||
arr.unshift("payment_link");
|
||||
window.location.href = window.location.origin + "/" + arr.join("/");
|
||||
}
|
||||
|
||||
function addText(id, msg) {
|
||||
var element = document.querySelector(id);
|
||||
element.innerText = msg;
|
||||
}
|
||||
|
||||
function addClass(id, className) {
|
||||
var element = document.querySelector(id);
|
||||
element.classList.add(className);
|
||||
}
|
||||
|
||||
function removeClass(id, className) {
|
||||
var element = document.querySelector(id);
|
||||
element.classList.remove(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use - format date in "hh:mm AM/PM timezone MM DD, YYYY"
|
||||
* @param {Date} date
|
||||
**/
|
||||
function formatDate(date) {
|
||||
var months = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
];
|
||||
|
||||
var hours = date.getHours();
|
||||
var minutes = date.getMinutes();
|
||||
minutes = minutes < 10 ? "0" + minutes : minutes;
|
||||
var suffix = hours > 11 ? "PM" : "AM";
|
||||
hours = hours % 12;
|
||||
hours = hours ? hours : 12;
|
||||
var day = date.getDate();
|
||||
var month = months[date.getMonth()];
|
||||
var year = date.getUTCFullYear();
|
||||
|
||||
// @ts-ignore
|
||||
var locale = navigator.language || navigator.userLanguage;
|
||||
var timezoneShorthand = date
|
||||
.toLocaleDateString(locale, {
|
||||
day: "2-digit",
|
||||
timeZoneName: "long",
|
||||
})
|
||||
.substring(4)
|
||||
.split(" ")
|
||||
.reduce(function (tz, c) {
|
||||
return tz + c.charAt(0).toUpperCase();
|
||||
}, "");
|
||||
|
||||
var formatted =
|
||||
hours +
|
||||
":" +
|
||||
minutes +
|
||||
" " +
|
||||
suffix +
|
||||
" " +
|
||||
timezoneShorthand +
|
||||
" " +
|
||||
month +
|
||||
" " +
|
||||
day +
|
||||
", " +
|
||||
year;
|
||||
return formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger - on boot
|
||||
* Uses
|
||||
* - Render payment related details (header bit)
|
||||
* - Amount
|
||||
* - Merchant's name
|
||||
* - Expiry
|
||||
* @param {PaymentDetails} paymentDetails
|
||||
**/
|
||||
function renderPaymentDetails(paymentDetails) {
|
||||
// Create price node
|
||||
var priceNode = document.createElement("div");
|
||||
priceNode.className = "hyper-checkout-payment-price";
|
||||
priceNode.innerText = paymentDetails.currency + " " + paymentDetails.amount;
|
||||
|
||||
// Create merchant name's node
|
||||
var merchantNameNode = document.createElement("div");
|
||||
merchantNameNode.className = "hyper-checkout-payment-merchant-name";
|
||||
merchantNameNode.innerText = "Requested by " + paymentDetails.merchant_name;
|
||||
|
||||
// Create payment ID node
|
||||
var paymentIdNode = document.createElement("div");
|
||||
paymentIdNode.className = "hyper-checkout-payment-ref";
|
||||
paymentIdNode.innerText = "Ref Id: " + paymentDetails.payment_id;
|
||||
|
||||
// Create merchant logo's node
|
||||
var merchantLogoNode = document.createElement("img");
|
||||
merchantLogoNode.src = paymentDetails.merchant_logo;
|
||||
|
||||
// Create expiry node
|
||||
var paymentExpiryNode = document.createElement("div");
|
||||
paymentExpiryNode.className = "hyper-checkout-payment-footer-expiry";
|
||||
var expiryDate = new Date(paymentDetails.session_expiry);
|
||||
var formattedDate = formatDate(expiryDate);
|
||||
paymentExpiryNode.innerText = "Link expires on: " + formattedDate;
|
||||
|
||||
// Append information to DOM
|
||||
var paymentContextNode = document.getElementById(
|
||||
"hyper-checkout-payment-context"
|
||||
);
|
||||
if (paymentContextNode instanceof HTMLDivElement) {
|
||||
paymentContextNode.prepend(priceNode);
|
||||
}
|
||||
var paymentMerchantDetails = document.getElementById(
|
||||
"hyper-checkout-payment-merchant-details"
|
||||
);
|
||||
if (paymentMerchantDetails instanceof HTMLDivElement) {
|
||||
paymentMerchantDetails.append(merchantNameNode);
|
||||
paymentMerchantDetails.append(paymentIdNode);
|
||||
}
|
||||
var merchantImageNode = document.getElementById(
|
||||
"hyper-checkout-merchant-image"
|
||||
);
|
||||
if (merchantImageNode instanceof HTMLDivElement) {
|
||||
merchantImageNode.prepend(merchantLogoNode);
|
||||
}
|
||||
var footerNode = document.getElementById("hyper-checkout-payment-footer");
|
||||
if (footerNode instanceof HTMLDivElement) {
|
||||
footerNode.append(paymentExpiryNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger - on boot
|
||||
* Uses
|
||||
* - Render cart wrapper and items
|
||||
* - Attaches an onclick event for toggling expand on the items list
|
||||
* @param {PaymentDetails} paymentDetails
|
||||
**/
|
||||
function renderCart(paymentDetails) {
|
||||
var orderDetails = paymentDetails.order_details;
|
||||
|
||||
// Cart items
|
||||
if (Array.isArray(orderDetails) && orderDetails.length > 0) {
|
||||
var cartNode = document.getElementById("hyper-checkout-cart");
|
||||
var cartItemsNode = document.getElementById("hyper-checkout-cart-items");
|
||||
var MAX_ITEMS_VISIBLE_AFTER_COLLAPSE =
|
||||
paymentDetails.max_items_visible_after_collapse;
|
||||
|
||||
orderDetails.map(function (item, index) {
|
||||
if (index >= MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) {
|
||||
return;
|
||||
}
|
||||
renderCartItem(
|
||||
item,
|
||||
paymentDetails,
|
||||
index !== 0 && index < MAX_ITEMS_VISIBLE_AFTER_COLLAPSE,
|
||||
cartItemsNode
|
||||
);
|
||||
});
|
||||
// Expand / collapse button
|
||||
var totalItems = orderDetails.length;
|
||||
if (totalItems > MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) {
|
||||
var expandButtonNode = document.createElement("div");
|
||||
expandButtonNode.className = "hyper-checkout-cart-button";
|
||||
expandButtonNode.onclick = () => {
|
||||
handleCartView(paymentDetails);
|
||||
};
|
||||
var buttonImageNode = document.createElement("svg");
|
||||
buttonImageNode.id = "hyper-checkout-cart-button-arrow";
|
||||
var arrowDownImage = document.getElementById("arrow-down");
|
||||
if (arrowDownImage instanceof Object) {
|
||||
buttonImageNode.innerHTML = arrowDownImage.innerHTML;
|
||||
}
|
||||
var buttonTextNode = document.createElement("span");
|
||||
buttonTextNode.id = "hyper-checkout-cart-button-text";
|
||||
var hiddenItemsCount =
|
||||
orderDetails.length - MAX_ITEMS_VISIBLE_AFTER_COLLAPSE;
|
||||
buttonTextNode.innerText = "Show More (" + hiddenItemsCount + ")";
|
||||
expandButtonNode.append(buttonTextNode, buttonImageNode);
|
||||
if (cartNode instanceof HTMLDivElement) {
|
||||
cartNode.insertBefore(expandButtonNode, cartNode.lastElementChild);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hide("#hyper-checkout-cart-header");
|
||||
hide("#hyper-checkout-cart-items");
|
||||
hide("#hyper-checkout-cart-image");
|
||||
if (
|
||||
typeof paymentDetails.merchant_description === "string" &&
|
||||
paymentDetails.merchant_description.length > 0
|
||||
) {
|
||||
var merchantDescriptionNode = document.getElementById(
|
||||
"hyper-checkout-merchant-description"
|
||||
);
|
||||
if (merchantDescriptionNode instanceof HTMLDivElement) {
|
||||
merchantDescriptionNode.innerText = paymentDetails.merchant_description;
|
||||
}
|
||||
show("#hyper-checkout-merchant-description");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger - on cart render
|
||||
* Uses
|
||||
* - Renders a single cart item which includes
|
||||
* - Product image
|
||||
* - Product name
|
||||
* - Quantity
|
||||
* - Single item amount
|
||||
* @param {OrderDetailsWithAmount} item
|
||||
* @param {PaymentDetails} paymentDetails
|
||||
* @param {boolean} shouldAddDividerNode
|
||||
* @param {HTMLDivElement} cartItemsNode
|
||||
**/
|
||||
function renderCartItem(
|
||||
item,
|
||||
paymentDetails,
|
||||
shouldAddDividerNode,
|
||||
cartItemsNode
|
||||
) {
|
||||
// Wrappers
|
||||
var itemWrapperNode = document.createElement("div");
|
||||
itemWrapperNode.className = "hyper-checkout-cart-item";
|
||||
var nameAndQuantityWrapperNode = document.createElement("div");
|
||||
nameAndQuantityWrapperNode.className = "hyper-checkout-cart-product-details";
|
||||
// Image
|
||||
var productImageNode = document.createElement("img");
|
||||
productImageNode.className = "hyper-checkout-cart-product-image";
|
||||
productImageNode.src = item.product_img_link;
|
||||
// Product title
|
||||
var productNameNode = document.createElement("div");
|
||||
productNameNode.className = "hyper-checkout-card-item-name";
|
||||
productNameNode.innerText = item.product_name;
|
||||
// Product quantity
|
||||
var quantityNode = document.createElement("div");
|
||||
quantityNode.className = "hyper-checkout-card-item-quantity";
|
||||
quantityNode.innerText = "Qty: " + item.quantity;
|
||||
// Product price
|
||||
var priceNode = document.createElement("div");
|
||||
priceNode.className = "hyper-checkout-card-item-price";
|
||||
priceNode.innerText = paymentDetails.currency + " " + item.amount;
|
||||
// Append items
|
||||
nameAndQuantityWrapperNode.append(productNameNode, quantityNode);
|
||||
itemWrapperNode.append(
|
||||
productImageNode,
|
||||
nameAndQuantityWrapperNode,
|
||||
priceNode
|
||||
);
|
||||
if (shouldAddDividerNode) {
|
||||
var dividerNode = document.createElement("div");
|
||||
dividerNode.className = "hyper-checkout-cart-item-divider";
|
||||
cartItemsNode.append(dividerNode);
|
||||
}
|
||||
cartItemsNode.append(itemWrapperNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger - on toggling expansion of cart list
|
||||
* Uses
|
||||
* - Render or delete items based on current state of the rendered cart list
|
||||
* @param {PaymentDetails} paymentDetails
|
||||
**/
|
||||
function handleCartView(paymentDetails) {
|
||||
var orderDetails = paymentDetails.order_details;
|
||||
var MAX_ITEMS_VISIBLE_AFTER_COLLAPSE =
|
||||
paymentDetails.max_items_visible_after_collapse;
|
||||
var itemsHTMLCollection = document.getElementsByClassName(
|
||||
"hyper-checkout-cart-item"
|
||||
);
|
||||
var dividerHTMLCollection = document.getElementsByClassName(
|
||||
"hyper-checkout-cart-item-divider"
|
||||
);
|
||||
var cartItems = [].slice.call(itemsHTMLCollection);
|
||||
var dividerItems = [].slice.call(dividerHTMLCollection);
|
||||
var isHidden = cartItems.length < orderDetails.length;
|
||||
var cartItemsNode = document.getElementById("hyper-checkout-cart-items");
|
||||
var cartButtonTextNode = document.getElementById(
|
||||
"hyper-checkout-cart-button-text"
|
||||
);
|
||||
var cartButtonImageNode = document.getElementById(
|
||||
"hyper-checkout-cart-button-arrow"
|
||||
);
|
||||
if (isHidden) {
|
||||
if (Array.isArray(orderDetails)) {
|
||||
orderDetails.map(function (item, index) {
|
||||
if (index < MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) {
|
||||
return;
|
||||
}
|
||||
renderCartItem(
|
||||
item,
|
||||
paymentDetails,
|
||||
index >= MAX_ITEMS_VISIBLE_AFTER_COLLAPSE,
|
||||
cartItemsNode
|
||||
);
|
||||
});
|
||||
}
|
||||
if (cartItemsNode instanceof HTMLDivElement) {
|
||||
cartItemsNode.style.maxHeight = cartItemsNode.scrollHeight + "px";
|
||||
cartItemsNode.style.height = cartItemsNode.scrollHeight + "px";
|
||||
}
|
||||
if (cartButtonTextNode instanceof HTMLButtonElement) {
|
||||
cartButtonTextNode.innerText = "Show Less";
|
||||
}
|
||||
var arrowUpImage = document.getElementById("arrow-up");
|
||||
if (
|
||||
cartButtonImageNode instanceof Object &&
|
||||
arrowUpImage instanceof Object
|
||||
) {
|
||||
cartButtonImageNode.innerHTML = arrowUpImage.innerHTML;
|
||||
}
|
||||
} else {
|
||||
if (cartItemsNode instanceof HTMLDivElement) {
|
||||
cartItemsNode.style.maxHeight = "300px";
|
||||
cartItemsNode.style.height = "290px";
|
||||
cartItemsNode.scrollTo({ top: 0, behavior: "smooth" });
|
||||
setTimeout(function () {
|
||||
cartItems.map(function (item, index) {
|
||||
if (index < MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) {
|
||||
return;
|
||||
}
|
||||
if (cartItemsNode instanceof HTMLDivElement) {
|
||||
cartItemsNode.removeChild(item);
|
||||
}
|
||||
});
|
||||
dividerItems.map(function (item, index) {
|
||||
if (index < MAX_ITEMS_VISIBLE_AFTER_COLLAPSE - 1) {
|
||||
return;
|
||||
}
|
||||
if (cartItemsNode instanceof HTMLDivElement) {
|
||||
cartItemsNode.removeChild(item);
|
||||
}
|
||||
});
|
||||
}, 300);
|
||||
}
|
||||
setTimeout(function () {
|
||||
var hiddenItemsCount =
|
||||
orderDetails.length - MAX_ITEMS_VISIBLE_AFTER_COLLAPSE;
|
||||
if (cartButtonTextNode instanceof HTMLButtonElement) {
|
||||
cartButtonTextNode.innerText = "Show More (" + hiddenItemsCount + ")";
|
||||
}
|
||||
var arrowDownImage = document.getElementById("arrow-down");
|
||||
if (
|
||||
cartButtonImageNode instanceof Object &&
|
||||
arrowDownImage instanceof Object
|
||||
) {
|
||||
cartButtonImageNode.innerHTML = arrowDownImage.innerHTML;
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use - hide cart when in mobile view
|
||||
**/
|
||||
function hideCartInMobileView() {
|
||||
window.history.back();
|
||||
var cartNode = document.getElementById("hyper-checkout-cart");
|
||||
if (cartNode instanceof HTMLDivElement) {
|
||||
cartNode.style.animation = "slide-to-right 0.3s linear";
|
||||
cartNode.style.right = "-582px";
|
||||
}
|
||||
setTimeout(function () {
|
||||
hide("#hyper-checkout-cart");
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use - show cart when in mobile view
|
||||
**/
|
||||
function viewCartInMobileView() {
|
||||
window.history.pushState("view-cart", "");
|
||||
var cartNode = document.getElementById("hyper-checkout-cart");
|
||||
if (cartNode instanceof HTMLDivElement) {
|
||||
cartNode.style.animation = "slide-from-right 0.3s linear";
|
||||
cartNode.style.right = "0px";
|
||||
}
|
||||
show("#hyper-checkout-cart");
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger - on boot
|
||||
* Uses
|
||||
* - Render SDK header node
|
||||
* - merchant's name
|
||||
* - currency + amount
|
||||
* @param {PaymentDetails} paymentDetails
|
||||
**/
|
||||
function renderSDKHeader(paymentDetails) {
|
||||
// SDK headers' items
|
||||
var sdkHeaderItemNode = document.createElement("div");
|
||||
sdkHeaderItemNode.className = "hyper-checkout-sdk-items";
|
||||
var sdkHeaderMerchantNameNode = document.createElement("div");
|
||||
sdkHeaderMerchantNameNode.className = "hyper-checkout-sdk-header-brand-name";
|
||||
sdkHeaderMerchantNameNode.innerText = paymentDetails.merchant_name;
|
||||
var sdkHeaderAmountNode = document.createElement("div");
|
||||
sdkHeaderAmountNode.className = "hyper-checkout-sdk-header-amount";
|
||||
sdkHeaderAmountNode.innerText =
|
||||
paymentDetails.currency + " " + paymentDetails.amount;
|
||||
sdkHeaderItemNode.append(sdkHeaderMerchantNameNode);
|
||||
sdkHeaderItemNode.append(sdkHeaderAmountNode);
|
||||
|
||||
// Append to SDK header's node
|
||||
var sdkHeaderNode = document.getElementById("hyper-checkout-sdk-header");
|
||||
if (sdkHeaderNode instanceof HTMLDivElement) {
|
||||
// sdkHeaderNode.append(sdkHeaderLogoNode);
|
||||
sdkHeaderNode.append(sdkHeaderItemNode);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,161 @@
|
||||
{{ 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;
|
||||
flex-flow: column;
|
||||
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;
|
||||
}
|
||||
|
||||
#hyper-checkout-status-redirect-message {
|
||||
margin-top: 20px;
|
||||
font-family: "Montserrat";
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Payment Status</title>
|
||||
<style>
|
||||
{{rendered_css}}
|
||||
</style>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800"
|
||||
/>
|
||||
<script>
|
||||
{{ rendered_js }}
|
||||
</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 id="hyper-checkout-status-redirect-message"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,379 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* UTIL FUNCTIONS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ref - https://github.com/onury/invert-color/blob/master/lib/cjs/invert.js
|
||||
*/
|
||||
function padz(str, len) {
|
||||
if (len === void 0) {
|
||||
len = 2;
|
||||
}
|
||||
return (new Array(len).join("0") + str).slice(-len);
|
||||
}
|
||||
function hexToRgbArray(hex) {
|
||||
if (hex.slice(0, 1) === "#") hex = hex.slice(1);
|
||||
var RE_HEX = /^(?:[0-9a-f]{3}){1,2}$/i;
|
||||
if (!RE_HEX.test(hex)) throw new Error('Invalid HEX color: "' + hex + '"');
|
||||
if (hex.length === 3) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
||||
}
|
||||
return [
|
||||
parseInt(hex.slice(0, 2), 16),
|
||||
parseInt(hex.slice(2, 4), 16),
|
||||
parseInt(hex.slice(4, 6), 16),
|
||||
];
|
||||
}
|
||||
function toRgbArray(c) {
|
||||
if (!c) throw new Error("Invalid color value");
|
||||
if (Array.isArray(c)) return c;
|
||||
return typeof c === "string" ? hexToRgbArray(c) : [c.r, c.g, c.b];
|
||||
}
|
||||
function getLuminance(c) {
|
||||
var i, x;
|
||||
var a = [];
|
||||
for (i = 0; i < c.length; i++) {
|
||||
x = c[i] / 255;
|
||||
a[i] = x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
|
||||
}
|
||||
function invertToBW(color, bw, asArr) {
|
||||
var DEFAULT_BW = {
|
||||
black: "#090302",
|
||||
white: "#FFFFFC",
|
||||
threshold: Math.sqrt(1.05 * 0.05) - 0.05,
|
||||
};
|
||||
var options = bw === true ? DEFAULT_BW : Object.assign({}, DEFAULT_BW, bw);
|
||||
return getLuminance(color) > options.threshold
|
||||
? asArr
|
||||
? hexToRgbArray(options.black)
|
||||
: options.black
|
||||
: asArr
|
||||
? hexToRgbArray(options.white)
|
||||
: options.white;
|
||||
}
|
||||
function invert(color, bw) {
|
||||
if (bw === void 0) {
|
||||
bw = false;
|
||||
}
|
||||
color = toRgbArray(color);
|
||||
if (bw) return invertToBW(color, bw);
|
||||
return (
|
||||
"#" +
|
||||
color
|
||||
.map(function (c) {
|
||||
return padz((255 - c).toString(16));
|
||||
})
|
||||
.join("")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* UTIL FUNCTIONS END HERE
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
{{ payment_details_js_script }}
|
||||
|
||||
// @ts-ignore
|
||||
window.state = {
|
||||
prevHeight: window.innerHeight,
|
||||
prevWidth: window.innerWidth,
|
||||
isMobileView: window.innerWidth <= 1400,
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger - init function invoked once the script tag is loaded
|
||||
* Use
|
||||
* - Update document's title
|
||||
* - Update document's icon
|
||||
* - Render and populate document with payment details and cart
|
||||
* - Initialize event listeners for updating UI on screen size changes
|
||||
* - Initialize SDK
|
||||
**/
|
||||
function boot() {
|
||||
// @ts-ignore
|
||||
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);
|
||||
}
|
||||
|
||||
// Render status details
|
||||
renderStatusDetails(paymentDetails);
|
||||
|
||||
// Add event listeners
|
||||
initializeEventListeners(paymentDetails);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger - on boot
|
||||
* Uses
|
||||
* - Render status details
|
||||
* - Header - (amount, merchant name, merchant logo)
|
||||
* - Body - status with image
|
||||
* - Footer - payment details (id | error code and msg, if any)
|
||||
* @param {PaymentDetails} paymentDetails
|
||||
**/
|
||||
function renderStatusDetails(paymentDetails) {
|
||||
var status = paymentDetails.status;
|
||||
|
||||
var statusDetails = {
|
||||
imageSource: "",
|
||||
message: "",
|
||||
status: status,
|
||||
amountText: "",
|
||||
items: [],
|
||||
};
|
||||
|
||||
// Payment details
|
||||
var paymentId = createItem("Ref Id", paymentDetails.payment_id);
|
||||
// @ts-ignore
|
||||
statusDetails.items.push(paymentId);
|
||||
|
||||
// Status specific information
|
||||
switch (status) {
|
||||
case "expired":
|
||||
statusDetails.imageSource = "https://live.hyperswitch.io/payment-link-assets/failed.png";
|
||||
statusDetails.status = "Payment Link Expired";
|
||||
statusDetails.message =
|
||||
"Sorry, this payment link has expired. Please use below reference for further investigation.";
|
||||
break;
|
||||
|
||||
case "succeeded":
|
||||
statusDetails.imageSource = "https://live.hyperswitch.io/payment-link-assets/success.png";
|
||||
statusDetails.message = "We have successfully received your payment";
|
||||
statusDetails.status = "Paid successfully";
|
||||
statusDetails.amountText = new Date(
|
||||
paymentDetails.created
|
||||
).toTimeString();
|
||||
break;
|
||||
|
||||
case "processing":
|
||||
statusDetails.imageSource = "https://live.hyperswitch.io/payment-link-assets/pending.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://live.hyperswitch.io/payment-link-assets/failed.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://live.hyperswitch.io/payment-link-assets/failed.png";
|
||||
statusDetails.status = "Payment Cancelled";
|
||||
break;
|
||||
|
||||
case "requires_merchant_action":
|
||||
statusDetails.imageSource = "https://live.hyperswitch.io/payment-link-assets/pending.png";
|
||||
statusDetails.status = "Payment under review";
|
||||
break;
|
||||
|
||||
case "requires_capture":
|
||||
statusDetails.imageSource = "https://live.hyperswitch.io/payment-link-assets/success.png";
|
||||
statusDetails.message = "We have successfully received your payment";
|
||||
statusDetails.status = "Payment Success";
|
||||
break;
|
||||
|
||||
case "partially_captured":
|
||||
statusDetails.imageSource = "https://live.hyperswitch.io/payment-link-assets/success.png";
|
||||
statusDetails.message = "Partial payment was captured.";
|
||||
statusDetails.status = "Payment Success";
|
||||
break;
|
||||
|
||||
default:
|
||||
statusDetails.imageSource = "https://live.hyperswitch.io/payment-link-assets/failed.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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Form header items
|
||||
var amountNode = document.createElement("div");
|
||||
amountNode.className = "hyper-checkout-status-amount";
|
||||
amountNode.innerText = paymentDetails.currency + " " + paymentDetails.amount;
|
||||
var merchantLogoNode = document.createElement("img");
|
||||
merchantLogoNode.className = "hyper-checkout-status-merchant-logo";
|
||||
// @ts-ignore
|
||||
merchantLogoNode.src = window.__PAYMENT_DETAILS.merchant_logo;
|
||||
merchantLogoNode.alt = "";
|
||||
|
||||
// Form content items
|
||||
var statusImageNode = document.createElement("img");
|
||||
statusImageNode.className = "hyper-checkout-status-image";
|
||||
statusImageNode.src = statusDetails.imageSource;
|
||||
var statusTextNode = document.createElement("div");
|
||||
statusTextNode.className = "hyper-checkout-status-text";
|
||||
statusTextNode.innerText = statusDetails.status;
|
||||
var statusMessageNode = document.createElement("div");
|
||||
statusMessageNode.className = "hyper-checkout-status-message";
|
||||
statusMessageNode.innerText = statusDetails.message;
|
||||
var statusDetailsNode = document.createElement("div");
|
||||
statusDetailsNode.className = "hyper-checkout-status-details";
|
||||
|
||||
// Append items
|
||||
if (statusDetailsNode instanceof HTMLDivElement) {
|
||||
statusDetails.items.map(function (item) {
|
||||
statusDetailsNode.append(item);
|
||||
});
|
||||
}
|
||||
var statusHeaderNode = document.getElementById(
|
||||
"hyper-checkout-status-header"
|
||||
);
|
||||
if (statusHeaderNode instanceof HTMLDivElement) {
|
||||
statusHeaderNode.append(amountNode, merchantLogoNode);
|
||||
}
|
||||
var statusContentNode = document.getElementById(
|
||||
"hyper-checkout-status-content"
|
||||
);
|
||||
if (statusContentNode instanceof HTMLDivElement) {
|
||||
statusContentNode.append(statusImageNode, statusTextNode);
|
||||
if (statusMessageNode instanceof HTMLDivElement) {
|
||||
statusContentNode.append(statusMessageNode);
|
||||
}
|
||||
statusContentNode.append(statusDetailsNode);
|
||||
}
|
||||
|
||||
if (paymentDetails.redirect === true) {
|
||||
// Form redirect text
|
||||
var statusRedirectTextNode = document.getElementById(
|
||||
"hyper-checkout-status-redirect-message"
|
||||
);
|
||||
if (
|
||||
statusRedirectTextNode instanceof HTMLDivElement &&
|
||||
typeof paymentDetails.return_url === "string"
|
||||
) {
|
||||
var timeout = 5,
|
||||
j = 0;
|
||||
for (var i = 0; i <= timeout; i++) {
|
||||
setTimeout(function () {
|
||||
var secondsLeft = timeout - j++;
|
||||
var innerText =
|
||||
secondsLeft === 0
|
||||
? "Redirecting ..."
|
||||
: "Redirecting in " + secondsLeft + " seconds ...";
|
||||
// @ts-ignore
|
||||
statusRedirectTextNode.innerText = innerText;
|
||||
if (secondsLeft === 0) {
|
||||
// Form query params
|
||||
var queryParams = {
|
||||
payment_id: paymentDetails.payment_id,
|
||||
status: paymentDetails.status,
|
||||
};
|
||||
var url = new URL(paymentDetails.return_url);
|
||||
var params = new URLSearchParams(url.search);
|
||||
// Attach query params to return_url
|
||||
for (var key in queryParams) {
|
||||
if (queryParams.hasOwnProperty(key)) {
|
||||
params.set(key, queryParams[key]);
|
||||
}
|
||||
}
|
||||
url.search = params.toString();
|
||||
setTimeout(function () {
|
||||
// Finally redirect
|
||||
window.location.href = url.toString();
|
||||
}, 1000);
|
||||
}
|
||||
}, i * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use - create an item which is a key-value pair of some information related to a payment
|
||||
* @param {String} heading
|
||||
* @param {String} value
|
||||
**/
|
||||
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.className = "hyper-checkout-item-value";
|
||||
valueNode.innerText = value;
|
||||
itemNode.append(headerNode);
|
||||
itemNode.append(valueNode);
|
||||
return itemNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use - add event listeners for changing UI on screen resize
|
||||
* @param {PaymentDetails} paymentDetails
|
||||
*/
|
||||
function initializeEventListeners(paymentDetails) {
|
||||
var primaryColor = paymentDetails.theme;
|
||||
var contrastBWColor = invert(primaryColor, true);
|
||||
var statusRedirectTextNode = document.getElementById(
|
||||
"hyper-checkout-status-redirect-message"
|
||||
);
|
||||
|
||||
if (window.innerWidth <= 1400) {
|
||||
if (statusRedirectTextNode instanceof HTMLDivElement) {
|
||||
statusRedirectTextNode.style.color = "#333333";
|
||||
}
|
||||
} else if (window.innerWidth > 1400) {
|
||||
if (statusRedirectTextNode instanceof HTMLDivElement) {
|
||||
statusRedirectTextNode.style.color = contrastBWColor;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("resize", function (event) {
|
||||
var currentHeight = window.innerHeight;
|
||||
var currentWidth = window.innerWidth;
|
||||
// @ts-ignore
|
||||
if (currentWidth <= 1400 && window.state.prevWidth > 1400) {
|
||||
try {
|
||||
if (statusRedirectTextNode instanceof HTMLDivElement) {
|
||||
statusRedirectTextNode.style.color = "#333333";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch primary-color, using default", error);
|
||||
}
|
||||
// @ts-ignore
|
||||
} else if (currentWidth > 1400 && window.state.prevWidth <= 1400) {
|
||||
try {
|
||||
if (statusRedirectTextNode instanceof HTMLDivElement) {
|
||||
statusRedirectTextNode.style.color = contrastBWColor;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to revert back to default colors", error);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
window.state.prevHeight = currentHeight;
|
||||
// @ts-ignore
|
||||
window.state.prevWidth = currentWidth;
|
||||
// @ts-ignore
|
||||
window.state.isMobileView = currentWidth <= 1400;
|
||||
});
|
||||
}
|
||||
@ -1,355 +0,0 @@
|
||||
<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>
|
||||
@ -892,6 +892,10 @@ impl PaymentLink {
|
||||
web::resource("{merchant_id}/{payment_id}")
|
||||
.route(web::get().to(initiate_payment_link)),
|
||||
)
|
||||
.service(
|
||||
web::resource("status/{merchant_id}/{payment_id}")
|
||||
.route(web::get().to(payment_link_status)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -147,9 +147,10 @@ impl From<Flow> for ApiIdentifier {
|
||||
| Flow::BusinessProfileDelete
|
||||
| Flow::BusinessProfileList => Self::Business,
|
||||
|
||||
Flow::PaymentLinkRetrieve | Flow::PaymentLinkInitiate | Flow::PaymentLinkList => {
|
||||
Self::PaymentLink
|
||||
}
|
||||
Flow::PaymentLinkRetrieve
|
||||
| Flow::PaymentLinkInitiate
|
||||
| Flow::PaymentLinkList
|
||||
| Flow::PaymentLinkStatus => Self::PaymentLink,
|
||||
|
||||
Flow::Verification => Self::Verification,
|
||||
|
||||
|
||||
@ -123,3 +123,33 @@ pub async fn payments_link_list(
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn payment_link_status(
|
||||
state: web::Data<AppState>,
|
||||
req: actix_web::HttpRequest,
|
||||
path: web::Path<(String, String)>,
|
||||
) -> impl Responder {
|
||||
let flow = Flow::PaymentLinkStatus;
|
||||
let (merchant_id, payment_id) = path.into_inner();
|
||||
let payload = api_models::payments::PaymentLinkInitiateRequest {
|
||||
payment_id,
|
||||
merchant_id: merchant_id.clone(),
|
||||
};
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
payload.clone(),
|
||||
|state, auth, _| {
|
||||
get_payment_link_status(
|
||||
state,
|
||||
auth.merchant_account,
|
||||
payload.merchant_id.clone(),
|
||||
payload.payment_id.clone(),
|
||||
)
|
||||
},
|
||||
&crate::services::authentication::MerchantIdAuth(merchant_id),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
@ -1749,19 +1749,50 @@ pub fn build_redirection_form(
|
||||
pub fn build_payment_link_html(
|
||||
payment_link_data: PaymentLinkFormData,
|
||||
) -> CustomResult<String, errors::ApiErrorResponse> {
|
||||
let html_template = include_str!("../core/payment_link/payment_link.html").to_string();
|
||||
|
||||
let mut tera = Tera::default();
|
||||
|
||||
// Add modification to css template with dynamic data
|
||||
let css_template =
|
||||
include_str!("../core/payment_link/payment_link_initiate/payment_link.css").to_string();
|
||||
let _ = tera.add_raw_template("payment_link_css", &css_template);
|
||||
let mut context = Context::new();
|
||||
context.insert("css_color_scheme", &payment_link_data.css_script);
|
||||
|
||||
let rendered_css = match tera.render("payment_link_css", &context) {
|
||||
Ok(rendered_css) => rendered_css,
|
||||
Err(tera_error) => {
|
||||
crate::logger::warn!("{tera_error}");
|
||||
Err(errors::ApiErrorResponse::InternalServerError)?
|
||||
}
|
||||
};
|
||||
|
||||
// Add modification to js template with dynamic data
|
||||
let js_template =
|
||||
include_str!("../core/payment_link/payment_link_initiate/payment_link.js").to_string();
|
||||
let _ = tera.add_raw_template("payment_link_js", &js_template);
|
||||
|
||||
context.insert("payment_details_js_script", &payment_link_data.js_script);
|
||||
|
||||
let rendered_js = match tera.render("payment_link_js", &context) {
|
||||
Ok(rendered_js) => rendered_js,
|
||||
Err(tera_error) => {
|
||||
crate::logger::warn!("{tera_error}");
|
||||
Err(errors::ApiErrorResponse::InternalServerError)?
|
||||
}
|
||||
};
|
||||
|
||||
// Modify Html template with rendered js and rendered css files
|
||||
let html_template =
|
||||
include_str!("../core/payment_link/payment_link_initiate/payment_link.html").to_string();
|
||||
|
||||
let _ = tera.add_raw_template("payment_link", &html_template);
|
||||
|
||||
let mut context = Context::new();
|
||||
context.insert(
|
||||
"hyperloader_sdk_link",
|
||||
&get_hyper_loader_sdk(&payment_link_data.sdk_url),
|
||||
);
|
||||
context.insert("css_color_scheme", &payment_link_data.css_script);
|
||||
context.insert("payment_details_js_script", &payment_link_data.js_script);
|
||||
context.insert("rendered_css", &rendered_css);
|
||||
context.insert("rendered_js", &rendered_js);
|
||||
|
||||
match tera.render("payment_link", &context) {
|
||||
Ok(rendered_html) => Ok(rendered_html),
|
||||
@ -1779,14 +1810,46 @@ fn get_hyper_loader_sdk(sdk_url: &str) -> String {
|
||||
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);
|
||||
|
||||
// Add modification to css template with dynamic data
|
||||
let css_template =
|
||||
include_str!("../core/payment_link/payment_link_status/status.css").to_string();
|
||||
let _ = tera.add_raw_template("payment_link_css", &css_template);
|
||||
let mut context = Context::new();
|
||||
context.insert("css_color_scheme", &payment_link_data.css_script);
|
||||
|
||||
let rendered_css = match tera.render("payment_link_css", &context) {
|
||||
Ok(rendered_css) => rendered_css,
|
||||
Err(tera_error) => {
|
||||
crate::logger::warn!("{tera_error}");
|
||||
Err(errors::ApiErrorResponse::InternalServerError)?
|
||||
}
|
||||
};
|
||||
|
||||
// Add modification to js template with dynamic data
|
||||
let js_template =
|
||||
include_str!("../core/payment_link/payment_link_status/status.js").to_string();
|
||||
let _ = tera.add_raw_template("payment_link_js", &js_template);
|
||||
context.insert("payment_details_js_script", &payment_link_data.js_script);
|
||||
|
||||
let rendered_js = match tera.render("payment_link_js", &context) {
|
||||
Ok(rendered_js) => rendered_js,
|
||||
Err(tera_error) => {
|
||||
crate::logger::warn!("{tera_error}");
|
||||
Err(errors::ApiErrorResponse::InternalServerError)?
|
||||
}
|
||||
};
|
||||
|
||||
// Modify Html template with rendered js and rendered css files
|
||||
let html_template =
|
||||
include_str!("../core/payment_link/payment_link_status/status.html").to_string();
|
||||
let _ = tera.add_raw_template("payment_link_status", &html_template);
|
||||
|
||||
context.insert("rendered_css", &rendered_css);
|
||||
|
||||
context.insert("rendered_js", &rendered_js);
|
||||
|
||||
match tera.render("payment_link_status", &context) {
|
||||
Ok(rendered_html) => Ok(rendered_html),
|
||||
Err(tera_error) => {
|
||||
|
||||
Reference in New Issue
Block a user