Procházet zdrojové kódy

feat(core): Allow ShippingCalculator to return arbitrary metadata

Relates to #136
Michael Bromley před 6 roky
rodič
revize
bdbdf9acf1

+ 1 - 0
packages/common/src/generated-shop-types.ts

@@ -2032,6 +2032,7 @@ export type ShippingMethodQuote = {
     price: Scalars['Int'];
     price: Scalars['Int'];
     priceWithTax: Scalars['Int'];
     priceWithTax: Scalars['Int'];
     description: Scalars['String'];
     description: Scalars['String'];
+    metadata?: Maybe<Scalars['JSON']>;
 };
 };
 
 
 /** The price value where the result has a single price */
 /** The price value where the result has a single price */

+ 10 - 7
packages/common/src/generated-types.ts

@@ -3040,6 +3040,7 @@ export type ShippingMethodQuote = {
   price: Scalars['Int'],
   price: Scalars['Int'],
   priceWithTax: Scalars['Int'],
   priceWithTax: Scalars['Int'],
   description: Scalars['String'],
   description: Scalars['String'],
+  metadata?: Maybe<Scalars['JSON']>,
 };
 };
 
 
 export type ShippingMethodSortParameter = {
 export type ShippingMethodSortParameter = {
@@ -3050,12 +3051,6 @@ export type ShippingMethodSortParameter = {
   description?: Maybe<SortOrder>,
   description?: Maybe<SortOrder>,
 };
 };
 
 
-export type ShippingPrice = {
-  __typename?: 'ShippingPrice',
-  price: Scalars['Int'],
-  priceWithTax: Scalars['Int'],
-};
-
 /** The price value where the result has a single price */
 /** The price value where the result has a single price */
 export type SinglePrice = {
 export type SinglePrice = {
   __typename?: 'SinglePrice',
   __typename?: 'SinglePrice',
@@ -3196,10 +3191,18 @@ export type TestShippingMethodOrderLineInput = {
   quantity: Scalars['Int'],
   quantity: Scalars['Int'],
 };
 };
 
 
+export type TestShippingMethodQuote = {
+  __typename?: 'TestShippingMethodQuote',
+  price: Scalars['Int'],
+  priceWithTax: Scalars['Int'],
+  description: Scalars['String'],
+  metadata?: Maybe<Scalars['JSON']>,
+};
+
 export type TestShippingMethodResult = {
 export type TestShippingMethodResult = {
   __typename?: 'TestShippingMethodResult',
   __typename?: 'TestShippingMethodResult',
   eligible: Scalars['Boolean'],
   eligible: Scalars['Boolean'],
-  price?: Maybe<ShippingPrice>,
+  quote?: Maybe<TestShippingMethodQuote>,
 };
 };
 
 
 export type UpdateAddressInput = {
 export type UpdateAddressInput = {

+ 20 - 10
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -2938,6 +2938,7 @@ export type ShippingMethodQuote = {
     price: Scalars['Int'];
     price: Scalars['Int'];
     priceWithTax: Scalars['Int'];
     priceWithTax: Scalars['Int'];
     description: Scalars['String'];
     description: Scalars['String'];
+    metadata?: Maybe<Scalars['JSON']>;
 };
 };
 
 
 export type ShippingMethodSortParameter = {
 export type ShippingMethodSortParameter = {
@@ -2948,12 +2949,6 @@ export type ShippingMethodSortParameter = {
     description?: Maybe<SortOrder>;
     description?: Maybe<SortOrder>;
 };
 };
 
 
-export type ShippingPrice = {
-    __typename?: 'ShippingPrice';
-    price: Scalars['Int'];
-    priceWithTax: Scalars['Int'];
-};
-
 /** The price value where the result has a single price */
 /** The price value where the result has a single price */
 export type SinglePrice = {
 export type SinglePrice = {
     __typename?: 'SinglePrice';
     __typename?: 'SinglePrice';
@@ -3095,10 +3090,18 @@ export type TestShippingMethodOrderLineInput = {
     quantity: Scalars['Int'];
     quantity: Scalars['Int'];
 };
 };
 
 
+export type TestShippingMethodQuote = {
+    __typename?: 'TestShippingMethodQuote';
+    price: Scalars['Int'];
+    priceWithTax: Scalars['Int'];
+    description: Scalars['String'];
+    metadata?: Maybe<Scalars['JSON']>;
+};
+
 export type TestShippingMethodResult = {
 export type TestShippingMethodResult = {
     __typename?: 'TestShippingMethodResult';
     __typename?: 'TestShippingMethodResult';
     eligible: Scalars['Boolean'];
     eligible: Scalars['Boolean'];
-    price?: Maybe<ShippingPrice>;
+    quote?: Maybe<TestShippingMethodQuote>;
 };
 };
 
 
 export type UpdateAddressInput = {
 export type UpdateAddressInput = {
@@ -4762,7 +4765,14 @@ export type TestShippingMethodQuery = { __typename?: 'Query' } & {
     testShippingMethod: { __typename?: 'TestShippingMethodResult' } & Pick<
     testShippingMethod: { __typename?: 'TestShippingMethodResult' } & Pick<
         TestShippingMethodResult,
         TestShippingMethodResult,
         'eligible'
         'eligible'
-    > & { price: Maybe<{ __typename?: 'ShippingPrice' } & Pick<ShippingPrice, 'price' | 'priceWithTax'>> };
+    > & {
+            quote: Maybe<
+                { __typename?: 'TestShippingMethodQuote' } & Pick<
+                    TestShippingMethodQuote,
+                    'price' | 'priceWithTax' | 'metadata'
+                >
+            >;
+        };
 };
 };
 
 
 export type TestEligibleMethodsQueryVariables = {
 export type TestEligibleMethodsQueryVariables = {
@@ -4773,7 +4783,7 @@ export type TestEligibleMethodsQuery = { __typename?: 'Query' } & {
     testEligibleShippingMethods: Array<
     testEligibleShippingMethods: Array<
         { __typename?: 'ShippingMethodQuote' } & Pick<
         { __typename?: 'ShippingMethodQuote' } & Pick<
             ShippingMethodQuote,
             ShippingMethodQuote,
-            'id' | 'description' | 'price' | 'priceWithTax'
+            'id' | 'description' | 'price' | 'priceWithTax' | 'metadata'
         >
         >
     >;
     >;
 };
 };
@@ -5948,7 +5958,7 @@ export namespace TestShippingMethod {
     export type Variables = TestShippingMethodQueryVariables;
     export type Variables = TestShippingMethodQueryVariables;
     export type Query = TestShippingMethodQuery;
     export type Query = TestShippingMethodQuery;
     export type TestShippingMethod = TestShippingMethodQuery['testShippingMethod'];
     export type TestShippingMethod = TestShippingMethodQuery['testShippingMethod'];
-    export type Price = NonNullable<TestShippingMethodQuery['testShippingMethod']['price']>;
+    export type Quote = NonNullable<TestShippingMethodQuery['testShippingMethod']['quote']>;
 }
 }
 
 
 export namespace TestEligibleMethods {
 export namespace TestEligibleMethods {

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

@@ -2032,6 +2032,7 @@ export type ShippingMethodQuote = {
     price: Scalars['Int'];
     price: Scalars['Int'];
     priceWithTax: Scalars['Int'];
     priceWithTax: Scalars['Int'];
     description: Scalars['String'];
     description: Scalars['String'];
+    metadata?: Maybe<Scalars['JSON']>;
 };
 };
 
 
 /** The price value where the result has a single price */
 /** The price value where the result has a single price */

+ 99 - 58
packages/core/e2e/shipping-method.e2e-spec.ts

@@ -2,8 +2,10 @@
 import gql from 'graphql-tag';
 import gql from 'graphql-tag';
 import path from 'path';
 import path from 'path';
 
 
+import { LanguageCode } from '../../common/lib/generated-types';
 import { defaultShippingCalculator } from '../src/config/shipping-method/default-shipping-calculator';
 import { defaultShippingCalculator } from '../src/config/shipping-method/default-shipping-calculator';
 import { defaultShippingEligibilityChecker } from '../src/config/shipping-method/default-shipping-eligibility-checker';
 import { defaultShippingEligibilityChecker } from '../src/config/shipping-method/default-shipping-eligibility-checker';
+import { ShippingCalculator } from '../src/config/shipping-method/shipping-calculator';
 
 
 import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
 import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
 import {
 import {
@@ -27,10 +29,18 @@ describe('ShippingMethod resolver', () => {
     const server = new TestServer();
     const server = new TestServer();
 
 
     beforeAll(async () => {
     beforeAll(async () => {
-        const token = await server.init({
-            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
-            customerCount: 1,
-        });
+        const token = await server.init(
+            {
+                productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
+                customerCount: 1,
+            },
+            {
+                shippingOptions: {
+                    shippingEligibilityCheckers: [defaultShippingEligibilityChecker],
+                    shippingCalculators: [defaultShippingCalculator, calculatorWithMetadata],
+                },
+            },
+        );
         await adminClient.init();
         await adminClient.init();
         await shopClient.init();
         await shopClient.init();
     }, TEST_SETUP_TIMEOUT_MS);
     }, TEST_SETUP_TIMEOUT_MS);
@@ -91,6 +101,11 @@ describe('ShippingMethod resolver', () => {
                 code: 'default-shipping-calculator',
                 code: 'default-shipping-calculator',
                 description: 'Default Flat-Rate Shipping Calculator',
                 description: 'Default Flat-Rate Shipping Calculator',
             },
             },
+            {
+                args: [],
+                code: 'calculator-with-metadata',
+                description: 'Has metadata',
+            },
         ]);
         ]);
     });
     });
 
 
@@ -123,10 +138,16 @@ describe('ShippingMethod resolver', () => {
                 description: 'new method',
                 description: 'new method',
                 checker: {
                 checker: {
                     code: defaultShippingEligibilityChecker.code,
                     code: defaultShippingEligibilityChecker.code,
-                    arguments: [],
+                    arguments: [
+                        {
+                            name: 'orderMinimum',
+                            type: 'int',
+                            value: '0',
+                        },
+                    ],
                 },
                 },
                 calculator: {
                 calculator: {
-                    code: defaultShippingCalculator.code,
+                    code: calculatorWithMetadata.code,
                     arguments: [],
                     arguments: [],
                 },
                 },
             },
             },
@@ -137,7 +158,7 @@ describe('ShippingMethod resolver', () => {
             code: 'new-method',
             code: 'new-method',
             description: 'new method',
             description: 'new method',
             calculator: {
             calculator: {
-                code: 'default-shipping-calculator',
+                code: 'calculator-with-metadata',
             },
             },
             checker: {
             checker: {
                 code: 'default-shipping-eligibility-checker',
                 code: 'default-shipping-eligibility-checker',
@@ -145,40 +166,6 @@ describe('ShippingMethod resolver', () => {
         });
         });
     });
     });
 
 
-    it('updateShippingMethod', async () => {
-        const { updateShippingMethod } = await adminClient.query<
-            UpdateShippingMethod.Mutation,
-            UpdateShippingMethod.Variables
-        >(UPDATE_SHIPPING_METHOD, {
-            input: {
-                id: 'T_3',
-                description: 'changed method',
-            },
-        });
-
-        expect(updateShippingMethod.description).toBe('changed method');
-    });
-
-    it('deleteShippingMethod', async () => {
-        const listResult1 = await adminClient.query<GetShippingMethodList.Query>(GET_SHIPPING_METHOD_LIST);
-        expect(listResult1.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2', 'T_3']);
-
-        const { deleteShippingMethod } = await adminClient.query<
-            DeleteShippingMethod.Mutation,
-            DeleteShippingMethod.Variables
-        >(DELETE_SHIPPING_METHOD, {
-            id: 'T_3',
-        });
-
-        expect(deleteShippingMethod).toEqual({
-            result: DeletionResult.DELETED,
-            message: null,
-        });
-
-        const listResult2 = await adminClient.query<GetShippingMethodList.Query>(GET_SHIPPING_METHOD_LIST);
-        expect(listResult2.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2']);
-    });
-
     it('testShippingMethod', async () => {
     it('testShippingMethod', async () => {
         const { testShippingMethod } = await adminClient.query<
         const { testShippingMethod } = await adminClient.query<
             TestShippingMethod.Query,
             TestShippingMethod.Query,
@@ -186,19 +173,8 @@ describe('ShippingMethod resolver', () => {
         >(TEST_SHIPPING_METHOD, {
         >(TEST_SHIPPING_METHOD, {
             input: {
             input: {
                 calculator: {
                 calculator: {
-                    code: defaultShippingCalculator.code,
-                    arguments: [
-                        {
-                            name: 'rate',
-                            type: 'int',
-                            value: '1000',
-                        },
-                        {
-                            name: 'taxRate',
-                            type: 'int',
-                            value: '20',
-                        },
-                    ],
+                    code: calculatorWithMetadata.code,
+                    arguments: [],
                 },
                 },
                 checker: {
                 checker: {
                     code: defaultShippingEligibilityChecker.code,
                     code: defaultShippingEligibilityChecker.code,
@@ -220,9 +196,10 @@ describe('ShippingMethod resolver', () => {
 
 
         expect(testShippingMethod).toEqual({
         expect(testShippingMethod).toEqual({
             eligible: true,
             eligible: true,
-            price: {
-                price: 1000,
-                priceWithTax: 1200,
+            quote: {
+                price: 100,
+                priceWithTax: 100,
+                metadata: TEST_METADATA,
             },
             },
         });
         });
     });
     });
@@ -242,20 +219,82 @@ describe('ShippingMethod resolver', () => {
         });
         });
 
 
         expect(testEligibleShippingMethods).toEqual([
         expect(testEligibleShippingMethods).toEqual([
+            {
+                id: 'T_3',
+                description: 'new method',
+                price: 100,
+                priceWithTax: 100,
+                metadata: TEST_METADATA,
+            },
+
             {
             {
                 id: 'T_1',
                 id: 'T_1',
                 description: 'Standard Shipping',
                 description: 'Standard Shipping',
                 price: 500,
                 price: 500,
                 priceWithTax: 500,
                 priceWithTax: 500,
+                metadata: null,
             },
             },
             {
             {
                 id: 'T_2',
                 id: 'T_2',
                 description: 'Express Shipping',
                 description: 'Express Shipping',
                 price: 1000,
                 price: 1000,
                 priceWithTax: 1000,
                 priceWithTax: 1000,
+                metadata: null,
             },
             },
         ]);
         ]);
     });
     });
+
+    it('updateShippingMethod', async () => {
+        const { updateShippingMethod } = await adminClient.query<
+            UpdateShippingMethod.Mutation,
+            UpdateShippingMethod.Variables
+        >(UPDATE_SHIPPING_METHOD, {
+            input: {
+                id: 'T_3',
+                description: 'changed method',
+            },
+        });
+
+        expect(updateShippingMethod.description).toBe('changed method');
+    });
+
+    it('deleteShippingMethod', async () => {
+        const listResult1 = await adminClient.query<GetShippingMethodList.Query>(GET_SHIPPING_METHOD_LIST);
+        expect(listResult1.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2', 'T_3']);
+
+        const { deleteShippingMethod } = await adminClient.query<
+            DeleteShippingMethod.Mutation,
+            DeleteShippingMethod.Variables
+        >(DELETE_SHIPPING_METHOD, {
+            id: 'T_3',
+        });
+
+        expect(deleteShippingMethod).toEqual({
+            result: DeletionResult.DELETED,
+            message: null,
+        });
+
+        const listResult2 = await adminClient.query<GetShippingMethodList.Query>(GET_SHIPPING_METHOD_LIST);
+        expect(listResult2.shippingMethods.items.map(i => i.id)).toEqual(['T_1', 'T_2']);
+    });
+});
+
+const TEST_METADATA = {
+    foo: 'bar',
+    baz: [1, 2, 3],
+};
+
+const calculatorWithMetadata = new ShippingCalculator({
+    code: 'calculator-with-metadata',
+    description: [{ languageCode: LanguageCode.en, value: 'Has metadata' }],
+    args: {},
+    calculate: order => {
+        return {
+            price: 100,
+            priceWithTax: 100,
+            metadata: TEST_METADATA,
+        };
+    },
 });
 });
 
 
 const SHIPPING_METHOD_FRAGMENT = gql`
 const SHIPPING_METHOD_FRAGMENT = gql`
@@ -356,9 +395,10 @@ const TEST_SHIPPING_METHOD = gql`
     query TestShippingMethod($input: TestShippingMethodInput!) {
     query TestShippingMethod($input: TestShippingMethodInput!) {
         testShippingMethod(input: $input) {
         testShippingMethod(input: $input) {
             eligible
             eligible
-            price {
+            quote {
                 price
                 price
                 priceWithTax
                 priceWithTax
+                metadata
             }
             }
         }
         }
     }
     }
@@ -371,6 +411,7 @@ export const TEST_ELIGIBLE_SHIPPING_METHODS = gql`
             description
             description
             price
             price
             priceWithTax
             priceWithTax
+            metadata
         }
         }
     }
     }
 `;
 `;

+ 4 - 2
packages/core/src/api/schema/admin-api/shipping-method.api.graphql

@@ -53,10 +53,12 @@ input TestShippingMethodOrderLineInput {
 
 
 type TestShippingMethodResult {
 type TestShippingMethodResult {
     eligible: Boolean!
     eligible: Boolean!
-    price: ShippingPrice
+    quote: TestShippingMethodQuote
 }
 }
 
 
-type ShippingPrice {
+type TestShippingMethodQuote {
     price: Int!
     price: Int!
     priceWithTax: Int!
     priceWithTax: Int!
+    description: String!
+    metadata: JSON
 }
 }

+ 1 - 0
packages/core/src/api/schema/type/order.type.graphql

@@ -46,6 +46,7 @@ type ShippingMethodQuote {
     price: Int!
     price: Int!
     priceWithTax: Int!
     priceWithTax: Int!
     description: String!
     description: String!
+    metadata: JSON
 }
 }
 
 
 type OrderItem implements Node {
 type OrderItem implements Node {

+ 51 - 20
packages/core/src/config/shipping-method/shipping-calculator.ts

@@ -13,23 +13,6 @@ import { Order } from '../../entity/order/order.entity';
 export type ShippingCalculatorArgType = ConfigArgSubset<'int' | 'float' | 'string' | 'boolean'>;
 export type ShippingCalculatorArgType = ConfigArgSubset<'int' | 'float' | 'string' | 'boolean'>;
 export type ShippingCalculatorArgs = ConfigArgs<ShippingCalculatorArgType>;
 export type ShippingCalculatorArgs = ConfigArgs<ShippingCalculatorArgType>;
 
 
-export type ShippingPrice = {
-    price: number;
-    priceWithTax: number;
-};
-
-/**
- * @description
- * A function which implements the specific shipping calculation logic. It takes an {@link Order} and
- * an arguments object and should return the shipping price as an integer in cents.
- *
- * @docsCategory shipping
- */
-export type CalculateShippingFn<T extends ShippingCalculatorArgs> = (
-    order: Order,
-    args: ConfigArgValues<T>,
-) => ShippingPrice | Promise<ShippingPrice>;
-
 /**
 /**
  * @description
  * @description
  * The ShippingCalculator is used by a {@link ShippingMethod} to calculate the price of shipping on a given {@link Order}.
  * The ShippingCalculator is used by a {@link ShippingMethod} to calculate the price of shipping on a given {@link Order}.
@@ -38,17 +21,21 @@ export type CalculateShippingFn<T extends ShippingCalculatorArgs> = (
  * ```ts
  * ```ts
  * const flatRateCalculator = new ShippingCalculator({
  * const flatRateCalculator = new ShippingCalculator({
  *     code: 'flat-rate-calculator',
  *     code: 'flat-rate-calculator',
- *     description: 'Default Flat-Rate Shipping Calculator',
+ *     description: [{ languageCode: LanguageCode.en, value: 'Default Flat-Rate Shipping Calculator' }],
  *     args: {
  *     args: {
  *         rate: { type: 'int', config: { inputType: 'money' } },
  *         rate: { type: 'int', config: { inputType: 'money' } },
  *     },
  *     },
  *     calculate: (order, args) => {
  *     calculate: (order, args) => {
- *         return args.rate;
+ *         return {
+ *             price: args.rate,
+ *             priceWithTax: args.rate * ((100 + args.taxRate) / 100),
+ *         };
  *     },
  *     },
  * });
  * });
  * ```
  * ```
  *
  *
  * @docsCategory shipping
  * @docsCategory shipping
+ * @docsPage ShippingCalculator
  */
  */
 export class ShippingCalculator<T extends ShippingCalculatorArgs = {}> implements ConfigurableOperationDef {
 export class ShippingCalculator<T extends ShippingCalculatorArgs = {}> implements ConfigurableOperationDef {
     /** @internal */
     /** @internal */
@@ -77,7 +64,51 @@ export class ShippingCalculator<T extends ShippingCalculatorArgs = {}> implement
      *
      *
      * @internal
      * @internal
      */
      */
-    calculate(order: Order, args: ConfigArg[]): ShippingPrice | Promise<ShippingPrice> {
+    calculate(
+        order: Order,
+        args: ConfigArg[],
+    ): ShippingCalculationResult | Promise<ShippingCalculationResult> {
         return this.calculateFn(order, argsArrayToHash(args));
         return this.calculateFn(order, argsArrayToHash(args));
     }
     }
 }
 }
+
+/**
+ * @description
+ * The return value of the {@link CalculateShippingFn}.
+ *
+ * @docsCategory shipping
+ * @docsPage ShippingCalculator
+ */
+export interface ShippingCalculationResult {
+    /**
+     * The shipping price without any taxes.
+     */
+    price: number;
+    /**
+     * @description
+     * The shipping price including taxes.
+     */
+    priceWithTax: number;
+    /**
+     * @description
+     * Arbitrary metadata may be returned from the calculation function. This can be used
+     * e.g. to return data on estimated delivery times or any other data which may be
+     * needed in the storefront application when listing eligible shipping methods.
+     */
+    metadata?: Record<string, any>;
+}
+
+/**
+ * @description
+ * A function which implements the specific shipping calculation logic. It takes an {@link Order} and
+ * an arguments object and should return the shipping price as an integer in cents.
+ *
+ * Should return a {@link ShippingCalculationResult} object.
+ *
+ * @docsCategory shipping
+ * @docsPage ShippingCalculator
+ */
+export type CalculateShippingFn<T extends ShippingCalculatorArgs> = (
+    order: Order,
+    args: ConfigArgValues<T>,
+) => ShippingCalculationResult | Promise<ShippingCalculationResult>;

+ 15 - 13
packages/core/src/config/shipping-method/shipping-eligibility-checker.ts

@@ -12,18 +12,6 @@ import { Order } from '../../entity/order/order.entity';
 export type ShippingEligibilityCheckerArgType = ConfigArgSubset<'int' | 'float' | 'string' | 'boolean'>;
 export type ShippingEligibilityCheckerArgType = ConfigArgSubset<'int' | 'float' | 'string' | 'boolean'>;
 export type ShippingEligibilityCheckerArgs = ConfigArgs<ShippingEligibilityCheckerArgType>;
 export type ShippingEligibilityCheckerArgs = ConfigArgs<ShippingEligibilityCheckerArgType>;
 
 
-/**
- * @description
- * A function which implements logic to determine whether a given {@link Order} is eligible for
- * a particular shipping method.
- *
- * @docsCategory shipping
- */
-export type CheckShippingEligibilityCheckerFn<T extends ShippingEligibilityCheckerArgs> = (
-    order: Order,
-    args: ConfigArgValues<T>,
-) => boolean | Promise<boolean>;
-
 /**
 /**
  * @description
  * @description
  * The ShippingEligibilityChecker class is used to check whether an order qualifies for a
  * The ShippingEligibilityChecker class is used to check whether an order qualifies for a
@@ -33,7 +21,7 @@ export type CheckShippingEligibilityCheckerFn<T extends ShippingEligibilityCheck
  * ```ts
  * ```ts
  * const minOrderTotalEligibilityChecker = new ShippingEligibilityChecker({
  * const minOrderTotalEligibilityChecker = new ShippingEligibilityChecker({
  *     code: 'min-order-total-eligibility-checker',
  *     code: 'min-order-total-eligibility-checker',
- *     description: 'Checks that the order total is above some minimum value',
+ *     description: [{ languageCode: LanguageCode.en, value: 'Checks that the order total is above some minimum value' }],
  *     args: {
  *     args: {
  *         orderMinimum: { type: 'int', config: { inputType: 'money' } },
  *         orderMinimum: { type: 'int', config: { inputType: 'money' } },
  *     },
  *     },
@@ -44,6 +32,7 @@ export type CheckShippingEligibilityCheckerFn<T extends ShippingEligibilityCheck
  * ```
  * ```
  *
  *
  * @docsCategory shipping
  * @docsCategory shipping
+ * @docsPage ShippingEligibilityChecker
  */
  */
 export class ShippingEligibilityChecker<T extends ShippingEligibilityCheckerArgs = {}>
 export class ShippingEligibilityChecker<T extends ShippingEligibilityCheckerArgs = {}>
     implements ConfigurableOperationDef {
     implements ConfigurableOperationDef {
@@ -77,3 +66,16 @@ export class ShippingEligibilityChecker<T extends ShippingEligibilityCheckerArgs
         return this.checkFn(order, argsArrayToHash(args));
         return this.checkFn(order, argsArrayToHash(args));
     }
     }
 }
 }
+
+/**
+ * @description
+ * A function which implements logic to determine whether a given {@link Order} is eligible for
+ * a particular shipping method.
+ *
+ * @docsCategory shipping
+ * @docsPage ShippingEligibilityChecker
+ */
+export type CheckShippingEligibilityCheckerFn<T extends ShippingEligibilityCheckerArgs> = (
+    order: Order,
+    args: ConfigArgValues<T>,
+) => boolean | Promise<boolean>;

+ 7 - 3
packages/core/src/entity/shipping-method/shipping-method.entity.ts

@@ -4,7 +4,10 @@ import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
 
 
 import { ChannelAware, SoftDeletable } from '../../common/types/common-types';
 import { ChannelAware, SoftDeletable } from '../../common/types/common-types';
 import { getConfig } from '../../config/config-helpers';
 import { getConfig } from '../../config/config-helpers';
-import { ShippingCalculator, ShippingPrice } from '../../config/shipping-method/shipping-calculator';
+import {
+    ShippingCalculationResult,
+    ShippingCalculator,
+} from '../../config/shipping-method/shipping-calculator';
 import { ShippingEligibilityChecker } from '../../config/shipping-method/shipping-eligibility-checker';
 import { ShippingEligibilityChecker } from '../../config/shipping-method/shipping-eligibility-checker';
 import { VendureEntity } from '../base/base.entity';
 import { VendureEntity } from '../base/base.entity';
 import { Channel } from '../channel/channel.entity';
 import { Channel } from '../channel/channel.entity';
@@ -48,13 +51,14 @@ export class ShippingMethod extends VendureEntity implements ChannelAware, SoftD
     @JoinTable()
     @JoinTable()
     channels: Channel[];
     channels: Channel[];
 
 
-    async apply(order: Order): Promise<ShippingPrice | undefined> {
+    async apply(order: Order): Promise<ShippingCalculationResult | undefined> {
         const calculator = this.allCalculators[this.calculator.code];
         const calculator = this.allCalculators[this.calculator.code];
         if (calculator) {
         if (calculator) {
-            const { price, priceWithTax } = await calculator.calculate(order, this.calculator.args);
+            const { price, priceWithTax, metadata } = await calculator.calculate(order, this.calculator.args);
             return {
             return {
                 price: Math.round(price),
                 price: Math.round(price),
                 priceWithTax: Math.round(priceWithTax),
                 priceWithTax: Math.round(priceWithTax),
+                metadata,
             };
             };
         }
         }
     }
     }

+ 4 - 4
packages/core/src/service/helpers/order-calculator/order-calculator.ts

@@ -8,7 +8,7 @@ import { Connection } from 'typeorm';
 
 
 import { RequestContext } from '../../../api/common/request-context';
 import { RequestContext } from '../../../api/common/request-context';
 import { idsAreEqual } from '../../../common/utils';
 import { idsAreEqual } from '../../../common/utils';
-import { PromotionUtils, ShippingPrice } from '../../../config';
+import { PromotionUtils, ShippingCalculationResult } from '../../../config';
 import { ConfigService } from '../../../config/config.service';
 import { ConfigService } from '../../../config/config.service';
 import { OrderLine, ProductVariant } from '../../../entity';
 import { OrderLine, ProductVariant } from '../../../entity';
 import { Order } from '../../../entity/order/order.entity';
 import { Order } from '../../../entity/order/order.entity';
@@ -134,13 +134,13 @@ export class OrderCalculator {
         const results = await this.shippingCalculator.getEligibleShippingMethods(ctx, order);
         const results = await this.shippingCalculator.getEligibleShippingMethods(ctx, order);
         const currentShippingMethod = order.shippingMethod;
         const currentShippingMethod = order.shippingMethod;
         if (results && results.length && currentShippingMethod) {
         if (results && results.length && currentShippingMethod) {
-            let selected: { method: ShippingMethod; price: ShippingPrice } | undefined;
+            let selected: { method: ShippingMethod; result: ShippingCalculationResult } | undefined;
             selected = results.find(r => idsAreEqual(r.method.id, currentShippingMethod.id));
             selected = results.find(r => idsAreEqual(r.method.id, currentShippingMethod.id));
             if (!selected) {
             if (!selected) {
                 selected = results[0];
                 selected = results[0];
             }
             }
-            order.shipping = selected.price.price;
-            order.shippingWithTax = selected.price.priceWithTax;
+            order.shipping = selected.result.price;
+            order.shippingWithTax = selected.result.priceWithTax;
         }
         }
     }
     }
 
 

+ 7 - 7
packages/core/src/service/helpers/shipping-calculator/shipping-calculator.ts

@@ -1,7 +1,7 @@
 import { Injectable } from '@nestjs/common';
 import { Injectable } from '@nestjs/common';
 
 
 import { RequestContext } from '../../../api/common/request-context';
 import { RequestContext } from '../../../api/common/request-context';
-import { ShippingPrice } from '../../../config/shipping-method/shipping-calculator';
+import { ShippingCalculationResult } from '../../../config/shipping-method/shipping-calculator';
 import { Order } from '../../../entity/order/order.entity';
 import { Order } from '../../../entity/order/order.entity';
 import { ShippingMethod } from '../../../entity/shipping-method/shipping-method.entity';
 import { ShippingMethod } from '../../../entity/shipping-method/shipping-method.entity';
 import { ShippingMethodService } from '../../services/shipping-method.service';
 import { ShippingMethodService } from '../../services/shipping-method.service';
@@ -17,18 +17,18 @@ export class ShippingCalculator {
     async getEligibleShippingMethods(
     async getEligibleShippingMethods(
         ctx: RequestContext,
         ctx: RequestContext,
         order: Order,
         order: Order,
-    ): Promise<Array<{ method: ShippingMethod; price: ShippingPrice }>> {
+    ): Promise<Array<{ method: ShippingMethod; result: ShippingCalculationResult }>> {
         const shippingMethods = this.shippingMethodService.getActiveShippingMethods(ctx.channel);
         const shippingMethods = this.shippingMethodService.getActiveShippingMethods(ctx.channel);
-        const eligibleMethods: Array<{ method: ShippingMethod; price: ShippingPrice }> = [];
+        const eligibleMethods: Array<{ method: ShippingMethod; result: ShippingCalculationResult }> = [];
         for (const method of shippingMethods) {
         for (const method of shippingMethods) {
             const eligible = await method.test(order);
             const eligible = await method.test(order);
             if (eligible) {
             if (eligible) {
-                const price = await method.apply(order);
-                if (price) {
-                    eligibleMethods.push({ method, price });
+                const result = await method.apply(order);
+                if (result) {
+                    eligibleMethods.push({ method, result });
                 }
                 }
             }
             }
         }
         }
-        return eligibleMethods.sort((a, b) => a.price.price - b.price.price);
+        return eligibleMethods.sort((a, b) => a.result.price - b.result.price);
     }
     }
 }
 }

+ 8 - 4
packages/core/src/service/services/order-testing.service.ts

@@ -48,10 +48,13 @@ export class OrderTestingService {
         });
         });
         const mockOrder = await this.buildMockOrder(ctx, input.shippingAddress, input.lines);
         const mockOrder = await this.buildMockOrder(ctx, input.shippingAddress, input.lines);
         const eligible = await shippingMethod.test(mockOrder);
         const eligible = await shippingMethod.test(mockOrder);
-        const price = eligible ? await shippingMethod.apply(mockOrder) : undefined;
+        const result = eligible ? await shippingMethod.apply(mockOrder) : undefined;
         return {
         return {
             eligible,
             eligible,
-            price,
+            quote: result && {
+                ...result,
+                description: shippingMethod.description,
+            },
         };
         };
     }
     }
 
 
@@ -67,9 +70,10 @@ export class OrderTestingService {
         const eligibleMethods = await this.shippingCalculator.getEligibleShippingMethods(ctx, mockOrder);
         const eligibleMethods = await this.shippingCalculator.getEligibleShippingMethods(ctx, mockOrder);
         return eligibleMethods.map(result => ({
         return eligibleMethods.map(result => ({
             id: result.method.id as string,
             id: result.method.id as string,
-            price: result.price.price,
-            priceWithTax: result.price.priceWithTax,
+            price: result.result.price,
+            priceWithTax: result.result.priceWithTax,
             description: result.method.description,
             description: result.method.description,
+            metadata: result.result.metadata,
         }));
         }));
     }
     }
 
 

+ 6 - 5
packages/core/src/service/services/order.service.ts

@@ -305,11 +305,12 @@ export class OrderService {
     async getEligibleShippingMethods(ctx: RequestContext, orderId: ID): Promise<ShippingMethodQuote[]> {
     async getEligibleShippingMethods(ctx: RequestContext, orderId: ID): Promise<ShippingMethodQuote[]> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         const order = await this.getOrderOrThrow(ctx, orderId);
         const eligibleMethods = await this.shippingCalculator.getEligibleShippingMethods(ctx, order);
         const eligibleMethods = await this.shippingCalculator.getEligibleShippingMethods(ctx, order);
-        return eligibleMethods.map(result => ({
-            id: result.method.id as string,
-            price: result.price.price,
-            priceWithTax: result.price.priceWithTax,
-            description: result.method.description,
+        return eligibleMethods.map(eligible => ({
+            id: eligible.method.id as string,
+            price: eligible.result.price,
+            priceWithTax: eligible.result.priceWithTax,
+            description: eligible.method.description,
+            metadata: eligible.result.metadata,
         }));
         }));
     }
     }
 
 

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
schema-admin.json


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
schema-shop.json


Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů