Browse Source

fix(payments-plugin): Mollie payment intent + Stripe unauthorized settlement fix (#1437)

Closes #1432, closes #1340.
Martijn 3 years ago
parent
commit
37e5f58777
24 changed files with 7606 additions and 4346 deletions
  1. 1 0
      packages/admin-ui/src/lib/core/src/public_api.ts
  2. 479 493
      packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts
  3. 736 751
      packages/common/src/generated-shop-types.ts
  4. 479 493
      packages/core/e2e/graphql/generated-e2e-admin-types.ts
  5. 699 714
      packages/core/e2e/graphql/generated-e2e-shop-types.ts
  6. 479 493
      packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts
  7. 3 0
      packages/payments-plugin/README.md
  8. 17 0
      packages/payments-plugin/e2e/graphql/admin-queries.ts
  9. 479 493
      packages/payments-plugin/e2e/graphql/generated-admin-types.ts
  10. 699 714
      packages/payments-plugin/e2e/graphql/generated-shop-types.ts
  11. 67 41
      packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts
  12. 5 3
      packages/payments-plugin/e2e/payment-helpers.ts
  13. 1 1
      packages/payments-plugin/package.json
  14. 0 5
      packages/payments-plugin/src/mollie/README.md
  15. 3153 0
      packages/payments-plugin/src/mollie/graphql/generated-shop-types.ts
  16. 18 0
      packages/payments-plugin/src/mollie/mollie-shop-schema.ts
  17. 10 55
      packages/payments-plugin/src/mollie/mollie.controller.ts
  18. 19 54
      packages/payments-plugin/src/mollie/mollie.handler.ts
  19. 24 20
      packages/payments-plugin/src/mollie/mollie.plugin.ts
  20. 34 0
      packages/payments-plugin/src/mollie/mollie.resolver.ts
  21. 180 0
      packages/payments-plugin/src/mollie/mollie.service.ts
  22. 4 1
      packages/payments-plugin/src/stripe/stripe.handler.ts
  23. 6 0
      scripts/codegen/generate-graphql-types.ts
  24. 14 15
      yarn.lock

+ 1 - 0
packages/admin-ui/src/lib/core/src/public_api.ts

@@ -98,6 +98,7 @@ export * from './shared/components/asset-gallery/asset-gallery.component';
 export * from './shared/components/asset-picker-dialog/asset-picker-dialog.component';
 export * from './shared/components/asset-preview/asset-preview.component';
 export * from './shared/components/asset-preview-dialog/asset-preview-dialog.component';
+export * from './shared/components/asset-preview-links/asset-preview-links.component';
 export * from './shared/components/asset-search-input/asset-search-input.component';
 export * from './shared/components/channel-assignment-control/channel-assignment-control.component';
 export * from './shared/components/channel-badge/channel-badge.component';

File diff suppressed because it is too large
+ 479 - 493
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts


File diff suppressed because it is too large
+ 736 - 751
packages/common/src/generated-shop-types.ts


File diff suppressed because it is too large
+ 479 - 493
packages/core/e2e/graphql/generated-e2e-admin-types.ts


File diff suppressed because it is too large
+ 699 - 714
packages/core/e2e/graphql/generated-e2e-shop-types.ts


File diff suppressed because it is too large
+ 479 - 493
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts


+ 3 - 0
packages/payments-plugin/README.md

@@ -0,0 +1,3 @@
+# Payments plugin
+
+For documentation, see https://www.vendure.io/docs/typescript-api/payments-plugin

+ 17 - 0
packages/payments-plugin/e2e/graphql/admin-queries.ts

@@ -77,3 +77,20 @@ export const REFUND_ORDER = gql`
     }
     ${REFUND_FRAGMENT}
 `;
+
+export const GET_ORDER_PAYMENTS = gql`
+    query order($id: ID!) {
+        order(id: $id) {
+            id
+            payments {
+                id
+                transactionId
+                method
+                amount
+                state
+                errorMessage
+                metadata
+            }
+        }
+    }
+`;

File diff suppressed because it is too large
+ 479 - 493
packages/payments-plugin/e2e/graphql/generated-admin-types.ts


File diff suppressed because it is too large
+ 699 - 714
packages/payments-plugin/e2e/graphql/generated-shop-types.ts


+ 67 - 41
packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts

@@ -1,13 +1,7 @@
 import { PaymentStatus } from '@mollie/api-client';
 import { DefaultLogger, LogLevel, mergeConfig } from '@vendure/core';
-import {
-    createTestEnvironment,
-    E2E_DEFAULT_CHANNEL_TOKEN,
-    registerInitializer,
-    SimpleGraphQLClient,
-    SqljsInitializer,
-    TestServer,
-} from '@vendure/testing';
+import { createTestEnvironment, E2E_DEFAULT_CHANNEL_TOKEN, SimpleGraphQLClient, TestServer } from '@vendure/testing';
+import gql from 'graphql-tag';
 import nock from 'nock';
 import fetch from 'node-fetch';
 import path from 'path';
@@ -17,16 +11,24 @@ import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-conf
 import { MolliePlugin } from '../src/mollie';
 import { molliePaymentHandler } from '../src/mollie/mollie.handler';
 
-import { CREATE_PAYMENT_METHOD, GET_CUSTOMER_LIST } from './graphql/admin-queries';
+import { CREATE_PAYMENT_METHOD, GET_CUSTOMER_LIST, GET_ORDER_PAYMENTS } from './graphql/admin-queries';
 import { CreatePaymentMethod, GetCustomerList, GetCustomerListQuery } from './graphql/generated-admin-types';
-import {
-    AddItemToOrder,
-    AddPaymentToOrder,
-    GetOrderByCode,
-    TestOrderFragmentFragment,
-} from './graphql/generated-shop-types';
-import { ADD_ITEM_TO_ORDER, ADD_PAYMENT, GET_ORDER_BY_CODE } from './graphql/shop-queries';
-import { proceedToArrangingPayment, refundOne } from './payment-helpers';
+import { AddItemToOrder, GetOrderByCode, TestOrderFragmentFragment } from './graphql/generated-shop-types';
+import { ADD_ITEM_TO_ORDER, GET_ORDER_BY_CODE } from './graphql/shop-queries';
+import { refundOne, setShipping } from './payment-helpers';
+
+export const CREATE_MOLLIE_PAYMENT_INTENT = gql`
+    mutation createMolliePaymentIntent($input: MolliePaymentIntentInput!) {
+        createMolliePaymentIntent(input: $input) {
+            ... on MolliePaymentIntent {
+                url
+            }
+            ... on MolliePaymentIntentError {
+                errorCode
+                message
+            }
+        }
+    }`;
 
 describe('Mollie payments', () => {
     const mockData = {
@@ -41,6 +43,14 @@ describe('Mollie payments', () => {
                     href: 'https://www.mollie.com/payscreen/select-method/mock-payment',
                 },
             },
+            resource: 'payment',
+            mode: 'test',
+            method: 'test-method',
+            profileId: '123',
+            settlementAmount: 'test amount',
+            customerId: '456',
+            authorizedAt: new Date(),
+            paidAt: new Date(),
         },
     };
     let shopClient: SimpleGraphQLClient;
@@ -84,11 +94,19 @@ describe('Mollie payments', () => {
         expect(customers).toHaveLength(2);
     });
 
+    it('Should prepare an order', async () => {
+        await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
+        const { addItemToOrder } = await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
+            productVariantId: 'T_1',
+            quantity: 2,
+        });
+        order = addItemToOrder as TestOrderFragmentFragment;
+        expect(order.code).toBeDefined();
+    });
+
     it('Should add a Mollie paymentMethod', async () => {
-        const { createPaymentMethod } = await adminClient.query<
-            CreatePaymentMethod.Mutation,
-            CreatePaymentMethod.Variables
-        >(CREATE_PAYMENT_METHOD, {
+        const { createPaymentMethod } = await adminClient.query<CreatePaymentMethod.Mutation,
+            CreatePaymentMethod.Variables>(CREATE_PAYMENT_METHOD, {
             input: {
                 code: mockData.methodCode,
                 name: 'Mollie payment test',
@@ -106,7 +124,17 @@ describe('Mollie payments', () => {
         expect(createPaymentMethod.code).toBe(mockData.methodCode);
     });
 
-    it('Should add payment to order', async () => {
+    it('Should fail to create payment intent without shippingmethod', async () => {
+        await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
+        const { createMolliePaymentIntent: result } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, {
+            input: {
+                paymentMethodCode: mockData.methodCode,
+            },
+        });
+        expect(result.errorCode).toBe('ORDER_PAYMENT_STATE_ERROR');
+    });
+
+    it('Should get payment url', async () => {
         let mollieRequest;
         nock('https://api.mollie.com/')
             .post(/.*/, body => {
@@ -115,22 +143,13 @@ describe('Mollie payments', () => {
             })
             .reply(200, mockData.mollieResponse);
         await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
-        await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
-            productVariantId: 'T_1',
-            quantity: 2,
-        });
-        await proceedToArrangingPayment(shopClient);
-        const { addPaymentToOrder } = await shopClient.query<
-            AddPaymentToOrder.Mutation,
-            AddPaymentToOrder.Variables
-        >(ADD_PAYMENT, {
+        await setShipping(shopClient);
+        const { createMolliePaymentIntent } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, {
             input: {
-                method: mockData.methodCode,
-                metadata: {},
+                paymentMethodCode: mockData.methodCode,
             },
         });
-        order = addPaymentToOrder as TestOrderFragmentFragment;
-        expect(order.state).toEqual('PaymentAuthorized');
+        expect(createMolliePaymentIntent).toEqual({ url: 'https://www.mollie.com/payscreen/select-method/mock-payment' });
         expect(mollieRequest?.metadata.orderCode).toEqual(order.code);
         expect(mollieRequest?.redirectUrl).toEqual(`${mockData.redirectUrl}/${order.code}`);
         expect(mollieRequest?.webhookUrl).toEqual(
@@ -138,10 +157,6 @@ describe('Mollie payments', () => {
         );
         expect(mollieRequest?.amount?.value).toBe('3127.60');
         expect(mollieRequest?.amount?.currency).toBeDefined();
-        // tslint:disable-next-line:no-non-null-assertion
-        const payment = order.payments![0];
-        expect(payment.transactionId).toEqual(mockData.mollieResponse.id);
-        expect(payment.metadata.public.redirectLink).toEqual(mockData.mollieResponse._links.checkout.href);
     });
 
     it('Should settle payment for order', async () => {
@@ -168,6 +183,17 @@ describe('Mollie payments', () => {
         expect(order.state).toBe('PaymentSettled');
     });
 
+    it('Should have Mollie metadata on payment', async () => {
+        const { order: { payments: [{ metadata }] } } = await adminClient.query(GET_ORDER_PAYMENTS, { id: order.id });
+        expect(metadata.mode).toBe(mockData.mollieResponse.mode);
+        expect(metadata.method).toBe(mockData.mollieResponse.method);
+        expect(metadata.profileId).toBe(mockData.mollieResponse.profileId);
+        expect(metadata.settlementAmount).toBe(mockData.mollieResponse.settlementAmount);
+        expect(metadata.customerId).toBe(mockData.mollieResponse.customerId);
+        expect(metadata.authorizedAt).toEqual(mockData.mollieResponse.authorizedAt.toISOString());
+        expect(metadata.paidAt).toEqual(mockData.mollieResponse.paidAt.toISOString());
+    });
+
     it('Should fail to refund', async () => {
         let mollieRequest;
         nock('https://api.mollie.com/')
@@ -175,7 +201,7 @@ describe('Mollie payments', () => {
                 mollieRequest = body;
                 return true;
             })
-            .reply(200, { status: 'failed' });
+            .reply(200, { status: 'failed', resource: 'payment' });
         const refund = await refundOne(adminClient, order.lines[0].id, order.payments[0].id);
         expect(refund.state).toBe('Failed');
     });
@@ -187,7 +213,7 @@ describe('Mollie payments', () => {
                 mollieRequest = body;
                 return true;
             })
-            .reply(200, { status: 'pending' });
+            .reply(200, { status: 'pending', resource: 'payment' });
         const refund = await refundOne(adminClient, order.lines[0].id, order.payments[0].id);
         expect(mollieRequest?.amount.value).toBe('1558.80');
         expect(refund.total).toBe(155880);

+ 5 - 3
packages/payments-plugin/e2e/payment-helpers.ts

@@ -16,7 +16,8 @@ import {
     TRANSITION_TO_STATE,
 } from './graphql/shop-queries';
 
-export async function proceedToArrangingPayment(shopClient: SimpleGraphQLClient): Promise<ID> {
+
+export async function setShipping(shopClient: SimpleGraphQLClient): Promise<void> {
     await shopClient.query(SET_SHIPPING_ADDRESS, {
         input: {
             fullName: 'name',
@@ -26,15 +27,16 @@ export async function proceedToArrangingPayment(shopClient: SimpleGraphQLClient)
             countryCode: 'US',
         },
     });
-
     const { eligibleShippingMethods } = await shopClient.query<GetShippingMethods.Query>(
         GET_ELIGIBLE_SHIPPING_METHODS,
     );
-
     await shopClient.query<SetShippingMethod.Mutation, SetShippingMethod.Variables>(SET_SHIPPING_METHOD, {
         id: eligibleShippingMethods[1].id,
     });
+}
 
+export async function proceedToArrangingPayment(shopClient: SimpleGraphQLClient): Promise<ID> {
+    await setShipping(shopClient);
     const { transitionOrderToState } = await shopClient.query<
         TransitionToState.Mutation,
         TransitionToState.Variables

+ 1 - 1
packages/payments-plugin/package.json

@@ -26,7 +26,7 @@
         "stripe": "8.x"
     },
     "devDependencies": {
-        "@mollie/api-client": "^3.5.1",
+        "@mollie/api-client": "^3.6.0",
         "@types/braintree": "^2.22.15",
         "@vendure/common": "^1.4.7",
         "@vendure/core": "^1.4.7",

+ 0 - 5
packages/payments-plugin/src/mollie/README.md

@@ -1,5 +0,0 @@
-# Mollie payment plugin
-
-Plugin to enable payments through the [Mollie platform](https://docs.mollie.com/).
-
-For documentation, see https://www.vendure.io/docs/typescript-api/payments-plugin/mollie-plugin

+ 3153 - 0
packages/payments-plugin/src/mollie/graphql/generated-shop-types.ts

@@ -0,0 +1,3153 @@
+// tslint:disable
+export type Maybe<T> = T | null;
+export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
+export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
+export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
+/** All built-in and custom scalars, mapped to their actual values */
+export type Scalars = {
+  ID: string;
+  String: string;
+  Boolean: boolean;
+  Int: number;
+  Float: number;
+  /** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
+  DateTime: any;
+  /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
+  JSON: any;
+  /** The `Upload` scalar type represents a file upload. */
+  Upload: any;
+};
+
+export type ActiveOrderResult = Order | NoActiveOrderError;
+
+export type AddPaymentToOrderResult = Order | OrderPaymentStateError | IneligiblePaymentMethodError | PaymentFailedError | PaymentDeclinedError | OrderStateTransitionError | NoActiveOrderError;
+
+export type Address = Node & {
+  __typename?: 'Address';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  fullName?: Maybe<Scalars['String']>;
+  company?: Maybe<Scalars['String']>;
+  streetLine1: Scalars['String'];
+  streetLine2?: Maybe<Scalars['String']>;
+  city?: Maybe<Scalars['String']>;
+  province?: Maybe<Scalars['String']>;
+  postalCode?: Maybe<Scalars['String']>;
+  country: Country;
+  phoneNumber?: Maybe<Scalars['String']>;
+  defaultShippingAddress?: Maybe<Scalars['Boolean']>;
+  defaultBillingAddress?: Maybe<Scalars['Boolean']>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type Adjustment = {
+  __typename?: 'Adjustment';
+  adjustmentSource: Scalars['String'];
+  type: AdjustmentType;
+  description: Scalars['String'];
+  amount: Scalars['Int'];
+};
+
+export enum AdjustmentType {
+  PROMOTION = 'PROMOTION',
+  DISTRIBUTED_ORDER_PROMOTION = 'DISTRIBUTED_ORDER_PROMOTION',
+  OTHER = 'OTHER'
+}
+
+/** Returned when attempting to set the Customer for an Order when already logged in. */
+export type AlreadyLoggedInError = ErrorResult & {
+  __typename?: 'AlreadyLoggedInError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+export type ApplyCouponCodeResult = Order | CouponCodeExpiredError | CouponCodeInvalidError | CouponCodeLimitError;
+
+export type Asset = Node & {
+  __typename?: 'Asset';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  name: Scalars['String'];
+  type: AssetType;
+  fileSize: Scalars['Int'];
+  mimeType: Scalars['String'];
+  width: Scalars['Int'];
+  height: Scalars['Int'];
+  source: Scalars['String'];
+  preview: Scalars['String'];
+  focalPoint?: Maybe<Coordinate>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type AssetList = PaginatedList & {
+  __typename?: 'AssetList';
+  items: Array<Asset>;
+  totalItems: Scalars['Int'];
+};
+
+export enum AssetType {
+  IMAGE = 'IMAGE',
+  VIDEO = 'VIDEO',
+  BINARY = 'BINARY'
+}
+
+export type AuthenticationInput = {
+  native?: Maybe<NativeAuthInput>;
+};
+
+export type AuthenticationMethod = Node & {
+  __typename?: 'AuthenticationMethod';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  strategy: Scalars['String'];
+};
+
+export type AuthenticationResult = CurrentUser | InvalidCredentialsError | NotVerifiedError;
+
+export type BooleanCustomFieldConfig = CustomField & {
+  __typename?: 'BooleanCustomFieldConfig';
+  name: Scalars['String'];
+  type: Scalars['String'];
+  list: Scalars['Boolean'];
+  label?: Maybe<Array<LocalizedString>>;
+  description?: Maybe<Array<LocalizedString>>;
+  readonly?: Maybe<Scalars['Boolean']>;
+  internal?: Maybe<Scalars['Boolean']>;
+  nullable?: Maybe<Scalars['Boolean']>;
+  ui?: Maybe<Scalars['JSON']>;
+};
+
+/** Operators for filtering on a list of Boolean fields */
+export type BooleanListOperators = {
+  inList: Scalars['Boolean'];
+};
+
+/** Operators for filtering on a Boolean field */
+export type BooleanOperators = {
+  eq?: Maybe<Scalars['Boolean']>;
+};
+
+export type Channel = Node & {
+  __typename?: 'Channel';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  code: Scalars['String'];
+  token: Scalars['String'];
+  defaultTaxZone?: Maybe<Zone>;
+  defaultShippingZone?: Maybe<Zone>;
+  defaultLanguageCode: LanguageCode;
+  currencyCode: CurrencyCode;
+  pricesIncludeTax: Scalars['Boolean'];
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type Collection = Node & {
+  __typename?: 'Collection';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode?: Maybe<LanguageCode>;
+  name: Scalars['String'];
+  slug: Scalars['String'];
+  breadcrumbs: Array<CollectionBreadcrumb>;
+  position: Scalars['Int'];
+  description: Scalars['String'];
+  featuredAsset?: Maybe<Asset>;
+  assets: Array<Asset>;
+  parent?: Maybe<Collection>;
+  children?: Maybe<Array<Collection>>;
+  filters: Array<ConfigurableOperation>;
+  translations: Array<CollectionTranslation>;
+  productVariants: ProductVariantList;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+
+export type CollectionProductVariantsArgs = {
+  options?: Maybe<ProductVariantListOptions>;
+};
+
+export type CollectionBreadcrumb = {
+  __typename?: 'CollectionBreadcrumb';
+  id: Scalars['ID'];
+  name: Scalars['String'];
+  slug: Scalars['String'];
+};
+
+export type CollectionFilterParameter = {
+  id?: Maybe<IdOperators>;
+  createdAt?: Maybe<DateOperators>;
+  updatedAt?: Maybe<DateOperators>;
+  languageCode?: Maybe<StringOperators>;
+  name?: Maybe<StringOperators>;
+  slug?: Maybe<StringOperators>;
+  position?: Maybe<NumberOperators>;
+  description?: Maybe<StringOperators>;
+};
+
+export type CollectionList = PaginatedList & {
+  __typename?: 'CollectionList';
+  items: Array<Collection>;
+  totalItems: Scalars['Int'];
+};
+
+export type CollectionListOptions = {
+  /** Skips the first n results, for use in pagination */
+  skip?: Maybe<Scalars['Int']>;
+  /** Takes n results, for use in pagination */
+  take?: Maybe<Scalars['Int']>;
+  /** Specifies which properties to sort the results by */
+  sort?: Maybe<CollectionSortParameter>;
+  /** Allows the results to be filtered */
+  filter?: Maybe<CollectionFilterParameter>;
+  /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+  filterOperator?: Maybe<LogicalOperator>;
+};
+
+/**
+ * Which Collections are present in the products returned
+ * by the search, and in what quantity.
+ */
+export type CollectionResult = {
+  __typename?: 'CollectionResult';
+  collection: Collection;
+  count: Scalars['Int'];
+};
+
+export type CollectionSortParameter = {
+  id?: Maybe<SortOrder>;
+  createdAt?: Maybe<SortOrder>;
+  updatedAt?: Maybe<SortOrder>;
+  name?: Maybe<SortOrder>;
+  slug?: Maybe<SortOrder>;
+  position?: Maybe<SortOrder>;
+  description?: Maybe<SortOrder>;
+};
+
+export type CollectionTranslation = {
+  __typename?: 'CollectionTranslation';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  name: Scalars['String'];
+  slug: Scalars['String'];
+  description: Scalars['String'];
+};
+
+export type ConfigArg = {
+  __typename?: 'ConfigArg';
+  name: Scalars['String'];
+  value: Scalars['String'];
+};
+
+export type ConfigArgDefinition = {
+  __typename?: 'ConfigArgDefinition';
+  name: Scalars['String'];
+  type: Scalars['String'];
+  list: Scalars['Boolean'];
+  required: Scalars['Boolean'];
+  defaultValue?: Maybe<Scalars['JSON']>;
+  label?: Maybe<Scalars['String']>;
+  description?: Maybe<Scalars['String']>;
+  ui?: Maybe<Scalars['JSON']>;
+};
+
+export type ConfigArgInput = {
+  name: Scalars['String'];
+  /** A JSON stringified representation of the actual value */
+  value: Scalars['String'];
+};
+
+export type ConfigurableOperation = {
+  __typename?: 'ConfigurableOperation';
+  code: Scalars['String'];
+  args: Array<ConfigArg>;
+};
+
+export type ConfigurableOperationDefinition = {
+  __typename?: 'ConfigurableOperationDefinition';
+  code: Scalars['String'];
+  args: Array<ConfigArgDefinition>;
+  description: Scalars['String'];
+};
+
+export type ConfigurableOperationInput = {
+  code: Scalars['String'];
+  arguments: Array<ConfigArgInput>;
+};
+
+export type Coordinate = {
+  __typename?: 'Coordinate';
+  x: Scalars['Float'];
+  y: Scalars['Float'];
+};
+
+export type Country = Node & {
+  __typename?: 'Country';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  code: Scalars['String'];
+  name: Scalars['String'];
+  enabled: Scalars['Boolean'];
+  translations: Array<CountryTranslation>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type CountryList = PaginatedList & {
+  __typename?: 'CountryList';
+  items: Array<Country>;
+  totalItems: Scalars['Int'];
+};
+
+export type CountryTranslation = {
+  __typename?: 'CountryTranslation';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  name: Scalars['String'];
+};
+
+/** Returned if the provided coupon code is invalid */
+export type CouponCodeExpiredError = ErrorResult & {
+  __typename?: 'CouponCodeExpiredError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+  couponCode: Scalars['String'];
+};
+
+/** Returned if the provided coupon code is invalid */
+export type CouponCodeInvalidError = ErrorResult & {
+  __typename?: 'CouponCodeInvalidError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+  couponCode: Scalars['String'];
+};
+
+/** Returned if the provided coupon code is invalid */
+export type CouponCodeLimitError = ErrorResult & {
+  __typename?: 'CouponCodeLimitError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+  couponCode: Scalars['String'];
+  limit: Scalars['Int'];
+};
+
+export type CreateAddressInput = {
+  fullName?: Maybe<Scalars['String']>;
+  company?: Maybe<Scalars['String']>;
+  streetLine1: Scalars['String'];
+  streetLine2?: Maybe<Scalars['String']>;
+  city?: Maybe<Scalars['String']>;
+  province?: Maybe<Scalars['String']>;
+  postalCode?: Maybe<Scalars['String']>;
+  countryCode: Scalars['String'];
+  phoneNumber?: Maybe<Scalars['String']>;
+  defaultShippingAddress?: Maybe<Scalars['Boolean']>;
+  defaultBillingAddress?: Maybe<Scalars['Boolean']>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type CreateCustomerInput = {
+  title?: Maybe<Scalars['String']>;
+  firstName: Scalars['String'];
+  lastName: Scalars['String'];
+  phoneNumber?: Maybe<Scalars['String']>;
+  emailAddress: Scalars['String'];
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+/**
+ * @description
+ * ISO 4217 currency code
+ *
+ * @docsCategory common
+ */
+export enum CurrencyCode {
+  /** United Arab Emirates dirham */
+  AED = 'AED',
+  /** Afghan afghani */
+  AFN = 'AFN',
+  /** Albanian lek */
+  ALL = 'ALL',
+  /** Armenian dram */
+  AMD = 'AMD',
+  /** Netherlands Antillean guilder */
+  ANG = 'ANG',
+  /** Angolan kwanza */
+  AOA = 'AOA',
+  /** Argentine peso */
+  ARS = 'ARS',
+  /** Australian dollar */
+  AUD = 'AUD',
+  /** Aruban florin */
+  AWG = 'AWG',
+  /** Azerbaijani manat */
+  AZN = 'AZN',
+  /** Bosnia and Herzegovina convertible mark */
+  BAM = 'BAM',
+  /** Barbados dollar */
+  BBD = 'BBD',
+  /** Bangladeshi taka */
+  BDT = 'BDT',
+  /** Bulgarian lev */
+  BGN = 'BGN',
+  /** Bahraini dinar */
+  BHD = 'BHD',
+  /** Burundian franc */
+  BIF = 'BIF',
+  /** Bermudian dollar */
+  BMD = 'BMD',
+  /** Brunei dollar */
+  BND = 'BND',
+  /** Boliviano */
+  BOB = 'BOB',
+  /** Brazilian real */
+  BRL = 'BRL',
+  /** Bahamian dollar */
+  BSD = 'BSD',
+  /** Bhutanese ngultrum */
+  BTN = 'BTN',
+  /** Botswana pula */
+  BWP = 'BWP',
+  /** Belarusian ruble */
+  BYN = 'BYN',
+  /** Belize dollar */
+  BZD = 'BZD',
+  /** Canadian dollar */
+  CAD = 'CAD',
+  /** Congolese franc */
+  CDF = 'CDF',
+  /** Swiss franc */
+  CHF = 'CHF',
+  /** Chilean peso */
+  CLP = 'CLP',
+  /** Renminbi (Chinese) yuan */
+  CNY = 'CNY',
+  /** Colombian peso */
+  COP = 'COP',
+  /** Costa Rican colon */
+  CRC = 'CRC',
+  /** Cuban convertible peso */
+  CUC = 'CUC',
+  /** Cuban peso */
+  CUP = 'CUP',
+  /** Cape Verde escudo */
+  CVE = 'CVE',
+  /** Czech koruna */
+  CZK = 'CZK',
+  /** Djiboutian franc */
+  DJF = 'DJF',
+  /** Danish krone */
+  DKK = 'DKK',
+  /** Dominican peso */
+  DOP = 'DOP',
+  /** Algerian dinar */
+  DZD = 'DZD',
+  /** Egyptian pound */
+  EGP = 'EGP',
+  /** Eritrean nakfa */
+  ERN = 'ERN',
+  /** Ethiopian birr */
+  ETB = 'ETB',
+  /** Euro */
+  EUR = 'EUR',
+  /** Fiji dollar */
+  FJD = 'FJD',
+  /** Falkland Islands pound */
+  FKP = 'FKP',
+  /** Pound sterling */
+  GBP = 'GBP',
+  /** Georgian lari */
+  GEL = 'GEL',
+  /** Ghanaian cedi */
+  GHS = 'GHS',
+  /** Gibraltar pound */
+  GIP = 'GIP',
+  /** Gambian dalasi */
+  GMD = 'GMD',
+  /** Guinean franc */
+  GNF = 'GNF',
+  /** Guatemalan quetzal */
+  GTQ = 'GTQ',
+  /** Guyanese dollar */
+  GYD = 'GYD',
+  /** Hong Kong dollar */
+  HKD = 'HKD',
+  /** Honduran lempira */
+  HNL = 'HNL',
+  /** Croatian kuna */
+  HRK = 'HRK',
+  /** Haitian gourde */
+  HTG = 'HTG',
+  /** Hungarian forint */
+  HUF = 'HUF',
+  /** Indonesian rupiah */
+  IDR = 'IDR',
+  /** Israeli new shekel */
+  ILS = 'ILS',
+  /** Indian rupee */
+  INR = 'INR',
+  /** Iraqi dinar */
+  IQD = 'IQD',
+  /** Iranian rial */
+  IRR = 'IRR',
+  /** Icelandic króna */
+  ISK = 'ISK',
+  /** Jamaican dollar */
+  JMD = 'JMD',
+  /** Jordanian dinar */
+  JOD = 'JOD',
+  /** Japanese yen */
+  JPY = 'JPY',
+  /** Kenyan shilling */
+  KES = 'KES',
+  /** Kyrgyzstani som */
+  KGS = 'KGS',
+  /** Cambodian riel */
+  KHR = 'KHR',
+  /** Comoro franc */
+  KMF = 'KMF',
+  /** North Korean won */
+  KPW = 'KPW',
+  /** South Korean won */
+  KRW = 'KRW',
+  /** Kuwaiti dinar */
+  KWD = 'KWD',
+  /** Cayman Islands dollar */
+  KYD = 'KYD',
+  /** Kazakhstani tenge */
+  KZT = 'KZT',
+  /** Lao kip */
+  LAK = 'LAK',
+  /** Lebanese pound */
+  LBP = 'LBP',
+  /** Sri Lankan rupee */
+  LKR = 'LKR',
+  /** Liberian dollar */
+  LRD = 'LRD',
+  /** Lesotho loti */
+  LSL = 'LSL',
+  /** Libyan dinar */
+  LYD = 'LYD',
+  /** Moroccan dirham */
+  MAD = 'MAD',
+  /** Moldovan leu */
+  MDL = 'MDL',
+  /** Malagasy ariary */
+  MGA = 'MGA',
+  /** Macedonian denar */
+  MKD = 'MKD',
+  /** Myanmar kyat */
+  MMK = 'MMK',
+  /** Mongolian tögrög */
+  MNT = 'MNT',
+  /** Macanese pataca */
+  MOP = 'MOP',
+  /** Mauritanian ouguiya */
+  MRU = 'MRU',
+  /** Mauritian rupee */
+  MUR = 'MUR',
+  /** Maldivian rufiyaa */
+  MVR = 'MVR',
+  /** Malawian kwacha */
+  MWK = 'MWK',
+  /** Mexican peso */
+  MXN = 'MXN',
+  /** Malaysian ringgit */
+  MYR = 'MYR',
+  /** Mozambican metical */
+  MZN = 'MZN',
+  /** Namibian dollar */
+  NAD = 'NAD',
+  /** Nigerian naira */
+  NGN = 'NGN',
+  /** Nicaraguan córdoba */
+  NIO = 'NIO',
+  /** Norwegian krone */
+  NOK = 'NOK',
+  /** Nepalese rupee */
+  NPR = 'NPR',
+  /** New Zealand dollar */
+  NZD = 'NZD',
+  /** Omani rial */
+  OMR = 'OMR',
+  /** Panamanian balboa */
+  PAB = 'PAB',
+  /** Peruvian sol */
+  PEN = 'PEN',
+  /** Papua New Guinean kina */
+  PGK = 'PGK',
+  /** Philippine peso */
+  PHP = 'PHP',
+  /** Pakistani rupee */
+  PKR = 'PKR',
+  /** Polish złoty */
+  PLN = 'PLN',
+  /** Paraguayan guaraní */
+  PYG = 'PYG',
+  /** Qatari riyal */
+  QAR = 'QAR',
+  /** Romanian leu */
+  RON = 'RON',
+  /** Serbian dinar */
+  RSD = 'RSD',
+  /** Russian ruble */
+  RUB = 'RUB',
+  /** Rwandan franc */
+  RWF = 'RWF',
+  /** Saudi riyal */
+  SAR = 'SAR',
+  /** Solomon Islands dollar */
+  SBD = 'SBD',
+  /** Seychelles rupee */
+  SCR = 'SCR',
+  /** Sudanese pound */
+  SDG = 'SDG',
+  /** Swedish krona/kronor */
+  SEK = 'SEK',
+  /** Singapore dollar */
+  SGD = 'SGD',
+  /** Saint Helena pound */
+  SHP = 'SHP',
+  /** Sierra Leonean leone */
+  SLL = 'SLL',
+  /** Somali shilling */
+  SOS = 'SOS',
+  /** Surinamese dollar */
+  SRD = 'SRD',
+  /** South Sudanese pound */
+  SSP = 'SSP',
+  /** São Tomé and Príncipe dobra */
+  STN = 'STN',
+  /** Salvadoran colón */
+  SVC = 'SVC',
+  /** Syrian pound */
+  SYP = 'SYP',
+  /** Swazi lilangeni */
+  SZL = 'SZL',
+  /** Thai baht */
+  THB = 'THB',
+  /** Tajikistani somoni */
+  TJS = 'TJS',
+  /** Turkmenistan manat */
+  TMT = 'TMT',
+  /** Tunisian dinar */
+  TND = 'TND',
+  /** Tongan paʻanga */
+  TOP = 'TOP',
+  /** Turkish lira */
+  TRY = 'TRY',
+  /** Trinidad and Tobago dollar */
+  TTD = 'TTD',
+  /** New Taiwan dollar */
+  TWD = 'TWD',
+  /** Tanzanian shilling */
+  TZS = 'TZS',
+  /** Ukrainian hryvnia */
+  UAH = 'UAH',
+  /** Ugandan shilling */
+  UGX = 'UGX',
+  /** United States dollar */
+  USD = 'USD',
+  /** Uruguayan peso */
+  UYU = 'UYU',
+  /** Uzbekistan som */
+  UZS = 'UZS',
+  /** Venezuelan bolívar soberano */
+  VES = 'VES',
+  /** Vietnamese đồng */
+  VND = 'VND',
+  /** Vanuatu vatu */
+  VUV = 'VUV',
+  /** Samoan tala */
+  WST = 'WST',
+  /** CFA franc BEAC */
+  XAF = 'XAF',
+  /** East Caribbean dollar */
+  XCD = 'XCD',
+  /** CFA franc BCEAO */
+  XOF = 'XOF',
+  /** CFP franc (franc Pacifique) */
+  XPF = 'XPF',
+  /** Yemeni rial */
+  YER = 'YER',
+  /** South African rand */
+  ZAR = 'ZAR',
+  /** Zambian kwacha */
+  ZMW = 'ZMW',
+  /** Zimbabwean dollar */
+  ZWL = 'ZWL'
+}
+
+export type CurrentUser = {
+  __typename?: 'CurrentUser';
+  id: Scalars['ID'];
+  identifier: Scalars['String'];
+  channels: Array<CurrentUserChannel>;
+};
+
+export type CurrentUserChannel = {
+  __typename?: 'CurrentUserChannel';
+  id: Scalars['ID'];
+  token: Scalars['String'];
+  code: Scalars['String'];
+  permissions: Array<Permission>;
+};
+
+export type CustomField = {
+  name: Scalars['String'];
+  type: Scalars['String'];
+  list: Scalars['Boolean'];
+  label?: Maybe<Array<LocalizedString>>;
+  description?: Maybe<Array<LocalizedString>>;
+  readonly?: Maybe<Scalars['Boolean']>;
+  internal?: Maybe<Scalars['Boolean']>;
+  nullable?: Maybe<Scalars['Boolean']>;
+  ui?: Maybe<Scalars['JSON']>;
+};
+
+export type CustomFieldConfig = StringCustomFieldConfig | LocaleStringCustomFieldConfig | IntCustomFieldConfig | FloatCustomFieldConfig | BooleanCustomFieldConfig | DateTimeCustomFieldConfig | RelationCustomFieldConfig | TextCustomFieldConfig;
+
+export type Customer = Node & {
+  __typename?: 'Customer';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  title?: Maybe<Scalars['String']>;
+  firstName: Scalars['String'];
+  lastName: Scalars['String'];
+  phoneNumber?: Maybe<Scalars['String']>;
+  emailAddress: Scalars['String'];
+  addresses?: Maybe<Array<Address>>;
+  orders: OrderList;
+  user?: Maybe<User>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+
+export type CustomerOrdersArgs = {
+  options?: Maybe<OrderListOptions>;
+};
+
+export type CustomerFilterParameter = {
+  id?: Maybe<IdOperators>;
+  createdAt?: Maybe<DateOperators>;
+  updatedAt?: Maybe<DateOperators>;
+  title?: Maybe<StringOperators>;
+  firstName?: Maybe<StringOperators>;
+  lastName?: Maybe<StringOperators>;
+  phoneNumber?: Maybe<StringOperators>;
+  emailAddress?: Maybe<StringOperators>;
+};
+
+export type CustomerGroup = Node & {
+  __typename?: 'CustomerGroup';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  name: Scalars['String'];
+  customers: CustomerList;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+
+export type CustomerGroupCustomersArgs = {
+  options?: Maybe<CustomerListOptions>;
+};
+
+export type CustomerList = PaginatedList & {
+  __typename?: 'CustomerList';
+  items: Array<Customer>;
+  totalItems: Scalars['Int'];
+};
+
+export type CustomerListOptions = {
+  /** Skips the first n results, for use in pagination */
+  skip?: Maybe<Scalars['Int']>;
+  /** Takes n results, for use in pagination */
+  take?: Maybe<Scalars['Int']>;
+  /** Specifies which properties to sort the results by */
+  sort?: Maybe<CustomerSortParameter>;
+  /** Allows the results to be filtered */
+  filter?: Maybe<CustomerFilterParameter>;
+  /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+  filterOperator?: Maybe<LogicalOperator>;
+};
+
+export type CustomerSortParameter = {
+  id?: Maybe<SortOrder>;
+  createdAt?: Maybe<SortOrder>;
+  updatedAt?: Maybe<SortOrder>;
+  title?: Maybe<SortOrder>;
+  firstName?: Maybe<SortOrder>;
+  lastName?: Maybe<SortOrder>;
+  phoneNumber?: Maybe<SortOrder>;
+  emailAddress?: Maybe<SortOrder>;
+};
+
+/** Operators for filtering on a list of Date fields */
+export type DateListOperators = {
+  inList: Scalars['DateTime'];
+};
+
+/** Operators for filtering on a DateTime field */
+export type DateOperators = {
+  eq?: Maybe<Scalars['DateTime']>;
+  before?: Maybe<Scalars['DateTime']>;
+  after?: Maybe<Scalars['DateTime']>;
+  between?: Maybe<DateRange>;
+};
+
+export type DateRange = {
+  start: Scalars['DateTime'];
+  end: Scalars['DateTime'];
+};
+
+
+/**
+ * Expects the same validation formats as the `<input type="datetime-local">` HTML element.
+ * See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local#Additional_attributes
+ */
+export type DateTimeCustomFieldConfig = CustomField & {
+  __typename?: 'DateTimeCustomFieldConfig';
+  name: Scalars['String'];
+  type: Scalars['String'];
+  list: Scalars['Boolean'];
+  label?: Maybe<Array<LocalizedString>>;
+  description?: Maybe<Array<LocalizedString>>;
+  readonly?: Maybe<Scalars['Boolean']>;
+  internal?: Maybe<Scalars['Boolean']>;
+  nullable?: Maybe<Scalars['Boolean']>;
+  min?: Maybe<Scalars['String']>;
+  max?: Maybe<Scalars['String']>;
+  step?: Maybe<Scalars['Int']>;
+  ui?: Maybe<Scalars['JSON']>;
+};
+
+export type DeletionResponse = {
+  __typename?: 'DeletionResponse';
+  result: DeletionResult;
+  message?: Maybe<Scalars['String']>;
+};
+
+export enum DeletionResult {
+  /** The entity was successfully deleted */
+  DELETED = 'DELETED',
+  /** Deletion did not take place, reason given in message */
+  NOT_DELETED = 'NOT_DELETED'
+}
+
+export type Discount = {
+  __typename?: 'Discount';
+  adjustmentSource: Scalars['String'];
+  type: AdjustmentType;
+  description: Scalars['String'];
+  amount: Scalars['Int'];
+  amountWithTax: Scalars['Int'];
+};
+
+/** Returned when attempting to create a Customer with an email address already registered to an existing User. */
+export type EmailAddressConflictError = ErrorResult & {
+  __typename?: 'EmailAddressConflictError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+export enum ErrorCode {
+  UNKNOWN_ERROR = 'UNKNOWN_ERROR',
+  NATIVE_AUTH_STRATEGY_ERROR = 'NATIVE_AUTH_STRATEGY_ERROR',
+  INVALID_CREDENTIALS_ERROR = 'INVALID_CREDENTIALS_ERROR',
+  ORDER_STATE_TRANSITION_ERROR = 'ORDER_STATE_TRANSITION_ERROR',
+  EMAIL_ADDRESS_CONFLICT_ERROR = 'EMAIL_ADDRESS_CONFLICT_ERROR',
+  ORDER_LIMIT_ERROR = 'ORDER_LIMIT_ERROR',
+  NEGATIVE_QUANTITY_ERROR = 'NEGATIVE_QUANTITY_ERROR',
+  INSUFFICIENT_STOCK_ERROR = 'INSUFFICIENT_STOCK_ERROR',
+  ORDER_MODIFICATION_ERROR = 'ORDER_MODIFICATION_ERROR',
+  INELIGIBLE_SHIPPING_METHOD_ERROR = 'INELIGIBLE_SHIPPING_METHOD_ERROR',
+  ORDER_PAYMENT_STATE_ERROR = 'ORDER_PAYMENT_STATE_ERROR',
+  INELIGIBLE_PAYMENT_METHOD_ERROR = 'INELIGIBLE_PAYMENT_METHOD_ERROR',
+  PAYMENT_FAILED_ERROR = 'PAYMENT_FAILED_ERROR',
+  PAYMENT_DECLINED_ERROR = 'PAYMENT_DECLINED_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',
+  ALREADY_LOGGED_IN_ERROR = 'ALREADY_LOGGED_IN_ERROR',
+  MISSING_PASSWORD_ERROR = 'MISSING_PASSWORD_ERROR',
+  PASSWORD_VALIDATION_ERROR = 'PASSWORD_VALIDATION_ERROR',
+  PASSWORD_ALREADY_SET_ERROR = 'PASSWORD_ALREADY_SET_ERROR',
+  VERIFICATION_TOKEN_INVALID_ERROR = 'VERIFICATION_TOKEN_INVALID_ERROR',
+  VERIFICATION_TOKEN_EXPIRED_ERROR = 'VERIFICATION_TOKEN_EXPIRED_ERROR',
+  IDENTIFIER_CHANGE_TOKEN_INVALID_ERROR = 'IDENTIFIER_CHANGE_TOKEN_INVALID_ERROR',
+  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'
+}
+
+export type ErrorResult = {
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+export type Facet = Node & {
+  __typename?: 'Facet';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  name: Scalars['String'];
+  code: Scalars['String'];
+  values: Array<FacetValue>;
+  translations: Array<FacetTranslation>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type FacetFilterParameter = {
+  id?: Maybe<IdOperators>;
+  createdAt?: Maybe<DateOperators>;
+  updatedAt?: Maybe<DateOperators>;
+  languageCode?: Maybe<StringOperators>;
+  name?: Maybe<StringOperators>;
+  code?: Maybe<StringOperators>;
+};
+
+export type FacetList = PaginatedList & {
+  __typename?: 'FacetList';
+  items: Array<Facet>;
+  totalItems: Scalars['Int'];
+};
+
+export type FacetListOptions = {
+  /** Skips the first n results, for use in pagination */
+  skip?: Maybe<Scalars['Int']>;
+  /** Takes n results, for use in pagination */
+  take?: Maybe<Scalars['Int']>;
+  /** Specifies which properties to sort the results by */
+  sort?: Maybe<FacetSortParameter>;
+  /** Allows the results to be filtered */
+  filter?: Maybe<FacetFilterParameter>;
+  /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+  filterOperator?: Maybe<LogicalOperator>;
+};
+
+export type FacetSortParameter = {
+  id?: Maybe<SortOrder>;
+  createdAt?: Maybe<SortOrder>;
+  updatedAt?: Maybe<SortOrder>;
+  name?: Maybe<SortOrder>;
+  code?: Maybe<SortOrder>;
+};
+
+export type FacetTranslation = {
+  __typename?: 'FacetTranslation';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  name: Scalars['String'];
+};
+
+export type FacetValue = Node & {
+  __typename?: 'FacetValue';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  facet: Facet;
+  name: Scalars['String'];
+  code: Scalars['String'];
+  translations: Array<FacetValueTranslation>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+/**
+ * Used to construct boolean expressions for filtering search results
+ * by FacetValue ID. Examples:
+ *
+ * * ID=1 OR ID=2: `{ facetValueFilters: [{ or: [1,2] }] }`
+ * * ID=1 AND ID=2: `{ facetValueFilters: [{ and: 1 }, { and: 2 }] }`
+ * * ID=1 AND (ID=2 OR ID=3): `{ facetValueFilters: [{ and: 1 }, { or: [2,3] }] }`
+ */
+export type FacetValueFilterInput = {
+  and?: Maybe<Scalars['ID']>;
+  or?: Maybe<Array<Scalars['ID']>>;
+};
+
+/**
+ * Which FacetValues are present in the products returned
+ * by the search, and in what quantity.
+ */
+export type FacetValueResult = {
+  __typename?: 'FacetValueResult';
+  facetValue: FacetValue;
+  count: Scalars['Int'];
+};
+
+export type FacetValueTranslation = {
+  __typename?: 'FacetValueTranslation';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  name: Scalars['String'];
+};
+
+export type FloatCustomFieldConfig = CustomField & {
+  __typename?: 'FloatCustomFieldConfig';
+  name: Scalars['String'];
+  type: Scalars['String'];
+  list: Scalars['Boolean'];
+  label?: Maybe<Array<LocalizedString>>;
+  description?: Maybe<Array<LocalizedString>>;
+  readonly?: Maybe<Scalars['Boolean']>;
+  internal?: Maybe<Scalars['Boolean']>;
+  nullable?: Maybe<Scalars['Boolean']>;
+  min?: Maybe<Scalars['Float']>;
+  max?: Maybe<Scalars['Float']>;
+  step?: Maybe<Scalars['Float']>;
+  ui?: Maybe<Scalars['JSON']>;
+};
+
+export type Fulfillment = Node & {
+  __typename?: 'Fulfillment';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  orderItems: Array<OrderItem>;
+  state: Scalars['String'];
+  method: Scalars['String'];
+  trackingCode?: Maybe<Scalars['String']>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export enum GlobalFlag {
+  TRUE = 'TRUE',
+  FALSE = 'FALSE',
+  INHERIT = 'INHERIT'
+}
+
+export type HistoryEntry = Node & {
+  __typename?: 'HistoryEntry';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  type: HistoryEntryType;
+  data: Scalars['JSON'];
+};
+
+export type HistoryEntryFilterParameter = {
+  id?: Maybe<IdOperators>;
+  createdAt?: Maybe<DateOperators>;
+  updatedAt?: Maybe<DateOperators>;
+  type?: Maybe<StringOperators>;
+};
+
+export type HistoryEntryList = PaginatedList & {
+  __typename?: 'HistoryEntryList';
+  items: Array<HistoryEntry>;
+  totalItems: Scalars['Int'];
+};
+
+export type HistoryEntryListOptions = {
+  /** Skips the first n results, for use in pagination */
+  skip?: Maybe<Scalars['Int']>;
+  /** Takes n results, for use in pagination */
+  take?: Maybe<Scalars['Int']>;
+  /** Specifies which properties to sort the results by */
+  sort?: Maybe<HistoryEntrySortParameter>;
+  /** Allows the results to be filtered */
+  filter?: Maybe<HistoryEntryFilterParameter>;
+  /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+  filterOperator?: Maybe<LogicalOperator>;
+};
+
+export type HistoryEntrySortParameter = {
+  id?: Maybe<SortOrder>;
+  createdAt?: Maybe<SortOrder>;
+  updatedAt?: Maybe<SortOrder>;
+};
+
+export enum HistoryEntryType {
+  CUSTOMER_REGISTERED = 'CUSTOMER_REGISTERED',
+  CUSTOMER_VERIFIED = 'CUSTOMER_VERIFIED',
+  CUSTOMER_DETAIL_UPDATED = 'CUSTOMER_DETAIL_UPDATED',
+  CUSTOMER_ADDED_TO_GROUP = 'CUSTOMER_ADDED_TO_GROUP',
+  CUSTOMER_REMOVED_FROM_GROUP = 'CUSTOMER_REMOVED_FROM_GROUP',
+  CUSTOMER_ADDRESS_CREATED = 'CUSTOMER_ADDRESS_CREATED',
+  CUSTOMER_ADDRESS_UPDATED = 'CUSTOMER_ADDRESS_UPDATED',
+  CUSTOMER_ADDRESS_DELETED = 'CUSTOMER_ADDRESS_DELETED',
+  CUSTOMER_PASSWORD_UPDATED = 'CUSTOMER_PASSWORD_UPDATED',
+  CUSTOMER_PASSWORD_RESET_REQUESTED = 'CUSTOMER_PASSWORD_RESET_REQUESTED',
+  CUSTOMER_PASSWORD_RESET_VERIFIED = 'CUSTOMER_PASSWORD_RESET_VERIFIED',
+  CUSTOMER_EMAIL_UPDATE_REQUESTED = 'CUSTOMER_EMAIL_UPDATE_REQUESTED',
+  CUSTOMER_EMAIL_UPDATE_VERIFIED = 'CUSTOMER_EMAIL_UPDATE_VERIFIED',
+  CUSTOMER_NOTE = 'CUSTOMER_NOTE',
+  ORDER_STATE_TRANSITION = 'ORDER_STATE_TRANSITION',
+  ORDER_PAYMENT_TRANSITION = 'ORDER_PAYMENT_TRANSITION',
+  ORDER_FULFILLMENT = 'ORDER_FULFILLMENT',
+  ORDER_CANCELLATION = 'ORDER_CANCELLATION',
+  ORDER_REFUND_TRANSITION = 'ORDER_REFUND_TRANSITION',
+  ORDER_FULFILLMENT_TRANSITION = 'ORDER_FULFILLMENT_TRANSITION',
+  ORDER_NOTE = 'ORDER_NOTE',
+  ORDER_COUPON_APPLIED = 'ORDER_COUPON_APPLIED',
+  ORDER_COUPON_REMOVED = 'ORDER_COUPON_REMOVED',
+  ORDER_MODIFIED = 'ORDER_MODIFIED'
+}
+
+/** Operators for filtering on a list of ID fields */
+export type IdListOperators = {
+  inList: Scalars['ID'];
+};
+
+/** Operators for filtering on an ID field */
+export type IdOperators = {
+  eq?: Maybe<Scalars['String']>;
+  notEq?: Maybe<Scalars['String']>;
+  in?: Maybe<Array<Scalars['String']>>;
+  notIn?: Maybe<Array<Scalars['String']>>;
+};
+
+/**
+ * Returned if the token used to change a Customer's email address is valid, but has
+ * expired according to the `verificationTokenDuration` setting in the AuthOptions.
+ */
+export type IdentifierChangeTokenExpiredError = ErrorResult & {
+  __typename?: 'IdentifierChangeTokenExpiredError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+/**
+ * Returned if the token used to change a Customer's email address is either
+ * invalid or does not match any expected tokens.
+ */
+export type IdentifierChangeTokenInvalidError = ErrorResult & {
+  __typename?: 'IdentifierChangeTokenInvalidError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+/** Returned when attempting to add a Payment using a PaymentMethod for which the Order is not eligible. */
+export type IneligiblePaymentMethodError = ErrorResult & {
+  __typename?: 'IneligiblePaymentMethodError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+  eligibilityCheckerMessage?: Maybe<Scalars['String']>;
+};
+
+/** 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';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+  quantityAvailable: Scalars['Int'];
+  order: Order;
+};
+
+export type IntCustomFieldConfig = CustomField & {
+  __typename?: 'IntCustomFieldConfig';
+  name: Scalars['String'];
+  type: Scalars['String'];
+  list: Scalars['Boolean'];
+  label?: Maybe<Array<LocalizedString>>;
+  description?: Maybe<Array<LocalizedString>>;
+  readonly?: Maybe<Scalars['Boolean']>;
+  internal?: Maybe<Scalars['Boolean']>;
+  nullable?: Maybe<Scalars['Boolean']>;
+  min?: Maybe<Scalars['Int']>;
+  max?: Maybe<Scalars['Int']>;
+  step?: Maybe<Scalars['Int']>;
+  ui?: Maybe<Scalars['JSON']>;
+};
+
+/** Returned if the user authentication credentials are not valid */
+export type InvalidCredentialsError = ErrorResult & {
+  __typename?: 'InvalidCredentialsError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+  authenticationError: Scalars['String'];
+};
+
+
+/**
+ * @description
+ * Languages in the form of a ISO 639-1 language code with optional
+ * region or script modifier (e.g. de_AT). The selection available is based
+ * on the [Unicode CLDR summary list](https://unicode-org.github.io/cldr-staging/charts/37/summary/root.html)
+ * and includes the major spoken languages of the world and any widely-used variants.
+ *
+ * @docsCategory common
+ */
+export enum LanguageCode {
+  /** Afrikaans */
+  af = 'af',
+  /** Akan */
+  ak = 'ak',
+  /** Albanian */
+  sq = 'sq',
+  /** Amharic */
+  am = 'am',
+  /** Arabic */
+  ar = 'ar',
+  /** Armenian */
+  hy = 'hy',
+  /** Assamese */
+  as = 'as',
+  /** Azerbaijani */
+  az = 'az',
+  /** Bambara */
+  bm = 'bm',
+  /** Bangla */
+  bn = 'bn',
+  /** Basque */
+  eu = 'eu',
+  /** Belarusian */
+  be = 'be',
+  /** Bosnian */
+  bs = 'bs',
+  /** Breton */
+  br = 'br',
+  /** Bulgarian */
+  bg = 'bg',
+  /** Burmese */
+  my = 'my',
+  /** Catalan */
+  ca = 'ca',
+  /** Chechen */
+  ce = 'ce',
+  /** Chinese */
+  zh = 'zh',
+  /** Simplified Chinese */
+  zh_Hans = 'zh_Hans',
+  /** Traditional Chinese */
+  zh_Hant = 'zh_Hant',
+  /** Church Slavic */
+  cu = 'cu',
+  /** Cornish */
+  kw = 'kw',
+  /** Corsican */
+  co = 'co',
+  /** Croatian */
+  hr = 'hr',
+  /** Czech */
+  cs = 'cs',
+  /** Danish */
+  da = 'da',
+  /** Dutch */
+  nl = 'nl',
+  /** Flemish */
+  nl_BE = 'nl_BE',
+  /** Dzongkha */
+  dz = 'dz',
+  /** English */
+  en = 'en',
+  /** Australian English */
+  en_AU = 'en_AU',
+  /** Canadian English */
+  en_CA = 'en_CA',
+  /** British English */
+  en_GB = 'en_GB',
+  /** American English */
+  en_US = 'en_US',
+  /** Esperanto */
+  eo = 'eo',
+  /** Estonian */
+  et = 'et',
+  /** Ewe */
+  ee = 'ee',
+  /** Faroese */
+  fo = 'fo',
+  /** Finnish */
+  fi = 'fi',
+  /** French */
+  fr = 'fr',
+  /** Canadian French */
+  fr_CA = 'fr_CA',
+  /** Swiss French */
+  fr_CH = 'fr_CH',
+  /** Fulah */
+  ff = 'ff',
+  /** Galician */
+  gl = 'gl',
+  /** Ganda */
+  lg = 'lg',
+  /** Georgian */
+  ka = 'ka',
+  /** German */
+  de = 'de',
+  /** Austrian German */
+  de_AT = 'de_AT',
+  /** Swiss High German */
+  de_CH = 'de_CH',
+  /** Greek */
+  el = 'el',
+  /** Gujarati */
+  gu = 'gu',
+  /** Haitian Creole */
+  ht = 'ht',
+  /** Hausa */
+  ha = 'ha',
+  /** Hebrew */
+  he = 'he',
+  /** Hindi */
+  hi = 'hi',
+  /** Hungarian */
+  hu = 'hu',
+  /** Icelandic */
+  is = 'is',
+  /** Igbo */
+  ig = 'ig',
+  /** Indonesian */
+  id = 'id',
+  /** Interlingua */
+  ia = 'ia',
+  /** Irish */
+  ga = 'ga',
+  /** Italian */
+  it = 'it',
+  /** Japanese */
+  ja = 'ja',
+  /** Javanese */
+  jv = 'jv',
+  /** Kalaallisut */
+  kl = 'kl',
+  /** Kannada */
+  kn = 'kn',
+  /** Kashmiri */
+  ks = 'ks',
+  /** Kazakh */
+  kk = 'kk',
+  /** Khmer */
+  km = 'km',
+  /** Kikuyu */
+  ki = 'ki',
+  /** Kinyarwanda */
+  rw = 'rw',
+  /** Korean */
+  ko = 'ko',
+  /** Kurdish */
+  ku = 'ku',
+  /** Kyrgyz */
+  ky = 'ky',
+  /** Lao */
+  lo = 'lo',
+  /** Latin */
+  la = 'la',
+  /** Latvian */
+  lv = 'lv',
+  /** Lingala */
+  ln = 'ln',
+  /** Lithuanian */
+  lt = 'lt',
+  /** Luba-Katanga */
+  lu = 'lu',
+  /** Luxembourgish */
+  lb = 'lb',
+  /** Macedonian */
+  mk = 'mk',
+  /** Malagasy */
+  mg = 'mg',
+  /** Malay */
+  ms = 'ms',
+  /** Malayalam */
+  ml = 'ml',
+  /** Maltese */
+  mt = 'mt',
+  /** Manx */
+  gv = 'gv',
+  /** Maori */
+  mi = 'mi',
+  /** Marathi */
+  mr = 'mr',
+  /** Mongolian */
+  mn = 'mn',
+  /** Nepali */
+  ne = 'ne',
+  /** North Ndebele */
+  nd = 'nd',
+  /** Northern Sami */
+  se = 'se',
+  /** Norwegian Bokmål */
+  nb = 'nb',
+  /** Norwegian Nynorsk */
+  nn = 'nn',
+  /** Nyanja */
+  ny = 'ny',
+  /** Odia */
+  or = 'or',
+  /** Oromo */
+  om = 'om',
+  /** Ossetic */
+  os = 'os',
+  /** Pashto */
+  ps = 'ps',
+  /** Persian */
+  fa = 'fa',
+  /** Dari */
+  fa_AF = 'fa_AF',
+  /** Polish */
+  pl = 'pl',
+  /** Portuguese */
+  pt = 'pt',
+  /** Brazilian Portuguese */
+  pt_BR = 'pt_BR',
+  /** European Portuguese */
+  pt_PT = 'pt_PT',
+  /** Punjabi */
+  pa = 'pa',
+  /** Quechua */
+  qu = 'qu',
+  /** Romanian */
+  ro = 'ro',
+  /** Moldavian */
+  ro_MD = 'ro_MD',
+  /** Romansh */
+  rm = 'rm',
+  /** Rundi */
+  rn = 'rn',
+  /** Russian */
+  ru = 'ru',
+  /** Samoan */
+  sm = 'sm',
+  /** Sango */
+  sg = 'sg',
+  /** Sanskrit */
+  sa = 'sa',
+  /** Scottish Gaelic */
+  gd = 'gd',
+  /** Serbian */
+  sr = 'sr',
+  /** Shona */
+  sn = 'sn',
+  /** Sichuan Yi */
+  ii = 'ii',
+  /** Sindhi */
+  sd = 'sd',
+  /** Sinhala */
+  si = 'si',
+  /** Slovak */
+  sk = 'sk',
+  /** Slovenian */
+  sl = 'sl',
+  /** Somali */
+  so = 'so',
+  /** Southern Sotho */
+  st = 'st',
+  /** Spanish */
+  es = 'es',
+  /** European Spanish */
+  es_ES = 'es_ES',
+  /** Mexican Spanish */
+  es_MX = 'es_MX',
+  /** Sundanese */
+  su = 'su',
+  /** Swahili */
+  sw = 'sw',
+  /** Congo Swahili */
+  sw_CD = 'sw_CD',
+  /** Swedish */
+  sv = 'sv',
+  /** Tajik */
+  tg = 'tg',
+  /** Tamil */
+  ta = 'ta',
+  /** Tatar */
+  tt = 'tt',
+  /** Telugu */
+  te = 'te',
+  /** Thai */
+  th = 'th',
+  /** Tibetan */
+  bo = 'bo',
+  /** Tigrinya */
+  ti = 'ti',
+  /** Tongan */
+  to = 'to',
+  /** Turkish */
+  tr = 'tr',
+  /** Turkmen */
+  tk = 'tk',
+  /** Ukrainian */
+  uk = 'uk',
+  /** Urdu */
+  ur = 'ur',
+  /** Uyghur */
+  ug = 'ug',
+  /** Uzbek */
+  uz = 'uz',
+  /** Vietnamese */
+  vi = 'vi',
+  /** Volapük */
+  vo = 'vo',
+  /** Welsh */
+  cy = 'cy',
+  /** Western Frisian */
+  fy = 'fy',
+  /** Wolof */
+  wo = 'wo',
+  /** Xhosa */
+  xh = 'xh',
+  /** Yiddish */
+  yi = 'yi',
+  /** Yoruba */
+  yo = 'yo',
+  /** Zulu */
+  zu = 'zu'
+}
+
+export type LocaleStringCustomFieldConfig = CustomField & {
+  __typename?: 'LocaleStringCustomFieldConfig';
+  name: Scalars['String'];
+  type: Scalars['String'];
+  list: Scalars['Boolean'];
+  length?: Maybe<Scalars['Int']>;
+  label?: Maybe<Array<LocalizedString>>;
+  description?: Maybe<Array<LocalizedString>>;
+  readonly?: Maybe<Scalars['Boolean']>;
+  internal?: Maybe<Scalars['Boolean']>;
+  nullable?: Maybe<Scalars['Boolean']>;
+  pattern?: Maybe<Scalars['String']>;
+  ui?: Maybe<Scalars['JSON']>;
+};
+
+export type LocalizedString = {
+  __typename?: 'LocalizedString';
+  languageCode: LanguageCode;
+  value: Scalars['String'];
+};
+
+export enum LogicalOperator {
+  AND = 'AND',
+  OR = 'OR'
+}
+
+/** Returned when attempting to register or verify a customer account without a password, when one is required. */
+export type MissingPasswordError = ErrorResult & {
+  __typename?: 'MissingPasswordError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+export type MolliePaymentIntent = {
+  __typename?: 'MolliePaymentIntent';
+  url: Scalars['String'];
+};
+
+export type MolliePaymentIntentError = ErrorResult & {
+  __typename?: 'MolliePaymentIntentError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+export type MolliePaymentIntentInput = {
+  paymentMethodCode: Scalars['String'];
+};
+
+export type MolliePaymentIntentResult = MolliePaymentIntent | MolliePaymentIntentError;
+
+export type Mutation = {
+  __typename?: 'Mutation';
+  /** Adds an item to the order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available. */
+  addItemToOrder: UpdateOrderItemsResult;
+  /** Add a Payment to the Order */
+  addPaymentToOrder: AddPaymentToOrderResult;
+  /** Adjusts an OrderLine. If custom fields are defined on the OrderLine entity, a third argument 'customFields' of type `OrderLineCustomFieldsInput` will be available. */
+  adjustOrderLine: UpdateOrderItemsResult;
+  /** Applies the given coupon code to the active Order */
+  applyCouponCode: ApplyCouponCodeResult;
+  /** Authenticates the user using a named authentication strategy */
+  authenticate: AuthenticationResult;
+  /** Create a new Customer Address */
+  createCustomerAddress: Address;
+  createMolliePaymentIntent: MolliePaymentIntentResult;
+  /** Delete an existing Address */
+  deleteCustomerAddress: Success;
+  /** Authenticates the user using the native authentication strategy. This mutation is an alias for `authenticate({ native: { ... }})` */
+  login: NativeAuthenticationResult;
+  /** End the current authenticated session */
+  logout: Success;
+  /** Regenerate and send a verification token for a new Customer registration. Only applicable if `authOptions.requireVerification` is set to true. */
+  refreshCustomerVerification: RefreshCustomerVerificationResult;
+  /**
+   * Register a Customer account with the given credentials. There are three possible registration flows:
+   *
+   * _If `authOptions.requireVerification` is set to `true`:_
+   *
+   * 1. **The Customer is registered _with_ a password**. A verificationToken will be created (and typically emailed to the Customer). That
+   *    verificationToken would then be passed to the `verifyCustomerAccount` mutation _without_ a password. The Customer is then
+   *    verified and authenticated in one step.
+   * 2. **The Customer is registered _without_ a password**. A verificationToken will be created (and typically emailed to the Customer). That
+   *    verificationToken would then be passed to the `verifyCustomerAccount` mutation _with_ the chosen password of the Customer. The Customer is then
+   *    verified and authenticated in one step.
+   *
+   * _If `authOptions.requireVerification` is set to `false`:_
+   *
+   * 3. The Customer _must_ be registered _with_ a password. No further action is needed - the Customer is able to authenticate immediately.
+   */
+  registerCustomerAccount: RegisterCustomerAccountResult;
+  /** Remove all OrderLine from the Order */
+  removeAllOrderLines: RemoveOrderItemsResult;
+  /** Removes the given coupon code from the active Order */
+  removeCouponCode?: Maybe<Order>;
+  /** Remove an OrderLine from the Order */
+  removeOrderLine: RemoveOrderItemsResult;
+  /** Requests a password reset email to be sent */
+  requestPasswordReset?: Maybe<RequestPasswordResetResult>;
+  /**
+   * Request to update the emailAddress of the active Customer. If `authOptions.requireVerification` is enabled
+   * (as is the default), then the `identifierChangeToken` will be assigned to the current User and
+   * a IdentifierChangeRequestEvent will be raised. This can then be used e.g. by the EmailPlugin to email
+   * that verification token to the Customer, which is then used to verify the change of email address.
+   */
+  requestUpdateCustomerEmailAddress: RequestUpdateCustomerEmailAddressResult;
+  /** Resets a Customer's password based on the provided token */
+  resetPassword: ResetPasswordResult;
+  /** Set the Customer for the Order. Required only if the Customer is not currently logged in */
+  setCustomerForOrder: SetCustomerForOrderResult;
+  /** Sets the billing address for this order */
+  setOrderBillingAddress: ActiveOrderResult;
+  /** Allows any custom fields to be set for the active order */
+  setOrderCustomFields: ActiveOrderResult;
+  /** Sets the shipping address for this order */
+  setOrderShippingAddress: ActiveOrderResult;
+  /** Sets the shipping method by id, which can be obtained with the `eligibleShippingMethods` query */
+  setOrderShippingMethod: SetOrderShippingMethodResult;
+  /** Transitions an Order to a new state. Valid next states can be found by querying `nextOrderStates` */
+  transitionOrderToState?: Maybe<TransitionOrderToStateResult>;
+  /** Update an existing Customer */
+  updateCustomer: Customer;
+  /** Update an existing Address */
+  updateCustomerAddress: Address;
+  /**
+   * Confirm the update of the emailAddress with the provided token, which has been generated by the
+   * `requestUpdateCustomerEmailAddress` mutation.
+   */
+  updateCustomerEmailAddress: UpdateCustomerEmailAddressResult;
+  /** Update the password of the active Customer */
+  updateCustomerPassword: UpdateCustomerPasswordResult;
+  /**
+   * Verify a Customer email address with the token sent to that address. Only applicable if `authOptions.requireVerification` is set to true.
+   *
+   * If the Customer was not registered with a password in the `registerCustomerAccount` mutation, the password _must_ be
+   * provided here.
+   */
+  verifyCustomerAccount: VerifyCustomerAccountResult;
+};
+
+
+export type MutationAddItemToOrderArgs = {
+  productVariantId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationAddPaymentToOrderArgs = {
+  input: PaymentInput;
+};
+
+
+export type MutationAdjustOrderLineArgs = {
+  orderLineId: Scalars['ID'];
+  quantity: Scalars['Int'];
+};
+
+
+export type MutationApplyCouponCodeArgs = {
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationAuthenticateArgs = {
+  input: AuthenticationInput;
+  rememberMe?: Maybe<Scalars['Boolean']>;
+};
+
+
+export type MutationCreateCustomerAddressArgs = {
+  input: CreateAddressInput;
+};
+
+
+export type MutationCreateMolliePaymentIntentArgs = {
+  input: MolliePaymentIntentInput;
+};
+
+
+export type MutationDeleteCustomerAddressArgs = {
+  id: Scalars['ID'];
+};
+
+
+export type MutationLoginArgs = {
+  username: Scalars['String'];
+  password: Scalars['String'];
+  rememberMe?: Maybe<Scalars['Boolean']>;
+};
+
+
+export type MutationRefreshCustomerVerificationArgs = {
+  emailAddress: Scalars['String'];
+};
+
+
+export type MutationRegisterCustomerAccountArgs = {
+  input: RegisterCustomerInput;
+};
+
+
+export type MutationRemoveCouponCodeArgs = {
+  couponCode: Scalars['String'];
+};
+
+
+export type MutationRemoveOrderLineArgs = {
+  orderLineId: Scalars['ID'];
+};
+
+
+export type MutationRequestPasswordResetArgs = {
+  emailAddress: Scalars['String'];
+};
+
+
+export type MutationRequestUpdateCustomerEmailAddressArgs = {
+  password: Scalars['String'];
+  newEmailAddress: Scalars['String'];
+};
+
+
+export type MutationResetPasswordArgs = {
+  token: Scalars['String'];
+  password: Scalars['String'];
+};
+
+
+export type MutationSetCustomerForOrderArgs = {
+  input: CreateCustomerInput;
+};
+
+
+export type MutationSetOrderBillingAddressArgs = {
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetOrderCustomFieldsArgs = {
+  input: UpdateOrderInput;
+};
+
+
+export type MutationSetOrderShippingAddressArgs = {
+  input: CreateAddressInput;
+};
+
+
+export type MutationSetOrderShippingMethodArgs = {
+  shippingMethodId: Scalars['ID'];
+};
+
+
+export type MutationTransitionOrderToStateArgs = {
+  state: Scalars['String'];
+};
+
+
+export type MutationUpdateCustomerArgs = {
+  input: UpdateCustomerInput;
+};
+
+
+export type MutationUpdateCustomerAddressArgs = {
+  input: UpdateAddressInput;
+};
+
+
+export type MutationUpdateCustomerEmailAddressArgs = {
+  token: Scalars['String'];
+};
+
+
+export type MutationUpdateCustomerPasswordArgs = {
+  currentPassword: Scalars['String'];
+  newPassword: Scalars['String'];
+};
+
+
+export type MutationVerifyCustomerAccountArgs = {
+  token: Scalars['String'];
+  password?: Maybe<Scalars['String']>;
+};
+
+export type NativeAuthInput = {
+  username: Scalars['String'];
+  password: Scalars['String'];
+};
+
+/** Returned when attempting an operation that relies on the NativeAuthStrategy, if that strategy is not configured. */
+export type NativeAuthStrategyError = ErrorResult & {
+  __typename?: 'NativeAuthStrategyError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+export type NativeAuthenticationResult = CurrentUser | InvalidCredentialsError | NotVerifiedError | NativeAuthStrategyError;
+
+/** Returned when attempting to set a negative OrderLine quantity. */
+export type NegativeQuantityError = ErrorResult & {
+  __typename?: 'NegativeQuantityError';
+  errorCode: ErrorCode;
+  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'];
+};
+
+export type Node = {
+  id: Scalars['ID'];
+};
+
+/**
+ * Returned if `authOptions.requireVerification` is set to `true` (which is the default)
+ * and an unverified user attempts to authenticate.
+ */
+export type NotVerifiedError = ErrorResult & {
+  __typename?: 'NotVerifiedError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+/** Operators for filtering on a list of Number fields */
+export type NumberListOperators = {
+  inList: Scalars['Float'];
+};
+
+/** Operators for filtering on a Int or Float field */
+export type NumberOperators = {
+  eq?: Maybe<Scalars['Float']>;
+  lt?: Maybe<Scalars['Float']>;
+  lte?: Maybe<Scalars['Float']>;
+  gt?: Maybe<Scalars['Float']>;
+  gte?: Maybe<Scalars['Float']>;
+  between?: Maybe<NumberRange>;
+};
+
+export type NumberRange = {
+  start: Scalars['Float'];
+  end: Scalars['Float'];
+};
+
+export type Order = Node & {
+  __typename?: 'Order';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  /**
+   * The date & time that the Order was placed, i.e. the Customer
+   * completed the checkout and the Order is no longer "active"
+   */
+  orderPlacedAt?: Maybe<Scalars['DateTime']>;
+  /** A unique code for the Order */
+  code: Scalars['String'];
+  state: Scalars['String'];
+  /** An order is active as long as the payment process has not been completed */
+  active: Scalars['Boolean'];
+  customer?: Maybe<Customer>;
+  shippingAddress?: Maybe<OrderAddress>;
+  billingAddress?: Maybe<OrderAddress>;
+  lines: Array<OrderLine>;
+  /**
+   * Surcharges are arbitrary modifications to the Order total which are neither
+   * ProductVariants nor discounts resulting from applied Promotions. For example,
+   * one-off discounts based on customer interaction, or surcharges based on payment
+   * methods.
+   */
+  surcharges: Array<Surcharge>;
+  discounts: Array<Discount>;
+  /** An array of all coupon codes applied to the Order */
+  couponCodes: Array<Scalars['String']>;
+  /** Promotions applied to the order. Only gets populated after the payment process has completed. */
+  promotions: Array<Promotion>;
+  payments?: Maybe<Array<Payment>>;
+  fulfillments?: Maybe<Array<Fulfillment>>;
+  totalQuantity: Scalars['Int'];
+  /**
+   * The subTotal is the total of all OrderLines in the Order. This figure also includes any Order-level
+   * discounts which have been prorated (proportionally distributed) amongst the OrderItems.
+   * To get a total of all OrderLines which does not account for prorated discounts, use the
+   * sum of `OrderLine.discountedLinePrice` values.
+   */
+  subTotal: Scalars['Int'];
+  /** Same as subTotal, but inclusive of tax */
+  subTotalWithTax: Scalars['Int'];
+  currencyCode: CurrencyCode;
+  shippingLines: Array<ShippingLine>;
+  shipping: Scalars['Int'];
+  shippingWithTax: Scalars['Int'];
+  /** Equal to subTotal plus shipping */
+  total: Scalars['Int'];
+  /** The final payable amount. Equal to subTotalWithTax plus shippingWithTax */
+  totalWithTax: Scalars['Int'];
+  /** A summary of the taxes being applied to this Order */
+  taxSummary: Array<OrderTaxSummary>;
+  history: HistoryEntryList;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+
+export type OrderHistoryArgs = {
+  options?: Maybe<HistoryEntryListOptions>;
+};
+
+export type OrderAddress = {
+  __typename?: 'OrderAddress';
+  fullName?: Maybe<Scalars['String']>;
+  company?: Maybe<Scalars['String']>;
+  streetLine1?: Maybe<Scalars['String']>;
+  streetLine2?: Maybe<Scalars['String']>;
+  city?: Maybe<Scalars['String']>;
+  province?: Maybe<Scalars['String']>;
+  postalCode?: Maybe<Scalars['String']>;
+  country?: Maybe<Scalars['String']>;
+  countryCode?: Maybe<Scalars['String']>;
+  phoneNumber?: Maybe<Scalars['String']>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type OrderFilterParameter = {
+  id?: Maybe<IdOperators>;
+  createdAt?: Maybe<DateOperators>;
+  updatedAt?: Maybe<DateOperators>;
+  orderPlacedAt?: Maybe<DateOperators>;
+  code?: Maybe<StringOperators>;
+  state?: Maybe<StringOperators>;
+  active?: Maybe<BooleanOperators>;
+  totalQuantity?: Maybe<NumberOperators>;
+  subTotal?: Maybe<NumberOperators>;
+  subTotalWithTax?: Maybe<NumberOperators>;
+  currencyCode?: Maybe<StringOperators>;
+  shipping?: Maybe<NumberOperators>;
+  shippingWithTax?: Maybe<NumberOperators>;
+  total?: Maybe<NumberOperators>;
+  totalWithTax?: Maybe<NumberOperators>;
+};
+
+export type OrderItem = Node & {
+  __typename?: 'OrderItem';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  cancelled: Scalars['Boolean'];
+  /** The price of a single unit, excluding tax and discounts */
+  unitPrice: Scalars['Int'];
+  /** The price of a single unit, including tax but excluding discounts */
+  unitPriceWithTax: Scalars['Int'];
+  /**
+   * The price of a single unit including discounts, excluding tax.
+   *
+   * If Order-level discounts have been applied, this will not be the
+   * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+   * correct price to display to customers to avoid confusion
+   * about the internal handling of distributed Order-level discounts.
+   */
+  discountedUnitPrice: Scalars['Int'];
+  /** The price of a single unit including discounts and tax */
+  discountedUnitPriceWithTax: Scalars['Int'];
+  /**
+   * The actual unit price, taking into account both item discounts _and_ prorated (proportionally-distributed)
+   * Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax
+   * and refund calculations.
+   */
+  proratedUnitPrice: Scalars['Int'];
+  /** The proratedUnitPrice including tax */
+  proratedUnitPriceWithTax: Scalars['Int'];
+  unitTax: Scalars['Int'];
+  taxRate: Scalars['Float'];
+  adjustments: Array<Adjustment>;
+  taxLines: Array<TaxLine>;
+  fulfillment?: Maybe<Fulfillment>;
+  refundId?: Maybe<Scalars['ID']>;
+};
+
+/** Returned when the maximum order size limit has been reached. */
+export type OrderLimitError = ErrorResult & {
+  __typename?: 'OrderLimitError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+  maxItems: Scalars['Int'];
+};
+
+export type OrderLine = Node & {
+  __typename?: 'OrderLine';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  productVariant: ProductVariant;
+  featuredAsset?: Maybe<Asset>;
+  /** The price of a single unit, excluding tax and discounts */
+  unitPrice: Scalars['Int'];
+  /** The price of a single unit, including tax but excluding discounts */
+  unitPriceWithTax: Scalars['Int'];
+  /** Non-zero if the unitPrice has changed since it was initially added to Order */
+  unitPriceChangeSinceAdded: Scalars['Int'];
+  /** Non-zero if the unitPriceWithTax has changed since it was initially added to Order */
+  unitPriceWithTaxChangeSinceAdded: Scalars['Int'];
+  /**
+   * The price of a single unit including discounts, excluding tax.
+   *
+   * If Order-level discounts have been applied, this will not be the
+   * actual taxable unit price (see `proratedUnitPrice`), but is generally the
+   * correct price to display to customers to avoid confusion
+   * about the internal handling of distributed Order-level discounts.
+   */
+  discountedUnitPrice: Scalars['Int'];
+  /** The price of a single unit including discounts and tax */
+  discountedUnitPriceWithTax: Scalars['Int'];
+  /**
+   * The actual unit price, taking into account both item discounts _and_ prorated (proportionally-distributed)
+   * Order-level discounts. This value is the true economic value of the OrderItem, and is used in tax
+   * and refund calculations.
+   */
+  proratedUnitPrice: Scalars['Int'];
+  /** The proratedUnitPrice including tax */
+  proratedUnitPriceWithTax: Scalars['Int'];
+  quantity: Scalars['Int'];
+  items: Array<OrderItem>;
+  taxRate: Scalars['Float'];
+  /** The total price of the line excluding tax and discounts. */
+  linePrice: Scalars['Int'];
+  /** The total price of the line including tax but excluding discounts. */
+  linePriceWithTax: Scalars['Int'];
+  /** The price of the line including discounts, excluding tax */
+  discountedLinePrice: Scalars['Int'];
+  /** The price of the line including discounts and tax */
+  discountedLinePriceWithTax: Scalars['Int'];
+  /**
+   * The actual line price, taking into account both item discounts _and_ prorated (proportionally-distributed)
+   * Order-level discounts. This value is the true economic value of the OrderLine, and is used in tax
+   * and refund calculations.
+   */
+  proratedLinePrice: Scalars['Int'];
+  /** The proratedLinePrice including tax */
+  proratedLinePriceWithTax: Scalars['Int'];
+  /** The total tax on this line */
+  lineTax: Scalars['Int'];
+  discounts: Array<Discount>;
+  taxLines: Array<TaxLine>;
+  order: Order;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type OrderList = PaginatedList & {
+  __typename?: 'OrderList';
+  items: Array<Order>;
+  totalItems: Scalars['Int'];
+};
+
+export type OrderListOptions = {
+  /** Skips the first n results, for use in pagination */
+  skip?: Maybe<Scalars['Int']>;
+  /** Takes n results, for use in pagination */
+  take?: Maybe<Scalars['Int']>;
+  /** Specifies which properties to sort the results by */
+  sort?: Maybe<OrderSortParameter>;
+  /** Allows the results to be filtered */
+  filter?: Maybe<OrderFilterParameter>;
+  /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+  filterOperator?: Maybe<LogicalOperator>;
+};
+
+/** 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 add a Payment to an Order that is not in the `ArrangingPayment` state. */
+export type OrderPaymentStateError = ErrorResult & {
+  __typename?: 'OrderPaymentStateError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+export type OrderSortParameter = {
+  id?: Maybe<SortOrder>;
+  createdAt?: Maybe<SortOrder>;
+  updatedAt?: Maybe<SortOrder>;
+  orderPlacedAt?: Maybe<SortOrder>;
+  code?: Maybe<SortOrder>;
+  state?: Maybe<SortOrder>;
+  totalQuantity?: Maybe<SortOrder>;
+  subTotal?: Maybe<SortOrder>;
+  subTotalWithTax?: Maybe<SortOrder>;
+  shipping?: Maybe<SortOrder>;
+  shippingWithTax?: Maybe<SortOrder>;
+  total?: Maybe<SortOrder>;
+  totalWithTax?: Maybe<SortOrder>;
+};
+
+/** Returned if there is an error in transitioning the Order state */
+export type OrderStateTransitionError = ErrorResult & {
+  __typename?: 'OrderStateTransitionError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+  transitionError: Scalars['String'];
+  fromState: Scalars['String'];
+  toState: Scalars['String'];
+};
+
+/**
+ * A summary of the taxes being applied to this order, grouped
+ * by taxRate.
+ */
+export type OrderTaxSummary = {
+  __typename?: 'OrderTaxSummary';
+  /** A description of this tax */
+  description: Scalars['String'];
+  /** The taxRate as a percentage */
+  taxRate: Scalars['Float'];
+  /** The total net price or OrderItems to which this taxRate applies */
+  taxBase: Scalars['Int'];
+  /** The total tax being applied to the Order at this taxRate */
+  taxTotal: Scalars['Int'];
+};
+
+export type PaginatedList = {
+  items: Array<Node>;
+  totalItems: Scalars['Int'];
+};
+
+/** Returned when attempting to verify a customer account with a password, when a password has already been set. */
+export type PasswordAlreadySetError = ErrorResult & {
+  __typename?: 'PasswordAlreadySetError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+/**
+ * Returned if the token used to reset a Customer's password is valid, but has
+ * expired according to the `verificationTokenDuration` setting in the AuthOptions.
+ */
+export type PasswordResetTokenExpiredError = ErrorResult & {
+  __typename?: 'PasswordResetTokenExpiredError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+/**
+ * Returned if the token used to reset a Customer's password is either
+ * invalid or does not match any expected tokens.
+ */
+export type PasswordResetTokenInvalidError = ErrorResult & {
+  __typename?: 'PasswordResetTokenInvalidError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+/** Returned when attempting to register or verify a customer account where the given password fails password validation. */
+export type PasswordValidationError = ErrorResult & {
+  __typename?: 'PasswordValidationError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+  validationErrorMessage: Scalars['String'];
+};
+
+export type Payment = Node & {
+  __typename?: 'Payment';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  method: Scalars['String'];
+  amount: Scalars['Int'];
+  state: Scalars['String'];
+  transactionId?: Maybe<Scalars['String']>;
+  errorMessage?: Maybe<Scalars['String']>;
+  refunds: Array<Refund>;
+  metadata?: Maybe<Scalars['JSON']>;
+};
+
+/** Returned when a Payment is declined by the payment provider. */
+export type PaymentDeclinedError = ErrorResult & {
+  __typename?: 'PaymentDeclinedError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+  paymentErrorMessage: Scalars['String'];
+};
+
+/** Returned when a Payment fails due to an error. */
+export type PaymentFailedError = ErrorResult & {
+  __typename?: 'PaymentFailedError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+  paymentErrorMessage: Scalars['String'];
+};
+
+/** Passed as input to the `addPaymentToOrder` mutation. */
+export type PaymentInput = {
+  /** This field should correspond to the `code` property of a PaymentMethod. */
+  method: Scalars['String'];
+  /**
+   * This field should contain arbitrary data passed to the specified PaymentMethodHandler's `createPayment()` method
+   * as the "metadata" argument. For example, it could contain an ID for the payment and other
+   * data generated by the payment provider.
+   */
+  metadata: Scalars['JSON'];
+};
+
+export type PaymentMethod = Node & {
+  __typename?: 'PaymentMethod';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  name: Scalars['String'];
+  code: Scalars['String'];
+  description: Scalars['String'];
+  enabled: Scalars['Boolean'];
+  checker?: Maybe<ConfigurableOperation>;
+  handler: ConfigurableOperation;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type PaymentMethodQuote = {
+  __typename?: 'PaymentMethodQuote';
+  id: Scalars['ID'];
+  code: Scalars['String'];
+  name: Scalars['String'];
+  description: Scalars['String'];
+  isEligible: Scalars['Boolean'];
+  eligibilityMessage?: Maybe<Scalars['String']>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+/**
+ * @description
+ * Permissions for administrators and customers. Used to control access to
+ * GraphQL resolvers via the {@link Allow} decorator.
+ *
+ * @docsCategory common
+ */
+export enum Permission {
+  /** Authenticated means simply that the user is logged in */
+  Authenticated = 'Authenticated',
+  /** SuperAdmin has unrestricted access to all operations */
+  SuperAdmin = 'SuperAdmin',
+  /** Owner means the user owns this entity, e.g. a Customer's own Order */
+  Owner = 'Owner',
+  /** Public means any unauthenticated user may perform the operation */
+  Public = 'Public',
+  /** Grants permission to update GlobalSettings */
+  UpdateGlobalSettings = 'UpdateGlobalSettings',
+  /** Grants permission to create Products, Facets, Assets, Collections */
+  CreateCatalog = 'CreateCatalog',
+  /** Grants permission to read Products, Facets, Assets, Collections */
+  ReadCatalog = 'ReadCatalog',
+  /** Grants permission to update Products, Facets, Assets, Collections */
+  UpdateCatalog = 'UpdateCatalog',
+  /** Grants permission to delete Products, Facets, Assets, Collections */
+  DeleteCatalog = 'DeleteCatalog',
+  /** Grants permission to create PaymentMethods, ShippingMethods, TaxCategories, TaxRates, Zones, Countries, System & GlobalSettings */
+  CreateSettings = 'CreateSettings',
+  /** Grants permission to read PaymentMethods, ShippingMethods, TaxCategories, TaxRates, Zones, Countries, System & GlobalSettings */
+  ReadSettings = 'ReadSettings',
+  /** Grants permission to update PaymentMethods, ShippingMethods, TaxCategories, TaxRates, Zones, Countries, System & GlobalSettings */
+  UpdateSettings = 'UpdateSettings',
+  /** Grants permission to delete PaymentMethods, ShippingMethods, TaxCategories, TaxRates, Zones, Countries, System & GlobalSettings */
+  DeleteSettings = 'DeleteSettings',
+  /** Grants permission to create Administrator */
+  CreateAdministrator = 'CreateAdministrator',
+  /** Grants permission to read Administrator */
+  ReadAdministrator = 'ReadAdministrator',
+  /** Grants permission to update Administrator */
+  UpdateAdministrator = 'UpdateAdministrator',
+  /** Grants permission to delete Administrator */
+  DeleteAdministrator = 'DeleteAdministrator',
+  /** Grants permission to create Asset */
+  CreateAsset = 'CreateAsset',
+  /** Grants permission to read Asset */
+  ReadAsset = 'ReadAsset',
+  /** Grants permission to update Asset */
+  UpdateAsset = 'UpdateAsset',
+  /** Grants permission to delete Asset */
+  DeleteAsset = 'DeleteAsset',
+  /** Grants permission to create Channel */
+  CreateChannel = 'CreateChannel',
+  /** Grants permission to read Channel */
+  ReadChannel = 'ReadChannel',
+  /** Grants permission to update Channel */
+  UpdateChannel = 'UpdateChannel',
+  /** Grants permission to delete Channel */
+  DeleteChannel = 'DeleteChannel',
+  /** Grants permission to create Collection */
+  CreateCollection = 'CreateCollection',
+  /** Grants permission to read Collection */
+  ReadCollection = 'ReadCollection',
+  /** Grants permission to update Collection */
+  UpdateCollection = 'UpdateCollection',
+  /** Grants permission to delete Collection */
+  DeleteCollection = 'DeleteCollection',
+  /** Grants permission to create Country */
+  CreateCountry = 'CreateCountry',
+  /** Grants permission to read Country */
+  ReadCountry = 'ReadCountry',
+  /** Grants permission to update Country */
+  UpdateCountry = 'UpdateCountry',
+  /** Grants permission to delete Country */
+  DeleteCountry = 'DeleteCountry',
+  /** Grants permission to create Customer */
+  CreateCustomer = 'CreateCustomer',
+  /** Grants permission to read Customer */
+  ReadCustomer = 'ReadCustomer',
+  /** Grants permission to update Customer */
+  UpdateCustomer = 'UpdateCustomer',
+  /** Grants permission to delete Customer */
+  DeleteCustomer = 'DeleteCustomer',
+  /** Grants permission to create CustomerGroup */
+  CreateCustomerGroup = 'CreateCustomerGroup',
+  /** Grants permission to read CustomerGroup */
+  ReadCustomerGroup = 'ReadCustomerGroup',
+  /** Grants permission to update CustomerGroup */
+  UpdateCustomerGroup = 'UpdateCustomerGroup',
+  /** Grants permission to delete CustomerGroup */
+  DeleteCustomerGroup = 'DeleteCustomerGroup',
+  /** Grants permission to create Facet */
+  CreateFacet = 'CreateFacet',
+  /** Grants permission to read Facet */
+  ReadFacet = 'ReadFacet',
+  /** Grants permission to update Facet */
+  UpdateFacet = 'UpdateFacet',
+  /** Grants permission to delete Facet */
+  DeleteFacet = 'DeleteFacet',
+  /** Grants permission to create Order */
+  CreateOrder = 'CreateOrder',
+  /** Grants permission to read Order */
+  ReadOrder = 'ReadOrder',
+  /** Grants permission to update Order */
+  UpdateOrder = 'UpdateOrder',
+  /** Grants permission to delete Order */
+  DeleteOrder = 'DeleteOrder',
+  /** Grants permission to create PaymentMethod */
+  CreatePaymentMethod = 'CreatePaymentMethod',
+  /** Grants permission to read PaymentMethod */
+  ReadPaymentMethod = 'ReadPaymentMethod',
+  /** Grants permission to update PaymentMethod */
+  UpdatePaymentMethod = 'UpdatePaymentMethod',
+  /** Grants permission to delete PaymentMethod */
+  DeletePaymentMethod = 'DeletePaymentMethod',
+  /** Grants permission to create Product */
+  CreateProduct = 'CreateProduct',
+  /** Grants permission to read Product */
+  ReadProduct = 'ReadProduct',
+  /** Grants permission to update Product */
+  UpdateProduct = 'UpdateProduct',
+  /** Grants permission to delete Product */
+  DeleteProduct = 'DeleteProduct',
+  /** Grants permission to create Promotion */
+  CreatePromotion = 'CreatePromotion',
+  /** Grants permission to read Promotion */
+  ReadPromotion = 'ReadPromotion',
+  /** Grants permission to update Promotion */
+  UpdatePromotion = 'UpdatePromotion',
+  /** Grants permission to delete Promotion */
+  DeletePromotion = 'DeletePromotion',
+  /** Grants permission to create ShippingMethod */
+  CreateShippingMethod = 'CreateShippingMethod',
+  /** Grants permission to read ShippingMethod */
+  ReadShippingMethod = 'ReadShippingMethod',
+  /** Grants permission to update ShippingMethod */
+  UpdateShippingMethod = 'UpdateShippingMethod',
+  /** Grants permission to delete ShippingMethod */
+  DeleteShippingMethod = 'DeleteShippingMethod',
+  /** Grants permission to create Tag */
+  CreateTag = 'CreateTag',
+  /** Grants permission to read Tag */
+  ReadTag = 'ReadTag',
+  /** Grants permission to update Tag */
+  UpdateTag = 'UpdateTag',
+  /** Grants permission to delete Tag */
+  DeleteTag = 'DeleteTag',
+  /** Grants permission to create TaxCategory */
+  CreateTaxCategory = 'CreateTaxCategory',
+  /** Grants permission to read TaxCategory */
+  ReadTaxCategory = 'ReadTaxCategory',
+  /** Grants permission to update TaxCategory */
+  UpdateTaxCategory = 'UpdateTaxCategory',
+  /** Grants permission to delete TaxCategory */
+  DeleteTaxCategory = 'DeleteTaxCategory',
+  /** Grants permission to create TaxRate */
+  CreateTaxRate = 'CreateTaxRate',
+  /** Grants permission to read TaxRate */
+  ReadTaxRate = 'ReadTaxRate',
+  /** Grants permission to update TaxRate */
+  UpdateTaxRate = 'UpdateTaxRate',
+  /** Grants permission to delete TaxRate */
+  DeleteTaxRate = 'DeleteTaxRate',
+  /** Grants permission to create System */
+  CreateSystem = 'CreateSystem',
+  /** Grants permission to read System */
+  ReadSystem = 'ReadSystem',
+  /** Grants permission to update System */
+  UpdateSystem = 'UpdateSystem',
+  /** Grants permission to delete System */
+  DeleteSystem = 'DeleteSystem',
+  /** Grants permission to create Zone */
+  CreateZone = 'CreateZone',
+  /** Grants permission to read Zone */
+  ReadZone = 'ReadZone',
+  /** Grants permission to update Zone */
+  UpdateZone = 'UpdateZone',
+  /** Grants permission to delete Zone */
+  DeleteZone = 'DeleteZone'
+}
+
+/** The price range where the result has more than one price */
+export type PriceRange = {
+  __typename?: 'PriceRange';
+  min: Scalars['Int'];
+  max: Scalars['Int'];
+};
+
+export type Product = Node & {
+  __typename?: 'Product';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  name: Scalars['String'];
+  slug: Scalars['String'];
+  description: Scalars['String'];
+  featuredAsset?: Maybe<Asset>;
+  assets: Array<Asset>;
+  /** Returns all ProductVariants */
+  variants: Array<ProductVariant>;
+  /** Returns a paginated, sortable, filterable list of ProductVariants */
+  variantList: ProductVariantList;
+  optionGroups: Array<ProductOptionGroup>;
+  facetValues: Array<FacetValue>;
+  translations: Array<ProductTranslation>;
+  collections: Array<Collection>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+
+export type ProductVariantListArgs = {
+  options?: Maybe<ProductVariantListOptions>;
+};
+
+export type ProductFilterParameter = {
+  id?: Maybe<IdOperators>;
+  createdAt?: Maybe<DateOperators>;
+  updatedAt?: Maybe<DateOperators>;
+  languageCode?: Maybe<StringOperators>;
+  name?: Maybe<StringOperators>;
+  slug?: Maybe<StringOperators>;
+  description?: Maybe<StringOperators>;
+};
+
+export type ProductList = PaginatedList & {
+  __typename?: 'ProductList';
+  items: Array<Product>;
+  totalItems: Scalars['Int'];
+};
+
+export type ProductListOptions = {
+  /** Skips the first n results, for use in pagination */
+  skip?: Maybe<Scalars['Int']>;
+  /** Takes n results, for use in pagination */
+  take?: Maybe<Scalars['Int']>;
+  /** Specifies which properties to sort the results by */
+  sort?: Maybe<ProductSortParameter>;
+  /** Allows the results to be filtered */
+  filter?: Maybe<ProductFilterParameter>;
+  /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+  filterOperator?: Maybe<LogicalOperator>;
+};
+
+export type ProductOption = Node & {
+  __typename?: 'ProductOption';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  code: Scalars['String'];
+  name: Scalars['String'];
+  groupId: Scalars['ID'];
+  group: ProductOptionGroup;
+  translations: Array<ProductOptionTranslation>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type ProductOptionGroup = Node & {
+  __typename?: 'ProductOptionGroup';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  code: Scalars['String'];
+  name: Scalars['String'];
+  options: Array<ProductOption>;
+  translations: Array<ProductOptionGroupTranslation>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type ProductOptionGroupTranslation = {
+  __typename?: 'ProductOptionGroupTranslation';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  name: Scalars['String'];
+};
+
+export type ProductOptionTranslation = {
+  __typename?: 'ProductOptionTranslation';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  name: Scalars['String'];
+};
+
+export type ProductSortParameter = {
+  id?: Maybe<SortOrder>;
+  createdAt?: Maybe<SortOrder>;
+  updatedAt?: Maybe<SortOrder>;
+  name?: Maybe<SortOrder>;
+  slug?: Maybe<SortOrder>;
+  description?: Maybe<SortOrder>;
+};
+
+export type ProductTranslation = {
+  __typename?: 'ProductTranslation';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  name: Scalars['String'];
+  slug: Scalars['String'];
+  description: Scalars['String'];
+};
+
+export type ProductVariant = Node & {
+  __typename?: 'ProductVariant';
+  id: Scalars['ID'];
+  product: Product;
+  productId: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  sku: Scalars['String'];
+  name: Scalars['String'];
+  featuredAsset?: Maybe<Asset>;
+  assets: Array<Asset>;
+  price: Scalars['Int'];
+  currencyCode: CurrencyCode;
+  priceWithTax: Scalars['Int'];
+  stockLevel: Scalars['String'];
+  taxRateApplied: TaxRate;
+  taxCategory: TaxCategory;
+  options: Array<ProductOption>;
+  facetValues: Array<FacetValue>;
+  translations: Array<ProductVariantTranslation>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type ProductVariantFilterParameter = {
+  id?: Maybe<IdOperators>;
+  productId?: Maybe<IdOperators>;
+  createdAt?: Maybe<DateOperators>;
+  updatedAt?: Maybe<DateOperators>;
+  languageCode?: Maybe<StringOperators>;
+  sku?: Maybe<StringOperators>;
+  name?: Maybe<StringOperators>;
+  price?: Maybe<NumberOperators>;
+  currencyCode?: Maybe<StringOperators>;
+  priceWithTax?: Maybe<NumberOperators>;
+  stockLevel?: Maybe<StringOperators>;
+};
+
+export type ProductVariantList = PaginatedList & {
+  __typename?: 'ProductVariantList';
+  items: Array<ProductVariant>;
+  totalItems: Scalars['Int'];
+};
+
+export type ProductVariantListOptions = {
+  /** Skips the first n results, for use in pagination */
+  skip?: Maybe<Scalars['Int']>;
+  /** Takes n results, for use in pagination */
+  take?: Maybe<Scalars['Int']>;
+  /** Specifies which properties to sort the results by */
+  sort?: Maybe<ProductVariantSortParameter>;
+  /** Allows the results to be filtered */
+  filter?: Maybe<ProductVariantFilterParameter>;
+  /** Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND. */
+  filterOperator?: Maybe<LogicalOperator>;
+};
+
+export type ProductVariantSortParameter = {
+  id?: Maybe<SortOrder>;
+  productId?: Maybe<SortOrder>;
+  createdAt?: Maybe<SortOrder>;
+  updatedAt?: Maybe<SortOrder>;
+  sku?: Maybe<SortOrder>;
+  name?: Maybe<SortOrder>;
+  price?: Maybe<SortOrder>;
+  priceWithTax?: Maybe<SortOrder>;
+  stockLevel?: Maybe<SortOrder>;
+};
+
+export type ProductVariantTranslation = {
+  __typename?: 'ProductVariantTranslation';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  name: Scalars['String'];
+};
+
+export type Promotion = Node & {
+  __typename?: 'Promotion';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  startsAt?: Maybe<Scalars['DateTime']>;
+  endsAt?: Maybe<Scalars['DateTime']>;
+  couponCode?: Maybe<Scalars['String']>;
+  perCustomerUsageLimit?: Maybe<Scalars['Int']>;
+  name: Scalars['String'];
+  enabled: Scalars['Boolean'];
+  conditions: Array<ConfigurableOperation>;
+  actions: Array<ConfigurableOperation>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type PromotionList = PaginatedList & {
+  __typename?: 'PromotionList';
+  items: Array<Promotion>;
+  totalItems: Scalars['Int'];
+};
+
+export type Query = {
+  __typename?: 'Query';
+  /** The active Channel */
+  activeChannel: Channel;
+  /** The active Customer */
+  activeCustomer?: Maybe<Customer>;
+  /**
+   * The active Order. Will be `null` until an Order is created via `addItemToOrder`. Once an Order reaches the
+   * state of `PaymentAuthorized` or `PaymentSettled`, then that Order is no longer considered "active" and this
+   * query will once again return `null`.
+   */
+  activeOrder?: Maybe<Order>;
+  /** An array of supported Countries */
+  availableCountries: Array<Country>;
+  /** A list of Collections available to the shop */
+  collections: CollectionList;
+  /** Returns a Collection either by its id or slug. If neither 'id' nor 'slug' is specified, an error will result. */
+  collection?: Maybe<Collection>;
+  /** Returns a list of eligible shipping methods based on the current active Order */
+  eligibleShippingMethods: Array<ShippingMethodQuote>;
+  /** Returns a list of payment methods and their eligibility based on the current active Order */
+  eligiblePaymentMethods: Array<PaymentMethodQuote>;
+  /** A list of Facets available to the shop */
+  facets: FacetList;
+  /** Returns a Facet by its id */
+  facet?: Maybe<Facet>;
+  /** Returns information about the current authenticated User */
+  me?: Maybe<CurrentUser>;
+  /** Returns the possible next states that the activeOrder can transition to */
+  nextOrderStates: Array<Scalars['String']>;
+  /**
+   * Returns an Order based on the id. Note that in the Shop API, only orders belonging to the
+   * currently-authenticated User may be queried.
+   */
+  order?: Maybe<Order>;
+  /**
+   * Returns an Order based on the order `code`. For guest Orders (i.e. Orders placed by non-authenticated Customers)
+   * this query will only return the Order within 2 hours of the Order being placed. This allows an Order confirmation
+   * screen to be shown immediately after completion of a guest checkout, yet prevents security risks of allowing
+   * general anonymous access to Order data.
+   */
+  orderByCode?: Maybe<Order>;
+  /** Get a Product either by id or slug. If neither 'id' nor 'slug' is specified, an error will result. */
+  product?: Maybe<Product>;
+  /** Get a list of Products */
+  products: ProductList;
+  /** Search Products based on the criteria set by the `SearchInput` */
+  search: SearchResponse;
+};
+
+
+export type QueryCollectionsArgs = {
+  options?: Maybe<CollectionListOptions>;
+};
+
+
+export type QueryCollectionArgs = {
+  id?: Maybe<Scalars['ID']>;
+  slug?: Maybe<Scalars['String']>;
+};
+
+
+export type QueryFacetsArgs = {
+  options?: Maybe<FacetListOptions>;
+};
+
+
+export type QueryFacetArgs = {
+  id: Scalars['ID'];
+};
+
+
+export type QueryOrderArgs = {
+  id: Scalars['ID'];
+};
+
+
+export type QueryOrderByCodeArgs = {
+  code: Scalars['String'];
+};
+
+
+export type QueryProductArgs = {
+  id?: Maybe<Scalars['ID']>;
+  slug?: Maybe<Scalars['String']>;
+};
+
+
+export type QueryProductsArgs = {
+  options?: Maybe<ProductListOptions>;
+};
+
+
+export type QuerySearchArgs = {
+  input: SearchInput;
+};
+
+export type RefreshCustomerVerificationResult = Success | NativeAuthStrategyError;
+
+export type Refund = Node & {
+  __typename?: 'Refund';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  items: Scalars['Int'];
+  shipping: Scalars['Int'];
+  adjustment: Scalars['Int'];
+  total: Scalars['Int'];
+  method?: Maybe<Scalars['String']>;
+  state: Scalars['String'];
+  transactionId?: Maybe<Scalars['String']>;
+  reason?: Maybe<Scalars['String']>;
+  orderItems: Array<OrderItem>;
+  paymentId: Scalars['ID'];
+  metadata?: Maybe<Scalars['JSON']>;
+};
+
+export type RegisterCustomerAccountResult = Success | MissingPasswordError | PasswordValidationError | NativeAuthStrategyError;
+
+export type RegisterCustomerInput = {
+  emailAddress: Scalars['String'];
+  title?: Maybe<Scalars['String']>;
+  firstName?: Maybe<Scalars['String']>;
+  lastName?: Maybe<Scalars['String']>;
+  phoneNumber?: Maybe<Scalars['String']>;
+  password?: Maybe<Scalars['String']>;
+};
+
+export type RelationCustomFieldConfig = CustomField & {
+  __typename?: 'RelationCustomFieldConfig';
+  name: Scalars['String'];
+  type: Scalars['String'];
+  list: Scalars['Boolean'];
+  label?: Maybe<Array<LocalizedString>>;
+  description?: Maybe<Array<LocalizedString>>;
+  readonly?: Maybe<Scalars['Boolean']>;
+  internal?: Maybe<Scalars['Boolean']>;
+  nullable?: Maybe<Scalars['Boolean']>;
+  entity: Scalars['String'];
+  scalarFields: Array<Scalars['String']>;
+  ui?: Maybe<Scalars['JSON']>;
+};
+
+export type RemoveOrderItemsResult = Order | OrderModificationError;
+
+export type RequestPasswordResetResult = Success | NativeAuthStrategyError;
+
+export type RequestUpdateCustomerEmailAddressResult = Success | InvalidCredentialsError | EmailAddressConflictError | NativeAuthStrategyError;
+
+export type ResetPasswordResult = CurrentUser | PasswordResetTokenInvalidError | PasswordResetTokenExpiredError | PasswordValidationError | NativeAuthStrategyError | NotVerifiedError;
+
+export type Role = Node & {
+  __typename?: 'Role';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  code: Scalars['String'];
+  description: Scalars['String'];
+  permissions: Array<Permission>;
+  channels: Array<Channel>;
+};
+
+export type RoleList = PaginatedList & {
+  __typename?: 'RoleList';
+  items: Array<Role>;
+  totalItems: Scalars['Int'];
+};
+
+export type SearchInput = {
+  term?: Maybe<Scalars['String']>;
+  facetValueIds?: Maybe<Array<Scalars['ID']>>;
+  facetValueOperator?: Maybe<LogicalOperator>;
+  facetValueFilters?: Maybe<Array<FacetValueFilterInput>>;
+  collectionId?: Maybe<Scalars['ID']>;
+  collectionSlug?: Maybe<Scalars['String']>;
+  groupByProduct?: Maybe<Scalars['Boolean']>;
+  take?: Maybe<Scalars['Int']>;
+  skip?: Maybe<Scalars['Int']>;
+  sort?: Maybe<SearchResultSortParameter>;
+};
+
+export type SearchReindexResponse = {
+  __typename?: 'SearchReindexResponse';
+  success: Scalars['Boolean'];
+};
+
+export type SearchResponse = {
+  __typename?: 'SearchResponse';
+  items: Array<SearchResult>;
+  totalItems: Scalars['Int'];
+  facetValues: Array<FacetValueResult>;
+  collections: Array<CollectionResult>;
+};
+
+export type SearchResult = {
+  __typename?: 'SearchResult';
+  sku: Scalars['String'];
+  slug: Scalars['String'];
+  productId: Scalars['ID'];
+  productName: Scalars['String'];
+  productAsset?: Maybe<SearchResultAsset>;
+  productVariantId: Scalars['ID'];
+  productVariantName: Scalars['String'];
+  productVariantAsset?: Maybe<SearchResultAsset>;
+  price: SearchResultPrice;
+  priceWithTax: SearchResultPrice;
+  currencyCode: CurrencyCode;
+  description: Scalars['String'];
+  facetIds: Array<Scalars['ID']>;
+  facetValueIds: Array<Scalars['ID']>;
+  /** An array of ids of the Collections in which this result appears */
+  collectionIds: Array<Scalars['ID']>;
+  /** A relevance score for the result. Differs between database implementations */
+  score: Scalars['Float'];
+};
+
+export type SearchResultAsset = {
+  __typename?: 'SearchResultAsset';
+  id: Scalars['ID'];
+  preview: Scalars['String'];
+  focalPoint?: Maybe<Coordinate>;
+};
+
+/** The price of a search result product, either as a range or as a single price */
+export type SearchResultPrice = PriceRange | SinglePrice;
+
+export type SearchResultSortParameter = {
+  name?: Maybe<SortOrder>;
+  price?: Maybe<SortOrder>;
+};
+
+export type SetCustomerForOrderResult = Order | AlreadyLoggedInError | EmailAddressConflictError | NoActiveOrderError;
+
+export type SetOrderShippingMethodResult = Order | OrderModificationError | IneligibleShippingMethodError | NoActiveOrderError;
+
+export type ShippingLine = {
+  __typename?: 'ShippingLine';
+  shippingMethod: ShippingMethod;
+  price: Scalars['Int'];
+  priceWithTax: Scalars['Int'];
+  discountedPrice: Scalars['Int'];
+  discountedPriceWithTax: Scalars['Int'];
+  discounts: Array<Discount>;
+};
+
+export type ShippingMethod = Node & {
+  __typename?: 'ShippingMethod';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  code: Scalars['String'];
+  name: Scalars['String'];
+  description: Scalars['String'];
+  fulfillmentHandlerCode: Scalars['String'];
+  checker: ConfigurableOperation;
+  calculator: ConfigurableOperation;
+  translations: Array<ShippingMethodTranslation>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type ShippingMethodList = PaginatedList & {
+  __typename?: 'ShippingMethodList';
+  items: Array<ShippingMethod>;
+  totalItems: Scalars['Int'];
+};
+
+export type ShippingMethodQuote = {
+  __typename?: 'ShippingMethodQuote';
+  id: Scalars['ID'];
+  price: Scalars['Int'];
+  priceWithTax: Scalars['Int'];
+  code: Scalars['String'];
+  name: Scalars['String'];
+  description: Scalars['String'];
+  /** Any optional metadata returned by the ShippingCalculator in the ShippingCalculationResult */
+  metadata?: Maybe<Scalars['JSON']>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type ShippingMethodTranslation = {
+  __typename?: 'ShippingMethodTranslation';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  languageCode: LanguageCode;
+  name: Scalars['String'];
+  description: Scalars['String'];
+};
+
+/** The price value where the result has a single price */
+export type SinglePrice = {
+  __typename?: 'SinglePrice';
+  value: Scalars['Int'];
+};
+
+export enum SortOrder {
+  ASC = 'ASC',
+  DESC = 'DESC'
+}
+
+export type StringCustomFieldConfig = CustomField & {
+  __typename?: 'StringCustomFieldConfig';
+  name: Scalars['String'];
+  type: Scalars['String'];
+  list: Scalars['Boolean'];
+  length?: Maybe<Scalars['Int']>;
+  label?: Maybe<Array<LocalizedString>>;
+  description?: Maybe<Array<LocalizedString>>;
+  readonly?: Maybe<Scalars['Boolean']>;
+  internal?: Maybe<Scalars['Boolean']>;
+  nullable?: Maybe<Scalars['Boolean']>;
+  pattern?: Maybe<Scalars['String']>;
+  options?: Maybe<Array<StringFieldOption>>;
+  ui?: Maybe<Scalars['JSON']>;
+};
+
+export type StringFieldOption = {
+  __typename?: 'StringFieldOption';
+  value: Scalars['String'];
+  label?: Maybe<Array<LocalizedString>>;
+};
+
+/** Operators for filtering on a list of String fields */
+export type StringListOperators = {
+  inList: Scalars['String'];
+};
+
+/** Operators for filtering on a String field */
+export type StringOperators = {
+  eq?: Maybe<Scalars['String']>;
+  notEq?: Maybe<Scalars['String']>;
+  contains?: Maybe<Scalars['String']>;
+  notContains?: Maybe<Scalars['String']>;
+  in?: Maybe<Array<Scalars['String']>>;
+  notIn?: Maybe<Array<Scalars['String']>>;
+  regex?: Maybe<Scalars['String']>;
+};
+
+/** Indicates that an operation succeeded, where we do not want to return any more specific information. */
+export type Success = {
+  __typename?: 'Success';
+  success: Scalars['Boolean'];
+};
+
+export type Surcharge = Node & {
+  __typename?: 'Surcharge';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  description: Scalars['String'];
+  sku?: Maybe<Scalars['String']>;
+  taxLines: Array<TaxLine>;
+  price: Scalars['Int'];
+  priceWithTax: Scalars['Int'];
+  taxRate: Scalars['Float'];
+};
+
+export type Tag = Node & {
+  __typename?: 'Tag';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  value: Scalars['String'];
+};
+
+export type TagList = PaginatedList & {
+  __typename?: 'TagList';
+  items: Array<Tag>;
+  totalItems: Scalars['Int'];
+};
+
+export type TaxCategory = Node & {
+  __typename?: 'TaxCategory';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  name: Scalars['String'];
+  isDefault: Scalars['Boolean'];
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type TaxLine = {
+  __typename?: 'TaxLine';
+  description: Scalars['String'];
+  taxRate: Scalars['Float'];
+};
+
+export type TaxRate = Node & {
+  __typename?: 'TaxRate';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  name: Scalars['String'];
+  enabled: Scalars['Boolean'];
+  value: Scalars['Float'];
+  category: TaxCategory;
+  zone: Zone;
+  customerGroup?: Maybe<CustomerGroup>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type TaxRateList = PaginatedList & {
+  __typename?: 'TaxRateList';
+  items: Array<TaxRate>;
+  totalItems: Scalars['Int'];
+};
+
+export type TextCustomFieldConfig = CustomField & {
+  __typename?: 'TextCustomFieldConfig';
+  name: Scalars['String'];
+  type: Scalars['String'];
+  list: Scalars['Boolean'];
+  label?: Maybe<Array<LocalizedString>>;
+  description?: Maybe<Array<LocalizedString>>;
+  readonly?: Maybe<Scalars['Boolean']>;
+  internal?: Maybe<Scalars['Boolean']>;
+  nullable?: Maybe<Scalars['Boolean']>;
+  ui?: Maybe<Scalars['JSON']>;
+};
+
+export type TransitionOrderToStateResult = Order | OrderStateTransitionError;
+
+export type UpdateAddressInput = {
+  id: Scalars['ID'];
+  fullName?: Maybe<Scalars['String']>;
+  company?: Maybe<Scalars['String']>;
+  streetLine1?: Maybe<Scalars['String']>;
+  streetLine2?: Maybe<Scalars['String']>;
+  city?: Maybe<Scalars['String']>;
+  province?: Maybe<Scalars['String']>;
+  postalCode?: Maybe<Scalars['String']>;
+  countryCode?: Maybe<Scalars['String']>;
+  phoneNumber?: Maybe<Scalars['String']>;
+  defaultShippingAddress?: Maybe<Scalars['Boolean']>;
+  defaultBillingAddress?: Maybe<Scalars['Boolean']>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type UpdateCustomerEmailAddressResult = Success | IdentifierChangeTokenInvalidError | IdentifierChangeTokenExpiredError | NativeAuthStrategyError;
+
+export type UpdateCustomerInput = {
+  title?: Maybe<Scalars['String']>;
+  firstName?: Maybe<Scalars['String']>;
+  lastName?: Maybe<Scalars['String']>;
+  phoneNumber?: Maybe<Scalars['String']>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type UpdateCustomerPasswordResult = Success | InvalidCredentialsError | PasswordValidationError | NativeAuthStrategyError;
+
+export type UpdateOrderInput = {
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+export type UpdateOrderItemsResult = Order | OrderModificationError | OrderLimitError | NegativeQuantityError | InsufficientStockError;
+
+
+export type User = Node & {
+  __typename?: 'User';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  identifier: Scalars['String'];
+  verified: Scalars['Boolean'];
+  roles: Array<Role>;
+  lastLogin?: Maybe<Scalars['DateTime']>;
+  authenticationMethods: Array<AuthenticationMethod>;
+  customFields?: Maybe<Scalars['JSON']>;
+};
+
+/**
+ * Returned if the verification token (used to verify a Customer's email address) is valid, but has
+ * expired according to the `verificationTokenDuration` setting in the AuthOptions.
+ */
+export type VerificationTokenExpiredError = ErrorResult & {
+  __typename?: 'VerificationTokenExpiredError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+/**
+ * Returned if the verification token (used to verify a Customer's email address) is either
+ * invalid or does not match any expected tokens.
+ */
+export type VerificationTokenInvalidError = ErrorResult & {
+  __typename?: 'VerificationTokenInvalidError';
+  errorCode: ErrorCode;
+  message: Scalars['String'];
+};
+
+export type VerifyCustomerAccountResult = CurrentUser | VerificationTokenInvalidError | VerificationTokenExpiredError | MissingPasswordError | PasswordValidationError | PasswordAlreadySetError | NativeAuthStrategyError;
+
+export type Zone = Node & {
+  __typename?: 'Zone';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  name: Scalars['String'];
+  members: Array<Country>;
+  customFields?: Maybe<Scalars['JSON']>;
+};

+ 18 - 0
packages/payments-plugin/src/mollie/mollie-shop-schema.ts

@@ -0,0 +1,18 @@
+import { gql } from 'graphql-tag';
+
+export const shopSchema = gql`
+    type MolliePaymentIntentError implements ErrorResult {
+        errorCode: ErrorCode!
+        message: String!
+    }
+    type MolliePaymentIntent {
+        url: String!
+    }
+    union MolliePaymentIntentResult = MolliePaymentIntent | MolliePaymentIntentError
+    input MolliePaymentIntentInput {
+        paymentMethodCode: String!
+    }
+    extend type Mutation {
+        createMolliePaymentIntent(input: MolliePaymentIntentInput!): MolliePaymentIntentResult!
+    }
+`;

+ 10 - 55
packages/payments-plugin/src/mollie/mollie.controller.ts

@@ -1,26 +1,12 @@
-import createMollieClient, { PaymentStatus } from '@mollie/api-client';
 import { Body, Controller, Param, Post } from '@nestjs/common';
-import {
-    ChannelService,
-    LanguageCode,
-    Logger,
-    OrderService,
-    Payment,
-    PaymentMethodService,
-    RequestContext,
-    TransactionalConnection,
-} from '@vendure/core';
+import { Logger } from '@vendure/core';
 
 import { loggerCtx } from './constants';
+import { MollieService } from './mollie.service';
 
 @Controller('payments')
 export class MollieController {
-    constructor(
-        private orderService: OrderService,
-        private connection: TransactionalConnection,
-        private paymentMethodService: PaymentMethodService,
-        private channelService: ChannelService,
-    ) {}
+    constructor(private mollieService: MollieService) {}
 
     @Post('mollie/:channelToken/:paymentMethodId')
     async webhook(
@@ -28,45 +14,14 @@ export class MollieController {
         @Param('paymentMethodId') paymentMethodId: string,
         @Body() body: any,
     ): Promise<void> {
-        const ctx = await this.createContext(channelToken);
-        Logger.info(`Received payment for ${channelToken}`, loggerCtx);
-        const paymentMethod = await this.paymentMethodService.findOne(ctx, paymentMethodId);
-        if (!paymentMethod) {
-            // Fail silently, as we don't want to expose if a paymentMethodId exists or not
-            return Logger.error(`No paymentMethod found with id ${paymentMethodId}`, loggerCtx);
+        if (!body.id) {
+            return Logger.warn(` Ignoring incoming webhook, because it has no body.id.`, loggerCtx);
         }
-        const apiKey = paymentMethod.handler.args.find(a => a.name === 'apiKey')?.value;
-        if (!apiKey) {
-            throw Error(`No apiKey found for payment ${paymentMethod.id} for channel ${channelToken}`);
+        try {
+            await this.mollieService.settlePayment({ channelToken, paymentMethodId, paymentId: body.id });
+        } catch (error) {
+            Logger.error(`Failed to process incoming webhook: ${error?.message}`, loggerCtx, error);
+            throw error;
         }
-        const client = createMollieClient({ apiKey });
-        const molliePayment = await client.payments.get(body.id);
-        Logger.info(
-            `Received payment ${molliePayment.id} for order ${molliePayment.metadata.orderCode} with status ${molliePayment.status}`,
-            loggerCtx,
-        );
-        const dbPayment = await this.connection
-            .getRepository(Payment)
-            .findOneOrFail({ where: { transactionId: molliePayment.id } });
-        if (molliePayment.status === PaymentStatus.paid) {
-            await this.orderService.settlePayment(ctx, dbPayment.id);
-            Logger.info(`Payment for order ${molliePayment.metadata.orderCode} settled`, loggerCtx);
-        } else {
-            Logger.warn(
-                `Received payment for order ${molliePayment.metadata.orderCode} with status ${molliePayment.status}`,
-                loggerCtx,
-            );
-        }
-    }
-
-    private async createContext(channelToken: string): Promise<RequestContext> {
-        const channel = await this.channelService.getChannelFromToken(channelToken);
-        return new RequestContext({
-            apiType: 'admin',
-            isAuthorized: true,
-            authorizedAsOwnerOnly: false,
-            channel,
-            languageCode: LanguageCode.en,
-        });
     }
 }

+ 19 - 54
packages/payments-plugin/src/mollie/mollie.handler.ts

@@ -9,12 +9,12 @@ import {
     PaymentMethodService,
     SettlePaymentResult,
 } from '@vendure/core';
+import { Permission } from '@vendure/core';
 
 import { loggerCtx, PLUGIN_INIT_OPTIONS } from './constants';
-import { MolliePluginOptions } from './mollie.plugin';
+import { MollieService } from './mollie.service';
 
-let paymentMethodService: PaymentMethodService;
-let options: MolliePluginOptions;
+let mollieService: MollieService;
 export const molliePaymentHandler = new PaymentMethodHandler({
     code: 'mollie-payment-handler',
     description: [
@@ -31,69 +31,34 @@ export const molliePaymentHandler = new PaymentMethodHandler({
         redirectUrl: {
             type: 'string',
             label: [{ languageCode: LanguageCode.en, value: 'Redirect URL' }],
+            description: [
+                { languageCode: LanguageCode.en, value: 'Redirect the client to this URL after payment' },
+            ],
         },
     },
     init(injector) {
-        paymentMethodService = injector.get(PaymentMethodService);
-        options = injector.get(PLUGIN_INIT_OPTIONS);
+        mollieService = injector.get(MollieService);
     },
     createPayment: async (
         ctx,
         order,
         amount,
         args,
-        _metadata,
+        metadata,
     ): Promise<CreatePaymentResult | CreatePaymentErrorResult> => {
-        try {
-            const { apiKey } = args;
-            let { redirectUrl } = args;
-            const paymentMethods = await paymentMethodService.findAll(ctx);
-            const paymentMethod = paymentMethods.items.find(
-                pm =>
-                    pm.handler.args.find(arg => arg.value === apiKey) &&
-                    pm.handler.args.find(arg => arg.value === redirectUrl),
-            );
-            if (!paymentMethod) {
-                throw Error(`No paymentMethod found for given apiKey`); // This should never happen
-            }
-            const mollieClient = createMollieClient({ apiKey });
-            redirectUrl = redirectUrl.endsWith('/') ? redirectUrl.slice(0, -1) : redirectUrl; // remove appending slash
-            const vendureHost = options.vendureHost.endsWith('/')
-                ? options.vendureHost.slice(0, -1)
-                : options.vendureHost; // remove appending slash
-            const payment = await mollieClient.payments.create({
-                amount: {
-                    value: `${(order.totalWithTax / 100).toFixed(2)}`,
-                    currency: order.currencyCode,
-                },
-                metadata: {
-                    orderCode: order.code,
-                },
-                description: `Order ${order.code}`,
-                redirectUrl: `${redirectUrl}/${order.code}`,
-                webhookUrl: `${vendureHost}/payments/mollie/${ctx.channel.token}/${paymentMethod.id}`,
-            });
-            return {
-                amount: order.totalWithTax,
-                transactionId: payment.id,
-                state: 'Authorized' as const,
-                metadata: {
-                    public: {
-                        redirectLink: payment.getPaymentUrl(),
-                    },
-                },
-            };
-        } catch (err) {
-            Logger.error(err, loggerCtx);
-            return {
-                amount: order.totalWithTax,
-                state: 'Error',
-                errorMessage: err.message,
-            };
+        // Creating a payment immediately settles the payment in Mollie flow, so only Admins and internal calls should be allowed to do this
+        if (ctx.apiType !== 'admin') {
+            throw Error(`CreatePayment is not allowed for apiType '${ctx.apiType}'`);
         }
+        return {
+            amount,
+            state: 'Settled' as const,
+            transactionId: metadata.paymentId,
+            metadata // Store all given metadata on a payment
+        };
     },
-    settlePayment: async (order, payment, args): Promise<SettlePaymentResult> => {
-        // Settlement is handled by incoming webhook in mollie.controller.ts
+    settlePayment: async (ctx, order, payment, args): Promise<SettlePaymentResult> => {
+        // this should never be called
         return { success: true };
     },
     createRefund: async (ctx, input, amount, order, payment, args): Promise<CreateRefundResult> => {

+ 24 - 20
packages/payments-plugin/src/mollie/mollie.plugin.ts

@@ -1,8 +1,12 @@
 import { PluginCommonModule, RuntimeVendureConfig, VendurePlugin } from '@vendure/core';
+import { gql } from 'graphql-tag';
 
 import { PLUGIN_INIT_OPTIONS } from './constants';
+import { shopSchema } from './mollie-shop-schema';
 import { MollieController } from './mollie.controller';
 import { molliePaymentHandler } from './mollie.handler';
+import { MollieResolver } from './mollie.resolver';
+import { MollieService } from './mollie.service';
 
 /**
  * @description
@@ -53,32 +57,26 @@ export interface MolliePluginOptions {
  *
  * ## Storefront usage
  *
- * In your storefront you add a payment to an order using the `addPaymentToOrder` mutation. In this example, our Mollie
+ * In your storefront you add a payment to an order using the `createMolliePaymentIntent` mutation. In this example, our Mollie
  * PaymentMethod was given the code "mollie-payment-method".
  *
  * ```GraphQL
- * mutation AddPaymentToOrder {
- *   addPaymentToOrder(input: {
- *     method: "mollie-payment-method"
- *     metadata: {}
+ * mutation CreateMolliePaymentIntent {
+ *   createMolliePaymentIntent(input: {
+ *     paymentMethodCode: "mollie-payment-method"
  *   }) {
- *    ...on Order {
- *      id
- *      state
- *      payments {
- *          id
- *          metadata
- *      }
- *    }
- *    ...on ErrorResult {
- *      errorCode
- *      message
- *    }
+ *          ... on MolliePaymentIntent {
+                url
+            }
+            ... on MolliePaymentIntentError {
+                errorCode
+                message
+            }
  *   }
  * }
  * ```
- * The response will have
- * a `order.payments.metadata.public.redirectLink` in it, which can be used to redirect your customer to the Mollie
+ * The response will contain
+ * a redirectUrl, which can be used to redirect your customer to the Mollie
  * platform.
  *
  * After completing payment on the Mollie platform,
@@ -100,11 +98,17 @@ export interface MolliePluginOptions {
 @VendurePlugin({
     imports: [PluginCommonModule],
     controllers: [MollieController],
-    providers: [{ provide: PLUGIN_INIT_OPTIONS, useFactory: () => MolliePlugin.options }],
+    providers: [
+        MollieService,
+        { provide: PLUGIN_INIT_OPTIONS, useFactory: () => MolliePlugin.options }],
     configuration: (config: RuntimeVendureConfig) => {
         config.paymentOptions.paymentMethodHandlers.push(molliePaymentHandler);
         return config;
     },
+    shopApiExtensions: {
+        schema: shopSchema,
+        resolvers: [MollieResolver],
+    },
 })
 export class MolliePlugin {
     static options: MolliePluginOptions;

+ 34 - 0
packages/payments-plugin/src/mollie/mollie.resolver.ts

@@ -0,0 +1,34 @@
+import { Args, Mutation, ResolveField, Resolver } from '@nestjs/graphql';
+import { Allow, Ctx, Permission, RequestContext } from '@vendure/core';
+
+import {
+    MolliePaymentIntent,
+    MolliePaymentIntentError,
+    MolliePaymentIntentResult,
+} from './graphql/generated-shop-types';
+import { MollieService } from './mollie.service';
+
+@Resolver()
+export class MollieResolver {
+    constructor(private mollieService: MollieService) {
+    }
+
+    @Mutation()
+    @Allow(Permission.Owner)
+    async createMolliePaymentIntent(
+        @Ctx() ctx: RequestContext,
+        @Args('input') input: { paymentMethodCode: string },
+    ): Promise<MolliePaymentIntentResult> {
+        return this.mollieService.createPaymentIntent(ctx, input.paymentMethodCode);
+    }
+
+    @ResolveField()
+    @Resolver('MolliePaymentIntentResult')
+    __resolveType(value: MolliePaymentIntentError | MolliePaymentIntent): string {
+        if((value as MolliePaymentIntentError).errorCode) {
+            return 'MolliePaymentIntentError';
+        } else {
+            return 'MolliePaymentIntent';
+        }
+    }
+}

+ 180 - 0
packages/payments-plugin/src/mollie/mollie.service.ts

@@ -0,0 +1,180 @@
+import createMollieClient, { PaymentStatus } from '@mollie/api-client';
+import { Inject, Injectable } from '@nestjs/common';
+import {
+    ActiveOrderService,
+    ChannelService,
+    EntityHydrator,
+    LanguageCode,
+    Logger,
+    Order,
+    OrderService,
+    PaymentMethodService,
+    RequestContext,
+} from '@vendure/core';
+import { OrderStateTransitionError } from '@vendure/core/dist/common/error/generated-graphql-shop-errors';
+
+import { loggerCtx, PLUGIN_INIT_OPTIONS } from './constants';
+import { ErrorCode, MolliePaymentIntentError, MolliePaymentIntentResult } from './graphql/generated-shop-types';
+import { MolliePluginOptions } from './mollie.plugin';
+
+interface SettlePaymentInput {
+    channelToken: string;
+    paymentMethodId: string;
+    paymentId: string;
+}
+
+class PaymentIntentError implements MolliePaymentIntentError {
+    errorCode = ErrorCode.ORDER_PAYMENT_STATE_ERROR;
+    constructor(public message: string) {
+    }
+}
+
+@Injectable()
+export class MollieService {
+    constructor(
+        private paymentMethodService: PaymentMethodService,
+        @Inject(PLUGIN_INIT_OPTIONS) private options: MolliePluginOptions,
+        private activeOrderService: ActiveOrderService,
+        private orderService: OrderService,
+        private channelService: ChannelService,
+        private entityHydrator: EntityHydrator,
+    ) {
+    }
+
+    /**
+     * Creates a redirectUrl to Mollie for the given paymentMethod and current activeOrder
+     */
+    async createPaymentIntent(ctx: RequestContext, paymentMethodCode: string): Promise<MolliePaymentIntentResult> {
+        const [order, paymentMethods] = await Promise.all([
+            this.activeOrderService.getOrderFromContext(ctx),
+            this.paymentMethodService.findAll(ctx),
+        ]);
+        if (!order) {
+            return new PaymentIntentError('No active order found for session');
+        }
+        await this.entityHydrator.hydrate(ctx, order, { relations: ['lines', 'customer', 'shippingLines'] });
+        if (!order.lines?.length) {
+            return new PaymentIntentError('Cannot create payment intent for empty order');
+        }
+        if (!order.customer) {
+            return new PaymentIntentError('Cannot create payment intent for order without customer');
+        }
+        if (!order.shippingLines?.length) {
+            return new PaymentIntentError('Cannot create payment intent for order without shippingMethod');
+        }
+        const paymentMethod = paymentMethods.items.find(pm => pm.code === paymentMethodCode);
+        if (!paymentMethod) {
+            return new PaymentIntentError(`No paymentMethod found with code ${paymentMethodCode}`);
+        }
+        const apiKeyArg = paymentMethod.handler.args.find(arg => arg.name === 'apiKey');
+        const redirectUrlArg = paymentMethod.handler.args.find(arg => arg.name === 'redirectUrl');
+        if (!apiKeyArg || !redirectUrlArg) {
+            Logger.warn(`CreatePaymentIntent failed, because no apiKey or redirect is configured for ${paymentMethod.code}`, loggerCtx);
+            return new PaymentIntentError(`Paymentmethod ${paymentMethod.code} has no apiKey or redirectUrl configured`);
+        }
+        const apiKey = apiKeyArg.value;
+        let redirectUrl = redirectUrlArg.value;
+        const mollieClient = createMollieClient({ apiKey });
+        redirectUrl = redirectUrl.endsWith('/') ? redirectUrl.slice(0, -1) : redirectUrl; // remove appending slash
+        const vendureHost = this.options.vendureHost.endsWith('/')
+            ? this.options.vendureHost.slice(0, -1)
+            : this.options.vendureHost; // remove appending slash
+        const payment = await mollieClient.payments.create({
+            amount: {
+                value: `${(order.totalWithTax / 100).toFixed(2)}`,
+                currency: order.currencyCode,
+            },
+            metadata: {
+                orderCode: order.code,
+            },
+            description: `Order ${order.code}`,
+            redirectUrl: `${redirectUrl}/${order.code}`,
+            webhookUrl: `${vendureHost}/payments/mollie/${ctx.channel.token}/${paymentMethod.id}`,
+        });
+        const url = payment.getCheckoutUrl();
+        if (!url) {
+            throw Error(`Unable to getCheckoutUrl() from Mollie payment`);
+        }
+        return {
+            url,
+        };
+    }
+
+    /**
+     * Makes a request to Mollie to verify the given payment by id
+     */
+    async settlePayment({ channelToken, paymentMethodId, paymentId }: SettlePaymentInput): Promise<void> {
+        const ctx = await this.createContext(channelToken);
+        Logger.info(`Received payment for ${channelToken}`, loggerCtx);
+        const paymentMethod = await this.paymentMethodService.findOne(ctx, paymentMethodId);
+        if (!paymentMethod) {
+            // Fail silently, as we don't want to expose if a paymentMethodId exists or not
+            return Logger.warn(`No paymentMethod found with id ${paymentMethodId}`, loggerCtx);
+        }
+        const apiKey = paymentMethod.handler.args.find(a => a.name === 'apiKey')?.value;
+        if (!apiKey) {
+            throw Error(`No apiKey found for payment ${paymentMethod.id} for channel ${channelToken}`);
+        }
+        const client = createMollieClient({ apiKey });
+        const molliePayment = await client.payments.get(paymentId);
+        const orderCode = molliePayment.metadata.orderCode;
+        if (molliePayment.status !== PaymentStatus.paid) {
+            return Logger.warn(
+                `Received payment for ${channelToken} for order ${orderCode} with status ${molliePayment.status}`,
+                loggerCtx,
+            );
+        }
+        if (!orderCode) {
+            throw Error(`Molliepayment does not have metadata.orderCode, unable to settle payment ${molliePayment.id}!`);
+        }
+        Logger.info(
+            `Received payment ${molliePayment.id} for order ${orderCode} with status ${molliePayment.status}`,
+            loggerCtx,
+        );
+        const order = await this.orderService.findOneByCode(ctx, orderCode);
+        if (!order) {
+            throw Error(`Unable to find order ${orderCode}, unable to settle payment ${molliePayment.id}!`);
+        }
+        if (order.state !== 'ArrangingPayment') {
+            const transitionToStateResult = await this.orderService.transitionToState(
+                ctx,
+                order.id,
+                'ArrangingPayment',
+            );
+            if (transitionToStateResult instanceof OrderStateTransitionError) {
+                throw Error(
+                    `Error transitioning order ${order.code} from ${transitionToStateResult.fromState} to ${transitionToStateResult.toState}: ${transitionToStateResult.message}`);
+            }
+        }
+        const addPaymentToOrderResult = await this.orderService.addPaymentToOrder(ctx, order.id, {
+            method: paymentMethod.code,
+            metadata: {
+                paymentId: molliePayment.id,
+                mode: molliePayment.mode,
+                method: molliePayment.method,
+                profileId: molliePayment.profileId,
+                settlementAmount: molliePayment.settlementAmount,
+                customerId: molliePayment.customerId,
+                authorizedAt: molliePayment.authorizedAt,
+                paidAt: molliePayment.paidAt,
+            },
+        });
+        if (!(addPaymentToOrderResult instanceof Order)) {
+            throw Error(
+                `Error adding payment to order ${orderCode}: ${addPaymentToOrderResult.message}`,
+            );
+        }
+        Logger.info(`Payment for order ${molliePayment.metadata.orderCode} settled`, loggerCtx);
+    }
+
+    private async createContext(channelToken: string): Promise<RequestContext> {
+        const channel = await this.channelService.getChannelFromToken(channelToken);
+        return new RequestContext({
+            apiType: 'admin',
+            isAuthorized: true,
+            authorizedAsOwnerOnly: false,
+            channel,
+            languageCode: LanguageCode.en,
+        });
+    }
+}

+ 4 - 1
packages/payments-plugin/src/stripe/stripe.handler.ts

@@ -28,9 +28,12 @@ export const stripePaymentMethodHandler = new PaymentMethodHandler({
         stripeService = injector.get(StripeService);
     },
 
-    async createPayment(_, __, amount, ___, metadata): Promise<CreatePaymentResult> {
+    async createPayment(ctx, _, amount, ___, metadata): Promise<CreatePaymentResult> {
         // Payment is already settled in Stripe by the time the webhook in stripe.controller.ts
         // adds the payment to the order
+        if (ctx.apiType !== 'admin') {
+            throw Error(`CreatePayment is not allowed for apiType '${ctx.apiType}'`);
+        }
         return {
             amount,
             state: 'Settled' as const,

+ 6 - 0
scripts/codegen/generate-graphql-types.ts

@@ -181,6 +181,12 @@ Promise.all([
                         plugins: clientPlugins,
                         config: e2eConfig,
                     },
+                [path.join(__dirname, '../../packages/payments-plugin/src/mollie/graphql/generated-shop-types.ts')]:
+                    {
+                        schema: [SHOP_SCHEMA_OUTPUT_FILE, path.join(__dirname, '../../packages/payments-plugin/src/mollie/mollie-shop-schema.ts')],
+                        plugins: clientPlugins,
+                        config,
+                    },
             },
         });
     })

+ 14 - 15
yarn.lock

@@ -3174,13 +3174,12 @@
   resolved "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d"
   integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==
 
-"@mollie/api-client@^3.5.1":
-  version "3.5.1"
-  resolved "https://registry.yarnpkg.com/@mollie/api-client/-/api-client-3.5.1.tgz#288500f75db1516926b5a379d8c7e9d755f1f84f"
-  integrity sha512-8nEWOvTe9wsK3MKTDvRbM7hmEUc+keRtPOd/9ijq9cyXgHfA8DQcLu9KAJRrd3Q/Cw9p6kYWRyKWoNCkXexghg==
+"@mollie/api-client@^3.6.0":
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/@mollie/api-client/-/api-client-3.6.0.tgz#ed4752035fc9f2abe7c15c50704af83d24db8d89"
+  integrity sha512-9eaygcSdBEf36LAWuHyYCnBXtRFWkv9OqbDujpriBc4txJMQryHLTejrSXkDU8TzJ5tEgLwzluVv+Kbgr8+7iQ==
   dependencies:
-    axios "^0.21.1"
-    lodash "^4.17.21"
+    axios "^0.25.0"
 
 "@n1ru4l/graphql-live-query@0.7.1":
   version "0.7.1"
@@ -5555,12 +5554,12 @@ axios@0.21.1:
   dependencies:
     follow-redirects "^1.10.0"
 
-axios@^0.21.1:
-  version "0.21.4"
-  resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
-  integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
+axios@^0.25.0:
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a"
+  integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==
   dependencies:
-    follow-redirects "^1.14.0"
+    follow-redirects "^1.14.7"
 
 axobject-query@2.0.2:
   version "2.0.2"
@@ -9080,10 +9079,10 @@ follow-redirects@^1.0.0, follow-redirects@^1.10.0:
   resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz#cecb825047c00f5e66b142f90fed4f515dec789b"
   integrity sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==
 
-follow-redirects@^1.14.0:
-  version "1.14.4"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
-  integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
+follow-redirects@^1.14.7:
+  version "1.14.9"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
+  integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
 
 for-each@^0.3.3:
   version "0.3.3"

Some files were not shown because too many files changed in this diff