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

View File

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

View File

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

View File

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

View File

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

View File

@ -3712,6 +3712,7 @@ impl AttemptType {
charge_id: None,
client_source: old_payment_attempt.client_source,
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 {
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();
@ -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.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")?;
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_connector,
setup_mandate,
customer_acceptance,
customer_acceptance: customer_acceptance.map(From::from),
token,
address: PaymentAddress::new(
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,
client_source,
client_version,
customer_acceptance: payment_data.payment_attempt.customer_acceptance,
},
storage_scheme,
)

View File

@ -281,6 +281,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
&payment_method_info,
merchant_key_store,
profile_id,
&customer_acceptance,
)
.await?;
@ -796,6 +797,7 @@ impl PaymentCreate {
payment_method_info: &Option<PaymentMethod>,
key_store: &domain::MerchantKeyStore,
profile_id: String,
customer_acceptance: &Option<payments::CustomerAcceptance>,
) -> RouterResult<(
storage::PaymentAttemptNew,
Option<api_models::payments::AdditionalPaymentData>,
@ -966,6 +968,13 @@ impl PaymentCreate {
charge_id: None,
client_source: 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,
))

View File

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

View File

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