mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 02:57:02 +08:00
feat(payment_methods_v2): implement a barebones version of list customer payment methods v2 (#6649)
This commit is contained in:
@ -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(())
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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());
|
||||
|
||||
|
||||
@ -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()))
|
||||
}
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user