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: add test_mode for quickly testing payout links #5669

Merged
merged 8 commits into from
Aug 27, 2024
16 changes: 15 additions & 1 deletion api-reference-v2/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -2953,7 +2953,15 @@
"$ref": "#/components/schemas/BusinessGenericLinkConfig"
},
{
"type": "object"
"type": "object",
"properties": {
"payout_test_mode": {
"type": "boolean",
"description": "Allows for removing any validations / pre-requisites which are necessary in a production environment",
"default": false,
"nullable": true
}
}
}
]
},
Expand Down Expand Up @@ -13927,6 +13935,12 @@
"description": "List of payout methods shown on collect UI",
"example": "[{\"payment_method\": \"bank_transfer\", \"payment_method_types\": [\"ach\", \"bacs\"]}]",
"nullable": true
},
"test_mode": {
"type": "boolean",
"description": "`test_mode` allows for opening payout links without any restrictions. This removes\n- domain name validations\n- check for making sure link is accessed within an iframe",
"example": false,
"nullable": true
}
}
}
Expand Down
16 changes: 15 additions & 1 deletion api-reference/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -7174,7 +7174,15 @@
"$ref": "#/components/schemas/BusinessGenericLinkConfig"
},
{
"type": "object"
"type": "object",
"properties": {
"payout_test_mode": {
"type": "boolean",
"description": "Allows for removing any validations / pre-requisites which are necessary in a production environment",
"default": false,
"nullable": true
}
}
}
]
},
Expand Down Expand Up @@ -18734,6 +18742,12 @@
"description": "List of payout methods shown on collect UI",
"example": "[{\"payment_method\": \"bank_transfer\", \"payment_method_types\": [\"ach\", \"bacs\"]}]",
"nullable": true
},
"test_mode": {
"type": "boolean",
"description": "`test_mode` allows for opening payout links without any restrictions. This removes\n- domain name validations\n- check for making sure link is accessed within an iframe",
"example": false,
"nullable": true
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/api_models/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2516,6 +2516,10 @@ pub struct BusinessCollectLinkConfig {
pub struct BusinessPayoutLinkConfig {
#[serde(flatten)]
pub config: BusinessGenericLinkConfig,

/// Allows for removing any validations / pre-requisites which are necessary in a production environment
#[schema(value_type = Option<bool>, default = false)]
pub payout_test_mode: Option<bool>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
Expand Down
8 changes: 8 additions & 0 deletions crates/api_models/src/payouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ pub struct PayoutCreatePayoutLinkConfig {
/// List of payout methods shown on collect UI
#[schema(value_type = Option<Vec<EnabledPaymentMethod>>, example = r#"[{"payment_method": "bank_transfer", "payment_method_types": ["ach", "bacs"]}]"#)]
pub enabled_payment_methods: Option<Vec<link_utils::EnabledPaymentMethod>>,

/// `test_mode` allows for opening payout links without any restrictions. This removes
/// - domain name validations
/// - check for making sure link is accessed within an iframe
#[schema(value_type = Option<bool>, example = false)]
pub test_mode: Option<bool>,
}

/// The payout method information required for carrying out a payout
Expand Down Expand Up @@ -772,6 +778,7 @@ pub struct PayoutLinkDetails {
pub amount: common_utils::types::StringMajorUnit,
pub currency: common_enums::Currency,
pub locale: String,
pub test_mode: bool,
}

#[derive(Clone, Debug, serde::Serialize)]
Expand All @@ -787,4 +794,5 @@ pub struct PayoutLinkStatusDetails {
pub error_message: Option<String>,
#[serde(flatten)]
pub ui_config: link_utils::GenericLinkUiConfigFormData,
pub test_mode: bool,
}
2 changes: 2 additions & 0 deletions crates/common_utils/src/link_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ pub struct PayoutLinkData {
pub currency: enums::Currency,
/// A list of allowed domains (glob patterns) where this link can be embedded / opened from
pub allowed_domains: HashSet<String>,
/// `test_mode` can be used for testing payout links without any restrictions
pub test_mode: Option<bool>,
}

crate::impl_to_sql_from_sql_json!(PayoutLinkData);
Expand Down
1 change: 1 addition & 0 deletions crates/diesel_models/src/business_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ common_utils::impl_to_sql_from_sql_json!(BusinessPaymentLinkConfig);
pub struct BusinessPayoutLinkConfig {
#[serde(flatten)]
pub config: BusinessGenericLinkConfig,
pub payout_test_mode: Option<bool>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// @ts-check

// Top level checks
// @ts-ignore
var payoutDetails = window.__PAYOUT_DETAILS;
var isTestMode = payoutDetails.test_mode;

var isFramed = false;
try {
isFramed = window.parent.location !== window.location;
Expand All @@ -12,7 +16,7 @@ try {
}

// Remove the script from DOM incase it's not iframed
if (!isFramed) {
if (!isTestMode && !isFramed) {
function initializePayoutSDK() {
var errMsg = "{{i18n_not_allowed}}";
var contentElement = document.getElementById("payout-link");
Expand Down
13 changes: 9 additions & 4 deletions crates/router/src/core/payout_link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ pub async fn initiate_payout_link(
message: "payout link not found".to_string(),
})?;

validator::validate_payout_link_render_request(request_headers, &payout_link)?;
let allowed_domains = validator::validate_payout_link_render_request_and_get_allowed_domains(
request_headers,
&payout_link,
)?;

// Check status and return form data accordingly
let has_expired = common_utils::date_time::now() > payout_link.expiry;
Expand Down Expand Up @@ -120,7 +123,7 @@ pub async fn initiate_payout_link(

Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
GenericLinks {
allowed_domains: (link_data.allowed_domains),
allowed_domains,
data: GenericLinksData::ExpiredLink(expired_link_data),
locale,
},
Expand Down Expand Up @@ -204,6 +207,7 @@ pub async fn initiate_payout_link(
amount,
currency: payout.destination_currency,
locale: locale.clone(),
test_mode: link_data.test_mode.unwrap_or(false),
};

let serialized_css_content = String::new();
Expand All @@ -224,7 +228,7 @@ pub async fn initiate_payout_link(
};
Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
GenericLinks {
allowed_domains: (link_data.allowed_domains),
allowed_domains,
data: GenericLinksData::PayoutLink(generic_form_data),
locale,
},
Expand All @@ -249,6 +253,7 @@ pub async fn initiate_payout_link(
error_code: payout_attempt.error_code,
error_message: payout_attempt.error_message,
ui_config: ui_config_data,
test_mode: link_data.test_mode.unwrap_or(false),
};

let serialized_css_content = String::new();
Expand All @@ -267,7 +272,7 @@ pub async fn initiate_payout_link(
};
Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
GenericLinks {
allowed_domains: (link_data.allowed_domains),
allowed_domains,
data: GenericLinksData::PayoutLinkStatus(generic_status_data),
locale,
},
Expand Down
40 changes: 30 additions & 10 deletions crates/router/src/core/payouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod helpers;
pub mod retry;
pub mod transformers;
pub mod validator;
use std::vec::IntoIter;
use std::{collections::HashSet, vec::IntoIter};

use api_models::{self, enums as api_enums, payouts::PayoutLinkResponse};
#[cfg(feature = "payout_retry")]
Expand All @@ -28,7 +28,7 @@ use futures::future::join_all;
use masking::{PeekInterface, Secret};
#[cfg(feature = "payout_retry")]
use retry::GsmValidation;
use router_env::{instrument, logger, tracing};
use router_env::{instrument, logger, tracing, Env};
use scheduler::utils as pt_utils;
use serde_json;
use time::Duration;
Expand Down Expand Up @@ -2614,15 +2614,34 @@ pub async fn create_payout_link(
.and_then(|config| config.ui_config.clone())
.or(profile_ui_config);

// Validate allowed_domains presence
let allowed_domains = profile_config
let test_mode_in_config = payout_link_config_req
.as_ref()
.map(|config| config.config.allowed_domains.to_owned())
.get_required_value("allowed_domains")
.change_context(errors::ApiErrorResponse::LinkConfigurationError {
message: "Payout links cannot be used without setting allowed_domains in profile"
.to_string(),
})?;
.and_then(|config| config.test_mode)
.or_else(|| profile_config.as_ref().and_then(|c| c.payout_test_mode));
let is_test_mode_enabled = test_mode_in_config.unwrap_or(false);

let allowed_domains = match (router_env::which(), is_test_mode_enabled) {
// Throw error in case test_mode was enabled in production
(Env::Production, true) => Err(report!(errors::ApiErrorResponse::LinkConfigurationError {
message: "test_mode cannot be true for creating payout_links in production".to_string()
})),
// Send empty set of whitelisted domains
(_, true) => {
Ok(HashSet::new())
},
// Otherwise, fetch and use allowed domains from profile config
(_, false) => {
profile_config
.as_ref()
.map(|config| config.config.allowed_domains.to_owned())
.get_required_value("allowed_domains")
.change_context(errors::ApiErrorResponse::LinkConfigurationError {
message:
"Payout links cannot be used without setting allowed_domains in profile. If you're using a non-production environment, you can set test_mode to true while in payout_link_config"
.to_string(),
})
}
}?;

// Form data to be injected in the link
let (logo, merchant_name, theme) = match ui_config {
Expand Down Expand Up @@ -2685,6 +2704,7 @@ pub async fn create_payout_link(
amount: MinorUnit::from(*amount),
currency: *currency,
allowed_domains,
test_mode: test_mode_in_config,
};

create_payout_link_db_entry(state, merchant_id, &data, req.return_url.clone()).await
Expand Down
Loading
Loading