feat(core): [Payment Link] add dynamic merchant fields (#5512)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Sakil Mostak
2024-08-08 19:50:07 +05:30
committed by GitHub
parent 0cbbc92a43
commit 03f0ea1582
11 changed files with 160 additions and 41 deletions

View File

@ -30,6 +30,7 @@ routing_v2 = []
[dependencies]
actix-web = { version = "4.5.1", optional = true }
error-stack = "0.4.1"
indexmap = "2.3.0"
mime = "0.3.17"
reqwest = { version = "0.11.27", optional = true }
serde = { version = "1.0.197", features = ["derive"] }

View File

@ -12,6 +12,7 @@ use common_utils::{
not(feature = "merchant_account_v2")
))]
use common_utils::{crypto::OptionalEncryptableName, ext_traits::ValueExt};
use indexmap::IndexMap;
#[cfg(all(feature = "v2", feature = "merchant_account_v2"))]
use masking::ExposeInterface;
use masking::Secret;
@ -2235,6 +2236,9 @@ pub struct PaymentLinkConfigRequest {
/// Enable saved payment method option for payment link
#[schema(default = false, example = true)]
pub enabled_saved_payment_method: Option<bool>,
/// Dynamic details related to merchant to be rendered in payment link
#[schema(value_type = Option<Object>, example = r#"{ "value1": "some-value", "value2": "some-value" }"#)]
pub transaction_details: Option<IndexMap<String, String>>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, ToSchema)]
@ -2253,6 +2257,8 @@ pub struct PaymentLinkConfig {
pub enabled_saved_payment_method: bool,
/// A list of allowed domains (glob patterns) where this link can be embedded / opened from
pub allowed_domains: Option<HashSet<String>>,
/// Dynamic details related to merchant to be rendered in payment link
pub transaction_details: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]

View File

@ -5385,8 +5385,8 @@ pub struct PaymentLinkInitiateRequest {
#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum PaymentLinkData {
PaymentLinkDetails(PaymentLinkDetails),
PaymentLinkStatusDetails(PaymentLinkStatusDetails),
PaymentLinkDetails(Box<PaymentLinkDetails>),
PaymentLinkStatusDetails(Box<PaymentLinkStatusDetails>),
}
#[derive(Debug, serde::Serialize, Clone)]
@ -5408,6 +5408,7 @@ pub struct PaymentLinkDetails {
pub sdk_layout: String,
pub display_sdk_only: bool,
pub locale: Option<String>,
pub transaction_details: Option<String>,
}
#[derive(Debug, serde::Serialize, Clone)]
@ -5433,6 +5434,7 @@ pub struct PaymentLinkStatusDetails {
pub theme: String,
pub return_url: String,
pub locale: Option<String>,
pub transaction_details: Option<String>,
}
#[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]

View File

@ -86,6 +86,9 @@ pub const DEFAULT_ENABLE_SAVED_PAYMENT_METHOD: bool = false;
/// Default allowed domains for payment links
pub const DEFAULT_ALLOWED_DOMAINS: Option<HashSet<String>> = None;
/// Default merchant details for payment links
pub const DEFAULT_TRANSACTION_DETAILS: Option<String> = None;
/// Default ttl for Extended card info in redis (in seconds)
pub const DEFAULT_TTL_FOR_EXTENDED_CARD_INFO: u16 = 15 * 60;

View File

@ -8,7 +8,7 @@ use common_utils::{
consts::{
DEFAULT_ALLOWED_DOMAINS, DEFAULT_BACKGROUND_COLOR, DEFAULT_DISPLAY_SDK_ONLY,
DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG,
DEFAULT_SDK_LAYOUT, DEFAULT_SESSION_EXPIRY,
DEFAULT_SDK_LAYOUT, DEFAULT_SESSION_EXPIRY, DEFAULT_TRANSACTION_DETAILS,
},
ext_traits::{OptionExt, ValueExt},
types::{AmountConvertor, MinorUnit, StringMajorUnitForCore},
@ -109,6 +109,7 @@ pub async fn form_payment_link_data(
display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY,
enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD,
allowed_domains: DEFAULT_ALLOWED_DOMAINS,
transaction_details: DEFAULT_TRANSACTION_DETAILS,
}
};
@ -219,36 +220,41 @@ pub async fn form_payment_link_data(
theme: payment_link_config.theme.clone(),
return_url: return_url.clone(),
locale: locale.clone(),
transaction_details: payment_link_config.transaction_details.clone(),
};
return Ok((
payment_link,
PaymentLinkData::PaymentLinkStatusDetails(payment_details),
PaymentLinkData::PaymentLinkStatusDetails(Box::new(payment_details)),
payment_link_config,
));
};
let payment_link_details =
PaymentLinkData::PaymentLinkDetails(api_models::payments::PaymentLinkDetails {
amount,
currency,
payment_id: payment_intent.payment_id,
merchant_name,
order_details,
return_url,
session_expiry,
pub_key: merchant_account.publishable_key,
client_secret,
merchant_logo: payment_link_config.logo.clone(),
max_items_visible_after_collapse: 3,
theme: payment_link_config.theme.clone(),
merchant_description: payment_intent.description,
sdk_layout: payment_link_config.sdk_layout.clone(),
display_sdk_only: payment_link_config.display_sdk_only,
locale,
});
let payment_link_details = api_models::payments::PaymentLinkDetails {
amount,
currency,
payment_id: payment_intent.payment_id,
merchant_name,
order_details,
return_url,
session_expiry,
pub_key: merchant_account.publishable_key,
client_secret,
merchant_logo: payment_link_config.logo.clone(),
max_items_visible_after_collapse: 3,
theme: payment_link_config.theme.clone(),
merchant_description: payment_intent.description,
sdk_layout: payment_link_config.sdk_layout.clone(),
display_sdk_only: payment_link_config.display_sdk_only,
locale,
transaction_details: payment_link_config.transaction_details.clone(),
};
Ok((payment_link, payment_link_details, payment_link_config))
Ok((
payment_link,
PaymentLinkData::PaymentLinkDetails(Box::new(payment_link_details)),
payment_link_config,
))
}
pub async fn initiate_secure_payment_link_flow(
@ -297,7 +303,7 @@ pub async fn initiate_secure_payment_link_flow(
PaymentLinkData::PaymentLinkDetails(link_details) => {
let secure_payment_link_details = api_models::payments::SecurePaymentLinkDetails {
enabled_saved_payment_method: payment_link_config.enabled_saved_payment_method,
payment_link_details: link_details.to_owned(),
payment_link_details: *link_details.to_owned(),
};
let js_script = format!(
"window.__PAYMENT_DETAILS = {}",
@ -602,6 +608,24 @@ pub fn get_payment_link_config_based_on_priority(
display_sdk_only,
enabled_saved_payment_method,
allowed_domains,
transaction_details: payment_create_link_config.and_then(|payment_link_config| {
payment_link_config
.theme_config
.transaction_details
.and_then(|transaction_details| {
match serde_json::to_string(&transaction_details).change_context(
errors::ApiErrorResponse::InvalidDataValue {
field_name: "transaction_details",
},
) {
Ok(details) => Some(details),
Err(err) => {
logger::error!("Failed to serialize transaction details: {:?}", err);
None
}
}
})
}),
};
Ok((payment_link_config, domain_name))
@ -689,6 +713,7 @@ pub async fn get_payment_link_status(
display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY,
enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD,
allowed_domains: DEFAULT_ALLOWED_DOMAINS,
transaction_details: DEFAULT_TRANSACTION_DETAILS,
}
};
@ -748,8 +773,11 @@ pub async fn get_payment_link_status(
theme: payment_link_config.theme.clone(),
return_url,
locale,
transaction_details: payment_link_config.transaction_details,
};
let js_script = get_js_script(&PaymentLinkData::PaymentLinkStatusDetails(payment_details))?;
let js_script = get_js_script(&PaymentLinkData::PaymentLinkStatusDetails(Box::new(
payment_details,
)))?;
let payment_link_status_data = services::PaymentLinkStatusData {
js_script,
css_script,

View File

@ -90,10 +90,21 @@ body {
.content-details-wrap {
display: flex;
flex-flow: row;
margin: 20px 20px 30px 20px;
margin: 20px 20px 10px 20px;
justify-content: space-between;
}
#hyper-checkout-payment-merchant-dynamic-details {
margin: 20px 20px 10px 20px;
}
.hyper-checkout-payment-horizontal-line {
margin: 0px 20px;
height: 2px;
background-color: #e5e5e5;
border: none;
}
.hyper-checkout-payment-price {
font-weight: 700;
font-size: 40px;
@ -111,6 +122,11 @@ body {
font-size: 19px;
}
.hyper-checkout-payment-merchant-dynamic-data {
font-size: 12px;
margin-top: 5px;
}
.hyper-checkout-payment-ref {
font-size: 12px;
margin-top: 5px;
@ -694,6 +710,18 @@ body {
margin: 0;
}
#hyper-checkout-payment-merchant-dynamic-details {
flex-flow: column;
flex-direction: column-reverse;
margin: 0;
}
.hyper-checkout-payment-horizontal-line {
margin: 10px 0px;
height: 2px;
border: none;
}
#hyper-checkout-merchant-image {
background-color: white;
}

View File

@ -225,6 +225,8 @@
</div>
</div>
</div>
<div id="hyper-checkout-payment-horizontal-line-container"></div>
<div id="hyper-checkout-payment-merchant-dynamic-details"></div>
<div id="hyper-checkout-payment-footer"></div>
</div>
</div>

View File

@ -238,6 +238,7 @@ function boot() {
}
else{
renderPaymentDetails(paymentDetails);
renderDynamicMerchantDetails(paymentDetails);
renderCart(paymentDetails);
renderSDKHeader(paymentDetails);
}
@ -605,6 +606,42 @@ function renderPaymentDetails(paymentDetails) {
}
}
function renderDynamicMerchantDetails(paymentDetails) {
var merchantDynamicDetails = document.getElementById(
"hyper-checkout-payment-merchant-dynamic-details"
);
if (merchantDynamicDetails instanceof HTMLDivElement) {
// add dynamic merchant details in the payment details section if present
appendMerchantDetails(paymentDetails, merchantDynamicDetails);
}
}
function appendMerchantDetails(paymentDetails, merchantDynamicDetails) {
if (Object.keys(paymentDetails.transaction_details).length === 0) {
return;
}
// render a horizontal line above dynamic merchant details
let horizontalLineContainer = document.getElementById("hyper-checkout-payment-horizontal-line-container");
let horizontalLine = document.createElement("hr");
horizontalLine.className = "hyper-checkout-payment-horizontal-line";
horizontalLineContainer.append(horizontalLine);
// max number of items to show in the merchant details
let maxItemsInDetails = 5;
let merchantDetailsObject = JSON.parse(paymentDetails.transaction_details);
for(const key in merchantDetailsObject) {
var merchantData = document.createElement("div");
merchantData.className = "hyper-checkout-payment-merchant-dynamic-data";
merchantData.innerHTML = key+": "+merchantDetailsObject[key].bold();
merchantDynamicDetails.append(merchantData);
if(--maxItemsInDetails === 0) {
break;
}
}
}
/**
* Trigger - on boot
* Uses

View File

@ -1740,6 +1740,7 @@ impl ForeignFrom<diesel_models::business_profile::PaymentLinkConfigRequest>
sdk_layout: item.sdk_layout,
display_sdk_only: item.display_sdk_only,
enabled_saved_payment_method: item.enabled_saved_payment_method,
transaction_details: None,
}
}
}