feat(openapi): add payment get to openapi (#6539)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: hrithikesh026 <hrithikesh.vm@juspay.in>
This commit is contained in:
Narayan Bhat
2024-11-13 15:23:40 +05:30
committed by GitHub
parent b82e7429e2
commit 600cf44684
14 changed files with 541 additions and 95 deletions

View File

@ -51,8 +51,8 @@ pub mod routes {
impl Analytics {
#[cfg(feature = "v2")]
pub fn server(_state: AppState) -> Scope {
todo!()
pub fn server(state: AppState) -> Scope {
web::scope("/analytics").app_data(web::Data::new(state))
}
#[cfg(feature = "v1")]
pub fn server(state: AppState) -> Scope {

View File

@ -2969,19 +2969,11 @@ pub async fn retrieve_connector(
#[cfg(all(feature = "olap", feature = "v2"))]
pub async fn list_connectors_for_a_profile(
state: SessionState,
merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
profile_id: id_type::ProfileId,
) -> RouterResponse<Vec<api_models::admin::MerchantConnectorListResponse>> {
let store = state.store.as_ref();
let key_manager_state = &(&state).into();
let key_store = store
.get_merchant_key_store_by_merchant_id(
key_manager_state,
merchant_account.get_id(),
&store.get_master_key().to_vec().into(),
)
.await
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
let merchant_connector_accounts = store
.list_connector_account_by_profile_id(key_manager_state, &profile_id, &key_store)

View File

@ -125,15 +125,19 @@ pub fn mk_app(
{
// This is a more specific route as compared to `MerchantConnectorAccount`
// so it is registered before `MerchantConnectorAccount`.
server_app = server_app
.service(routes::ProfileNew::server(state.clone()))
.service(routes::Profile::server(state.clone()))
#[cfg(feature = "v1")]
{
server_app = server_app
.service(routes::ProfileNew::server(state.clone()))
.service(routes::Forex::server(state.clone()));
}
server_app = server_app.service(routes::Profile::server(state.clone()))
}
server_app = server_app
.service(routes::Payments::server(state.clone()))
.service(routes::Customers::server(state.clone()))
.service(routes::Configs::server(state.clone()))
.service(routes::Forex::server(state.clone()))
.service(routes::MerchantConnectorAccount::server(state.clone()));
#[cfg(feature = "v1")]
@ -163,7 +167,6 @@ pub fn mk_app(
.service(routes::Organization::server(state.clone()))
.service(routes::MerchantAccount::server(state.clone()))
.service(routes::ApiKeys::server(state.clone()))
.service(routes::Analytics::server(state.clone()))
.service(routes::Routing::server(state.clone()));
#[cfg(feature = "v1")]
@ -178,11 +181,12 @@ pub fn mk_app(
.service(routes::User::server(state.clone()))
.service(routes::ConnectorOnboarding::server(state.clone()))
.service(routes::Verify::server(state.clone()))
.service(routes::Analytics::server(state.clone()))
.service(routes::WebhookEvents::server(state.clone()));
}
}
#[cfg(feature = "payouts")]
#[cfg(all(feature = "payouts", feature = "v1"))]
{
server_app = server_app
.service(routes::Payouts::server(state.clone()))
@ -195,7 +199,9 @@ pub fn mk_app(
not(feature = "customer_v2")
))]
{
server_app = server_app.service(routes::StripeApis::server(state.clone()));
server_app = server_app
.service(routes::StripeApis::server(state.clone()))
.service(routes::Cards::server(state.clone()));
}
#[cfg(all(feature = "recon", feature = "v1"))]
@ -203,7 +209,6 @@ pub fn mk_app(
server_app = server_app.service(routes::Recon::server(state.clone()));
}
server_app = server_app.service(routes::Cards::server(state.clone()));
server_app = server_app.service(routes::Cache::server(state.clone()));
server_app = server_app.service(routes::Health::server(state.clone()));

View File

@ -61,13 +61,11 @@ pub mod webhooks;
#[cfg(feature = "dummy_connector")]
pub use self::app::DummyConnector;
#[cfg(any(feature = "olap", feature = "oltp"))]
pub use self::app::Forex;
#[cfg(all(feature = "olap", feature = "recon", feature = "v1"))]
pub use self::app::Recon;
pub use self::app::{
ApiKeys, AppState, ApplePayCertificatesMigration, Cache, Cards, Configs, ConnectorOnboarding,
Customers, Disputes, EphemeralKey, Files, Gsm, Health, Mandates, MerchantAccount,
Customers, Disputes, EphemeralKey, Files, Forex, Gsm, Health, Mandates, MerchantAccount,
MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Poll, Profile, ProfileNew,
Refunds, SessionState, User, Webhooks,
};

View File

@ -458,14 +458,14 @@ pub async fn connector_retrieve(
state,
&req,
payload,
|state, auth_data, req, _| {
retrieve_connector(
state,
auth_data.merchant_account,
auth_data.key_store,
req.id.clone(),
)
},
|state,
auth::AuthenticationData {
merchant_account,
key_store,
..
},
req,
_| { retrieve_connector(state, merchant_account, key_store, req.id.clone()) },
auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromHeader,
&auth::JWTAuthMerchantFromHeader {
@ -493,8 +493,8 @@ pub async fn connector_list(
state,
&req,
profile_id.to_owned(),
|state, auth, _, _| {
list_connectors_for_a_profile(state, auth.merchant_account.clone(), profile_id.clone())
|state, auth::AuthenticationData { key_store, .. }, _, _| {
list_connectors_for_a_profile(state, key_store, profile_id.clone())
},
auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromHeader,
@ -793,14 +793,14 @@ pub async fn connector_delete(
state,
&req,
payload,
|state, auth_data, req, _| {
delete_connector(
state,
auth_data.merchant_account,
auth_data.key_store,
req.id,
)
},
|state,
auth::AuthenticationData {
merchant_account,
key_store,
..
},
req,
_| { delete_connector(state, merchant_account, key_store, req.id) },
auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromHeader,
&auth::JWTAuthMerchantFromHeader {

View File

@ -56,8 +56,8 @@ pub async fn api_key_create(
state,
&req,
payload,
|state, auth_data, payload, _| async {
api_keys::create_api_key(state, payload, auth_data.key_store).await
|state, auth::AuthenticationDataWithoutProfile { key_store, .. }, payload, _| async {
api_keys::create_api_key(state, payload, key_store).await
},
auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromHeader,
@ -86,10 +86,15 @@ pub async fn api_key_retrieve(
state,
&req,
&key_id,
|state, auth_data, key_id, _| {
|state,
auth::AuthenticationDataWithoutProfile {
merchant_account, ..
},
key_id,
_| {
api_keys::retrieve_api_key(
state,
auth_data.merchant_account.get_id().to_owned(),
merchant_account.get_id().to_owned(),
key_id.to_owned(),
)
},
@ -190,8 +195,13 @@ pub async fn api_key_update(
state,
&req,
payload,
|state, authentication_data, mut payload, _| {
payload.merchant_id = authentication_data.merchant_account.get_id().to_owned();
|state,
auth::AuthenticationDataWithoutProfile {
merchant_account, ..
},
mut payload,
_| {
payload.merchant_id = merchant_account.get_id().to_owned();
api_keys::update_api_key(state, payload)
},
auth::auth_type(
@ -319,8 +329,13 @@ pub async fn api_key_list(
state,
&req,
payload,
|state, authentication_data, payload, _| async move {
let merchant_id = authentication_data.merchant_account.get_id().to_owned();
|state,
auth::AuthenticationDataWithoutProfile {
merchant_account, ..
},
payload,
_| async move {
let merchant_id = merchant_account.get_id().to_owned();
api_keys::list_api_keys(state, merchant_id, payload.limit, payload.skip).await
},
auth::auth_type(

View File

@ -674,9 +674,8 @@ impl Payments {
#[cfg(any(feature = "olap", feature = "oltp"))]
pub struct Forex;
#[cfg(any(feature = "olap", feature = "oltp"))]
#[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v1"))]
impl Forex {
#[cfg(feature = "v1")]
pub fn server(state: AppState) -> Scope {
web::scope("/forex")
.app_data(web::Data::new(state.clone()))
@ -686,10 +685,6 @@ impl Forex {
web::resource("/convert_from_minor").route(web::get().to(currency::convert_forex)),
)
}
#[cfg(feature = "v2")]
pub fn server(state: AppState) -> Scope {
todo!()
}
}
#[cfg(feature = "olap")]
@ -1064,13 +1059,8 @@ impl Refunds {
#[cfg(feature = "payouts")]
pub struct Payouts;
#[cfg(feature = "payouts")]
#[cfg(all(feature = "payouts", feature = "v1"))]
impl Payouts {
#[cfg(feature = "v2")]
pub fn server(state: AppState) -> Scope {
todo!()
}
#[cfg(feature = "v1")]
pub fn server(state: AppState) -> Scope {
let mut route = web::scope("/payouts").app_data(web::Data::new(state));
route = route.service(web::resource("/create").route(web::post().to(payouts_create)));
@ -1578,17 +1568,13 @@ impl Disputes {
pub struct Cards;
#[cfg(feature = "v1")]
impl Cards {
#[cfg(feature = "v1")]
pub fn server(state: AppState) -> Scope {
web::scope("/cards")
.app_data(web::Data::new(state))
.service(web::resource("/{bin}").route(web::get().to(card_iin_info)))
}
#[cfg(feature = "v2")]
pub fn server(state: AppState) -> Scope {
todo!()
}
}
pub struct Files;
@ -1647,9 +1633,8 @@ impl PaymentLink {
#[cfg(feature = "payouts")]
pub struct PayoutLink;
#[cfg(feature = "payouts")]
#[cfg(all(feature = "payouts", feature = "v1"))]
impl PayoutLink {
#[cfg(feature = "v1")]
pub fn server(state: AppState) -> Scope {
let mut route = web::scope("/payout_link").app_data(web::Data::new(state));
route = route.service(
@ -1657,10 +1642,6 @@ impl PayoutLink {
);
route
}
#[cfg(feature = "v2")]
pub fn server(state: AppState) -> Scope {
todo!()
}
}
pub struct Profile;
@ -1789,7 +1770,7 @@ impl ProfileNew {
}
#[cfg(feature = "v2")]
pub fn server(state: AppState) -> Scope {
todo!()
web::scope("/account/{account_id}/profile").app_data(web::Data::new(state))
}
}

View File

@ -56,9 +56,13 @@ pub async fn profile_create(
state,
&req,
payload,
|state, auth_data, req, _| {
create_profile(state, req, auth_data.merchant_account, auth_data.key_store)
},
|state,
auth::AuthenticationDataWithoutProfile {
merchant_account,
key_store,
},
req,
_| { create_profile(state, req, merchant_account, key_store) },
auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromHeader,
&auth::JWTAuthMerchantFromHeader {
@ -118,7 +122,9 @@ pub async fn profile_retrieve(
state,
&req,
profile_id,
|state, auth_data, profile_id, _| retrieve_profile(state, profile_id, auth_data.key_store),
|state, auth::AuthenticationDataWithoutProfile { key_store, .. }, profile_id, _| {
retrieve_profile(state, profile_id, key_store)
},
auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromHeader,
&auth::JWTAuthMerchantFromHeader {
@ -181,7 +187,9 @@ pub async fn profile_update(
state,
&req,
json_payload.into_inner(),
|state, auth_data, req, _| update_profile(state, &profile_id, auth_data.key_store, req),
|state, auth::AuthenticationDataWithoutProfile { key_store, .. }, req, _| {
update_profile(state, &profile_id, key_store, req)
},
auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromHeader,
&auth::JWTAuthMerchantFromHeader {
@ -217,6 +225,8 @@ pub async fn profile_delete(
)
.await
}
#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::ProfileList))]
pub async fn profiles_list(
state: web::Data<AppState>,
@ -233,7 +243,38 @@ pub async fn profiles_list(
merchant_id.clone(),
|state, _auth, merchant_id, _| list_profile(state, merchant_id, None),
auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromHeader,
&auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()),
&auth::JWTAuthMerchantFromRoute {
merchant_id,
required_permission: permissions::Permission::MerchantAccountRead,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::ProfileList))]
pub async fn profiles_list(
state: web::Data<AppState>,
req: HttpRequest,
path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
let flow = Flow::ProfileList;
let merchant_id = path.into_inner();
Box::pin(api::server_wrap(
flow,
state,
&req,
merchant_id.clone(),
|state, auth::AuthenticationDataWithoutProfile { .. }, merchant_id, _| {
list_profile(state, merchant_id, None)
},
auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()),
&auth::JWTAuthMerchantFromRoute {
merchant_id,
required_permission: permissions::Permission::MerchantAccountRead,

View File

@ -69,6 +69,12 @@ pub struct AuthenticationData {
pub profile: domain::Profile,
}
#[derive(Clone, Debug)]
pub struct AuthenticationDataWithoutProfile {
pub merchant_account: domain::MerchantAccount,
pub key_store: domain::MerchantKeyStore,
}
#[derive(Clone, Debug)]
pub struct AuthenticationDataWithMultipleProfiles {
pub merchant_account: domain::MerchantAccount,
@ -437,17 +443,6 @@ where
.change_context(errors::ApiErrorResponse::Unauthorized)
.attach_printable("Failed to fetch merchant key store for the merchant id")?;
let profile = state
.store()
.find_business_profile_by_merchant_id_profile_id(
key_manager_state,
&key_store,
&stored_api_key.merchant_id,
&profile_id,
)
.await
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
let merchant = state
.store()
.find_merchant_account_by_merchant_id(
@ -458,6 +453,12 @@ where
.await
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
let profile = state
.store()
.find_business_profile_by_profile_id(key_manager_state, &key_store, &profile_id)
.await
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
let auth = AuthenticationData {
merchant_account: merchant,
key_store,
@ -683,14 +684,14 @@ where
let key_manager_state = &(&state.session_state()).into();
let profile = state
.store()
.find_business_profile_by_merchant_id_profile_id(
.find_business_profile_by_profile_id(
key_manager_state,
&auth_data.key_store,
auth_data.merchant_account.get_id(),
&profile_id,
)
.await
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
let auth_data_v2 = AuthenticationData {
merchant_account: auth_data.merchant_account,
key_store: auth_data.key_store,
@ -1006,6 +1007,53 @@ where
}
}
#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationDataWithoutProfile, A>
for AdminApiAuthWithMerchantIdFromRoute
where
A: SessionStateInfo + Sync,
{
async fn authenticate_and_fetch(
&self,
request_headers: &HeaderMap,
state: &A,
) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> {
AdminApiAuth
.authenticate_and_fetch(request_headers, state)
.await?;
let merchant_id = self.0.clone();
let key_manager_state = &(&state.session_state()).into();
let key_store = state
.store()
.get_merchant_key_store_by_merchant_id(
key_manager_state,
&merchant_id,
&state.store().get_master_key().to_vec().into(),
)
.await
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
let merchant = state
.store()
.find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store)
.await
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
let auth = AuthenticationDataWithoutProfile {
merchant_account: merchant,
key_store,
};
Ok((
auth,
AuthenticationType::AdminApiAuthWithMerchantId { merchant_id },
))
}
}
/// A helper struct to extract headers from the request
pub(crate) struct HeaderMapStruct<'a> {
headers: &'a HeaderMap,
@ -1181,6 +1229,53 @@ where
}
}
#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationDataWithoutProfile, A>
for AdminApiAuthWithMerchantIdFromHeader
where
A: SessionStateInfo + Sync,
{
async fn authenticate_and_fetch(
&self,
request_headers: &HeaderMap,
state: &A,
) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> {
AdminApiAuth
.authenticate_and_fetch(request_headers, state)
.await?;
let merchant_id = HeaderMapStruct::new(request_headers)
.get_id_type_from_header::<id_type::MerchantId>(headers::X_MERCHANT_ID)?;
let key_manager_state = &(&state.session_state()).into();
let key_store = state
.store()
.get_merchant_key_store_by_merchant_id(
key_manager_state,
&merchant_id,
&state.store().get_master_key().to_vec().into(),
)
.await
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
let merchant = state
.store()
.find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store)
.await
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
let auth = AuthenticationDataWithoutProfile {
merchant_account: merchant,
key_store,
};
Ok((
auth,
AuthenticationType::AdminApiAuthWithMerchantId { merchant_id },
))
}
}
#[derive(Debug)]
pub struct EphemeralKeyAuth;
@ -1827,6 +1922,72 @@ where
}
}
#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationDataWithoutProfile, A> for JWTAuthMerchantFromHeader
where
A: SessionStateInfo + Sync,
{
async fn authenticate_and_fetch(
&self,
request_headers: &HeaderMap,
state: &A,
) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.required_permission, &role_info)?;
let merchant_id_from_header = HeaderMapStruct::new(request_headers)
.get_id_type_from_header::<id_type::MerchantId>(headers::X_MERCHANT_ID)?;
// Check if token has access to MerchantId that has been requested through headers
if payload.merchant_id != merchant_id_from_header {
return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
}
let key_manager_state = &(&state.session_state()).into();
let key_store = state
.store()
.get_merchant_key_store_by_merchant_id(
key_manager_state,
&payload.merchant_id,
&state.store().get_master_key().to_vec().into(),
)
.await
.to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
.attach_printable("Failed to fetch merchant key store for the merchant id")?;
let merchant = state
.store()
.find_merchant_account_by_merchant_id(
key_manager_state,
&payload.merchant_id,
&key_store,
)
.await
.to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
.attach_printable("Failed to fetch merchant account for the merchant id")?;
let auth = AuthenticationDataWithoutProfile {
merchant_account: merchant,
key_store,
};
Ok((
auth,
AuthenticationType::MerchantJwt {
merchant_id: payload.merchant_id,
user_id: Some(payload.user_id),
},
))
}
}
#[async_trait]
impl<A> AuthenticateAndFetch<(), A> for JWTAuthMerchantFromRoute
where
@ -2000,6 +2161,68 @@ where
))
}
}
#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationDataWithoutProfile, A> for JWTAuthMerchantFromRoute
where
A: SessionStateInfo + Sync,
{
async fn authenticate_and_fetch(
&self,
request_headers: &HeaderMap,
state: &A,
) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
if payload.merchant_id != self.merchant_id {
return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
}
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.required_permission, &role_info)?;
let key_manager_state = &(&state.session_state()).into();
let key_store = state
.store()
.get_merchant_key_store_by_merchant_id(
key_manager_state,
&payload.merchant_id,
&state.store().get_master_key().to_vec().into(),
)
.await
.to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
.attach_printable("Failed to fetch merchant key store for the merchant id")?;
let merchant = state
.store()
.find_merchant_account_by_merchant_id(
key_manager_state,
&payload.merchant_id,
&key_store,
)
.await
.to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
.attach_printable("Failed to fetch merchant account for the merchant id")?;
let auth = AuthenticationDataWithoutProfile {
merchant_account: merchant,
key_store,
};
Ok((
auth.clone(),
AuthenticationType::MerchantJwt {
merchant_id: auth.merchant_account.get_id().clone(),
user_id: Some(payload.user_id),
},
))
}
}
pub struct JWTAuthMerchantAndProfileFromRoute {
pub merchant_id: id_type::MerchantId,
pub profile_id: id_type::ProfileId,