From 723ca387a1f2ed9dd1dde78a262fdc5fdd1038f1 Mon Sep 17 00:00:00 2001 From: st170001 Date: Fri, 10 May 2024 13:31:45 +0200 Subject: [PATCH 1/7] Additional order fields --- src/authentication.rs | 83 ------------------------------ src/authorization.rs | 95 +++++++++++++++++++++++++++++++++++ src/collection_helper.rs | 11 ++++ src/main.rs | 5 +- src/mutation.rs | 45 +++++++++++++---- src/mutation_input_structs.rs | 15 ++++-- src/order.rs | 10 ++++ src/payment_authorization.rs | 22 ++++++++ src/query.rs | 6 +-- src/user.rs | 4 +- 10 files changed, 192 insertions(+), 104 deletions(-) delete mode 100644 src/authentication.rs create mode 100644 src/authorization.rs create mode 100644 src/collection_helper.rs create mode 100644 src/payment_authorization.rs diff --git a/src/authentication.rs b/src/authentication.rs deleted file mode 100644 index dd8cc45..0000000 --- a/src/authentication.rs +++ /dev/null @@ -1,83 +0,0 @@ -use async_graphql::{Context, Error, Result}; -use axum::http::HeaderMap; -use bson::Uuid; -use serde::{Deserialize, Serialize}; - -// Authorized-User HTTP header. -#[derive(Serialize, Deserialize, Debug)] -pub struct AuthorizedUserHeader { - id: Uuid, - roles: Vec, -} - -// Extraction of AuthorizedUserHeader from HeaderMap. -impl TryFrom<&HeaderMap> for AuthorizedUserHeader { - type Error = Error; - - // Tries to extract the AuthorizedUserHeader from a HeaderMap. - // - // Returns a GraphQL Error if the extraction fails. - fn try_from(header_map: &HeaderMap) -> Result { - if let Some(authenticate_user_header_value) = header_map.get("Authorized-User") { - if let Ok(authenticate_user_header_str) = authenticate_user_header_value.to_str() { - let authenticate_user_header: AuthorizedUserHeader = - serde_json::from_str(authenticate_user_header_str)?; - return Ok(authenticate_user_header); - } - } - Err(Error::new( - "Authentication failed. Authorized-User header is not set or could not be parsed.", - )) - } -} - -// Role of user. -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] -#[serde(rename_all = "snake_case")] -enum Role { - Buyer, - Admin, - Employee, -} - -impl Role { - // Defines if user has a permissive role. - fn is_permissive(self) -> bool { - match self { - Self::Buyer => false, - Self::Admin => true, - Self::Employee => true, - } - } -} - -// Authenticate user of UUID for a Context. -pub fn authenticate_user(ctx: &Context, id: Uuid) -> Result<()> { - match ctx.data::() { - Ok(authenticate_user_header) => check_permissions(&authenticate_user_header, id), - Err(_) => Err(Error::new( - "Authentication failed. Authorized-User header is not set or could not be parsed.", - )), - } -} - -// Check if user of UUID has a valid permission according to the AuthorizedUserHeader. -// -// Permission is valid if the user has `Role::Buyer` and the same UUID as provided in the function parameter. -// Permission is valid if the user has a permissive role: `user.is_permissive() == true`, regardless of the users UUID. -pub fn check_permissions(authenticate_user_header: &AuthorizedUserHeader, id: Uuid) -> Result<()> { - if authenticate_user_header - .roles - .iter() - .any(|r| r.is_permissive()) - || authenticate_user_header.id == id - { - return Ok(()); - } else { - let message = format!( - "Authentication failed for user of UUID: `{}`. Operation not permitted.", - authenticate_user_header.id - ); - return Err(Error::new(message)); - } -} diff --git a/src/authorization.rs b/src/authorization.rs new file mode 100644 index 0000000..d960cdb --- /dev/null +++ b/src/authorization.rs @@ -0,0 +1,95 @@ +use async_graphql::{Context, Error, Result}; +use axum::http::HeaderMap; +use bson::Uuid; +use serde::{Deserialize, Serialize}; + +/// `Authorized-User` HTTP header. +#[derive(Deserialize, Debug, Serialize)] +pub struct AuthorizedUserHeader { + id: Uuid, + roles: Vec, +} + +/// Extraction of `Authorized-User` header from header map. +impl TryFrom<&HeaderMap> for AuthorizedUserHeader { + type Error = Error; + + /// Tries to extract the `Authorized-User` header from a header map. + /// + /// Returns a GraphQL error if the extraction fails. + fn try_from(header_map: &HeaderMap) -> Result { + if let Some(authorized_user_header_value) = header_map.get("Authorized-User") { + if let Ok(authorized_user_header_str) = authorized_user_header_value.to_str() { + let authorized_user_header: AuthorizedUserHeader = + serde_json::from_str(authorized_user_header_str)?; + return Ok(authorized_user_header); + } + } + Err(Error::new( + "Authorization failed. Authorized-User header is not set or could not be parsed.", + )) + } +} + +/// Role of user. +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[serde(rename_all = "snake_case")] +enum Role { + Buyer, + Admin, + Employee, +} + +impl Role { + /// Defines if user has a permissive role. + fn is_permissive(self) -> bool { + match self { + Self::Buyer => false, + Self::Admin => true, + Self::Employee => true, + } + } +} + +/// Authorize user of UUID for a context. +/// +/// * `context` - GraphQL context containing the `Authorized-User` header. +/// * `id` - Option of UUID of the user to authorize. +pub fn authorize_user(ctx: &Context, id: Option) -> Result<()> { + match ctx.data::() { + Ok(authorized_user_header) => check_permissions(&authorized_user_header, id), + Err(_) => Err(Error::new( + "Authentication failed. Authorized-User header is not set or could not be parsed.", + )), + } +} + +/// Check if user of UUID has a valid permission according to the `Authorized-User` header. +/// +/// Permission is valid if the user has `Role::Buyer` and the same UUID as provided in the function parameter. +/// Permission is valid if the user has a permissive role: `user.is_permissive() == true`, regardless of the users UUID. +/// +/// * `authorized_user_header` - `Authorized-User` header containing the users UUID and role. +/// * `id` - Option of UUID of the user to authorize. +pub fn check_permissions( + authorized_user_header: &AuthorizedUserHeader, + id: Option, +) -> Result<()> { + let id_contained_in_header = id + .and_then(|id| Some(authorized_user_header.id == id)) + .unwrap_or(false); + if authorized_user_header + .roles + .iter() + .any(|role| role.is_permissive()) + || id_contained_in_header + { + return Ok(()); + } else { + let message = format!( + "Authentication failed for user of UUID: `{}`. Operation not permitted.", + authorized_user_header.id + ); + return Err(Error::new(message)); + } +} diff --git a/src/collection_helper.rs b/src/collection_helper.rs new file mode 100644 index 0000000..7f37248 --- /dev/null +++ b/src/collection_helper.rs @@ -0,0 +1,11 @@ +use std::any::type_name; + +// Matches MongoDB collection name to a type T. +fn infer_collection_name() -> String { + let type_name = type_name::(); + type_name.find(pattern) +} + +fn extract_prefixless_type(input: &str) -> IResult<&str, &str> { + +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 9f16820..b8ae548 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,8 +46,8 @@ use http_event_service::{ on_user_address_creation_event, HttpEventServiceState, }; -mod authentication; -use authentication::AuthorizedUserHeader; +mod authorization; +use authorization::AuthorizedUserHeader; mod order_compensation; @@ -58,6 +58,7 @@ mod mutation_input_structs; mod order_connection; mod order_datatypes; mod order_item_connection; +mod payment_authorization; mod product_variant_version_connection; /// Builds the GraphiQL frontend. diff --git a/src/mutation.rs b/src/mutation.rs index 44a1e47..d104079 100644 --- a/src/mutation.rs +++ b/src/mutation.rs @@ -16,8 +16,8 @@ use std::collections::HashMap; use std::time::Duration; use std::time::SystemTime; -use crate::authentication::authenticate_user; -use crate::authentication::AuthorizedUserHeader; +use crate::authorization::authorize_user; +use crate::authorization::AuthorizedUserHeader; use crate::foreign_types::Coupon; use crate::foreign_types::Discount; use crate::foreign_types::ProductVariant; @@ -31,6 +31,7 @@ use crate::mutation_input_structs::OrderItemInput; use crate::order::OrderDTO; use crate::order::OrderStatus; use crate::order_item::OrderItem; +use crate::payment_authorization::PaymentAuthorization; use crate::query::query_object; use crate::query::query_objects; use crate::user::User; @@ -51,7 +52,7 @@ impl Mutation { ctx: &Context<'a>, #[graphql(desc = "CreateOrderInput")] input: CreateOrderInput, ) -> Result { - authenticate_user(&ctx, input.user_id)?; + authorize_user(&ctx, Some(input.user_id))?; let db_client = ctx.data::()?; let collection: Collection = db_client.collection::("orders"); validate_order_input(db_client, &input).await?; @@ -62,6 +63,7 @@ impl Mutation { let invoice_address = UserAddress::from(input.invoice_address_id); let compensatable_order_amount = calculate_compensatable_order_amount(&internal_order_items); + let payment_authorization = build_payment_authorization(&input); let order = Order { _id: Uuid::new(), user: User::from(input.user_id), @@ -74,14 +76,10 @@ impl Mutation { invoice_address, compensatable_order_amount, payment_information_id: input.payment_information_id, + vat_number: input.vat_number, + payment_authorization, }; - match collection.insert_one(order, None).await { - Ok(result) => { - let id = uuid_from_bson(result.inserted_id)?; - query_order(&collection, id).await - } - Err(_) => Err(Error::new("Adding order failed in MongoDB.")), - } + insert_order_in_mongodb(&collection, order).await } /// Places an existing order by changing its status to `OrderStatus::Placed`. @@ -93,7 +91,7 @@ impl Mutation { let db_client = ctx.data::()?; let collection: Collection = db_client.collection::("orders"); let mut order = query_order(&collection, id).await?; - authenticate_user(&ctx, order.user._id)?; + authorize_user(&ctx, Some(order.user._id))?; set_status_placed(&collection, id).await?; order = query_order(&collection, id).await?; send_order_created_event(order.clone()).await?; @@ -101,6 +99,31 @@ impl Mutation { } } +/// Builds payment authorization from create order input. +/// +/// `create_order_input` - The create order input to build the payment authorization from. +fn build_payment_authorization(input: &CreateOrderInput) -> Option { + input + .payment_authorization + .clone() + .and_then(|definitely_payment_authorization| { + Option::::from(definitely_payment_authorization) + }) +} + +/// Inserts order in MongoDB and returns the order itself. +/// +/// * `collection` - MongoDB collection to insert order in. +/// * `order` - Order to insert. +async fn insert_order_in_mongodb(collection: &Collection, order: Order) -> Result { + match collection.insert_one(order, None).await { + Ok(result) => { + let id = uuid_from_bson(result.inserted_id)?; + query_order(&collection, id).await + } + Err(_) => Err(Error::new("Adding order failed in MongoDB.")), + } +} /// Calculates the total compensatable amount of all order items in the input by summing up their `compensatable_amount` attributes. fn calculate_compensatable_order_amount(order_items: &Vec) -> u64 { order_items.iter().map(|o| o.compensatable_amount).sum() diff --git a/src/mutation_input_structs.rs b/src/mutation_input_structs.rs index 5c83947..c119b59 100644 --- a/src/mutation_input_structs.rs +++ b/src/mutation_input_structs.rs @@ -1,11 +1,11 @@ -use async_graphql::{InputObject, SimpleObject}; +use async_graphql::InputObject; use bson::Uuid; use std::{ cmp::Ordering, collections::{BTreeSet, HashSet}, }; -#[derive(SimpleObject, InputObject)] +#[derive(InputObject)] pub struct CreateOrderInput { /// UUID of user owning the order. pub user_id: Uuid, @@ -17,9 +17,13 @@ pub struct CreateOrderInput { pub invoice_address_id: Uuid, /// UUID of payment information that the order should be processed with. pub payment_information_id: Uuid, + /// VAT number. + pub vat_number: String, + /// Optional payment authorization data. + pub payment_authorization: Option, } -#[derive(SimpleObject, InputObject, PartialEq, Eq, Clone)] +#[derive(InputObject, PartialEq, Eq, Clone)] pub struct OrderItemInput { /// UUID of shopping cart item associated with order item. pub shopping_cart_item_id: Uuid, @@ -29,6 +33,11 @@ pub struct OrderItemInput { pub coupon_ids: HashSet, } +#[derive(Debug, InputObject, Clone)] +pub struct PaymentAuthorizationInput { + pub cvc: Option, +} + impl PartialOrd for OrderItemInput { fn partial_cmp(&self, other: &Self) -> Option { self.shopping_cart_item_id diff --git a/src/order.rs b/src/order.rs index fc2bbf3..58262a3 100644 --- a/src/order.rs +++ b/src/order.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::foreign_types::UserAddress; use crate::order_datatypes::OrderDirection; use crate::order_item::OrderItemDTO; +use crate::payment_authorization::PaymentAuthorization; use crate::{ order_datatypes::CommonOrderInput, order_item::OrderItem, order_item_connection::OrderItemConnection, user::User, @@ -41,6 +42,12 @@ pub struct Order { pub compensatable_order_amount: u64, /// UUID of payment information that the order should be processed with. pub payment_information_id: Uuid, + /// VAT number. + pub vat_number: String, + /// Optional payment authorization information. + /// This field is not queriable with GraphQL. + #[graphql(skip)] + pub payment_authorization: Option, } #[ComplexObject] @@ -161,6 +168,8 @@ pub struct OrderDTO { pub compensatable_order_amount: u64, /// UUID of payment information that the order should be processed with. pub payment_information_id: Uuid, + /// Optional payment authorization information. + pub payment_authorization: Option, } impl TryFrom for OrderDTO { @@ -187,6 +196,7 @@ impl TryFrom for OrderDTO { invoice_address_id: value.invoice_address._id, compensatable_order_amount: value.compensatable_order_amount, payment_information_id: value.payment_information_id, + payment_authorization: value.payment_authorization, }; Ok(order_dto) } diff --git a/src/payment_authorization.rs b/src/payment_authorization.rs new file mode 100644 index 0000000..180ab79 --- /dev/null +++ b/src/payment_authorization.rs @@ -0,0 +1,22 @@ +use serde::{Deserialize, Serialize}; + +use crate::mutation_input_structs::PaymentAuthorizationInput; + +/// Payment authorization data that can be attached to order. +/// +/// This datatype can be extended for different payment authorization. +/// The conversion implementation needs to be adapted accordingly. +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub enum PaymentAuthorization { + /// CVC/CVV number of 3-4 digits. + CVC(u16), +} + +impl From for Option { + fn from(value: PaymentAuthorizationInput) -> Self { + match value.cvc { + Some(definitely_cvc) => Some(PaymentAuthorization::CVC(definitely_cvc)), + None => None, + } + } +} diff --git a/src/query.rs b/src/query.rs index 5a46fa0..215d1f8 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,6 +1,6 @@ use std::{any::type_name, collections::HashMap}; -use crate::{authentication::authenticate_user, order_item::OrderItem, user::User, Order}; +use crate::{authorization::authorize_user, order_item::OrderItem, user::User, Order}; use async_graphql::{Context, Error, Object, Result}; use bson::Uuid; @@ -34,7 +34,7 @@ impl Query { let db_client = ctx.data::()?; let collection: Collection = db_client.collection::("orders"); let order = query_object(&collection, id).await?; - authenticate_user(&ctx, order.user._id)?; + authorize_user(&ctx, Some(order.user._id))?; Ok(order) } @@ -63,7 +63,7 @@ impl Query { db_client.collection::("order_items"); let order_item = query_object(&order_item_collection, id).await?; let user = query_user_from_order_item_id(&order_collection, id).await?; - authenticate_user(&ctx, user._id)?; + authorize_user(&ctx, Some(user._id))?; Ok(order_item) } diff --git a/src/user.rs b/src/user.rs index 33cd5c3..045356b 100644 --- a/src/user.rs +++ b/src/user.rs @@ -5,7 +5,7 @@ use mongodb_cursor_pagination::{error::CursorError, FindResult, PaginatedCursor} use serde::{Deserialize, Serialize}; use crate::{ - authentication::authenticate_user, + authorization::authorize_user, base_connection::{BaseConnection, FindResultWrapper}, order::Order, order_connection::OrderConnection, @@ -38,7 +38,7 @@ impl User { OrderOrderInput, >, ) -> Result { - authenticate_user(&ctx, self._id)?; + authorize_user(&ctx, Some(self._id))?; let db_client = ctx.data::()?; let collection: Collection = db_client.collection::("orders"); let order_order = order_by.unwrap_or_default(); From a2f390bf6f42ff0adbedc7aa00eb05fe06ef5d42 Mon Sep 17 00:00:00 2001 From: st170001 Date: Fri, 10 May 2024 13:35:23 +0200 Subject: [PATCH 2/7] Removed unused file --- src/collection_helper.rs | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 src/collection_helper.rs diff --git a/src/collection_helper.rs b/src/collection_helper.rs deleted file mode 100644 index 7f37248..0000000 --- a/src/collection_helper.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::any::type_name; - -// Matches MongoDB collection name to a type T. -fn infer_collection_name() -> String { - let type_name = type_name::(); - type_name.find(pattern) -} - -fn extract_prefixless_type(input: &str) -> IResult<&str, &str> { - -} \ No newline at end of file From 22131f066851c6ae19fc590968c54716153795b9 Mon Sep 17 00:00:00 2001 From: st170001 Date: Fri, 10 May 2024 13:36:27 +0200 Subject: [PATCH 3/7] Added doc comment --- src/mutation_input_structs.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mutation_input_structs.rs b/src/mutation_input_structs.rs index c119b59..b1f5ec0 100644 --- a/src/mutation_input_structs.rs +++ b/src/mutation_input_structs.rs @@ -35,6 +35,7 @@ pub struct OrderItemInput { #[derive(Debug, InputObject, Clone)] pub struct PaymentAuthorizationInput { + /// CVC/CVV number of 3-4 digits. pub cvc: Option, } From 86b7681e3c896e2697230446853b02f9a05c705e Mon Sep 17 00:00:00 2001 From: st170001 Date: Fri, 10 May 2024 13:37:52 +0200 Subject: [PATCH 4/7] Fixed typo --- src/payment_authorization.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/payment_authorization.rs b/src/payment_authorization.rs index 180ab79..6cd6dd8 100644 --- a/src/payment_authorization.rs +++ b/src/payment_authorization.rs @@ -4,7 +4,7 @@ use crate::mutation_input_structs::PaymentAuthorizationInput; /// Payment authorization data that can be attached to order. /// -/// This datatype can be extended for different payment authorization. +/// This datatype can be extended with different payment authorization formats. /// The conversion implementation needs to be adapted accordingly. #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub enum PaymentAuthorization { From 5e7a26c54165f5fcdf084744409b3c5c5314ffa6 Mon Sep 17 00:00:00 2001 From: st170001 Date: Fri, 10 May 2024 13:49:34 +0200 Subject: [PATCH 5/7] Small fix in OrderDTO --- src/mutation.rs | 2 +- src/order.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mutation.rs b/src/mutation.rs index d104079..f890f93 100644 --- a/src/mutation.rs +++ b/src/mutation.rs @@ -76,8 +76,8 @@ impl Mutation { invoice_address, compensatable_order_amount, payment_information_id: input.payment_information_id, - vat_number: input.vat_number, payment_authorization, + vat_number: input.vat_number, }; insert_order_in_mongodb(&collection, order).await } diff --git a/src/order.rs b/src/order.rs index 58262a3..eb612ab 100644 --- a/src/order.rs +++ b/src/order.rs @@ -42,12 +42,13 @@ pub struct Order { pub compensatable_order_amount: u64, /// UUID of payment information that the order should be processed with. pub payment_information_id: Uuid, - /// VAT number. - pub vat_number: String, /// Optional payment authorization information. /// This field is not queriable with GraphQL. #[graphql(skip)] pub payment_authorization: Option, + /// VAT number. + #[graphql(skip)] + pub vat_number: String, } #[ComplexObject] @@ -170,6 +171,8 @@ pub struct OrderDTO { pub payment_information_id: Uuid, /// Optional payment authorization information. pub payment_authorization: Option, + /// VAT number. + pub vat_number: String, } impl TryFrom for OrderDTO { @@ -197,6 +200,7 @@ impl TryFrom for OrderDTO { compensatable_order_amount: value.compensatable_order_amount, payment_information_id: value.payment_information_id, payment_authorization: value.payment_authorization, + vat_number: value.vat_number, }; Ok(order_dto) } From cb134e25841c6c65e88dcfe2747e2ed08a55f2f2 Mon Sep 17 00:00:00 2001 From: st170001 Date: Fri, 10 May 2024 16:41:26 +0200 Subject: [PATCH 6/7] Rename payment authorization fields to camelCase on serialization --- src/payment_authorization.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/payment_authorization.rs b/src/payment_authorization.rs index 6cd6dd8..0da7045 100644 --- a/src/payment_authorization.rs +++ b/src/payment_authorization.rs @@ -7,6 +7,7 @@ use crate::mutation_input_structs::PaymentAuthorizationInput; /// This datatype can be extended with different payment authorization formats. /// The conversion implementation needs to be adapted accordingly. #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] pub enum PaymentAuthorization { /// CVC/CVV number of 3-4 digits. CVC(u16), From 98f47b299155399e8daa345728a365aa1307a24a Mon Sep 17 00:00:00 2001 From: st170001 Date: Sun, 12 May 2024 12:08:26 +0200 Subject: [PATCH 7/7] Payment authorization information on order placement mutation --- src/mutation.rs | 26 +++++++++++++------------ src/mutation_input_structs.rs | 14 ++++++++++---- src/order.rs | 36 +++++++++++++++++------------------ 3 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/mutation.rs b/src/mutation.rs index f890f93..e295414 100644 --- a/src/mutation.rs +++ b/src/mutation.rs @@ -28,6 +28,7 @@ use crate::foreign_types::TaxRateVersion; use crate::foreign_types::UserAddress; use crate::mutation_input_structs::CreateOrderInput; use crate::mutation_input_structs::OrderItemInput; +use crate::mutation_input_structs::PlaceOrderInput; use crate::order::OrderDTO; use crate::order::OrderStatus; use crate::order_item::OrderItem; @@ -63,7 +64,6 @@ impl Mutation { let invoice_address = UserAddress::from(input.invoice_address_id); let compensatable_order_amount = calculate_compensatable_order_amount(&internal_order_items); - let payment_authorization = build_payment_authorization(&input); let order = Order { _id: Uuid::new(), user: User::from(input.user_id), @@ -76,33 +76,36 @@ impl Mutation { invoice_address, compensatable_order_amount, payment_information_id: input.payment_information_id, - payment_authorization, vat_number: input.vat_number, }; insert_order_in_mongodb(&collection, order).await } /// Places an existing order by changing its status to `OrderStatus::Placed`. + /// + /// Adds optional payment authorization input to order DTO when placing order. async fn place_order<'a>( &self, ctx: &Context<'a>, - #[graphql(desc = "Uuid of order to place")] id: Uuid, + #[graphql(desc = "PlaceOrderInput")] input: PlaceOrderInput, ) -> Result { let db_client = ctx.data::()?; let collection: Collection = db_client.collection::("orders"); - let mut order = query_order(&collection, id).await?; + let mut order = query_order(&collection, input.id).await?; authorize_user(&ctx, Some(order.user._id))?; - set_status_placed(&collection, id).await?; - order = query_order(&collection, id).await?; - send_order_created_event(order.clone()).await?; + let payment_authorization = build_payment_authorization(&input); + set_status_placed(&collection, input.id).await?; + order = query_order(&collection, input.id).await?; + let order_dto = OrderDTO::try_from((order.clone(), payment_authorization))?; + send_order_created_event(order_dto).await?; Ok(order) } } -/// Builds payment authorization from create order input. +/// Builds payment authorization from place order input. /// -/// `create_order_input` - The create order input to build the payment authorization from. -fn build_payment_authorization(input: &CreateOrderInput) -> Option { +/// `input` - The place order input to build the payment authorization from. +fn build_payment_authorization(input: &PlaceOrderInput) -> Option { input .payment_authorization .clone() @@ -950,9 +953,8 @@ fn build_calculate_shipment_fees_input( } /// Sends an `order/order/created` created event containing the order context. -async fn send_order_created_event(order: Order) -> Result<()> { +async fn send_order_created_event(order_dto: OrderDTO) -> Result<()> { let client = reqwest::Client::new(); - let order_dto = OrderDTO::try_from(order)?; client .post("http://localhost:3500/v1.0/publish/pubsub/order/order/created") .json(&order_dto) diff --git a/src/mutation_input_structs.rs b/src/mutation_input_structs.rs index b1f5ec0..b618634 100644 --- a/src/mutation_input_structs.rs +++ b/src/mutation_input_structs.rs @@ -5,7 +5,7 @@ use std::{ collections::{BTreeSet, HashSet}, }; -#[derive(InputObject)] +#[derive(Debug, InputObject)] pub struct CreateOrderInput { /// UUID of user owning the order. pub user_id: Uuid, @@ -19,11 +19,9 @@ pub struct CreateOrderInput { pub payment_information_id: Uuid, /// VAT number. pub vat_number: String, - /// Optional payment authorization data. - pub payment_authorization: Option, } -#[derive(InputObject, PartialEq, Eq, Clone)] +#[derive(Debug, InputObject, PartialEq, Eq, Clone)] pub struct OrderItemInput { /// UUID of shopping cart item associated with order item. pub shopping_cart_item_id: Uuid, @@ -51,3 +49,11 @@ impl Ord for OrderItemInput { self.shopping_cart_item_id.cmp(&other.shopping_cart_item_id) } } + +#[derive(Debug, InputObject)] +pub struct PlaceOrderInput { + /// UUID of order to place. + pub id: Uuid, + /// Optional payment authorization data. + pub payment_authorization: Option, +} diff --git a/src/order.rs b/src/order.rs index eb612ab..689869b 100644 --- a/src/order.rs +++ b/src/order.rs @@ -42,10 +42,6 @@ pub struct Order { pub compensatable_order_amount: u64, /// UUID of payment information that the order should be processed with. pub payment_information_id: Uuid, - /// Optional payment authorization information. - /// This field is not queriable with GraphQL. - #[graphql(skip)] - pub payment_authorization: Option, /// VAT number. #[graphql(skip)] pub vat_number: String, @@ -175,32 +171,34 @@ pub struct OrderDTO { pub vat_number: String, } -impl TryFrom for OrderDTO { +impl TryFrom<(Order, Option)> for OrderDTO { type Error = Error; - fn try_from(value: Order) -> Result { - let order_item_dtos = value + fn try_from( + (order, payment_authorization): (Order, Option), + ) -> Result { + let order_item_dtos = order .internal_order_items .iter() .map(|o| OrderItemDTO::from(o.clone())) .collect(); let message = format!("OrderDTO cannot be created, `placed_at` of the given Order is `None`"); - let placed_at = value.placed_at.ok_or(Error::new(message))?.to_chrono(); + let placed_at = order.placed_at.ok_or(Error::new(message))?.to_chrono(); let order_dto = Self { - id: value._id, - user_id: value.user._id, - created_at: value.created_at.to_chrono(), - order_status: value.order_status, + id: order._id, + user_id: order.user._id, + created_at: order.created_at.to_chrono(), + order_status: order.order_status, placed_at, - rejection_reason: value.rejection_reason, + rejection_reason: order.rejection_reason, order_items: order_item_dtos, - shipment_address_id: value.shipment_address._id, - invoice_address_id: value.invoice_address._id, - compensatable_order_amount: value.compensatable_order_amount, - payment_information_id: value.payment_information_id, - payment_authorization: value.payment_authorization, - vat_number: value.vat_number, + shipment_address_id: order.shipment_address._id, + invoice_address_id: order.invoice_address._id, + compensatable_order_amount: order.compensatable_order_amount, + payment_information_id: order.payment_information_id, + payment_authorization: payment_authorization, + vat_number: order.vat_number, }; Ok(order_dto) }