Skip to content

Commit

Permalink
refactor(payment_link): segregated payment link in html css js files,…
Browse files Browse the repository at this point in the history
… sdk over flow issue, surcharge bug, block SPM customer call for payment link (#3410)

Co-authored-by: Kashif <[email protected]>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Kashif <[email protected]>
Co-authored-by: Narayan Bhat <[email protected]>
  • Loading branch information
5 people authored Jan 30, 2024
1 parent 46c1822 commit a7bc8c6
Show file tree
Hide file tree
Showing 16 changed files with 2,743 additions and 2,509 deletions.
14 changes: 12 additions & 2 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3496,10 +3496,12 @@ pub struct PaymentLinkStatusDetails {
pub merchant_name: String,
#[serde(with = "common_utils::custom_serde::iso8601")]
pub created: PrimitiveDateTime,
pub intent_status: api_enums::IntentStatus,
pub payment_link_status: PaymentLinkStatus,
pub status: PaymentLinkStatusWrap,
pub error_code: Option<String>,
pub error_message: Option<String>,
pub redirect: bool,
pub theme: String,
pub return_url: String,
}

#[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]
Expand Down Expand Up @@ -3583,3 +3585,11 @@ pub enum PaymentLinkStatus {
Active,
Expired,
}

#[derive(PartialEq, Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
#[serde(untagged)]
pub enum PaymentLinkStatusWrap {
PaymentLinkStatus(PaymentLinkStatus),
IntentStatus(api_enums::IntentStatus),
}
6 changes: 4 additions & 2 deletions crates/common_utils/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ pub const X_HS_LATENCY: &str = "x-hs-latency";
pub const DEFAULT_BACKGROUND_COLOR: &str = "#212E46";

/// Default product Img Link
pub const DEFAULT_PRODUCT_IMG: &str = "https://i.imgur.com/On3VtKF.png";
pub const DEFAULT_PRODUCT_IMG: &str =
"https://live.hyperswitch.io/payment-link-assets/cart_placeholder.png";

/// Default Merchant Logo Link
pub const DEFAULT_MERCHANT_LOGO: &str = "https://i.imgur.com/RfxPFQo.png";
pub const DEFAULT_MERCHANT_LOGO: &str =
"https://live.hyperswitch.io/payment-link-assets/Merchant_placeholder.png";

/// Redirect url for Prophetpay
pub const PROPHETPAY_REDIRECT_URL: &str = "https://ccm-thirdparty.cps.golf/hp/tokenize/";
Expand Down
173 changes: 163 additions & 10 deletions crates/router/src/core/payment_link.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand All @@ -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),
Expand All @@ -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(
Expand Down Expand Up @@ -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),
)))
}
Loading

0 comments on commit a7bc8c6

Please sign in to comment.