mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 09:38:33 +08:00
feat(router): Better UI payment link and order details product image and merchant config support (#2583)
Co-authored-by: Sahkal Poddar <sahkal.poddar@juspay.in> Co-authored-by: Kashif <46213975+kashif-m@users.noreply.github.com> Co-authored-by: Kashif <mohammed.kashif@juspay.in> Co-authored-by: Bernard Eugine <114725419+bernard-eugine@users.noreply.github.com> Co-authored-by: Kashif <kashif@protonmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -95,6 +95,8 @@ pub struct MerchantAccountCreate {
|
||||
|
||||
/// The id of the organization to which the merchant belongs to
|
||||
pub organization_id: Option<String>,
|
||||
|
||||
pub payment_link_config: Option<PaymentLinkConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)]
|
||||
@ -184,6 +186,8 @@ pub struct MerchantAccountUpdate {
|
||||
/// To unset this field, pass an empty string
|
||||
#[schema(max_length = 64)]
|
||||
pub default_profile: Option<String>,
|
||||
|
||||
pub payment_link_config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, ToSchema, Serialize)]
|
||||
@ -277,6 +281,8 @@ pub struct MerchantAccountResponse {
|
||||
/// A enum value to indicate the status of recon service. By default it is not_requested.
|
||||
#[schema(value_type = ReconStatus, example = "not_requested")]
|
||||
pub recon_status: enums::ReconStatus,
|
||||
|
||||
pub payment_link_config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, ToSchema, Serialize)]
|
||||
@ -497,6 +503,22 @@ pub struct PrimaryBusinessDetails {
|
||||
pub business: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, ToSchema, Serialize, PartialEq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PaymentLinkConfig {
|
||||
pub merchant_logo: Option<String>,
|
||||
pub color_scheme: Option<PaymentLinkColorSchema>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, ToSchema, Serialize, PartialEq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
|
||||
pub struct PaymentLinkColorSchema {
|
||||
pub primary_color: Option<String>,
|
||||
pub primary_accent_color: Option<String>,
|
||||
pub secondary_color: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, ToSchema, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WebhookDetails {
|
||||
|
||||
@ -226,6 +226,7 @@ pub struct PaymentsRequest {
|
||||
"product_name": "gillete creme",
|
||||
"quantity": 15,
|
||||
"amount" : 900
|
||||
"product_img_link" : "https://dummy-img-link.com"
|
||||
}]"#)]
|
||||
pub order_details: Option<Vec<OrderDetailsWithAmount>>,
|
||||
|
||||
@ -2418,6 +2419,8 @@ pub struct OrderDetailsWithAmount {
|
||||
pub quantity: u16,
|
||||
/// the amount per quantity of product
|
||||
pub amount: i64,
|
||||
/// The image URL of the product
|
||||
pub product_img_link: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
|
||||
@ -2428,6 +2431,8 @@ pub struct OrderDetails {
|
||||
/// The quantity of the product to be purchased
|
||||
#[schema(example = 1)]
|
||||
pub quantity: u16,
|
||||
/// The image URL of the product
|
||||
pub product_img_link: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
|
||||
@ -3120,3 +3125,19 @@ pub struct PaymentLinkInitiateRequest {
|
||||
pub merchant_id: String,
|
||||
pub payment_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct PaymentLinkDetails {
|
||||
pub amount: i64,
|
||||
pub currency: api_enums::Currency,
|
||||
pub pub_key: String,
|
||||
pub client_secret: String,
|
||||
pub payment_id: String,
|
||||
#[serde(with = "common_utils::custom_serde::iso8601")]
|
||||
pub expiry: PrimitiveDateTime,
|
||||
pub merchant_logo: String,
|
||||
pub return_url: String,
|
||||
pub merchant_name: crypto::OptionalEncryptableName,
|
||||
pub order_details: Vec<pii::SecretSerdeValue>,
|
||||
pub max_items_visible_after_collapse: i8,
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ pub struct MerchantAccount {
|
||||
pub is_recon_enabled: bool,
|
||||
pub default_profile: Option<String>,
|
||||
pub recon_status: storage_enums::ReconStatus,
|
||||
pub payment_link_config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
|
||||
@ -69,6 +70,7 @@ pub struct MerchantAccountNew {
|
||||
pub is_recon_enabled: bool,
|
||||
pub default_profile: Option<String>,
|
||||
pub recon_status: storage_enums::ReconStatus,
|
||||
pub payment_link_config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
|
||||
@ -97,4 +99,5 @@ pub struct MerchantAccountUpdateInternal {
|
||||
pub is_recon_enabled: bool,
|
||||
pub default_profile: Option<Option<String>>,
|
||||
pub recon_status: storage_enums::ReconStatus,
|
||||
pub payment_link_config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
@ -442,6 +442,7 @@ diesel::table! {
|
||||
#[max_length = 64]
|
||||
default_profile -> Nullable<Varchar>,
|
||||
recon_status -> ReconStatus,
|
||||
payment_link_config -> Nullable<Jsonb>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -136,6 +136,17 @@ pub async fn create_merchant_account(
|
||||
.transpose()?
|
||||
.map(Secret::new);
|
||||
|
||||
let payment_link_config = req
|
||||
.payment_link_config
|
||||
.as_ref()
|
||||
.map(|pl_metadata| {
|
||||
utils::Encode::<admin_types::PaymentLinkConfig>::encode_to_value(pl_metadata)
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "payment_link_config",
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let mut merchant_account = async {
|
||||
Ok(domain::MerchantAccount {
|
||||
merchant_id: req.merchant_id,
|
||||
@ -171,6 +182,7 @@ pub async fn create_merchant_account(
|
||||
is_recon_enabled: false,
|
||||
default_profile: None,
|
||||
recon_status: diesel_models::enums::ReconStatus::NotRequested,
|
||||
payment_link_config,
|
||||
})
|
||||
}
|
||||
.await
|
||||
@ -458,6 +470,7 @@ pub async fn merchant_account_update(
|
||||
intent_fulfillment_time: req.intent_fulfillment_time.map(i64::from),
|
||||
payout_routing_algorithm: req.payout_routing_algorithm,
|
||||
default_profile: business_profile_id_update,
|
||||
payment_link_config: req.payment_link_config,
|
||||
};
|
||||
|
||||
let response = db
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
use api_models::admin as admin_types;
|
||||
use common_utils::ext_traits::AsyncExt;
|
||||
use error_stack::ResultExt;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
|
||||
use super::errors::{self, StorageErrorExt};
|
||||
use super::errors::{self, RouterResult, StorageErrorExt};
|
||||
use crate::{
|
||||
core::payments::helpers,
|
||||
errors::RouterResponse,
|
||||
@ -41,6 +42,19 @@ pub async fn intiate_payment_link_flow(
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
|
||||
|
||||
helpers::validate_payment_status_against_not_allowed_statuses(
|
||||
&payment_intent.status,
|
||||
&[
|
||||
storage_enums::IntentStatus::Cancelled,
|
||||
storage_enums::IntentStatus::Succeeded,
|
||||
storage_enums::IntentStatus::Processing,
|
||||
storage_enums::IntentStatus::RequiresCapture,
|
||||
storage_enums::IntentStatus::RequiresMerchantAction,
|
||||
],
|
||||
"create payment link",
|
||||
)?;
|
||||
|
||||
let fulfillment_time = payment_intent
|
||||
.payment_link_id
|
||||
.as_ref()
|
||||
@ -56,32 +70,63 @@ pub async fn intiate_payment_link_flow(
|
||||
.get_required_value("fulfillment_time")
|
||||
.change_context(errors::ApiErrorResponse::PaymentNotFound)?;
|
||||
|
||||
helpers::validate_payment_status_against_not_allowed_statuses(
|
||||
&payment_intent.status,
|
||||
&[
|
||||
storage_enums::IntentStatus::Cancelled,
|
||||
storage_enums::IntentStatus::Succeeded,
|
||||
storage_enums::IntentStatus::Processing,
|
||||
storage_enums::IntentStatus::RequiresCapture,
|
||||
storage_enums::IntentStatus::RequiresMerchantAction,
|
||||
],
|
||||
"create payment link",
|
||||
let payment_link_config = merchant_account
|
||||
.payment_link_config
|
||||
.map(|pl_config| {
|
||||
serde_json::from_value::<admin_types::PaymentLinkConfig>(pl_config)
|
||||
.into_report()
|
||||
.change_context(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "payment_link_config",
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let order_details = payment_intent
|
||||
.order_details
|
||||
.get_required_value("order_details")
|
||||
.change_context(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "order_details",
|
||||
})?;
|
||||
|
||||
let return_url = if let Some(payment_create_return_url) = payment_intent.return_url {
|
||||
payment_create_return_url
|
||||
} else {
|
||||
merchant_account
|
||||
.return_url
|
||||
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "return_url",
|
||||
})?
|
||||
};
|
||||
|
||||
let (pub_key, currency, client_secret) = validate_sdk_requirements(
|
||||
merchant_account.publishable_key,
|
||||
payment_intent.currency,
|
||||
payment_intent.client_secret,
|
||||
)?;
|
||||
|
||||
let expiry = fulfillment_time.assume_utc().unix_timestamp();
|
||||
|
||||
let js_script = get_js_script(
|
||||
payment_intent.amount.to_string(),
|
||||
payment_intent.currency.unwrap_or_default().to_string(),
|
||||
merchant_account.publishable_key.unwrap_or_default(),
|
||||
payment_intent.client_secret.unwrap_or_default(),
|
||||
payment_intent.payment_id,
|
||||
expiry,
|
||||
);
|
||||
let payment_details = api_models::payments::PaymentLinkDetails {
|
||||
amount: payment_intent.amount,
|
||||
currency,
|
||||
payment_id: payment_intent.payment_id,
|
||||
merchant_name: merchant_account.merchant_name,
|
||||
order_details,
|
||||
return_url,
|
||||
expiry: fulfillment_time,
|
||||
pub_key,
|
||||
client_secret,
|
||||
merchant_logo: payment_link_config
|
||||
.clone()
|
||||
.map(|pl_metadata| pl_metadata.merchant_logo.unwrap_or_default())
|
||||
.unwrap_or_default(),
|
||||
max_items_visible_after_collapse: 3,
|
||||
};
|
||||
|
||||
let js_script = get_js_script(payment_details)?;
|
||||
let css_script = get_color_scheme_css(payment_link_config.clone());
|
||||
let payment_link_data = services::PaymentLinkFormData {
|
||||
js_script,
|
||||
sdk_url: state.conf.payment_link.sdk_url.clone(),
|
||||
css_script,
|
||||
};
|
||||
Ok(services::ApplicationResponse::PaymenkLinkForm(Box::new(
|
||||
payment_link_data,
|
||||
@ -93,80 +138,68 @@ The get_js_script function is used to inject dynamic value to payment_link sdk,
|
||||
*/
|
||||
|
||||
fn get_js_script(
|
||||
amount: String,
|
||||
currency: String,
|
||||
pub_key: String,
|
||||
secret: String,
|
||||
payment_id: String,
|
||||
expiry: i64,
|
||||
) -> String {
|
||||
format!(
|
||||
"window.__PAYMENT_DETAILS_STR = JSON.stringify({{
|
||||
client_secret: '{secret}',
|
||||
amount: '{amount}',
|
||||
currency: '{currency}',
|
||||
payment_id: '{payment_id}',
|
||||
expiry: {expiry},
|
||||
// TODO: Remove hardcoded values
|
||||
merchant_logo: 'https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg',
|
||||
return_url: 'http://localhost:5500/public/index.html',
|
||||
currency_symbol: '$',
|
||||
merchant: 'Steam',
|
||||
max_items_visible_after_collapse: 3,
|
||||
order_details: [
|
||||
{{
|
||||
product_name:
|
||||
'dskjghbdsiuh sagfvbsajd ugbfiusedg fiudshgiu sdhgvishd givuhdsifu gnb gidsug biuesbdg iubsedg bsduxbg jhdxbgv jdskfbgi sdfgibuh ew87t54378 ghdfjbv jfdhgvb dufhvbfidu hg5784ghdfbjnk f (taxes incl.)',
|
||||
quantity: 2,
|
||||
amount: 100,
|
||||
product_image:
|
||||
'https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg',
|
||||
}},
|
||||
{{
|
||||
product_name: \"F1 '23\",
|
||||
quantity: 4,
|
||||
amount: 500,
|
||||
product_image:
|
||||
'https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg',
|
||||
}},
|
||||
{{
|
||||
product_name: \"Motosport '24\",
|
||||
quantity: 4,
|
||||
amount: 500,
|
||||
product_image:
|
||||
'https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg',
|
||||
}},
|
||||
{{
|
||||
product_name: 'Trackmania',
|
||||
quantity: 4,
|
||||
amount: 500,
|
||||
product_image:
|
||||
'https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg',
|
||||
}},
|
||||
{{
|
||||
product_name: 'Ghost Recon',
|
||||
quantity: 4,
|
||||
amount: 500,
|
||||
product_image:
|
||||
'https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg',
|
||||
}},
|
||||
{{
|
||||
product_name: 'Cup of Tea',
|
||||
quantity: 4,
|
||||
amount: 500,
|
||||
product_image:
|
||||
'https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg',
|
||||
}},
|
||||
{{
|
||||
product_name: 'Tea cups',
|
||||
quantity: 4,
|
||||
amount: 500,
|
||||
product_image:
|
||||
'https://upload.wikimedia.org/wikipedia/commons/8/83/Steam_icon_logo.svg',
|
||||
}},
|
||||
]
|
||||
}});
|
||||
payment_details: api_models::payments::PaymentLinkDetails,
|
||||
) -> RouterResult<String> {
|
||||
let payment_details_str = serde_json::to_string(&payment_details)
|
||||
.into_report()
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to serialize PaymentLinkDetails")?;
|
||||
Ok(format!("window.__PAYMENT_DETAILS = {payment_details_str};"))
|
||||
}
|
||||
|
||||
const hyper = Hyper(\"{pub_key}\");"
|
||||
fn get_color_scheme_css(
|
||||
payment_link_config: Option<api_models::admin::PaymentLinkConfig>,
|
||||
) -> String {
|
||||
let (default_primary_color, default_accent_color, default_secondary_color) = (
|
||||
"#C6C7C8".to_string(),
|
||||
"#6A8EF5".to_string(),
|
||||
"#0C48F6".to_string(),
|
||||
);
|
||||
|
||||
let (primary_color, primary_accent_color, secondary_color) = payment_link_config
|
||||
.and_then(|pl_config| {
|
||||
pl_config.color_scheme.map(|color| {
|
||||
(
|
||||
color.primary_color.unwrap_or(default_primary_color.clone()),
|
||||
color
|
||||
.primary_accent_color
|
||||
.unwrap_or(default_accent_color.clone()),
|
||||
color
|
||||
.secondary_color
|
||||
.unwrap_or(default_secondary_color.clone()),
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap_or((
|
||||
default_primary_color,
|
||||
default_accent_color,
|
||||
default_secondary_color,
|
||||
));
|
||||
|
||||
format!(
|
||||
":root {{
|
||||
--primary-color: {primary_color};
|
||||
--primary-accent-color: {primary_accent_color};
|
||||
--secondary-color: {secondary_color};
|
||||
}}"
|
||||
)
|
||||
}
|
||||
|
||||
fn validate_sdk_requirements(
|
||||
pub_key: Option<String>,
|
||||
currency: Option<api_models::enums::Currency>,
|
||||
client_secret: Option<String>,
|
||||
) -> Result<(String, api_models::enums::Currency, String), errors::ApiErrorResponse> {
|
||||
let pub_key = pub_key.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "pub_key",
|
||||
})?;
|
||||
|
||||
let currency = currency.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "currency",
|
||||
})?;
|
||||
|
||||
let client_secret = client_secret.ok_or(errors::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "client_secret",
|
||||
})?;
|
||||
Ok((pub_key, currency, client_secret))
|
||||
}
|
||||
|
||||
@ -3,15 +3,12 @@
|
||||
<head>
|
||||
{{ hyperloader_sdk_link }}
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #26c5a0;
|
||||
--primary-accent-color: #f8e5a0;
|
||||
--secondary-color: #1c7ed9;
|
||||
}
|
||||
{{ css_color_scheme }}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -24,6 +21,19 @@
|
||||
color: #292929;
|
||||
}
|
||||
|
||||
/* 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 */
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
@ -125,17 +135,18 @@
|
||||
#hyper-checkout-cart {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
min-width: 582px;
|
||||
width: min-content;
|
||||
min-width: 584px;
|
||||
width: 400px;
|
||||
margin-top: 30px;
|
||||
max-height: 600px;
|
||||
}
|
||||
|
||||
#hyper-checkout-cart-items {
|
||||
margin: 10px 0;
|
||||
max-height: 600px;
|
||||
margin: 10px 20px 10px 0;
|
||||
max-height: 354px;
|
||||
height: 354px;
|
||||
overflow: scroll;
|
||||
padding-right: 20px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-header {
|
||||
@ -152,18 +163,20 @@
|
||||
|
||||
.cart-close {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-item {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
margin: 20px 0;
|
||||
padding: 20px 10px;
|
||||
font-size: 15px;
|
||||
box-shadow: 0px 0px 10px #efefef;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-product-image {
|
||||
height: 88px;
|
||||
width: 88px;
|
||||
height: 72px;
|
||||
width: 72px;
|
||||
}
|
||||
|
||||
.hyper-checkout-card-item-name {
|
||||
@ -199,6 +212,7 @@
|
||||
font-size: 16px;
|
||||
padding-left: 30px;
|
||||
text-align: end;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.hyper-checkout-cart-item-divider {
|
||||
@ -373,6 +387,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-from-right {
|
||||
from {
|
||||
right: -582px;
|
||||
}
|
||||
to {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-to-right {
|
||||
from {
|
||||
right: 0;
|
||||
}
|
||||
to {
|
||||
right: -582px;
|
||||
}
|
||||
}
|
||||
|
||||
.spinner:before {
|
||||
width: 10.4px;
|
||||
height: 20.4px;
|
||||
@ -481,12 +513,14 @@
|
||||
z-index: 100;
|
||||
margin: 0;
|
||||
min-width: 300px;
|
||||
max-width: 600px;
|
||||
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 {
|
||||
@ -499,7 +533,7 @@
|
||||
}
|
||||
|
||||
#hyper-checkout-cart-items {
|
||||
margin: 20px;
|
||||
margin: 20px 20px 0 20px;
|
||||
padding: 0;
|
||||
max-height: max-content;
|
||||
}
|
||||
@ -594,7 +628,7 @@
|
||||
<path
|
||||
d="M671.504,577.829l110.485-432.609H902.86v-68H729.174L703.128,179.2L0,178.697l74.753,399.129h596.751V577.829z
|
||||
M685.766,247.188l-67.077,262.64H131.199L81.928,246.756L685.766,247.188z"
|
||||
/>
|
||||
></path>
|
||||
<path
|
||||
d="M578.418,825.641c59.961,0,108.743-48.783,108.743-108.744s-48.782-108.742-108.743-108.742H168.717
|
||||
c-59.961,0-108.744,48.781-108.744,108.742s48.782,108.744,108.744,108.744c59.962,0,108.743-48.783,108.743-108.744
|
||||
@ -603,7 +637,7 @@
|
||||
c-22.466,0-40.744-18.277-40.744-40.744c0-22.465,18.277-40.742,40.744-40.742C191.183,676.155,209.46,694.432,209.46,716.897z
|
||||
M619.162,716.897c0,22.467-18.277,40.744-40.743,40.744s-40.743-18.277-40.743-40.744c0-22.465,18.277-40.742,40.743-40.742
|
||||
S619.162,694.432,619.162,716.897z"
|
||||
/>
|
||||
></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
@ -612,7 +646,7 @@
|
||||
</div>
|
||||
<div id="hyper-checkout-payment-footer"></div>
|
||||
</div>
|
||||
<div id="hyper-checkout-cart">
|
||||
<div id="hyper-checkout-cart" class="hidden">
|
||||
<div class="hyper-checkout-cart-header">
|
||||
<svg
|
||||
width="16"
|
||||
@ -632,18 +666,18 @@
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<rect width="16" height="16" fill="#D9D9D9" />
|
||||
<rect width="16" height="16" fill="#D9D9D9"></rect>
|
||||
</mask>
|
||||
<g mask="url(#mask0_11421_6708)">
|
||||
<path
|
||||
d="M3.53716 14.3331C3.20469 14.3331 2.92071 14.2153 2.68525 13.9798C2.44977 13.7444 2.33203 13.4604 2.33203 13.1279V5.53823C2.33203 5.20575 2.44977 4.92178 2.68525 4.68631C2.92071 4.45083 3.20469 4.3331 3.53716 4.3331H4.9987C4.9987 3.50063 5.29058 2.79252 5.87433 2.20876C6.4581 1.62501 7.16621 1.33313 7.99868 1.33313C8.83115 1.33313 9.53927 1.62501 10.123 2.20876C10.7068 2.79252 10.9987 3.50063 10.9987 4.3331H12.4602C12.7927 4.3331 13.0766 4.45083 13.3121 4.68631C13.5476 4.92178 13.6653 5.20575 13.6653 5.53823V13.1279C13.6653 13.4604 13.5476 13.7444 13.3121 13.9798C13.0766 14.2153 12.7927 14.3331 12.4602 14.3331H3.53716ZM3.53716 13.3331H12.4602C12.5115 13.3331 12.5585 13.3117 12.6012 13.269C12.644 13.2262 12.6653 13.1792 12.6653 13.1279V5.53823C12.6653 5.48694 12.644 5.43992 12.6012 5.39718C12.5585 5.35445 12.5115 5.33308 12.4602 5.33308H3.53716C3.48588 5.33308 3.43886 5.35445 3.39611 5.39718C3.35338 5.43992 3.33201 5.48694 3.33201 5.53823V13.1279C3.33201 13.1792 3.35338 13.2262 3.39611 13.269C3.43886 13.3117 3.48588 13.3331 3.53716 13.3331ZM7.99868 8.99973C8.83115 8.99973 9.53927 8.70785 10.123 8.1241C10.7068 7.54033 10.9987 6.83221 10.9987 5.99975H9.99868C9.99868 6.5553 9.80424 7.02752 9.41535 7.41641C9.02646 7.8053 8.55424 7.99975 7.99868 7.99975C7.44313 7.99975 6.9709 7.8053 6.58202 7.41641C6.19313 7.02752 5.99868 6.5553 5.99868 5.99975H4.9987C4.9987 6.83221 5.29058 7.54033 5.87433 8.1241C6.4581 8.70785 7.16621 8.99973 7.99868 8.99973ZM5.99868 4.3331H9.99868C9.99868 3.77754 9.80424 3.30532 9.41535 2.91643C9.02646 2.52754 8.55424 2.3331 7.99868 2.3331C7.44313 2.3331 6.9709 2.52754 6.58202 2.91643C6.19313 3.30532 5.99868 3.77754 5.99868 4.3331Z"
|
||||
fill="#333333"
|
||||
/>
|
||||
></path>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_11421_6708">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
<rect width="16" height="16" fill="white"></rect>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
@ -658,10 +692,10 @@
|
||||
>
|
||||
<path
|
||||
d="M 9.15625 6.3125 L 6.3125 9.15625 L 22.15625 25 L 6.21875 40.96875 L 9.03125 43.78125 L 25 27.84375 L 40.9375 43.78125 L 43.78125 40.9375 L 27.84375 25 L 43.6875 9.15625 L 40.84375 6.3125 L 25 22.15625 Z"
|
||||
/>
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div id="hyper-checkout-cart-items"></div>
|
||||
<div id="hyper-checkout-cart-items" class="hide-scrollbar"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hyper-checkout-sdk" id="hyper-checkout-sdk">
|
||||
@ -762,16 +796,11 @@
|
||||
prevHeight: window.innerHeight,
|
||||
prevWidth: window.innerWidth,
|
||||
isMobileView: window.innerWidth <= 1200,
|
||||
}
|
||||
};
|
||||
|
||||
var widgets = null;
|
||||
|
||||
window.__PAYMENT_DETAILS = {};
|
||||
try {
|
||||
window.__PAYMENT_DETAILS = JSON.parse(window.__PAYMENT_DETAILS_STR);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse payment details");
|
||||
}
|
||||
const pub_key = window.__PAYMENT_DETAILS.pub_key;
|
||||
const hyper = Hyper(pub_key);
|
||||
|
||||
async function initialize() {
|
||||
const paymentDetails = window.__PAYMENT_DETAILS;
|
||||
@ -1076,12 +1105,13 @@
|
||||
var priceNode = document.createElement("div");
|
||||
priceNode.className = "hyper-checkout-payment-price";
|
||||
priceNode.innerText =
|
||||
paymentDetails.currency_symbol + paymentDetails.amount;
|
||||
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;
|
||||
merchantNameNode.innerText =
|
||||
"Requested by " + paymentDetails.merchant_name;
|
||||
|
||||
// Create payment ID node
|
||||
var paymentIdNode = document.createElement("div");
|
||||
@ -1128,46 +1158,15 @@
|
||||
// Cart items
|
||||
if (Array.isArray(orderDetails)) {
|
||||
orderDetails.map((item, index) => {
|
||||
// Wrappers
|
||||
var itemWrapperNode = document.createElement("div");
|
||||
itemWrapperNode.className = `${
|
||||
index >= MAX_ITEMS_VISIBLE_AFTER_COLLAPSE ? "hidden " : ""
|
||||
}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_image;
|
||||
// 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_symbol + item.amount;
|
||||
// Divider node
|
||||
var dividerNode = document.createElement("div");
|
||||
dividerNode.className = `${
|
||||
index !== 0 && index < MAX_ITEMS_VISIBLE_AFTER_COLLAPSE
|
||||
? ""
|
||||
: "hidden "
|
||||
}hyper-checkout-cart-item-divider`;
|
||||
// Append items
|
||||
nameAndQuantityWrapperNode.append(productNameNode, quantityNode);
|
||||
itemWrapperNode.append(
|
||||
productImageNode,
|
||||
nameAndQuantityWrapperNode,
|
||||
priceNode
|
||||
if (index >= MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) {
|
||||
return;
|
||||
}
|
||||
renderCartItem(
|
||||
item,
|
||||
paymentDetails,
|
||||
index !== 0 && index < MAX_ITEMS_VISIBLE_AFTER_COLLAPSE,
|
||||
cartItemsNode
|
||||
);
|
||||
cartItemsNode.append(dividerNode, itemWrapperNode);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1188,6 +1187,49 @@
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function handleCartView() {
|
||||
const paymentDetails = window.__PAYMENT_DETAILS;
|
||||
const orderDetails = paymentDetails.order_details;
|
||||
@ -1201,34 +1243,70 @@
|
||||
);
|
||||
var cartItems = [].slice.call(itemsHTMLCollection);
|
||||
var dividerItems = [].slice.call(dividerHTMLCollection);
|
||||
var isHidden = false;
|
||||
cartItems.map((item) => {
|
||||
isHidden = item.classList.contains("hidden");
|
||||
});
|
||||
|
||||
cartItems.map((item, index) => {
|
||||
if (index >= MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) {
|
||||
item.className = `${
|
||||
isHidden ? "" : "hidden "
|
||||
}hyper-checkout-cart-item`;
|
||||
}
|
||||
});
|
||||
dividerItems.map((divider, index) => {
|
||||
if (index >= MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) {
|
||||
divider.className = `${
|
||||
isHidden ? "" : "hidden "
|
||||
}hyper-checkout-cart-item-divider`;
|
||||
}
|
||||
});
|
||||
|
||||
var isHidden = cartItems.length < orderDetails.length;
|
||||
var cartItemsNode = document.getElementById("hyper-checkout-cart-items");
|
||||
var cartButtonTextNode = document.getElementById(
|
||||
"hyper-checkout-cart-button-text"
|
||||
);
|
||||
if (isHidden) {
|
||||
if (Array.isArray(orderDetails)) {
|
||||
orderDetails.map((item, index) => {
|
||||
if (index < MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) {
|
||||
return;
|
||||
}
|
||||
renderCartItem(
|
||||
item,
|
||||
paymentDetails,
|
||||
index >= MAX_ITEMS_VISIBLE_AFTER_COLLAPSE,
|
||||
cartItemsNode
|
||||
);
|
||||
});
|
||||
}
|
||||
cartItemsNode.style.maxHeight = cartItemsNode.scrollHeight + "px";
|
||||
cartItemsNode.style.height = cartItemsNode.scrollHeight + "px";
|
||||
cartButtonTextNode.innerText = "Show Less";
|
||||
} else {
|
||||
cartItemsNode.style.maxHeight = "354px";
|
||||
cartItemsNode.style.height = "354px";
|
||||
cartItemsNode.scrollTo({ top: 0, behavior: "smooth" });
|
||||
setTimeout(() => {
|
||||
cartItems.map((item, index) => {
|
||||
if (index < MAX_ITEMS_VISIBLE_AFTER_COLLAPSE) {
|
||||
return;
|
||||
}
|
||||
cartItemsNode.removeChild(item);
|
||||
});
|
||||
dividerItems.map((item, index) => {
|
||||
if (index < MAX_ITEMS_VISIBLE_AFTER_COLLAPSE - 1) {
|
||||
return;
|
||||
}
|
||||
cartItemsNode.removeChild(item);
|
||||
});
|
||||
}, 300);
|
||||
setTimeout(() => {
|
||||
const hiddenItemsCount =
|
||||
orderDetails.length - MAX_ITEMS_VISIBLE_AFTER_COLLAPSE;
|
||||
cartButtonTextNode.innerText = isHidden
|
||||
? "Show Less"
|
||||
: `Show More (${hiddenItemsCount})`;
|
||||
cartButtonTextNode.innerText = `Show More (${hiddenItemsCount})`;
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
|
||||
function hideCartInMobileView() {
|
||||
window.history.back();
|
||||
const cartNode = document.getElementById("hyper-checkout-cart");
|
||||
cartNode.style.animation = "slide-to-right 0.3s linear";
|
||||
cartNode.style.right = "-582px";
|
||||
setTimeout(() => {
|
||||
hide("#hyper-checkout-cart");
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function viewCartInMobileView() {
|
||||
window.history.pushState("view-cart", "");
|
||||
const cartNode = document.getElementById("hyper-checkout-cart");
|
||||
cartNode.style.animation = "slide-from-right 0.3s linear";
|
||||
cartNode.style.right = "0px";
|
||||
show("#hyper-checkout-cart");
|
||||
}
|
||||
|
||||
function hideCartInMobileView() {
|
||||
@ -1250,7 +1328,7 @@
|
||||
var sdkHeaderMerchantNameNode = document.createElement("div");
|
||||
sdkHeaderMerchantNameNode.className =
|
||||
"hyper-checkout-sdk-header-brand-name";
|
||||
sdkHeaderMerchantNameNode.innerText = paymentDetails.merchant;
|
||||
sdkHeaderMerchantNameNode.innerText = paymentDetails.merchant_name;
|
||||
var sdkHeaderAmountNode = document.createElement("div");
|
||||
sdkHeaderAmountNode.className = "hyper-checkout-sdk-header-amount";
|
||||
sdkHeaderAmountNode.innerText =
|
||||
@ -1295,7 +1373,6 @@
|
||||
window.addEventListener("resize", (event) => {
|
||||
const currentHeight = window.innerHeight;
|
||||
const currentWidth = window.innerWidth;
|
||||
|
||||
if (currentWidth <= 1200 && window.state.prevWidth > 1200) {
|
||||
hide("#hyper-checkout-cart");
|
||||
} else if (currentWidth > 1200 && window.state.prevWidth <= 1200) {
|
||||
|
||||
@ -3410,6 +3410,7 @@ impl ApplePayData {
|
||||
pub fn validate_payment_link_request(
|
||||
payment_link_object: &api_models::payments::PaymentLinkObject,
|
||||
confirm: Option<bool>,
|
||||
order_details: Option<Vec<api_models::payments::OrderDetailsWithAmount>>,
|
||||
) -> Result<(), errors::ApiErrorResponse> {
|
||||
if let Some(cnf) = confirm {
|
||||
if !cnf {
|
||||
@ -3418,6 +3419,10 @@ pub fn validate_payment_link_request(
|
||||
return Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: "link_expiry time cannot be less than current time".to_string(),
|
||||
});
|
||||
} else if order_details.is_none() {
|
||||
return Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||
message: "cannot create payment link without order details".to_string(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Err(errors::ApiErrorResponse::InvalidRequestData {
|
||||
|
||||
@ -486,7 +486,11 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen
|
||||
helpers::validate_customer_details_in_request(request)?;
|
||||
|
||||
if let Some(payment_link_object) = &request.payment_link_object {
|
||||
helpers::validate_payment_link_request(payment_link_object, request.confirm)?;
|
||||
helpers::validate_payment_link_request(
|
||||
payment_link_object,
|
||||
request.confirm,
|
||||
request.order_details.clone(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let given_payment_id = match &request.payment_id {
|
||||
|
||||
@ -916,6 +916,7 @@ pub fn change_order_details_to_new_type(
|
||||
product_name: order_details.product_name,
|
||||
quantity: order_details.quantity,
|
||||
amount: order_amount,
|
||||
product_img_link: order_details.product_img_link,
|
||||
}])
|
||||
}
|
||||
|
||||
|
||||
@ -180,6 +180,8 @@ Never share your secret api keys. Keep them guarded and secure.
|
||||
api_models::admin::MerchantConnectorDetailsWrap,
|
||||
api_models::admin::MerchantConnectorDetails,
|
||||
api_models::admin::MerchantConnectorWebhookDetails,
|
||||
api_models::admin::PaymentLinkConfig,
|
||||
api_models::admin::PaymentLinkColorSchema,
|
||||
api_models::disputes::DisputeResponse,
|
||||
api_models::disputes::DisputeResponsePaymentsRetrieve,
|
||||
api_models::payments::AddressDetails,
|
||||
|
||||
@ -663,6 +663,7 @@ pub enum ApplicationResponse<R> {
|
||||
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PaymentLinkFormData {
|
||||
pub js_script: String,
|
||||
pub css_script: String,
|
||||
pub sdk_url: String,
|
||||
}
|
||||
|
||||
@ -1378,6 +1379,7 @@ pub fn build_payment_link_html(
|
||||
"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);
|
||||
|
||||
match tera.render("payment_link", &context) {
|
||||
|
||||
@ -46,6 +46,7 @@ impl TryFrom<domain::MerchantAccount> for MerchantAccountResponse {
|
||||
is_recon_enabled: item.is_recon_enabled,
|
||||
default_profile: item.default_profile,
|
||||
recon_status: item.recon_status,
|
||||
payment_link_config: item.payment_link_config,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ pub struct MerchantAccount {
|
||||
pub is_recon_enabled: bool,
|
||||
pub default_profile: Option<String>,
|
||||
pub recon_status: diesel_models::enums::ReconStatus,
|
||||
pub payment_link_config: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
@ -68,6 +69,7 @@ pub enum MerchantAccountUpdate {
|
||||
frm_routing_algorithm: Option<serde_json::Value>,
|
||||
payout_routing_algorithm: Option<serde_json::Value>,
|
||||
default_profile: Option<Option<String>>,
|
||||
payment_link_config: Option<serde_json::Value>,
|
||||
},
|
||||
StorageSchemeUpdate {
|
||||
storage_scheme: MerchantStorageScheme,
|
||||
@ -100,6 +102,7 @@ impl From<MerchantAccountUpdate> for MerchantAccountUpdateInternal {
|
||||
frm_routing_algorithm,
|
||||
payout_routing_algorithm,
|
||||
default_profile,
|
||||
payment_link_config,
|
||||
} => Self {
|
||||
merchant_name: merchant_name.map(Encryption::from),
|
||||
merchant_details: merchant_details.map(Encryption::from),
|
||||
@ -120,6 +123,7 @@ impl From<MerchantAccountUpdate> for MerchantAccountUpdateInternal {
|
||||
intent_fulfillment_time,
|
||||
payout_routing_algorithm,
|
||||
default_profile,
|
||||
payment_link_config,
|
||||
..Default::default()
|
||||
},
|
||||
MerchantAccountUpdate::StorageSchemeUpdate { storage_scheme } => Self {
|
||||
@ -173,6 +177,7 @@ impl super::behaviour::Conversion for MerchantAccount {
|
||||
is_recon_enabled: self.is_recon_enabled,
|
||||
default_profile: self.default_profile,
|
||||
recon_status: self.recon_status,
|
||||
payment_link_config: self.payment_link_config,
|
||||
})
|
||||
}
|
||||
|
||||
@ -217,6 +222,7 @@ impl super::behaviour::Conversion for MerchantAccount {
|
||||
is_recon_enabled: item.is_recon_enabled,
|
||||
default_profile: item.default_profile,
|
||||
recon_status: item.recon_status,
|
||||
payment_link_config: item.payment_link_config,
|
||||
})
|
||||
}
|
||||
.await
|
||||
@ -252,6 +258,7 @@ impl super::behaviour::Conversion for MerchantAccount {
|
||||
is_recon_enabled: self.is_recon_enabled,
|
||||
default_profile: self.default_profile,
|
||||
recon_status: self.recon_status,
|
||||
payment_link_config: self.payment_link_config,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,6 +76,7 @@ fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
|
||||
product_name: "iphone 13".to_string(),
|
||||
quantity: 1,
|
||||
amount: 1000,
|
||||
product_img_link: None,
|
||||
}]),
|
||||
router_return_url: Some("https://hyperswitch.io".to_string()),
|
||||
webhook_url: Some("https://hyperswitch.io".to_string()),
|
||||
@ -370,6 +371,7 @@ async fn should_fail_payment_for_incorrect_cvc() {
|
||||
product_name: "iphone 13".to_string(),
|
||||
quantity: 1,
|
||||
amount: 100,
|
||||
product_img_link: None,
|
||||
}]),
|
||||
router_return_url: Some("https://hyperswitch.io".to_string()),
|
||||
webhook_url: Some("https://hyperswitch.io".to_string()),
|
||||
@ -402,6 +404,7 @@ async fn should_fail_payment_for_invalid_exp_month() {
|
||||
product_name: "iphone 13".to_string(),
|
||||
quantity: 1,
|
||||
amount: 100,
|
||||
product_img_link: None,
|
||||
}]),
|
||||
router_return_url: Some("https://hyperswitch.io".to_string()),
|
||||
webhook_url: Some("https://hyperswitch.io".to_string()),
|
||||
@ -434,6 +437,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() {
|
||||
product_name: "iphone 13".to_string(),
|
||||
quantity: 1,
|
||||
amount: 100,
|
||||
product_img_link: None,
|
||||
}]),
|
||||
router_return_url: Some("https://hyperswitch.io".to_string()),
|
||||
webhook_url: Some("https://hyperswitch.io".to_string()),
|
||||
|
||||
@ -313,6 +313,7 @@ async fn should_fail_payment_for_incorrect_card_number() {
|
||||
product_name: "test".to_string(),
|
||||
quantity: 1,
|
||||
amount: 1000,
|
||||
product_img_link: None,
|
||||
}]),
|
||||
email: Some(Email::from_str("test@gmail.com").unwrap()),
|
||||
webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()),
|
||||
@ -348,6 +349,7 @@ async fn should_fail_payment_for_incorrect_cvc() {
|
||||
product_name: "test".to_string(),
|
||||
quantity: 1,
|
||||
amount: 1000,
|
||||
product_img_link: None,
|
||||
}]),
|
||||
email: Some(Email::from_str("test@gmail.com").unwrap()),
|
||||
webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()),
|
||||
@ -383,6 +385,7 @@ async fn should_fail_payment_for_invalid_exp_month() {
|
||||
product_name: "test".to_string(),
|
||||
quantity: 1,
|
||||
amount: 1000,
|
||||
product_img_link: None,
|
||||
}]),
|
||||
email: Some(Email::from_str("test@gmail.com").unwrap()),
|
||||
webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()),
|
||||
@ -418,6 +421,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() {
|
||||
product_name: "test".to_string(),
|
||||
quantity: 1,
|
||||
amount: 1000,
|
||||
product_img_link: None,
|
||||
}]),
|
||||
email: Some(Email::from_str("test@gmail.com").unwrap()),
|
||||
webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()),
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
ALTER TABLE merchant_account DROP COLUMN payment_link_config;
|
||||
@ -0,0 +1,4 @@
|
||||
-- Your SQL goes here
|
||||
ALTER TABLE merchant_account
|
||||
ADD COLUMN IF NOT EXISTS payment_link_config JSONB NULL;
|
||||
|
||||
@ -6172,6 +6172,14 @@
|
||||
"type": "string",
|
||||
"description": "The id of the organization to which the merchant belongs to",
|
||||
"nullable": true
|
||||
},
|
||||
"payment_link_config": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/PaymentLinkConfig"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6345,6 +6353,9 @@
|
||||
},
|
||||
"recon_status": {
|
||||
"$ref": "#/components/schemas/ReconStatus"
|
||||
},
|
||||
"payment_link_config": {
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6477,6 +6488,9 @@
|
||||
"description": "The default business profile that must be used for creating merchant accounts and payments\nTo unset this field, pass an empty string",
|
||||
"nullable": true,
|
||||
"maxLength": 64
|
||||
},
|
||||
"payment_link_config": {
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -7323,6 +7337,11 @@
|
||||
"description": "The quantity of the product to be purchased",
|
||||
"example": 1,
|
||||
"minimum": 0
|
||||
},
|
||||
"product_img_link": {
|
||||
"type": "string",
|
||||
"description": "The image URL of the product",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -7351,6 +7370,11 @@
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "the amount per quantity of product"
|
||||
},
|
||||
"product_img_link": {
|
||||
"type": "string",
|
||||
"description": "The image URL of the product",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -7790,6 +7814,40 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"PaymentLinkColorSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"primary_color": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"primary_accent_color": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"secondary_color": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"PaymentLinkConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"merchant_logo": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"color_scheme": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/PaymentLinkColorSchema"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"PaymentLinkInitiateRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -8778,7 +8836,7 @@
|
||||
"$ref": "#/components/schemas/OrderDetailsWithAmount"
|
||||
},
|
||||
"description": "Information about the product , quantity and amount for connectors. (e.g. Klarna)",
|
||||
"example": "[{\n \"product_name\": \"gillete creme\",\n \"quantity\": 15,\n \"amount\" : 900\n }]",
|
||||
"example": "[{\n \"product_name\": \"gillete creme\",\n \"quantity\": 15,\n \"amount\" : 900\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]",
|
||||
"nullable": true
|
||||
},
|
||||
"client_secret": {
|
||||
@ -9142,7 +9200,7 @@
|
||||
"$ref": "#/components/schemas/OrderDetailsWithAmount"
|
||||
},
|
||||
"description": "Information about the product , quantity and amount for connectors. (e.g. Klarna)",
|
||||
"example": "[{\n \"product_name\": \"gillete creme\",\n \"quantity\": 15,\n \"amount\" : 900\n }]",
|
||||
"example": "[{\n \"product_name\": \"gillete creme\",\n \"quantity\": 15,\n \"amount\" : 900\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]",
|
||||
"nullable": true
|
||||
},
|
||||
"client_secret": {
|
||||
|
||||
Reference in New Issue
Block a user