diff --git a/pallets/orders/src/functions.rs b/pallets/orders/src/functions.rs new file mode 100644 index 00000000..7fd066f8 --- /dev/null +++ b/pallets/orders/src/functions.rs @@ -0,0 +1,152 @@ +use crate::*; + +use frame_support::{ + pallet_prelude::*, + sp_runtime::traits::{AccountIdConversion, Hash}, + PalletId, +}; +use sp_std::vec; + +pub const PALLET_ID: PalletId = PalletId(*b"orders!!"); + +impl Pallet { + pub fn staking_account_id(order_id: HashOf) -> AccountIdOf { + PALLET_ID.into_sub_account(order_id) + } + + pub fn generate_order_id(customer_id: &T::AccountId, service_id: &T::Hash) -> T::Hash { + let mut customer_id_bytes = customer_id.encode(); + let mut service_id_bytes = service_id.encode(); + let account_info = frame_system::Pallet::::account(customer_id); + let mut nonce_bytes = account_info.nonce.encode(); + + customer_id_bytes.append(&mut service_id_bytes); + customer_id_bytes.append(&mut nonce_bytes); + + let seed = &customer_id_bytes; + T::Hashing::hash(seed) + } + + pub fn update_order_status(order_id: &T::Hash, status: OrderStatus) -> Option> { + Orders::::mutate(order_id, |order| match order { + None => None, + Some(order) => { + order.status = status; + order.updated_at = pallet_timestamp::Pallet::::get(); + Some(order.clone()) + }, + }) + } + + pub fn insert_order_to_storage(order: &OrderOf) { + Orders::::insert(order.id, order); + LastOrderByCustomer::::insert(&order.customer_id, order.id); + Self::insert_order_id_into_orders_by_seller(order); + Self::insert_order_id_into_pending_orders_by_seller(order); + Self::insert_order_id_into_orders_by_customer(order); + } + + pub fn insert_order_id_into_orders_by_seller(order: &OrderOf) { + match OrdersBySeller::::get(&order.seller_id) { + None => { + OrdersBySeller::::insert(&order.seller_id, vec![order.id]); + }, + Some(mut orders) => { + orders.push(order.id); + OrdersBySeller::::insert(&order.seller_id, orders); + }, + } + } + + pub fn insert_order_id_into_orders_by_customer(order: &OrderOf) { + match OrdersByCustomer::::get(&order.customer_id) { + None => { + OrdersByCustomer::::insert(&order.customer_id, vec![order.id]); + }, + Some(mut orders) => { + orders.push(order.id); + OrdersByCustomer::::insert(&order.customer_id, orders); + }, + } + } + + pub fn insert_order_id_into_pending_orders_by_seller(order: &OrderOf) { + match PendingOrdersBySeller::::get(&order.seller_id) { + None => { + PendingOrdersBySeller::::insert(&order.seller_id, vec![order.id]); + }, + Some(mut orders) => { + orders.push(order.id); + PendingOrdersBySeller::::insert(&order.seller_id, orders); + }, + } + } + + pub fn remove_order_id_from_pending_orders_by_seller( + seller_id: &T::AccountId, + order_id: &T::Hash, + ) { + let mut orders = PendingOrdersBySeller::::get(seller_id).unwrap_or_default(); + orders.retain(|o_id| o_id != order_id); + PendingOrdersBySeller::::insert(seller_id, orders); + } + + pub fn remove_order_id_from_orders_by_seller(seller_id: &T::AccountId, order_id: &T::Hash) { + let mut orders = OrdersBySeller::::get(seller_id).unwrap_or_default(); + orders.retain(|o_id| o_id != order_id); + OrdersBySeller::::insert(seller_id, orders); + } + + pub fn remove_order_id_from_orders_by_customer(customer_id: &T::AccountId, order_id: &T::Hash) { + let mut orders = OrdersByCustomer::::get(customer_id).unwrap_or_default(); + orders.retain(|o_id| o_id != order_id); + OrdersByCustomer::::insert(customer_id, orders); + } + + pub fn order_can_be_refunded(order: OrderOf) -> bool { + let dna_sample = + T::GeneticTesting::dna_sample_by_tracking_id(&order.dna_sample_tracking_id).unwrap(); + if !dna_sample.is_rejected() { + return false + } + true + } + + fn is_pending_order_ids_by_seller_exist(account_id: &T::AccountId) -> bool { + match PendingOrdersBySeller::::get(account_id) { + Some(_arr) => !_arr.is_empty(), + None => false, + } + } +} + +impl OrderEventEmitter for Pallet { + fn emit_event_order_failed(order_id: &HashOf) { + match Self::order_by_id(order_id) { + None => Self::deposit_event(Event::OrderNotFound), + Some(order) => Self::deposit_event(Event::OrderFailed(order)), + } + } +} + +impl OrderStatusUpdater for Pallet { + fn update_status_failed(order_id: &HashOf) { + match Self::order_by_id(order_id) { + None => Self::deposit_event(Event::OrderNotFound), + Some(order) => { + Self::update_order_status(&order.id, OrderStatus::Failed); + }, + } + } + + fn remove_order_id_from_pending_orders_by_seller( + seller_id: &AccountIdOf, + order_id: &HashOf, + ) { + Self::remove_order_id_from_pending_orders_by_seller(seller_id, order_id); + } + + fn is_pending_order_by_seller_exist(seller_id: &AccountIdOf) -> bool { + Self::is_pending_order_ids_by_seller_exist(seller_id) + } +} diff --git a/pallets/orders/src/impl_orders.rs b/pallets/orders/src/impl_orders.rs new file mode 100644 index 00000000..ecc66e5f --- /dev/null +++ b/pallets/orders/src/impl_orders.rs @@ -0,0 +1,159 @@ +use crate::*; + +impl OrderInterface for Pallet { + type Order = OrderOf; + type Error = Error; + + fn create_order( + customer_id: &T::AccountId, + service_id: &T::Hash, + price_index: u32, + customer_box_public_key: &T::Hash, + order_flow: ServiceFlow, + ) -> Result { + let service = + T::Services::service_by_id(service_id).ok_or(Error::::ServiceDoesNotExist)?; + + let order_id = Self::generate_order_id(customer_id, service_id); + let seller_id = service.get_owner_id(); + let prices_by_currency = service.get_prices_by_currency(); + + if prices_by_currency.is_empty() || + prices_by_currency.len() - 1 < price_index.try_into().unwrap() + { + return Err(Error::::PriceIndexNotFound) + } + + let price_by_currency = &prices_by_currency[price_index as usize]; + + let currency = &price_by_currency.currency; + let prices = &price_by_currency.price_components; + let additional_prices = &price_by_currency.additional_prices; + + let now = pallet_timestamp::Pallet::::get(); + + // Initialize DnaSample + let dna_sample = T::GeneticTesting::register_dna_sample(seller_id, customer_id, &order_id) + .map_err(|_| Error::::DnaSampleInitalizationError)?; + + let order = Order::new( + order_id, + *service_id, + customer_id.clone(), + *customer_box_public_key, + seller_id.clone(), + dna_sample.get_tracking_id().clone(), + currency.clone(), + order_flow, + prices.clone(), + additional_prices.clone(), + now, + now, + ); + + Self::insert_order_to_storage(&order); + + Ok(order) + } + + fn cancel_order( + customer_id: &T::AccountId, + order_id: &T::Hash, + ) -> Result { + let order = Orders::::get(order_id).ok_or(Error::::OrderNotFound)?; + + if order.get_customer_id() != customer_id { + return Err(Error::::UnauthorizedOrderCancellation) + } + + let dna_sample_result = + T::GeneticTesting::dna_sample_by_tracking_id(&order.dna_sample_tracking_id); + + if let Some(dna_sample) = dna_sample_result { + if !dna_sample.is_registered() { + return Err(Error::::OngoingOrderCannotBeCancelled) + } + } + + // Delete dna sample associated with the order + let _ = T::GeneticTesting::delete_dna_sample(&order.dna_sample_tracking_id); + + let order = Self::update_order_status(order_id, OrderStatus::Cancelled) + .ok_or(Error::::OrderNotFound)?; + + Ok(order) + } + + fn set_order_paid( + escrow_account_id: &T::AccountId, + order_id: &T::Hash, + ) -> Result { + let _ = EscrowKey::::get() + .filter(|account_id| account_id == escrow_account_id) + .ok_or(Error::::Unauthorized)?; + + let order = Self::update_order_status(order_id, OrderStatus::Paid) + .ok_or(Error::::OrderNotFound)?; + + Ok(order) + } + + fn fulfill_order( + seller_id: &T::AccountId, + order_id: &T::Hash, + ) -> Result { + let order = Orders::::get(order_id).ok_or(Error::::OrderNotFound)?; + + // Only the seller can fulfill the order + if order.get_seller_id() != seller_id { + return Err(Error::::UnauthorizedOrderFulfillment) + } + + let dna_sample_result = + T::GeneticTesting::dna_sample_by_tracking_id(&order.dna_sample_tracking_id); + + if let Some(dna_sample) = dna_sample_result { + if !dna_sample.process_success() { + return Err(Error::::DnaSampleNotSuccessfullyProcessed) + } + } + + let order = Self::update_order_status(order_id, OrderStatus::Fulfilled) + .ok_or(Error::::OrderNotFound)?; + + Ok(order) + } + + fn set_order_refunded( + escrow_account_id: &T::AccountId, + order_id: &T::Hash, + ) -> Result { + let _ = EscrowKey::::get() + .filter(|account_id| account_id == escrow_account_id) + .ok_or(Error::::Unauthorized)?; + + let order = Orders::::get(order_id).ok_or(Error::::OrderNotFound)?; + + if !Self::order_can_be_refunded(order) { + return Err(Error::::OrderNotYetExpired) + } + + let order = Self::update_order_status(order_id, OrderStatus::Refunded) + .ok_or(Error::::OrderNotFound)?; + + Ok(order) + } + + fn update_escrow_key( + account_id: &T::AccountId, + escrow_key: &T::AccountId, + ) -> Result<(), Self::Error> { + let _ = EscrowKey::::get() + .filter(|e| e == account_id) + .ok_or(Error::::Unauthorized)?; + + EscrowKey::::put(escrow_key); + + Ok(()) + } +} diff --git a/pallets/orders/src/interface.rs b/pallets/orders/src/interface.rs index 10e09777..fa81d5ed 100644 --- a/pallets/orders/src/interface.rs +++ b/pallets/orders/src/interface.rs @@ -33,5 +33,4 @@ pub trait OrderInterface { account_id: &T::AccountId, escrow_key: &T::AccountId, ) -> Result<(), Self::Error>; - fn is_pending_order_ids_by_seller_exist(account_id: &T::AccountId) -> bool; } diff --git a/pallets/orders/src/lib.rs b/pallets/orders/src/lib.rs index df05bdaa..740ca3ac 100644 --- a/pallets/orders/src/lib.rs +++ b/pallets/orders/src/lib.rs @@ -1,110 +1,34 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub mod interface; -pub mod weights; -use interface::OrderInterface; - -use frame_support::{ - codec::{Decode, Encode}, - pallet_prelude::*, - traits::Currency, -}; -pub use pallet::*; -use primitives_price_and_currency::{CurrencyType, Price}; -pub use scale_info::TypeInfo; -use sp_std::{prelude::*, vec}; -use traits_genetic_testing::{DnaSampleTracking, DnaSampleTrackingId, GeneticTestingProvider}; -use traits_order::{OrderEventEmitter, OrderStatusUpdater}; -use traits_services::{types::ServiceFlow, ServiceInfo, ServicesProvider}; -pub use weights::WeightInfo; - #[cfg(test)] mod mock; #[cfg(test)] mod tests; -#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] -pub enum OrderStatus { - Unpaid, - Paid, - Fulfilled, - Refunded, - Cancelled, - Failed, -} -impl Default for OrderStatus { - fn default() -> Self { - OrderStatus::Unpaid - } -} - -#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo)] -pub struct Order { - pub id: Hash, - pub service_id: Hash, - pub customer_id: AccountId, - pub customer_box_public_key: Hash, - pub seller_id: AccountId, - pub dna_sample_tracking_id: DnaSampleTrackingId, - pub currency: CurrencyType, - pub prices: Vec>, - pub additional_prices: Vec>, - pub status: OrderStatus, - pub order_flow: ServiceFlow, - pub created_at: Moment, - pub updated_at: Moment, -} -#[allow(clippy::too_many_arguments)] -impl Order { - pub fn new( - id: Hash, - service_id: Hash, - customer_id: AccountId, - customer_box_public_key: Hash, - seller_id: AccountId, - dna_sample_tracking_id: DnaSampleTrackingId, - currency: CurrencyType, - order_flow: ServiceFlow, - prices: Vec>, - additional_prices: Vec>, - created_at: Moment, - updated_at: Moment, - ) -> Self { - Self { - id, - service_id, - customer_id, - customer_box_public_key, - seller_id, - dna_sample_tracking_id, - currency, - prices, - additional_prices, - status: OrderStatus::default(), - order_flow, - created_at, - updated_at, - } - } - - pub fn get_id(&self) -> &Hash { - &self.id - } +pub use pallet::*; - pub fn get_created_at(&self) -> &Moment { - &self.created_at - } +pub mod functions; +pub mod impl_orders; +pub mod interface; +pub mod types; +pub mod weights; - pub fn get_service_id(&self) -> &Hash { - &self.service_id - } -} +pub use interface::OrderInterface; +pub use sp_std::{prelude::*, vec}; +pub use traits_genetic_testing::{DnaSampleTracking, DnaSampleTrackingId, GeneticTestingProvider}; +pub use traits_order::{OrderEventEmitter, OrderStatusUpdater}; +pub use traits_services::{types::ServiceFlow, ServiceInfo, ServicesProvider}; +pub use types::*; +pub use weights::WeightInfo; #[frame_support::pallet] pub mod pallet { - use crate::*; - use frame_support::dispatch::DispatchResultWithPostInfo; + use super::*; + + use frame_support::{ + dispatch::DispatchResultWithPostInfo, pallet_prelude::*, traits::Currency, + }; use frame_system::pallet_prelude::*; #[pallet::config] @@ -126,16 +50,6 @@ pub mod pallet { impl Hooks> for Pallet {} // -------------------------------------------------------- - // ---- Types -------------------------------------------- - pub type AccountIdOf = ::AccountId; - pub type MomentOf = ::Moment; - pub type HashOf = ::Hash; - pub type CurrencyOf = ::Currency; - pub type BalanceOf = as Currency>>::Balance; - pub type OrderOf = Order, AccountIdOf, BalanceOf, MomentOf>; - type OrderIdsOf = Vec>; - // ------------------------------------------------------- - // ------ Storage -------------------------- #[pallet::storage] #[pallet::getter(fn order_by_id)] @@ -340,7 +254,7 @@ pub mod pallet { match >::update_escrow_key(&who, &account_id) { Ok(_) => { - Self::deposit_event(Event::UpdateOrderEscrowKeySuccessful(who.clone())); + Self::deposit_event(Event::UpdateOrderEscrowKeySuccessful(who)); Ok(().into()) }, Err(error) => Err(error.into()), @@ -362,309 +276,3 @@ pub mod pallet { } } } - -impl OrderInterface for Pallet { - type Order = OrderOf; - type Error = Error; - - fn create_order( - customer_id: &T::AccountId, - service_id: &T::Hash, - price_index: u32, - customer_box_public_key: &T::Hash, - order_flow: ServiceFlow, - ) -> Result { - let service = T::Services::service_by_id(service_id); - if service.is_none() { - return Err(Error::::ServiceDoesNotExist) - } - let service = service.unwrap(); - let order_id = Self::generate_order_id(customer_id, service_id); - let seller_id = service.get_owner_id(); - let prices_by_currency = service.get_prices_by_currency(); - - if prices_by_currency.is_empty() || - prices_by_currency.len() - 1 < price_index.try_into().unwrap() - { - return Err(Error::::PriceIndexNotFound) - } - - let price_by_currency = &prices_by_currency[price_index as usize]; - - let currency = &price_by_currency.currency; - let prices = &price_by_currency.price_components; - let additional_prices = &price_by_currency.additional_prices; - - let now = pallet_timestamp::Pallet::::get(); - - // Initialize DnaSample - let dna_sample = T::GeneticTesting::register_dna_sample(seller_id, customer_id, &order_id); - if dna_sample.is_err() { - return Err(Error::::DnaSampleInitalizationError) - } - let dna_sample = dna_sample.ok().unwrap(); - - let order = Order::new( - order_id, - *service_id, - customer_id.clone(), - *customer_box_public_key, - seller_id.clone(), - dna_sample.get_tracking_id().clone(), - currency.clone(), - order_flow, - prices.clone(), - additional_prices.clone(), - now, - now, - ); - Self::insert_order_to_storage(&order); - - Ok(order) - } - - fn cancel_order( - customer_id: &T::AccountId, - order_id: &T::Hash, - ) -> Result { - let order = Orders::::get(order_id); - if order.is_none() { - return Err(Error::::OrderNotFound) - } - let order = order.unwrap(); - - if order.customer_id != customer_id.clone() { - return Err(Error::::UnauthorizedOrderCancellation) - } - - let dna_sample = - T::GeneticTesting::dna_sample_by_tracking_id(&order.dna_sample_tracking_id).unwrap(); - if !dna_sample.is_registered() { - return Err(Error::::OngoingOrderCannotBeCancelled) - } - - // Delete dna sample associated with the order - let _ = T::GeneticTesting::delete_dna_sample(&order.dna_sample_tracking_id); - - let order = Self::update_order_status(order_id, OrderStatus::Cancelled).unwrap(); - - Ok(order) - } - - fn set_order_paid( - escrow_account_id: &T::AccountId, - order_id: &T::Hash, - ) -> Result { - if escrow_account_id.clone() != EscrowKey::::get().unwrap() { - return Err(Error::::Unauthorized) - } - - let order = Self::update_order_status(order_id, OrderStatus::Paid); - if order.is_none() { - return Err(Error::::OrderNotFound) - } - - Ok(order.unwrap()) - } - - fn fulfill_order( - seller_id: &T::AccountId, - order_id: &T::Hash, - ) -> Result { - let order = Orders::::get(order_id); - if order.is_none() { - return Err(Error::::OrderNotFound) - } - let order = order.unwrap(); - - // Only the seller can fulfill the order - if order.seller_id != seller_id.clone() { - return Err(Error::::UnauthorizedOrderFulfillment) - } - - let dna_sample = - T::GeneticTesting::dna_sample_by_tracking_id(&order.dna_sample_tracking_id); - if !dna_sample.unwrap().process_success() { - return Err(Error::::DnaSampleNotSuccessfullyProcessed) - } - - let order = Self::update_order_status(order_id, OrderStatus::Fulfilled); - - Ok(order.unwrap()) - } - - fn set_order_refunded( - escrow_account_id: &T::AccountId, - order_id: &T::Hash, - ) -> Result { - if escrow_account_id.clone() != EscrowKey::::get().unwrap() { - return Err(Error::::Unauthorized) - } - - let order = Orders::::get(order_id); - if order.is_none() { - return Err(Error::::OrderNotFound) - } - - let order_can_be_refunded = Self::order_can_be_refunded(order.unwrap()); - if !order_can_be_refunded { - return Err(Error::::OrderNotYetExpired) - } - - let order = Self::update_order_status(order_id, OrderStatus::Refunded); - Ok(order.unwrap()) - } - - fn update_escrow_key( - account_id: &T::AccountId, - escrow_key: &T::AccountId, - ) -> Result<(), Self::Error> { - if account_id.clone() != EscrowKey::::get().unwrap() { - return Err(Error::::Unauthorized) - } - - EscrowKey::::put(escrow_key); - - Ok(()) - } - - fn is_pending_order_ids_by_seller_exist(account_id: &T::AccountId) -> bool { - match PendingOrdersBySeller::::get(account_id) { - Some(_arr) => !_arr.is_empty(), - None => false, - } - } -} - -use frame_support::{sp_runtime::traits::Hash, sp_std::convert::TryInto}; - -impl Pallet { - pub fn generate_order_id(customer_id: &T::AccountId, service_id: &T::Hash) -> T::Hash { - let mut customer_id_bytes = customer_id.encode(); - let mut service_id_bytes = service_id.encode(); - let account_info = frame_system::Pallet::::account(customer_id); - let mut nonce_bytes = account_info.nonce.encode(); - - customer_id_bytes.append(&mut service_id_bytes); - customer_id_bytes.append(&mut nonce_bytes); - - let seed = &customer_id_bytes; - T::Hashing::hash(seed) - } - - pub fn update_order_status(order_id: &T::Hash, status: OrderStatus) -> Option> { - Orders::::mutate(order_id, |order| match order { - None => None, - Some(order) => { - order.status = status; - order.updated_at = pallet_timestamp::Pallet::::get(); - Some(order.clone()) - }, - }) - } - - pub fn insert_order_to_storage(order: &OrderOf) { - Orders::::insert(order.id, order); - LastOrderByCustomer::::insert(&order.customer_id, order.id); - Self::insert_order_id_into_orders_by_seller(order); - Self::insert_order_id_into_pending_orders_by_seller(order); - Self::insert_order_id_into_orders_by_customer(order); - } - - pub fn insert_order_id_into_orders_by_seller(order: &OrderOf) { - match OrdersBySeller::::get(&order.seller_id) { - None => { - OrdersBySeller::::insert(&order.seller_id, vec![order.id]); - }, - Some(mut orders) => { - orders.push(order.id); - OrdersBySeller::::insert(&order.seller_id, orders); - }, - } - } - - pub fn insert_order_id_into_orders_by_customer(order: &OrderOf) { - match OrdersByCustomer::::get(&order.customer_id) { - None => { - OrdersByCustomer::::insert(&order.customer_id, vec![order.id]); - }, - Some(mut orders) => { - orders.push(order.id); - OrdersByCustomer::::insert(&order.customer_id, orders); - }, - } - } - - pub fn insert_order_id_into_pending_orders_by_seller(order: &OrderOf) { - match PendingOrdersBySeller::::get(&order.seller_id) { - None => { - PendingOrdersBySeller::::insert(&order.seller_id, vec![order.id]); - }, - Some(mut orders) => { - orders.push(order.id); - PendingOrdersBySeller::::insert(&order.seller_id, orders); - }, - } - } - - pub fn remove_order_id_from_pending_orders_by_seller( - seller_id: &T::AccountId, - order_id: &T::Hash, - ) { - let mut orders = PendingOrdersBySeller::::get(seller_id).unwrap_or_default(); - orders.retain(|o_id| o_id != order_id); - PendingOrdersBySeller::::insert(seller_id, orders); - } - - pub fn remove_order_id_from_orders_by_seller(seller_id: &T::AccountId, order_id: &T::Hash) { - let mut orders = OrdersBySeller::::get(seller_id).unwrap_or_default(); - orders.retain(|o_id| o_id != order_id); - OrdersBySeller::::insert(seller_id, orders); - } - - pub fn remove_order_id_from_orders_by_customer(customer_id: &T::AccountId, order_id: &T::Hash) { - let mut orders = OrdersByCustomer::::get(customer_id).unwrap_or_default(); - orders.retain(|o_id| o_id != order_id); - OrdersByCustomer::::insert(customer_id, orders); - } - - pub fn order_can_be_refunded(order: OrderOf) -> bool { - let dna_sample = - T::GeneticTesting::dna_sample_by_tracking_id(&order.dna_sample_tracking_id).unwrap(); - if !dna_sample.is_rejected() { - return false - } - true - } -} - -impl OrderEventEmitter for Pallet { - fn emit_event_order_failed(order_id: &HashOf) { - match Self::order_by_id(order_id) { - None => Self::deposit_event(Event::OrderNotFound), - Some(order) => Self::deposit_event(Event::OrderFailed(order)), - } - } -} - -impl OrderStatusUpdater for Pallet { - fn update_status_failed(order_id: &HashOf) { - match Self::order_by_id(order_id) { - None => Self::deposit_event(Event::OrderNotFound), - Some(order) => { - Self::update_order_status(&order.id, OrderStatus::Failed); - }, - } - } - - fn remove_order_id_from_pending_orders_by_seller( - seller_id: &AccountIdOf, - order_id: &HashOf, - ) { - Self::remove_order_id_from_pending_orders_by_seller(seller_id, order_id); - } - - fn is_pending_order_by_seller_exist(seller_id: &AccountIdOf) -> bool { - Self::is_pending_order_ids_by_seller_exist(seller_id) - } -} diff --git a/pallets/orders/src/types.rs b/pallets/orders/src/types.rs new file mode 100644 index 00000000..c8825f3f --- /dev/null +++ b/pallets/orders/src/types.rs @@ -0,0 +1,99 @@ +use crate::*; + +use frame_support::{pallet_prelude::*, traits::Currency}; +use primitives_price_and_currency::{CurrencyType, Price}; +use scale_info::TypeInfo; +use sp_std::vec::Vec; + +pub type AccountIdOf = ::AccountId; +pub type MomentOf = ::Moment; +pub type HashOf = ::Hash; +pub type CurrencyOf = ::Currency; +pub type BalanceOf = as Currency>>::Balance; +pub type OrderOf = Order, AccountIdOf, BalanceOf, MomentOf>; +pub type OrderIdsOf = Vec>; + +#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub enum OrderStatus { + Unpaid, + Paid, + Fulfilled, + Refunded, + Cancelled, + Failed, +} +impl Default for OrderStatus { + fn default() -> Self { + OrderStatus::Unpaid + } +} + +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct Order { + pub id: Hash, + pub service_id: Hash, + pub customer_id: AccountId, + pub customer_box_public_key: Hash, + pub seller_id: AccountId, + pub dna_sample_tracking_id: DnaSampleTrackingId, + pub currency: CurrencyType, + pub prices: Vec>, + pub additional_prices: Vec>, + pub status: OrderStatus, + pub order_flow: ServiceFlow, + pub created_at: Moment, + pub updated_at: Moment, +} +#[allow(clippy::too_many_arguments)] +impl Order { + pub fn new( + id: Hash, + service_id: Hash, + customer_id: AccountId, + customer_box_public_key: Hash, + seller_id: AccountId, + dna_sample_tracking_id: DnaSampleTrackingId, + currency: CurrencyType, + order_flow: ServiceFlow, + prices: Vec>, + additional_prices: Vec>, + created_at: Moment, + updated_at: Moment, + ) -> Self { + Self { + id, + service_id, + customer_id, + customer_box_public_key, + seller_id, + dna_sample_tracking_id, + currency, + prices, + additional_prices, + status: OrderStatus::default(), + order_flow, + created_at, + updated_at, + } + } + + pub fn get_id(&self) -> &Hash { + &self.id + } + + pub fn get_customer_id(&self) -> &AccountId { + &self.customer_id + } + + pub fn get_seller_id(&self) -> &AccountId { + &self.seller_id + } + + pub fn get_created_at(&self) -> &Moment { + &self.created_at + } + + pub fn get_service_id(&self) -> &Hash { + &self.service_id + } +} diff --git a/pallets/services/src/functions.rs b/pallets/services/src/functions.rs new file mode 100644 index 00000000..18dcad5f --- /dev/null +++ b/pallets/services/src/functions.rs @@ -0,0 +1,38 @@ +use crate::*; + +use frame_support::{pallet_prelude::*, sp_runtime::traits::Hash}; + +/// Pallet Methods +impl Pallet { + pub fn generate_service_id(owner_id: &T::AccountId, service_count: u64) -> T::Hash { + let mut account_id_bytes = owner_id.encode(); + let mut service_count_bytes = service_count.encode(); + account_id_bytes.append(&mut service_count_bytes); + + let seed = &account_id_bytes; + T::Hashing::hash(seed) + } + + // Services Count Addition and Substraction Helpers + // Add services count + pub fn add_services_count() { + let services_count = >::get().unwrap_or(0); + >::put(services_count.wrapping_add(1)); + } + // Add services count by owner + pub fn add_services_count_by_owner(owner_id: &T::AccountId) { + let services_count = ServicesCountByOwner::::get(owner_id).unwrap_or(0); + ServicesCountByOwner::::insert(owner_id, services_count.wrapping_add(1)) + } + + // Subtract services count + pub fn sub_services_count() { + let services_count = >::get().unwrap_or(1); + ServicesCount::::put(services_count - 1); + } + // Subtract services count by owner + pub fn sub_services_count_by_owner(owner_id: &T::AccountId) { + let services_count = ServicesCountByOwner::::get(owner_id).unwrap_or(1); + ServicesCountByOwner::::insert(owner_id, services_count - 1); + } +} diff --git a/pallets/services/src/impl_services.rs b/pallets/services/src/impl_services.rs new file mode 100644 index 00000000..26a9c320 --- /dev/null +++ b/pallets/services/src/impl_services.rs @@ -0,0 +1,150 @@ +use crate::*; + +use traits_services::ServiceOwnerInfo; + +/// Service Interface Implementation +impl ServiceInterface for Pallet { + type Error = Error; + type ServiceId = T::Hash; + type Service = ServiceOf; + type ServiceInfo = ServiceInfoOf; + type ServiceFlow = ServiceFlow; + + /// Create Service + /// Add reference to ServicesByCountryCity storage + /// Associate service reference to the owner (creator) + /// Increment Counts + fn create_service( + owner_id: &T::AccountId, + service_info: &Self::ServiceInfo, + service_flow: &Self::ServiceFlow, + ) -> Result { + // Check if user can create_service + let can_create_service = T::ServiceOwner::can_create_service(owner_id); + if !can_create_service { + return Err(Error::::NotAllowedToCreate) + } + + let owner_service_count = >::services_count_by_owner(owner_id); + let service_id = Self::generate_service_id(owner_id, owner_service_count); + + // Calculate total price + let mut service_info_mut = service_info.clone(); + for (idx, price_by_currency) in service_info.prices_by_currency.iter().enumerate() { + service_info_mut.prices_by_currency[idx].total_price -= price_by_currency.total_price; + for price_component in price_by_currency.price_components.iter() { + service_info_mut.prices_by_currency[idx].total_price += price_component.value; + } + + for additional_price in price_by_currency.additional_prices.iter() { + service_info_mut.prices_by_currency[idx].total_price += additional_price.value; + } + } + + let service = + Service::new(service_id, owner_id.clone(), service_info_mut, service_flow.clone()); + // Store to Services storage + Services::::insert(service_id, &service); + + // Increment Services Count + Self::add_services_count(); + // Increment ServicesCountByOwner + Self::add_services_count_by_owner(&service.owner_id); + + // Associate created service to the owner + T::ServiceOwner::associate(owner_id, &service_id); + + Ok(service) + } + + /// Update Service information + fn update_service( + owner_id: &T::AccountId, + service_id: &Self::ServiceId, + service_info: &Self::ServiceInfo, + ) -> Result { + let service = Services::::get(service_id); + if service.is_none() { + return Err(Error::::ServiceDoesNotExist) + } + let mut service = service.unwrap(); + + if service.owner_id != owner_id.clone() { + return Err(Error::::NotServiceOwner) + } + + // Calculate total price + let mut service_info_mut = service_info.clone(); + for (idx, price_by_currency) in service_info.prices_by_currency.iter().enumerate() { + service_info_mut.prices_by_currency[idx].total_price -= price_by_currency.total_price; + for price_component in price_by_currency.price_components.iter() { + service_info_mut.prices_by_currency[idx].total_price += price_component.value; + } + + for additional_price in price_by_currency.additional_prices.iter() { + service_info_mut.prices_by_currency[idx].total_price += additional_price.value; + } + } + + service.info = service_info_mut; + Services::::insert(service_id, &service); + + Ok(service) + } + + /// Delete Service + /// Delete from Services Storage + /// Remove the service id reference in ServicesByCountryCity storage + /// Disassociate service id from the owner + /// Decrement Counts + fn delete_service( + owner_id: &T::AccountId, + service_id: &Self::ServiceId, + ) -> Result { + let service = Services::::get(service_id); + if service.is_none() { + return Err(Error::::ServiceDoesNotExist) + } + let service = service.unwrap(); + + if service.owner_id != owner_id.clone() { + return Err(Error::::NotServiceOwner) + } + // Remove service from storage + let service = Services::::take(service_id).unwrap(); + + let owner = T::ServiceOwner::get_owner(owner_id).unwrap(); + // disassociate service reference from the owner + T::ServiceOwner::disassociate(owner.get_id(), &service.id); + // Decrement counts + Self::sub_services_count(); + Self::sub_services_count_by_owner(owner.get_id()); + + Ok(service) + } + + fn service_by_id(service_id: &Self::ServiceId) -> Option { + Services::::get(service_id) + } + + fn services_count_by_owner(owner_id: &T::AccountId) -> u64 { + Self::services_count_by_owner(owner_id).unwrap_or(0) + } +} + +/// ServicesProvider Trait Implementation +impl ServicesProvider for Pallet +where + ServiceOf: traits_services::ServiceInfo, +{ + type Error = Error; + type Service = ServiceOf; + + fn service_by_id(id: &T::Hash) -> Option> { + >::service_by_id(id) + } + + fn delete_service(owner_id: &T::AccountId, id: &T::Hash) -> Result { + >::delete_service(owner_id, id) + } +} diff --git a/pallets/services/src/interface.rs b/pallets/services/src/interface.rs index ba8e5965..949c43a6 100644 --- a/pallets/services/src/interface.rs +++ b/pallets/services/src/interface.rs @@ -7,8 +7,6 @@ pub trait ServiceInterface { type ServiceInfo; type ServiceFlow; - fn generate_service_id(owner_id: &T::AccountId, service_count: u64) -> Self::ServiceId; - fn create_service( owner_id: &T::AccountId, service: &Self::ServiceInfo, diff --git a/pallets/services/src/lib.rs b/pallets/services/src/lib.rs index cd93bb63..744dbd45 100644 --- a/pallets/services/src/lib.rs +++ b/pallets/services/src/lib.rs @@ -1,101 +1,26 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ - codec::{Decode, Encode}, - pallet_prelude::*, - traits::Currency, -}; pub use pallet::*; -pub use scale_info::TypeInfo; -use traits_services::{ - types::ServiceFlow, ServiceInfo as ServiceInfoT, ServiceOwner, ServicesProvider, -}; - -use primitives_duration::ExpectedDuration; -use primitives_price_and_currency::PriceByCurrency; +pub mod functions; +pub mod impl_services; pub mod interface; +pub mod types; pub mod weights; -pub use interface::ServiceInterface; -use sp_std::prelude::*; - -/// ServiceInfo struct -/// Information that is mutable by user -#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo)] -pub struct ServiceInfo { - pub name: Vec, - pub prices_by_currency: Vec>, - pub expected_duration: ExpectedDuration, - pub category: Vec, - pub description: Vec, // TODO: limit the length - pub dna_collection_process: Vec, - pub test_result_sample: Vec, - pub long_description: Option>, - pub image: Option>, -} - -#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo)] -pub struct Service { - pub id: Hash, - pub owner_id: AccountId, - pub info: ServiceInfo, - pub service_flow: ServiceFlow, -} -impl Service { - pub fn new( - id: Hash, - owner_id: AccountId, - info: ServiceInfo, - service_flow: ServiceFlow, - ) -> Self { - Self { id, owner_id, info, service_flow } - } - - pub fn get_id(&self) -> &Hash { - &self.id - } - - pub fn get_owner_id(&self) -> &AccountId { - &self.owner_id - } - - pub fn get_service_flow(&self) -> &ServiceFlow { - &self.service_flow - } - - pub fn get_prices_by_currency(&self) -> &Vec> { - &self.info.prices_by_currency - } -} -impl ServiceInfoT for Service -where - T: frame_system::Config, -{ - fn get_id(&self) -> &Hash { - self.get_id() - } - fn get_owner_id(&self) -> &AccountId { - self.get_owner_id() - } - fn get_service_flow(&self) -> &ServiceFlow { - self.get_service_flow() - } - fn get_prices_by_currency(&self) -> &Vec> { - self.get_prices_by_currency() - } -} +pub use interface::ServiceInterface; +pub use traits_services::{types::ServiceFlow, ServiceOwner, ServicesProvider}; +pub use types::*; +pub use weights::WeightInfo; #[frame_support::pallet] pub mod pallet { - use crate::{ - interface::ServiceInterface, weights::WeightInfo, Currency, Service, ServiceInfo, - ServiceOwner, + use super::*; + + use frame_support::{ + dispatch::DispatchResultWithPostInfo, pallet_prelude::*, traits::Currency, }; - use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*}; use frame_system::pallet_prelude::*; - pub use sp_std::prelude::*; - use traits_services::types::ServiceFlow; #[pallet::config] pub trait Config: frame_system::Config { @@ -115,15 +40,6 @@ pub mod pallet { impl Hooks> for Pallet {} // -------------------------------------------------------- - // ----- Types ------- - pub type AccountIdOf = ::AccountId; - pub type HashOf = ::Hash; - pub type CurrencyOf = ::Currency; - pub type BalanceOf = as Currency>>::Balance; - pub type ServiceOf = Service, HashOf, BalanceOf>; - pub type ServiceInfoOf = ServiceInfo>; - pub type ServiceIdOf = HashOf; - // ------- Storage ------------- #[pallet::storage] #[pallet::getter(fn service_by_id)] @@ -216,188 +132,3 @@ pub mod pallet { } } } - -use frame_support::sp_runtime::traits::Hash; -use traits_services::ServiceOwnerInfo; - -/// Service Interface Implementation -impl ServiceInterface for Pallet { - type Error = Error; - type ServiceId = T::Hash; - type Service = ServiceOf; - type ServiceInfo = ServiceInfoOf; - type ServiceFlow = ServiceFlow; - - fn generate_service_id(owner_id: &T::AccountId, service_count: u64) -> Self::ServiceId { - let mut account_id_bytes = owner_id.encode(); - let mut service_count_bytes = service_count.encode(); - account_id_bytes.append(&mut service_count_bytes); - - let seed = &account_id_bytes; - T::Hashing::hash(seed) - } - - /// Create Service - /// Add reference to ServicesByCountryCity storage - /// Associate service reference to the owner (creator) - /// Increment Counts - fn create_service( - owner_id: &T::AccountId, - service_info: &Self::ServiceInfo, - service_flow: &Self::ServiceFlow, - ) -> Result { - // Check if user can create_service - let can_create_service = T::ServiceOwner::can_create_service(owner_id); - if !can_create_service { - return Err(Error::::NotAllowedToCreate) - } - - let owner_service_count = >::services_count_by_owner(owner_id); - let service_id = Self::generate_service_id(owner_id, owner_service_count); - - // Calculate total price - let mut service_info_mut = service_info.clone(); - for (idx, price_by_currency) in service_info.prices_by_currency.iter().enumerate() { - service_info_mut.prices_by_currency[idx].total_price -= price_by_currency.total_price; - for price_component in price_by_currency.price_components.iter() { - service_info_mut.prices_by_currency[idx].total_price += price_component.value; - } - - for additional_price in price_by_currency.additional_prices.iter() { - service_info_mut.prices_by_currency[idx].total_price += additional_price.value; - } - } - - let service = - Service::new(service_id, owner_id.clone(), service_info_mut, service_flow.clone()); - // Store to Services storage - Services::::insert(service_id, &service); - - // Increment Services Count - Self::add_services_count(); - // Increment ServicesCountByOwner - Self::add_services_count_by_owner(&service.owner_id); - - // Associate created service to the owner - T::ServiceOwner::associate(owner_id, &service_id); - - Ok(service) - } - - /// Update Service information - fn update_service( - owner_id: &T::AccountId, - service_id: &Self::ServiceId, - service_info: &Self::ServiceInfo, - ) -> Result { - let service = Services::::get(service_id); - if service.is_none() { - return Err(Error::::ServiceDoesNotExist) - } - let mut service = service.unwrap(); - - if service.owner_id != owner_id.clone() { - return Err(Error::::NotServiceOwner) - } - - // Calculate total price - let mut service_info_mut = service_info.clone(); - for (idx, price_by_currency) in service_info.prices_by_currency.iter().enumerate() { - service_info_mut.prices_by_currency[idx].total_price -= price_by_currency.total_price; - for price_component in price_by_currency.price_components.iter() { - service_info_mut.prices_by_currency[idx].total_price += price_component.value; - } - - for additional_price in price_by_currency.additional_prices.iter() { - service_info_mut.prices_by_currency[idx].total_price += additional_price.value; - } - } - - service.info = service_info_mut; - Services::::insert(service_id, &service); - - Ok(service) - } - - /// Delete Service - /// Delete from Services Storage - /// Remove the service id reference in ServicesByCountryCity storage - /// Disassociate service id from the owner - /// Decrement Counts - fn delete_service( - owner_id: &T::AccountId, - service_id: &Self::ServiceId, - ) -> Result { - let service = Services::::get(service_id); - if service.is_none() { - return Err(Error::::ServiceDoesNotExist) - } - let service = service.unwrap(); - - if service.owner_id != owner_id.clone() { - return Err(Error::::NotServiceOwner) - } - // Remove service from storage - let service = Services::::take(service_id).unwrap(); - - let owner = T::ServiceOwner::get_owner(owner_id).unwrap(); - // disassociate service reference from the owner - T::ServiceOwner::disassociate(owner.get_id(), &service.id); - // Decrement counts - Self::sub_services_count(); - Self::sub_services_count_by_owner(owner.get_id()); - - Ok(service) - } - - fn service_by_id(service_id: &Self::ServiceId) -> Option { - Services::::get(service_id) - } - - fn services_count_by_owner(owner_id: &T::AccountId) -> u64 { - Self::services_count_by_owner(owner_id).unwrap_or(0) - } -} - -/// Pallet Methods -impl Pallet { - // Services Count Addition and Substraction Helpers - // Add services count - pub fn add_services_count() { - let services_count = >::get().unwrap_or(0); - >::put(services_count.wrapping_add(1)); - } - // Add services count by owner - pub fn add_services_count_by_owner(owner_id: &T::AccountId) { - let services_count = ServicesCountByOwner::::get(owner_id).unwrap_or(0); - ServicesCountByOwner::::insert(owner_id, services_count.wrapping_add(1)) - } - - // Subtract services count - pub fn sub_services_count() { - let services_count = >::get().unwrap_or(1); - ServicesCount::::put(services_count - 1); - } - // Subtract services count by owner - pub fn sub_services_count_by_owner(owner_id: &T::AccountId) { - let services_count = ServicesCountByOwner::::get(owner_id).unwrap_or(1); - ServicesCountByOwner::::insert(owner_id, services_count - 1); - } -} - -/// ServicesProvider Trait Implementation -impl ServicesProvider for Pallet -where - ServiceOf: traits_services::ServiceInfo, -{ - type Error = Error; - type Service = ServiceOf; - - fn service_by_id(id: &T::Hash) -> Option> { - >::service_by_id(id) - } - - fn delete_service(owner_id: &T::AccountId, id: &T::Hash) -> Result { - >::delete_service(owner_id, id) - } -} diff --git a/pallets/services/src/types.rs b/pallets/services/src/types.rs new file mode 100644 index 00000000..c1ce50a7 --- /dev/null +++ b/pallets/services/src/types.rs @@ -0,0 +1,83 @@ +use crate::*; + +use frame_support::{pallet_prelude::*, traits::Currency}; +use primitives_duration::ExpectedDuration; +use primitives_price_and_currency::PriceByCurrency; +use scale_info::TypeInfo; +use sp_std::vec::Vec; +use traits_services::{types::ServiceFlow, ServiceInfo as ServiceInfoT}; + +pub type AccountIdOf = ::AccountId; +pub type HashOf = ::Hash; +pub type CurrencyOf = ::Currency; +pub type BalanceOf = as Currency>>::Balance; +pub type ServiceOf = Service, HashOf, BalanceOf>; +pub type ServiceInfoOf = ServiceInfo>; +pub type ServiceIdOf = HashOf; + +/// ServiceInfo struct +/// Information that is mutable by user +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct ServiceInfo { + pub name: Vec, + pub prices_by_currency: Vec>, + pub expected_duration: ExpectedDuration, + pub category: Vec, + pub description: Vec, // TODO: limit the length + pub dna_collection_process: Vec, + pub test_result_sample: Vec, + pub long_description: Option>, + pub image: Option>, +} + +#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct Service { + pub id: Hash, + pub owner_id: AccountId, + pub info: ServiceInfo, + pub service_flow: ServiceFlow, +} +impl Service { + pub fn new( + id: Hash, + owner_id: AccountId, + info: ServiceInfo, + service_flow: ServiceFlow, + ) -> Self { + Self { id, owner_id, info, service_flow } + } + + pub fn get_id(&self) -> &Hash { + &self.id + } + + pub fn get_owner_id(&self) -> &AccountId { + &self.owner_id + } + + pub fn get_service_flow(&self) -> &ServiceFlow { + &self.service_flow + } + + pub fn get_prices_by_currency(&self) -> &Vec> { + &self.info.prices_by_currency + } +} + +impl ServiceInfoT for Service +where + T: frame_system::Config, +{ + fn get_id(&self) -> &Hash { + self.get_id() + } + fn get_owner_id(&self) -> &AccountId { + self.get_owner_id() + } + fn get_service_flow(&self) -> &ServiceFlow { + self.get_service_flow() + } + fn get_prices_by_currency(&self) -> &Vec> { + self.get_prices_by_currency() + } +}