mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 09:07:09 +08:00
feat(router): Add /apply-payment-method-data endpoint (v2)
This commit is contained in:
@ -1,10 +1,15 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::{collections::HashMap, marker::PhantomData};
|
||||
|
||||
use api_models::payments::{
|
||||
GetPaymentMethodType, PaymentMethodBalanceCheckRequest, PaymentMethodBalanceCheckResponse,
|
||||
ApplyPaymentMethodDataRequest, ApplyPaymentMethodDataResponse, GetPaymentMethodType,
|
||||
PaymentMethodBalanceCheckRequest, PaymentMethodBalanceCheckResponse,
|
||||
};
|
||||
use common_enums::CallConnectorAction;
|
||||
use common_utils::{ext_traits::Encode, id_type, types::MinorUnit};
|
||||
use common_utils::{
|
||||
ext_traits::{Encode, StringExt},
|
||||
id_type,
|
||||
types::MinorUnit,
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::{
|
||||
payments::HeaderPayload,
|
||||
@ -197,6 +202,52 @@ pub async fn payments_check_gift_card_balance_core(
|
||||
Ok(services::ApplicationResponse::Json(resp))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn payments_apply_pm_data_core(
|
||||
state: SessionState,
|
||||
merchant_context: domain::MerchantContext,
|
||||
_req_state: ReqState,
|
||||
req: ApplyPaymentMethodDataRequest,
|
||||
payment_id: id_type::GlobalPaymentId,
|
||||
) -> RouterResponse<ApplyPaymentMethodDataResponse> {
|
||||
let db = state.store.as_ref();
|
||||
let key_manager_state = &(&state).into();
|
||||
let storage_scheme = merchant_context.get_merchant_account().storage_scheme;
|
||||
let payment_intent = db
|
||||
.find_payment_intent_by_id(
|
||||
key_manager_state,
|
||||
&payment_id,
|
||||
merchant_context.get_merchant_key_store(),
|
||||
storage_scheme,
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
|
||||
|
||||
let balances =
|
||||
fetch_payment_methods_balances_from_redis(&state, &payment_intent.id, &req.payment_methods)
|
||||
.await
|
||||
.attach_printable("Failed to retrieve payment method balances from redis")?;
|
||||
|
||||
let total_balance: i64 = balances
|
||||
.values()
|
||||
.map(|value| value.balance.get_amount_as_i64())
|
||||
.sum();
|
||||
|
||||
let remaining_amount = payment_intent
|
||||
.amount_details
|
||||
.order_amount
|
||||
.get_amount_as_i64()
|
||||
.saturating_sub(total_balance);
|
||||
|
||||
let resp = ApplyPaymentMethodDataResponse {
|
||||
remaining_amount: MinorUnit::new(remaining_amount),
|
||||
requires_additional_pm_data: remaining_amount > 0,
|
||||
surcharge_details: None, // TODO: Implement surcharge recalculation logic
|
||||
};
|
||||
|
||||
Ok(services::ApplicationResponse::Json(resp))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn persist_individual_pm_balance_details_in_redis<'a>(
|
||||
state: &SessionState,
|
||||
@ -236,7 +287,59 @@ pub async fn persist_individual_pm_balance_details_in_redis<'a>(
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to write to redis")?;
|
||||
|
||||
logger::debug!("Surcharge results stored in redis with key = {}", redis_key);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fetch_payment_methods_balances_from_redis(
|
||||
state: &SessionState,
|
||||
payment_intent_id: &id_type::GlobalPaymentId,
|
||||
payment_methods: &[api_models::payments::BalanceCheckPaymentMethodData],
|
||||
) -> errors::RouterResult<HashMap<domain::PaymentMethodBalanceKey, domain::PaymentMethodBalanceValue>>
|
||||
{
|
||||
let redis_conn = state
|
||||
.store
|
||||
.get_redis_conn()
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to get redis connection")?;
|
||||
|
||||
let balance_data = domain::PaymentMethodBalanceData::new(payment_intent_id);
|
||||
|
||||
let balance_values: HashMap<String, domain::PaymentMethodBalanceValue> = redis_conn
|
||||
.get_hash_fields::<Vec<(String, String)>>(&balance_data.get_pm_balance_redis_key().into())
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to read payment method balance data from redis")?
|
||||
.into_iter()
|
||||
.map(|(key, value)| {
|
||||
value
|
||||
.parse_struct("PaymentMethodBalanceValue")
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to parse PaymentMethodBalanceValue")
|
||||
.map(|parsed| (key, parsed))
|
||||
})
|
||||
.collect::<errors::RouterResult<HashMap<_, _>>>()?;
|
||||
|
||||
payment_methods
|
||||
.iter()
|
||||
.map(|pm| {
|
||||
let api_models::payments::BalanceCheckPaymentMethodData::GiftCard(gift_card_data) = pm;
|
||||
let pm_balance_key = domain::PaymentMethodBalanceKey {
|
||||
payment_method_type: common_enums::PaymentMethod::GiftCard,
|
||||
payment_method_subtype: gift_card_data.get_payment_method_type(),
|
||||
payment_method_key: domain::GiftCardData::from(gift_card_data.clone())
|
||||
.get_payment_method_key()
|
||||
.expose(),
|
||||
};
|
||||
let redis_key = pm_balance_key.get_redis_key();
|
||||
let balance_value = balance_values.get(&redis_key).cloned().ok_or(
|
||||
errors::ApiErrorResponse::GenericNotFoundError {
|
||||
message: "Balance not found for one or more payment methods".to_string(),
|
||||
},
|
||||
)?;
|
||||
Ok((pm_balance_key, balance_value))
|
||||
})
|
||||
.collect::<errors::RouterResult<HashMap<_, _>>>()
|
||||
}
|
||||
|
||||
@ -797,6 +797,10 @@ impl Payments {
|
||||
.route(web::post().to(payments::payment_check_gift_card_balance)),
|
||||
),
|
||||
)
|
||||
.service(
|
||||
web::resource("/apply-payment-method-data")
|
||||
.route(web::post().to(payments::payments_apply_pm_data)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/finish-redirection/{publishable_key}/{profile_id}")
|
||||
.route(web::get().to(payments::payments_finish_redirection)),
|
||||
|
||||
@ -163,6 +163,7 @@ impl From<Flow> for ApiIdentifier {
|
||||
| Flow::PaymentsCreateIntent
|
||||
| Flow::PaymentsGetIntent
|
||||
| Flow::PaymentMethodBalanceCheck
|
||||
| Flow::ApplyPaymentMethodData
|
||||
| Flow::PaymentsPostSessionTokens
|
||||
| Flow::PaymentsUpdateMetadata
|
||||
| Flow::PaymentsUpdateIntent
|
||||
|
||||
@ -3189,6 +3189,60 @@ pub async fn payment_check_gift_card_balance(
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(feature = "v2")]
|
||||
#[instrument(skip_all, fields(flow = ?Flow::ApplyPaymentMethodData, payment_id))]
|
||||
pub async fn payments_apply_pm_data(
|
||||
state: web::Data<app::AppState>,
|
||||
req: actix_web::HttpRequest,
|
||||
json_payload: web::Json<api_models::payments::ApplyPaymentMethodDataRequest>,
|
||||
path: web::Path<common_utils::id_type::GlobalPaymentId>,
|
||||
) -> impl Responder {
|
||||
let flow = Flow::ApplyPaymentMethodData;
|
||||
|
||||
let global_payment_id = path.into_inner();
|
||||
tracing::Span::current().record("payment_id", global_payment_id.get_string_repr());
|
||||
|
||||
let internal_payload = internal_payload_types::PaymentsGenericRequestWithResourceId {
|
||||
global_payment_id: global_payment_id.clone(),
|
||||
payload: json_payload.into_inner(),
|
||||
};
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
internal_payload,
|
||||
|state, auth: auth::AuthenticationData, req, req_state| async {
|
||||
let payment_id = req.global_payment_id;
|
||||
let request = req.payload;
|
||||
|
||||
let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
|
||||
domain::Context(auth.merchant_account, auth.key_store),
|
||||
));
|
||||
Box::pin(gift_card::payments_apply_pm_data_core(
|
||||
state,
|
||||
merchant_context,
|
||||
req_state,
|
||||
request,
|
||||
payment_id,
|
||||
))
|
||||
.await
|
||||
},
|
||||
auth::api_or_client_auth(
|
||||
&auth::V2ApiKeyAuth {
|
||||
is_connected_allowed: false,
|
||||
is_platform_allowed: false,
|
||||
},
|
||||
&auth::V2ClientAuth(common_utils::types::authentication::ResourceId::Payment(
|
||||
global_payment_id,
|
||||
)),
|
||||
req.headers(),
|
||||
),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(feature = "v2")]
|
||||
#[instrument(skip_all, fields(flow = ?Flow::ProxyConfirmIntent, payment_id))]
|
||||
pub async fn proxy_confirm_intent(
|
||||
|
||||
Reference in New Issue
Block a user