mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
refactor(payment_methods): revamp payment methods update endpoint (#4305)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -76,7 +76,7 @@ pub struct PaymentMethodUpdate {
|
||||
"card_exp_month": "10",
|
||||
"card_exp_year": "25",
|
||||
"card_holder_name": "John Doe"}))]
|
||||
pub card: Option<CardDetail>,
|
||||
pub card: Option<CardDetailUpdate>,
|
||||
|
||||
/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
|
||||
#[schema(value_type = Option<CardNetwork>,example = "Visa")]
|
||||
@ -95,6 +95,10 @@ pub struct PaymentMethodUpdate {
|
||||
/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
|
||||
#[schema(value_type = Option<Object>,example = json!({ "city": "NY", "unit": "245" }))]
|
||||
pub metadata: Option<pii::SecretSerdeValue>,
|
||||
|
||||
/// This is a 15 minute expiry token which shall be used from the client to authenticate and perform sessions from the SDK
|
||||
#[schema(max_length = 30, min_length = 30, example = "secret_k2uj3he2893eiu2d")]
|
||||
pub client_secret: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
|
||||
@ -134,6 +138,54 @@ pub struct CardDetail {
|
||||
pub card_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CardDetailUpdate {
|
||||
/// Card Expiry Month
|
||||
#[schema(value_type = String,example = "10")]
|
||||
pub card_exp_month: Option<masking::Secret<String>>,
|
||||
|
||||
/// Card Expiry Year
|
||||
#[schema(value_type = String,example = "25")]
|
||||
pub card_exp_year: Option<masking::Secret<String>>,
|
||||
|
||||
/// Card Holder Name
|
||||
#[schema(value_type = String,example = "John Doe")]
|
||||
pub card_holder_name: Option<masking::Secret<String>>,
|
||||
|
||||
/// Card Holder's Nick Name
|
||||
#[schema(value_type = Option<String>,example = "John Doe")]
|
||||
pub nick_name: Option<masking::Secret<String>>,
|
||||
}
|
||||
|
||||
impl CardDetailUpdate {
|
||||
pub fn apply(&self, card_data_from_locker: Card) -> CardDetail {
|
||||
CardDetail {
|
||||
card_number: card_data_from_locker.card_number,
|
||||
card_exp_month: self
|
||||
.card_exp_month
|
||||
.clone()
|
||||
.unwrap_or(card_data_from_locker.card_exp_month),
|
||||
card_exp_year: self
|
||||
.card_exp_year
|
||||
.clone()
|
||||
.unwrap_or(card_data_from_locker.card_exp_year),
|
||||
card_holder_name: self
|
||||
.card_holder_name
|
||||
.clone()
|
||||
.or(card_data_from_locker.name_on_card),
|
||||
nick_name: self
|
||||
.nick_name
|
||||
.clone()
|
||||
.or(card_data_from_locker.nick_name.map(masking::Secret::new)),
|
||||
card_issuing_country: None,
|
||||
card_network: None,
|
||||
card_issuer: None,
|
||||
card_type: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)]
|
||||
pub struct PaymentMethodResponse {
|
||||
/// Unique identifier for a merchant
|
||||
@ -242,6 +294,17 @@ pub struct BankAccountConnectorDetails {
|
||||
pub enum BankAccountAccessCreds {
|
||||
AccessToken(masking::Secret<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct Card {
|
||||
pub card_number: CardNumber,
|
||||
pub name_on_card: Option<masking::Secret<String>>,
|
||||
pub card_exp_month: masking::Secret<String>,
|
||||
pub card_exp_year: masking::Secret<String>,
|
||||
pub card_brand: Option<String>,
|
||||
pub card_isin: Option<String>,
|
||||
pub nick_name: Option<String>,
|
||||
}
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
|
||||
pub struct CardDetailFromLocker {
|
||||
pub scheme: Option<String>,
|
||||
|
||||
@ -204,6 +204,7 @@ Never share your secret api keys. Keep them guarded and secure.
|
||||
api_models::payment_methods::CustomerDefaultPaymentMethodResponse,
|
||||
api_models::payment_methods::CardDetailFromLocker,
|
||||
api_models::payment_methods::CardDetail,
|
||||
api_models::payment_methods::CardDetailUpdate,
|
||||
api_models::payment_methods::RequestPaymentMethodTypes,
|
||||
api_models::customers::CustomerResponse,
|
||||
api_models::admin::AcceptedCountries,
|
||||
|
||||
@ -7,8 +7,8 @@ use api_models::{
|
||||
admin::{self, PaymentMethodsEnabled},
|
||||
enums::{self as api_enums},
|
||||
payment_methods::{
|
||||
BankAccountTokenData, CardDetailsPaymentMethod, CardNetworkTypes, CountryCodeWithName,
|
||||
CustomerDefaultPaymentMethodResponse, ListCountriesCurrenciesRequest,
|
||||
BankAccountTokenData, Card, CardDetailUpdate, CardDetailsPaymentMethod, CardNetworkTypes,
|
||||
CountryCodeWithName, CustomerDefaultPaymentMethodResponse, ListCountriesCurrenciesRequest,
|
||||
ListCountriesCurrenciesResponse, MaskedBankDetails, PaymentExperienceTypes,
|
||||
PaymentMethodsData, RequestPaymentMethodTypes, RequiredFieldInfo,
|
||||
ResponsePaymentMethodIntermediate, ResponsePaymentMethodTypes,
|
||||
@ -44,10 +44,7 @@ use crate::{
|
||||
configs::settings,
|
||||
core::{
|
||||
errors::{self, StorageErrorExt},
|
||||
payment_methods::{
|
||||
transformers::{self as payment_methods},
|
||||
vault,
|
||||
},
|
||||
payment_methods::{transformers as payment_methods, vault},
|
||||
payments::{
|
||||
helpers,
|
||||
routing::{self, SessionFlowRoutingInput},
|
||||
@ -270,7 +267,15 @@ pub async fn add_payment_method(
|
||||
},
|
||||
api_enums::PaymentMethod::Card => match req.card.clone() {
|
||||
Some(card) => {
|
||||
add_card_to_locker(&state, req.clone(), &card, &customer_id, merchant_account)
|
||||
helpers::validate_card_expiry(&card.card_exp_month, &card.card_exp_year)?;
|
||||
add_card_to_locker(
|
||||
&state,
|
||||
req.clone(),
|
||||
&card,
|
||||
&customer_id,
|
||||
merchant_account,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Add Card Failed")
|
||||
@ -472,15 +477,91 @@ pub async fn update_customer_payment_method(
|
||||
payment_method_id: &str,
|
||||
key_store: domain::MerchantKeyStore,
|
||||
) -> errors::RouterResponse<api::PaymentMethodResponse> {
|
||||
// Currently update is supported only for cards
|
||||
if let Some(card_update) = req.card.clone() {
|
||||
let db = state.store.as_ref();
|
||||
|
||||
let pm = db
|
||||
.delete_payment_method_by_merchant_id_payment_method_id(
|
||||
&merchant_account.merchant_id,
|
||||
payment_method_id,
|
||||
)
|
||||
.find_payment_method(payment_method_id, merchant_account.storage_scheme)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;
|
||||
if pm.payment_method == enums::PaymentMethod::Card {
|
||||
|
||||
// Fetch the existing payment method data from db
|
||||
let existing_card_data = decrypt::<serde_json::Value, masking::WithType>(
|
||||
pm.payment_method_data.clone(),
|
||||
key_store.key.get_inner().peek(),
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to decrypt card details")?
|
||||
.map(|x| x.into_inner().expose())
|
||||
.map(
|
||||
|value| -> Result<PaymentMethodsData, error_stack::Report<errors::ApiErrorResponse>> {
|
||||
value
|
||||
.parse_value::<PaymentMethodsData>("PaymentMethodsData")
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to deserialize payment methods data")
|
||||
},
|
||||
)
|
||||
.transpose()?
|
||||
.and_then(|pmd| match pmd {
|
||||
PaymentMethodsData::Card(crd) => Some(api::CardDetailFromLocker::from(crd)),
|
||||
_ => None,
|
||||
})
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to obtain decrypted card object from db")?;
|
||||
|
||||
let is_card_updation_required =
|
||||
validate_payment_method_update(card_update.clone(), existing_card_data.clone());
|
||||
|
||||
let response = if is_card_updation_required {
|
||||
// Fetch the existing card data from locker for getting card number
|
||||
let card_data_from_locker = get_card_from_locker(
|
||||
&state,
|
||||
&pm.customer_id,
|
||||
&pm.merchant_id,
|
||||
pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Error getting card from locker")?;
|
||||
|
||||
if card_update.card_exp_month.is_some() || card_update.card_exp_year.is_some() {
|
||||
helpers::validate_card_expiry(
|
||||
card_update
|
||||
.card_exp_month
|
||||
.as_ref()
|
||||
.unwrap_or(&card_data_from_locker.card_exp_month),
|
||||
card_update
|
||||
.card_exp_year
|
||||
.as_ref()
|
||||
.unwrap_or(&card_data_from_locker.card_exp_year),
|
||||
)?;
|
||||
}
|
||||
|
||||
let updated_card_details = card_update.apply(card_data_from_locker.clone());
|
||||
|
||||
// Construct new payment method object from request
|
||||
let new_pm = api::PaymentMethodCreate {
|
||||
payment_method: pm.payment_method,
|
||||
payment_method_type: pm.payment_method_type,
|
||||
payment_method_issuer: pm.payment_method_issuer.clone(),
|
||||
payment_method_issuer_code: pm.payment_method_issuer_code,
|
||||
#[cfg(feature = "payouts")]
|
||||
bank_transfer: req.bank_transfer,
|
||||
card: Some(updated_card_details.clone()),
|
||||
#[cfg(feature = "payouts")]
|
||||
wallet: req.wallet,
|
||||
metadata: req.metadata,
|
||||
customer_id: Some(pm.customer_id.clone()),
|
||||
card_network: req
|
||||
.card_network
|
||||
.as_ref()
|
||||
.map(|card_network| card_network.to_string()),
|
||||
};
|
||||
new_pm.validate()?;
|
||||
|
||||
// Delete old payment method from locker
|
||||
delete_card_from_locker(
|
||||
&state,
|
||||
&pm.customer_id,
|
||||
@ -488,31 +569,137 @@ pub async fn update_customer_payment_method(
|
||||
pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Add the updated payment method data to locker
|
||||
let (mut add_card_resp, _) = add_card_to_locker(
|
||||
&state,
|
||||
new_pm.clone(),
|
||||
&updated_card_details,
|
||||
&pm.customer_id,
|
||||
&merchant_account,
|
||||
Some(pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id)),
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to add updated payment method to locker")?;
|
||||
|
||||
// Construct new updated card object. Consider a field if passed in request or else populate it with the existing value from existing_card_data
|
||||
let updated_card = Some(api::CardDetailFromLocker {
|
||||
scheme: existing_card_data.scheme,
|
||||
last4_digits: Some(card_data_from_locker.card_number.clone().get_last4()),
|
||||
issuer_country: existing_card_data.issuer_country,
|
||||
card_number: existing_card_data.card_number,
|
||||
expiry_month: card_update
|
||||
.card_exp_month
|
||||
.or(existing_card_data.expiry_month),
|
||||
expiry_year: card_update.card_exp_year.or(existing_card_data.expiry_year),
|
||||
card_token: existing_card_data.card_token,
|
||||
card_fingerprint: existing_card_data.card_fingerprint,
|
||||
card_holder_name: card_update
|
||||
.card_holder_name
|
||||
.or(existing_card_data.card_holder_name),
|
||||
nick_name: card_update.nick_name.or(existing_card_data.nick_name),
|
||||
card_network: existing_card_data.card_network,
|
||||
card_isin: existing_card_data.card_isin,
|
||||
card_issuer: existing_card_data.card_issuer,
|
||||
card_type: existing_card_data.card_type,
|
||||
saved_to_locker: true,
|
||||
});
|
||||
|
||||
let updated_pmd = updated_card
|
||||
.as_ref()
|
||||
.map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())));
|
||||
let pm_data_encrypted =
|
||||
create_encrypted_payment_method_data(&key_store, updated_pmd).await;
|
||||
|
||||
let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate {
|
||||
payment_method_data: pm_data_encrypted,
|
||||
};
|
||||
let new_pm = api::PaymentMethodCreate {
|
||||
|
||||
add_card_resp.payment_method_id = pm.payment_method_id.clone();
|
||||
|
||||
db.update_payment_method(pm, pm_update, merchant_account.storage_scheme)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to update payment method in db")?;
|
||||
|
||||
add_card_resp
|
||||
} else {
|
||||
// Return existing payment method data as response without any changes
|
||||
api::PaymentMethodResponse {
|
||||
merchant_id: pm.merchant_id.to_owned(),
|
||||
customer_id: Some(pm.customer_id),
|
||||
payment_method_id: pm.payment_method_id,
|
||||
payment_method: pm.payment_method,
|
||||
payment_method_type: pm.payment_method_type,
|
||||
payment_method_issuer: pm.payment_method_issuer,
|
||||
payment_method_issuer_code: pm.payment_method_issuer_code,
|
||||
#[cfg(feature = "payouts")]
|
||||
bank_transfer: req.bank_transfer,
|
||||
card: req.card,
|
||||
#[cfg(feature = "payouts")]
|
||||
wallet: req.wallet,
|
||||
metadata: req.metadata,
|
||||
customer_id: Some(pm.customer_id),
|
||||
card_network: req
|
||||
.card_network
|
||||
.as_ref()
|
||||
.map(|card_network| card_network.to_string()),
|
||||
bank_transfer: None,
|
||||
card: Some(existing_card_data),
|
||||
metadata: pm.metadata,
|
||||
created: Some(pm.created_at),
|
||||
recurring_enabled: false,
|
||||
installment_payment_enabled: false,
|
||||
payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
|
||||
last_used_at: Some(common_utils::date_time::now()),
|
||||
}
|
||||
};
|
||||
Box::pin(add_payment_method(
|
||||
state,
|
||||
new_pm,
|
||||
&merchant_account,
|
||||
&key_store,
|
||||
))
|
||||
.await
|
||||
|
||||
Ok(services::ApplicationResponse::Json(response))
|
||||
} else {
|
||||
Err(report!(errors::ApiErrorResponse::NotSupported {
|
||||
message: "Payment method update for the given payment method is not supported".into()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_payment_method_update(
|
||||
card_updation_obj: CardDetailUpdate,
|
||||
existing_card_data: api::CardDetailFromLocker,
|
||||
) -> bool {
|
||||
// Return true If any one of the below condition returns true,
|
||||
// If a field is not passed in the update request, return false.
|
||||
// If the field is present, it depends on the existing field data:
|
||||
// - If existing field data is not present, or if it is present and doesn't match
|
||||
// the update request data, then return true.
|
||||
// - Or else return false
|
||||
card_updation_obj
|
||||
.card_exp_month
|
||||
.map(|exp_month| exp_month.expose())
|
||||
.map_or(false, |new_exp_month| {
|
||||
existing_card_data
|
||||
.expiry_month
|
||||
.map(|exp_month| exp_month.expose())
|
||||
.map_or(true, |old_exp_month| new_exp_month != old_exp_month)
|
||||
})
|
||||
|| card_updation_obj
|
||||
.card_exp_year
|
||||
.map(|exp_year| exp_year.expose())
|
||||
.map_or(false, |new_exp_year| {
|
||||
existing_card_data
|
||||
.expiry_year
|
||||
.map(|exp_year| exp_year.expose())
|
||||
.map_or(true, |old_exp_year| new_exp_year != old_exp_year)
|
||||
})
|
||||
|| card_updation_obj
|
||||
.card_holder_name
|
||||
.map(|name| name.expose())
|
||||
.map_or(false, |new_card_holder_name| {
|
||||
existing_card_data
|
||||
.card_holder_name
|
||||
.map(|name| name.expose())
|
||||
.map_or(true, |old_card_holder_name| {
|
||||
new_card_holder_name != old_card_holder_name
|
||||
})
|
||||
})
|
||||
|| card_updation_obj
|
||||
.nick_name
|
||||
.map(|nick_name| nick_name.expose())
|
||||
.map_or(false, |new_nick_name| {
|
||||
existing_card_data
|
||||
.nick_name
|
||||
.map(|nick_name| nick_name.expose())
|
||||
.map_or(true, |old_nick_name| new_nick_name != old_nick_name)
|
||||
})
|
||||
}
|
||||
|
||||
// Wrapper function to switch lockers
|
||||
@ -588,6 +775,7 @@ pub async fn add_card_to_locker(
|
||||
card: &api::CardDetail,
|
||||
customer_id: &String,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
card_reference: Option<&str>,
|
||||
) -> errors::CustomResult<
|
||||
(
|
||||
api::PaymentMethodResponse,
|
||||
@ -605,7 +793,7 @@ pub async fn add_card_to_locker(
|
||||
customer_id.to_string(),
|
||||
merchant_account,
|
||||
api_enums::LockerChoice::HyperswitchCardVault,
|
||||
None,
|
||||
card_reference,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
@ -634,7 +822,7 @@ pub async fn get_card_from_locker(
|
||||
customer_id: &str,
|
||||
merchant_id: &str,
|
||||
card_reference: &str,
|
||||
) -> errors::RouterResult<payment_methods::Card> {
|
||||
) -> errors::RouterResult<Card> {
|
||||
metrics::GET_FROM_LOCKER.add(&metrics::CONTEXT, 1, &[]);
|
||||
|
||||
let get_card_from_rs_locker_resp = request::record_operation_time(
|
||||
@ -713,7 +901,7 @@ pub async fn add_card_hs(
|
||||
merchant_id: &merchant_account.merchant_id,
|
||||
merchant_customer_id: customer_id.to_owned(),
|
||||
requestor_card_reference: card_reference.map(str::to_string),
|
||||
card: payment_methods::Card {
|
||||
card: Card {
|
||||
card_number: card.card_number.to_owned(),
|
||||
name_on_card: card.card_holder_name.to_owned(),
|
||||
card_exp_month: card.card_exp_month.to_owned(),
|
||||
@ -899,7 +1087,7 @@ pub async fn get_card_from_hs_locker<'a>(
|
||||
merchant_id: &str,
|
||||
card_reference: &'a str,
|
||||
locker_choice: api_enums::LockerChoice,
|
||||
) -> errors::CustomResult<payment_methods::Card, errors::VaultError> {
|
||||
) -> errors::CustomResult<Card, errors::VaultError> {
|
||||
let locker = &state.conf.locker;
|
||||
let jwekey = &state.conf.jwekey.get_inner();
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use api_models::enums as api_enums;
|
||||
use api_models::{enums as api_enums, payment_methods::Card};
|
||||
use common_utils::{
|
||||
ext_traits::{Encode, StringExt},
|
||||
pii::Email,
|
||||
@ -53,17 +53,6 @@ pub struct StoreGenericReq<'a> {
|
||||
pub enc_data: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Card {
|
||||
pub card_number: cards::CardNumber,
|
||||
pub name_on_card: Option<Secret<String>>,
|
||||
pub card_exp_month: Secret<String>,
|
||||
pub card_exp_year: Secret<String>,
|
||||
pub card_brand: Option<String>,
|
||||
pub card_isin: Option<String>,
|
||||
pub nick_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct StoreCardResp {
|
||||
pub status: String,
|
||||
|
||||
@ -803,8 +803,17 @@ pub fn validate_card_data(
|
||||
},
|
||||
)?;
|
||||
|
||||
let exp_month = card
|
||||
.card_exp_month
|
||||
validate_card_expiry(&card.card_exp_month, &card.card_exp_year)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn validate_card_expiry(
|
||||
card_exp_month: &masking::Secret<String>,
|
||||
card_exp_year: &masking::Secret<String>,
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
let exp_month = card_exp_month
|
||||
.peek()
|
||||
.to_string()
|
||||
.parse::<u8>()
|
||||
@ -816,7 +825,8 @@ pub fn validate_card_data(
|
||||
message: "Invalid Expiry Month".to_string(),
|
||||
},
|
||||
)?;
|
||||
let mut year_str = card.card_exp_year.peek().to_string();
|
||||
|
||||
let mut year_str = card_exp_year.peek().to_string();
|
||||
if year_str.len() == 2 {
|
||||
year_str = format!("20{}", year_str);
|
||||
}
|
||||
@ -843,7 +853,7 @@ pub fn validate_card_data(
|
||||
message: "Card Expired".to_string()
|
||||
}))?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@ -648,6 +648,7 @@ pub async fn save_in_locker(
|
||||
&card,
|
||||
&customer_id,
|
||||
merchant_account,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use api_models::{enums, payouts};
|
||||
use api_models::{enums, payment_methods::Card, payouts};
|
||||
use common_utils::{
|
||||
errors::CustomResult,
|
||||
ext_traits::{AsyncExt, StringExt},
|
||||
@ -14,9 +14,7 @@ use crate::{
|
||||
errors::{self, RouterResult, StorageErrorExt},
|
||||
payment_methods::{
|
||||
cards,
|
||||
transformers::{
|
||||
self, DataDuplicationCheck, StoreCardReq, StoreGenericReq, StoreLockerReq,
|
||||
},
|
||||
transformers::{DataDuplicationCheck, StoreCardReq, StoreGenericReq, StoreLockerReq},
|
||||
vault,
|
||||
},
|
||||
payments::{
|
||||
@ -212,7 +210,7 @@ pub async fn save_payout_data_to_locker(
|
||||
let payload = StoreLockerReq::LockerCard(StoreCardReq {
|
||||
merchant_id: merchant_account.merchant_id.as_ref(),
|
||||
merchant_customer_id: payout_attempt.customer_id.to_owned(),
|
||||
card: transformers::Card {
|
||||
card: Card {
|
||||
card_number: card.card_number.to_owned(),
|
||||
name_on_card: card.card_holder_name.to_owned(),
|
||||
card_exp_month: card.expiry_month.to_owned(),
|
||||
|
||||
@ -776,9 +776,12 @@ impl PaymentMethods {
|
||||
.service(
|
||||
web::resource("/{payment_method_id}")
|
||||
.route(web::get().to(payment_method_retrieve_api))
|
||||
.route(web::post().to(payment_method_update_api))
|
||||
.route(web::delete().to(payment_method_delete_api)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/{payment_method_id}/update")
|
||||
.route(web::post().to(payment_method_update_api)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/auth/link").route(web::post().to(pm_auth::link_token_create)),
|
||||
)
|
||||
|
||||
@ -219,22 +219,28 @@ pub async fn payment_method_update_api(
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::PaymentMethodsUpdate;
|
||||
let payment_method_id = path.into_inner();
|
||||
let payload = json_payload.into_inner();
|
||||
|
||||
let (auth, _) = match auth::check_client_secret_and_get_auth(req.headers(), &payload) {
|
||||
Ok((auth, _auth_flow)) => (auth, _auth_flow),
|
||||
Err(e) => return api::log_and_return_error_response(e),
|
||||
};
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
json_payload.into_inner(),
|
||||
|state, auth, payload, _| {
|
||||
payload,
|
||||
|state, auth, req, _| {
|
||||
cards::update_customer_payment_method(
|
||||
state,
|
||||
auth.merchant_account,
|
||||
payload,
|
||||
req,
|
||||
&payment_method_id,
|
||||
auth.key_store,
|
||||
)
|
||||
},
|
||||
&auth::ApiKeyAuth,
|
||||
&*auth,
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
|
||||
@ -862,6 +862,12 @@ impl ClientSecretFetch for api_models::pm_auth::ExchangeTokenCreateRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientSecretFetch for api_models::payment_methods::PaymentMethodUpdate {
|
||||
fn get_client_secret(&self) -> Option<&String> {
|
||||
self.client_secret.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_auth_type_and_flow<A: AppStateInfo + Sync>(
|
||||
headers: &HeaderMap,
|
||||
) -> RouterResult<(
|
||||
|
||||
@ -6994,6 +6994,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"CardDetailUpdate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"card_exp_month",
|
||||
"card_exp_year",
|
||||
"card_holder_name"
|
||||
],
|
||||
"properties": {
|
||||
"card_exp_month": {
|
||||
"type": "string",
|
||||
"description": "Card Expiry Month",
|
||||
"example": "10"
|
||||
},
|
||||
"card_exp_year": {
|
||||
"type": "string",
|
||||
"description": "Card Expiry Year",
|
||||
"example": "25"
|
||||
},
|
||||
"card_holder_name": {
|
||||
"type": "string",
|
||||
"description": "Card Holder Name",
|
||||
"example": "John Doe"
|
||||
},
|
||||
"nick_name": {
|
||||
"type": "string",
|
||||
"description": "Card Holder's Nick Name",
|
||||
"example": "John Doe",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"CardNetwork": {
|
||||
"type": "string",
|
||||
"description": "Indicates the card network.",
|
||||
@ -12912,7 +12944,7 @@
|
||||
"card": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/CardDetail"
|
||||
"$ref": "#/components/schemas/CardDetailUpdate"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
@ -12945,6 +12977,14 @@
|
||||
"type": "object",
|
||||
"description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.",
|
||||
"nullable": true
|
||||
},
|
||||
"client_secret": {
|
||||
"type": "string",
|
||||
"description": "This is a 15 minute expiry token which shall be used from the client to authenticate and perform sessions from the SDK",
|
||||
"example": "secret_k2uj3he2893eiu2d",
|
||||
"nullable": true,
|
||||
"maxLength": 30,
|
||||
"minLength": 30
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
Reference in New Issue
Block a user