fix(compatibility): fix mismatched fields in the payments flow (#1640)

This commit is contained in:
Kritik Modi
2023-07-12 23:05:14 +05:30
committed by GitHub
parent 5a0e8be8c4
commit e0113b98fd
2 changed files with 140 additions and 87 deletions

View File

@ -1,15 +1,21 @@
use std::str::FromStr;
use api_models::payments;
use common_utils::{crypto::Encryptable, date_time, ext_traits::StringExt, pii as secret};
use common_utils::{
crypto::Encryptable,
date_time,
ext_traits::StringExt,
pii::{IpAddress, SecretSerdeValue},
};
use error_stack::{IntoReport, ResultExt};
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use crate::{
compatibility::stripe::refunds::types as stripe_refunds,
consts,
core::errors,
pii::{self, Email, PeekInterface},
pii::{Email, PeekInterface},
types::{
api::{admin, enums as api_enums},
transformers::{ForeignFrom, ForeignTryFrom},
@ -21,7 +27,7 @@ pub struct StripeBillingDetails {
pub address: Option<payments::AddressDetails>,
pub email: Option<Email>,
pub name: Option<String>,
pub phone: Option<pii::Secret<String>>,
pub phone: Option<masking::Secret<String>>,
}
impl From<StripeBillingDetails> for payments::Address {
@ -42,10 +48,10 @@ impl From<StripeBillingDetails> for payments::Address {
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
pub struct StripeCard {
pub number: cards::CardNumber,
pub exp_month: pii::Secret<String>,
pub exp_year: pii::Secret<String>,
pub cvc: pii::Secret<String>,
pub holder_name: Option<pii::Secret<String>>,
pub exp_month: masking::Secret<String>,
pub exp_year: masking::Secret<String>,
pub cvc: masking::Secret<String>,
pub holder_name: Option<masking::Secret<String>>,
}
#[derive(Serialize, PartialEq, Eq, Deserialize, Clone)]
@ -78,7 +84,7 @@ pub struct StripePaymentMethodData {
pub billing_details: Option<StripeBillingDetails>,
#[serde(flatten)]
pub payment_method_details: Option<StripePaymentMethodDetails>, // enum
pub metadata: Option<secret::SecretSerdeValue>,
pub metadata: Option<SecretSerdeValue>,
}
#[derive(PartialEq, Eq, Deserialize, Clone)]
@ -127,11 +133,21 @@ impl From<StripePaymentMethodDetails> for payments::PaymentMethodData {
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
pub struct Shipping {
pub address: Option<payments::AddressDetails>,
pub name: Option<String>,
pub address: AddressDetails,
pub name: Option<masking::Secret<String>>,
pub carrier: Option<String>,
pub phone: Option<pii::Secret<String>>,
pub tracking_number: Option<pii::Secret<String>>,
pub phone: Option<masking::Secret<String>>,
pub tracking_number: Option<masking::Secret<String>>,
}
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
pub struct AddressDetails {
pub city: Option<String>,
pub country: Option<api_enums::CountryAlpha2>,
pub line1: Option<masking::Secret<String>>,
pub line2: Option<masking::Secret<String>>,
pub postal_code: Option<masking::Secret<String>>,
pub state: Option<masking::Secret<String>>,
}
impl From<Shipping> for payments::Address {
@ -139,15 +155,57 @@ impl From<Shipping> for payments::Address {
Self {
phone: Some(payments::PhoneDetails {
number: details.phone,
country_code: details.address.as_ref().and_then(|address| {
address.country.as_ref().map(|country| country.to_string())
country_code: details.address.country.map(|country| country.to_string()),
}),
address: Some(payments::AddressDetails {
city: details.address.city,
country: details.address.country,
line1: details.address.line1,
line2: details.address.line2,
zip: details.address.postal_code,
state: details.address.state,
first_name: details.name,
line3: None,
last_name: None,
}),
address: details.address,
}
}
}
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
pub struct MandateData {
pub customer_acceptance: CustomerAcceptance,
pub mandate_type: Option<StripeMandateType>,
pub amount: Option<i64>,
#[serde(default, with = "common_utils::custom_serde::timestamp::option")]
pub start_date: Option<PrimitiveDateTime>,
#[serde(default, with = "common_utils::custom_serde::timestamp::option")]
pub end_date: Option<PrimitiveDateTime>,
}
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
pub struct CustomerAcceptance {
#[serde(rename = "type")]
pub acceptance_type: Option<AcceptanceType>,
pub accepted_at: Option<PrimitiveDateTime>,
pub online: Option<OnlineMandate>,
}
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "lowercase")]
pub enum AcceptanceType {
Online,
#[default]
Offline,
}
#[derive(Default, Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct OnlineMandate {
pub ip_address: masking::Secret<String, IpAddress>,
pub user_agent: String,
}
#[derive(Deserialize, Clone)]
pub struct StripePaymentIntentRequest {
pub id: Option<String>,
@ -167,39 +225,26 @@ pub struct StripePaymentIntentRequest {
pub shipping: Option<Shipping>,
pub statement_descriptor: Option<String>,
pub statement_descriptor_suffix: Option<String>,
pub metadata: Option<secret::SecretSerdeValue>,
pub client_secret: Option<pii::Secret<String>>,
pub metadata: Option<SecretSerdeValue>,
pub client_secret: Option<masking::Secret<String>>,
pub payment_method_options: Option<StripePaymentMethodOptions>,
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
pub mandate_id: Option<String>,
pub mandate: Option<String>,
pub off_session: Option<bool>,
pub payment_method_type: Option<api_enums::PaymentMethodType>,
pub payment_method_types: Option<api_enums::PaymentMethodType>,
pub receipt_ipaddress: Option<String>,
pub user_agent: Option<String>,
pub mandate_data: Option<MandateData>,
pub automatic_payment_methods: Option<SecretSerdeValue>, // not used
pub payment_method: Option<String>, // not used
pub confirmation_method: Option<String>, // not used
pub error_on_requires_action: Option<String>, // not used
pub radar_options: Option<SecretSerdeValue>, // not used
}
impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
type Error = error_stack::Report<errors::ApiErrorResponse>;
fn try_from(item: StripePaymentIntentRequest) -> errors::RouterResult<Self> {
let (mandate_options, authentication_type) = match item.payment_method_options {
Some(pmo) => {
let StripePaymentMethodOptions::Card {
request_three_d_secure,
mandate_options,
}: StripePaymentMethodOptions = pmo;
(
Option::<payments::MandateData>::foreign_try_from((
mandate_options,
item.currency.to_owned(),
))?,
Some(api_enums::AuthenticationType::foreign_from(
request_three_d_secure,
)),
)
}
None => (None, None),
};
let routable_connector: Option<api_enums::RoutableConnectors> =
item.connector.and_then(|v| v.into_iter().next());
@ -262,13 +307,26 @@ impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
statement_descriptor_suffix: item.statement_descriptor_suffix,
metadata: item.metadata,
client_secret: item.client_secret.map(|s| s.peek().clone()),
authentication_type,
mandate_data: mandate_options,
authentication_type: match item.payment_method_options {
Some(pmo) => {
let StripePaymentMethodOptions::Card {
request_three_d_secure,
}: StripePaymentMethodOptions = pmo;
Some(api_enums::AuthenticationType::foreign_from(
request_three_d_secure,
))
}
None => None,
},
mandate_data: ForeignTryFrom::foreign_try_from((
item.mandate_data,
item.currency.to_owned(),
))?,
merchant_connector_details: item.merchant_connector_details,
setup_future_usage: item.setup_future_usage,
mandate_id: item.mandate_id,
mandate_id: item.mandate,
off_session: item.off_session,
payment_method_type: item.payment_method_type,
payment_method_type: item.payment_method_types,
routing,
browser_info: Some(
serde_json::to_value(crate::types::BrowserInformation {
@ -280,6 +338,7 @@ impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("convert to browser info failed")?,
),
..Self::default()
});
request
@ -334,6 +393,7 @@ impl ToString for CancellationReason {
})
}
}
#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
pub struct StripePaymentCancelRequest {
cancellation_reason: Option<CancellationReason>,
@ -348,11 +408,6 @@ impl From<StripePaymentCancelRequest> for payments::PaymentsCancelRequest {
}
}
#[derive(Default, PartialEq, Eq, Deserialize, Clone)]
pub struct StripeCaptureRequest {
pub amount_to_capture: Option<i64>,
}
#[derive(Default, Eq, PartialEq, Serialize, Debug)]
pub struct StripePaymentIntentResponse {
pub id: Option<String>,
@ -366,8 +421,8 @@ pub struct StripePaymentIntentResponse {
pub created: Option<i64>,
pub customer: Option<String>,
pub refunds: Option<Vec<stripe_refunds::StripeRefundResponse>>,
pub mandate_id: Option<String>,
pub metadata: Option<secret::SecretSerdeValue>,
pub mandate: Option<String>,
pub metadata: Option<SecretSerdeValue>,
pub charges: Charges,
pub connector: Option<String>,
pub description: Option<String>,
@ -382,7 +437,7 @@ pub struct StripePaymentIntentResponse {
pub shipping: Option<payments::Address>,
pub billing: Option<payments::Address>,
#[serde(with = "common_utils::custom_serde::iso8601::option")]
pub capture_on: Option<time::PrimitiveDateTime>,
pub capture_on: Option<PrimitiveDateTime>,
pub payment_token: Option<String>,
pub email: Option<Email>,
pub phone: Option<masking::Secret<String>>,
@ -423,7 +478,7 @@ impl From<payments::PaymentsResponse> for StripePaymentIntentResponse {
refunds: resp
.refunds
.map(|a| a.into_iter().map(Into::into).collect()),
mandate_id: resp.mandate_id,
mandate: resp.mandate_id,
mandate_data: resp.mandate_data,
setup_future_usage: resp.setup_future_usage,
off_session: resp.off_session,
@ -543,7 +598,7 @@ impl TryFrom<StripePaymentListConstraints> for payments::PaymentListConstraints
#[inline]
fn from_timestamp_to_datetime(
time: Option<i64>,
) -> Result<Option<time::PrimitiveDateTime>, errors::ApiErrorResponse> {
) -> Result<Option<PrimitiveDateTime>, errors::ApiErrorResponse> {
if let Some(time) = time {
let time = time::OffsetDateTime::from_unix_timestamp(time).map_err(|_| {
errors::ApiErrorResponse::InvalidRequestData {
@ -581,7 +636,6 @@ impl From<payments::PaymentListResponse> for StripePaymentIntentListResponse {
pub enum StripePaymentMethodOptions {
Card {
request_three_d_secure: Option<Request3DS>,
mandate_options: Option<MandateOption>,
},
}
@ -595,21 +649,21 @@ pub enum StripeMandateType {
#[derive(PartialEq, Eq, Clone, Default, Deserialize, Serialize, Debug)]
pub struct MandateOption {
#[serde(default, with = "common_utils::custom_serde::timestamp::option")]
pub accepted_at: Option<time::PrimitiveDateTime>,
pub accepted_at: Option<PrimitiveDateTime>,
pub user_agent: Option<String>,
pub ip_address: Option<pii::Secret<String, common_utils::pii::IpAddress>>,
pub ip_address: Option<masking::Secret<String, IpAddress>>,
pub mandate_type: Option<StripeMandateType>,
pub amount: Option<i64>,
#[serde(default, with = "common_utils::custom_serde::timestamp::option")]
pub start_date: Option<time::PrimitiveDateTime>,
pub start_date: Option<PrimitiveDateTime>,
#[serde(default, with = "common_utils::custom_serde::timestamp::option")]
pub end_date: Option<time::PrimitiveDateTime>,
pub end_date: Option<PrimitiveDateTime>,
}
impl ForeignTryFrom<(Option<MandateOption>, Option<String>)> for Option<payments::MandateData> {
impl ForeignTryFrom<(Option<MandateData>, Option<String>)> for Option<payments::MandateData> {
type Error = error_stack::Report<errors::ApiErrorResponse>;
fn foreign_try_from(
(mandate_options, currency): (Option<MandateOption>, Option<String>),
(mandate_data, currency): (Option<MandateData>, Option<String>),
) -> errors::RouterResult<Self> {
let currency = currency
.ok_or(errors::ApiErrorResponse::MissingRequiredField {
@ -623,7 +677,7 @@ impl ForeignTryFrom<(Option<MandateOption>, Option<String>)> for Option<payments
},
)
})?;
let mandate_data = mandate_options.map(|mandate| payments::MandateData {
let mandate_data = mandate_data.map(|mandate| payments::MandateData {
mandate_type: match mandate.mandate_type {
Some(item) => match item {
StripeMandateType::SingleUse => Some(payments::MandateType::SingleUse(
@ -641,10 +695,13 @@ impl ForeignTryFrom<(Option<MandateOption>, Option<String>)> for Option<payments
},
customer_acceptance: Some(payments::CustomerAcceptance {
acceptance_type: payments::AcceptanceType::Online,
accepted_at: mandate.accepted_at,
online: Some(payments::OnlineMandate {
ip_address: mandate.ip_address,
user_agent: mandate.user_agent.unwrap_or_default(),
accepted_at: mandate.customer_acceptance.accepted_at,
online: mandate
.customer_acceptance
.online
.map(|online| payments::OnlineMandate {
ip_address: Some(online.ip_address),
user_agent: online.user_agent,
}),
}),
});

View File

@ -147,29 +147,12 @@ pub struct StripeSetupIntentRequest {
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
pub receipt_ipaddress: Option<String>,
pub user_agent: Option<String>,
pub mandate_data: Option<payment_intent::MandateData>,
}
impl TryFrom<StripeSetupIntentRequest> for payments::PaymentsRequest {
type Error = error_stack::Report<errors::ApiErrorResponse>;
fn try_from(item: StripeSetupIntentRequest) -> errors::RouterResult<Self> {
let (mandate_options, authentication_type) = match item.payment_method_options {
Some(pmo) => {
let payment_intent::StripePaymentMethodOptions::Card {
request_three_d_secure,
mandate_options,
}: payment_intent::StripePaymentMethodOptions = pmo;
(
Option::<payments::MandateData>::foreign_try_from((
mandate_options,
item.currency.to_owned(),
))?,
Some(api_enums::AuthenticationType::foreign_from(
request_three_d_secure,
)),
)
}
None => (None, None),
};
let routable_connector: Option<api_enums::RoutableConnectors> =
item.connector.and_then(|v| v.into_iter().next());
@ -243,9 +226,22 @@ impl TryFrom<StripeSetupIntentRequest> for payments::PaymentsRequest {
client_secret: item.client_secret.map(|s| s.peek().clone()),
setup_future_usage: item.setup_future_usage,
merchant_connector_details: item.merchant_connector_details,
authentication_type,
routing,
mandate_data: mandate_options,
authentication_type: match item.payment_method_options {
Some(pmo) => {
let payment_intent::StripePaymentMethodOptions::Card {
request_three_d_secure,
}: payment_intent::StripePaymentMethodOptions = pmo;
Some(api_enums::AuthenticationType::foreign_from(
request_three_d_secure,
))
}
None => None,
},
mandate_data: ForeignTryFrom::foreign_try_from((
item.mandate_data,
item.currency.to_owned(),
))?,
browser_info: Some(
serde_json::to_value(crate::types::BrowserInformation {
ip_address,