mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +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