feat(payment_methods_v2): implement a barebones version of list customer payment methods v2 (#6649)

This commit is contained in:
Sanchith Hegde
2024-12-02 15:29:28 +05:30
committed by GitHub
parent 982b26a8c2
commit 797a0db773
15 changed files with 172 additions and 186 deletions

View File

@ -865,9 +865,10 @@ pub async fn create_payment_method(
.attach_printable("Unable to encrypt Payment method billing address")?;
// create pm
let payment_method_id = id_type::GlobalPaymentMethodId::generate("random_cell_id")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to generate GlobalPaymentMethodId")?;
let payment_method_id =
id_type::GlobalPaymentMethodId::generate(&state.conf.cell_information.id)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to generate GlobalPaymentMethodId")?;
let payment_method = create_payment_method_for_intent(
state,
@ -978,9 +979,10 @@ pub async fn payment_method_intent_create(
// create pm entry
let payment_method_id = id_type::GlobalPaymentMethodId::generate("random_cell_id")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to generate GlobalPaymentMethodId")?;
let payment_method_id =
id_type::GlobalPaymentMethodId::generate(&state.conf.cell_information.id)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to generate GlobalPaymentMethodId")?;
let payment_method = create_payment_method_for_intent(
state,
@ -1324,69 +1326,82 @@ pub async fn vault_payment_method(
feature = "customer_v2"
))]
async fn get_pm_list_context(
state: &SessionState,
payment_method: &enums::PaymentMethod,
_key_store: &domain::MerchantKeyStore,
pm: &domain::PaymentMethod,
_parent_payment_method_token: Option<String>,
payment_method_type: enums::PaymentMethod,
payment_method: &domain::PaymentMethod,
is_payment_associated: bool,
) -> Result<Option<PaymentMethodListContext>, error_stack::Report<errors::ApiErrorResponse>> {
let payment_method_retrieval_context = match payment_method {
enums::PaymentMethod::Card => {
let card_details = cards::get_card_details_with_locker_fallback(pm, state).await?;
let payment_method_data = payment_method
.payment_method_data
.clone()
.map(|payment_method_data| payment_method_data.into_inner().expose().into_inner());
card_details.as_ref().map(|card| PaymentMethodListContext {
card_details: Some(card.clone()),
#[cfg(feature = "payouts")]
bank_transfer_details: None,
hyperswitch_token_data: is_payment_associated.then_some(
let payment_method_retrieval_context = match payment_method_data {
Some(payment_methods::PaymentMethodsData::Card(card)) => {
Some(PaymentMethodListContext::Card {
card_details: api::CardDetailFromLocker::from(card),
token_data: is_payment_associated.then_some(
storage::PaymentTokenData::permanent_card(
Some(pm.get_id().clone()),
pm.locker_id
Some(payment_method.get_id().clone()),
payment_method
.locker_id
.as_ref()
.map(|id| id.get_string_repr().clone())
.or(Some(pm.get_id().get_string_repr().to_owned())),
pm.locker_id
.map(|id| id.get_string_repr().to_owned())
.or_else(|| Some(payment_method.get_id().get_string_repr().to_owned())),
payment_method
.locker_id
.as_ref()
.map(|id| id.get_string_repr().clone())
.unwrap_or(pm.get_id().get_string_repr().to_owned()),
.map(|id| id.get_string_repr().to_owned())
.unwrap_or_else(|| {
payment_method.get_id().get_string_repr().to_owned()
}),
),
),
})
}
Some(payment_methods::PaymentMethodsData::BankDetails(bank_details)) => {
let get_bank_account_token_data =
|| -> errors::CustomResult<payment_methods::BankAccountTokenData, errors::ApiErrorResponse> {
let connector_details = bank_details
.connector_details
.first()
.cloned()
.ok_or(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to obtain bank account connector details")?;
let payment_method_subtype = payment_method
.get_payment_method_subtype()
.get_required_value("payment_method_subtype")
.attach_printable("PaymentMethodType not found")?;
Ok(payment_methods::BankAccountTokenData {
payment_method_type: payment_method_subtype,
payment_method: payment_method_type,
connector_details,
})
};
enums::PaymentMethod::BankDebit => {
// Retrieve the pm_auth connector details so that it can be tokenized
let bank_account_token_data = cards::get_bank_account_connector_details(pm)
.await
.unwrap_or_else(|err| {
logger::error!(error=?err);
None
});
let bank_account_token_data = get_bank_account_token_data()
.inspect_err(|error| logger::error!(?error))
.ok();
bank_account_token_data.map(|data| {
let token_data = storage::PaymentTokenData::AuthBankDebit(data);
PaymentMethodListContext {
card_details: None,
#[cfg(feature = "payouts")]
bank_transfer_details: None,
hyperswitch_token_data: is_payment_associated.then_some(token_data),
PaymentMethodListContext::Bank {
token_data: is_payment_associated.then_some(token_data),
}
})
}
_ => Some(PaymentMethodListContext {
card_details: None,
#[cfg(feature = "payouts")]
bank_transfer_details: None,
hyperswitch_token_data: is_payment_associated.then_some(
storage::PaymentTokenData::temporary_generic(generate_id(
consts::ID_LENGTH,
"token",
)),
),
}),
Some(payment_methods::PaymentMethodsData::WalletDetails(_)) | None => {
Some(PaymentMethodListContext::TemporaryToken {
token_data: is_payment_associated.then_some(
storage::PaymentTokenData::temporary_generic(generate_id(
consts::ID_LENGTH,
"token",
)),
),
})
}
};
Ok(payment_method_retrieval_context)
@ -1471,9 +1486,9 @@ pub async fn list_customer_payment_method(
let key_manager_state = &(state).into();
let customer = db
.find_customer_by_merchant_reference_id_merchant_id(
.find_customer_by_global_id(
key_manager_state,
customer_id,
customer_id.get_string_repr(),
merchant_account.get_id(),
&key_store,
merchant_account.storage_scheme,
@ -1509,25 +1524,23 @@ pub async fn list_customer_payment_method(
.to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?;
let mut filtered_saved_payment_methods_ctx = Vec::new();
for pm in saved_payment_methods.into_iter() {
let payment_method = pm
for payment_method in saved_payment_methods.into_iter() {
let payment_method_type = payment_method
.get_payment_method_type()
.get_required_value("payment_method")?;
let parent_payment_method_token =
is_payment_associated.then(|| generate_id(consts::ID_LENGTH, "token"));
let pm_list_context = get_pm_list_context(
state,
&payment_method,
&key_store,
&pm,
parent_payment_method_token.clone(),
is_payment_associated,
)
.await?;
let pm_list_context =
get_pm_list_context(payment_method_type, &payment_method, is_payment_associated)
.await?;
if let Some(ctx) = pm_list_context {
filtered_saved_payment_methods_ctx.push((ctx, parent_payment_method_token, pm));
filtered_saved_payment_methods_ctx.push((
ctx,
parent_payment_method_token,
payment_method,
));
}
}
@ -1576,6 +1589,8 @@ pub async fn list_customer_payment_method(
is_guest_customer: is_payment_associated.then_some(false), //to return this key only when the request is tied to a payment intent
};
/*
TODO: Implement surcharge for v2
if is_payment_associated {
Box::pin(cards::perform_surcharge_ops(
payments_info.as_ref().map(|pi| pi.payment_intent.clone()),
@ -1587,6 +1602,7 @@ pub async fn list_customer_payment_method(
))
.await?;
}
*/
Ok(services::ApplicationResponse::Json(response))
}
@ -1661,15 +1677,20 @@ async fn generate_saved_pm_response(
requires_cvv && !(off_session_payment_flag && pm.connector_mandate_details.is_some())
};
let pmd = if let Some(card) = pm_list_context.card_details.as_ref() {
Some(api::PaymentMethodListData::Card(card.clone()))
} else if cfg!(feature = "payouts") {
pm_list_context
.bank_transfer_details
.clone()
.map(api::PaymentMethodListData::Bank)
} else {
None
let pmd = match &pm_list_context {
PaymentMethodListContext::Card { card_details, .. } => {
Some(api::PaymentMethodListData::Card(card_details.clone()))
}
#[cfg(feature = "payouts")]
PaymentMethodListContext::BankTransfer {
bank_transfer_details,
..
} => Some(api::PaymentMethodListData::Bank(
bank_transfer_details.clone(),
)),
PaymentMethodListContext::Bank { .. } | PaymentMethodListContext::TemporaryToken { .. } => {
None
}
};
let pma = api::CustomerPaymentMethod {
@ -1680,7 +1701,7 @@ async fn generate_saved_pm_response(
payment_method_subtype: pm.get_payment_method_subtype(),
payment_method_data: pmd,
recurring_enabled: mca_enabled,
created: Some(pm.created_at),
created: pm.created_at,
bank: bank_details,
surcharge_details: None,
requires_cvv: requires_cvv
@ -1952,8 +1973,8 @@ impl pm_types::SavedPMLPaymentsInfo {
let token = parent_payment_method_token
.as_ref()
.get_required_value("parent_payment_method_token")?;
let hyperswitch_token_data = pm_list_context
.hyperswitch_token_data
let token_data = pm_list_context
.get_token_data()
.get_required_value("PaymentTokenData")?;
let intent_fulfillment_time = self
@ -1962,7 +1983,7 @@ impl pm_types::SavedPMLPaymentsInfo {
.unwrap_or(common_utils::consts::DEFAULT_INTENT_FULFILLMENT_TIME);
pm_routes::ParentPaymentMethodToken::create_key_for_token((token, pma.payment_method_type))
.insert(intent_fulfillment_time, hyperswitch_token_data, state)
.insert(intent_fulfillment_time, token_data, state)
.await?;
Ok(())

View File

@ -5328,14 +5328,6 @@ pub async fn get_card_details_with_locker_fallback(
})
}
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
pub async fn get_card_details_with_locker_fallback(
pm: &domain::PaymentMethod,
state: &routes::SessionState,
) -> errors::RouterResult<Option<api::CardDetailFromLocker>> {
todo!()
}
#[cfg(all(
any(feature = "v2", feature = "v1"),
not(feature = "payment_methods_v2")
@ -5365,14 +5357,6 @@ pub async fn get_card_details_without_locker_fallback(
})
}
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
pub async fn get_card_details_without_locker_fallback(
pm: &domain::PaymentMethod,
state: &routes::SessionState,
) -> errors::RouterResult<api::CardDetailFromLocker> {
todo!()
}
#[cfg(all(
any(feature = "v2", feature = "v1"),
not(feature = "payment_methods_v2")
@ -5460,13 +5444,13 @@ pub async fn get_masked_bank_details(
}
}
#[cfg(all(
any(feature = "v2", feature = "v1"),
not(feature = "payment_methods_v2")
))]
pub async fn get_bank_account_connector_details(
pm: &domain::PaymentMethod,
) -> errors::RouterResult<Option<BankAccountTokenData>> {
#[cfg(all(
any(feature = "v2", feature = "v1"),
not(feature = "payment_methods_v2")
))]
let payment_method_data = pm
.payment_method_data
.clone()
@ -5481,12 +5465,6 @@ pub async fn get_bank_account_connector_details(
)
.transpose()?;
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
let payment_method_data = pm
.payment_method_data
.clone()
.map(|x| x.into_inner().expose().into_inner());
match payment_method_data {
Some(pmd) => match pmd {
PaymentMethodsData::Card(_) => Err(errors::ApiErrorResponse::UnprocessableEntity {

View File

@ -1019,7 +1019,7 @@ where
.to_validate_request()?
.validate_request(&req, &merchant_account)?;
let payment_id = id_type::GlobalPaymentId::generate(state.conf.cell_information.id.clone());
let payment_id = id_type::GlobalPaymentId::generate(&state.conf.cell_information.id.clone());
tracing::Span::current().record("global_payment_id", payment_id.get_string_repr());

View File

@ -132,7 +132,7 @@ pub fn mk_app(
.service(routes::Forex::server(state.clone()));
}
server_app = server_app.service(routes::Profile::server(state.clone()))
server_app = server_app.service(routes::Profile::server(state.clone()));
}
server_app = server_app
.service(routes::Payments::server(state.clone()))
@ -141,6 +141,11 @@ pub fn mk_app(
.service(routes::MerchantConnectorAccount::server(state.clone()))
.service(routes::Webhooks::server(state.clone()));
#[cfg(feature = "oltp")]
{
server_app = server_app.service(routes::PaymentMethods::server(state.clone()));
}
#[cfg(feature = "v1")]
{
server_app = server_app
@ -157,8 +162,6 @@ pub fn mk_app(
{
server_app = server_app
.service(routes::EphemeralKey::server(state.clone()))
.service(routes::Webhooks::server(state.clone()))
.service(routes::PaymentMethods::server(state.clone()))
.service(routes::Poll::server(state.clone()))
}

View File

@ -954,6 +954,10 @@ pub struct Customers;
impl Customers {
pub fn server(state: AppState) -> Scope {
let mut route = web::scope("/v2/customers").app_data(web::Data::new(state));
#[cfg(all(feature = "olap", feature = "v2", feature = "customer_v2"))]
{
route = route.service(web::resource("/list").route(web::get().to(customers_list)))
}
#[cfg(all(feature = "oltp", feature = "v2", feature = "customer_v2"))]
{
route = route
@ -965,10 +969,6 @@ impl Customers {
.route(web::delete().to(customers_delete)),
)
}
#[cfg(all(feature = "olap", feature = "v2", feature = "customer_v2"))]
{
route = route.service(web::resource("/list").route(web::get().to(customers_list)))
}
#[cfg(all(feature = "oltp", feature = "v2", feature = "payment_methods_v2"))]
{
route = route.service(

View File

@ -1323,7 +1323,6 @@ where
#[derive(Debug)]
pub struct EphemeralKeyAuth;
// #[cfg(feature = "v1")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for EphemeralKeyAuth
where
@ -2987,7 +2986,6 @@ pub fn get_auth_type_and_flow<A: SessionStateInfo + Sync + Send>(
Ok((Box::new(HeaderAuth(ApiKeyAuth)), api::AuthFlow::Merchant))
}
#[cfg(feature = "v1")]
pub fn check_client_secret_and_get_auth<T>(
headers: &HeaderMap,
payload: &impl ClientSecretFetch,
@ -3023,44 +3021,6 @@ where
Ok((Box::new(HeaderAuth(ApiKeyAuth)), api::AuthFlow::Merchant))
}
#[cfg(feature = "v2")]
pub fn check_client_secret_and_get_auth<T>(
headers: &HeaderMap,
payload: &impl ClientSecretFetch,
) -> RouterResult<(
Box<dyn AuthenticateAndFetch<AuthenticationData, T>>,
api::AuthFlow,
)>
where
T: SessionStateInfo + Sync + Send,
ApiKeyAuth: AuthenticateAndFetch<AuthenticationData, T>,
PublishableKeyAuth: AuthenticateAndFetch<AuthenticationData, T>,
{
let api_key = get_api_key(headers)?;
if api_key.starts_with("pk_") {
payload
.get_client_secret()
.check_value_present("client_secret")
.map_err(|_| errors::ApiErrorResponse::MissingRequiredField {
field_name: "client_secret",
})?;
return Ok((
Box::new(HeaderAuth(PublishableKeyAuth)),
api::AuthFlow::Client,
));
}
if payload.get_client_secret().is_some() {
return Err(errors::ApiErrorResponse::InvalidRequestData {
message: "client_secret is not a valid parameter".to_owned(),
}
.into());
}
Ok((Box::new(HeaderAuth(ApiKeyAuth)), api::AuthFlow::Merchant))
}
#[cfg(feature = "v1")]
pub async fn get_ephemeral_or_other_auth<T>(
headers: &HeaderMap,
is_merchant_flow: bool,
@ -3093,7 +3053,6 @@ where
}
}
#[cfg(feature = "v1")]
pub fn is_ephemeral_auth<A: SessionStateInfo + Sync + Send>(
headers: &HeaderMap,
) -> RouterResult<Box<dyn AuthenticateAndFetch<AuthenticationData, A>>> {
@ -3106,13 +3065,6 @@ pub fn is_ephemeral_auth<A: SessionStateInfo + Sync + Send>(
}
}
#[cfg(feature = "v2")]
pub fn is_ephemeral_auth<A: SessionStateInfo + Sync + Send>(
headers: &HeaderMap,
) -> RouterResult<Box<dyn AuthenticateAndFetch<AuthenticationData, A>>> {
todo!()
}
pub fn is_jwt_auth(headers: &HeaderMap) -> bool {
headers.get(headers::AUTHORIZATION).is_some()
|| get_cookie_from_header(headers)

View File

@ -105,6 +105,10 @@ impl PaymentTokenData {
}
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "payment_methods_v2")
))]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaymentMethodListContext {
pub card_details: Option<api::CardDetailFromLocker>,
@ -113,6 +117,38 @@ pub struct PaymentMethodListContext {
pub bank_transfer_details: Option<api::BankPayout>,
}
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum PaymentMethodListContext {
Card {
card_details: api::CardDetailFromLocker,
token_data: Option<PaymentTokenData>,
},
Bank {
token_data: Option<PaymentTokenData>,
},
#[cfg(feature = "payouts")]
BankTransfer {
bank_transfer_details: api::BankPayout,
token_data: Option<PaymentTokenData>,
},
TemporaryToken {
token_data: Option<PaymentTokenData>,
},
}
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
impl PaymentMethodListContext {
pub(crate) fn get_token_data(&self) -> Option<PaymentTokenData> {
match self {
Self::Card { token_data, .. }
| Self::Bank { token_data }
| Self::BankTransfer { token_data, .. }
| Self::TemporaryToken { token_data } => token_data.clone(),
}
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PaymentMethodStatusTrackingData {
pub payment_method_id: String,