fix: introduce net_amount field in payment response (#3115)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Hrithikesh
2024-01-08 19:57:37 +05:30
committed by GitHub
parent 7d43c5a736
commit 23e0c63541
13 changed files with 104 additions and 6 deletions

View File

@ -2037,6 +2037,11 @@ pub struct PaymentsResponse {
#[schema(example = 100)]
pub amount: i64,
/// The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount,
/// If no surcharge_details, net_amount = amount
#[schema(example = 110)]
pub net_amount: i64,
/// The maximum amount that could be captured from the payment
#[schema(minimum = 100, example = 6540)]
pub amount_capturable: Option<i64>,

View File

@ -107,6 +107,7 @@ pub struct PaymentAttempt {
pub attempt_id: String,
pub status: storage_enums::AttemptStatus,
pub amount: i64,
pub net_amount: i64,
pub currency: Option<storage_enums::Currency>,
pub save_to_locker: Option<bool>,
pub connector: Option<String>,
@ -183,6 +184,9 @@ pub struct PaymentAttemptNew {
pub attempt_id: String,
pub status: storage_enums::AttemptStatus,
pub amount: i64,
/// amount + surcharge_amount + tax_amount
/// This field will always be derived before updating in the Database
pub net_amount: i64,
pub currency: Option<storage_enums::Currency>,
// pub auto_capture: Option<bool>,
pub save_to_locker: Option<bool>,
@ -230,6 +234,19 @@ pub struct PaymentAttemptNew {
pub unified_message: Option<String>,
}
impl PaymentAttemptNew {
/// returns amount + surcharge_amount + tax_amount
pub fn calculate_net_amount(&self) -> i64 {
self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0)
}
pub fn populate_derived_fields(self) -> Self {
let mut payment_attempt_new = self;
payment_attempt_new.net_amount = payment_attempt_new.calculate_net_amount();
payment_attempt_new
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PaymentAttemptUpdate {
Update {

View File

@ -63,6 +63,15 @@ pub struct PaymentAttempt {
pub encoded_data: Option<String>,
pub unified_code: Option<String>,
pub unified_message: Option<String>,
pub net_amount: Option<i64>,
}
impl PaymentAttempt {
pub fn get_or_calculate_net_amount(&self) -> i64 {
self.net_amount.unwrap_or(
self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0),
)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Queryable, Serialize, Deserialize)]
@ -128,6 +137,25 @@ pub struct PaymentAttemptNew {
pub encoded_data: Option<String>,
pub unified_code: Option<String>,
pub unified_message: Option<String>,
pub net_amount: Option<i64>,
}
impl PaymentAttemptNew {
/// returns amount + surcharge_amount + tax_amount
pub fn calculate_net_amount(&self) -> i64 {
self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0)
}
pub fn get_or_calculate_net_amount(&self) -> i64 {
self.net_amount
.unwrap_or_else(|| self.calculate_net_amount())
}
pub fn populate_derived_fields(self) -> Self {
let mut payment_attempt_new = self;
payment_attempt_new.net_amount = Some(payment_attempt_new.calculate_net_amount());
payment_attempt_new
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -279,6 +307,7 @@ pub enum PaymentAttemptUpdate {
#[diesel(table_name = payment_attempt)]
pub struct PaymentAttemptUpdateInternal {
amount: Option<i64>,
net_amount: Option<i64>,
currency: Option<storage_enums::Currency>,
status: Option<storage_enums::AttemptStatus>,
connector_transaction_id: Option<String>,
@ -316,10 +345,29 @@ pub struct PaymentAttemptUpdateInternal {
unified_message: Option<Option<String>>,
}
impl PaymentAttemptUpdateInternal {
pub fn populate_derived_fields(self, source: &PaymentAttempt) -> Self {
let mut update_internal = self;
update_internal.net_amount = Some(
update_internal.amount.unwrap_or(source.amount)
+ update_internal
.surcharge_amount
.or(source.surcharge_amount)
.unwrap_or(0)
+ update_internal
.tax_amount
.or(source.tax_amount)
.unwrap_or(0),
);
update_internal
}
}
impl PaymentAttemptUpdate {
pub fn apply_changeset(self, source: PaymentAttempt) -> PaymentAttempt {
let PaymentAttemptUpdateInternal {
amount,
net_amount,
currency,
status,
connector_transaction_id,
@ -355,9 +403,10 @@ impl PaymentAttemptUpdate {
encoded_data,
unified_code,
unified_message,
} = self.into();
} = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source);
PaymentAttempt {
amount: amount.unwrap_or(source.amount),
net_amount: net_amount.or(source.net_amount),
currency: currency.or(source.currency),
status: status.unwrap_or(source.status),
connector_transaction_id: connector_transaction_id.or(source.connector_transaction_id),

View File

@ -23,7 +23,7 @@ use crate::{
impl PaymentAttemptNew {
#[instrument(skip(conn))]
pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<PaymentAttempt> {
generics::generic_insert(conn, self).await
generics::generic_insert(conn, self.populate_derived_fields()).await
}
}
@ -44,7 +44,7 @@ impl PaymentAttempt {
dsl::attempt_id
.eq(self.attempt_id.to_owned())
.and(dsl::merchant_id.eq(self.merchant_id.to_owned())),
PaymentAttemptUpdateInternal::from(payment_attempt),
PaymentAttemptUpdateInternal::from(payment_attempt).populate_derived_fields(&self),
)
.await
{

View File

@ -643,6 +643,7 @@ diesel::table! {
unified_code -> Nullable<Varchar>,
#[max_length = 1024]
unified_message -> Nullable<Varchar>,
net_amount -> Nullable<Int8>,
}
}

View File

@ -62,6 +62,7 @@ pub struct PaymentAttemptBatchNew {
pub encoded_data: Option<String>,
pub unified_code: Option<String>,
pub unified_message: Option<String>,
pub net_amount: Option<i64>,
}
#[allow(dead_code)]
@ -114,6 +115,7 @@ impl PaymentAttemptBatchNew {
encoded_data: self.encoded_data,
unified_code: self.unified_code,
unified_message: self.unified_message,
net_amount: self.net_amount,
}
}
}

View File

@ -3089,8 +3089,8 @@ impl AttemptType {
error_message: None,
offer_amount: old_payment_attempt.offer_amount,
surcharge_amount: old_payment_attempt.surcharge_amount,
tax_amount: old_payment_attempt.tax_amount,
surcharge_amount: None,
tax_amount: None,
payment_method_id: None,
payment_method: None,
capture_method: old_payment_attempt.capture_method,
@ -3133,6 +3133,7 @@ impl AttemptType {
merchant_connector_id: None,
unified_code: None,
unified_message: None,
net_amount: old_payment_attempt.amount,
}
}

View File

@ -565,6 +565,7 @@ where
});
services::ApplicationResponse::JsonWithHeaders((
response
.set_net_amount(payment_attempt.net_amount)
.set_payment_id(Some(payment_attempt.payment_id))
.set_merchant_id(Some(payment_attempt.merchant_id))
.set_status(payment_intent.status)
@ -714,6 +715,7 @@ where
}
None => services::ApplicationResponse::JsonWithHeaders((
api::PaymentsResponse {
net_amount: payment_attempt.net_amount,
payment_id: Some(payment_attempt.payment_id),
merchant_id: Some(payment_attempt.merchant_id),
status: payment_intent.status,

View File

@ -97,7 +97,7 @@ impl PaymentAttemptInterface for MockDb {
#[allow(clippy::as_conversions)]
let id = payment_attempts.len() as i32;
let time = common_utils::date_time::now();
let payment_attempt = payment_attempt.populate_derived_fields();
let payment_attempt = PaymentAttempt {
id,
payment_id: payment_attempt.payment_id,
@ -105,6 +105,7 @@ impl PaymentAttemptInterface for MockDb {
attempt_id: payment_attempt.attempt_id,
status: payment_attempt.status,
amount: payment_attempt.amount,
net_amount: payment_attempt.net_amount,
currency: payment_attempt.currency,
save_to_locker: payment_attempt.save_to_locker,
connector: payment_attempt.connector,

View File

@ -331,6 +331,7 @@ impl<T: DatabaseStore> PaymentAttemptInterface for KVRouterStore<T> {
.await
}
MerchantStorageScheme::RedisKv => {
let payment_attempt = payment_attempt.populate_derived_fields();
let key = format!(
"mid_{}_pid_{}",
payment_attempt.merchant_id, payment_attempt.payment_id
@ -343,6 +344,7 @@ impl<T: DatabaseStore> PaymentAttemptInterface for KVRouterStore<T> {
attempt_id: payment_attempt.attempt_id.clone(),
status: payment_attempt.status,
amount: payment_attempt.amount,
net_amount: payment_attempt.net_amount,
currency: payment_attempt.currency,
save_to_locker: payment_attempt.save_to_locker,
connector: payment_attempt.connector.clone(),
@ -1035,6 +1037,7 @@ impl DataModelExt for PaymentAttempt {
attempt_id: self.attempt_id,
status: self.status,
amount: self.amount,
net_amount: Some(self.net_amount),
currency: self.currency,
save_to_locker: self.save_to_locker,
connector: self.connector,
@ -1081,6 +1084,7 @@ impl DataModelExt for PaymentAttempt {
fn from_storage_model(storage_model: Self::StorageModel) -> Self {
Self {
net_amount: storage_model.get_or_calculate_net_amount(),
id: storage_model.id,
payment_id: storage_model.payment_id,
merchant_id: storage_model.merchant_id,
@ -1139,6 +1143,7 @@ impl DataModelExt for PaymentAttemptNew {
fn to_storage_model(self) -> Self::StorageModel {
DieselPaymentAttemptNew {
net_amount: Some(self.net_amount),
payment_id: self.payment_id,
merchant_id: self.merchant_id,
attempt_id: self.attempt_id,
@ -1189,6 +1194,7 @@ impl DataModelExt for PaymentAttemptNew {
fn from_storage_model(storage_model: Self::StorageModel) -> Self {
Self {
net_amount: storage_model.get_or_calculate_net_amount(),
payment_id: storage_model.payment_id,
merchant_id: storage_model.merchant_id,
attempt_id: storage_model.attempt_id,

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
ALTER TABLE payment_attempt DROP COLUMN IF EXISTS net_amount;

View File

@ -0,0 +1,5 @@
ALTER TABLE payment_attempt
ADD COLUMN IF NOT EXISTS net_amount BIGINT;
-- Backfill
UPDATE payment_attempt pa
SET net_amount = pa.amount + COALESCE(pa.surcharge_amount, 0) + COALESCE(pa.tax_amount, 0);

View File

@ -10275,6 +10275,7 @@
"required": [
"status",
"amount",
"net_amount",
"currency",
"payment_method",
"attempt_count"
@ -10309,6 +10310,12 @@
"description": "The payment amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,",
"example": 100
},
"net_amount": {
"type": "integer",
"format": "int64",
"description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount,\nIf no surcharge_details, net_amount = amount",
"example": 110
},
"amount_capturable": {
"type": "integer",
"format": "int64",