From f116728d1cba458a1e184c2fdf5a1cc012430c35 Mon Sep 17 00:00:00 2001 From: ItsMeShashank Date: Thu, 5 Oct 2023 16:30:47 +0530 Subject: [PATCH] feat(router): add support for payment_type field in payment intent (#2448) --- crates/api_models/src/payment_methods.rs | 3 ++ crates/api_models/src/payments.rs | 5 +++ crates/common_enums/src/enums.rs | 26 ++++++++++++++- .../src/payments/payment_intent.rs | 2 ++ crates/diesel_models/src/enums.rs | 2 +- crates/diesel_models/src/payment_intent.rs | 2 ++ crates/diesel_models/src/schema.rs | 1 + .../router/src/core/payment_methods/cards.rs | 1 + crates/router/src/core/payments/helpers.rs | 24 ++++++++++++++ .../payments/operations/payment_create.rs | 5 +++ crates/router/src/openapi.rs | 1 + .../src/mock_db/payment_intent.rs | 1 + .../src/payments/payment_intent.rs | 5 +++ .../down.sql | 6 ++++ .../up.sql | 11 +++++++ openapi/openapi_spec.json | 33 +++++++++++++++++++ 16 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 migrations/2023-10-04-120026_add_payment_type_column_in_payment_intent/down.sql create mode 100644 migrations/2023-10-04-120026_add_payment_type_column_in_payment_intent/up.sql diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index e69b9fc82641..2e9cf4093300 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -538,6 +538,9 @@ pub struct PaymentMethodListResponse { #[schema(value_type = Option)] pub merchant_name: OptionalEncryptableName, + + #[schema(value_type = Option)] + pub payment_type: Option, } #[derive(Eq, PartialEq, Hash, Debug, serde::Deserialize, ToSchema)] diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 5f99e050f678..c7415c5d5457 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -298,6 +298,11 @@ pub struct PaymentsRequest { /// The business profile to use for this payment, if not passed the default business profile /// associated with the merchant account will be used. pub profile_id: Option, + + /// The type of the payment that differentiates between normal and various types of mandate payments + #[schema(value_type = Option)] + #[serde(default)] + pub payment_type: api_enums::PaymentType, } #[derive(Default, Debug, Clone, Copy)] diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index c693ea57fc58..28ced6458e54 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -12,7 +12,7 @@ pub mod diesel_exports { DbDisputeStage as DisputeStage, DbDisputeStatus as DisputeStatus, DbEventType as EventType, DbFutureUsage as FutureUsage, DbIntentStatus as IntentStatus, DbMandateStatus as MandateStatus, DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, - DbRefundStatus as RefundStatus, + DbPaymentType as PaymentType, DbRefundStatus as RefundStatus, }; } @@ -1065,6 +1065,30 @@ pub enum PaymentMethod { GiftCard, } +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "pg_enum")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum PaymentType { + #[default] + Normal, + NewMandate, + SetupMandate, + RecurringMandate, +} + #[derive( Clone, Copy, diff --git a/crates/data_models/src/payments/payment_intent.rs b/crates/data_models/src/payments/payment_intent.rs index b3bf8af8c36b..db182839b952 100644 --- a/crates/data_models/src/payments/payment_intent.rs +++ b/crates/data_models/src/payments/payment_intent.rs @@ -105,6 +105,7 @@ pub struct PaymentIntent { // Manual review can occur when the transaction is marked as risky by the frm_processor, payment processor or when there is underpayment/over payment incase of crypto payment pub merchant_decision: Option, pub payment_confirm_source: Option, + pub payment_type: Option, } #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] @@ -144,6 +145,7 @@ pub struct PaymentIntentNew { pub profile_id: Option, pub merchant_decision: Option, pub payment_confirm_source: Option, + pub payment_type: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs index 7a1c76ba41d1..93bffa467074 100644 --- a/crates/diesel_models/src/enums.rs +++ b/crates/diesel_models/src/enums.rs @@ -11,7 +11,7 @@ pub mod diesel_exports { DbMandateStatus as MandateStatus, DbMandateType as MandateType, DbMerchantStorageScheme as MerchantStorageScheme, DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, DbPaymentSource as PaymentSource, - DbPayoutStatus as PayoutStatus, DbPayoutType as PayoutType, + DbPaymentType as PaymentType, DbPayoutStatus as PayoutStatus, DbPayoutType as PayoutType, DbProcessTrackerStatus as ProcessTrackerStatus, DbReconStatus as ReconStatus, DbRefundStatus as RefundStatus, DbRefundType as RefundType, }; diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 1212c9d52424..a6de1137de2c 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -47,6 +47,7 @@ pub struct PaymentIntent { // Manual review can occur when the transaction is marked as risky by the frm_processor, payment processor or when there is underpayment/over payment incase of crypto payment pub merchant_decision: Option, pub payment_confirm_source: Option, + pub payment_type: Option, } #[derive( @@ -98,6 +99,7 @@ pub struct PaymentIntentNew { pub profile_id: Option, pub merchant_decision: Option, pub payment_confirm_source: Option, + pub payment_type: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 3a6e2cd2a936..f1ebcdb4346f 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -609,6 +609,7 @@ diesel::table! { #[max_length = 64] merchant_decision -> Nullable, payment_confirm_source -> Nullable, + payment_type -> Nullable, } } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index c0c0a3f00f1a..f5428592da1d 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -1281,6 +1281,7 @@ pub async fn list_payment_methods( redirect_url: merchant_account.return_url, merchant_name: merchant_account.merchant_name, payment_methods: payment_method_responses, + payment_type: payment_intent.as_ref().and_then(|pi| pi.payment_type), mandate_payment: payment_attempt.and_then(|inner| inner.mandate_details).map( |d| match d { data_models::mandates::MandateDataType::SingleUse(i) => { diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 60ebfd2bdf0f..3417e9db4e25 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -632,6 +632,27 @@ pub fn validate_card_data( Ok(()) } +pub fn infer_payment_type( + amount: &api::Amount, + mandate_type: Option<&api::MandateTransactionType>, +) -> api_enums::PaymentType { + match mandate_type { + Some(api::MandateTransactionType::NewMandateTransaction) => { + if let api::Amount::Value(_) = amount { + api_enums::PaymentType::NewMandate + } else { + api_enums::PaymentType::SetupMandate + } + } + + Some(api::MandateTransactionType::RecurringMandateTransaction) => { + api_enums::PaymentType::RecurringMandate + } + + None => api_enums::PaymentType::Normal, + } +} + pub fn validate_mandate( req: impl Into, is_confirm_operation: bool, @@ -2377,6 +2398,7 @@ mod tests { profile_id: None, merchant_decision: None, payment_confirm_source: None, + payment_type: Some(api_enums::PaymentType::Normal), }; let req_cs = Some("1".to_string()); let merchant_fulfillment_time = Some(900); @@ -2424,6 +2446,7 @@ mod tests { profile_id: None, merchant_decision: None, payment_confirm_source: None, + payment_type: Some(api_enums::PaymentType::Normal), }; let req_cs = Some("1".to_string()); let merchant_fulfillment_time = Some(10); @@ -2471,6 +2494,7 @@ mod tests { profile_id: None, merchant_decision: None, payment_confirm_source: None, + payment_type: Some(api_enums::PaymentType::Normal), }; let req_cs = Some("1".to_string()); let merchant_fulfillment_time = Some(10); diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 17d8d5f6c3d7..8971aaeed336 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -77,6 +77,8 @@ impl GetTracker, api::PaymentsRequest> for Pa core_utils::validate_and_get_business_profile(db, request.profile_id.as_ref(), merchant_id) .await?; + let payment_type = helpers::infer_payment_type(&amount, mandate_type.as_ref()); + let ( token, payment_method, @@ -145,6 +147,7 @@ impl GetTracker, api::PaymentsRequest> for Pa request, shipping_address.clone().map(|x| x.address_id), billing_address.clone().map(|x| x.address_id), + payment_type, attempt_id, state, ) @@ -603,6 +606,7 @@ impl PaymentCreate { request: &api::PaymentsRequest, shipping_address_id: Option, billing_address_id: Option, + payment_type: enums::PaymentType, active_attempt_id: String, state: &AppState, ) -> RouterResult { @@ -677,6 +681,7 @@ impl PaymentCreate { profile_id: Some(profile_id), merchant_decision: None, payment_confirm_source: None, + payment_type: Some(payment_type), }) } diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index 248dcd74a10f..a0f8643be3ec 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -140,6 +140,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::AcceptedCountries, api_models::admin::AcceptedCurrencies, api_models::enums::RoutingAlgorithm, + api_models::enums::PaymentType, api_models::enums::PaymentMethod, api_models::enums::PaymentMethodType, api_models::enums::ConnectorType, diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index 43d7207d452a..ff8ae44758a0 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -106,6 +106,7 @@ impl PaymentIntentInterface for MockDb { profile_id: new.profile_id, merchant_decision: new.merchant_decision, payment_confirm_source: new.payment_confirm_source, + payment_type: new.payment_type, }; payment_intents.push(payment_intent.clone()); Ok(payment_intent) diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 8352158be052..f30d26e30d7e 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -91,6 +91,7 @@ impl PaymentIntentInterface for KVRouterStore { profile_id: new.profile_id.clone(), merchant_decision: new.merchant_decision.clone(), payment_confirm_source: new.payment_confirm_source, + payment_type: new.payment_type, }; match self @@ -707,6 +708,7 @@ impl DataModelExt for PaymentIntentNew { profile_id: self.profile_id, merchant_decision: self.merchant_decision, payment_confirm_source: self.payment_confirm_source, + payment_type: self.payment_type, } } @@ -744,6 +746,7 @@ impl DataModelExt for PaymentIntentNew { profile_id: storage_model.profile_id, merchant_decision: storage_model.merchant_decision, payment_confirm_source: storage_model.payment_confirm_source, + payment_type: storage_model.payment_type, } } } @@ -786,6 +789,7 @@ impl DataModelExt for PaymentIntent { profile_id: self.profile_id, merchant_decision: self.merchant_decision, payment_confirm_source: self.payment_confirm_source, + payment_type: self.payment_type, } } @@ -824,6 +828,7 @@ impl DataModelExt for PaymentIntent { profile_id: storage_model.profile_id, merchant_decision: storage_model.merchant_decision, payment_confirm_source: storage_model.payment_confirm_source, + payment_type: storage_model.payment_type, } } } diff --git a/migrations/2023-10-04-120026_add_payment_type_column_in_payment_intent/down.sql b/migrations/2023-10-04-120026_add_payment_type_column_in_payment_intent/down.sql new file mode 100644 index 000000000000..74f43b254fd9 --- /dev/null +++ b/migrations/2023-10-04-120026_add_payment_type_column_in_payment_intent/down.sql @@ -0,0 +1,6 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE payment_intent +DROP COLUMN payment_type; + +DROP TYPE "PaymentType"; diff --git a/migrations/2023-10-04-120026_add_payment_type_column_in_payment_intent/up.sql b/migrations/2023-10-04-120026_add_payment_type_column_in_payment_intent/up.sql new file mode 100644 index 000000000000..622f24b48b3f --- /dev/null +++ b/migrations/2023-10-04-120026_add_payment_type_column_in_payment_intent/up.sql @@ -0,0 +1,11 @@ +-- Your SQL goes here + +CREATE TYPE "PaymentType" AS ENUM ( + 'normal', + 'new_mandate', + 'setup_mandate', + 'recurring_mandate' +); + +ALTER TABLE payment_intent +ADD COLUMN payment_type "PaymentType"; diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 7f1647d11328..d442127b0697 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -8080,6 +8080,14 @@ "merchant_name": { "type": "string", "nullable": true + }, + "payment_type": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentType" + } + ], + "nullable": true } } }, @@ -8329,6 +8337,15 @@ } } }, + "PaymentType": { + "type": "string", + "enum": [ + "normal", + "new_mandate", + "setup_mandate", + "recurring_mandate" + ] + }, "PaymentsCancelRequest": { "type": "object", "required": [ @@ -8731,6 +8748,14 @@ "type": "string", "description": "The business profile to use for this payment, if not passed the default business profile\nassociated with the merchant account will be used.", "nullable": true + }, + "payment_type": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentType" + } + ], + "nullable": true } } }, @@ -9070,6 +9095,14 @@ "type": "string", "description": "The business profile to use for this payment, if not passed the default business profile\nassociated with the merchant account will be used.", "nullable": true + }, + "payment_type": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentType" + } + ], + "nullable": true } } },