Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(payment_link): add multiple custom css support in business level #5137

Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions api-reference/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -6819,6 +6819,15 @@
"properties": {
"domain_name": {
"type": "string",
"description": "Custom domain name to be used for hosting the link in your own domain",
"nullable": true
},
"business_specific_configs": {
"type": "object",
"description": "list of configs for multi theme setup",
"additionalProperties": {
"$ref": "#/components/schemas/PaymentLinkConfigRequest"
},
"nullable": true
}
}
Expand Down Expand Up @@ -14986,6 +14995,11 @@
],
"nullable": true
},
"payment_link_config_id": {
"type": "string",
"description": "custom payment link config id set at business profile send only if business_specific_configs is configured",
"nullable": true
},
"payment_type": {
"allOf": [
{
Expand Down Expand Up @@ -15355,6 +15369,11 @@
],
"nullable": true
},
"payment_link_config_id": {
"type": "string",
"description": "custom payment link config id set at business profile send only if business_specific_configs is configured",
"nullable": true
},
"profile_id": {
"type": "string",
"description": "The business profile to use for this payment, if not passed the default business profile\nassociated with the merchant account will be used.",
Expand Down Expand Up @@ -15876,6 +15895,11 @@
],
"nullable": true
},
"payment_link_config_id": {
"type": "string",
"description": "custom payment link config id set at business profile send only if business_specific_configs is configured",
"nullable": true
},
"profile_id": {
"type": "string",
"description": "The business profile to use for this payment, if not passed the default business profile\nassociated with the merchant account will be used.",
Expand Down Expand Up @@ -16912,6 +16936,11 @@
],
"nullable": true
},
"payment_link_config_id": {
"type": "string",
"description": "custom payment link config id set at business profile send only if business_specific_configs is configured",
"nullable": true
},
"surcharge_details": {
"allOf": [
{
Expand Down
7 changes: 6 additions & 1 deletion crates/api_models/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1157,9 +1157,14 @@ pub struct BusinessGenericLinkConfig {

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
pub struct BusinessPaymentLinkConfig {
/// Custom domain name to be used for hosting the link in your own domain
pub domain_name: Option<String>,
/// Default payment link config for all future payment link
#[serde(flatten)]
pub config: PaymentLinkConfigRequest,
#[schema(value_type = PaymentLinkConfigRequest)]
pub default_config: Option<PaymentLinkConfigRequest>,
/// list of configs for multi theme setup
pub business_specific_configs: Option<HashMap<String, PaymentLinkConfigRequest>>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
Expand Down
12 changes: 8 additions & 4 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,9 @@ pub struct PaymentsRequest {
#[schema(value_type = Option<PaymentCreatePaymentLinkConfig>)]
pub payment_link_config: Option<PaymentCreatePaymentLinkConfig>,

/// custom payment link config id set at business profile send only if business_specific_configs is configured
pub payment_link_config_id: Option<String>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be sent in Confirm and update as well? If not can you hide it in the api ref for those two requests?


/// The business profile to use for this payment, if not passed the default business profile
/// associated with the merchant account will be used.
#[remove_in(PaymentsUpdateRequest, PaymentsConfirmRequest)]
Expand Down Expand Up @@ -5000,7 +5003,7 @@ pub enum PaymentLinkData<'a> {

#[derive(Debug, serde::Serialize, Clone)]
pub struct PaymentLinkDetails {
pub amount: String,
pub amount: StringMajorUnit,
pub currency: api_enums::Currency,
pub pub_key: String,
pub client_secret: String,
Expand All @@ -5021,7 +5024,7 @@ pub struct PaymentLinkDetails {

#[derive(Debug, serde::Serialize)]
pub struct PaymentLinkStatusDetails {
pub amount: String,
pub amount: StringMajorUnit,
pub currency: api_enums::Currency,
pub payment_id: String,
pub merchant_logo: String,
Expand Down Expand Up @@ -5094,7 +5097,8 @@ pub struct PaymentLinkListResponse {
pub struct PaymentCreatePaymentLinkConfig {
#[serde(flatten)]
#[schema(value_type = Option<PaymentLinkConfigRequest>)]
pub config: admin::PaymentLinkConfigRequest,
/// Theme config for the particular payment
pub theme_config: admin::PaymentLinkConfigRequest,
}

#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
Expand All @@ -5106,7 +5110,7 @@ pub struct OrderDetailsWithStringAmount {
#[schema(example = 1)]
pub quantity: u16,
/// the amount per quantity of product
pub amount: String,
pub amount: StringMajorUnit,
/// Product Image link
pub product_img_link: Option<String>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ pub enum ApiErrorResponse {
MissingTenantId,
#[error(error_type = ErrorType::ProcessingError, code = "HE_06", message = "Invalid tenant id: {tenant_id}")]
InvalidTenant { tenant_id: String },
#[error(error_type = ErrorType::ValidationError, code = "HE_01", message = "Failed to convert amount to {amount_type} type")]
AmountConversionFailed { amount_type: &'static str },
}

#[derive(Clone)]
Expand Down Expand Up @@ -613,6 +615,9 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon
Self::InvalidTenant { tenant_id } => {
AER::InternalServerError(ApiError::new("HE", 6, format!("Invalid Tenant {tenant_id}"), None))
}
Self::AmountConversionFailed { amount_type } => {
AER::InternalServerError(ApiError::new("HE", 6, format!("Failed to convert amount to {amount_type} type"), None))
}
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion crates/router/src/compatibility/stripe/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ pub enum StripeErrorCode {
ExtendedCardInfoNotFound,
#[error(error_type = StripeErrorType::InvalidRequestError, code = "IR_28", message = "Invalid tenant")]
InvalidTenant,
#[error(error_type = StripeErrorType::HyperswitchError, code = "HE_01", message = "Failed to convert amount to {amount_type} type")]
AmountConversionFailed { amount_type: &'static str },
// [#216]: https://github.com/juspay/hyperswitch/issues/216
// Implement the remaining stripe error codes

Expand Down Expand Up @@ -650,6 +652,9 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
errors::ApiErrorResponse::ExtendedCardInfoNotFound => Self::ExtendedCardInfoNotFound,
errors::ApiErrorResponse::InvalidTenant { tenant_id: _ }
| errors::ApiErrorResponse::MissingTenantId => Self::InvalidTenant,
errors::ApiErrorResponse::AmountConversionFailed { amount_type } => {
Self::AmountConversionFailed { amount_type }
}
}
}
}
Expand Down Expand Up @@ -730,7 +735,8 @@ impl actix_web::ResponseError for StripeErrorCode {
| Self::MandateActive
| Self::CustomerRedacted
| Self::WebhookProcessingError
| Self::InvalidTenant => StatusCode::INTERNAL_SERVER_ERROR,
| Self::InvalidTenant
| Self::AmountConversionFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR,
Self::ReturnUrlUnavailable => StatusCode::SERVICE_UNAVAILABLE,
Self::ExternalConnectorError { status_code, .. } => {
StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
Expand Down
121 changes: 48 additions & 73 deletions crates/router/src/core/payment_link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use common_utils::{
DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, DEFAULT_SESSION_EXPIRY,
},
ext_traits::{OptionExt, ValueExt},
types::{AmountConvertor, MinorUnit, StringMajorUnitForConnector},
};
use error_stack::ResultExt;
use futures::future;
Expand All @@ -14,6 +15,7 @@ use time::PrimitiveDateTime;
use super::errors::{self, RouterResult, StorageErrorExt};
use crate::{
errors::RouterResponse,
get_payment_link_config_value, get_payment_link_config_value_based_on_priority,
routes::SessionState,
services,
types::{
Expand Down Expand Up @@ -121,9 +123,15 @@ pub async fn initiate_payment_link_flow(
payment_intent.currency,
payment_intent.client_secret.clone(),
)?;
let amount = currency
.to_currency_base_unit(payment_intent.amount.get_amount_as_i64())
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?;

let required_conversion_type = StringMajorUnitForConnector;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a different type for this like StringMajorUnit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i cant use pub struct StringMajorUnit since we are already using it for conversion type pub struct StringMajorUnit(String)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, added StringMajorUnitForCore type to impl on!


let amount = required_conversion_type
.convert(payment_intent.amount, currency)
.change_context(errors::ApiErrorResponse::AmountConversionFailed {
amount_type: "StringMajorUnit",
})?;

let order_details = validate_order_details(payment_intent.order_details.clone(), currency)?;

let session_expiry = payment_link.fulfilment_time.unwrap_or_else(|| {
Expand Down Expand Up @@ -325,6 +333,7 @@ fn validate_order_details(
Option<Vec<api_models::payments::OrderDetailsWithStringAmount>>,
error_stack::Report<errors::ApiErrorResponse>,
> {
let required_conversion_type = StringMajorUnitForConnector;
let order_details = order_details
.map(|order_details| {
order_details
Expand Down Expand Up @@ -356,10 +365,11 @@ fn validate_order_details(
.product_img_link
.clone_from(&order.product_img_link)
};
order_details_amount_string.amount =
currency
.to_currency_base_unit(order.amount)
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?;
order_details_amount_string.amount = required_conversion_type
.convert(MinorUnit::new(order.amount), currency)
.change_context(errors::ApiErrorResponse::AmountConversionFailed {
amount_type: "StringMajorUnit",
})?;
order_details_amount_string.product_name =
capitalize_first_char(&order.product_name.clone());
order_details_amount_string.quantity = order.quantity;
Expand All @@ -386,9 +396,11 @@ pub fn get_payment_link_config_based_on_priority(
business_link_config: Option<serde_json::Value>,
merchant_name: String,
default_domain_name: String,
payment_link_config_id: Option<String>,
) -> Result<(admin_types::PaymentLinkConfig, String), error_stack::Report<errors::ApiErrorResponse>>
{
let (domain_name, business_config) = if let Some(business_config) = business_link_config {
let (domain_name, business_theme_configs) = if let Some(business_config) = business_link_config
{
let extracted_value: api_models::admin::BusinessPaymentLinkConfig = business_config
.parse_value("BusinessPaymentLinkConfig")
.change_context(errors::ApiErrorResponse::InvalidDataValue {
Expand All @@ -402,73 +414,32 @@ pub fn get_payment_link_config_based_on_priority(
.clone()
.map(|d_name| format!("https://{}", d_name))
.unwrap_or_else(|| default_domain_name.clone()),
Some(extracted_value.config),
payment_link_config_id
.and_then(|id| {
extracted_value
.business_specific_configs
.as_ref()
.and_then(|specific_configs| specific_configs.get(&id).cloned())
})
.or(extracted_value.default_config),
)
} else {
(default_domain_name, None)
};

let theme = payment_create_link_config
.as_ref()
.and_then(|pc_config| pc_config.config.theme.clone())
.or_else(|| {
business_config
.as_ref()
.and_then(|business_config| business_config.theme.clone())
})
.unwrap_or(DEFAULT_BACKGROUND_COLOR.to_string());

let logo = payment_create_link_config
.as_ref()
.and_then(|pc_config| pc_config.config.logo.clone())
.or_else(|| {
business_config
.as_ref()
.and_then(|business_config| business_config.logo.clone())
})
.unwrap_or(DEFAULT_MERCHANT_LOGO.to_string());

let seller_name = payment_create_link_config
.as_ref()
.and_then(|pc_config| pc_config.config.seller_name.clone())
.or_else(|| {
business_config
.as_ref()
.and_then(|business_config| business_config.seller_name.clone())
})
.unwrap_or(merchant_name.clone());

let sdk_layout = payment_create_link_config
.as_ref()
.and_then(|pc_config| pc_config.config.sdk_layout.clone())
.or_else(|| {
business_config
.as_ref()
.and_then(|business_config| business_config.sdk_layout.clone())
})
.unwrap_or(DEFAULT_SDK_LAYOUT.to_owned());

let display_sdk_only = payment_create_link_config
.as_ref()
.and_then(|pc_config| {
pc_config.config.display_sdk_only.or_else(|| {
business_config
.as_ref()
.and_then(|business_config| business_config.display_sdk_only)
})
})
.unwrap_or(DEFAULT_DISPLAY_SDK_ONLY);

let enabled_saved_payment_method = payment_create_link_config
.as_ref()
.and_then(|pc_config| {
pc_config.config.enabled_saved_payment_method.or_else(|| {
business_config
.as_ref()
.and_then(|business_config| business_config.enabled_saved_payment_method)
})
})
.unwrap_or(DEFAULT_ENABLE_SAVED_PAYMENT_METHOD);
let (theme, logo, seller_name, sdk_layout, display_sdk_only, enabled_saved_payment_method) = get_payment_link_config_value!(
payment_create_link_config,
business_theme_configs,
(theme, DEFAULT_BACKGROUND_COLOR.to_string()),
(logo, DEFAULT_MERCHANT_LOGO.to_string()),
(seller_name, merchant_name.clone()),
(sdk_layout, DEFAULT_SDK_LAYOUT.to_owned()),
(display_sdk_only, DEFAULT_DISPLAY_SDK_ONLY),
(
enabled_saved_payment_method,
DEFAULT_ENABLE_SAVED_PAYMENT_METHOD
)
);

let payment_link_config = admin_types::PaymentLinkConfig {
theme,
Expand Down Expand Up @@ -567,9 +538,13 @@ pub async fn get_payment_link_status(
field_name: "currency",
})?;

let amount = currency
.to_currency_base_unit(payment_attempt.net_amount.get_amount_as_i64())
.change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?;
let required_conversion_type = StringMajorUnitForConnector;

let amount = required_conversion_type
.convert(payment_attempt.net_amount, currency)
.change_context(errors::ApiErrorResponse::AmountConversionFailed {
amount_type: "StringMajorUnit",
})?;

// converting first letter of merchant name to upperCase
let merchant_name = capitalize_first_char(&payment_link_config.seller_name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,18 +216,18 @@ function boot() {
"quantity": null
});
}
}

if (paymentDetails.merchant_name) {
document.title = "Payment requested by " + paymentDetails.merchant_name;
}
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);
}
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

Expand Down
Loading
Loading