mirror of
https://github.com/juspay/hyperswitch.git
synced 2026-03-13 09:02:06 +08:00
feat(payment_methods): Store necessary payment method data in payment_methods table (#2073)
Co-authored-by: Sarthak Soni <sarthak.soni@juspay.in>
This commit is contained in:
@@ -144,6 +144,20 @@ pub struct PaymentMethodResponse {
|
||||
pub created: Option<time::PrimitiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub enum PaymentMethodsData {
|
||||
Card(CardDetailsPaymentMethod),
|
||||
}
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct CardDetailsPaymentMethod {
|
||||
pub last4_digits: Option<String>,
|
||||
pub issuer_country: Option<String>,
|
||||
pub expiry_month: Option<masking::Secret<String>>,
|
||||
pub expiry_year: Option<masking::Secret<String>>,
|
||||
pub nick_name: Option<masking::Secret<String>>,
|
||||
pub card_holder_name: Option<masking::Secret<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
|
||||
pub struct CardDetailFromLocker {
|
||||
pub scheme: Option<String>,
|
||||
@@ -172,6 +186,36 @@ pub struct CardDetailFromLocker {
|
||||
pub nick_name: Option<masking::Secret<String>>,
|
||||
}
|
||||
|
||||
impl From<CardDetailsPaymentMethod> for CardDetailFromLocker {
|
||||
fn from(item: CardDetailsPaymentMethod) -> Self {
|
||||
Self {
|
||||
scheme: None,
|
||||
issuer_country: item.issuer_country,
|
||||
last4_digits: item.last4_digits,
|
||||
card_number: None,
|
||||
expiry_month: item.expiry_month,
|
||||
expiry_year: item.expiry_year,
|
||||
card_token: None,
|
||||
card_holder_name: item.card_holder_name,
|
||||
card_fingerprint: None,
|
||||
nick_name: item.nick_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CardDetailFromLocker> for CardDetailsPaymentMethod {
|
||||
fn from(item: CardDetailFromLocker) -> Self {
|
||||
Self {
|
||||
issuer_country: item.issuer_country,
|
||||
last4_digits: item.last4_digits,
|
||||
expiry_month: item.expiry_month,
|
||||
expiry_year: item.expiry_year,
|
||||
nick_name: item.nick_name,
|
||||
card_holder_name: item.card_holder_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq, Eq)]
|
||||
pub struct PaymentExperienceTypes {
|
||||
/// The payment experience enabled
|
||||
|
||||
@@ -7,7 +7,7 @@ use diesel::{
|
||||
};
|
||||
use masking::Secret;
|
||||
|
||||
#[derive(Debug, AsExpression, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, AsExpression, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
|
||||
#[diesel(sql_type = diesel::sql_types::Binary)]
|
||||
#[repr(transparent)]
|
||||
pub struct Encryption {
|
||||
|
||||
@@ -4,7 +4,7 @@ use masking::Secret;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
use crate::{enums as storage_enums, schema::payment_methods};
|
||||
use crate::{encryption::Encryption, enums as storage_enums, schema::payment_methods};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Identifiable, Queryable)]
|
||||
#[diesel(table_name = payment_methods)]
|
||||
@@ -32,6 +32,7 @@ pub struct PaymentMethod {
|
||||
pub payment_method_issuer: Option<String>,
|
||||
pub payment_method_issuer_code: Option<storage_enums::PaymentMethodIssuerCode>,
|
||||
pub metadata: Option<pii::SecretSerdeValue>,
|
||||
pub payment_method_data: Option<Encryption>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Insertable, Queryable, router_derive::DebugAsDisplay)]
|
||||
@@ -57,6 +58,7 @@ pub struct PaymentMethodNew {
|
||||
pub created_at: PrimitiveDateTime,
|
||||
pub last_modified: PrimitiveDateTime,
|
||||
pub metadata: Option<pii::SecretSerdeValue>,
|
||||
pub payment_method_data: Option<Encryption>,
|
||||
}
|
||||
|
||||
impl Default for PaymentMethodNew {
|
||||
@@ -84,6 +86,7 @@ impl Default for PaymentMethodNew {
|
||||
created_at: now,
|
||||
last_modified: now,
|
||||
metadata: Option::default(),
|
||||
payment_method_data: Option::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,6 +638,7 @@ diesel::table! {
|
||||
payment_method_issuer -> Nullable<Varchar>,
|
||||
payment_method_issuer_code -> Nullable<PaymentMethodIssuerCode>,
|
||||
metadata -> Nullable<Json>,
|
||||
payment_method_data -> Nullable<Bytea>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ use api_models::{
|
||||
admin::{self, PaymentMethodsEnabled},
|
||||
enums::{self as api_enums},
|
||||
payment_methods::{
|
||||
CardNetworkTypes, PaymentExperienceTypes, RequestPaymentMethodTypes, RequiredFieldInfo,
|
||||
ResponsePaymentMethodIntermediate, ResponsePaymentMethodTypes,
|
||||
ResponsePaymentMethodsEnabled,
|
||||
CardDetailsPaymentMethod, CardNetworkTypes, PaymentExperienceTypes, PaymentMethodsData,
|
||||
RequestPaymentMethodTypes, RequiredFieldInfo, ResponsePaymentMethodIntermediate,
|
||||
ResponsePaymentMethodTypes, ResponsePaymentMethodsEnabled,
|
||||
},
|
||||
payments::BankCodeResponse,
|
||||
};
|
||||
@@ -43,7 +43,10 @@ use crate::{
|
||||
services,
|
||||
types::{
|
||||
api::{self, PaymentMethodCreateExt},
|
||||
domain::{self, types::decrypt},
|
||||
domain::{
|
||||
self,
|
||||
types::{decrypt, encrypt_optional, AsyncLift},
|
||||
},
|
||||
storage::{self, enums},
|
||||
transformers::{ForeignFrom, ForeignInto},
|
||||
},
|
||||
@@ -58,6 +61,7 @@ pub async fn create_payment_method(
|
||||
payment_method_id: &str,
|
||||
merchant_id: &str,
|
||||
pm_metadata: Option<serde_json::Value>,
|
||||
payment_method_data: Option<Encryption>,
|
||||
) -> errors::CustomResult<storage::PaymentMethod, errors::StorageError> {
|
||||
let response = db
|
||||
.insert_payment_method(storage::PaymentMethodNew {
|
||||
@@ -69,6 +73,7 @@ pub async fn create_payment_method(
|
||||
payment_method_issuer: req.payment_method_issuer.clone(),
|
||||
scheme: req.card_network.clone(),
|
||||
metadata: pm_metadata.map(masking::Secret::new),
|
||||
payment_method_data,
|
||||
..storage::PaymentMethodNew::default()
|
||||
})
|
||||
.await?;
|
||||
@@ -81,6 +86,7 @@ pub async fn add_payment_method(
|
||||
state: &routes::AppState,
|
||||
req: api::PaymentMethodCreate,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> errors::RouterResponse<api::PaymentMethodResponse> {
|
||||
req.validate()?;
|
||||
let merchant_id = &merchant_account.merchant_id;
|
||||
@@ -118,6 +124,15 @@ pub async fn add_payment_method(
|
||||
let (resp, is_duplicate) = response?;
|
||||
if !is_duplicate {
|
||||
let pm_metadata = resp.metadata.as_ref().map(|data| data.peek());
|
||||
|
||||
let pm_card_details = resp
|
||||
.card
|
||||
.as_ref()
|
||||
.map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())));
|
||||
|
||||
let pm_data_encrypted =
|
||||
create_encrypted_payment_method_data(key_store, pm_card_details).await;
|
||||
|
||||
create_payment_method(
|
||||
&*state.store,
|
||||
&req,
|
||||
@@ -125,6 +140,7 @@ pub async fn add_payment_method(
|
||||
&resp.payment_method_id,
|
||||
&resp.merchant_id,
|
||||
pm_metadata.cloned(),
|
||||
pm_data_encrypted,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
@@ -140,6 +156,7 @@ pub async fn update_customer_payment_method(
|
||||
merchant_account: domain::MerchantAccount,
|
||||
req: api::PaymentMethodUpdate,
|
||||
payment_method_id: &str,
|
||||
key_store: domain::MerchantKeyStore,
|
||||
) -> errors::RouterResponse<api::PaymentMethodResponse> {
|
||||
let db = &*state.store;
|
||||
let pm = db
|
||||
@@ -171,7 +188,7 @@ pub async fn update_customer_payment_method(
|
||||
.as_ref()
|
||||
.map(|card_network| card_network.to_string()),
|
||||
};
|
||||
add_payment_method(state, new_pm, &merchant_account).await
|
||||
add_payment_method(state, new_pm, &merchant_account, &key_store).await
|
||||
}
|
||||
|
||||
// Wrapper function to switch lockers
|
||||
@@ -1751,6 +1768,8 @@ pub async fn list_customer_payment_method(
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;
|
||||
|
||||
let key = key_store.key.get_inner().peek();
|
||||
|
||||
let is_requires_cvv = db
|
||||
.find_config_by_key(format!("{}_requires_cvv", merchant_account.merchant_id).as_str())
|
||||
.await;
|
||||
@@ -1782,11 +1801,13 @@ pub async fn list_customer_payment_method(
|
||||
for pm in resp.into_iter() {
|
||||
let parent_payment_method_token = generate_id(consts::ID_LENGTH, "token");
|
||||
let hyperswitch_token = generate_id(consts::ID_LENGTH, "token");
|
||||
|
||||
let card = if pm.payment_method == enums::PaymentMethod::Card {
|
||||
Some(get_lookup_key_from_locker(state, &hyperswitch_token, &pm).await?)
|
||||
get_card_details(&pm, key, state, &hyperswitch_token).await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
let pmd = if pm.payment_method == enums::PaymentMethod::BankTransfer {
|
||||
Some(
|
||||
@@ -1872,6 +1893,34 @@ pub async fn list_customer_payment_method(
|
||||
Ok(services::ApplicationResponse::Json(response))
|
||||
}
|
||||
|
||||
async fn get_card_details(
|
||||
pm: &payment_method::PaymentMethod,
|
||||
key: &[u8],
|
||||
state: &routes::AppState,
|
||||
hyperswitch_token: &str,
|
||||
) -> errors::RouterResult<Option<api::CardDetailFromLocker>> {
|
||||
let mut card_decrypted =
|
||||
decrypt::<serde_json::Value, masking::WithType>(pm.payment_method_data.clone(), key)
|
||||
.await
|
||||
.change_context(errors::StorageError::DecryptionError)
|
||||
.attach_printable("unable to decrypt card details")
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|x| x.into_inner().expose())
|
||||
.and_then(|v| serde_json::from_value::<PaymentMethodsData>(v).ok())
|
||||
.map(|pmd| match pmd {
|
||||
PaymentMethodsData::Card(crd) => api::CardDetailFromLocker::from(crd),
|
||||
});
|
||||
|
||||
card_decrypted = if let Some(mut crd) = card_decrypted {
|
||||
crd.scheme = pm.scheme.clone();
|
||||
Some(crd)
|
||||
} else {
|
||||
Some(get_lookup_key_from_locker(state, hyperswitch_token, pm).await?)
|
||||
};
|
||||
Ok(card_decrypted)
|
||||
}
|
||||
|
||||
pub async fn get_lookup_key_from_locker(
|
||||
state: &routes::AppState,
|
||||
payment_token: &str,
|
||||
@@ -2191,3 +2240,33 @@ pub async fn delete_payment_method(
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn create_encrypted_payment_method_data(
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
pm_data: Option<PaymentMethodsData>,
|
||||
) -> Option<Encryption> {
|
||||
let key = key_store.key.get_inner().peek();
|
||||
|
||||
let pm_data_encrypted: Option<Encryption> = pm_data
|
||||
.as_ref()
|
||||
.map(utils::Encode::<PaymentMethodsData>::encode_to_value)
|
||||
.transpose()
|
||||
.change_context(errors::StorageError::SerializationFailed)
|
||||
.attach_printable("Unable to convert payment method data to a value")
|
||||
.unwrap_or_else(|err| {
|
||||
logger::error!(err=?err);
|
||||
None
|
||||
})
|
||||
.map(masking::Secret::<_, masking::WithType>::new)
|
||||
.async_lift(|inner| encrypt_optional(inner, key))
|
||||
.await
|
||||
.change_context(errors::StorageError::EncryptionError)
|
||||
.attach_printable("Unable to encrypt payment method data")
|
||||
.unwrap_or_else(|err| {
|
||||
logger::error!(err=?err);
|
||||
None
|
||||
})
|
||||
.map(|details| details.into());
|
||||
|
||||
pm_data_encrypted
|
||||
}
|
||||
|
||||
@@ -668,6 +668,7 @@ where
|
||||
call_connector_action,
|
||||
merchant_account,
|
||||
connector_request,
|
||||
key_store,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
@@ -721,6 +722,7 @@ where
|
||||
CallConnectorAction::Trigger,
|
||||
merchant_account,
|
||||
None,
|
||||
key_store,
|
||||
);
|
||||
|
||||
join_handlers.push(res);
|
||||
|
||||
@@ -33,6 +33,7 @@ pub trait ConstructFlowSpecificData<F, Req, Res> {
|
||||
) -> RouterResult<types::RouterData<F, Req, Res>>;
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[async_trait]
|
||||
pub trait Feature<F, T> {
|
||||
async fn decide_flows<'a>(
|
||||
@@ -43,6 +44,7 @@ pub trait Feature<F, T> {
|
||||
call_connector_action: payments::CallConnectorAction,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
connector_request: Option<services::Request>,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
|
||||
@@ -48,6 +48,7 @@ impl Feature<api::Approve, types::PaymentsApproveData>
|
||||
_call_connector_action: payments::CallConnectorAction,
|
||||
_merchant_account: &domain::MerchantAccount,
|
||||
_connector_request: Option<services::Request>,
|
||||
_key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self> {
|
||||
Err(ApiErrorResponse::NotImplemented {
|
||||
message: NotImplementedMessage::Reason("Flow not supported".to_string()),
|
||||
|
||||
@@ -57,6 +57,7 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
|
||||
call_connector_action: payments::CallConnectorAction,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
connector_request: Option<services::Request>,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self> {
|
||||
let connector_integration: services::BoxedConnectorIntegration<
|
||||
'_,
|
||||
@@ -87,6 +88,7 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
|
||||
maybe_customer,
|
||||
merchant_account,
|
||||
self.request.payment_method_type,
|
||||
key_store,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ impl Feature<api::Void, types::PaymentsCancelData>
|
||||
call_connector_action: payments::CallConnectorAction,
|
||||
_merchant_account: &domain::MerchantAccount,
|
||||
connector_request: Option<services::Request>,
|
||||
_key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self> {
|
||||
metrics::PAYMENT_CANCEL_COUNT.add(
|
||||
&metrics::CONTEXT,
|
||||
|
||||
@@ -48,6 +48,7 @@ impl Feature<api::Capture, types::PaymentsCaptureData>
|
||||
call_connector_action: payments::CallConnectorAction,
|
||||
_merchant_account: &domain::MerchantAccount,
|
||||
connector_request: Option<services::Request>,
|
||||
_key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self> {
|
||||
let connector_integration: services::BoxedConnectorIntegration<
|
||||
'_,
|
||||
|
||||
@@ -65,6 +65,7 @@ impl Feature<api::CompleteAuthorize, types::CompleteAuthorizeData>
|
||||
call_connector_action: payments::CallConnectorAction,
|
||||
_merchant_account: &domain::MerchantAccount,
|
||||
connector_request: Option<services::Request>,
|
||||
_key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self> {
|
||||
let connector_integration: services::BoxedConnectorIntegration<
|
||||
'_,
|
||||
|
||||
@@ -51,6 +51,7 @@ impl Feature<api::PSync, types::PaymentsSyncData>
|
||||
call_connector_action: payments::CallConnectorAction,
|
||||
_merchant_account: &domain::MerchantAccount,
|
||||
connector_request: Option<services::Request>,
|
||||
_key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self> {
|
||||
let connector_integration: services::BoxedConnectorIntegration<
|
||||
'_,
|
||||
|
||||
@@ -47,6 +47,7 @@ impl Feature<api::Reject, types::PaymentsRejectData>
|
||||
_call_connector_action: payments::CallConnectorAction,
|
||||
_merchant_account: &domain::MerchantAccount,
|
||||
_connector_request: Option<services::Request>,
|
||||
_key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self> {
|
||||
Err(ApiErrorResponse::NotImplemented {
|
||||
message: NotImplementedMessage::Reason("Flow not supported".to_string()),
|
||||
|
||||
@@ -51,6 +51,7 @@ impl Feature<api::Session, types::PaymentsSessionData> for types::PaymentsSessio
|
||||
call_connector_action: payments::CallConnectorAction,
|
||||
_merchant_account: &domain::MerchantAccount,
|
||||
_connector_request: Option<services::Request>,
|
||||
_key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self> {
|
||||
metrics::SESSION_TOKEN_CREATED.add(
|
||||
&metrics::CONTEXT,
|
||||
|
||||
@@ -46,6 +46,7 @@ impl Feature<api::Verify, types::VerifyRequestData> for types::VerifyRouterData
|
||||
call_connector_action: payments::CallConnectorAction,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
connector_request: Option<services::Request>,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self> {
|
||||
let connector_integration: services::BoxedConnectorIntegration<
|
||||
'_,
|
||||
@@ -70,6 +71,7 @@ impl Feature<api::Verify, types::VerifyRequestData> for types::VerifyRouterData
|
||||
maybe_customer,
|
||||
merchant_account,
|
||||
self.request.payment_method_type,
|
||||
key_store,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -156,6 +158,7 @@ impl TryFrom<types::VerifyRequestData> for types::ConnectorCustomerData {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
impl types::VerifyRouterData {
|
||||
pub async fn decide_flow<'a, 'b>(
|
||||
&'b self,
|
||||
@@ -165,6 +168,7 @@ impl types::VerifyRouterData {
|
||||
confirm: Option<bool>,
|
||||
call_connector_action: payments::CallConnectorAction,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Self> {
|
||||
match confirm {
|
||||
Some(true) => {
|
||||
@@ -192,6 +196,7 @@ impl types::VerifyRouterData {
|
||||
maybe_customer,
|
||||
merchant_account,
|
||||
payment_method_type,
|
||||
key_store,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
services,
|
||||
types::{
|
||||
self,
|
||||
api::{self, PaymentMethodCreateExt},
|
||||
api::{self, CardDetailsPaymentMethod, PaymentMethodCreateExt},
|
||||
domain,
|
||||
storage::enums as storage_enums,
|
||||
},
|
||||
@@ -27,6 +27,7 @@ pub async fn save_payment_method<F: Clone, FData>(
|
||||
maybe_customer: &Option<domain::Customer>,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
payment_method_type: Option<storage_enums::PaymentMethodType>,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> RouterResult<Option<String>>
|
||||
where
|
||||
FData: mandate::MandateBehaviour,
|
||||
@@ -71,6 +72,19 @@ where
|
||||
.await?;
|
||||
let is_duplicate = locker_response.1;
|
||||
|
||||
let pm_card_details = locker_response.0.card.as_ref().map(|card| {
|
||||
api::payment_methods::PaymentMethodsData::Card(CardDetailsPaymentMethod::from(
|
||||
card.clone(),
|
||||
))
|
||||
});
|
||||
|
||||
let pm_data_encrypted =
|
||||
payment_methods::cards::create_encrypted_payment_method_data(
|
||||
key_store,
|
||||
pm_card_details,
|
||||
)
|
||||
.await;
|
||||
|
||||
if is_duplicate {
|
||||
let existing_pm = db
|
||||
.find_payment_method(&locker_response.0.payment_method_id)
|
||||
@@ -103,6 +117,7 @@ where
|
||||
&locker_response.0.payment_method_id,
|
||||
merchant_id,
|
||||
pm_metadata,
|
||||
pm_data_encrypted,
|
||||
)
|
||||
.await
|
||||
.change_context(
|
||||
@@ -131,6 +146,7 @@ where
|
||||
&locker_response.0.payment_method_id,
|
||||
merchant_id,
|
||||
pm_metadata,
|
||||
pm_data_encrypted,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
|
||||
@@ -209,6 +209,24 @@ pub async fn save_payout_data_to_locker(
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Error updating payouts in saved payout method")?;
|
||||
|
||||
let pm_data = api::payment_methods::PaymentMethodsData::Card(
|
||||
api::payment_methods::CardDetailsPaymentMethod {
|
||||
last4_digits: card_details
|
||||
.as_ref()
|
||||
.map(|c| c.card_number.clone().get_last4()),
|
||||
issuer_country: None,
|
||||
expiry_month: card_details.as_ref().map(|c| c.card_exp_month.clone()),
|
||||
expiry_year: card_details.as_ref().map(|c| c.card_exp_year.clone()),
|
||||
nick_name: card_details.as_ref().and_then(|c| c.nick_name.clone()),
|
||||
card_holder_name: card_details
|
||||
.as_ref()
|
||||
.and_then(|c| c.card_holder_name.clone()),
|
||||
},
|
||||
);
|
||||
|
||||
let card_details_encrypted =
|
||||
cards::create_encrypted_payment_method_data(key_store, Some(pm_data)).await;
|
||||
|
||||
// Insert in payment_method table
|
||||
let payment_method = api::PaymentMethodCreate {
|
||||
payment_method: api_enums::PaymentMethod::foreign_from(payout_method_data.to_owned()),
|
||||
@@ -220,6 +238,7 @@ pub async fn save_payout_data_to_locker(
|
||||
customer_id: Some(payout_attempt.customer_id.to_owned()),
|
||||
card_network: None,
|
||||
};
|
||||
|
||||
cards::create_payment_method(
|
||||
db,
|
||||
&payment_method,
|
||||
@@ -227,6 +246,7 @@ pub async fn save_payout_data_to_locker(
|
||||
&stored_resp.card_reference,
|
||||
&merchant_account.merchant_id,
|
||||
None,
|
||||
card_details_encrypted,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
|
||||
@@ -159,6 +159,7 @@ impl PaymentMethodInterface for MockDb {
|
||||
payment_method_issuer: payment_method_new.payment_method_issuer,
|
||||
payment_method_issuer_code: payment_method_new.payment_method_issuer_code,
|
||||
metadata: payment_method_new.metadata,
|
||||
payment_method_data: payment_method_new.payment_method_data,
|
||||
};
|
||||
payment_methods.push(payment_method.clone());
|
||||
Ok(payment_method)
|
||||
|
||||
@@ -40,7 +40,7 @@ pub async fn create_payment_method_api(
|
||||
&req,
|
||||
json_payload.into_inner(),
|
||||
|state, auth, req| async move {
|
||||
cards::add_payment_method(state, req, &auth.merchant_account).await
|
||||
cards::add_payment_method(state, req, &auth.merchant_account, &auth.key_store).await
|
||||
},
|
||||
&auth::ApiKeyAuth,
|
||||
)
|
||||
@@ -289,6 +289,7 @@ pub async fn payment_method_update_api(
|
||||
auth.merchant_account,
|
||||
payload,
|
||||
&payment_method_id,
|
||||
auth.key_store,
|
||||
)
|
||||
},
|
||||
&auth::ApiKeyAuth,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use api_models::enums as api_enums;
|
||||
pub use api_models::payment_methods::{
|
||||
CardDetail, CardDetailFromLocker, CustomerPaymentMethod, CustomerPaymentMethodsListResponse,
|
||||
DeleteTokenizeByDateRequest, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest,
|
||||
GetTokenizePayloadResponse, PaymentMethodCreate, PaymentMethodDeleteResponse, PaymentMethodId,
|
||||
PaymentMethodList, PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodResponse,
|
||||
PaymentMethodUpdate, TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1,
|
||||
TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2,
|
||||
CardDetail, CardDetailFromLocker, CardDetailsPaymentMethod, CustomerPaymentMethod,
|
||||
CustomerPaymentMethodsListResponse, DeleteTokenizeByDateRequest, DeleteTokenizeByTokenRequest,
|
||||
GetTokenizePayloadRequest, GetTokenizePayloadResponse, PaymentMethodCreate,
|
||||
PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodList, PaymentMethodListRequest,
|
||||
PaymentMethodListResponse, PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData,
|
||||
TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2,
|
||||
TokenizedWalletValue1, TokenizedWalletValue2,
|
||||
};
|
||||
use error_stack::report;
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
ALTER TABLE payment_methods DROP COLUMN IF EXISTS payment_method_data;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Your SQL goes here
|
||||
ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS payment_method_data BYTEA DEFAULT NULL;
|
||||
Reference in New Issue
Block a user