refactor(payment_links): Update API contract for dynamic transaction details and upgrade UI (#5849)

This commit is contained in:
Sakil Mostak
2024-09-12 18:58:21 +05:30
committed by GitHub
parent 1929f56e2a
commit a96e9f3e22
10 changed files with 210 additions and 42 deletions

View File

@ -10805,7 +10805,10 @@
"nullable": true "nullable": true
}, },
"transaction_details": { "transaction_details": {
"type": "string", "type": "array",
"items": {
"$ref": "#/components/schemas/PaymentLinkTransactionDetails"
},
"description": "Dynamic details related to merchant to be rendered in payment link", "description": "Dynamic details related to merchant to be rendered in payment link",
"nullable": true "nullable": true
} }
@ -10857,7 +10860,10 @@
"nullable": true "nullable": true
}, },
"transaction_details": { "transaction_details": {
"type": "object", "type": "array",
"items": {
"$ref": "#/components/schemas/PaymentLinkTransactionDetails"
},
"description": "Dynamic details related to merchant to be rendered in payment link", "description": "Dynamic details related to merchant to be rendered in payment link",
"nullable": true "nullable": true
} }
@ -10908,6 +10914,35 @@
"expired" "expired"
] ]
}, },
"PaymentLinkTransactionDetails": {
"type": "object",
"required": [
"key",
"value"
],
"properties": {
"key": {
"type": "string",
"description": "Key for the transaction details",
"example": "Policy-Number",
"maxLength": 255
},
"value": {
"type": "string",
"description": "Value for the transaction details",
"example": "297472368473924",
"maxLength": 255
},
"ui_configuration": {
"allOf": [
{
"$ref": "#/components/schemas/TransactionDetailsUiConfiguration"
}
],
"nullable": true
}
}
},
"PaymentListConstraints": { "PaymentListConstraints": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -18094,6 +18129,32 @@
"TouchNGoRedirection": { "TouchNGoRedirection": {
"type": "object" "type": "object"
}, },
"TransactionDetailsUiConfiguration": {
"type": "object",
"properties": {
"position": {
"type": "integer",
"format": "int32",
"description": "Position of the key-value pair in the UI",
"example": 5,
"nullable": true
},
"is_key_bold": {
"type": "boolean",
"description": "Whether the key should be bold",
"default": false,
"example": true,
"nullable": true
},
"is_value_bold": {
"type": "boolean",
"description": "Whether the value should be bold",
"default": false,
"example": true,
"nullable": true
}
}
},
"TransactionStatus": { "TransactionStatus": {
"type": "string", "type": "string",
"description": "Indicates the transaction status", "description": "Indicates the transaction status",

View File

@ -14803,7 +14803,10 @@
"nullable": true "nullable": true
}, },
"transaction_details": { "transaction_details": {
"type": "string", "type": "array",
"items": {
"$ref": "#/components/schemas/PaymentLinkTransactionDetails"
},
"description": "Dynamic details related to merchant to be rendered in payment link", "description": "Dynamic details related to merchant to be rendered in payment link",
"nullable": true "nullable": true
} }
@ -14855,7 +14858,10 @@
"nullable": true "nullable": true
}, },
"transaction_details": { "transaction_details": {
"type": "object", "type": "array",
"items": {
"$ref": "#/components/schemas/PaymentLinkTransactionDetails"
},
"description": "Dynamic details related to merchant to be rendered in payment link", "description": "Dynamic details related to merchant to be rendered in payment link",
"nullable": true "nullable": true
} }
@ -14906,6 +14912,35 @@
"expired" "expired"
] ]
}, },
"PaymentLinkTransactionDetails": {
"type": "object",
"required": [
"key",
"value"
],
"properties": {
"key": {
"type": "string",
"description": "Key for the transaction details",
"example": "Policy-Number",
"maxLength": 255
},
"value": {
"type": "string",
"description": "Value for the transaction details",
"example": "297472368473924",
"maxLength": 255
},
"ui_configuration": {
"allOf": [
{
"$ref": "#/components/schemas/TransactionDetailsUiConfiguration"
}
],
"nullable": true
}
}
},
"PaymentListConstraints": { "PaymentListConstraints": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -22543,6 +22578,32 @@
"TouchNGoRedirection": { "TouchNGoRedirection": {
"type": "object" "type": "object"
}, },
"TransactionDetailsUiConfiguration": {
"type": "object",
"properties": {
"position": {
"type": "integer",
"format": "int32",
"description": "Position of the key-value pair in the UI",
"example": 5,
"nullable": true
},
"is_key_bold": {
"type": "boolean",
"description": "Whether the key should be bold",
"default": false,
"example": true,
"nullable": true
},
"is_value_bold": {
"type": "boolean",
"description": "Whether the value should be bold",
"default": false,
"example": true,
"nullable": true
}
}
},
"TransactionStatus": { "TransactionStatus": {
"type": "string", "type": "string",
"description": "Indicates the transaction status", "description": "Indicates the transaction status",

View File

@ -9,7 +9,6 @@ use common_utils::{
}; };
#[cfg(feature = "v1")] #[cfg(feature = "v1")]
use common_utils::{crypto::OptionalEncryptableName, ext_traits::ValueExt}; use common_utils::{crypto::OptionalEncryptableName, ext_traits::ValueExt};
use indexmap::IndexMap;
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
use masking::ExposeInterface; use masking::ExposeInterface;
use masking::Secret; use masking::Secret;
@ -2584,8 +2583,32 @@ pub struct PaymentLinkConfigRequest {
#[schema(default = false, example = true)] #[schema(default = false, example = true)]
pub enabled_saved_payment_method: Option<bool>, pub enabled_saved_payment_method: Option<bool>,
/// Dynamic details related to merchant to be rendered in payment link /// 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<Vec<PaymentLinkTransactionDetails>>,
pub transaction_details: Option<IndexMap<String, String>>, }
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
pub struct PaymentLinkTransactionDetails {
/// Key for the transaction details
#[schema(value_type = String, max_length = 255, example = "Policy-Number")]
pub key: String,
/// Value for the transaction details
#[schema(value_type = String, max_length = 255, example = "297472368473924")]
pub value: String,
/// UI configuration for the transaction details
pub ui_configuration: Option<TransactionDetailsUiConfiguration>,
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
pub struct TransactionDetailsUiConfiguration {
/// Position of the key-value pair in the UI
#[schema(value_type = Option<i8>, example = 5)]
pub position: Option<i8>,
/// Whether the key should be bold
#[schema(default = false, example = true)]
pub is_key_bold: Option<bool>,
/// Whether the value should be bold
#[schema(default = false, example = true)]
pub is_value_bold: Option<bool>,
} }
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, ToSchema)] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, ToSchema)]
@ -2605,7 +2628,7 @@ pub struct PaymentLinkConfig {
/// A list of allowed domains (glob patterns) where this link can be embedded / opened from /// A list of allowed domains (glob patterns) where this link can be embedded / opened from
pub allowed_domains: Option<HashSet<String>>, pub allowed_domains: Option<HashSet<String>>,
/// Dynamic details related to merchant to be rendered in payment link /// Dynamic details related to merchant to be rendered in payment link
pub transaction_details: Option<String>, pub transaction_details: Option<Vec<PaymentLinkTransactionDetails>>,
} }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]

View File

@ -5616,7 +5616,7 @@ pub struct PaymentLinkDetails {
pub sdk_layout: String, pub sdk_layout: String,
pub display_sdk_only: bool, pub display_sdk_only: bool,
pub locale: Option<String>, pub locale: Option<String>,
pub transaction_details: Option<String>, pub transaction_details: Option<Vec<admin::PaymentLinkTransactionDetails>>,
} }
#[derive(Debug, serde::Serialize, Clone)] #[derive(Debug, serde::Serialize, Clone)]
@ -5642,7 +5642,7 @@ pub struct PaymentLinkStatusDetails {
pub theme: String, pub theme: String,
pub return_url: String, pub return_url: String,
pub locale: Option<String>, pub locale: Option<String>,
pub transaction_details: Option<String>, pub transaction_details: Option<Vec<admin::PaymentLinkTransactionDetails>>,
pub unified_code: Option<String>, pub unified_code: Option<String>,
pub unified_message: Option<String>, pub unified_message: Option<String>,
} }

View File

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

View File

@ -285,6 +285,8 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::admin::BusinessPaymentLinkConfig, api_models::admin::BusinessPaymentLinkConfig,
api_models::admin::PaymentLinkConfigRequest, api_models::admin::PaymentLinkConfigRequest,
api_models::admin::PaymentLinkConfig, api_models::admin::PaymentLinkConfig,
api_models::admin::PaymentLinkTransactionDetails,
api_models::admin::TransactionDetailsUiConfiguration,
api_models::disputes::DisputeResponse, api_models::disputes::DisputeResponse,
api_models::disputes::DisputeResponsePaymentsRetrieve, api_models::disputes::DisputeResponsePaymentsRetrieve,
api_models::gsm::GsmCreateRequest, api_models::gsm::GsmCreateRequest,

View File

@ -203,6 +203,8 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::admin::BusinessPaymentLinkConfig, api_models::admin::BusinessPaymentLinkConfig,
api_models::admin::PaymentLinkConfigRequest, api_models::admin::PaymentLinkConfigRequest,
api_models::admin::PaymentLinkConfig, api_models::admin::PaymentLinkConfig,
api_models::admin::PaymentLinkTransactionDetails,
api_models::admin::TransactionDetailsUiConfiguration,
api_models::disputes::DisputeResponse, api_models::disputes::DisputeResponse,
api_models::disputes::DisputeResponsePaymentsRetrieve, api_models::disputes::DisputeResponsePaymentsRetrieve,
api_models::gsm::GsmCreateRequest, api_models::gsm::GsmCreateRequest,

View File

@ -9,7 +9,6 @@ use common_utils::{
DEFAULT_ALLOWED_DOMAINS, DEFAULT_BACKGROUND_COLOR, DEFAULT_DISPLAY_SDK_ONLY, DEFAULT_ALLOWED_DOMAINS, DEFAULT_BACKGROUND_COLOR, DEFAULT_DISPLAY_SDK_ONLY,
DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, DEFAULT_LOCALE, DEFAULT_MERCHANT_LOGO, DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, DEFAULT_LOCALE, DEFAULT_MERCHANT_LOGO,
DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, DEFAULT_SESSION_EXPIRY, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, DEFAULT_SESSION_EXPIRY,
DEFAULT_TRANSACTION_DETAILS,
}, },
ext_traits::{AsyncExt, OptionExt, ValueExt}, ext_traits::{AsyncExt, OptionExt, ValueExt},
types::{AmountConvertor, MinorUnit, StringMajorUnitForCore}, types::{AmountConvertor, MinorUnit, StringMajorUnitForCore},
@ -114,7 +113,7 @@ pub async fn form_payment_link_data(
display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY, display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY,
enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD,
allowed_domains: DEFAULT_ALLOWED_DOMAINS, allowed_domains: DEFAULT_ALLOWED_DOMAINS,
transaction_details: DEFAULT_TRANSACTION_DETAILS, transaction_details: None,
} }
}; };
@ -616,24 +615,8 @@ pub fn get_payment_link_config_based_on_priority(
display_sdk_only, display_sdk_only,
enabled_saved_payment_method, enabled_saved_payment_method,
allowed_domains, allowed_domains,
transaction_details: payment_create_link_config.and_then(|payment_link_config| { transaction_details: payment_create_link_config
payment_link_config .and_then(|payment_link_config| payment_link_config.theme_config.transaction_details),
.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)) Ok((payment_link_config, domain_name))
@ -721,7 +704,7 @@ pub async fn get_payment_link_status(
display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY, display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY,
enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD,
allowed_domains: DEFAULT_ALLOWED_DOMAINS, allowed_domains: DEFAULT_ALLOWED_DOMAINS,
transaction_details: DEFAULT_TRANSACTION_DETAILS, transaction_details: None,
} }
}; };

View File

@ -96,6 +96,10 @@ body {
#hyper-checkout-payment-merchant-dynamic-details { #hyper-checkout-payment-merchant-dynamic-details {
margin: 20px 20px 10px 20px; margin: 20px 20px 10px 20px;
overflow-y: scroll;
max-width: 35vw;
max-height: 10vh;
min-height: 80px;
} }
.hyper-checkout-payment-horizontal-line { .hyper-checkout-payment-horizontal-line {
@ -714,6 +718,10 @@ body {
flex-flow: column; flex-flow: column;
flex-direction: column-reverse; flex-direction: column-reverse;
margin: 0; margin: 0;
max-width: 100%;
overflow-y: scroll;
max-height: 10vh;
min-height: 80px;
} }
.hyper-checkout-payment-horizontal-line { .hyper-checkout-payment-horizontal-line {

View File

@ -617,26 +617,57 @@ function renderDynamicMerchantDetails(paymentDetails) {
} }
function appendMerchantDetails(paymentDetails, merchantDynamicDetails) { function appendMerchantDetails(paymentDetails, merchantDynamicDetails) {
if (!(typeof paymentDetails.transaction_details === "string" && paymentDetails.transaction_details.length > 0)) { if (
!(
typeof paymentDetails.transaction_details === "object" &&
Object.keys(paymentDetails.transaction_details).length > 0
)
) {
return; return;
} }
try { try {
let merchantDetailsObject = JSON.parse(paymentDetails.transaction_details); let merchantDetailsObject = paymentDetails.transaction_details;
// sort the merchant details based on the position
// if position is null, then it will be shown at the end
merchantDetailsObject.sort((a, b) => {
if (a.ui_configuration === null || a.ui_configuration.position === null)
return 1;
if (b.ui_configuration === null || b.ui_configuration.position === null)
return -1;
if (Object.keys(merchantDetailsObject).length > 0) { if (typeof a.ui_configuration.position === "number" && typeof b.ui_configuration.position === "number") {
return a.ui_configuration.position - b.ui_configuration.position;
}
else return 0;
});
if (merchantDetailsObject.length > 0) {
// render a horizontal line above dynamic merchant details // render a horizontal line above dynamic merchant details
var horizontalLineContainer = document.getElementById("hyper-checkout-payment-horizontal-line-container"); var horizontalLineContainer = document.getElementById(
"hyper-checkout-payment-horizontal-line-container",
);
var horizontalLine = document.createElement("hr"); var horizontalLine = document.createElement("hr");
horizontalLine.className = "hyper-checkout-payment-horizontal-line"; horizontalLine.className = "hyper-checkout-payment-horizontal-line";
horizontalLineContainer.append(horizontalLine); horizontalLineContainer.append(horizontalLine);
// max number of items to show in the merchant details // max number of items to show in the merchant details
let maxItemsInDetails = 5; let maxItemsInDetails = 50;
for (var key in merchantDetailsObject) { for (var item of merchantDetailsObject) {
var merchantData = document.createElement("div"); var merchantData = document.createElement("div");
merchantData.className = "hyper-checkout-payment-merchant-dynamic-data"; merchantData.className = "hyper-checkout-payment-merchant-dynamic-data";
merchantData.innerHTML = key+": "+merchantDetailsObject[key].bold(); // make the key and value bold if specified in the ui_configuration
var key = item.ui_configuration
? item.ui_configuration.is_key_bold
? item.key.bold()
: item.key
: item.key;
var value = item.ui_configuration
? item.ui_configuration.is_value_bold
? item.value.bold()
: item.value
: item.value;
merchantData.innerHTML = key + " : " + value;
merchantDynamicDetails.append(merchantData); merchantDynamicDetails.append(merchantData);
if (--maxItemsInDetails === 0) { if (--maxItemsInDetails === 0) {
@ -667,7 +698,7 @@ function renderCart(paymentDetails) {
var MAX_ITEMS_VISIBLE_AFTER_COLLAPSE = var MAX_ITEMS_VISIBLE_AFTER_COLLAPSE =
paymentDetails.max_items_visible_after_collapse; paymentDetails.max_items_visible_after_collapse;
var yourCartText = document.createElement("span"); var yourCartText = document.createElement("span");
var yourCartText = document.getElementById('your-cart-text'); var yourCartText = document.getElementById("your-cart-text");
if (yourCartText) { if (yourCartText) {
yourCartText.textContent = translations.yourCart; yourCartText.textContent = translations.yourCart;
} }