Skip to content

Commit ad4b7de

Browse files
lsamprasThisIsManiArjunKarthiksai-harsha-vardhanSanchithHegde
authored
refactor(storage_impl): split payment attempt models to domain + diesel (#2010)
Signed-off-by: chikke srujan <[email protected]> Co-authored-by: Mani Chandra <[email protected]> Co-authored-by: Arjun Karthik <[email protected]> Co-authored-by: Sai Harsha Vardhan <[email protected]> Co-authored-by: Sanchith Hegde <[email protected]> Co-authored-by: chikke srujan <[email protected]> Co-authored-by: Prasunna Soppa <[email protected]> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: DEEPANSHU BANSAL <[email protected]> Co-authored-by: Arvind Patel <[email protected]> Co-authored-by: Jagan Elavarasan <[email protected]> Co-authored-by: arvindpatel24 <[email protected]> Co-authored-by: anji-reddy-j <[email protected]> Co-authored-by: Hrithikesh <[email protected]> Co-authored-by: Apoorv Dixit <[email protected]> Co-authored-by: Pa1NarK <[email protected]>
1 parent 25e82a1 commit ad4b7de

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2358
-1542
lines changed

Cargo.lock

Lines changed: 9 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/common_enums/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ diesel = { version = "2.1.0", features = ["postgres"] }
1515
serde = { version = "1.0.160", features = [ "derive" ] }
1616
serde_json = "1.0.96"
1717
strum = { version = "0.25", features = [ "derive" ] }
18-
time = { version = "0.3.20", features = ["serde", "serde-well-known", "std"] }
18+
time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] }
1919
utoipa = { version = "3.3.0", features = ["preserve_order"] }
2020

2121
# First party crates

crates/data_models/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ olap = []
1515
[dependencies]
1616
# First party deps
1717
api_models = { version = "0.1.0", path = "../api_models" }
18+
masking = { version = "0.1.0", path = "../masking" }
1819
common_enums = { version = "0.1.0", path = "../common_enums" }
1920
common_utils = { version = "0.1.0", path = "../common_utils" }
2021

crates/data_models/src/mandates.rs

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
use api_models::payments::{
2+
AcceptanceType as ApiAcceptanceType, CustomerAcceptance as ApiCustomerAcceptance,
3+
MandateAmountData as ApiMandateAmountData, MandateData as ApiMandateData, MandateType,
4+
OnlineMandate as ApiOnlineMandate,
5+
};
16
use common_enums::Currency;
2-
use common_utils::pii;
7+
use common_utils::{date_time, errors::ParsingError, pii};
8+
use error_stack::{IntoReport, ResultExt};
9+
use masking::{PeekInterface, Secret};
310
use time::PrimitiveDateTime;
411

512
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
@@ -17,3 +24,135 @@ pub struct MandateAmountData {
1724
pub end_date: Option<PrimitiveDateTime>,
1825
pub metadata: Option<pii::SecretSerdeValue>,
1926
}
27+
28+
// The fields on this struct are optional, as we want to allow the merchant to provide partial
29+
// information about creating mandates
30+
#[derive(Default, Eq, PartialEq, Debug, Clone)]
31+
pub struct MandateData {
32+
/// A concent from the customer to store the payment method
33+
pub customer_acceptance: Option<CustomerAcceptance>,
34+
/// A way to select the type of mandate used
35+
pub mandate_type: Option<MandateDataType>,
36+
}
37+
38+
#[derive(Default, Eq, PartialEq, Debug, Clone)]
39+
pub struct CustomerAcceptance {
40+
/// Type of acceptance provided by the
41+
pub acceptance_type: AcceptanceType,
42+
/// Specifying when the customer acceptance was provided
43+
pub accepted_at: Option<PrimitiveDateTime>,
44+
/// Information required for online mandate generation
45+
pub online: Option<OnlineMandate>,
46+
}
47+
48+
#[derive(Default, Debug, PartialEq, Eq, Clone)]
49+
pub enum AcceptanceType {
50+
Online,
51+
#[default]
52+
Offline,
53+
}
54+
55+
#[derive(Default, Eq, PartialEq, Debug, Clone)]
56+
pub struct OnlineMandate {
57+
/// Ip address of the customer machine from which the mandate was created
58+
pub ip_address: Option<Secret<String, pii::IpAddress>>,
59+
/// The user-agent of the customer's browser
60+
pub user_agent: String,
61+
}
62+
63+
impl From<MandateType> for MandateDataType {
64+
fn from(mandate_type: MandateType) -> Self {
65+
match mandate_type {
66+
MandateType::SingleUse(mandate_amount_data) => {
67+
Self::SingleUse(mandate_amount_data.into())
68+
}
69+
MandateType::MultiUse(mandate_amount_data) => {
70+
Self::MultiUse(mandate_amount_data.map(|d| d.into()))
71+
}
72+
}
73+
}
74+
}
75+
76+
impl From<ApiMandateAmountData> for MandateAmountData {
77+
fn from(value: ApiMandateAmountData) -> Self {
78+
Self {
79+
amount: value.amount,
80+
currency: value.currency,
81+
start_date: value.start_date,
82+
end_date: value.end_date,
83+
metadata: value.metadata,
84+
}
85+
}
86+
}
87+
88+
impl From<ApiMandateData> for MandateData {
89+
fn from(value: ApiMandateData) -> Self {
90+
Self {
91+
customer_acceptance: value.customer_acceptance.map(|d| d.into()),
92+
mandate_type: value.mandate_type.map(|d| d.into()),
93+
}
94+
}
95+
}
96+
97+
impl From<ApiCustomerAcceptance> for CustomerAcceptance {
98+
fn from(value: ApiCustomerAcceptance) -> Self {
99+
Self {
100+
acceptance_type: value.acceptance_type.into(),
101+
accepted_at: value.accepted_at,
102+
online: value.online.map(|d| d.into()),
103+
}
104+
}
105+
}
106+
107+
impl From<ApiAcceptanceType> for AcceptanceType {
108+
fn from(value: ApiAcceptanceType) -> Self {
109+
match value {
110+
ApiAcceptanceType::Online => Self::Online,
111+
ApiAcceptanceType::Offline => Self::Offline,
112+
}
113+
}
114+
}
115+
116+
impl From<ApiOnlineMandate> for OnlineMandate {
117+
fn from(value: ApiOnlineMandate) -> Self {
118+
Self {
119+
ip_address: value.ip_address,
120+
user_agent: value.user_agent,
121+
}
122+
}
123+
}
124+
125+
impl CustomerAcceptance {
126+
pub fn get_ip_address(&self) -> Option<String> {
127+
self.online
128+
.as_ref()
129+
.and_then(|data| data.ip_address.as_ref().map(|ip| ip.peek().to_owned()))
130+
}
131+
132+
pub fn get_user_agent(&self) -> Option<String> {
133+
self.online.as_ref().map(|data| data.user_agent.clone())
134+
}
135+
136+
pub fn get_accepted_at(&self) -> PrimitiveDateTime {
137+
self.accepted_at
138+
.unwrap_or_else(common_utils::date_time::now)
139+
}
140+
}
141+
142+
impl MandateAmountData {
143+
pub fn get_end_date(
144+
&self,
145+
format: date_time::DateFormat,
146+
) -> error_stack::Result<Option<String>, ParsingError> {
147+
self.end_date
148+
.map(|date| {
149+
date_time::format_date(date, format)
150+
.into_report()
151+
.change_context(ParsingError::DateTimeParsingError)
152+
})
153+
.transpose()
154+
}
155+
pub fn get_metadata(&self) -> Option<pii::SecretSerdeValue> {
156+
self.metadata.clone()
157+
}
158+
}

crates/data_models/src/payments/payment_attempt.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use api_models::enums::Connector;
12
use common_enums as storage_enums;
23
use serde::{Deserialize, Serialize};
34
use time::PrimitiveDateTime;
@@ -77,6 +78,15 @@ pub trait PaymentAttemptInterface {
7778
merchant_id: &str,
7879
storage_scheme: MerchantStorageScheme,
7980
) -> error_stack::Result<PaymentListFilters, errors::StorageError>;
81+
82+
async fn get_total_count_of_filtered_payment_attempts(
83+
&self,
84+
merchant_id: &str,
85+
active_attempt_ids: &[String],
86+
connector: Option<Vec<Connector>>,
87+
payment_methods: Option<Vec<storage_enums::PaymentMethod>>,
88+
storage_scheme: MerchantStorageScheme,
89+
) -> error_stack::Result<i64, errors::StorageError>;
8090
}
8191

8292
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
@@ -129,7 +139,7 @@ pub struct PaymentAttempt {
129139
pub connector_response_reference_id: Option<String>,
130140
}
131141

132-
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
142+
#[derive(Clone, Debug, Eq, PartialEq)]
133143
pub struct PaymentListFilters {
134144
pub connector: Vec<String>,
135145
pub currency: Vec<storage_enums::Currency>,
@@ -222,6 +232,13 @@ pub enum PaymentAttemptUpdate {
222232
payment_experience: Option<storage_enums::PaymentExperience>,
223233
business_sub_label: Option<String>,
224234
straight_through_algorithm: Option<serde_json::Value>,
235+
error_code: Option<Option<String>>,
236+
error_message: Option<Option<String>>,
237+
},
238+
RejectUpdate {
239+
status: storage_enums::AttemptStatus,
240+
error_code: Option<Option<String>>,
241+
error_message: Option<Option<String>>,
225242
},
226243
VoidUpdate {
227244
status: storage_enums::AttemptStatus,
@@ -261,9 +278,8 @@ pub enum PaymentAttemptUpdate {
261278
error_message: Option<Option<String>>,
262279
error_reason: Option<Option<String>>,
263280
},
264-
MultipleCaptureUpdate {
265-
status: Option<storage_enums::AttemptStatus>,
266-
multiple_capture_count: Option<i16>,
281+
MultipleCaptureCountUpdate {
282+
multiple_capture_count: i16,
267283
},
268284
PreprocessingUpdate {
269285
status: storage_enums::AttemptStatus,

crates/diesel_models/src/query/payment_attempt.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use crate::{
1414
errors::{self, DatabaseError},
1515
payment_attempt::{
1616
PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate, PaymentAttemptUpdateInternal,
17-
PaymentListFilters,
1817
},
1918
payment_intent::PaymentIntent,
2019
query::generics::db_metrics,
@@ -212,15 +211,20 @@ impl PaymentAttempt {
212211
conn: &PgPooledConn,
213212
pi: &[PaymentIntent],
214213
merchant_id: &str,
215-
) -> StorageResult<PaymentListFilters> {
216-
let active_attempt_ids: Vec<String> = pi
214+
) -> StorageResult<(
215+
Vec<String>,
216+
Vec<enums::Currency>,
217+
Vec<IntentStatus>,
218+
Vec<enums::PaymentMethod>,
219+
)> {
220+
let active_attempts: Vec<String> = pi
217221
.iter()
218222
.map(|payment_intent| payment_intent.clone().active_attempt_id)
219223
.collect();
220224

221225
let filter = <Self as HasTable>::table()
222226
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
223-
.filter(dsl::attempt_id.eq_any(active_attempt_ids));
227+
.filter(dsl::attempt_id.eq_any(active_attempts));
224228

225229
let intent_status: Vec<IntentStatus> = pi
226230
.iter()
@@ -268,14 +272,12 @@ impl PaymentAttempt {
268272
.flatten()
269273
.collect::<Vec<enums::PaymentMethod>>();
270274

271-
let filters = PaymentListFilters {
272-
connector: filter_connector,
273-
currency: filter_currency,
274-
status: intent_status,
275-
payment_method: filter_payment_method,
276-
};
277-
278-
Ok(filters)
275+
Ok((
276+
filter_connector,
277+
filter_currency,
278+
intent_status,
279+
filter_payment_method,
280+
))
279281
}
280282
pub async fn get_total_count_of_attempts(
281283
conn: &PgPooledConn,

crates/router/src/connector/nuvei/transformers.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ use common_utils::{
44
date_time, fp_utils, pii,
55
pii::Email,
66
};
7+
use data_models::mandates::MandateDataType;
78
use error_stack::{IntoReport, ResultExt};
89
use masking::{PeekInterface, Secret};
910
use reqwest::Url;
1011
use serde::{Deserialize, Serialize};
1112

1213
use crate::{
1314
connector::utils::{
14-
self, AddressDetailsData, BrowserInformationData, MandateData,
15-
PaymentsAuthorizeRequestData, PaymentsCancelRequestData, RouterData,
15+
self, AddressDetailsData, BrowserInformationData, PaymentsAuthorizeRequestData,
16+
PaymentsCancelRequestData, RouterData,
1617
},
1718
consts,
1819
core::errors,
@@ -790,20 +791,28 @@ fn get_card_info<F>(
790791
.change_context(errors::ConnectorError::MissingRequiredField {
791792
field_name: "mandate_type",
792793
})? {
793-
payments::MandateType::SingleUse(details) => details,
794-
payments::MandateType::MultiUse(details) => {
794+
MandateDataType::SingleUse(details) => details,
795+
MandateDataType::MultiUse(details) => {
795796
details.ok_or(errors::ConnectorError::MissingRequiredField {
796797
field_name: "mandate_data.mandate_type.multi_use",
797798
})?
798799
}
799800
};
800-
let mandate_meta: NuveiMandateMeta =
801-
utils::to_connector_meta_from_secret(Some(details.get_metadata()?))?;
801+
let mandate_meta: NuveiMandateMeta = utils::to_connector_meta_from_secret(
802+
Some(details.get_metadata().ok_or_else(utils::missing_field_err(
803+
"mandate_data.mandate_type.{multi_use|single_use}.metadata",
804+
))?),
805+
)?;
802806
(
803807
Some("0".to_string()), // In case of first installment, rebilling should be 0
804808
Some(V2AdditionalParams {
805809
rebill_expiry: Some(
806-
details.get_end_date(date_time::DateFormat::YYYYMMDD)?,
810+
details
811+
.get_end_date(date_time::DateFormat::YYYYMMDD)
812+
.change_context(errors::ConnectorError::DateFormattingFailed)?
813+
.ok_or_else(utils::missing_field_err(
814+
"mandate_data.mandate_type.{multi_use|single_use}.end_date",
815+
))?,
807816
),
808817
rebill_frequency: Some(mandate_meta.frequency),
809818
challenge_window_size: None,

0 commit comments

Comments
 (0)