Просмотр исходного кода

feat(core): Implement APIs for draft order creation

Relates to #1453
Michael Bromley 3 лет назад
Родитель
Сommit
cb8ae032d4
22 измененных файлов с 992 добавлено и 88 удалено
  1. 126 1
      packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts
  2. 2 2
      packages/common/src/generated-shop-types.ts
  3. 129 1
      packages/common/src/generated-types.ts
  4. 126 1
      packages/core/e2e/graphql/generated-e2e-admin-types.ts
  5. 2 2
      packages/core/e2e/graphql/generated-e2e-shop-types.ts
  6. 7 20
      packages/core/src/api/api-internal-modules.ts
  7. 183 0
      packages/core/src/api/resolvers/admin/draft-order.resolver.ts
  8. 28 0
      packages/core/src/api/schema/admin-api/order.api.graphql
  9. 21 0
      packages/core/src/api/schema/common/common-error-results.graphql
  10. 15 0
      packages/core/src/api/schema/common/common-types.graphql
  11. 0 21
      packages/core/src/api/schema/shop-api/shop-error-results.graphql
  12. 0 13
      packages/core/src/api/schema/shop-api/shop.api.graphql
  13. 56 1
      packages/core/src/common/error/generated-graphql-admin-errors.ts
  14. 1 1
      packages/core/src/service/helpers/order-modifier/order-modifier.ts
  15. 7 3
      packages/core/src/service/helpers/order-state-machine/order-state.ts
  16. 33 15
      packages/core/src/service/services/order.service.ts
  17. 126 1
      packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts
  18. 126 1
      packages/payments-plugin/e2e/graphql/generated-admin-types.ts
  19. 2 2
      packages/payments-plugin/e2e/graphql/generated-shop-types.ts
  20. 2 3
      packages/payments-plugin/src/mollie/graphql/generated-shop-types.ts
  21. 0 0
      schema-admin.json
  22. 0 0
      schema-shop.json

+ 126 - 1
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -149,6 +149,8 @@ export type AlreadyRefundedError = ErrorResult & {
   refundId: Scalars['ID'];
 };
 
+export type ApplyCouponCodeResult = Order | CouponCodeExpiredError | CouponCodeInvalidError | CouponCodeLimitError;
+
 export type Asset = Node & {
   tags: Array<Tag>;
   id: Scalars['ID'];
@@ -1417,7 +1419,10 @@ export enum ErrorCode {
   INSUFFICIENT_STOCK_ERROR = 'INSUFFICIENT_STOCK_ERROR',
   COUPON_CODE_INVALID_ERROR = 'COUPON_CODE_INVALID_ERROR',
   COUPON_CODE_EXPIRED_ERROR = 'COUPON_CODE_EXPIRED_ERROR',
-  COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR'
+  COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR',
+  ORDER_MODIFICATION_ERROR = 'ORDER_MODIFICATION_ERROR',
+  INELIGIBLE_SHIPPING_METHOD_ERROR = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
+  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR'
 }
 
 export type ErrorResult = {
@@ -1698,6 +1703,12 @@ export type ImportInfo = {
   imported: Scalars['Int'];
 };
 
+/** Returned when attempting to set a ShippingMethod for which the Order is not eligible */
+export type IneligibleShippingMethodError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when attempting to add more items to the Order than are available */
 export type InsufficientStockError = ErrorResult & {
   errorCode: ErrorCode;
@@ -2387,6 +2398,27 @@ export type Mutation = {
    * Payment.
    */
   addManualPaymentToOrder: AddManualPaymentToOrderResult;
+  /** Creates a draft Order */
+  createDraftOrder: Order;
+  /** Adds an item to the draft Order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available. */
+  addItemToDraftOrder: UpdateOrderItemsResult;
+  /** Adjusts a draft OrderLine. If custom fields are defined on the OrderLine entity, a third argument 'customFields' of type `OrderLineCustomFieldsInput` will be available. */
+  adjustDraftOrderLine: UpdateOrderItemsResult;
+  /** Remove an OrderLine from the draft Order */
+  removeDraftOrderLine: RemoveOrderItemsResult;
+  setCustomerForDraftOrder: SetCustomerForDraftOrderResult;
+  /** Sets the shipping address for a draft Order */
+  setDraftOrderShippingAddress: Order;
+  /** Sets the billing address for a draft Order */
+  setDraftOrderBillingAddress: Order;
+  /** Allows any custom fields to be set for the active order */
+  setDraftOrderCustomFields: Order;
+  /** Applies the given coupon code to the draft Order */
+  applyCouponCodeToDraftOrder: ApplyCouponCodeResult;
+  /** Removes the given coupon code from the draft Order */
+  removeCouponCodeFromDraftOrder?: Maybe<Order>;
+  /** Sets the shipping method by id, which can be obtained with the `eligibleShippingMethodsForDraftOrder` query */
+  setDraftOrderShippingMethod: SetOrderShippingMethodResult;
   /** Create existing PaymentMethod */
   createPaymentMethod: PaymentMethod;
   /** Update an existing PaymentMethod */
@@ -2840,6 +2872,69 @@ export type MutationAddManualPaymentToOrderArgs = {
 };
 
 
+export type MutationAddItemToDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  productVariantId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationAdjustDraftOrderLineArgs = {
+  orderId: Scalars['ID'];
+  orderLineId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationRemoveDraftOrderLineArgs = {
+  orderId: Scalars['ID'];
+  orderLineId: Scalars['ID'];
+};
+
+
+export type MutationSetCustomerForDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  customerId?: Maybe<Scalars['ID']>;
+  input?: Maybe<CreateCustomerInput>;
+};
+
+
+export type MutationSetDraftOrderShippingAddressArgs = {
+  orderId: Scalars['ID'];
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetDraftOrderBillingAddressArgs = {
+  orderId: Scalars['ID'];
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetDraftOrderCustomFieldsArgs = {
+  orderId: Scalars['ID'];
+  input: UpdateOrderInput;
+};
+
+
+export type MutationApplyCouponCodeToDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationRemoveCouponCodeFromDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationSetDraftOrderShippingMethodArgs = {
+  orderId: Scalars['ID'];
+  shippingMethodId: Scalars['ID'];
+};
+
+
 export type MutationCreatePaymentMethodArgs = {
   input: CreatePaymentMethodInput;
 };
@@ -3103,6 +3198,15 @@ export type NegativeQuantityError = ErrorResult & {
   message: Scalars['String'];
 };
 
+/**
+ * Returned when invoking a mutation which depends on there being an active Order on the
+ * current session.
+ */
+export type NoActiveOrderError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when a call to modifyOrder fails to specify any changes */
 export type NoChangesSpecifiedError = ErrorResult & {
   errorCode: ErrorCode;
@@ -3376,6 +3480,12 @@ export type OrderModification = Node & {
   isSettled: Scalars['Boolean'];
 };
 
+/** Returned when attempting to modify the contents of an Order that is not in the `AddingItems` state. */
+export type OrderModificationError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when attempting to modify the contents of an Order that is not in the `Modifying` state. */
 export type OrderModificationStateError = ErrorResult & {
   errorCode: ErrorCode;
@@ -4101,6 +4211,8 @@ export type Query = {
   jobBufferSize: Array<JobBufferSize>;
   order?: Maybe<Order>;
   orders: OrderList;
+  /** Returns a list of eligible shipping methods for the draft Order */
+  eligibleShippingMethodsForDraftOrder: Array<ShippingMethodQuote>;
   paymentMethods: PaymentMethodList;
   paymentMethod?: Maybe<PaymentMethod>;
   paymentMethodEligibilityCheckers: Array<ConfigurableOperationDefinition>;
@@ -4253,6 +4365,11 @@ export type QueryOrdersArgs = {
 };
 
 
+export type QueryEligibleShippingMethodsForDraftOrderArgs = {
+  orderId: Scalars['ID'];
+};
+
+
 export type QueryPaymentMethodsArgs = {
   options?: Maybe<PaymentMethodListOptions>;
 };
@@ -4460,6 +4577,8 @@ export type RemoveFacetsFromChannelInput = {
 
 export type RemoveOptionGroupFromProductResult = Product | ProductOptionInUseError;
 
+export type RemoveOrderItemsResult = Order | OrderModificationError;
+
 export type RemoveProductVariantsFromChannelInput = {
   productVariantIds: Array<Scalars['ID']>;
   channelId: Scalars['ID'];
@@ -4608,6 +4727,10 @@ export type ServerConfig = {
   customFieldConfig: CustomFields;
 };
 
+export type SetCustomerForDraftOrderResult = Order | EmailAddressConflictError;
+
+export type SetOrderShippingMethodResult = Order | OrderModificationError | IneligibleShippingMethodError | NoActiveOrderError;
+
 /** Returned if the Payment settlement fails */
 export type SettlePaymentError = ErrorResult & {
   errorCode: ErrorCode;
@@ -5124,6 +5247,8 @@ export type UpdateOrderInput = {
   customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateOrderItemsResult = Order | OrderModificationError | OrderLimitError | NegativeQuantityError | InsufficientStockError;
+
 export type UpdateOrderNoteInput = {
   noteId: Scalars['ID'];
   note?: Maybe<Scalars['String']>;

+ 2 - 2
packages/common/src/generated-shop-types.ts

@@ -875,6 +875,7 @@ export enum ErrorCode {
   COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR',
   ORDER_MODIFICATION_ERROR = 'ORDER_MODIFICATION_ERROR',
   INELIGIBLE_SHIPPING_METHOD_ERROR = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
+  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR',
   ORDER_PAYMENT_STATE_ERROR = 'ORDER_PAYMENT_STATE_ERROR',
   INELIGIBLE_PAYMENT_METHOD_ERROR = 'INELIGIBLE_PAYMENT_METHOD_ERROR',
   PAYMENT_FAILED_ERROR = 'PAYMENT_FAILED_ERROR',
@@ -889,8 +890,7 @@ export enum ErrorCode {
   IDENTIFIER_CHANGE_TOKEN_EXPIRED_ERROR = 'IDENTIFIER_CHANGE_TOKEN_EXPIRED_ERROR',
   PASSWORD_RESET_TOKEN_INVALID_ERROR = 'PASSWORD_RESET_TOKEN_INVALID_ERROR',
   PASSWORD_RESET_TOKEN_EXPIRED_ERROR = 'PASSWORD_RESET_TOKEN_EXPIRED_ERROR',
-  NOT_VERIFIED_ERROR = 'NOT_VERIFIED_ERROR',
-  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR'
+  NOT_VERIFIED_ERROR = 'NOT_VERIFIED_ERROR'
 }
 
 export type ErrorResult = {

+ 129 - 1
packages/common/src/generated-types.ts

@@ -155,6 +155,8 @@ export type AlreadyRefundedError = ErrorResult & {
   refundId: Scalars['ID'];
 };
 
+export type ApplyCouponCodeResult = Order | CouponCodeExpiredError | CouponCodeInvalidError | CouponCodeLimitError;
+
 export type Asset = Node & {
   __typename?: 'Asset';
   tags: Array<Tag>;
@@ -1461,7 +1463,10 @@ export enum ErrorCode {
   INSUFFICIENT_STOCK_ERROR = 'INSUFFICIENT_STOCK_ERROR',
   COUPON_CODE_INVALID_ERROR = 'COUPON_CODE_INVALID_ERROR',
   COUPON_CODE_EXPIRED_ERROR = 'COUPON_CODE_EXPIRED_ERROR',
-  COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR'
+  COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR',
+  ORDER_MODIFICATION_ERROR = 'ORDER_MODIFICATION_ERROR',
+  INELIGIBLE_SHIPPING_METHOD_ERROR = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
+  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR'
 }
 
 export type ErrorResult = {
@@ -1757,6 +1762,13 @@ export type ImportInfo = {
   imported: Scalars['Int'];
 };
 
+/** Returned when attempting to set a ShippingMethod for which the Order is not eligible */
+export type IneligibleShippingMethodError = ErrorResult & {
+  __typename?: 'IneligibleShippingMethodError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when attempting to add more items to the Order than are available */
 export type InsufficientStockError = ErrorResult & {
   __typename?: 'InsufficientStockError';
@@ -2464,6 +2476,27 @@ export type Mutation = {
    * Payment.
    */
   addManualPaymentToOrder: AddManualPaymentToOrderResult;
+  /** Creates a draft Order */
+  createDraftOrder: Order;
+  /** Adds an item to the draft Order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available. */
+  addItemToDraftOrder: UpdateOrderItemsResult;
+  /** Adjusts a draft OrderLine. If custom fields are defined on the OrderLine entity, a third argument 'customFields' of type `OrderLineCustomFieldsInput` will be available. */
+  adjustDraftOrderLine: UpdateOrderItemsResult;
+  /** Remove an OrderLine from the draft Order */
+  removeDraftOrderLine: RemoveOrderItemsResult;
+  setCustomerForDraftOrder: SetCustomerForDraftOrderResult;
+  /** Sets the shipping address for a draft Order */
+  setDraftOrderShippingAddress: Order;
+  /** Sets the billing address for a draft Order */
+  setDraftOrderBillingAddress: Order;
+  /** Allows any custom fields to be set for the active order */
+  setDraftOrderCustomFields: Order;
+  /** Applies the given coupon code to the draft Order */
+  applyCouponCodeToDraftOrder: ApplyCouponCodeResult;
+  /** Removes the given coupon code from the draft Order */
+  removeCouponCodeFromDraftOrder?: Maybe<Order>;
+  /** Sets the shipping method by id, which can be obtained with the `eligibleShippingMethodsForDraftOrder` query */
+  setDraftOrderShippingMethod: SetOrderShippingMethodResult;
   /** Create existing PaymentMethod */
   createPaymentMethod: PaymentMethod;
   /** Update an existing PaymentMethod */
@@ -2917,6 +2950,69 @@ export type MutationAddManualPaymentToOrderArgs = {
 };
 
 
+export type MutationAddItemToDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  productVariantId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationAdjustDraftOrderLineArgs = {
+  orderId: Scalars['ID'];
+  orderLineId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationRemoveDraftOrderLineArgs = {
+  orderId: Scalars['ID'];
+  orderLineId: Scalars['ID'];
+};
+
+
+export type MutationSetCustomerForDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  customerId?: Maybe<Scalars['ID']>;
+  input?: Maybe<CreateCustomerInput>;
+};
+
+
+export type MutationSetDraftOrderShippingAddressArgs = {
+  orderId: Scalars['ID'];
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetDraftOrderBillingAddressArgs = {
+  orderId: Scalars['ID'];
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetDraftOrderCustomFieldsArgs = {
+  orderId: Scalars['ID'];
+  input: UpdateOrderInput;
+};
+
+
+export type MutationApplyCouponCodeToDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationRemoveCouponCodeFromDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationSetDraftOrderShippingMethodArgs = {
+  orderId: Scalars['ID'];
+  shippingMethodId: Scalars['ID'];
+};
+
+
 export type MutationCreatePaymentMethodArgs = {
   input: CreatePaymentMethodInput;
 };
@@ -3182,6 +3278,16 @@ export type NegativeQuantityError = ErrorResult & {
   message: Scalars['String'];
 };
 
+/**
+ * Returned when invoking a mutation which depends on there being an active Order on the
+ * current session.
+ */
+export type NoActiveOrderError = ErrorResult & {
+  __typename?: 'NoActiveOrderError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when a call to modifyOrder fails to specify any changes */
 export type NoChangesSpecifiedError = ErrorResult & {
   __typename?: 'NoChangesSpecifiedError';
@@ -3464,6 +3570,13 @@ export type OrderModification = Node & {
   isSettled: Scalars['Boolean'];
 };
 
+/** Returned when attempting to modify the contents of an Order that is not in the `AddingItems` state. */
+export type OrderModificationError = ErrorResult & {
+  __typename?: 'OrderModificationError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when attempting to modify the contents of an Order that is not in the `Modifying` state. */
 export type OrderModificationStateError = ErrorResult & {
   __typename?: 'OrderModificationStateError';
@@ -4217,6 +4330,8 @@ export type Query = {
   jobBufferSize: Array<JobBufferSize>;
   order?: Maybe<Order>;
   orders: OrderList;
+  /** Returns a list of eligible shipping methods for the draft Order */
+  eligibleShippingMethodsForDraftOrder: Array<ShippingMethodQuote>;
   paymentMethods: PaymentMethodList;
   paymentMethod?: Maybe<PaymentMethod>;
   paymentMethodEligibilityCheckers: Array<ConfigurableOperationDefinition>;
@@ -4369,6 +4484,11 @@ export type QueryOrdersArgs = {
 };
 
 
+export type QueryEligibleShippingMethodsForDraftOrderArgs = {
+  orderId: Scalars['ID'];
+};
+
+
 export type QueryPaymentMethodsArgs = {
   options?: Maybe<PaymentMethodListOptions>;
 };
@@ -4582,6 +4702,8 @@ export type RemoveFacetsFromChannelInput = {
 
 export type RemoveOptionGroupFromProductResult = Product | ProductOptionInUseError;
 
+export type RemoveOrderItemsResult = Order | OrderModificationError;
+
 export type RemoveProductVariantsFromChannelInput = {
   productVariantIds: Array<Scalars['ID']>;
   channelId: Scalars['ID'];
@@ -4739,6 +4861,10 @@ export type ServerConfig = {
   customFieldConfig: CustomFields;
 };
 
+export type SetCustomerForDraftOrderResult = Order | EmailAddressConflictError;
+
+export type SetOrderShippingMethodResult = Order | OrderModificationError | IneligibleShippingMethodError | NoActiveOrderError;
+
 /** Returned if the Payment settlement fails */
 export type SettlePaymentError = ErrorResult & {
   __typename?: 'SettlePaymentError';
@@ -5277,6 +5403,8 @@ export type UpdateOrderInput = {
   customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateOrderItemsResult = Order | OrderModificationError | OrderLimitError | NegativeQuantityError | InsufficientStockError;
+
 export type UpdateOrderNoteInput = {
   noteId: Scalars['ID'];
   note?: Maybe<Scalars['String']>;

+ 126 - 1
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -149,6 +149,8 @@ export type AlreadyRefundedError = ErrorResult & {
   refundId: Scalars['ID'];
 };
 
+export type ApplyCouponCodeResult = Order | CouponCodeExpiredError | CouponCodeInvalidError | CouponCodeLimitError;
+
 export type Asset = Node & {
   tags: Array<Tag>;
   id: Scalars['ID'];
@@ -1417,7 +1419,10 @@ export enum ErrorCode {
   INSUFFICIENT_STOCK_ERROR = 'INSUFFICIENT_STOCK_ERROR',
   COUPON_CODE_INVALID_ERROR = 'COUPON_CODE_INVALID_ERROR',
   COUPON_CODE_EXPIRED_ERROR = 'COUPON_CODE_EXPIRED_ERROR',
-  COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR'
+  COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR',
+  ORDER_MODIFICATION_ERROR = 'ORDER_MODIFICATION_ERROR',
+  INELIGIBLE_SHIPPING_METHOD_ERROR = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
+  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR'
 }
 
 export type ErrorResult = {
@@ -1698,6 +1703,12 @@ export type ImportInfo = {
   imported: Scalars['Int'];
 };
 
+/** Returned when attempting to set a ShippingMethod for which the Order is not eligible */
+export type IneligibleShippingMethodError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when attempting to add more items to the Order than are available */
 export type InsufficientStockError = ErrorResult & {
   errorCode: ErrorCode;
@@ -2387,6 +2398,27 @@ export type Mutation = {
    * Payment.
    */
   addManualPaymentToOrder: AddManualPaymentToOrderResult;
+  /** Creates a draft Order */
+  createDraftOrder: Order;
+  /** Adds an item to the draft Order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available. */
+  addItemToDraftOrder: UpdateOrderItemsResult;
+  /** Adjusts a draft OrderLine. If custom fields are defined on the OrderLine entity, a third argument 'customFields' of type `OrderLineCustomFieldsInput` will be available. */
+  adjustDraftOrderLine: UpdateOrderItemsResult;
+  /** Remove an OrderLine from the draft Order */
+  removeDraftOrderLine: RemoveOrderItemsResult;
+  setCustomerForDraftOrder: SetCustomerForDraftOrderResult;
+  /** Sets the shipping address for a draft Order */
+  setDraftOrderShippingAddress: Order;
+  /** Sets the billing address for a draft Order */
+  setDraftOrderBillingAddress: Order;
+  /** Allows any custom fields to be set for the active order */
+  setDraftOrderCustomFields: Order;
+  /** Applies the given coupon code to the draft Order */
+  applyCouponCodeToDraftOrder: ApplyCouponCodeResult;
+  /** Removes the given coupon code from the draft Order */
+  removeCouponCodeFromDraftOrder?: Maybe<Order>;
+  /** Sets the shipping method by id, which can be obtained with the `eligibleShippingMethodsForDraftOrder` query */
+  setDraftOrderShippingMethod: SetOrderShippingMethodResult;
   /** Create existing PaymentMethod */
   createPaymentMethod: PaymentMethod;
   /** Update an existing PaymentMethod */
@@ -2840,6 +2872,69 @@ export type MutationAddManualPaymentToOrderArgs = {
 };
 
 
+export type MutationAddItemToDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  productVariantId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationAdjustDraftOrderLineArgs = {
+  orderId: Scalars['ID'];
+  orderLineId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationRemoveDraftOrderLineArgs = {
+  orderId: Scalars['ID'];
+  orderLineId: Scalars['ID'];
+};
+
+
+export type MutationSetCustomerForDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  customerId?: Maybe<Scalars['ID']>;
+  input?: Maybe<CreateCustomerInput>;
+};
+
+
+export type MutationSetDraftOrderShippingAddressArgs = {
+  orderId: Scalars['ID'];
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetDraftOrderBillingAddressArgs = {
+  orderId: Scalars['ID'];
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetDraftOrderCustomFieldsArgs = {
+  orderId: Scalars['ID'];
+  input: UpdateOrderInput;
+};
+
+
+export type MutationApplyCouponCodeToDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationRemoveCouponCodeFromDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationSetDraftOrderShippingMethodArgs = {
+  orderId: Scalars['ID'];
+  shippingMethodId: Scalars['ID'];
+};
+
+
 export type MutationCreatePaymentMethodArgs = {
   input: CreatePaymentMethodInput;
 };
@@ -3103,6 +3198,15 @@ export type NegativeQuantityError = ErrorResult & {
   message: Scalars['String'];
 };
 
+/**
+ * Returned when invoking a mutation which depends on there being an active Order on the
+ * current session.
+ */
+export type NoActiveOrderError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when a call to modifyOrder fails to specify any changes */
 export type NoChangesSpecifiedError = ErrorResult & {
   errorCode: ErrorCode;
@@ -3376,6 +3480,12 @@ export type OrderModification = Node & {
   isSettled: Scalars['Boolean'];
 };
 
+/** Returned when attempting to modify the contents of an Order that is not in the `AddingItems` state. */
+export type OrderModificationError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when attempting to modify the contents of an Order that is not in the `Modifying` state. */
 export type OrderModificationStateError = ErrorResult & {
   errorCode: ErrorCode;
@@ -4101,6 +4211,8 @@ export type Query = {
   jobBufferSize: Array<JobBufferSize>;
   order?: Maybe<Order>;
   orders: OrderList;
+  /** Returns a list of eligible shipping methods for the draft Order */
+  eligibleShippingMethodsForDraftOrder: Array<ShippingMethodQuote>;
   paymentMethods: PaymentMethodList;
   paymentMethod?: Maybe<PaymentMethod>;
   paymentMethodEligibilityCheckers: Array<ConfigurableOperationDefinition>;
@@ -4253,6 +4365,11 @@ export type QueryOrdersArgs = {
 };
 
 
+export type QueryEligibleShippingMethodsForDraftOrderArgs = {
+  orderId: Scalars['ID'];
+};
+
+
 export type QueryPaymentMethodsArgs = {
   options?: Maybe<PaymentMethodListOptions>;
 };
@@ -4460,6 +4577,8 @@ export type RemoveFacetsFromChannelInput = {
 
 export type RemoveOptionGroupFromProductResult = Product | ProductOptionInUseError;
 
+export type RemoveOrderItemsResult = Order | OrderModificationError;
+
 export type RemoveProductVariantsFromChannelInput = {
   productVariantIds: Array<Scalars['ID']>;
   channelId: Scalars['ID'];
@@ -4608,6 +4727,10 @@ export type ServerConfig = {
   customFieldConfig: CustomFields;
 };
 
+export type SetCustomerForDraftOrderResult = Order | EmailAddressConflictError;
+
+export type SetOrderShippingMethodResult = Order | OrderModificationError | IneligibleShippingMethodError | NoActiveOrderError;
+
 /** Returned if the Payment settlement fails */
 export type SettlePaymentError = ErrorResult & {
   errorCode: ErrorCode;
@@ -5124,6 +5247,8 @@ export type UpdateOrderInput = {
   customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateOrderItemsResult = Order | OrderModificationError | OrderLimitError | NegativeQuantityError | InsufficientStockError;
+
 export type UpdateOrderNoteInput = {
   noteId: Scalars['ID'];
   note?: Maybe<Scalars['String']>;

+ 2 - 2
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -842,6 +842,7 @@ export enum ErrorCode {
   COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR',
   ORDER_MODIFICATION_ERROR = 'ORDER_MODIFICATION_ERROR',
   INELIGIBLE_SHIPPING_METHOD_ERROR = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
+  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR',
   ORDER_PAYMENT_STATE_ERROR = 'ORDER_PAYMENT_STATE_ERROR',
   INELIGIBLE_PAYMENT_METHOD_ERROR = 'INELIGIBLE_PAYMENT_METHOD_ERROR',
   PAYMENT_FAILED_ERROR = 'PAYMENT_FAILED_ERROR',
@@ -856,8 +857,7 @@ export enum ErrorCode {
   IDENTIFIER_CHANGE_TOKEN_EXPIRED_ERROR = 'IDENTIFIER_CHANGE_TOKEN_EXPIRED_ERROR',
   PASSWORD_RESET_TOKEN_INVALID_ERROR = 'PASSWORD_RESET_TOKEN_INVALID_ERROR',
   PASSWORD_RESET_TOKEN_EXPIRED_ERROR = 'PASSWORD_RESET_TOKEN_EXPIRED_ERROR',
-  NOT_VERIFIED_ERROR = 'NOT_VERIFIED_ERROR',
-  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR'
+  NOT_VERIFIED_ERROR = 'NOT_VERIFIED_ERROR'
 }
 
 export type ErrorResult = {

+ 7 - 20
packages/core/src/api/api-internal-modules.ts

@@ -19,6 +19,7 @@ import { CollectionResolver } from './resolvers/admin/collection.resolver';
 import { CountryResolver } from './resolvers/admin/country.resolver';
 import { CustomerGroupResolver } from './resolvers/admin/customer-group.resolver';
 import { CustomerResolver } from './resolvers/admin/customer.resolver';
+import { DraftOrderResolver } from './resolvers/admin/draft-order.resolver';
 import { FacetResolver } from './resolvers/admin/facet.resolver';
 import { GlobalSettingsResolver } from './resolvers/admin/global-settings.resolver';
 import { ImportResolver } from './resolvers/admin/import.resolver';
@@ -39,36 +40,21 @@ import { AdministratorEntityResolver } from './resolvers/entity/administrator-en
 import { AssetEntityResolver } from './resolvers/entity/asset-entity.resolver';
 import { CollectionEntityResolver } from './resolvers/entity/collection-entity.resolver';
 import { CountryEntityResolver } from './resolvers/entity/country-entity.resolver';
-import {
-    CustomerAdminEntityResolver,
-    CustomerEntityResolver,
-} from './resolvers/entity/customer-entity.resolver';
+import { CustomerAdminEntityResolver, CustomerEntityResolver, } from './resolvers/entity/customer-entity.resolver';
 import { CustomerGroupEntityResolver } from './resolvers/entity/customer-group-entity.resolver';
 import { FacetEntityResolver } from './resolvers/entity/facet-entity.resolver';
 import { FacetValueEntityResolver } from './resolvers/entity/facet-value-entity.resolver';
-import {
-    FulfillmentAdminEntityResolver,
-    FulfillmentEntityResolver,
-} from './resolvers/entity/fulfillment-entity.resolver';
+import { FulfillmentAdminEntityResolver, FulfillmentEntityResolver, } from './resolvers/entity/fulfillment-entity.resolver';
 import { JobEntityResolver } from './resolvers/entity/job-entity.resolver';
 import { OrderAdminEntityResolver, OrderEntityResolver } from './resolvers/entity/order-entity.resolver';
 import { OrderItemEntityResolver } from './resolvers/entity/order-item-entity.resolver';
 import { OrderLineEntityResolver } from './resolvers/entity/order-line-entity.resolver';
-import {
-    PaymentAdminEntityResolver,
-    PaymentEntityResolver,
-} from './resolvers/entity/payment-entity.resolver';
+import { PaymentAdminEntityResolver, PaymentEntityResolver, } from './resolvers/entity/payment-entity.resolver';
 import { PaymentMethodEntityResolver } from './resolvers/entity/payment-method-entity.resolver';
-import {
-    ProductAdminEntityResolver,
-    ProductEntityResolver,
-} from './resolvers/entity/product-entity.resolver';
+import { ProductAdminEntityResolver, ProductEntityResolver, } from './resolvers/entity/product-entity.resolver';
 import { ProductOptionEntityResolver } from './resolvers/entity/product-option-entity.resolver';
 import { ProductOptionGroupEntityResolver } from './resolvers/entity/product-option-group-entity.resolver';
-import {
-    ProductVariantAdminEntityResolver,
-    ProductVariantEntityResolver,
-} from './resolvers/entity/product-variant-entity.resolver';
+import { ProductVariantAdminEntityResolver, ProductVariantEntityResolver, } from './resolvers/entity/product-variant-entity.resolver';
 import { RefundEntityResolver } from './resolvers/entity/refund-entity.resolver';
 import { RoleEntityResolver } from './resolvers/entity/role-entity.resolver';
 import { ShippingLineEntityResolver } from './resolvers/entity/shipping-line-entity.resolver';
@@ -90,6 +76,7 @@ const adminResolvers = [
     CountryResolver,
     CustomerGroupResolver,
     CustomerResolver,
+    DraftOrderResolver,
     FacetResolver,
     GlobalSettingsResolver,
     ImportResolver,

+ 183 - 0
packages/core/src/api/resolvers/admin/draft-order.resolver.ts

@@ -0,0 +1,183 @@
+import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
+import {
+    ApplyCouponCodeResult,
+    ActiveOrderResult,
+    RemoveOrderItemsResult,
+    UpdateOrderItemsResult,
+    SetOrderShippingMethodResult,
+} from '@vendure/common/lib/generated-shop-types';
+import {
+    MutationAddItemToDraftOrderArgs,
+    MutationAdjustDraftOrderLineArgs,
+    MutationApplyCouponCodeToDraftOrderArgs,
+    MutationRemoveCouponCodeFromDraftOrderArgs,
+    MutationRemoveDraftOrderLineArgs,
+    MutationSetCustomerForDraftOrderArgs,
+    MutationSetDraftOrderBillingAddressArgs,
+    MutationSetDraftOrderShippingAddressArgs,
+    MutationSetDraftOrderShippingMethodArgs,
+    Permission,
+    QueryEligibleShippingMethodsForDraftOrderArgs,
+    SetCustomerForDraftOrderResult,
+    ShippingMethodQuote,
+} from '@vendure/common/lib/generated-types';
+
+import { ErrorResultUnion, isGraphQlErrorResult, UserInputError } from '../../../common/index';
+import { TransactionalConnection } from '../../../connection/index';
+import { Customer, Order } from '../../../entity/index';
+import { CustomerService, OrderService } from '../../../service/index';
+import { RequestContext } from '../../common/request-context';
+import { Allow } from '../../decorators/allow.decorator';
+import { Ctx } from '../../decorators/request-context.decorator';
+import { Transaction } from '../../decorators/transaction.decorator';
+
+@Resolver()
+export class DraftOrderResolver {
+    constructor(
+        private orderService: OrderService,
+        private customerService: CustomerService,
+        private connection: TransactionalConnection,
+    ) {}
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.CreateOrder)
+    async createDraftOrder(@Ctx() ctx: RequestContext): Promise<Order> {
+        return this.orderService.createDraft(ctx);
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.CreateOrder)
+    async addItemToDraftOrder(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationAddItemToDraftOrderArgs,
+    ): Promise<ErrorResultUnion<UpdateOrderItemsResult, Order>> {
+        return this.orderService.addItemToOrder(
+            ctx,
+            args.orderId,
+            args.productVariantId,
+            args.quantity,
+            (args as any).customFields,
+        );
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.UpdateOrder, Permission.Owner)
+    async adjustDraftOrderLine(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationAdjustDraftOrderLineArgs,
+    ): Promise<ErrorResultUnion<UpdateOrderItemsResult, Order>> {
+        if (args.quantity === 0) {
+            return this.removeDraftOrderLine(ctx, { orderId: args.orderId, orderLineId: args.orderLineId });
+        }
+        return this.orderService.adjustOrderLine(
+            ctx,
+            args.orderId,
+            args.orderLineId,
+            args.quantity,
+            (args as any).customFields,
+        );
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.CreateOrder)
+    async removeDraftOrderLine(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationRemoveDraftOrderLineArgs,
+    ): Promise<ErrorResultUnion<RemoveOrderItemsResult, Order>> {
+        return this.orderService.removeItemFromOrder(ctx, args.orderId, args.orderLineId);
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.CreateOrder)
+    async setCustomerForDraftOrder(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationSetCustomerForDraftOrderArgs,
+    ): Promise<ErrorResultUnion<SetCustomerForDraftOrderResult, Order>> {
+        let customer: Customer;
+        if (args.customerId) {
+            const result = await this.customerService.findOne(ctx, args.customerId);
+            if (!result) {
+                throw new UserInputError(
+                    `No customer with the id "${args.customerId}" was found in this Channel`,
+                );
+            }
+            customer = result;
+        } else if (args.input) {
+            const result = await this.customerService.createOrUpdate(ctx, args.input, true);
+            if (isGraphQlErrorResult(result)) {
+                return result;
+            }
+            customer = result;
+        } else {
+            throw new UserInputError(
+                `Either "customerId" or "input" must be supplied to setCustomerForDraftOrder`,
+            );
+        }
+
+        return this.orderService.addCustomerToOrder(ctx, args.orderId, customer);
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.CreateOrder)
+    async setDraftOrderShippingAddress(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationSetDraftOrderShippingAddressArgs,
+    ): Promise<Order> {
+        return this.orderService.setShippingAddress(ctx, args.orderId, args.input);
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.CreateOrder)
+    async setDraftOrderBillingAddress(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationSetDraftOrderBillingAddressArgs,
+    ): Promise<ErrorResultUnion<ActiveOrderResult, Order>> {
+        return this.orderService.setBillingAddress(ctx, args.orderId, args.input);
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.CreateOrder)
+    async applyCouponCodeToDraftOrder(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationApplyCouponCodeToDraftOrderArgs,
+    ): Promise<ErrorResultUnion<ApplyCouponCodeResult, Order>> {
+        return this.orderService.applyCouponCode(ctx, args.orderId, args.couponCode);
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.CreateOrder)
+    async removeCouponCodeFromDraftOrder(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationRemoveCouponCodeFromDraftOrderArgs,
+    ): Promise<Order> {
+        return this.orderService.removeCouponCode(ctx, args.orderId, args.couponCode);
+    }
+
+    @Query()
+    @Allow(Permission.CreateOrder)
+    async eligibleShippingMethodsForDraftOrder(
+        @Ctx() ctx: RequestContext,
+        @Args() args: QueryEligibleShippingMethodsForDraftOrderArgs,
+    ): Promise<ShippingMethodQuote[]> {
+        return this.orderService.getEligibleShippingMethods(ctx, args.orderId);
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.CreateOrder)
+    async setDraftOrderShippingMethod(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationSetDraftOrderShippingMethodArgs,
+    ): Promise<ErrorResultUnion<SetOrderShippingMethodResult, Order>> {
+        return this.orderService.setShippingMethod(ctx, args.orderId, args.shippingMethodId);
+    }
+}

+ 28 - 0
packages/core/src/api/schema/admin-api/order.api.graphql

@@ -1,6 +1,8 @@
 type Query {
     order(id: ID!): Order
     orders(options: OrderListOptions): OrderList!
+    "Returns a list of eligible shipping methods for the draft Order"
+    eligibleShippingMethodsForDraftOrder(orderId: ID!): [ShippingMethodQuote!]!
 }
 
 type Mutation {
@@ -32,6 +34,31 @@ type Mutation {
     Payment.
     """
     addManualPaymentToOrder(input: ManualPaymentInput!): AddManualPaymentToOrderResult!
+    "Creates a draft Order"
+    createDraftOrder: Order!
+    "Adds an item to the draft Order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available."
+    addItemToDraftOrder(orderId: ID!, productVariantId: ID!, quantity: Int!): UpdateOrderItemsResult!
+    "Adjusts a draft OrderLine. If custom fields are defined on the OrderLine entity, a third argument 'customFields' of type `OrderLineCustomFieldsInput` will be available."
+    adjustDraftOrderLine(orderId: ID!, orderLineId: ID!, quantity: Int!): UpdateOrderItemsResult!
+    "Remove an OrderLine from the draft Order"
+    removeDraftOrderLine(orderId: ID!, orderLineId: ID!): RemoveOrderItemsResult!
+    setCustomerForDraftOrder(
+        orderId: ID!
+        customerId: ID
+        input: CreateCustomerInput
+    ): SetCustomerForDraftOrderResult!
+    "Sets the shipping address for a draft Order"
+    setDraftOrderShippingAddress(orderId: ID!, input: CreateAddressInput!): Order!
+    "Sets the billing address for a draft Order"
+    setDraftOrderBillingAddress(orderId: ID!, input: CreateAddressInput!): Order!
+    "Allows any custom fields to be set for the active order"
+    setDraftOrderCustomFields(orderId: ID!, input: UpdateOrderInput!): Order!
+    "Applies the given coupon code to the draft Order"
+    applyCouponCodeToDraftOrder(orderId: ID!, couponCode: String!): ApplyCouponCodeResult!
+    "Removes the given coupon code from the draft Order"
+    removeCouponCodeFromDraftOrder(orderId: ID!, couponCode: String!): Order
+    "Sets the shipping method by id, which can be obtained with the `eligibleShippingMethodsForDraftOrder` query"
+    setDraftOrderShippingMethod(orderId: ID!, shippingMethodId: ID!): SetOrderShippingMethodResult!
 }
 
 type Order {
@@ -383,3 +410,4 @@ union ModifyOrderResult =
     | CouponCodeInvalidError
     | CouponCodeLimitError
 union AddManualPaymentToOrderResult = Order | ManualPaymentStateError
+union SetCustomerForDraftOrderResult = Order | EmailAddressConflictError

+ 21 - 0
packages/core/src/api/schema/common/common-error-results.graphql

@@ -68,3 +68,24 @@ type CouponCodeLimitError implements ErrorResult {
     couponCode: String!
     limit: Int!
 }
+
+"Returned when attempting to modify the contents of an Order that is not in the `AddingItems` state."
+type OrderModificationError implements ErrorResult {
+    errorCode: ErrorCode!
+    message: String!
+}
+
+"Returned when attempting to set a ShippingMethod for which the Order is not eligible"
+type IneligibleShippingMethodError implements ErrorResult {
+    errorCode: ErrorCode!
+    message: String!
+}
+
+"""
+Returned when invoking a mutation which depends on there being an active Order on the
+current session.
+"""
+type NoActiveOrderError implements ErrorResult {
+    errorCode: ErrorCode!
+    message: String!
+}

+ 15 - 0
packages/core/src/api/schema/common/common-types.graphql

@@ -244,3 +244,18 @@ type PaymentMethodQuote {
     isEligible: Boolean!
     eligibilityMessage: String
 }
+
+union UpdateOrderItemsResult =
+      Order
+    | OrderModificationError
+    | OrderLimitError
+    | NegativeQuantityError
+    | InsufficientStockError
+union RemoveOrderItemsResult = Order | OrderModificationError
+union SetOrderShippingMethodResult =
+      Order
+    | OrderModificationError
+    | IneligibleShippingMethodError
+    | NoActiveOrderError
+union ApplyCouponCodeResult = Order | CouponCodeExpiredError | CouponCodeInvalidError | CouponCodeLimitError
+

+ 0 - 21
packages/core/src/api/schema/shop-api/shop-error-results.graphql

@@ -1,15 +1,3 @@
-"Returned when attempting to modify the contents of an Order that is not in the `AddingItems` state."
-type OrderModificationError implements ErrorResult {
-    errorCode: ErrorCode!
-    message: String!
-}
-
-"Returned when attempting to set a ShippingMethod for which the Order is not eligible"
-type IneligibleShippingMethodError implements ErrorResult {
-    errorCode: ErrorCode!
-    message: String!
-}
-
 "Returned when attempting to add a Payment to an Order that is not in the `ArrangingPayment` state."
 type OrderPaymentStateError implements ErrorResult {
     errorCode: ErrorCode!
@@ -124,12 +112,3 @@ type NotVerifiedError implements ErrorResult {
     errorCode: ErrorCode!
     message: String!
 }
-
-"""
-Returned when invoking a mutation which depends on there being an active Order on the
-current session.
-"""
-type NoActiveOrderError implements ErrorResult {
-    errorCode: ErrorCode!
-    message: String!
-}

+ 0 - 13
packages/core/src/api/schema/shop-api/shop.api.graphql

@@ -190,19 +190,6 @@ input ProductListOptions
 # generated by generateListOptions function
 input ProductVariantListOptions
 
-union UpdateOrderItemsResult =
-      Order
-    | OrderModificationError
-    | OrderLimitError
-    | NegativeQuantityError
-    | InsufficientStockError
-union RemoveOrderItemsResult = Order | OrderModificationError
-union SetOrderShippingMethodResult =
-      Order
-    | OrderModificationError
-    | IneligibleShippingMethodError
-    | NoActiveOrderError
-union ApplyCouponCodeResult = Order | CouponCodeExpiredError | CouponCodeInvalidError | CouponCodeLimitError
 union AddPaymentToOrderResult =
       Order
     | OrderPaymentStateError

+ 56 - 1
packages/core/src/common/error/generated-graphql-admin-errors.ts

@@ -154,6 +154,16 @@ export class FulfillmentStateTransitionError extends ErrorResult {
   }
 }
 
+export class IneligibleShippingMethodError extends ErrorResult {
+  readonly __typename = 'IneligibleShippingMethodError';
+  readonly errorCode = 'INELIGIBLE_SHIPPING_METHOD_ERROR' as any;
+  readonly message = 'INELIGIBLE_SHIPPING_METHOD_ERROR';
+  constructor(
+  ) {
+    super();
+  }
+}
+
 export class InsufficientStockError extends ErrorResult {
   readonly __typename = 'InsufficientStockError';
   readonly errorCode = 'INSUFFICIENT_STOCK_ERROR' as any;
@@ -283,6 +293,16 @@ export class NegativeQuantityError extends ErrorResult {
   }
 }
 
+export class NoActiveOrderError extends ErrorResult {
+  readonly __typename = 'NoActiveOrderError';
+  readonly errorCode = 'NO_ACTIVE_ORDER_ERROR' as any;
+  readonly message = 'NO_ACTIVE_ORDER_ERROR';
+  constructor(
+  ) {
+    super();
+  }
+}
+
 export class NoChangesSpecifiedError extends ErrorResult {
   readonly __typename = 'NoChangesSpecifiedError';
   readonly errorCode = 'NO_CHANGES_SPECIFIED_ERROR' as any;
@@ -314,6 +334,16 @@ export class OrderLimitError extends ErrorResult {
   }
 }
 
+export class OrderModificationError extends ErrorResult {
+  readonly __typename = 'OrderModificationError';
+  readonly errorCode = 'ORDER_MODIFICATION_ERROR' as any;
+  readonly message = 'ORDER_MODIFICATION_ERROR';
+  constructor(
+  ) {
+    super();
+  }
+}
+
 export class OrderModificationStateError extends ErrorResult {
   readonly __typename = 'OrderModificationStateError';
   readonly errorCode = 'ORDER_MODIFICATION_STATE_ERROR' as any;
@@ -438,7 +468,7 @@ export class SettlePaymentError extends ErrorResult {
 }
 
 
-const errorTypeNames = new Set(['AlreadyRefundedError', 'CancelActiveOrderError', 'CancelPaymentError', 'ChannelDefaultLanguageError', 'CouponCodeExpiredError', 'CouponCodeInvalidError', 'CouponCodeLimitError', 'CreateFulfillmentError', 'EmailAddressConflictError', 'EmptyOrderLineSelectionError', 'FacetInUseError', 'FulfillmentStateTransitionError', 'InsufficientStockError', 'InsufficientStockOnHandError', 'InvalidCredentialsError', 'InvalidFulfillmentHandlerError', 'ItemsAlreadyFulfilledError', 'LanguageNotAvailableError', 'ManualPaymentStateError', 'MimeTypeError', 'MissingConditionsError', 'MultipleOrderError', 'NativeAuthStrategyError', 'NegativeQuantityError', 'NoChangesSpecifiedError', 'NothingToRefundError', 'OrderLimitError', 'OrderModificationStateError', 'OrderStateTransitionError', 'PaymentMethodMissingError', 'PaymentOrderMismatchError', 'PaymentStateTransitionError', 'ProductOptionInUseError', 'QuantityTooGreatError', 'RefundOrderStateError', 'RefundPaymentIdMissingError', 'RefundStateTransitionError', 'SettlePaymentError']);
+const errorTypeNames = new Set(['AlreadyRefundedError', 'CancelActiveOrderError', 'CancelPaymentError', 'ChannelDefaultLanguageError', 'CouponCodeExpiredError', 'CouponCodeInvalidError', 'CouponCodeLimitError', 'CreateFulfillmentError', 'EmailAddressConflictError', 'EmptyOrderLineSelectionError', 'FacetInUseError', 'FulfillmentStateTransitionError', 'IneligibleShippingMethodError', 'InsufficientStockError', 'InsufficientStockOnHandError', 'InvalidCredentialsError', 'InvalidFulfillmentHandlerError', 'ItemsAlreadyFulfilledError', 'LanguageNotAvailableError', 'ManualPaymentStateError', 'MimeTypeError', 'MissingConditionsError', 'MultipleOrderError', 'NativeAuthStrategyError', 'NegativeQuantityError', 'NoActiveOrderError', 'NoChangesSpecifiedError', 'NothingToRefundError', 'OrderLimitError', 'OrderModificationError', 'OrderModificationStateError', 'OrderStateTransitionError', 'PaymentMethodMissingError', 'PaymentOrderMismatchError', 'PaymentStateTransitionError', 'ProductOptionInUseError', 'QuantityTooGreatError', 'RefundOrderStateError', 'RefundPaymentIdMissingError', 'RefundStateTransitionError', 'SettlePaymentError']);
 function isGraphQLError(input: any): input is import('@vendure/common/lib/generated-types').ErrorResult {
   return input instanceof ErrorResult || errorTypeNames.has(input.__typename);
 }
@@ -544,6 +574,31 @@ export const adminErrorOperationTypeResolvers = {
       return isGraphQLError(value) ? (value as any).__typename : 'Order';
     },
   },
+  UpdateOrderItemsResult: {
+    __resolveType(value: any) {
+      return isGraphQLError(value) ? (value as any).__typename : 'Order';
+    },
+  },
+  RemoveOrderItemsResult: {
+    __resolveType(value: any) {
+      return isGraphQLError(value) ? (value as any).__typename : 'Order';
+    },
+  },
+  SetCustomerForDraftOrderResult: {
+    __resolveType(value: any) {
+      return isGraphQLError(value) ? (value as any).__typename : 'Order';
+    },
+  },
+  ApplyCouponCodeResult: {
+    __resolveType(value: any) {
+      return isGraphQLError(value) ? (value as any).__typename : 'Order';
+    },
+  },
+  SetOrderShippingMethodResult: {
+    __resolveType(value: any) {
+      return isGraphQLError(value) ? (value as any).__typename : 'Order';
+    },
+  },
   RemoveOptionGroupFromProductResult: {
     __resolveType(value: any) {
       return isGraphQLError(value) ? (value as any).__typename : 'Product';

+ 1 - 1
packages/core/src/service/helpers/order-modifier/order-modifier.ts

@@ -214,7 +214,7 @@ export class OrderModifier {
             orderLine.items = await this.connection
                 .getRepository(ctx, OrderItem)
                 .find({ where: { line: orderLine } });
-            if (!order.active) {
+            if (!order.active && order.state !== 'Draft') {
                 await this.stockMovementService.createAllocationsForOrderLines(ctx, [
                     {
                         orderLine,

+ 7 - 3
packages/core/src/service/helpers/order-state-machine/order-state.ts

@@ -5,10 +5,10 @@ import { Order } from '../../../entity/order/order.entity';
 /**
  * @description
  * An interface to extend standard {@link OrderState}.
- * 
+ *
  * @docsCategory orders
  */
- export interface CustomOrderStates {}
+export interface CustomOrderStates {}
 
 /**
  * @description
@@ -19,6 +19,7 @@ import { Order } from '../../../entity/order/order.entity';
  */
 export type OrderState =
     | 'Created'
+    | 'Draft'
     | 'AddingItems'
     | 'ArrangingPayment'
     | 'PaymentAuthorized'
@@ -34,7 +35,10 @@ export type OrderState =
 
 export const orderStateTransitions: Transitions<OrderState> = {
     Created: {
-        to: ['AddingItems'],
+        to: ['AddingItems', 'Draft'],
+    },
+    Draft: {
+        to: ['Cancelled', 'PaymentAuthorized', 'PaymentSettled'],
     },
     AddingItems: {
         to: ['ArrangingPayment', 'Cancelled'],

+ 33 - 15
packages/core/src/service/services/order.service.ts

@@ -412,19 +412,7 @@ export class OrderService {
      * User's Customer account.
      */
     async create(ctx: RequestContext, userId?: ID): Promise<Order> {
-        const newOrder = new Order({
-            code: await this.configService.orderOptions.orderCodeStrategy.generate(ctx),
-            state: this.orderStateMachine.getInitialState(),
-            lines: [],
-            surcharges: [],
-            couponCodes: [],
-            modifications: [],
-            shippingAddress: {},
-            billingAddress: {},
-            subTotal: 0,
-            subTotalWithTax: 0,
-            currencyCode: ctx.channel.currencyCode,
-        });
+        const newOrder = await this.createEmptyOrderEntity(ctx);
         if (userId) {
             const customer = await this.customerService.findOneByUserId(ctx, userId);
             if (customer) {
@@ -442,6 +430,36 @@ export class OrderService {
         return transitionResult;
     }
 
+    async createDraft(ctx: RequestContext) {
+        const newOrder = await this.createEmptyOrderEntity(ctx);
+        newOrder.active = false;
+        await this.channelService.assignToCurrentChannel(newOrder, ctx);
+        const order = await this.connection.getRepository(ctx, Order).save(newOrder);
+        this.eventBus.publish(new OrderEvent(ctx, order, 'created'));
+        const transitionResult = await this.transitionToState(ctx, order.id, 'Draft');
+        if (isGraphQlErrorResult(transitionResult)) {
+            // this should never occur, so we will throw rather than return
+            throw transitionResult;
+        }
+        return transitionResult;
+    }
+
+    private async createEmptyOrderEntity(ctx: RequestContext) {
+        return new Order({
+            code: await this.configService.orderOptions.orderCodeStrategy.generate(ctx),
+            state: this.orderStateMachine.getInitialState(),
+            lines: [],
+            surcharges: [],
+            couponCodes: [],
+            modifications: [],
+            shippingAddress: {},
+            billingAddress: {},
+            subTotal: 0,
+            subTotalWithTax: 0,
+            currencyCode: ctx.channel.currencyCode,
+        });
+    }
+
     /**
      * @description
      * Updates the custom fields of an Order.
@@ -1686,10 +1704,10 @@ export class OrderService {
     }
 
     /**
-     * Returns error if the Order is not in the "AddingItems" state.
+     * Returns error if the Order is not in the "AddingItems" or "Draft" state.
      */
     private assertAddingItemsState(order: Order) {
-        if (order.state !== 'AddingItems') {
+        if (order.state !== 'AddingItems' && order.state !== 'Draft') {
             return new OrderModificationError();
         }
     }

+ 126 - 1
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -149,6 +149,8 @@ export type AlreadyRefundedError = ErrorResult & {
   refundId: Scalars['ID'];
 };
 
+export type ApplyCouponCodeResult = Order | CouponCodeExpiredError | CouponCodeInvalidError | CouponCodeLimitError;
+
 export type Asset = Node & {
   tags: Array<Tag>;
   id: Scalars['ID'];
@@ -1417,7 +1419,10 @@ export enum ErrorCode {
   INSUFFICIENT_STOCK_ERROR = 'INSUFFICIENT_STOCK_ERROR',
   COUPON_CODE_INVALID_ERROR = 'COUPON_CODE_INVALID_ERROR',
   COUPON_CODE_EXPIRED_ERROR = 'COUPON_CODE_EXPIRED_ERROR',
-  COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR'
+  COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR',
+  ORDER_MODIFICATION_ERROR = 'ORDER_MODIFICATION_ERROR',
+  INELIGIBLE_SHIPPING_METHOD_ERROR = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
+  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR'
 }
 
 export type ErrorResult = {
@@ -1698,6 +1703,12 @@ export type ImportInfo = {
   imported: Scalars['Int'];
 };
 
+/** Returned when attempting to set a ShippingMethod for which the Order is not eligible */
+export type IneligibleShippingMethodError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when attempting to add more items to the Order than are available */
 export type InsufficientStockError = ErrorResult & {
   errorCode: ErrorCode;
@@ -2387,6 +2398,27 @@ export type Mutation = {
    * Payment.
    */
   addManualPaymentToOrder: AddManualPaymentToOrderResult;
+  /** Creates a draft Order */
+  createDraftOrder: Order;
+  /** Adds an item to the draft Order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available. */
+  addItemToDraftOrder: UpdateOrderItemsResult;
+  /** Adjusts a draft OrderLine. If custom fields are defined on the OrderLine entity, a third argument 'customFields' of type `OrderLineCustomFieldsInput` will be available. */
+  adjustDraftOrderLine: UpdateOrderItemsResult;
+  /** Remove an OrderLine from the draft Order */
+  removeDraftOrderLine: RemoveOrderItemsResult;
+  setCustomerForDraftOrder: SetCustomerForDraftOrderResult;
+  /** Sets the shipping address for a draft Order */
+  setDraftOrderShippingAddress: Order;
+  /** Sets the billing address for a draft Order */
+  setDraftOrderBillingAddress: Order;
+  /** Allows any custom fields to be set for the active order */
+  setDraftOrderCustomFields: Order;
+  /** Applies the given coupon code to the draft Order */
+  applyCouponCodeToDraftOrder: ApplyCouponCodeResult;
+  /** Removes the given coupon code from the draft Order */
+  removeCouponCodeFromDraftOrder?: Maybe<Order>;
+  /** Sets the shipping method by id, which can be obtained with the `eligibleShippingMethodsForDraftOrder` query */
+  setDraftOrderShippingMethod: SetOrderShippingMethodResult;
   /** Create existing PaymentMethod */
   createPaymentMethod: PaymentMethod;
   /** Update an existing PaymentMethod */
@@ -2840,6 +2872,69 @@ export type MutationAddManualPaymentToOrderArgs = {
 };
 
 
+export type MutationAddItemToDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  productVariantId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationAdjustDraftOrderLineArgs = {
+  orderId: Scalars['ID'];
+  orderLineId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationRemoveDraftOrderLineArgs = {
+  orderId: Scalars['ID'];
+  orderLineId: Scalars['ID'];
+};
+
+
+export type MutationSetCustomerForDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  customerId?: Maybe<Scalars['ID']>;
+  input?: Maybe<CreateCustomerInput>;
+};
+
+
+export type MutationSetDraftOrderShippingAddressArgs = {
+  orderId: Scalars['ID'];
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetDraftOrderBillingAddressArgs = {
+  orderId: Scalars['ID'];
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetDraftOrderCustomFieldsArgs = {
+  orderId: Scalars['ID'];
+  input: UpdateOrderInput;
+};
+
+
+export type MutationApplyCouponCodeToDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationRemoveCouponCodeFromDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationSetDraftOrderShippingMethodArgs = {
+  orderId: Scalars['ID'];
+  shippingMethodId: Scalars['ID'];
+};
+
+
 export type MutationCreatePaymentMethodArgs = {
   input: CreatePaymentMethodInput;
 };
@@ -3103,6 +3198,15 @@ export type NegativeQuantityError = ErrorResult & {
   message: Scalars['String'];
 };
 
+/**
+ * Returned when invoking a mutation which depends on there being an active Order on the
+ * current session.
+ */
+export type NoActiveOrderError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when a call to modifyOrder fails to specify any changes */
 export type NoChangesSpecifiedError = ErrorResult & {
   errorCode: ErrorCode;
@@ -3376,6 +3480,12 @@ export type OrderModification = Node & {
   isSettled: Scalars['Boolean'];
 };
 
+/** Returned when attempting to modify the contents of an Order that is not in the `AddingItems` state. */
+export type OrderModificationError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when attempting to modify the contents of an Order that is not in the `Modifying` state. */
 export type OrderModificationStateError = ErrorResult & {
   errorCode: ErrorCode;
@@ -4101,6 +4211,8 @@ export type Query = {
   jobBufferSize: Array<JobBufferSize>;
   order?: Maybe<Order>;
   orders: OrderList;
+  /** Returns a list of eligible shipping methods for the draft Order */
+  eligibleShippingMethodsForDraftOrder: Array<ShippingMethodQuote>;
   paymentMethods: PaymentMethodList;
   paymentMethod?: Maybe<PaymentMethod>;
   paymentMethodEligibilityCheckers: Array<ConfigurableOperationDefinition>;
@@ -4253,6 +4365,11 @@ export type QueryOrdersArgs = {
 };
 
 
+export type QueryEligibleShippingMethodsForDraftOrderArgs = {
+  orderId: Scalars['ID'];
+};
+
+
 export type QueryPaymentMethodsArgs = {
   options?: Maybe<PaymentMethodListOptions>;
 };
@@ -4460,6 +4577,8 @@ export type RemoveFacetsFromChannelInput = {
 
 export type RemoveOptionGroupFromProductResult = Product | ProductOptionInUseError;
 
+export type RemoveOrderItemsResult = Order | OrderModificationError;
+
 export type RemoveProductVariantsFromChannelInput = {
   productVariantIds: Array<Scalars['ID']>;
   channelId: Scalars['ID'];
@@ -4608,6 +4727,10 @@ export type ServerConfig = {
   customFieldConfig: CustomFields;
 };
 
+export type SetCustomerForDraftOrderResult = Order | EmailAddressConflictError;
+
+export type SetOrderShippingMethodResult = Order | OrderModificationError | IneligibleShippingMethodError | NoActiveOrderError;
+
 /** Returned if the Payment settlement fails */
 export type SettlePaymentError = ErrorResult & {
   errorCode: ErrorCode;
@@ -5124,6 +5247,8 @@ export type UpdateOrderInput = {
   customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateOrderItemsResult = Order | OrderModificationError | OrderLimitError | NegativeQuantityError | InsufficientStockError;
+
 export type UpdateOrderNoteInput = {
   noteId: Scalars['ID'];
   note?: Maybe<Scalars['String']>;

+ 126 - 1
packages/payments-plugin/e2e/graphql/generated-admin-types.ts

@@ -149,6 +149,8 @@ export type AlreadyRefundedError = ErrorResult & {
   refundId: Scalars['ID'];
 };
 
+export type ApplyCouponCodeResult = Order | CouponCodeExpiredError | CouponCodeInvalidError | CouponCodeLimitError;
+
 export type Asset = Node & {
   tags: Array<Tag>;
   id: Scalars['ID'];
@@ -1417,7 +1419,10 @@ export enum ErrorCode {
   INSUFFICIENT_STOCK_ERROR = 'INSUFFICIENT_STOCK_ERROR',
   COUPON_CODE_INVALID_ERROR = 'COUPON_CODE_INVALID_ERROR',
   COUPON_CODE_EXPIRED_ERROR = 'COUPON_CODE_EXPIRED_ERROR',
-  COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR'
+  COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR',
+  ORDER_MODIFICATION_ERROR = 'ORDER_MODIFICATION_ERROR',
+  INELIGIBLE_SHIPPING_METHOD_ERROR = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
+  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR'
 }
 
 export type ErrorResult = {
@@ -1698,6 +1703,12 @@ export type ImportInfo = {
   imported: Scalars['Int'];
 };
 
+/** Returned when attempting to set a ShippingMethod for which the Order is not eligible */
+export type IneligibleShippingMethodError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when attempting to add more items to the Order than are available */
 export type InsufficientStockError = ErrorResult & {
   errorCode: ErrorCode;
@@ -2387,6 +2398,27 @@ export type Mutation = {
    * Payment.
    */
   addManualPaymentToOrder: AddManualPaymentToOrderResult;
+  /** Creates a draft Order */
+  createDraftOrder: Order;
+  /** Adds an item to the draft Order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available. */
+  addItemToDraftOrder: UpdateOrderItemsResult;
+  /** Adjusts a draft OrderLine. If custom fields are defined on the OrderLine entity, a third argument 'customFields' of type `OrderLineCustomFieldsInput` will be available. */
+  adjustDraftOrderLine: UpdateOrderItemsResult;
+  /** Remove an OrderLine from the draft Order */
+  removeDraftOrderLine: RemoveOrderItemsResult;
+  setCustomerForDraftOrder: SetCustomerForDraftOrderResult;
+  /** Sets the shipping address for a draft Order */
+  setDraftOrderShippingAddress: Order;
+  /** Sets the billing address for a draft Order */
+  setDraftOrderBillingAddress: Order;
+  /** Allows any custom fields to be set for the active order */
+  setDraftOrderCustomFields: Order;
+  /** Applies the given coupon code to the draft Order */
+  applyCouponCodeToDraftOrder: ApplyCouponCodeResult;
+  /** Removes the given coupon code from the draft Order */
+  removeCouponCodeFromDraftOrder?: Maybe<Order>;
+  /** Sets the shipping method by id, which can be obtained with the `eligibleShippingMethodsForDraftOrder` query */
+  setDraftOrderShippingMethod: SetOrderShippingMethodResult;
   /** Create existing PaymentMethod */
   createPaymentMethod: PaymentMethod;
   /** Update an existing PaymentMethod */
@@ -2840,6 +2872,69 @@ export type MutationAddManualPaymentToOrderArgs = {
 };
 
 
+export type MutationAddItemToDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  productVariantId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationAdjustDraftOrderLineArgs = {
+  orderId: Scalars['ID'];
+  orderLineId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationRemoveDraftOrderLineArgs = {
+  orderId: Scalars['ID'];
+  orderLineId: Scalars['ID'];
+};
+
+
+export type MutationSetCustomerForDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  customerId?: Maybe<Scalars['ID']>;
+  input?: Maybe<CreateCustomerInput>;
+};
+
+
+export type MutationSetDraftOrderShippingAddressArgs = {
+  orderId: Scalars['ID'];
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetDraftOrderBillingAddressArgs = {
+  orderId: Scalars['ID'];
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetDraftOrderCustomFieldsArgs = {
+  orderId: Scalars['ID'];
+  input: UpdateOrderInput;
+};
+
+
+export type MutationApplyCouponCodeToDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationRemoveCouponCodeFromDraftOrderArgs = {
+  orderId: Scalars['ID'];
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationSetDraftOrderShippingMethodArgs = {
+  orderId: Scalars['ID'];
+  shippingMethodId: Scalars['ID'];
+};
+
+
 export type MutationCreatePaymentMethodArgs = {
   input: CreatePaymentMethodInput;
 };
@@ -3103,6 +3198,15 @@ export type NegativeQuantityError = ErrorResult & {
   message: Scalars['String'];
 };
 
+/**
+ * Returned when invoking a mutation which depends on there being an active Order on the
+ * current session.
+ */
+export type NoActiveOrderError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when a call to modifyOrder fails to specify any changes */
 export type NoChangesSpecifiedError = ErrorResult & {
   errorCode: ErrorCode;
@@ -3376,6 +3480,12 @@ export type OrderModification = Node & {
   isSettled: Scalars['Boolean'];
 };
 
+/** Returned when attempting to modify the contents of an Order that is not in the `AddingItems` state. */
+export type OrderModificationError = ErrorResult & {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
 /** Returned when attempting to modify the contents of an Order that is not in the `Modifying` state. */
 export type OrderModificationStateError = ErrorResult & {
   errorCode: ErrorCode;
@@ -4101,6 +4211,8 @@ export type Query = {
   jobBufferSize: Array<JobBufferSize>;
   order?: Maybe<Order>;
   orders: OrderList;
+  /** Returns a list of eligible shipping methods for the draft Order */
+  eligibleShippingMethodsForDraftOrder: Array<ShippingMethodQuote>;
   paymentMethods: PaymentMethodList;
   paymentMethod?: Maybe<PaymentMethod>;
   paymentMethodEligibilityCheckers: Array<ConfigurableOperationDefinition>;
@@ -4253,6 +4365,11 @@ export type QueryOrdersArgs = {
 };
 
 
+export type QueryEligibleShippingMethodsForDraftOrderArgs = {
+  orderId: Scalars['ID'];
+};
+
+
 export type QueryPaymentMethodsArgs = {
   options?: Maybe<PaymentMethodListOptions>;
 };
@@ -4460,6 +4577,8 @@ export type RemoveFacetsFromChannelInput = {
 
 export type RemoveOptionGroupFromProductResult = Product | ProductOptionInUseError;
 
+export type RemoveOrderItemsResult = Order | OrderModificationError;
+
 export type RemoveProductVariantsFromChannelInput = {
   productVariantIds: Array<Scalars['ID']>;
   channelId: Scalars['ID'];
@@ -4608,6 +4727,10 @@ export type ServerConfig = {
   customFieldConfig: CustomFields;
 };
 
+export type SetCustomerForDraftOrderResult = Order | EmailAddressConflictError;
+
+export type SetOrderShippingMethodResult = Order | OrderModificationError | IneligibleShippingMethodError | NoActiveOrderError;
+
 /** Returned if the Payment settlement fails */
 export type SettlePaymentError = ErrorResult & {
   errorCode: ErrorCode;
@@ -5124,6 +5247,8 @@ export type UpdateOrderInput = {
   customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type UpdateOrderItemsResult = Order | OrderModificationError | OrderLimitError | NegativeQuantityError | InsufficientStockError;
+
 export type UpdateOrderNoteInput = {
   noteId: Scalars['ID'];
   note?: Maybe<Scalars['String']>;

+ 2 - 2
packages/payments-plugin/e2e/graphql/generated-shop-types.ts

@@ -842,6 +842,7 @@ export enum ErrorCode {
   COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR',
   ORDER_MODIFICATION_ERROR = 'ORDER_MODIFICATION_ERROR',
   INELIGIBLE_SHIPPING_METHOD_ERROR = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
+  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR',
   ORDER_PAYMENT_STATE_ERROR = 'ORDER_PAYMENT_STATE_ERROR',
   INELIGIBLE_PAYMENT_METHOD_ERROR = 'INELIGIBLE_PAYMENT_METHOD_ERROR',
   PAYMENT_FAILED_ERROR = 'PAYMENT_FAILED_ERROR',
@@ -856,8 +857,7 @@ export enum ErrorCode {
   IDENTIFIER_CHANGE_TOKEN_EXPIRED_ERROR = 'IDENTIFIER_CHANGE_TOKEN_EXPIRED_ERROR',
   PASSWORD_RESET_TOKEN_INVALID_ERROR = 'PASSWORD_RESET_TOKEN_INVALID_ERROR',
   PASSWORD_RESET_TOKEN_EXPIRED_ERROR = 'PASSWORD_RESET_TOKEN_EXPIRED_ERROR',
-  NOT_VERIFIED_ERROR = 'NOT_VERIFIED_ERROR',
-  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR'
+  NOT_VERIFIED_ERROR = 'NOT_VERIFIED_ERROR'
 }
 
 export type ErrorResult = {

+ 2 - 3
packages/payments-plugin/src/mollie/graphql/generated-shop-types.ts

@@ -875,6 +875,7 @@ export enum ErrorCode {
   COUPON_CODE_LIMIT_ERROR = 'COUPON_CODE_LIMIT_ERROR',
   ORDER_MODIFICATION_ERROR = 'ORDER_MODIFICATION_ERROR',
   INELIGIBLE_SHIPPING_METHOD_ERROR = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
+  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR',
   ORDER_PAYMENT_STATE_ERROR = 'ORDER_PAYMENT_STATE_ERROR',
   INELIGIBLE_PAYMENT_METHOD_ERROR = 'INELIGIBLE_PAYMENT_METHOD_ERROR',
   PAYMENT_FAILED_ERROR = 'PAYMENT_FAILED_ERROR',
@@ -889,8 +890,7 @@ export enum ErrorCode {
   IDENTIFIER_CHANGE_TOKEN_EXPIRED_ERROR = 'IDENTIFIER_CHANGE_TOKEN_EXPIRED_ERROR',
   PASSWORD_RESET_TOKEN_INVALID_ERROR = 'PASSWORD_RESET_TOKEN_INVALID_ERROR',
   PASSWORD_RESET_TOKEN_EXPIRED_ERROR = 'PASSWORD_RESET_TOKEN_EXPIRED_ERROR',
-  NOT_VERIFIED_ERROR = 'NOT_VERIFIED_ERROR',
-  NO_ACTIVE_ORDER_ERROR = 'NO_ACTIVE_ORDER_ERROR'
+  NOT_VERIFIED_ERROR = 'NOT_VERIFIED_ERROR'
 }
 
 export type ErrorResult = {
@@ -1570,7 +1570,6 @@ export type MolliePaymentIntentError = ErrorResult & {
 
 export type MolliePaymentIntentInput = {
   paymentMethodCode: Scalars['String'];
-  /** The molliePaymentMethod can be found using the 'code' field of one of the methods from the query 'molliePaymentMethods' */
   molliePaymentMethodCode?: Maybe<Scalars['String']>;
 };
 

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
schema-admin.json


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
schema-shop.json


Некоторые файлы не были показаны из-за большого количества измененных файлов