mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 11:24:45 +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:
@ -44,6 +44,8 @@ pub mod relay;
|
|||||||
#[cfg(feature = "v2")]
|
#[cfg(feature = "v2")]
|
||||||
pub mod revenue_recovery_data_backfill;
|
pub mod revenue_recovery_data_backfill;
|
||||||
pub mod routing;
|
pub mod routing;
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
|
pub mod subscription;
|
||||||
pub mod surcharge_decision_configs;
|
pub mod surcharge_decision_configs;
|
||||||
pub mod three_ds_decision_rule;
|
pub mod three_ds_decision_rule;
|
||||||
#[cfg(feature = "tokenization_v2")]
|
#[cfg(feature = "tokenization_v2")]
|
||||||
|
|||||||
109
crates/api_models/src/subscription.rs
Normal file
109
crates/api_models/src/subscription.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
use common_utils::events::ApiEventMetric;
|
||||||
|
use masking::Secret;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
// use crate::{
|
||||||
|
// customers::{CustomerRequest, CustomerResponse},
|
||||||
|
// payments::CustomerDetailsResponse,
|
||||||
|
// };
|
||||||
|
|
||||||
|
/// Request payload for creating a subscription.
|
||||||
|
///
|
||||||
|
/// This struct captures details required to create a subscription,
|
||||||
|
/// including plan, profile, merchant connector, and optional customer info.
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||||
|
pub struct CreateSubscriptionRequest {
|
||||||
|
/// Merchant specific Unique identifier.
|
||||||
|
pub merchant_reference_id: Option<String>,
|
||||||
|
|
||||||
|
/// Identifier for the subscription plan.
|
||||||
|
pub plan_id: Option<String>,
|
||||||
|
|
||||||
|
/// Optional coupon code applied to the subscription.
|
||||||
|
pub coupon_code: Option<String>,
|
||||||
|
|
||||||
|
/// customer ID associated with this subscription.
|
||||||
|
pub customer_id: common_utils::id_type::CustomerId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response payload returned after successfully creating a subscription.
|
||||||
|
///
|
||||||
|
/// Includes details such as subscription ID, status, plan, merchant, and customer info.
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, ToSchema)]
|
||||||
|
pub struct CreateSubscriptionResponse {
|
||||||
|
/// Unique identifier for the subscription.
|
||||||
|
pub id: common_utils::id_type::SubscriptionId,
|
||||||
|
|
||||||
|
/// Merchant specific Unique identifier.
|
||||||
|
pub merchant_reference_id: Option<String>,
|
||||||
|
|
||||||
|
/// Current status of the subscription.
|
||||||
|
pub status: SubscriptionStatus,
|
||||||
|
|
||||||
|
/// Identifier for the associated subscription plan.
|
||||||
|
pub plan_id: Option<String>,
|
||||||
|
|
||||||
|
/// Associated profile ID.
|
||||||
|
pub profile_id: common_utils::id_type::ProfileId,
|
||||||
|
|
||||||
|
/// Optional client secret used for secure client-side interactions.
|
||||||
|
pub client_secret: Option<Secret<String>>,
|
||||||
|
|
||||||
|
/// Merchant identifier owning this subscription.
|
||||||
|
pub merchant_id: common_utils::id_type::MerchantId,
|
||||||
|
|
||||||
|
/// Optional coupon code applied to this subscription.
|
||||||
|
pub coupon_code: Option<String>,
|
||||||
|
|
||||||
|
/// Optional customer ID associated with this subscription.
|
||||||
|
pub customer_id: common_utils::id_type::CustomerId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Possible states of a subscription lifecycle.
|
||||||
|
///
|
||||||
|
/// - `Created`: Subscription was created but not yet activated.
|
||||||
|
/// - `Active`: Subscription is currently active.
|
||||||
|
/// - `InActive`: Subscription is inactive (e.g., cancelled or expired).
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, strum::EnumString, strum::Display, ToSchema)]
|
||||||
|
pub enum SubscriptionStatus {
|
||||||
|
/// Subscription is active.
|
||||||
|
Active,
|
||||||
|
/// Subscription is created but not yet active.
|
||||||
|
Created,
|
||||||
|
/// Subscription is inactive.
|
||||||
|
InActive,
|
||||||
|
/// Subscription is in pending state.
|
||||||
|
Pending,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateSubscriptionResponse {
|
||||||
|
/// Creates a new [`CreateSubscriptionResponse`] with the given identifiers.
|
||||||
|
///
|
||||||
|
/// By default, `client_secret`, `coupon_code`, and `customer` fields are `None`.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn new(
|
||||||
|
id: common_utils::id_type::SubscriptionId,
|
||||||
|
merchant_reference_id: Option<String>,
|
||||||
|
status: SubscriptionStatus,
|
||||||
|
plan_id: Option<String>,
|
||||||
|
profile_id: common_utils::id_type::ProfileId,
|
||||||
|
merchant_id: common_utils::id_type::MerchantId,
|
||||||
|
client_secret: Option<Secret<String>>,
|
||||||
|
customer_id: common_utils::id_type::CustomerId,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
merchant_reference_id,
|
||||||
|
status,
|
||||||
|
plan_id,
|
||||||
|
profile_id,
|
||||||
|
client_secret,
|
||||||
|
merchant_id,
|
||||||
|
coupon_code: None,
|
||||||
|
customer_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiEventMetric for CreateSubscriptionResponse {}
|
||||||
|
impl ApiEventMetric for CreateSubscriptionRequest {}
|
||||||
@ -8412,6 +8412,7 @@ pub enum Resource {
|
|||||||
RunRecon,
|
RunRecon,
|
||||||
ReconConfig,
|
ReconConfig,
|
||||||
RevenueRecovery,
|
RevenueRecovery,
|
||||||
|
Subscription,
|
||||||
InternalConnector,
|
InternalConnector,
|
||||||
Theme,
|
Theme,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,7 @@ pub enum ApiEventsType {
|
|||||||
payment_id: Option<id_type::GlobalPaymentId>,
|
payment_id: Option<id_type::GlobalPaymentId>,
|
||||||
},
|
},
|
||||||
Routing,
|
Routing,
|
||||||
|
Subscription,
|
||||||
ResourceListAPI,
|
ResourceListAPI,
|
||||||
#[cfg(feature = "v1")]
|
#[cfg(feature = "v1")]
|
||||||
PaymentRedirectionResponse {
|
PaymentRedirectionResponse {
|
||||||
|
|||||||
@ -17,6 +17,7 @@ mod profile_acquirer;
|
|||||||
mod refunds;
|
mod refunds;
|
||||||
mod relay;
|
mod relay;
|
||||||
mod routing;
|
mod routing;
|
||||||
|
mod subscription;
|
||||||
mod tenant;
|
mod tenant;
|
||||||
|
|
||||||
use std::{borrow::Cow, fmt::Debug};
|
use std::{borrow::Cow, fmt::Debug};
|
||||||
@ -55,6 +56,7 @@ pub use self::{
|
|||||||
refunds::RefundReferenceId,
|
refunds::RefundReferenceId,
|
||||||
relay::RelayId,
|
relay::RelayId,
|
||||||
routing::RoutingId,
|
routing::RoutingId,
|
||||||
|
subscription::SubscriptionId,
|
||||||
tenant::TenantId,
|
tenant::TenantId,
|
||||||
};
|
};
|
||||||
use crate::{fp_utils::when, generate_id_with_default_len};
|
use crate::{fp_utils::when, generate_id_with_default_len};
|
||||||
|
|||||||
21
crates/common_utils/src/id_type/subscription.rs
Normal file
21
crates/common_utils/src/id_type/subscription.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
crate::id_type!(
|
||||||
|
SubscriptionId,
|
||||||
|
" A type for subscription_id that can be used for subscription ids"
|
||||||
|
);
|
||||||
|
|
||||||
|
crate::impl_id_type_methods!(SubscriptionId, "subscription_id");
|
||||||
|
|
||||||
|
// This is to display the `SubscriptionId` as SubscriptionId(subs)
|
||||||
|
crate::impl_debug_id_type!(SubscriptionId);
|
||||||
|
crate::impl_try_from_cow_str_id_type!(SubscriptionId, "subscription_id");
|
||||||
|
|
||||||
|
crate::impl_generate_id_id_type!(SubscriptionId, "subscription");
|
||||||
|
crate::impl_serializable_secret_id_type!(SubscriptionId);
|
||||||
|
crate::impl_queryable_id_type!(SubscriptionId);
|
||||||
|
crate::impl_to_sql_from_sql_id_type!(SubscriptionId);
|
||||||
|
|
||||||
|
impl crate::events::ApiEventMetric for SubscriptionId {
|
||||||
|
fn get_api_event_type(&self) -> Option<crate::events::ApiEventsType> {
|
||||||
|
Some(crate::events::ApiEventsType::Subscription)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,13 +19,13 @@ impl Subscription {
|
|||||||
pub async fn find_by_merchant_id_subscription_id(
|
pub async fn find_by_merchant_id_subscription_id(
|
||||||
conn: &PgPooledConn,
|
conn: &PgPooledConn,
|
||||||
merchant_id: &common_utils::id_type::MerchantId,
|
merchant_id: &common_utils::id_type::MerchantId,
|
||||||
subscription_id: String,
|
id: String,
|
||||||
) -> StorageResult<Self> {
|
) -> StorageResult<Self> {
|
||||||
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
|
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
|
||||||
conn,
|
conn,
|
||||||
dsl::merchant_id
|
dsl::merchant_id
|
||||||
.eq(merchant_id.to_owned())
|
.eq(merchant_id.to_owned())
|
||||||
.and(dsl::subscription_id.eq(subscription_id.to_owned())),
|
.and(dsl::id.eq(id.to_owned())),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@ impl Subscription {
|
|||||||
pub async fn update_subscription_entry(
|
pub async fn update_subscription_entry(
|
||||||
conn: &PgPooledConn,
|
conn: &PgPooledConn,
|
||||||
merchant_id: &common_utils::id_type::MerchantId,
|
merchant_id: &common_utils::id_type::MerchantId,
|
||||||
subscription_id: String,
|
id: String,
|
||||||
subscription_update: SubscriptionUpdate,
|
subscription_update: SubscriptionUpdate,
|
||||||
) -> StorageResult<Self> {
|
) -> StorageResult<Self> {
|
||||||
generics::generic_update_with_results::<
|
generics::generic_update_with_results::<
|
||||||
@ -43,8 +43,8 @@ impl Subscription {
|
|||||||
_,
|
_,
|
||||||
>(
|
>(
|
||||||
conn,
|
conn,
|
||||||
dsl::subscription_id
|
dsl::id
|
||||||
.eq(subscription_id.to_owned())
|
.eq(id.to_owned())
|
||||||
.and(dsl::merchant_id.eq(merchant_id.to_owned())),
|
.and(dsl::merchant_id.eq(merchant_id.to_owned())),
|
||||||
subscription_update,
|
subscription_update,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1530,9 +1530,9 @@ diesel::table! {
|
|||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use crate::enums::diesel_exports::*;
|
use crate::enums::diesel_exports::*;
|
||||||
|
|
||||||
subscription (subscription_id, merchant_id) {
|
subscription (id) {
|
||||||
#[max_length = 128]
|
#[max_length = 128]
|
||||||
subscription_id -> Varchar,
|
id -> Varchar,
|
||||||
#[max_length = 128]
|
#[max_length = 128]
|
||||||
status -> Varchar,
|
status -> Varchar,
|
||||||
#[max_length = 128]
|
#[max_length = 128]
|
||||||
@ -1554,6 +1554,8 @@ diesel::table! {
|
|||||||
modified_at -> Timestamp,
|
modified_at -> Timestamp,
|
||||||
#[max_length = 64]
|
#[max_length = 64]
|
||||||
profile_id -> Varchar,
|
profile_id -> Varchar,
|
||||||
|
#[max_length = 128]
|
||||||
|
merchant_reference_id -> Nullable<Varchar>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1465,9 +1465,9 @@ diesel::table! {
|
|||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use crate::enums::diesel_exports::*;
|
use crate::enums::diesel_exports::*;
|
||||||
|
|
||||||
subscription (subscription_id, merchant_id) {
|
subscription (id) {
|
||||||
#[max_length = 128]
|
#[max_length = 128]
|
||||||
subscription_id -> Varchar,
|
id -> Varchar,
|
||||||
#[max_length = 128]
|
#[max_length = 128]
|
||||||
status -> Varchar,
|
status -> Varchar,
|
||||||
#[max_length = 128]
|
#[max_length = 128]
|
||||||
@ -1489,6 +1489,8 @@ diesel::table! {
|
|||||||
modified_at -> Timestamp,
|
modified_at -> Timestamp,
|
||||||
#[max_length = 64]
|
#[max_length = 64]
|
||||||
profile_id -> Varchar,
|
profile_id -> Varchar,
|
||||||
|
#[max_length = 128]
|
||||||
|
merchant_reference_id -> Nullable<Varchar>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use common_utils::pii::SecretSerdeValue;
|
use common_utils::{generate_id_with_default_len, pii::SecretSerdeValue};
|
||||||
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
|
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
|
||||||
|
use masking::Secret;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::schema::subscription;
|
use crate::schema::subscription;
|
||||||
@ -7,7 +8,7 @@ use crate::schema::subscription;
|
|||||||
#[derive(Clone, Debug, Eq, Insertable, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Eq, Insertable, PartialEq, Serialize, Deserialize)]
|
||||||
#[diesel(table_name = subscription)]
|
#[diesel(table_name = subscription)]
|
||||||
pub struct SubscriptionNew {
|
pub struct SubscriptionNew {
|
||||||
subscription_id: String,
|
id: common_utils::id_type::SubscriptionId,
|
||||||
status: String,
|
status: String,
|
||||||
billing_processor: Option<String>,
|
billing_processor: Option<String>,
|
||||||
payment_method_id: Option<String>,
|
payment_method_id: Option<String>,
|
||||||
@ -20,14 +21,15 @@ pub struct SubscriptionNew {
|
|||||||
created_at: time::PrimitiveDateTime,
|
created_at: time::PrimitiveDateTime,
|
||||||
modified_at: time::PrimitiveDateTime,
|
modified_at: time::PrimitiveDateTime,
|
||||||
profile_id: common_utils::id_type::ProfileId,
|
profile_id: common_utils::id_type::ProfileId,
|
||||||
|
merchant_reference_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Selectable, Deserialize, Serialize,
|
Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Selectable, Deserialize, Serialize,
|
||||||
)]
|
)]
|
||||||
#[diesel(table_name = subscription, primary_key(subscription_id, merchant_id), check_for_backend(diesel::pg::Pg))]
|
#[diesel(table_name = subscription, primary_key(id), check_for_backend(diesel::pg::Pg))]
|
||||||
pub struct Subscription {
|
pub struct Subscription {
|
||||||
pub subscription_id: String,
|
pub id: common_utils::id_type::SubscriptionId,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
pub billing_processor: Option<String>,
|
pub billing_processor: Option<String>,
|
||||||
pub payment_method_id: Option<String>,
|
pub payment_method_id: Option<String>,
|
||||||
@ -40,6 +42,7 @@ pub struct Subscription {
|
|||||||
pub created_at: time::PrimitiveDateTime,
|
pub created_at: time::PrimitiveDateTime,
|
||||||
pub modified_at: time::PrimitiveDateTime,
|
pub modified_at: time::PrimitiveDateTime,
|
||||||
pub profile_id: common_utils::id_type::ProfileId,
|
pub profile_id: common_utils::id_type::ProfileId,
|
||||||
|
pub merchant_reference_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, AsChangeset, router_derive::DebugAsDisplay, Deserialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, AsChangeset, router_derive::DebugAsDisplay, Deserialize)]
|
||||||
@ -53,7 +56,7 @@ pub struct SubscriptionUpdate {
|
|||||||
impl SubscriptionNew {
|
impl SubscriptionNew {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
subscription_id: String,
|
id: common_utils::id_type::SubscriptionId,
|
||||||
status: String,
|
status: String,
|
||||||
billing_processor: Option<String>,
|
billing_processor: Option<String>,
|
||||||
payment_method_id: Option<String>,
|
payment_method_id: Option<String>,
|
||||||
@ -64,10 +67,11 @@ impl SubscriptionNew {
|
|||||||
customer_id: common_utils::id_type::CustomerId,
|
customer_id: common_utils::id_type::CustomerId,
|
||||||
metadata: Option<SecretSerdeValue>,
|
metadata: Option<SecretSerdeValue>,
|
||||||
profile_id: common_utils::id_type::ProfileId,
|
profile_id: common_utils::id_type::ProfileId,
|
||||||
|
merchant_reference_id: Option<String>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let now = common_utils::date_time::now();
|
let now = common_utils::date_time::now();
|
||||||
Self {
|
Self {
|
||||||
subscription_id,
|
id,
|
||||||
status,
|
status,
|
||||||
billing_processor,
|
billing_processor,
|
||||||
payment_method_id,
|
payment_method_id,
|
||||||
@ -80,8 +84,16 @@ impl SubscriptionNew {
|
|||||||
created_at: now,
|
created_at: now,
|
||||||
modified_at: now,
|
modified_at: now,
|
||||||
profile_id,
|
profile_id,
|
||||||
|
merchant_reference_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_and_set_client_secret(&mut self) -> Secret<String> {
|
||||||
|
let client_secret =
|
||||||
|
generate_id_with_default_len(&format!("{}_secret", self.id.get_string_repr()));
|
||||||
|
self.client_secret = Some(client_secret.clone());
|
||||||
|
Secret::new(client_secret)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubscriptionUpdate {
|
impl SubscriptionUpdate {
|
||||||
|
|||||||
@ -51,6 +51,8 @@ pub mod refunds_v2;
|
|||||||
#[cfg(feature = "v1")]
|
#[cfg(feature = "v1")]
|
||||||
pub mod debit_routing;
|
pub mod debit_routing;
|
||||||
pub mod routing;
|
pub mod routing;
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
|
pub mod subscription;
|
||||||
pub mod surcharge_decision_config;
|
pub mod surcharge_decision_config;
|
||||||
pub mod three_ds_decision_rule;
|
pub mod three_ds_decision_rule;
|
||||||
#[cfg(feature = "olap")]
|
#[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::Files::server(state.clone()))
|
||||||
.service(routes::Disputes::server(state.clone()))
|
.service(routes::Disputes::server(state.clone()))
|
||||||
.service(routes::Blocklist::server(state.clone()))
|
.service(routes::Blocklist::server(state.clone()))
|
||||||
|
.service(routes::Subscription::server(state.clone()))
|
||||||
.service(routes::Gsm::server(state.clone()))
|
.service(routes::Gsm::server(state.clone()))
|
||||||
.service(routes::ApplePayCertificatesMigration::server(state.clone()))
|
.service(routes::ApplePayCertificatesMigration::server(state.clone()))
|
||||||
.service(routes::PaymentLink::server(state.clone()))
|
.service(routes::PaymentLink::server(state.clone()))
|
||||||
|
|||||||
@ -52,6 +52,8 @@ pub mod refunds;
|
|||||||
pub mod revenue_recovery_data_backfill;
|
pub mod revenue_recovery_data_backfill;
|
||||||
#[cfg(feature = "olap")]
|
#[cfg(feature = "olap")]
|
||||||
pub mod routing;
|
pub mod routing;
|
||||||
|
#[cfg(feature = "v1")]
|
||||||
|
pub mod subscription;
|
||||||
pub mod three_ds_decision_rule;
|
pub mod three_ds_decision_rule;
|
||||||
pub mod tokenization;
|
pub mod tokenization;
|
||||||
#[cfg(feature = "olap")]
|
#[cfg(feature = "olap")]
|
||||||
@ -96,7 +98,7 @@ pub use self::app::{
|
|||||||
User, UserDeprecated, Webhooks,
|
User, UserDeprecated, Webhooks,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "olap")]
|
#[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")]
|
#[cfg(feature = "payouts")]
|
||||||
pub use self::app::{PayoutLink, Payouts};
|
pub use self::app::{PayoutLink, Payouts};
|
||||||
#[cfg(feature = "v2")]
|
#[cfg(feature = "v2")]
|
||||||
|
|||||||
@ -65,7 +65,9 @@ use super::{
|
|||||||
profiles, relay, user, user_role,
|
profiles, relay, user, user_role,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "v1")]
|
#[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"))]
|
#[cfg(any(feature = "olap", feature = "oltp"))]
|
||||||
use super::{configs::*, customers, payments};
|
use super::{configs::*, customers, payments};
|
||||||
#[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v1"))]
|
#[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;
|
pub struct Customers;
|
||||||
|
|
||||||
#[cfg(all(feature = "v2", any(feature = "olap", feature = "oltp")))]
|
#[cfg(all(feature = "v2", any(feature = "olap", feature = "oltp")))]
|
||||||
|
|||||||
@ -26,6 +26,7 @@ pub enum ApiIdentifier {
|
|||||||
ApiKeys,
|
ApiKeys,
|
||||||
PaymentLink,
|
PaymentLink,
|
||||||
Routing,
|
Routing,
|
||||||
|
Subscription,
|
||||||
Blocklist,
|
Blocklist,
|
||||||
Forex,
|
Forex,
|
||||||
RustLockerMigration,
|
RustLockerMigration,
|
||||||
@ -89,6 +90,8 @@ impl From<Flow> for ApiIdentifier {
|
|||||||
| Flow::DecisionEngineDecideGatewayCall
|
| Flow::DecisionEngineDecideGatewayCall
|
||||||
| Flow::DecisionEngineGatewayFeedbackCall => Self::Routing,
|
| Flow::DecisionEngineGatewayFeedbackCall => Self::Routing,
|
||||||
|
|
||||||
|
Flow::CreateSubscription => Self::Subscription,
|
||||||
|
|
||||||
Flow::RetrieveForexFlow => Self::Forex,
|
Flow::RetrieveForexFlow => Self::Forex,
|
||||||
|
|
||||||
Flow::AddToBlocklist => Self::Blocklist,
|
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],
|
scopes: [Read, Write],
|
||||||
entities: [Profile, Merchant]
|
entities: [Profile, Merchant]
|
||||||
},
|
},
|
||||||
|
Subscription: {
|
||||||
|
scopes: [Read, Write],
|
||||||
|
entities: [Profile, Merchant]
|
||||||
|
},
|
||||||
ThreeDsDecisionManager: {
|
ThreeDsDecisionManager: {
|
||||||
scopes: [Read, Write],
|
scopes: [Read, Write],
|
||||||
entities: [Merchant, Profile]
|
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")
|
Some("Payment Processors, Payout Processors, Fraud & Risk Managers")
|
||||||
}
|
}
|
||||||
(Resource::Routing, _) => Some("Routing"),
|
(Resource::Routing, _) => Some("Routing"),
|
||||||
|
(Resource::Subscription, _) => Some("Subscription"),
|
||||||
(Resource::RevenueRecovery, _) => Some("Revenue Recovery"),
|
(Resource::RevenueRecovery, _) => Some("Revenue Recovery"),
|
||||||
(Resource::ThreeDsDecisionManager, _) => Some("3DS Decision Manager"),
|
(Resource::ThreeDsDecisionManager, _) => Some("3DS Decision Manager"),
|
||||||
(Resource::SurchargeDecisionManager, _) => Some("Surcharge Decision Manager"),
|
(Resource::SurchargeDecisionManager, _) => Some("Surcharge Decision Manager"),
|
||||||
|
|||||||
@ -263,6 +263,8 @@ pub enum Flow {
|
|||||||
RoutingUpdateDefaultConfig,
|
RoutingUpdateDefaultConfig,
|
||||||
/// Routing delete config
|
/// Routing delete config
|
||||||
RoutingDeleteConfig,
|
RoutingDeleteConfig,
|
||||||
|
/// Subscription create flow,
|
||||||
|
CreateSubscription,
|
||||||
/// Create dynamic routing
|
/// Create dynamic routing
|
||||||
CreateDynamicRoutingConfig,
|
CreateDynamicRoutingConfig,
|
||||||
/// Toggle dynamic routing
|
/// Toggle dynamic routing
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
ALTER TABLE subscription
|
||||||
|
DROP CONSTRAINT subscription_pkey,
|
||||||
|
DROP COLUMN merchant_reference_id;
|
||||||
|
|
||||||
|
ALTER TABLE subscription
|
||||||
|
RENAME COLUMN id TO subscription_id;
|
||||||
|
|
||||||
|
ALTER TABLE subscription
|
||||||
|
ADD PRIMARY KEY (subscription_id, merchant_id);
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
ALTER TABLE subscription
|
||||||
|
DROP CONSTRAINT subscription_pkey,
|
||||||
|
ADD COLUMN merchant_reference_id VARCHAR(128);
|
||||||
|
|
||||||
|
ALTER TABLE subscription
|
||||||
|
RENAME COLUMN subscription_id TO id;
|
||||||
|
|
||||||
|
ALTER TABLE subscription
|
||||||
|
ADD PRIMARY KEY (id);
|
||||||
Reference in New Issue
Block a user