fix(router): store customer_acceptance in payment_attempt, use it in confirm flow for delayed authorizations like external 3ds flow (#5308)

This commit is contained in:
Sai Harsha Vardhan
2024-07-15 21:16:44 +05:30
committed by GitHub
parent 61b3aef661
commit 0f70473a3a
12 changed files with 65 additions and 5 deletions

View File

@ -1,3 +1,4 @@
use common_utils::pii;
use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
@ -72,6 +73,7 @@ pub struct PaymentAttempt {
pub charge_id: Option<String>, pub charge_id: Option<String>,
pub client_source: Option<String>, pub client_source: Option<String>,
pub client_version: Option<String>, pub client_version: Option<String>,
pub customer_acceptance: Option<pii::SecretSerdeValue>,
} }
impl PaymentAttempt { impl PaymentAttempt {
@ -155,6 +157,7 @@ pub struct PaymentAttemptNew {
pub charge_id: Option<String>, pub charge_id: Option<String>,
pub client_source: Option<String>, pub client_source: Option<String>,
pub client_version: Option<String>, pub client_version: Option<String>,
pub customer_acceptance: Option<pii::SecretSerdeValue>,
} }
impl PaymentAttemptNew { impl PaymentAttemptNew {
@ -240,6 +243,7 @@ pub enum PaymentAttemptUpdate {
payment_method_billing_address_id: Option<String>, payment_method_billing_address_id: Option<String>,
client_source: Option<String>, client_source: Option<String>,
client_version: Option<String>, client_version: Option<String>,
customer_acceptance: Option<pii::SecretSerdeValue>,
}, },
VoidUpdate { VoidUpdate {
status: storage_enums::AttemptStatus, status: storage_enums::AttemptStatus,
@ -410,6 +414,7 @@ pub struct PaymentAttemptUpdateInternal {
charge_id: Option<String>, charge_id: Option<String>,
client_source: Option<String>, client_source: Option<String>,
client_version: Option<String>, client_version: Option<String>,
customer_acceptance: Option<pii::SecretSerdeValue>,
} }
impl PaymentAttemptUpdateInternal { impl PaymentAttemptUpdateInternal {
@ -478,6 +483,7 @@ impl PaymentAttemptUpdate {
charge_id, charge_id,
client_source, client_source,
client_version, client_version,
customer_acceptance,
} = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source); } = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source);
PaymentAttempt { PaymentAttempt {
amount: amount.unwrap_or(source.amount), amount: amount.unwrap_or(source.amount),
@ -529,6 +535,7 @@ impl PaymentAttemptUpdate {
charge_id: charge_id.or(source.charge_id), charge_id: charge_id.or(source.charge_id),
client_source: client_source.or(source.client_source), client_source: client_source.or(source.client_source),
client_version: client_version.or(source.client_version), client_version: client_version.or(source.client_version),
customer_acceptance: customer_acceptance.or(source.customer_acceptance),
..source ..source
} }
} }
@ -617,6 +624,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
payment_method_id, payment_method_id,
client_source, client_source,
client_version, client_version,
customer_acceptance,
} => Self { } => Self {
amount: Some(amount), amount: Some(amount),
currency: Some(currency), currency: Some(currency),
@ -648,6 +656,7 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
capture_method, capture_method,
client_source, client_source,
client_version, client_version,
customer_acceptance,
..Default::default() ..Default::default()
}, },
PaymentAttemptUpdate::VoidUpdate { PaymentAttemptUpdate::VoidUpdate {

View File

@ -820,6 +820,7 @@ diesel::table! {
client_source -> Nullable<Varchar>, client_source -> Nullable<Varchar>,
#[max_length = 64] #[max_length = 64]
client_version -> Nullable<Varchar>, client_version -> Nullable<Varchar>,
customer_acceptance -> Nullable<Jsonb>,
} }
} }

View File

@ -76,6 +76,7 @@ pub struct PaymentAttemptBatchNew {
pub charge_id: Option<String>, pub charge_id: Option<String>,
pub client_source: Option<String>, pub client_source: Option<String>,
pub client_version: Option<String>, pub client_version: Option<String>,
pub customer_acceptance: Option<common_utils::pii::SecretSerdeValue>,
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -139,6 +140,7 @@ impl PaymentAttemptBatchNew {
charge_id: self.charge_id, charge_id: self.charge_id,
client_source: self.client_source, client_source: self.client_source,
client_version: self.client_version, client_version: self.client_version,
customer_acceptance: self.customer_acceptance,
} }
} }
} }

View File

@ -43,7 +43,7 @@ pub struct MandateData {
pub mandate_type: Option<MandateDataType>, pub mandate_type: Option<MandateDataType>,
} }
#[derive(Default, Eq, PartialEq, Debug, Clone, serde::Deserialize)] #[derive(Default, Eq, PartialEq, Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct CustomerAcceptance { pub struct CustomerAcceptance {
/// Type of acceptance provided by the /// Type of acceptance provided by the
pub acceptance_type: AcceptanceType, pub acceptance_type: AcceptanceType,
@ -54,7 +54,7 @@ pub struct CustomerAcceptance {
pub online: Option<OnlineMandate>, pub online: Option<OnlineMandate>,
} }
#[derive(Default, Debug, PartialEq, Eq, Clone, serde::Deserialize)] #[derive(Default, Debug, PartialEq, Eq, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum AcceptanceType { pub enum AcceptanceType {
Online, Online,
@ -62,7 +62,7 @@ pub enum AcceptanceType {
Offline, Offline,
} }
#[derive(Default, Eq, PartialEq, Debug, Clone, serde::Deserialize)] #[derive(Default, Eq, PartialEq, Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct OnlineMandate { pub struct OnlineMandate {
/// Ip address of the customer machine from which the mandate was created /// Ip address of the customer machine from which the mandate was created
#[serde(skip_deserializing)] #[serde(skip_deserializing)]

View File

@ -2,6 +2,7 @@ use api_models::enums::Connector;
use common_enums as storage_enums; use common_enums as storage_enums;
use common_utils::{ use common_utils::{
errors::{CustomResult, ValidationError}, errors::{CustomResult, ValidationError},
pii,
types::MinorUnit, types::MinorUnit,
}; };
use error_stack::ResultExt; use error_stack::ResultExt;
@ -175,6 +176,7 @@ pub struct PaymentAttempt {
pub charge_id: Option<String>, pub charge_id: Option<String>,
pub client_source: Option<String>, pub client_source: Option<String>,
pub client_version: Option<String>, pub client_version: Option<String>,
pub customer_acceptance: Option<pii::SecretSerdeValue>,
} }
impl PaymentAttempt { impl PaymentAttempt {
@ -264,6 +266,7 @@ pub struct PaymentAttemptNew {
pub charge_id: Option<String>, pub charge_id: Option<String>,
pub client_source: Option<String>, pub client_source: Option<String>,
pub client_version: Option<String>, pub client_version: Option<String>,
pub customer_acceptance: Option<pii::SecretSerdeValue>,
} }
impl PaymentAttemptNew { impl PaymentAttemptNew {
@ -346,6 +349,7 @@ pub enum PaymentAttemptUpdate {
payment_method_id: Option<String>, payment_method_id: Option<String>,
client_source: Option<String>, client_source: Option<String>,
client_version: Option<String>, client_version: Option<String>,
customer_acceptance: Option<pii::SecretSerdeValue>,
}, },
RejectUpdate { RejectUpdate {
status: storage_enums::AttemptStatus, status: storage_enums::AttemptStatus,

View File

@ -3712,6 +3712,7 @@ impl AttemptType {
charge_id: None, charge_id: None,
client_source: old_payment_attempt.client_source, client_source: old_payment_attempt.client_source,
client_version: old_payment_attempt.client_version, client_version: old_payment_attempt.client_version,
customer_acceptance: old_payment_attempt.customer_acceptance,
} }
} }

View File

@ -341,7 +341,17 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
.change_context(errors::ApiErrorResponse::InvalidDataValue { .change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "browser_info", field_name: "browser_info",
})?; })?;
let customer_acceptance = request.customer_acceptance.clone().map(From::from); let customer_acceptance = request.customer_acceptance.clone().or(payment_attempt
.customer_acceptance
.clone()
.map(|customer_acceptance| {
customer_acceptance
.expose()
.parse_value("CustomerAcceptance")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while deserializing customer_acceptance")
})
.transpose()?);
let recurring_details = request.recurring_details.clone(); let recurring_details = request.recurring_details.clone();
@ -360,6 +370,16 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
payment_attempt.capture_method = request.capture_method.or(payment_attempt.capture_method); payment_attempt.capture_method = request.capture_method.or(payment_attempt.capture_method);
payment_attempt.customer_acceptance = request
.customer_acceptance
.clone()
.map(|customer_acceptance| customer_acceptance.encode_to_value())
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encoding customer_acceptance to value")?
.map(masking::Secret::new)
.or(payment_attempt.customer_acceptance);
currency = payment_attempt.currency.get_required_value("currency")?; currency = payment_attempt.currency.get_required_value("currency")?;
amount = payment_attempt.get_total_amount().into(); amount = payment_attempt.get_total_amount().into();
@ -616,7 +636,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
mandate_id: None, mandate_id: None,
mandate_connector, mandate_connector,
setup_mandate, setup_mandate,
customer_acceptance, customer_acceptance: customer_acceptance.map(From::from),
token, token,
address: PaymentAddress::new( address: PaymentAddress::new(
shipping_address.as_ref().map(From::from), shipping_address.as_ref().map(From::from),
@ -1211,6 +1231,7 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen
payment_method_id: m_payment_method_id, payment_method_id: m_payment_method_id,
client_source, client_source,
client_version, client_version,
customer_acceptance: payment_data.payment_attempt.customer_acceptance,
}, },
storage_scheme, storage_scheme,
) )

View File

@ -281,6 +281,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
&payment_method_info, &payment_method_info,
merchant_key_store, merchant_key_store,
profile_id, profile_id,
&customer_acceptance,
) )
.await?; .await?;
@ -796,6 +797,7 @@ impl PaymentCreate {
payment_method_info: &Option<PaymentMethod>, payment_method_info: &Option<PaymentMethod>,
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
profile_id: String, profile_id: String,
customer_acceptance: &Option<payments::CustomerAcceptance>,
) -> RouterResult<( ) -> RouterResult<(
storage::PaymentAttemptNew, storage::PaymentAttemptNew,
Option<api_models::payments::AdditionalPaymentData>, Option<api_models::payments::AdditionalPaymentData>,
@ -966,6 +968,13 @@ impl PaymentCreate {
charge_id: None, charge_id: None,
client_source: None, client_source: None,
client_version: None, client_version: None,
customer_acceptance: customer_acceptance
.clone()
.map(|customer_acceptance| customer_acceptance.encode_to_value())
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to serialize customer_acceptance")?
.map(Secret::new),
}, },
additional_pm_data, additional_pm_data,
)) ))

View File

@ -157,6 +157,7 @@ impl PaymentAttemptInterface for MockDb {
charge_id: payment_attempt.charge_id, charge_id: payment_attempt.charge_id,
client_source: payment_attempt.client_source, client_source: payment_attempt.client_source,
client_version: payment_attempt.client_version, client_version: payment_attempt.client_version,
customer_acceptance: payment_attempt.customer_acceptance,
}; };
payment_attempts.push(payment_attempt.clone()); payment_attempts.push(payment_attempt.clone());
Ok(payment_attempt) Ok(payment_attempt)

View File

@ -418,6 +418,7 @@ impl<T: DatabaseStore> PaymentAttemptInterface for KVRouterStore<T> {
charge_id: payment_attempt.charge_id.clone(), charge_id: payment_attempt.charge_id.clone(),
client_source: payment_attempt.client_source.clone(), client_source: payment_attempt.client_source.clone(),
client_version: payment_attempt.client_version.clone(), client_version: payment_attempt.client_version.clone(),
customer_acceptance: payment_attempt.customer_acceptance.clone(),
}; };
let field = format!("pa_{}", created_attempt.attempt_id); let field = format!("pa_{}", created_attempt.attempt_id);
@ -1202,6 +1203,7 @@ impl DataModelExt for PaymentAttempt {
charge_id: self.charge_id, charge_id: self.charge_id,
client_source: self.client_source, client_source: self.client_source,
client_version: self.client_version, client_version: self.client_version,
customer_acceptance: self.customer_acceptance,
} }
} }
@ -1268,6 +1270,7 @@ impl DataModelExt for PaymentAttempt {
charge_id: storage_model.charge_id, charge_id: storage_model.charge_id,
client_source: storage_model.client_source, client_source: storage_model.client_source,
client_version: storage_model.client_version, client_version: storage_model.client_version,
customer_acceptance: storage_model.customer_acceptance,
} }
} }
} }
@ -1339,6 +1342,7 @@ impl DataModelExt for PaymentAttemptNew {
charge_id: self.charge_id, charge_id: self.charge_id,
client_source: self.client_source, client_source: self.client_source,
client_version: self.client_version, client_version: self.client_version,
customer_acceptance: self.customer_acceptance,
} }
} }
@ -1404,6 +1408,7 @@ impl DataModelExt for PaymentAttemptNew {
charge_id: storage_model.charge_id, charge_id: storage_model.charge_id,
client_source: storage_model.client_source, client_source: storage_model.client_source,
client_version: storage_model.client_version, client_version: storage_model.client_version,
customer_acceptance: storage_model.customer_acceptance,
} }
} }
} }
@ -1528,6 +1533,7 @@ impl DataModelExt for PaymentAttemptUpdate {
payment_method_billing_address_id, payment_method_billing_address_id,
client_source, client_source,
client_version, client_version,
customer_acceptance,
} => DieselPaymentAttemptUpdate::ConfirmUpdate { } => DieselPaymentAttemptUpdate::ConfirmUpdate {
amount: amount.get_amount_as_i64(), amount: amount.get_amount_as_i64(),
currency, currency,
@ -1560,6 +1566,7 @@ impl DataModelExt for PaymentAttemptUpdate {
payment_method_billing_address_id, payment_method_billing_address_id,
client_source, client_source,
client_version, client_version,
customer_acceptance,
}, },
Self::VoidUpdate { Self::VoidUpdate {
status, status,
@ -1863,6 +1870,7 @@ impl DataModelExt for PaymentAttemptUpdate {
payment_method_billing_address_id, payment_method_billing_address_id,
client_source, client_source,
client_version, client_version,
customer_acceptance,
} => Self::ConfirmUpdate { } => Self::ConfirmUpdate {
amount: MinorUnit::new(amount), amount: MinorUnit::new(amount),
currency, currency,
@ -1893,6 +1901,7 @@ impl DataModelExt for PaymentAttemptUpdate {
payment_method_billing_address_id, payment_method_billing_address_id,
client_source, client_source,
client_version, client_version,
customer_acceptance,
}, },
DieselPaymentAttemptUpdate::VoidUpdate { DieselPaymentAttemptUpdate::VoidUpdate {
status, status,

View File

@ -0,0 +1 @@
ALTER TABLE payment_attempt DROP COLUMN IF EXISTS customer_acceptance;

View File

@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE payment_attempt ADD COLUMN IF NOT EXISTS customer_acceptance JSONB DEFAULT NULL;