mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(mandate): added amount based validation and database fields (#99)
This commit is contained in:
		| @ -146,6 +146,8 @@ pub(crate) enum ErrorCode { | |||||||
|         current_value: String, |         current_value: String, | ||||||
|         states: String, |         states: String, | ||||||
|     }, |     }, | ||||||
|  |     #[error(error_type = StripeErrorType::InvalidRequestError, code = "", message = "The mandate information is invalid. {message}")] | ||||||
|  |     PaymentIntentMandateInvalid { message: String }, | ||||||
|     // TODO: Some day implement all stripe error codes https://stripe.com/docs/error-codes |     // TODO: Some day implement all stripe error codes https://stripe.com/docs/error-codes | ||||||
|     // AccountCountryInvalidAddress, |     // AccountCountryInvalidAddress, | ||||||
|     // AccountErrorCountryChangeRequiresAdditionalSteps, |     // AccountErrorCountryChangeRequiresAdditionalSteps, | ||||||
| @ -227,7 +229,6 @@ pub(crate) enum ErrorCode { | |||||||
|     // PaymentIntentIncompatiblePaymentMethod, |     // PaymentIntentIncompatiblePaymentMethod, | ||||||
|     // PaymentIntentInvalidParameter, |     // PaymentIntentInvalidParameter, | ||||||
|     // PaymentIntentKonbiniRejectedConfirmationNumber, |     // PaymentIntentKonbiniRejectedConfirmationNumber, | ||||||
|     // PaymentIntentMandateInvalid, |  | ||||||
|     // PaymentIntentPaymentAttemptExpired, |     // PaymentIntentPaymentAttemptExpired, | ||||||
|     // PaymentIntentUnexpectedState, |     // PaymentIntentUnexpectedState, | ||||||
|     // PaymentMethodBankAccountAlreadyVerified, |     // PaymentMethodBankAccountAlreadyVerified, | ||||||
| @ -350,6 +351,9 @@ impl From<ApiErrorResponse> for ErrorCode { | |||||||
|                 ErrorCode::MerchantConnectorAccountNotFound |                 ErrorCode::MerchantConnectorAccountNotFound | ||||||
|             } |             } | ||||||
|             ApiErrorResponse::MandateNotFound => ErrorCode::MandateNotFound, |             ApiErrorResponse::MandateNotFound => ErrorCode::MandateNotFound, | ||||||
|  |             ApiErrorResponse::MandateValidationFailed { reason } => { | ||||||
|  |                 ErrorCode::PaymentIntentMandateInvalid { message: reason } | ||||||
|  |             } | ||||||
|             ApiErrorResponse::ReturnUrlUnavailable => ErrorCode::ReturnUrlUnavailable, |             ApiErrorResponse::ReturnUrlUnavailable => ErrorCode::ReturnUrlUnavailable, | ||||||
|             ApiErrorResponse::DuplicateMerchantAccount => ErrorCode::DuplicateMerchantAccount, |             ApiErrorResponse::DuplicateMerchantAccount => ErrorCode::DuplicateMerchantAccount, | ||||||
|             ApiErrorResponse::DuplicateMerchantConnectorAccount => { |             ApiErrorResponse::DuplicateMerchantConnectorAccount => { | ||||||
| @ -427,6 +431,7 @@ impl actix_web::ResponseError for ErrorCode { | |||||||
|             | ErrorCode::SuccessfulPaymentNotFound |             | ErrorCode::SuccessfulPaymentNotFound | ||||||
|             | ErrorCode::AddressNotFound |             | ErrorCode::AddressNotFound | ||||||
|             | ErrorCode::ResourceIdNotFound |             | ErrorCode::ResourceIdNotFound | ||||||
|  |             | ErrorCode::PaymentIntentMandateInvalid { .. } | ||||||
|             | ErrorCode::PaymentIntentUnexpectedState { .. } => StatusCode::BAD_REQUEST, |             | ErrorCode::PaymentIntentUnexpectedState { .. } => StatusCode::BAD_REQUEST, | ||||||
|             ErrorCode::RefundFailed | ErrorCode::InternalServerError => { |             ErrorCode::RefundFailed | ErrorCode::InternalServerError => { | ||||||
|                 StatusCode::INTERNAL_SERVER_ERROR |                 StatusCode::INTERNAL_SERVER_ERROR | ||||||
|  | |||||||
| @ -120,6 +120,8 @@ pub enum ApiErrorResponse { | |||||||
|     SuccessfulPaymentNotFound, |     SuccessfulPaymentNotFound, | ||||||
|     #[error(error_type = ErrorType::ObjectNotFound, code = "RE_05", message = "Address does not exist in our records.")] |     #[error(error_type = ErrorType::ObjectNotFound, code = "RE_05", message = "Address does not exist in our records.")] | ||||||
|     AddressNotFound, |     AddressNotFound, | ||||||
|  |     #[error(error_type = ErrorType::ValidationError, code = "RE_03", message = "Mandate Validation Failed" )] | ||||||
|  |     MandateValidationFailed { reason: String }, | ||||||
|     #[error(error_type = ErrorType::ServerNotAvailable, code = "IR_00", message = "This API is under development and will be made available soon.")] |     #[error(error_type = ErrorType::ServerNotAvailable, code = "IR_00", message = "This API is under development and will be made available soon.")] | ||||||
|     NotImplemented, |     NotImplemented, | ||||||
| } | } | ||||||
| @ -159,7 +161,8 @@ impl actix_web::ResponseError for ApiErrorResponse { | |||||||
|             | ApiErrorResponse::CardExpired { .. } |             | ApiErrorResponse::CardExpired { .. } | ||||||
|             | ApiErrorResponse::RefundFailed { .. } |             | ApiErrorResponse::RefundFailed { .. } | ||||||
|             | ApiErrorResponse::VerificationFailed { .. } |             | ApiErrorResponse::VerificationFailed { .. } | ||||||
|             | ApiErrorResponse::PaymentUnexpectedState { .. } => StatusCode::BAD_REQUEST, // 400 |             | ApiErrorResponse::PaymentUnexpectedState { .. } | ||||||
|  |             | ApiErrorResponse::MandateValidationFailed { .. } => StatusCode::BAD_REQUEST, // 400 | ||||||
|  |  | ||||||
|             ApiErrorResponse::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, // 500 |             ApiErrorResponse::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, // 500 | ||||||
|             ApiErrorResponse::DuplicateRefundRequest => StatusCode::BAD_REQUEST,        // 400 |             ApiErrorResponse::DuplicateRefundRequest => StatusCode::BAD_REQUEST,        // 400 | ||||||
|  | |||||||
| @ -1,10 +1,8 @@ | |||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use error_stack::ResultExt; | use error_stack::ResultExt; | ||||||
| use masking::Secret; |  | ||||||
|  |  | ||||||
| use super::{ConstructFlowSpecificData, Feature}; | use super::{ConstructFlowSpecificData, Feature}; | ||||||
| use crate::{ | use crate::{ | ||||||
|     consts, |  | ||||||
|     core::{ |     core::{ | ||||||
|         errors::{self, ConnectorErrorExt, RouterResult, StorageErrorExt}, |         errors::{self, ConnectorErrorExt, RouterResult, StorageErrorExt}, | ||||||
|         payments::{self, helpers, transformers, PaymentData}, |         payments::{self, helpers, transformers, PaymentData}, | ||||||
| @ -17,7 +15,6 @@ use crate::{ | |||||||
|         storage::{self, enums as storage_enums}, |         storage::{self, enums as storage_enums}, | ||||||
|         PaymentsAuthorizeData, PaymentsAuthorizeRouterData, PaymentsResponseData, |         PaymentsAuthorizeData, PaymentsAuthorizeRouterData, PaymentsResponseData, | ||||||
|     }, |     }, | ||||||
|     utils, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #[async_trait] | #[async_trait] | ||||||
| @ -124,7 +121,20 @@ impl PaymentsAuthorizeRouterData { | |||||||
|                                 ) |                                 ) | ||||||
|                                 .await |                                 .await | ||||||
|                                 .change_context(errors::ApiErrorResponse::MandateNotFound), |                                 .change_context(errors::ApiErrorResponse::MandateNotFound), | ||||||
|                             storage_enums::MandateType::MultiUse => Ok(mandate), |                             storage_enums::MandateType::MultiUse => state | ||||||
|  |                                 .store | ||||||
|  |                                 .update_mandate_by_merchant_id_mandate_id( | ||||||
|  |                                     &resp.merchant_id, | ||||||
|  |                                     mandate_id, | ||||||
|  |                                     storage::MandateUpdate::CaptureAmountUpdate { | ||||||
|  |                                         amount_captured: Some( | ||||||
|  |                                             mandate.amount_captured.unwrap_or(0) | ||||||
|  |                                                 + self.request.amount, | ||||||
|  |                                         ), | ||||||
|  |                                     }, | ||||||
|  |                                 ) | ||||||
|  |                                 .await | ||||||
|  |                                 .change_context(errors::ApiErrorResponse::MandateNotFound), | ||||||
|                         }?; |                         }?; | ||||||
|  |  | ||||||
|                         resp.payment_method_id = Some(mandate.payment_method_id); |                         resp.payment_method_id = Some(mandate.payment_method_id); | ||||||
| @ -142,9 +152,13 @@ impl PaymentsAuthorizeRouterData { | |||||||
|                             .payment_method_id; |                             .payment_method_id; | ||||||
|  |  | ||||||
|                             resp.payment_method_id = Some(payment_method_id.clone()); |                             resp.payment_method_id = Some(payment_method_id.clone()); | ||||||
|                             if let Some(new_mandate_data) = |                             if let Some(new_mandate_data) = helpers::generate_mandate( | ||||||
|                                 self.generate_mandate(maybe_customer, payment_method_id) |                                 self.merchant_id.clone(), | ||||||
|                             { |                                 self.connector.clone(), | ||||||
|  |                                 None, | ||||||
|  |                                 maybe_customer, | ||||||
|  |                                 payment_method_id, | ||||||
|  |                             ) { | ||||||
|                                 resp.request.mandate_id = Some(new_mandate_data.mandate_id.clone()); |                                 resp.request.mandate_id = Some(new_mandate_data.mandate_id.clone()); | ||||||
|                                 state.store.insert_mandate(new_mandate_data).await.map_err( |                                 state.store.insert_mandate(new_mandate_data).await.map_err( | ||||||
|                                     |err| { |                                     |err| { | ||||||
| @ -163,44 +177,4 @@ impl PaymentsAuthorizeRouterData { | |||||||
|             _ => Ok(self.clone()), |             _ => Ok(self.clone()), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn generate_mandate( |  | ||||||
|         &self, |  | ||||||
|         customer: &Option<storage::Customer>, |  | ||||||
|         payment_method_id: String, |  | ||||||
|     ) -> Option<storage::MandateNew> { |  | ||||||
|         match (self.request.setup_mandate_details.clone(), customer) { |  | ||||||
|             (Some(data), Some(cus)) => { |  | ||||||
|                 let mandate_id = utils::generate_id(consts::ID_LENGTH, "man"); |  | ||||||
|  |  | ||||||
|                 // The construction of the mandate new must be visible |  | ||||||
|                 let mut new_mandate = storage::MandateNew::default(); |  | ||||||
|  |  | ||||||
|                 new_mandate |  | ||||||
|                     .set_mandate_id(mandate_id) |  | ||||||
|                     .set_customer_id(cus.customer_id.clone()) |  | ||||||
|                     .set_merchant_id(self.merchant_id.clone()) |  | ||||||
|                     .set_payment_method_id(payment_method_id) |  | ||||||
|                     .set_mandate_status(storage_enums::MandateStatus::Active) |  | ||||||
|                     .set_customer_ip_address( |  | ||||||
|                         data.customer_acceptance.get_ip_address().map(Secret::new), |  | ||||||
|                     ) |  | ||||||
|                     .set_customer_user_agent(data.customer_acceptance.get_user_agent()) |  | ||||||
|                     .set_customer_accepted_at(Some(data.customer_acceptance.get_accepted_at())); |  | ||||||
|  |  | ||||||
|                 Some(match data.mandate_type { |  | ||||||
|                     api::MandateType::SingleUse(data) => new_mandate |  | ||||||
|                         .set_single_use_amount(Some(data.amount)) |  | ||||||
|                         .set_single_use_currency(Some(data.currency)) |  | ||||||
|                         .set_mandate_type(storage_enums::MandateType::SingleUse) |  | ||||||
|                         .to_owned(), |  | ||||||
|  |  | ||||||
|                     api::MandateType::MultiUse => new_mandate |  | ||||||
|                         .set_mandate_type(storage_enums::MandateType::MultiUse) |  | ||||||
|                         .to_owned(), |  | ||||||
|                 }) |  | ||||||
|             } |  | ||||||
|             (_, _) => None, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,10 +1,8 @@ | |||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use error_stack::ResultExt; | use error_stack::ResultExt; | ||||||
| use masking::Secret; |  | ||||||
|  |  | ||||||
| use super::{ConstructFlowSpecificData, Feature}; | use super::{ConstructFlowSpecificData, Feature}; | ||||||
| use crate::{ | use crate::{ | ||||||
|     consts, |  | ||||||
|     core::{ |     core::{ | ||||||
|         errors::{self, ConnectorErrorExt, RouterResult, StorageErrorExt}, |         errors::{self, ConnectorErrorExt, RouterResult, StorageErrorExt}, | ||||||
|         payments::{self, helpers, transformers, PaymentData}, |         payments::{self, helpers, transformers, PaymentData}, | ||||||
| @ -15,7 +13,6 @@ use crate::{ | |||||||
|         self, api, |         self, api, | ||||||
|         storage::{self, enums}, |         storage::{self, enums}, | ||||||
|     }, |     }, | ||||||
|     utils, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #[async_trait] | #[async_trait] | ||||||
| @ -108,8 +105,9 @@ impl types::VerifyRouterData { | |||||||
|                             .payment_method_id; |                             .payment_method_id; | ||||||
|  |  | ||||||
|                             resp.payment_method_id = Some(payment_method_id.clone()); |                             resp.payment_method_id = Some(payment_method_id.clone()); | ||||||
|                             if let Some(new_mandate_data) = generate_mandate( |                             if let Some(new_mandate_data) = helpers::generate_mandate( | ||||||
|                                 self.merchant_id.clone(), |                                 self.merchant_id.clone(), | ||||||
|  |                                 self.connector.clone(), | ||||||
|                                 self.request.setup_mandate_details.clone(), |                                 self.request.setup_mandate_details.clone(), | ||||||
|                                 maybe_customer, |                                 maybe_customer, | ||||||
|                                 payment_method_id, |                                 payment_method_id, | ||||||
| @ -132,29 +130,3 @@ impl types::VerifyRouterData { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fn generate_mandate( |  | ||||||
|     merchant_id: String, |  | ||||||
|     setup_mandate_details: Option<api::MandateData>, |  | ||||||
|     customer: &Option<storage::Customer>, |  | ||||||
|     payment_method_id: String, |  | ||||||
| ) -> Option<storage::MandateNew> { |  | ||||||
|     match (setup_mandate_details, customer) { |  | ||||||
|         (Some(data), Some(cus)) => { |  | ||||||
|             let mandate_id = utils::generate_id(consts::ID_LENGTH, "man"); |  | ||||||
|             Some(storage::MandateNew { |  | ||||||
|                 mandate_id, |  | ||||||
|                 customer_id: cus.customer_id.clone(), |  | ||||||
|                 merchant_id, |  | ||||||
|                 payment_method_id, |  | ||||||
|                 mandate_status: enums::MandateStatus::Active, |  | ||||||
|                 mandate_type: enums::MandateType::MultiUse, |  | ||||||
|                 customer_ip_address: data.customer_acceptance.get_ip_address().map(Secret::new), |  | ||||||
|                 customer_user_agent: data.customer_acceptance.get_user_agent(), |  | ||||||
|                 customer_accepted_at: Some(data.customer_acceptance.get_accepted_at()), |  | ||||||
|                 ..Default::default() |  | ||||||
|             }) |  | ||||||
|         } |  | ||||||
|         (_, _) => None, |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -141,10 +141,7 @@ pub async fn get_token_for_recurring_mandate( | |||||||
|         .await |         .await | ||||||
|         .map_err(|error| error.to_not_found_response(errors::ApiErrorResponse::MandateNotFound))?; |         .map_err(|error| error.to_not_found_response(errors::ApiErrorResponse::MandateNotFound))?; | ||||||
|  |  | ||||||
|     utils::when( |     // TODO: Make currency in payments request as Currency enum | ||||||
|         mandate.mandate_status != storage_enums::MandateStatus::Active, |  | ||||||
|         Err(errors::ApiErrorResponse::MandateNotFound), |  | ||||||
|     )?; |  | ||||||
|  |  | ||||||
|     let customer = req.customer_id.clone().get_required_value("customer_id")?; |     let customer = req.customer_id.clone().get_required_value("customer_id")?; | ||||||
|  |  | ||||||
| @ -159,8 +156,13 @@ pub async fn get_token_for_recurring_mandate( | |||||||
|                 message: "mandate is not active".into() |                 message: "mandate is not active".into() | ||||||
|             }))? |             }))? | ||||||
|         }; |         }; | ||||||
|         mandate.payment_method_id |         mandate.payment_method_id.clone() | ||||||
|     }; |     }; | ||||||
|  |     verify_mandate_details( | ||||||
|  |         req.amount.get_required_value("amount")?.into(), | ||||||
|  |         req.currency.clone().get_required_value("currency")?, | ||||||
|  |         mandate.clone(), | ||||||
|  |     )?; | ||||||
|  |  | ||||||
|     let payment_method = db |     let payment_method = db | ||||||
|         .find_payment_method(payment_method_id.as_str()) |         .find_payment_method(payment_method_id.as_str()) | ||||||
| @ -346,6 +348,44 @@ fn validate_recurring_mandate(req: api::MandateValidationFields) -> RouterResult | |||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn verify_mandate_details( | ||||||
|  |     request_amount: i32, | ||||||
|  |     request_currency: String, | ||||||
|  |     mandate: storage::Mandate, | ||||||
|  | ) -> RouterResult<()> { | ||||||
|  |     match mandate.mandate_type { | ||||||
|  |         storage_enums::MandateType::SingleUse => utils::when( | ||||||
|  |             mandate | ||||||
|  |                 .mandate_amount | ||||||
|  |                 .map(|mandate_amount| request_amount > mandate_amount) | ||||||
|  |                 .unwrap_or(true), | ||||||
|  |             Err(report!(errors::ApiErrorResponse::MandateValidationFailed { | ||||||
|  |                 reason: "request amount is greater than mandate amount".to_string() | ||||||
|  |             })), | ||||||
|  |         ), | ||||||
|  |         storage::enums::MandateType::MultiUse => utils::when( | ||||||
|  |             mandate | ||||||
|  |                 .mandate_amount | ||||||
|  |                 .map(|mandate_amount| { | ||||||
|  |                     (mandate.amount_captured.unwrap_or(0) + request_amount) > mandate_amount | ||||||
|  |                 }) | ||||||
|  |                 .unwrap_or(false), | ||||||
|  |             Err(report!(errors::ApiErrorResponse::MandateValidationFailed { | ||||||
|  |                 reason: "request amount is greater than mandate amount".to_string() | ||||||
|  |             })), | ||||||
|  |         ), | ||||||
|  |     }?; | ||||||
|  |     utils::when( | ||||||
|  |         mandate | ||||||
|  |             .mandate_currency | ||||||
|  |             .map(|mandate_currency| mandate_currency.to_string() != request_currency) | ||||||
|  |             .unwrap_or(true), | ||||||
|  |         Err(report!(errors::ApiErrorResponse::MandateValidationFailed { | ||||||
|  |             reason: "cross currency mandates not supported".to_string() | ||||||
|  |         })), | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  |  | ||||||
| #[instrument(skip_all)] | #[instrument(skip_all)] | ||||||
| pub fn payment_attempt_status_fsm( | pub fn payment_attempt_status_fsm( | ||||||
|     payment_method_data: &Option<api::PaymentMethod>, |     payment_method_data: &Option<api::PaymentMethod>, | ||||||
| @ -1077,3 +1117,53 @@ pub fn hmac_sha256_sorted_query_params<'a>( | |||||||
| pub fn check_if_operation_confirm<Op: std::fmt::Debug>(operations: Op) -> bool { | pub fn check_if_operation_confirm<Op: std::fmt::Debug>(operations: Op) -> bool { | ||||||
|     format!("{:?}", operations) == "PaymentConfirm" |     format!("{:?}", operations) == "PaymentConfirm" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn generate_mandate( | ||||||
|  |     merchant_id: String, | ||||||
|  |     connector: String, | ||||||
|  |     setup_mandate_details: Option<api::MandateData>, | ||||||
|  |     customer: &Option<storage::Customer>, | ||||||
|  |     payment_method_id: String, | ||||||
|  | ) -> Option<storage::MandateNew> { | ||||||
|  |     match (setup_mandate_details, customer) { | ||||||
|  |         (Some(data), Some(cus)) => { | ||||||
|  |             let mandate_id = utils::generate_id(consts::ID_LENGTH, "man"); | ||||||
|  |  | ||||||
|  |             // The construction of the mandate new must be visible | ||||||
|  |             let mut new_mandate = storage::MandateNew::default(); | ||||||
|  |  | ||||||
|  |             new_mandate | ||||||
|  |                 .set_mandate_id(mandate_id) | ||||||
|  |                 .set_customer_id(cus.customer_id.clone()) | ||||||
|  |                 .set_merchant_id(merchant_id) | ||||||
|  |                 .set_payment_method_id(payment_method_id) | ||||||
|  |                 .set_connector(connector) | ||||||
|  |                 .set_mandate_status(storage_enums::MandateStatus::Active) | ||||||
|  |                 .set_customer_ip_address( | ||||||
|  |                     data.customer_acceptance | ||||||
|  |                         .get_ip_address() | ||||||
|  |                         .map(masking::Secret::new), | ||||||
|  |                 ) | ||||||
|  |                 .set_customer_user_agent(data.customer_acceptance.get_user_agent()) | ||||||
|  |                 .set_customer_accepted_at(Some(data.customer_acceptance.get_accepted_at())); | ||||||
|  |  | ||||||
|  |             Some(match data.mandate_type { | ||||||
|  |                 api::MandateType::SingleUse(data) => new_mandate | ||||||
|  |                     .set_mandate_amount(Some(data.amount)) | ||||||
|  |                     .set_mandate_currency(Some(data.currency)) | ||||||
|  |                     .set_mandate_type(storage_enums::MandateType::SingleUse) | ||||||
|  |                     .to_owned(), | ||||||
|  |  | ||||||
|  |                 api::MandateType::MultiUse(op_data) => match op_data { | ||||||
|  |                     Some(data) => new_mandate | ||||||
|  |                         .set_mandate_amount(Some(data.amount)) | ||||||
|  |                         .set_mandate_currency(Some(data.currency)), | ||||||
|  |                     None => &mut new_mandate, | ||||||
|  |                 } | ||||||
|  |                 .set_mandate_type(storage_enums::MandateType::MultiUse) | ||||||
|  |                 .to_owned(), | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         (_, _) => None, | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -127,8 +127,11 @@ diesel::table! { | |||||||
|         network_transaction_id -> Nullable<Varchar>, |         network_transaction_id -> Nullable<Varchar>, | ||||||
|         previous_transaction_id -> Nullable<Varchar>, |         previous_transaction_id -> Nullable<Varchar>, | ||||||
|         created_at -> Timestamp, |         created_at -> Timestamp, | ||||||
|         single_use_amount -> Nullable<Int4>, |         mandate_amount -> Nullable<Int4>, | ||||||
|         single_use_currency -> Nullable<Currency>, |         mandate_currency -> Nullable<Currency>, | ||||||
|  |         amount_captured -> Nullable<Int4>, | ||||||
|  |         connector -> Varchar, | ||||||
|  |         connector_mandate_id -> Nullable<Varchar>, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -154,11 +154,16 @@ pub struct MandateData { | |||||||
|     pub mandate_type: MandateType, |     pub mandate_type: MandateType, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Default, Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)] | #[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)] | ||||||
| pub enum MandateType { | pub enum MandateType { | ||||||
|     SingleUse(storage::SingleUseMandate), |     SingleUse(storage::MandateAmountData), | ||||||
|     #[default] |     MultiUse(Option<storage::MandateAmountData>), | ||||||
|     MultiUse, | } | ||||||
|  |  | ||||||
|  | impl Default for MandateType { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self::MultiUse(None) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Default, Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)] | #[derive(Default, Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)] | ||||||
|  | |||||||
| @ -24,8 +24,11 @@ pub struct Mandate { | |||||||
|     pub network_transaction_id: Option<String>, |     pub network_transaction_id: Option<String>, | ||||||
|     pub previous_transaction_id: Option<String>, |     pub previous_transaction_id: Option<String>, | ||||||
|     pub created_at: PrimitiveDateTime, |     pub created_at: PrimitiveDateTime, | ||||||
|     pub single_use_amount: Option<i32>, |     pub mandate_amount: Option<i32>, | ||||||
|     pub single_use_currency: Option<storage_enums::Currency>, |     pub mandate_currency: Option<storage_enums::Currency>, | ||||||
|  |     pub amount_captured: Option<i32>, | ||||||
|  |     pub connector: String, | ||||||
|  |     pub connector_mandate_id: Option<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive( | #[derive( | ||||||
| @ -45,8 +48,11 @@ pub struct MandateNew { | |||||||
|     pub network_transaction_id: Option<String>, |     pub network_transaction_id: Option<String>, | ||||||
|     pub previous_transaction_id: Option<String>, |     pub previous_transaction_id: Option<String>, | ||||||
|     pub created_at: Option<PrimitiveDateTime>, |     pub created_at: Option<PrimitiveDateTime>, | ||||||
|     pub single_use_amount: Option<i32>, |     pub mandate_amount: Option<i32>, | ||||||
|     pub single_use_currency: Option<storage_enums::Currency>, |     pub mandate_currency: Option<storage_enums::Currency>, | ||||||
|  |     pub amount_captured: Option<i32>, | ||||||
|  |     pub connector: String, | ||||||
|  |     pub connector_mandate_id: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| @ -54,10 +60,13 @@ pub enum MandateUpdate { | |||||||
|     StatusUpdate { |     StatusUpdate { | ||||||
|         mandate_status: storage_enums::MandateStatus, |         mandate_status: storage_enums::MandateStatus, | ||||||
|     }, |     }, | ||||||
|  |     CaptureAmountUpdate { | ||||||
|  |         amount_captured: Option<i32>, | ||||||
|  |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Eq, PartialEq, Copy, Debug, Default, serde::Serialize, serde::Deserialize)] | #[derive(Clone, Eq, PartialEq, Copy, Debug, Default, serde::Serialize, serde::Deserialize)] | ||||||
| pub struct SingleUseMandate { | pub struct MandateAmountData { | ||||||
|     pub amount: i32, |     pub amount: i32, | ||||||
|     pub currency: storage_enums::Currency, |     pub currency: storage_enums::Currency, | ||||||
| } | } | ||||||
| @ -65,13 +74,21 @@ pub struct SingleUseMandate { | |||||||
| #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] | #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] | ||||||
| #[diesel(table_name = mandate)] | #[diesel(table_name = mandate)] | ||||||
| pub(super) struct MandateUpdateInternal { | pub(super) struct MandateUpdateInternal { | ||||||
|     mandate_status: storage_enums::MandateStatus, |     mandate_status: Option<storage_enums::MandateStatus>, | ||||||
|  |     amount_captured: Option<i32>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<MandateUpdate> for MandateUpdateInternal { | impl From<MandateUpdate> for MandateUpdateInternal { | ||||||
|     fn from(mandate_update: MandateUpdate) -> Self { |     fn from(mandate_update: MandateUpdate) -> Self { | ||||||
|         match mandate_update { |         match mandate_update { | ||||||
|             MandateUpdate::StatusUpdate { mandate_status } => Self { mandate_status }, |             MandateUpdate::StatusUpdate { mandate_status } => Self { | ||||||
|  |                 mandate_status: Some(mandate_status), | ||||||
|  |                 amount_captured: None, | ||||||
|  |             }, | ||||||
|  |             MandateUpdate::CaptureAmountUpdate { amount_captured } => Self { | ||||||
|  |                 mandate_status: None, | ||||||
|  |                 amount_captured, | ||||||
|  |             }, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,9 @@ | |||||||
|  | -- This file should undo anything in `up.sql` | ||||||
|  | ALTER TABLE mandate | ||||||
|  | RENAME COLUMN mandate_amount TO single_use_amount; | ||||||
|  | ALTER TABLE mandate | ||||||
|  | RENAME COLUMN mandate_currency TO single_use_currency; | ||||||
|  | ALTER TABLE mandate | ||||||
|  | DROP COLUMN IF EXISTS amount_captured, | ||||||
|  | DROP COLUMN IF EXISTS connector, | ||||||
|  | DROP COLUMN IF EXISTS connector_mandate_id; | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | -- Your SQL goes here | ||||||
|  | ALTER TABLE mandate | ||||||
|  | RENAME COLUMN single_use_amount TO mandate_amount; | ||||||
|  | ALTER TABLE mandate | ||||||
|  | RENAME COLUMN single_use_currency TO mandate_currency; | ||||||
|  | ALTER TABLE mandate | ||||||
|  | ADD IF NOT EXISTS amount_captured INTEGER DEFAULT NULL, | ||||||
|  | ADD IF NOT EXISTS connector VARCHAR(255) NOT NULL DEFAULT 'dummy', | ||||||
|  | ADD IF NOT EXISTS connector_mandate_id VARCHAR(255) DEFAULT NULL; | ||||||
		Reference in New Issue
	
	Block a user
	 Nishant Joshi
					Nishant Joshi