mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 04:04:43 +08:00
feat(organization): add organization table (#2669)
This commit is contained in:
@ -11,6 +11,7 @@ pub mod ephemeral_key;
|
|||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod files;
|
pub mod files;
|
||||||
pub mod mandates;
|
pub mod mandates;
|
||||||
|
pub mod organization;
|
||||||
pub mod payment_methods;
|
pub mod payment_methods;
|
||||||
pub mod payments;
|
pub mod payments;
|
||||||
#[cfg(feature = "payouts")]
|
#[cfg(feature = "payouts")]
|
||||||
|
|||||||
13
crates/api_models/src/organization.rs
Normal file
13
crates/api_models/src/organization.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
pub struct OrganizationNew {
|
||||||
|
pub org_id: String,
|
||||||
|
pub org_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OrganizationNew {
|
||||||
|
pub fn new(org_name: Option<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
org_id: common_utils::generate_id_with_default_len("org"),
|
||||||
|
org_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,6 +23,7 @@ pub mod mandate;
|
|||||||
pub mod merchant_account;
|
pub mod merchant_account;
|
||||||
pub mod merchant_connector_account;
|
pub mod merchant_connector_account;
|
||||||
pub mod merchant_key_store;
|
pub mod merchant_key_store;
|
||||||
|
pub mod organization;
|
||||||
pub mod payment_attempt;
|
pub mod payment_attempt;
|
||||||
pub mod payment_intent;
|
pub mod payment_intent;
|
||||||
pub mod payment_link;
|
pub mod payment_link;
|
||||||
|
|||||||
35
crates/diesel_models/src/organization.rs
Normal file
35
crates/diesel_models/src/organization.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use diesel::{AsChangeset, Identifiable, Insertable, Queryable};
|
||||||
|
|
||||||
|
use crate::schema::organization;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Identifiable, Queryable)]
|
||||||
|
#[diesel(table_name = organization, primary_key(org_id))]
|
||||||
|
pub struct Organization {
|
||||||
|
pub org_id: String,
|
||||||
|
pub org_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Insertable)]
|
||||||
|
#[diesel(table_name = organization, primary_key(org_id))]
|
||||||
|
pub struct OrganizationNew {
|
||||||
|
pub org_id: String,
|
||||||
|
pub org_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, AsChangeset)]
|
||||||
|
#[diesel(table_name = organization)]
|
||||||
|
pub struct OrganizationUpdateInternal {
|
||||||
|
org_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum OrganizationUpdate {
|
||||||
|
Update { org_name: Option<String> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<OrganizationUpdate> for OrganizationUpdateInternal {
|
||||||
|
fn from(value: OrganizationUpdate) -> Self {
|
||||||
|
match value {
|
||||||
|
OrganizationUpdate::Update { org_name } => Self { org_name },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,6 +16,7 @@ pub mod mandate;
|
|||||||
pub mod merchant_account;
|
pub mod merchant_account;
|
||||||
pub mod merchant_connector_account;
|
pub mod merchant_connector_account;
|
||||||
pub mod merchant_key_store;
|
pub mod merchant_key_store;
|
||||||
|
pub mod organization;
|
||||||
pub mod payment_attempt;
|
pub mod payment_attempt;
|
||||||
pub mod payment_intent;
|
pub mod payment_intent;
|
||||||
pub mod payment_link;
|
pub mod payment_link;
|
||||||
|
|||||||
38
crates/diesel_models/src/query/organization.rs
Normal file
38
crates/diesel_models/src/query/organization.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use diesel::{associations::HasTable, ExpressionMethods};
|
||||||
|
use router_env::tracing::{self, instrument};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
organization::*, query::generics, schema::organization::dsl, PgPooledConn, StorageResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl OrganizationNew {
|
||||||
|
#[instrument(skip(conn))]
|
||||||
|
pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Organization> {
|
||||||
|
generics::generic_insert(conn, self).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Organization {
|
||||||
|
pub async fn find_by_org_id(conn: &PgPooledConn, org_id: String) -> StorageResult<Self> {
|
||||||
|
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(conn, dsl::org_id.eq(org_id))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_by_org_id(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
org_id: String,
|
||||||
|
update: OrganizationUpdate,
|
||||||
|
) -> StorageResult<Self> {
|
||||||
|
generics::generic_update_with_unique_predicate_get_result::<
|
||||||
|
<Self as HasTable>::Table,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
>(
|
||||||
|
conn,
|
||||||
|
dsl::org_id.eq(org_id),
|
||||||
|
OrganizationUpdateInternal::from(update),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -505,6 +505,17 @@ diesel::table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
use diesel::sql_types::*;
|
||||||
|
use crate::enums::diesel_exports::*;
|
||||||
|
|
||||||
|
organization (org_id) {
|
||||||
|
#[max_length = 32]
|
||||||
|
org_id -> Varchar,
|
||||||
|
org_name -> Nullable<Text>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use crate::enums::diesel_exports::*;
|
use crate::enums::diesel_exports::*;
|
||||||
@ -879,6 +890,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||||||
merchant_account,
|
merchant_account,
|
||||||
merchant_connector_account,
|
merchant_connector_account,
|
||||||
merchant_key_store,
|
merchant_key_store,
|
||||||
|
organization,
|
||||||
payment_attempt,
|
payment_attempt,
|
||||||
payment_intent,
|
payment_intent,
|
||||||
payment_link,
|
payment_link,
|
||||||
|
|||||||
@ -26,13 +26,11 @@ use crate::{
|
|||||||
types::{self as domain_types, AsyncLift},
|
types::{self as domain_types, AsyncLift},
|
||||||
},
|
},
|
||||||
storage::{self, enums::MerchantStorageScheme},
|
storage::{self, enums::MerchantStorageScheme},
|
||||||
transformers::ForeignTryFrom,
|
transformers::{ForeignFrom, ForeignTryFrom},
|
||||||
},
|
},
|
||||||
utils::{self, OptionExt},
|
utils::{self, OptionExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_ORG_ID: &str = "org_abcdefghijklmn";
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn create_merchant_publishable_key() -> String {
|
pub fn create_merchant_publishable_key() -> String {
|
||||||
format!(
|
format!(
|
||||||
@ -146,6 +144,24 @@ pub async fn create_merchant_account(
|
|||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
|
let organization_id = if let Some(organization_id) = req.organization_id.as_ref() {
|
||||||
|
db.find_organization_by_org_id(organization_id)
|
||||||
|
.await
|
||||||
|
.to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
|
||||||
|
message: "organization with the given id does not exist".to_string(),
|
||||||
|
})?;
|
||||||
|
organization_id.to_string()
|
||||||
|
} else {
|
||||||
|
let new_organization = api_models::organization::OrganizationNew::new(None);
|
||||||
|
let db_organization = ForeignFrom::foreign_from(new_organization);
|
||||||
|
let organization = db
|
||||||
|
.insert_organization(db_organization)
|
||||||
|
.await
|
||||||
|
.to_duplicate_response(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable("Error when creating organization")?;
|
||||||
|
organization.org_id
|
||||||
|
};
|
||||||
|
|
||||||
let mut merchant_account = async {
|
let mut merchant_account = async {
|
||||||
Ok(domain::MerchantAccount {
|
Ok(domain::MerchantAccount {
|
||||||
merchant_id: req.merchant_id,
|
merchant_id: req.merchant_id,
|
||||||
@ -177,7 +193,7 @@ pub async fn create_merchant_account(
|
|||||||
intent_fulfillment_time: req.intent_fulfillment_time.map(i64::from),
|
intent_fulfillment_time: req.intent_fulfillment_time.map(i64::from),
|
||||||
payout_routing_algorithm: req.payout_routing_algorithm,
|
payout_routing_algorithm: req.payout_routing_algorithm,
|
||||||
id: None,
|
id: None,
|
||||||
organization_id: req.organization_id.unwrap_or(DEFAULT_ORG_ID.to_string()),
|
organization_id,
|
||||||
is_recon_enabled: false,
|
is_recon_enabled: false,
|
||||||
default_profile: None,
|
default_profile: None,
|
||||||
recon_status: diesel_models::enums::ReconStatus::NotRequested,
|
recon_status: diesel_models::enums::ReconStatus::NotRequested,
|
||||||
|
|||||||
@ -17,6 +17,7 @@ pub mod mandate;
|
|||||||
pub mod merchant_account;
|
pub mod merchant_account;
|
||||||
pub mod merchant_connector_account;
|
pub mod merchant_connector_account;
|
||||||
pub mod merchant_key_store;
|
pub mod merchant_key_store;
|
||||||
|
pub mod organization;
|
||||||
pub mod payment_link;
|
pub mod payment_link;
|
||||||
pub mod payment_method;
|
pub mod payment_method;
|
||||||
pub mod payout_attempt;
|
pub mod payout_attempt;
|
||||||
@ -75,6 +76,7 @@ pub trait StorageInterface:
|
|||||||
+ payment_link::PaymentLinkInterface
|
+ payment_link::PaymentLinkInterface
|
||||||
+ RedisConnInterface
|
+ RedisConnInterface
|
||||||
+ business_profile::BusinessProfileInterface
|
+ business_profile::BusinessProfileInterface
|
||||||
|
+ organization::OrganizationInterface
|
||||||
+ 'static
|
+ 'static
|
||||||
{
|
{
|
||||||
fn get_scheduler_db(&self) -> Box<dyn scheduler::SchedulerInterface>;
|
fn get_scheduler_db(&self) -> Box<dyn scheduler::SchedulerInterface>;
|
||||||
|
|||||||
131
crates/router/src/db/organization.rs
Normal file
131
crates/router/src/db/organization.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use common_utils::errors::CustomResult;
|
||||||
|
use diesel_models::organization as storage;
|
||||||
|
use error_stack::IntoReport;
|
||||||
|
|
||||||
|
use crate::{connection, core::errors, services::Store};
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait OrganizationInterface {
|
||||||
|
async fn insert_organization(
|
||||||
|
&self,
|
||||||
|
organization: storage::OrganizationNew,
|
||||||
|
) -> CustomResult<storage::Organization, errors::StorageError>;
|
||||||
|
|
||||||
|
async fn find_organization_by_org_id(
|
||||||
|
&self,
|
||||||
|
org_id: &str,
|
||||||
|
) -> CustomResult<storage::Organization, errors::StorageError>;
|
||||||
|
|
||||||
|
async fn update_organization_by_org_id(
|
||||||
|
&self,
|
||||||
|
user_id: &str,
|
||||||
|
update: storage::OrganizationUpdate,
|
||||||
|
) -> CustomResult<storage::Organization, errors::StorageError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl OrganizationInterface for Store {
|
||||||
|
async fn insert_organization(
|
||||||
|
&self,
|
||||||
|
organization: storage::OrganizationNew,
|
||||||
|
) -> CustomResult<storage::Organization, errors::StorageError> {
|
||||||
|
let conn = connection::pg_connection_write(self).await?;
|
||||||
|
organization
|
||||||
|
.insert(&conn)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
.into_report()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_organization_by_org_id(
|
||||||
|
&self,
|
||||||
|
org_id: &str,
|
||||||
|
) -> CustomResult<storage::Organization, errors::StorageError> {
|
||||||
|
let conn = connection::pg_connection_read(self).await?;
|
||||||
|
storage::Organization::find_by_org_id(&conn, org_id.to_string())
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
.into_report()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_organization_by_org_id(
|
||||||
|
&self,
|
||||||
|
org_id: &str,
|
||||||
|
update: storage::OrganizationUpdate,
|
||||||
|
) -> CustomResult<storage::Organization, errors::StorageError> {
|
||||||
|
let conn = connection::pg_connection_write(self).await?;
|
||||||
|
|
||||||
|
storage::Organization::update_by_org_id(&conn, org_id.to_string(), update)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
.into_report()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl OrganizationInterface for super::MockDb {
|
||||||
|
async fn insert_organization(
|
||||||
|
&self,
|
||||||
|
organization: storage::OrganizationNew,
|
||||||
|
) -> CustomResult<storage::Organization, errors::StorageError> {
|
||||||
|
let mut organizations = self.organizations.lock().await;
|
||||||
|
|
||||||
|
if organizations
|
||||||
|
.iter()
|
||||||
|
.any(|org| org.org_id == organization.org_id)
|
||||||
|
{
|
||||||
|
Err(errors::StorageError::DuplicateValue {
|
||||||
|
entity: "org_id",
|
||||||
|
key: None,
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
let org = storage::Organization {
|
||||||
|
org_id: organization.org_id.clone(),
|
||||||
|
org_name: organization.org_name,
|
||||||
|
};
|
||||||
|
organizations.push(org.clone());
|
||||||
|
Ok(org)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_organization_by_org_id(
|
||||||
|
&self,
|
||||||
|
org_id: &str,
|
||||||
|
) -> CustomResult<storage::Organization, errors::StorageError> {
|
||||||
|
let organizations = self.organizations.lock().await;
|
||||||
|
|
||||||
|
organizations
|
||||||
|
.iter()
|
||||||
|
.find(|org| org.org_id == org_id)
|
||||||
|
.cloned()
|
||||||
|
.ok_or(
|
||||||
|
errors::StorageError::ValueNotFound(format!(
|
||||||
|
"No organization available for org_id = {org_id}"
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_organization_by_org_id(
|
||||||
|
&self,
|
||||||
|
org_id: &str,
|
||||||
|
update: storage::OrganizationUpdate,
|
||||||
|
) -> CustomResult<storage::Organization, errors::StorageError> {
|
||||||
|
let mut organizations = self.organizations.lock().await;
|
||||||
|
|
||||||
|
organizations
|
||||||
|
.iter_mut()
|
||||||
|
.find(|org| org.org_id == org_id)
|
||||||
|
.map(|org| match &update {
|
||||||
|
storage::OrganizationUpdate::Update { org_name } => storage::Organization {
|
||||||
|
org_name: org_name.clone(),
|
||||||
|
..org.to_owned()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.ok_or(
|
||||||
|
errors::StorageError::ValueNotFound(format!(
|
||||||
|
"No organization available for org_id = {org_id}"
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -861,3 +861,14 @@ impl From<domain::Address> for payments::AddressDetails {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ForeignFrom<api_models::organization::OrganizationNew>
|
||||||
|
for diesel_models::organization::OrganizationNew
|
||||||
|
{
|
||||||
|
fn foreign_from(item: api_models::organization::OrganizationNew) -> Self {
|
||||||
|
Self {
|
||||||
|
org_id: item.org_id,
|
||||||
|
org_name: item.org_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ pub struct MockDb {
|
|||||||
pub business_profiles: Arc<Mutex<Vec<crate::store::business_profile::BusinessProfile>>>,
|
pub business_profiles: Arc<Mutex<Vec<crate::store::business_profile::BusinessProfile>>>,
|
||||||
pub reverse_lookups: Arc<Mutex<Vec<store::ReverseLookup>>>,
|
pub reverse_lookups: Arc<Mutex<Vec<store::ReverseLookup>>>,
|
||||||
pub payment_link: Arc<Mutex<Vec<store::payment_link::PaymentLink>>>,
|
pub payment_link: Arc<Mutex<Vec<store::payment_link::PaymentLink>>>,
|
||||||
|
pub organizations: Arc<Mutex<Vec<store::organization::Organization>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockDb {
|
impl MockDb {
|
||||||
@ -74,6 +75,7 @@ impl MockDb {
|
|||||||
business_profiles: Default::default(),
|
business_profiles: Default::default(),
|
||||||
reverse_lookups: Default::default(),
|
reverse_lookups: Default::default(),
|
||||||
payment_link: Default::default(),
|
payment_link: Default::default(),
|
||||||
|
organizations: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TABLE IF EXISTS ORGANIZATION;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
CREATE TABLE IF NOT EXISTS ORGANIZATION (
|
||||||
|
org_id VARCHAR(32) PRIMARY KEY NOT NULL,
|
||||||
|
org_name TEXT
|
||||||
|
);
|
||||||
@ -67,3 +67,11 @@ if (jsonData?.merchant_id) {
|
|||||||
"INFO - Unable to assign variable {{organization_id}}, as jsonData.organization_id is undefined.",
|
"INFO - Unable to assign variable {{organization_id}}, as jsonData.organization_id is undefined.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Response body should have "mandate_id"
|
||||||
|
pm.test(
|
||||||
|
"[POST]::/accounts - Organization id is generated",
|
||||||
|
function () {
|
||||||
|
pm.expect(typeof jsonData.organization_id !== "undefined").to.be.true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user