diff --git a/crates/api_models/src/disputes.rs b/crates/api_models/src/disputes.rs index 4ddb50af0f2a..c5448b0dfc6d 100644 --- a/crates/api_models/src/disputes.rs +++ b/crates/api_models/src/disputes.rs @@ -6,8 +6,8 @@ use serde::de::Error; use time::PrimitiveDateTime; use utoipa::ToSchema; -use super::enums::{DisputeStage, DisputeStatus}; -use crate::{admin::MerchantConnectorInfo, enums, files}; +use super::enums::{DisputeStage, DisputeStatus, Currency}; +use crate::{admin::MerchantConnectorInfo, files}; #[derive(Clone, Debug, Serialize, ToSchema, Eq, PartialEq)] pub struct DisputeResponse { @@ -21,7 +21,8 @@ pub struct DisputeResponse { /// The dispute amount pub amount: String, /// The three-letter ISO currency code - pub currency: String, + #[schema(value_type = Currency)] + pub currency: Currency, /// Stage of the dispute pub dispute_stage: DisputeStage, /// Status of the dispute @@ -137,7 +138,7 @@ pub struct DisputeListGetConstraints { pub connector: Option>, /// The comma separated list of currencies of the disputes #[serde(default, deserialize_with = "parse_comma_separated")] - pub currency: Option>, + pub currency: Option>, /// The merchant connector id to filter the disputes list pub merchant_connector_id: Option, /// The time range for which objects are needed. TimeRange has two fields start_time and end_time from which objects can be filtered as per required scenarios (created_at, time less than, greater than etc). @@ -150,7 +151,7 @@ pub struct DisputeListFilters { /// The map of available connector filters, where the key is the connector name and the value is a list of MerchantConnectorInfo instances pub connector: HashMap>, /// The list of available currency filters - pub currency: Vec, + pub currency: Vec, /// The list of available dispute status filters pub dispute_status: Vec, /// The list of available dispute stage filters diff --git a/crates/diesel_models/src/dispute.rs b/crates/diesel_models/src/dispute.rs index 130e46aa9cc1..20544b9ac2fc 100644 --- a/crates/diesel_models/src/dispute.rs +++ b/crates/diesel_models/src/dispute.rs @@ -31,6 +31,7 @@ pub struct DisputeNew { pub merchant_connector_id: Option, pub dispute_amount: i64, pub organization_id: common_utils::id_type::OrganizationId, + pub dispute_currency: storage_enums::Currency, } #[derive(Clone, Debug, PartialEq, Serialize, Identifiable, Queryable, Selectable)] @@ -61,6 +62,7 @@ pub struct Dispute { pub merchant_connector_id: Option, pub dispute_amount: i64, pub organization_id: common_utils::id_type::OrganizationId, + pub dispute_currency: storage_enums::Currency, } #[derive(Debug)] diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 19a0763d770c..c2541c8fc815 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -351,7 +351,6 @@ diesel::table! { dispute_id -> Varchar, #[max_length = 255] amount -> Varchar, - #[max_length = 255] currency -> Varchar, dispute_stage -> DisputeStage, dispute_status -> DisputeStatus, @@ -384,6 +383,7 @@ diesel::table! { dispute_amount -> Int8, #[max_length = 32] organization_id -> Varchar, + dispute_currency -> Currency, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index aa7f3f7232c6..020a15cd65fb 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -396,6 +396,7 @@ diesel::table! { dispute_amount -> Int8, #[max_length = 32] organization_id -> Varchar, + dispute_currency -> Currency, } } diff --git a/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs index bb3bc3990772..040a1422e0e0 100644 --- a/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs @@ -829,7 +829,7 @@ pub struct AirwallexObjectData { pub struct AirwallexDisputeObject { pub payment_intent_id: String, pub dispute_amount: i64, - pub dispute_currency: String, + pub dispute_currency: enums::Currency, pub stage: AirwallexDisputeStage, pub dispute_id: String, pub dispute_reason_type: Option, diff --git a/crates/hyperswitch_interfaces/src/disputes.rs b/crates/hyperswitch_interfaces/src/disputes.rs index acc7b56e906e..55a6d13c91d1 100644 --- a/crates/hyperswitch_interfaces/src/disputes.rs +++ b/crates/hyperswitch_interfaces/src/disputes.rs @@ -8,7 +8,7 @@ pub struct DisputePayload { /// amount pub amount: String, /// currency - pub currency: String, + pub currency: common_enums::enums::Currency, /// dispute_stage pub dispute_stage: common_enums::enums::DisputeStage, /// connector_status diff --git a/crates/router/src/compatibility/stripe/webhooks.rs b/crates/router/src/compatibility/stripe/webhooks.rs index ddc291e6b139..999f2418a860 100644 --- a/crates/router/src/compatibility/stripe/webhooks.rs +++ b/crates/router/src/compatibility/stripe/webhooks.rs @@ -1,7 +1,7 @@ #[cfg(feature = "payouts")] use api_models::payouts as payout_models; use api_models::{ - enums::{DisputeStatus, MandateStatus}, + enums::{DisputeStatus, MandateStatus, Currency}, webhooks::{self as api}, }; #[cfg(feature = "payouts")] @@ -93,7 +93,7 @@ pub enum StripeWebhookObject { pub struct StripeDisputeResponse { pub id: String, pub amount: String, - pub currency: String, + pub currency: Currency, pub payment_intent: common_utils::id_type::PaymentId, pub reason: Option, pub status: StripeDisputeStatus, diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index 7b491674e92a..c9ccd8a9c629 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -1895,7 +1895,7 @@ impl api::IncomingWebhook for Adyen { .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; Ok(api::disputes::DisputePayload { amount: notif.amount.value.to_string(), - currency: notif.amount.currency.to_string(), + currency: notif.amount.currency, dispute_stage: api_models::enums::DisputeStage::from(notif.event_code.clone()), connector_dispute_id: notif.psp_reference, connector_reason: notif.reason, diff --git a/crates/router/src/connector/bluesnap.rs b/crates/router/src/connector/bluesnap.rs index 468f68dc340c..181f1dbf3d7e 100644 --- a/crates/router/src/connector/bluesnap.rs +++ b/crates/router/src/connector/bluesnap.rs @@ -1149,7 +1149,7 @@ impl api::IncomingWebhook for Bluesnap { dispute_details.invoice_charge_amount.abs().to_string(), dispute_details.currency, )?, - currency: dispute_details.currency.to_string(), + currency: dispute_details.currency, dispute_stage: api_models::enums::DisputeStage::Dispute, connector_dispute_id: dispute_details.reversal_ref_num, connector_reason: dispute_details.reversal_reason, diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs index 85b310397b26..2451a7318854 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/router/src/connector/braintree.rs @@ -1,5 +1,4 @@ pub mod transformers; -use std::str::FromStr; use api_models::webhooks::IncomingWebhookEvent; use base64::Engine; @@ -999,12 +998,10 @@ impl api::IncomingWebhook for Braintree { match response.dispute { Some(dispute_data) => { - let currency = enums::Currency::from_str(dispute_data.currency_iso_code.as_str()) - .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; Ok(api::disputes::DisputePayload { amount: connector_utils::to_currency_lower_unit( dispute_data.amount_disputed.to_string(), - currency, + dispute_data.currency_iso_code, )?, currency: dispute_data.currency_iso_code, dispute_stage: transformers::get_dispute_stage(dispute_data.kind.as_str())?, diff --git a/crates/router/src/connector/braintree/transformers.rs b/crates/router/src/connector/braintree/transformers.rs index bcef8124c65c..24c6118e44a8 100644 --- a/crates/router/src/connector/braintree/transformers.rs +++ b/crates/router/src/connector/braintree/transformers.rs @@ -1766,7 +1766,7 @@ pub struct BraintreeDisputeData { pub amount_won: Option, pub case_number: Option, pub chargeback_protection_level: Option, - pub currency_iso_code: String, + pub currency_iso_code: enums::Currency, #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub created_at: Option, pub evidence: Option, diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index 1f6b737367d0..0d4d82010034 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -1252,7 +1252,7 @@ pub struct CheckoutDisputeWebhookData { pub payment_id: Option, pub action_id: Option, pub amount: i32, - pub currency: String, + pub currency: enums::Currency, #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub evidence_required_by: Option, pub reason_code: Option, diff --git a/crates/router/src/connector/payme.rs b/crates/router/src/connector/payme.rs index 6bf26b4f60bd..bc5524e5c8fc 100644 --- a/crates/router/src/connector/payme.rs +++ b/crates/router/src/connector/payme.rs @@ -1280,7 +1280,7 @@ impl api::IncomingWebhook for Payme { Ok(api::disputes::DisputePayload { amount: webhook_object.price.to_string(), - currency: webhook_object.currency.to_string(), + currency: webhook_object.currency, dispute_stage: api_models::enums::DisputeStage::Dispute, connector_dispute_id: webhook_object.payme_transaction_id, connector_reason: None, diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index 48a35c78cc25..e79912f61ff2 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -2001,7 +2001,7 @@ impl api::IncomingWebhook for Paypal { payload.dispute_amount.value.get_amount_as_string(), payload.dispute_amount.currency_code, )?, - currency: payload.dispute_amount.currency_code.to_string(), + currency: payload.dispute_amount.currency_code, dispute_stage: api_models::enums::DisputeStage::from( payload.dispute_life_cycle_stage.clone(), ), diff --git a/crates/router/src/connector/rapyd.rs b/crates/router/src/connector/rapyd.rs index 196eb4ce73dd..64c3fcdf5a1b 100644 --- a/crates/router/src/connector/rapyd.rs +++ b/crates/router/src/connector/rapyd.rs @@ -974,7 +974,7 @@ impl api::IncomingWebhook for Rapyd { }?; Ok(api::disputes::DisputePayload { amount: webhook_dispute_data.amount.to_string(), - currency: webhook_dispute_data.currency.to_string(), + currency: webhook_dispute_data.currency, dispute_stage: api_models::enums::DisputeStage::Dispute, connector_dispute_id: webhook_dispute_data.token, connector_reason: Some(webhook_dispute_data.dispute_reason_description), diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 32fa8da0320e..6d9a4f1c459d 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -3640,7 +3640,8 @@ pub struct WebhookEventObjectData { pub id: String, pub object: WebhookEventObjectType, pub amount: Option, - pub currency: String, + #[serde(default, deserialize_with = "connector_util::convert_uppercase")] + pub currency: enums::Currency, pub payment_intent: Option, pub client_secret: Option>, pub reason: Option, diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 8312b3e26593..dafca256529e 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -1790,7 +1790,7 @@ pub struct WebhookReferences { #[serde(rename_all = "PascalCase")] pub struct WebhookAmount { pub amount: f64, - pub currency: String, + pub currency: enums::Currency, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 687edd5aa238..6b6ee33f642c 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -28,7 +28,7 @@ use hyperswitch_domain_models::{ SyncIntegrityObject, }, }; -use masking::{ExposeInterface, Secret}; +use masking::{ExposeInterface, Secret, Deserialize}; use once_cell::sync::Lazy; use regex::Regex; use serde::Serializer; @@ -3143,3 +3143,14 @@ impl NetworkTokenData for domain::NetworkTokenData { Secret::new(year) } } + +pub fn convert_uppercase<'de, D, T>(v: D) -> Result +where + D: serde::Deserializer<'de>, + T: FromStr, + ::Err: std::fmt::Debug + std::fmt::Display + std::error::Error, +{ + use serde::de::Error; + let output = <&str>::deserialize(v)?; + output.to_uppercase().parse::().map_err(D::Error::custom) +} \ No newline at end of file diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index b049ffdb1298..3f7830b78828 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -942,7 +942,7 @@ async fn get_or_update_dispute_object( let new_dispute = diesel_models::dispute::DisputeNew { dispute_id, amount: dispute_details.amount.clone(), - currency: dispute_details.currency, + currency: dispute_details.currency.to_string(), dispute_stage: dispute_details.dispute_stage, dispute_status: common_enums::DisputeStatus::foreign_try_from(event_type) .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) @@ -963,6 +963,7 @@ async fn get_or_update_dispute_object( merchant_connector_id: payment_attempt.merchant_connector_id.clone(), dispute_amount: dispute_details.amount.parse::().unwrap_or(0), organization_id: organization_id.clone(), + dispute_currency: dispute_details.currency, }; state .store diff --git a/crates/router/src/db/dispute.rs b/crates/router/src/db/dispute.rs index d7f4af63015e..519d06ddce83 100644 --- a/crates/router/src/db/dispute.rs +++ b/crates/router/src/db/dispute.rs @@ -199,6 +199,7 @@ impl DisputeInterface for MockDb { merchant_connector_id: dispute.merchant_connector_id, dispute_amount: dispute.dispute_amount, organization_id: dispute.organization_id, + dispute_currency: dispute.dispute_currency, }; locked_disputes.push(new_dispute.clone()); @@ -310,7 +311,7 @@ impl DisputeInterface for MockDb { .map_or(true, |currencies| { currencies .iter() - .any(|currency| dispute.currency.as_str() == currency.to_string()) + .any(|currency| &dispute.dispute_currency == currency) }) && dispute_constraints .time_range @@ -502,6 +503,7 @@ mod tests { merchant_connector_id: None, dispute_amount: 1040, organization_id: common_utils::id_type::OrganizationId::default(), + dispute_currency: common_enums::Currency::default(), } } diff --git a/crates/router/src/services/kafka/dispute.rs b/crates/router/src/services/kafka/dispute.rs index 2afb41cc8673..d45cea065568 100644 --- a/crates/router/src/services/kafka/dispute.rs +++ b/crates/router/src/services/kafka/dispute.rs @@ -9,7 +9,7 @@ use crate::types::storage::dispute::Dispute; pub struct KafkaDispute<'a> { pub dispute_id: &'a String, pub dispute_amount: i64, - pub currency: &'a String, + pub currency: &'a storage_enums::Currency, pub dispute_stage: &'a storage_enums::DisputeStage, pub dispute_status: &'a storage_enums::DisputeStatus, pub payment_id: &'a id_type::PaymentId, @@ -41,7 +41,7 @@ impl<'a> KafkaDispute<'a> { Self { dispute_id: &dispute.dispute_id, dispute_amount: dispute.amount.parse::().unwrap_or_default(), - currency: &dispute.currency, + currency: &dispute.dispute_currency, dispute_stage: &dispute.dispute_stage, dispute_status: &dispute.dispute_status, payment_id: &dispute.payment_id, diff --git a/crates/router/src/services/kafka/dispute_event.rs b/crates/router/src/services/kafka/dispute_event.rs index 057a060decda..899175ee6b78 100644 --- a/crates/router/src/services/kafka/dispute_event.rs +++ b/crates/router/src/services/kafka/dispute_event.rs @@ -9,7 +9,7 @@ use crate::types::storage::dispute::Dispute; pub struct KafkaDisputeEvent<'a> { pub dispute_id: &'a String, pub dispute_amount: i64, - pub currency: &'a String, + pub currency: &'a storage_enums::Currency, pub dispute_stage: &'a storage_enums::DisputeStage, pub dispute_status: &'a storage_enums::DisputeStatus, pub payment_id: &'a common_utils::id_type::PaymentId, @@ -41,7 +41,7 @@ impl<'a> KafkaDisputeEvent<'a> { Self { dispute_id: &dispute.dispute_id, dispute_amount: dispute.amount.parse::().unwrap_or_default(), - currency: &dispute.currency, + currency: &dispute.dispute_currency, dispute_stage: &dispute.dispute_stage, dispute_status: &dispute.dispute_status, payment_id: &dispute.payment_id, diff --git a/crates/router/src/types/storage/dispute.rs b/crates/router/src/types/storage/dispute.rs index 28a9573b3330..2d42aaa0a8a9 100644 --- a/crates/router/src/types/storage/dispute.rs +++ b/crates/router/src/types/storage/dispute.rs @@ -83,14 +83,8 @@ impl DisputeDbExt for Dispute { if let Some(dispute_status) = &dispute_list_constraints.dispute_status { filter = filter.filter(dsl::dispute_status.eq_any(dispute_status.clone())); } - if let Some(currency_list) = &dispute_list_constraints.currency { - let currency: Vec = currency_list - .iter() - .map(|currency| currency.to_string()) - .collect(); - - filter = filter.filter(dsl::currency.eq_any(currency)); + filter = filter.filter(dsl::dispute_currency.eq_any(currency_list.clone())); } if let Some(merchant_connector_id) = &dispute_list_constraints.merchant_connector_id { filter = filter.filter(dsl::merchant_connector_id.eq(merchant_connector_id.clone())) diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 8ba6783372b6..23327b1f50b5 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -886,7 +886,7 @@ impl ForeignFrom for api_models::disputes::DisputeResponse { payment_id: dispute.payment_id, attempt_id: dispute.attempt_id, amount: dispute.amount, - currency: dispute.currency, + currency: dispute.dispute_currency, dispute_stage: dispute.dispute_stage, dispute_status: dispute.dispute_status, connector: dispute.connector, diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 4f859d8e56a9..31dfeec22eab 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -435,6 +435,9 @@ pub async fn generate_sample_data( merchant_connector_id: payment_attempt.merchant_connector_id.clone(), dispute_amount: amount * 100, organization_id: org_id.clone(), + dispute_currency: payment_intent + .currency + .unwrap_or_default(), }) } else { None diff --git a/migrations/2024-10-28-125949_change_currency_type_in_dispute/down.sql b/migrations/2024-10-28-125949_change_currency_type_in_dispute/down.sql new file mode 100644 index 000000000000..052223ca0ae8 --- /dev/null +++ b/migrations/2024-10-28-125949_change_currency_type_in_dispute/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE dispute DROP COLUMN IF EXISTS dispute_currency; \ No newline at end of file diff --git a/migrations/2024-10-28-125949_change_currency_type_in_dispute/up.sql b/migrations/2024-10-28-125949_change_currency_type_in_dispute/up.sql new file mode 100644 index 000000000000..c73d7c550887 --- /dev/null +++ b/migrations/2024-10-28-125949_change_currency_type_in_dispute/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE dispute ADD COLUMN IF NOT EXISTS dispute_currency TYPE "Currency" USING currency::"Currency"; -- Migration query to be run after deployment before running this query \ No newline at end of file