refactor(stripe): return all the missing fields in a request (#935)

Co-authored-by: jeeva <jeeva.ramu@codurance.com>
Co-authored-by: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com>
Co-authored-by: ItsMeShashank <sattarde9913@gmail.com>
This commit is contained in:
Jeeva
2023-05-02 21:26:52 +01:00
committed by GitHub
parent 4e0489cf1c
commit e9fc34ff62
9 changed files with 315 additions and 28 deletions

View File

@ -1,6 +1,6 @@
use api_models::{self, enums as api_enums, payments};
use base64::Engine;
use common_utils::{errors::CustomResult, fp_utils, pii};
use common_utils::{errors::CustomResult, pii};
use error_stack::{IntoReport, ResultExt};
use masking::{ExposeInterface, ExposeOptionInterface, Secret};
use serde::{Deserialize, Serialize};
@ -8,7 +8,7 @@ use url::Url;
use uuid::Uuid;
use crate::{
consts,
collect_missing_value_keys, consts,
core::errors,
services,
types::{self, api, storage::enums},
@ -429,30 +429,21 @@ fn validate_shipping_address_against_payment_method(
payment_method: &StripePaymentMethodType,
) -> Result<(), error_stack::Report<errors::ConnectorError>> {
if let StripePaymentMethodType::AfterpayClearpay = payment_method {
fp_utils::when(shipping_address.name.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.address.first_name",
})
})?;
let missing_fields = collect_missing_value_keys!(
("shipping.address.first_name", shipping_address.name),
("shipping.address.line1", shipping_address.line1),
("shipping.address.country", shipping_address.country),
("shipping.address.zip", shipping_address.zip)
);
fp_utils::when(shipping_address.line1.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.address.line1",
if !missing_fields.is_empty() {
return Err(errors::ConnectorError::MissingRequiredFields {
field_names: missing_fields,
})
})?;
fp_utils::when(shipping_address.country.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.address.country",
})
})?;
fp_utils::when(shipping_address.zip.is_none(), || {
Err(errors::ConnectorError::MissingRequiredField {
field_name: "shipping.address.zip",
})
})?;
.into_report();
}
}
Ok(())
}
@ -1799,3 +1790,192 @@ pub struct DisputeObj {
pub dispute_id: String,
pub status: String,
}
#[cfg(test)]
mod test_validate_shipping_address_against_payment_method {
#![allow(clippy::unwrap_used)]
use api_models::enums::CountryCode;
use masking::Secret;
use crate::{
connector::stripe::transformers::{
validate_shipping_address_against_payment_method, StripePaymentMethodType,
StripeShippingAddress,
},
core::errors,
};
#[test]
fn should_return_ok() {
// Arrange
let stripe_shipping_address = create_stripe_shipping_address(
Some("name".to_string()),
Some("line1".to_string()),
Some(CountryCode::AD),
Some("zip".to_string()),
);
let payment_method = &StripePaymentMethodType::AfterpayClearpay;
//Act
let result = validate_shipping_address_against_payment_method(
&stripe_shipping_address,
payment_method,
);
// Assert
assert!(result.is_ok());
}
#[test]
fn should_return_err_for_empty_name() {
// Arrange
let stripe_shipping_address = create_stripe_shipping_address(
None,
Some("line1".to_string()),
Some(CountryCode::AD),
Some("zip".to_string()),
);
let payment_method = &StripePaymentMethodType::AfterpayClearpay;
//Act
let result = validate_shipping_address_against_payment_method(
&stripe_shipping_address,
payment_method,
);
// Assert
assert!(result.is_err());
let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned();
assert_eq!(missing_fields.len(), 1);
assert_eq!(missing_fields[0], "shipping.address.first_name");
}
#[test]
fn should_return_err_for_empty_line1() {
// Arrange
let stripe_shipping_address = create_stripe_shipping_address(
Some("name".to_string()),
None,
Some(CountryCode::AD),
Some("zip".to_string()),
);
let payment_method = &StripePaymentMethodType::AfterpayClearpay;
//Act
let result = validate_shipping_address_against_payment_method(
&stripe_shipping_address,
payment_method,
);
// Assert
assert!(result.is_err());
let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned();
assert_eq!(missing_fields.len(), 1);
assert_eq!(missing_fields[0], "shipping.address.line1");
}
#[test]
fn should_return_err_for_empty_country() {
// Arrange
let stripe_shipping_address = create_stripe_shipping_address(
Some("name".to_string()),
Some("line1".to_string()),
None,
Some("zip".to_string()),
);
let payment_method = &StripePaymentMethodType::AfterpayClearpay;
//Act
let result = validate_shipping_address_against_payment_method(
&stripe_shipping_address,
payment_method,
);
// Assert
assert!(result.is_err());
let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned();
assert_eq!(missing_fields.len(), 1);
assert_eq!(missing_fields[0], "shipping.address.country");
}
#[test]
fn should_return_err_for_empty_zip() {
// Arrange
let stripe_shipping_address = create_stripe_shipping_address(
Some("name".to_string()),
Some("line1".to_string()),
Some(CountryCode::AD),
None,
);
let payment_method = &StripePaymentMethodType::AfterpayClearpay;
//Act
let result = validate_shipping_address_against_payment_method(
&stripe_shipping_address,
payment_method,
);
// Assert
assert!(result.is_err());
let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned();
assert_eq!(missing_fields.len(), 1);
assert_eq!(missing_fields[0], "shipping.address.zip");
}
#[test]
fn should_return_error_when_missing_multiple_fields() {
// Arrange
let expected_missing_field_names: Vec<&'static str> =
vec!["shipping.address.zip", "shipping.address.country"];
let stripe_shipping_address = create_stripe_shipping_address(
Some("name".to_string()),
Some("line1".to_string()),
None,
None,
);
let payment_method = &StripePaymentMethodType::AfterpayClearpay;
//Act
let result = validate_shipping_address_against_payment_method(
&stripe_shipping_address,
payment_method,
);
// Assert
assert!(result.is_err());
let missing_fields = get_missing_fields(result.unwrap_err().current_context()).to_owned();
for field in missing_fields {
assert!(expected_missing_field_names.contains(&field));
}
}
fn get_missing_fields(connector_error: &errors::ConnectorError) -> Vec<&'static str> {
if let errors::ConnectorError::MissingRequiredFields { field_names } = connector_error {
return field_names.to_vec();
}
vec![]
}
fn create_stripe_shipping_address(
name: Option<String>,
line1: Option<String>,
country: Option<CountryCode>,
zip: Option<String>,
) -> StripeShippingAddress {
StripeShippingAddress {
name: name.map(Secret::new),
line1: line1.map(Secret::new),
country,
zip: zip.map(Secret::new),
city: Some(String::from("city")),
line2: Some(Secret::new(String::from("line2"))),
state: Some(Secret::new(String::from("state"))),
phone: Some(Secret::new(String::from("pbone number"))),
}
}
}

View File

@ -241,6 +241,8 @@ pub enum ConnectorError {
ResponseHandlingFailed,
#[error("Missing required field: {field_name}")]
MissingRequiredField { field_name: &'static str },
#[error("Missing required fields: {field_names:?}")]
MissingRequiredFields { field_names: Vec<&'static str> },
#[error("Failed to obtain authentication type")]
FailedToObtainAuthType,
#[error("Failed to obtain certificate")]

View File

@ -51,3 +51,18 @@ macro_rules! async_spawn {
tokio::spawn(async move { $t });
};
}
#[macro_export]
macro_rules! collect_missing_value_keys {
[$(($key:literal, $option:expr)),+] => {
{
let mut keys: Vec<&'static str> = Vec::new();
$(
if $option.is_none() {
keys.push($key);
}
)*
keys
}
};
}

View File

@ -4,8 +4,11 @@ use std::{
};
use error_stack::{report, ResultExt};
#[cfg(not(target_os = "windows"))]
use futures::StreamExt;
use redis_interface::{RedisConnectionPool, RedisEntryId};
use router_env::opentelemetry;
use tokio::sync::oneshot;
use uuid::Uuid;
use super::{consumer, metrics, process_data, workflows};
@ -376,3 +379,36 @@ where
Ok(())
}
}
#[cfg(not(target_os = "windows"))]
pub(crate) async fn signal_handler(
mut sig: signal_hook_tokio::Signals,
sender: oneshot::Sender<()>,
) {
if let Some(signal) = sig.next().await {
logger::info!(
"Received signal: {:?}",
signal_hook::low_level::signal_name(signal)
);
match signal {
signal_hook::consts::SIGTERM | signal_hook::consts::SIGINT => match sender.send(()) {
Ok(_) => {
logger::info!("Request for force shutdown received")
}
Err(_) => {
logger::error!(
"The receiver is closed, a termination call might already be sent"
)
}
},
_ => {}
}
}
}
#[cfg(target_os = "windows")]
pub(crate) async fn signal_handler(
_sig: common_utils::signals::DummySignal,
_sender: oneshot::Sender<()>,
) {
}