mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat: delete customer data in compliance with GDPR regulations (#64)
This commit is contained in:
@ -39,7 +39,9 @@ pub struct CustomerId {
|
|||||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||||
pub struct CustomerDeleteResponse {
|
pub struct CustomerDeleteResponse {
|
||||||
pub customer_id: String,
|
pub customer_id: String,
|
||||||
pub deleted: bool,
|
pub customer_deleted: bool,
|
||||||
|
pub address_deleted: bool,
|
||||||
|
pub payment_methods_deleted: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_customer_id() -> String {
|
pub fn generate_customer_id() -> String {
|
||||||
|
|||||||
@ -111,9 +111,7 @@ pub async fn customer_delete(
|
|||||||
&state,
|
&state,
|
||||||
&req,
|
&req,
|
||||||
payload,
|
payload,
|
||||||
|state, merchant_account, req| {
|
customers::delete_customer,
|
||||||
customers::delete_customer(&*state.store, merchant_account, req)
|
|
||||||
},
|
|
||||||
api::MerchantAuthentication::ApiKey,
|
api::MerchantAuthentication::ApiKey,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@ -104,7 +104,7 @@ impl From<api::CustomerDeleteResponse> for CustomerDeleteResponse {
|
|||||||
fn from(cust: api::CustomerDeleteResponse) -> Self {
|
fn from(cust: api::CustomerDeleteResponse) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: cust.customer_id,
|
id: cust.customer_id,
|
||||||
deleted: cust.deleted,
|
deleted: cust.customer_deleted,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,6 +55,12 @@ pub(crate) enum ErrorCode {
|
|||||||
#[error(error_type = StripeErrorType::ApiError, code = "internal_server_error", message = "Server is down")]
|
#[error(error_type = StripeErrorType::ApiError, code = "internal_server_error", message = "Server is down")]
|
||||||
DuplicateRefundRequest,
|
DuplicateRefundRequest,
|
||||||
|
|
||||||
|
#[error(error_type = StripeErrorType::InvalidRequestError, code = "active_mandate", message = "Customer has active mandate")]
|
||||||
|
MandateActive,
|
||||||
|
|
||||||
|
#[error(error_type = StripeErrorType::InvalidRequestError, code = "customer_redacted", message = "Customer has redacted")]
|
||||||
|
CustomerRedacted,
|
||||||
|
|
||||||
#[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such refund")]
|
#[error(error_type = StripeErrorType::InvalidRequestError, code = "resource_missing", message = "No such refund")]
|
||||||
RefundNotFound,
|
RefundNotFound,
|
||||||
|
|
||||||
@ -340,6 +346,8 @@ impl From<ApiErrorResponse> for ErrorCode {
|
|||||||
ApiErrorResponse::RefundFailed { data } => ErrorCode::RefundFailed, // Nothing at stripe to map
|
ApiErrorResponse::RefundFailed { data } => ErrorCode::RefundFailed, // Nothing at stripe to map
|
||||||
|
|
||||||
ApiErrorResponse::InternalServerError => ErrorCode::InternalServerError, // not a stripe code
|
ApiErrorResponse::InternalServerError => ErrorCode::InternalServerError, // not a stripe code
|
||||||
|
ApiErrorResponse::MandateActive => ErrorCode::MandateActive, //not a stripe code
|
||||||
|
ApiErrorResponse::CustomerRedacted => ErrorCode::CustomerRedacted, //not a stripe code
|
||||||
ApiErrorResponse::DuplicateRefundRequest => ErrorCode::DuplicateRefundRequest,
|
ApiErrorResponse::DuplicateRefundRequest => ErrorCode::DuplicateRefundRequest,
|
||||||
ApiErrorResponse::RefundNotFound => ErrorCode::RefundNotFound,
|
ApiErrorResponse::RefundNotFound => ErrorCode::RefundNotFound,
|
||||||
ApiErrorResponse::CustomerNotFound => ErrorCode::CustomerNotFound,
|
ApiErrorResponse::CustomerNotFound => ErrorCode::CustomerNotFound,
|
||||||
@ -433,9 +441,10 @@ impl actix_web::ResponseError for ErrorCode {
|
|||||||
| ErrorCode::ResourceIdNotFound
|
| ErrorCode::ResourceIdNotFound
|
||||||
| ErrorCode::PaymentIntentMandateInvalid { .. }
|
| ErrorCode::PaymentIntentMandateInvalid { .. }
|
||||||
| ErrorCode::PaymentIntentUnexpectedState { .. } => StatusCode::BAD_REQUEST,
|
| ErrorCode::PaymentIntentUnexpectedState { .. } => StatusCode::BAD_REQUEST,
|
||||||
ErrorCode::RefundFailed | ErrorCode::InternalServerError => {
|
ErrorCode::RefundFailed
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
| ErrorCode::InternalServerError
|
||||||
}
|
| ErrorCode::MandateActive
|
||||||
|
| ErrorCode::CustomerRedacted => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
ErrorCode::ReturnUrlUnavailable => StatusCode::SERVICE_UNAVAILABLE,
|
ErrorCode::ReturnUrlUnavailable => StatusCode::SERVICE_UNAVAILABLE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,16 @@ use error_stack::ResultExt;
|
|||||||
use router_env::{tracing, tracing::instrument};
|
use router_env::{tracing, tracing::instrument};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::errors::{self, RouterResponse, StorageErrorExt},
|
core::{
|
||||||
|
errors::{self, RouterResponse, StorageErrorExt},
|
||||||
|
payment_methods::cards,
|
||||||
|
},
|
||||||
db::StorageInterface,
|
db::StorageInterface,
|
||||||
|
routes::AppState,
|
||||||
services,
|
services,
|
||||||
types::{
|
types::{
|
||||||
api::customers::{self, CustomerRequestExt},
|
api::customers::{self, CustomerRequestExt},
|
||||||
storage,
|
storage::{self, enums},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -63,20 +67,99 @@ pub async fn retrieve_customer(
|
|||||||
Ok(services::BachResponse::Json(response.into()))
|
Ok(services::BachResponse::Json(response.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(db))]
|
#[instrument(skip_all)]
|
||||||
pub async fn delete_customer(
|
pub async fn delete_customer(
|
||||||
db: &dyn StorageInterface,
|
state: &AppState,
|
||||||
merchant_account: storage::MerchantAccount,
|
merchant_account: storage::MerchantAccount,
|
||||||
req: customers::CustomerId,
|
req: customers::CustomerId,
|
||||||
) -> RouterResponse<customers::CustomerDeleteResponse> {
|
) -> RouterResponse<customers::CustomerDeleteResponse> {
|
||||||
let response = db
|
let db = &state.store;
|
||||||
.delete_customer_by_customer_id_merchant_id(&req.customer_id, &merchant_account.merchant_id)
|
|
||||||
|
let cust = db
|
||||||
|
.find_customer_by_customer_id_merchant_id(&req.customer_id, &merchant_account.merchant_id)
|
||||||
.await
|
.await
|
||||||
.map(|response| customers::CustomerDeleteResponse {
|
.map_err(|err| err.to_not_found_response(errors::ApiErrorResponse::CustomerNotFound))?;
|
||||||
customer_id: req.customer_id,
|
if cust.name == Some("Redacted".to_string()) {
|
||||||
deleted: response,
|
Err(errors::ApiErrorResponse::CustomerRedacted)?
|
||||||
})
|
}
|
||||||
.map_err(|error| error.to_not_found_response(errors::ApiErrorResponse::CustomerNotFound))?;
|
|
||||||
|
let customer_mandates = db
|
||||||
|
.find_mandate_by_merchant_id_customer_id(&merchant_account.merchant_id, &req.customer_id)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_not_found_response(errors::ApiErrorResponse::MandateNotFound))?;
|
||||||
|
|
||||||
|
for mandate in customer_mandates.into_iter() {
|
||||||
|
if mandate.mandate_status == enums::MandateStatus::Active {
|
||||||
|
Err(errors::ApiErrorResponse::MandateActive)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let customer_payment_methods = db
|
||||||
|
.find_payment_method_by_customer_id_merchant_id_list(
|
||||||
|
&req.customer_id,
|
||||||
|
&merchant_account.merchant_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
err.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)
|
||||||
|
})?;
|
||||||
|
for pm in customer_payment_methods.into_iter() {
|
||||||
|
if pm.payment_method == enums::PaymentMethodType::Card {
|
||||||
|
cards::delete_card(state, &merchant_account.merchant_id, &pm.payment_method_id).await?;
|
||||||
|
}
|
||||||
|
db.delete_payment_method_by_merchant_id_payment_method_id(
|
||||||
|
&merchant_account.merchant_id,
|
||||||
|
&pm.payment_method_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|error| {
|
||||||
|
error.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let update_address = storage::AddressUpdate::Update {
|
||||||
|
city: Some("Redacted".to_string()),
|
||||||
|
country: Some("Redacted".to_string()),
|
||||||
|
line1: Some("Redacted".to_string().into()),
|
||||||
|
line2: Some("Redacted".to_string().into()),
|
||||||
|
line3: Some("Redacted".to_string().into()),
|
||||||
|
state: Some("Redacted".to_string().into()),
|
||||||
|
zip: Some("Redacted".to_string().into()),
|
||||||
|
first_name: Some("Redacted".to_string().into()),
|
||||||
|
last_name: Some("Redacted".to_string().into()),
|
||||||
|
phone_number: Some("Redacted".to_string().into()),
|
||||||
|
country_code: Some("Redacted".to_string()),
|
||||||
|
};
|
||||||
|
db.update_address_by_merchant_id_customer_id(
|
||||||
|
&req.customer_id,
|
||||||
|
&merchant_account.merchant_id,
|
||||||
|
update_address,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(errors::ApiErrorResponse::AddressNotFound)?;
|
||||||
|
|
||||||
|
let updated_customer = storage::CustomerUpdate::Update {
|
||||||
|
name: Some("Redacted".to_string()),
|
||||||
|
email: Some("Redacted".to_string().into()),
|
||||||
|
phone: Some("Redacted".to_string().into()),
|
||||||
|
description: Some("Redacted".to_string()),
|
||||||
|
phone_country_code: Some("Redacted".to_string()),
|
||||||
|
metadata: None,
|
||||||
|
};
|
||||||
|
db.update_customer_by_customer_id_merchant_id(
|
||||||
|
req.customer_id.clone(),
|
||||||
|
merchant_account.merchant_id,
|
||||||
|
updated_customer,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(errors::ApiErrorResponse::CustomerNotFound)?;
|
||||||
|
|
||||||
|
let response = customers::CustomerDeleteResponse {
|
||||||
|
customer_id: req.customer_id,
|
||||||
|
customer_deleted: true,
|
||||||
|
address_deleted: true,
|
||||||
|
payment_methods_deleted: true,
|
||||||
|
};
|
||||||
Ok(services::BachResponse::Json(response))
|
Ok(services::BachResponse::Json(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,8 +28,6 @@ pub enum ApiErrorResponse {
|
|||||||
the Dashboard Settings section."
|
the Dashboard Settings section."
|
||||||
)]
|
)]
|
||||||
BadCredentials,
|
BadCredentials,
|
||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_10", message = "Invalid Ephemeral Key for the customer")]
|
|
||||||
InvalidEphermeralKey,
|
|
||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_03", message = "Unrecognized request URL.")]
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_03", message = "Unrecognized request URL.")]
|
||||||
InvalidRequestUrl,
|
InvalidRequestUrl,
|
||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_04", message = "The HTTP method is not applicable for this API.")]
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_04", message = "The HTTP method is not applicable for this API.")]
|
||||||
@ -46,14 +44,20 @@ pub enum ApiErrorResponse {
|
|||||||
},
|
},
|
||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "{message}")]
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "{message}")]
|
||||||
InvalidRequestData { message: String },
|
InvalidRequestData { message: String },
|
||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Refund amount exceeds the payment amount.")]
|
|
||||||
RefundAmountExceedsPaymentAmount,
|
|
||||||
/// Typically used when a field has invalid value, or deserialization of the value contained in
|
/// Typically used when a field has invalid value, or deserialization of the value contained in
|
||||||
/// a field fails.
|
/// a field fails.
|
||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "Invalid value provided: {field_name}.")]
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "Invalid value provided: {field_name}.")]
|
||||||
InvalidDataValue { field_name: &'static str },
|
InvalidDataValue { field_name: &'static str },
|
||||||
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "The client_secret provided does not match the client_secret associated with the Payment.")]
|
||||||
|
ClientSecretInvalid,
|
||||||
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "Customer has existing mandate/subsciption.")]
|
||||||
|
MandateActive,
|
||||||
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "Customer has already redacted.")]
|
||||||
|
CustomerRedacted,
|
||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Reached maximum refund attempts")]
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Reached maximum refund attempts")]
|
||||||
MaximumRefundCount,
|
MaximumRefundCount,
|
||||||
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Refund amount exceeds the payment amount.")]
|
||||||
|
RefundAmountExceedsPaymentAmount,
|
||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_09", message = "This PaymentIntent could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}.")]
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_09", message = "This PaymentIntent could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}.")]
|
||||||
PaymentUnexpectedState {
|
PaymentUnexpectedState {
|
||||||
current_flow: String,
|
current_flow: String,
|
||||||
@ -61,14 +65,13 @@ pub enum ApiErrorResponse {
|
|||||||
current_value: String,
|
current_value: String,
|
||||||
states: String,
|
states: String,
|
||||||
},
|
},
|
||||||
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_10", message = "Invalid Ephemeral Key for the customer")]
|
||||||
|
InvalidEphermeralKey,
|
||||||
/// Typically used when information involving multiple fields or previously provided
|
/// Typically used when information involving multiple fields or previously provided
|
||||||
/// information doesn't satisfy a condition.
|
/// information doesn't satisfy a condition.
|
||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_10", message = "{message}")]
|
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_10", message = "{message}")]
|
||||||
PreconditionFailed { message: String },
|
PreconditionFailed { message: String },
|
||||||
|
|
||||||
#[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "The client_secret provided does not match the client_secret associated with the Payment.")]
|
|
||||||
ClientSecretInvalid,
|
|
||||||
|
|
||||||
#[error(error_type = ErrorType::ProcessingError, code = "CE_01", message = "Payment failed while processing with connector. Retry payment.")]
|
#[error(error_type = ErrorType::ProcessingError, code = "CE_01", message = "Payment failed while processing with connector. Retry payment.")]
|
||||||
PaymentAuthorizationFailed { data: Option<serde_json::Value> },
|
PaymentAuthorizationFailed { data: Option<serde_json::Value> },
|
||||||
#[error(error_type = ErrorType::ProcessingError, code = "CE_02", message = "Payment failed while processing with connector. Retry payment.")]
|
#[error(error_type = ErrorType::ProcessingError, code = "CE_02", message = "Payment failed while processing with connector. Retry payment.")]
|
||||||
@ -168,6 +171,8 @@ impl actix_web::ResponseError for ApiErrorResponse {
|
|||||||
ApiErrorResponse::DuplicateRefundRequest => StatusCode::BAD_REQUEST, // 400
|
ApiErrorResponse::DuplicateRefundRequest => StatusCode::BAD_REQUEST, // 400
|
||||||
ApiErrorResponse::RefundNotFound
|
ApiErrorResponse::RefundNotFound
|
||||||
| ApiErrorResponse::CustomerNotFound
|
| ApiErrorResponse::CustomerNotFound
|
||||||
|
| ApiErrorResponse::MandateActive
|
||||||
|
| ApiErrorResponse::CustomerRedacted
|
||||||
| ApiErrorResponse::PaymentNotFound
|
| ApiErrorResponse::PaymentNotFound
|
||||||
| ApiErrorResponse::PaymentMethodNotFound
|
| ApiErrorResponse::PaymentMethodNotFound
|
||||||
| ApiErrorResponse::MerchantAccountNotFound
|
| ApiErrorResponse::MerchantAccountNotFound
|
||||||
|
|||||||
@ -204,6 +204,23 @@ pub async fn mock_get_card<'a>(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn mock_delete_card<'a>(
|
||||||
|
db: &dyn db::StorageInterface,
|
||||||
|
card_id: &'a str,
|
||||||
|
) -> errors::CustomResult<payment_methods::DeleteCardResponse, errors::CardVaultError> {
|
||||||
|
let locker_mock_up = db
|
||||||
|
.delete_locker_mock_up(card_id)
|
||||||
|
.await
|
||||||
|
.change_context(errors::CardVaultError::FetchCardFailed)?;
|
||||||
|
Ok(payment_methods::DeleteCardResponse {
|
||||||
|
card_id: locker_mock_up.card_id,
|
||||||
|
external_id: locker_mock_up.external_id,
|
||||||
|
card_isin: None,
|
||||||
|
status: "SUCCESS".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn get_card_from_legacy_locker<'a>(
|
pub async fn get_card_from_legacy_locker<'a>(
|
||||||
state: &'a routes::AppState,
|
state: &'a routes::AppState,
|
||||||
@ -245,17 +262,25 @@ pub async fn delete_card<'a>(
|
|||||||
merchant_id: &'a str,
|
merchant_id: &'a str,
|
||||||
card_id: &'a str,
|
card_id: &'a str,
|
||||||
) -> errors::RouterResult<payment_methods::DeleteCardResponse> {
|
) -> errors::RouterResult<payment_methods::DeleteCardResponse> {
|
||||||
|
let locker = &state.conf.locker;
|
||||||
let request = payment_methods::mk_delete_card_request(&state.conf.locker, merchant_id, card_id)
|
let request = payment_methods::mk_delete_card_request(&state.conf.locker, merchant_id, card_id)
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
.attach_printable("Making Delete card request Failed")?;
|
.attach_printable("Making Delete card request Failed")?;
|
||||||
// FIXME use call_api 2. Serde's handle should be inside the generic function
|
// FIXME use call_api 2. Serde's handle should be inside the generic function
|
||||||
let delete_card_resp = services::call_connector_api(state, request)
|
let delete_card_resp = if !locker.mock_locker {
|
||||||
.await
|
services::call_connector_api(state, request)
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
.await
|
||||||
.map_err(|_x| errors::ApiErrorResponse::InternalServerError)?
|
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||||
.response
|
.map_err(|_x| errors::ApiErrorResponse::InternalServerError)?
|
||||||
.parse_struct("DeleteCardResponse")
|
.response
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)?;
|
.parse_struct("DeleteCardResponse")
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||||
|
} else {
|
||||||
|
mock_delete_card(&*state.store, card_id)
|
||||||
|
.await
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)?
|
||||||
|
};
|
||||||
|
|
||||||
Ok(delete_card_resp)
|
Ok(delete_card_resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,14 +14,23 @@ pub trait AddressInterface {
|
|||||||
address_id: String,
|
address_id: String,
|
||||||
address: storage::AddressUpdate,
|
address: storage::AddressUpdate,
|
||||||
) -> CustomResult<storage::Address, errors::StorageError>;
|
) -> CustomResult<storage::Address, errors::StorageError>;
|
||||||
|
|
||||||
async fn insert_address(
|
async fn insert_address(
|
||||||
&self,
|
&self,
|
||||||
address: storage::AddressNew,
|
address: storage::AddressNew,
|
||||||
) -> CustomResult<storage::Address, errors::StorageError>;
|
) -> CustomResult<storage::Address, errors::StorageError>;
|
||||||
|
|
||||||
async fn find_address(
|
async fn find_address(
|
||||||
&self,
|
&self,
|
||||||
address_id: &str,
|
address_id: &str,
|
||||||
) -> CustomResult<storage::Address, errors::StorageError>;
|
) -> CustomResult<storage::Address, errors::StorageError>;
|
||||||
|
|
||||||
|
async fn update_address_by_merchant_id_customer_id(
|
||||||
|
&self,
|
||||||
|
customer_id: &str,
|
||||||
|
merchant_id: &str,
|
||||||
|
address: storage::AddressUpdate,
|
||||||
|
) -> CustomResult<Vec<storage::Address>, errors::StorageError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@ -60,6 +69,24 @@ impl AddressInterface for Store {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
.into_report()
|
.into_report()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn update_address_by_merchant_id_customer_id(
|
||||||
|
&self,
|
||||||
|
customer_id: &str,
|
||||||
|
merchant_id: &str,
|
||||||
|
address: storage::AddressUpdate,
|
||||||
|
) -> CustomResult<Vec<storage::Address>, errors::StorageError> {
|
||||||
|
let conn = pg_connection(&self.master_pool).await;
|
||||||
|
storage::Address::update_by_merchant_id_customer_id(
|
||||||
|
&conn,
|
||||||
|
customer_id,
|
||||||
|
merchant_id,
|
||||||
|
address,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
.into_report()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@ -85,4 +112,13 @@ impl AddressInterface for MockDb {
|
|||||||
) -> CustomResult<storage::Address, errors::StorageError> {
|
) -> CustomResult<storage::Address, errors::StorageError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn update_address_by_merchant_id_customer_id(
|
||||||
|
&self,
|
||||||
|
_customer_id: &str,
|
||||||
|
_merchant_id: &str,
|
||||||
|
_address: storage::AddressUpdate,
|
||||||
|
) -> CustomResult<Vec<storage::Address>, errors::StorageError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,11 @@ pub trait LockerMockUpInterface {
|
|||||||
&self,
|
&self,
|
||||||
new: storage::LockerMockUpNew,
|
new: storage::LockerMockUpNew,
|
||||||
) -> CustomResult<storage::LockerMockUp, errors::StorageError>;
|
) -> CustomResult<storage::LockerMockUp, errors::StorageError>;
|
||||||
|
|
||||||
|
async fn delete_locker_mock_up(
|
||||||
|
&self,
|
||||||
|
card_id: &str,
|
||||||
|
) -> CustomResult<storage::LockerMockUp, errors::StorageError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@ -40,6 +45,17 @@ impl LockerMockUpInterface for Store {
|
|||||||
let conn = pg_connection(&self.master_pool).await;
|
let conn = pg_connection(&self.master_pool).await;
|
||||||
new.insert(&conn).await.map_err(Into::into).into_report()
|
new.insert(&conn).await.map_err(Into::into).into_report()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn delete_locker_mock_up(
|
||||||
|
&self,
|
||||||
|
card_id: &str,
|
||||||
|
) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
|
||||||
|
let conn = pg_connection(&self.master_pool).await;
|
||||||
|
storage::LockerMockUp::delete_by_card_id(&conn, card_id)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
.into_report()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@ -57,4 +73,11 @@ impl LockerMockUpInterface for MockDb {
|
|||||||
) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
|
) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn delete_locker_mock_up(
|
||||||
|
&self,
|
||||||
|
_card_id: &str,
|
||||||
|
) -> CustomResult<storage::LockerMockUp, errors::StorageError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,7 +94,7 @@ pub async fn customers_delete(
|
|||||||
&state,
|
&state,
|
||||||
&req,
|
&req,
|
||||||
payload,
|
payload,
|
||||||
|state, merchant_account, req| delete_customer(&*state.store, merchant_account, req),
|
delete_customer,
|
||||||
api::MerchantAuthentication::ApiKey,
|
api::MerchantAuthentication::ApiKey,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use diesel::{associations::HasTable, ExpressionMethods};
|
use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
|
||||||
use router_env::{tracing, tracing::instrument};
|
use router_env::{tracing, tracing::instrument};
|
||||||
|
|
||||||
use super::generics::{self, ExecuteQuery};
|
use super::generics::{self, ExecuteQuery};
|
||||||
@ -61,6 +61,23 @@ impl Address {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_by_merchant_id_customer_id(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
customer_id: &str,
|
||||||
|
merchant_id: &str,
|
||||||
|
address: AddressUpdate,
|
||||||
|
) -> CustomResult<Vec<Self>, errors::DatabaseError> {
|
||||||
|
generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, Self, _>(
|
||||||
|
conn,
|
||||||
|
dsl::merchant_id
|
||||||
|
.eq(merchant_id.to_owned())
|
||||||
|
.and(dsl::customer_id.eq(customer_id.to_owned())),
|
||||||
|
AddressUpdateInternal::from(address),
|
||||||
|
ExecuteQuery::new(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip(conn))]
|
#[instrument(skip(conn))]
|
||||||
pub async fn find_by_address_id<'a>(
|
pub async fn find_by_address_id<'a>(
|
||||||
conn: &PgPooledConn,
|
conn: &PgPooledConn,
|
||||||
|
|||||||
@ -31,4 +31,17 @@ impl LockerMockUp {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(conn))]
|
||||||
|
pub async fn delete_by_card_id(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
card_id: &str,
|
||||||
|
) -> CustomResult<Self, errors::DatabaseError> {
|
||||||
|
generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, Self, _>(
|
||||||
|
conn,
|
||||||
|
dsl::card_id.eq(card_id.to_owned()),
|
||||||
|
ExecuteQuery::new(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user