mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(subscriptions): add route for creating subscription intent (#9123)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -51,6 +51,8 @@ pub mod refunds_v2;
|
||||
#[cfg(feature = "v1")]
|
||||
pub mod debit_routing;
|
||||
pub mod routing;
|
||||
#[cfg(feature = "v1")]
|
||||
pub mod subscription;
|
||||
pub mod surcharge_decision_config;
|
||||
pub mod three_ds_decision_rule;
|
||||
#[cfg(feature = "olap")]
|
||||
|
||||
65
crates/router/src/core/subscription.rs
Normal file
65
crates/router/src/core/subscription.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use api_models::subscription::{
|
||||
self as subscription_types, CreateSubscriptionResponse, SubscriptionStatus,
|
||||
};
|
||||
use common_utils::id_type::GenerateId;
|
||||
use diesel_models::subscription::SubscriptionNew;
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::{api::ApplicationResponse, merchant_context::MerchantContext};
|
||||
use masking::Secret;
|
||||
|
||||
use super::errors::{self, RouterResponse};
|
||||
use crate::routes::SessionState;
|
||||
|
||||
pub async fn create_subscription(
|
||||
state: SessionState,
|
||||
merchant_context: MerchantContext,
|
||||
profile_id: String,
|
||||
request: subscription_types::CreateSubscriptionRequest,
|
||||
) -> RouterResponse<CreateSubscriptionResponse> {
|
||||
let store = state.store.clone();
|
||||
let db = store.as_ref();
|
||||
let id = common_utils::id_type::SubscriptionId::generate();
|
||||
let profile_id = common_utils::id_type::ProfileId::from_str(&profile_id).change_context(
|
||||
errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "X-Profile-Id",
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut subscription = SubscriptionNew::new(
|
||||
id,
|
||||
SubscriptionStatus::Created.to_string(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
merchant_context.get_merchant_account().get_id().clone(),
|
||||
request.customer_id.clone(),
|
||||
None,
|
||||
profile_id,
|
||||
request.merchant_reference_id,
|
||||
);
|
||||
|
||||
subscription.generate_and_set_client_secret();
|
||||
let subscription_response = db
|
||||
.insert_subscription_entry(subscription)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("subscriptions: unable to insert subscription entry to database")?;
|
||||
|
||||
let response = CreateSubscriptionResponse::new(
|
||||
subscription_response.id.clone(),
|
||||
subscription_response.merchant_reference_id,
|
||||
SubscriptionStatus::from_str(&subscription_response.status)
|
||||
.unwrap_or(SubscriptionStatus::Created),
|
||||
None,
|
||||
subscription_response.profile_id,
|
||||
subscription_response.merchant_id,
|
||||
subscription_response.client_secret.map(Secret::new),
|
||||
request.customer_id,
|
||||
);
|
||||
|
||||
Ok(ApplicationResponse::Json(response))
|
||||
}
|
||||
@ -211,6 +211,7 @@ pub fn mk_app(
|
||||
.service(routes::Files::server(state.clone()))
|
||||
.service(routes::Disputes::server(state.clone()))
|
||||
.service(routes::Blocklist::server(state.clone()))
|
||||
.service(routes::Subscription::server(state.clone()))
|
||||
.service(routes::Gsm::server(state.clone()))
|
||||
.service(routes::ApplePayCertificatesMigration::server(state.clone()))
|
||||
.service(routes::PaymentLink::server(state.clone()))
|
||||
|
||||
@ -52,6 +52,8 @@ pub mod refunds;
|
||||
pub mod revenue_recovery_data_backfill;
|
||||
#[cfg(feature = "olap")]
|
||||
pub mod routing;
|
||||
#[cfg(feature = "v1")]
|
||||
pub mod subscription;
|
||||
pub mod three_ds_decision_rule;
|
||||
pub mod tokenization;
|
||||
#[cfg(feature = "olap")]
|
||||
@ -96,7 +98,7 @@ pub use self::app::{
|
||||
User, UserDeprecated, Webhooks,
|
||||
};
|
||||
#[cfg(feature = "olap")]
|
||||
pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents};
|
||||
pub use self::app::{Blocklist, Organization, Routing, Subscription, Verify, WebhookEvents};
|
||||
#[cfg(feature = "payouts")]
|
||||
pub use self::app::{PayoutLink, Payouts};
|
||||
#[cfg(feature = "v2")]
|
||||
|
||||
@ -65,7 +65,9 @@ use super::{
|
||||
profiles, relay, user, user_role,
|
||||
};
|
||||
#[cfg(feature = "v1")]
|
||||
use super::{apple_pay_certificates_migration, blocklist, payment_link, webhook_events};
|
||||
use super::{
|
||||
apple_pay_certificates_migration, blocklist, payment_link, subscription, webhook_events,
|
||||
};
|
||||
#[cfg(any(feature = "olap", feature = "oltp"))]
|
||||
use super::{configs::*, customers, payments};
|
||||
#[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v1"))]
|
||||
@ -1163,6 +1165,22 @@ impl Routing {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "oltp")]
|
||||
pub struct Subscription;
|
||||
|
||||
#[cfg(all(feature = "oltp", feature = "v1"))]
|
||||
impl Subscription {
|
||||
pub fn server(state: AppState) -> Scope {
|
||||
web::scope("/subscription/create")
|
||||
.app_data(web::Data::new(state.clone()))
|
||||
.service(web::resource("").route(
|
||||
web::post().to(|state, req, payload| {
|
||||
subscription::create_subscription(state, req, payload)
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Customers;
|
||||
|
||||
#[cfg(all(feature = "v2", any(feature = "olap", feature = "oltp")))]
|
||||
|
||||
@ -26,6 +26,7 @@ pub enum ApiIdentifier {
|
||||
ApiKeys,
|
||||
PaymentLink,
|
||||
Routing,
|
||||
Subscription,
|
||||
Blocklist,
|
||||
Forex,
|
||||
RustLockerMigration,
|
||||
@ -89,6 +90,8 @@ impl From<Flow> for ApiIdentifier {
|
||||
| Flow::DecisionEngineDecideGatewayCall
|
||||
| Flow::DecisionEngineGatewayFeedbackCall => Self::Routing,
|
||||
|
||||
Flow::CreateSubscription => Self::Subscription,
|
||||
|
||||
Flow::RetrieveForexFlow => Self::Forex,
|
||||
|
||||
Flow::AddToBlocklist => Self::Blocklist,
|
||||
|
||||
69
crates/router/src/routes/subscription.rs
Normal file
69
crates/router/src/routes/subscription.rs
Normal file
@ -0,0 +1,69 @@
|
||||
//! Analysis for usage of Subscription in Payment flows
|
||||
//!
|
||||
//! Functions that are used to perform the api level configuration and retrieval
|
||||
//! of various types under Subscriptions.
|
||||
|
||||
use actix_web::{web, HttpRequest, HttpResponse, Responder};
|
||||
use api_models::subscription as subscription_types;
|
||||
use hyperswitch_domain_models::errors;
|
||||
use router_env::{
|
||||
tracing::{self, instrument},
|
||||
Flow,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
core::{api_locking, subscription},
|
||||
headers::X_PROFILE_ID,
|
||||
routes::AppState,
|
||||
services::{api as oss_api, authentication as auth, authorization::permissions::Permission},
|
||||
types::domain,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "oltp", feature = "v1"))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn create_subscription(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
json_payload: web::Json<subscription_types::CreateSubscriptionRequest>,
|
||||
) -> impl Responder {
|
||||
let flow = Flow::CreateSubscription;
|
||||
let profile_id = match req.headers().get(X_PROFILE_ID) {
|
||||
Some(val) => val.to_str().unwrap_or_default().to_string(),
|
||||
None => {
|
||||
return HttpResponse::BadRequest().json(
|
||||
errors::api_error_response::ApiErrorResponse::MissingRequiredField {
|
||||
field_name: "x-profile-id",
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
Box::pin(oss_api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
json_payload.into_inner(),
|
||||
move |state, auth: auth::AuthenticationData, payload, _| {
|
||||
let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
|
||||
domain::Context(auth.merchant_account, auth.key_store),
|
||||
));
|
||||
subscription::create_subscription(
|
||||
state,
|
||||
merchant_context,
|
||||
profile_id.clone(),
|
||||
payload.clone(),
|
||||
)
|
||||
},
|
||||
auth::auth_type(
|
||||
&auth::HeaderAuth(auth::ApiKeyAuth {
|
||||
is_connected_allowed: false,
|
||||
is_platform_allowed: false,
|
||||
}),
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::ProfileSubscriptionWrite,
|
||||
},
|
||||
req.headers(),
|
||||
),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
@ -43,6 +43,10 @@ generate_permissions! {
|
||||
scopes: [Read, Write],
|
||||
entities: [Profile, Merchant]
|
||||
},
|
||||
Subscription: {
|
||||
scopes: [Read, Write],
|
||||
entities: [Profile, Merchant]
|
||||
},
|
||||
ThreeDsDecisionManager: {
|
||||
scopes: [Read, Write],
|
||||
entities: [Merchant, Profile]
|
||||
@ -123,6 +127,7 @@ pub fn get_resource_name(resource: Resource, entity_type: EntityType) -> Option<
|
||||
Some("Payment Processors, Payout Processors, Fraud & Risk Managers")
|
||||
}
|
||||
(Resource::Routing, _) => Some("Routing"),
|
||||
(Resource::Subscription, _) => Some("Subscription"),
|
||||
(Resource::RevenueRecovery, _) => Some("Revenue Recovery"),
|
||||
(Resource::ThreeDsDecisionManager, _) => Some("3DS Decision Manager"),
|
||||
(Resource::SurchargeDecisionManager, _) => Some("Surcharge Decision Manager"),
|
||||
|
||||
Reference in New Issue
Block a user