Browse Source

fix(core): Clear shippingLines if no eligible ShippingMethods exist

Closes #1195
Michael Bromley 4 years ago
parent
commit
f9bc532d5f

+ 15 - 0
packages/core/e2e/fixtures/test-shipping-eligibility-checkers.ts

@@ -0,0 +1,15 @@
+import { LanguageCode } from '@vendure/common/lib/generated-types';
+import { ShippingEligibilityChecker } from '@vendure/core';
+
+export const countryCodeShippingEligibilityChecker = new ShippingEligibilityChecker({
+    code: 'country-code-shipping-eligibility-checker',
+    description: [{ languageCode: LanguageCode.en, value: 'Country Shipping Eligibility Checker' }],
+    args: {
+        countryCode: {
+            type: 'string',
+        },
+    },
+    check: (ctx, order, args) => {
+        return order.shippingAddress?.countryCode === args.countryCode;
+    },
+});

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

@@ -6354,6 +6354,12 @@ export type GetTaxRatesQuery = {
     taxRates: Pick<TaxRateList, 'totalItems'> & { items: Array<TaxRateFragment> };
 };
 
+export type GetShippingMethodListQueryVariables = Exact<{ [key: string]: never }>;
+
+export type GetShippingMethodListQuery = {
+    shippingMethods: Pick<ShippingMethodList, 'totalItems'> & { items: Array<ShippingMethodFragment> };
+};
+
 export type CancelJobMutationVariables = Exact<{
     id: Scalars['ID'];
 }>;
@@ -6780,12 +6786,6 @@ export type LogoutMutationVariables = Exact<{ [key: string]: never }>;
 
 export type LogoutMutation = { logout: Pick<Success, 'success'> };
 
-export type GetShippingMethodListQueryVariables = Exact<{ [key: string]: never }>;
-
-export type GetShippingMethodListQuery = {
-    shippingMethods: Pick<ShippingMethodList, 'totalItems'> & { items: Array<ShippingMethodFragment> };
-};
-
 export type GetShippingMethodQueryVariables = Exact<{
     id: Scalars['ID'];
 }>;
@@ -8696,6 +8696,15 @@ export namespace GetTaxRates {
     export type Items = NonNullable<NonNullable<NonNullable<GetTaxRatesQuery['taxRates']>['items']>[number]>;
 }
 
+export namespace GetShippingMethodList {
+    export type Variables = GetShippingMethodListQueryVariables;
+    export type Query = GetShippingMethodListQuery;
+    export type ShippingMethods = NonNullable<GetShippingMethodListQuery['shippingMethods']>;
+    export type Items = NonNullable<
+        NonNullable<NonNullable<GetShippingMethodListQuery['shippingMethods']>['items']>[number]
+    >;
+}
+
 export namespace CancelJob {
     export type Variables = CancelJobMutationVariables;
     export type Mutation = CancelJobMutation;
@@ -9152,15 +9161,6 @@ export namespace Logout {
     export type Logout = NonNullable<LogoutMutation['logout']>;
 }
 
-export namespace GetShippingMethodList {
-    export type Variables = GetShippingMethodListQueryVariables;
-    export type Query = GetShippingMethodListQuery;
-    export type ShippingMethods = NonNullable<GetShippingMethodListQuery['shippingMethods']>;
-    export type Items = NonNullable<
-        NonNullable<NonNullable<GetShippingMethodListQuery['shippingMethods']>['items']>[number]
-    >;
-}
-
 export namespace GetShippingMethod {
     export type Variables = GetShippingMethodQueryVariables;
     export type Query = GetShippingMethodQuery;

+ 11 - 0
packages/core/e2e/graphql/shared-definitions.ts

@@ -922,3 +922,14 @@ export const GET_TAX_RATES_LIST = gql`
     }
     ${TAX_RATE_FRAGMENT}
 `;
+export const GET_SHIPPING_METHOD_LIST = gql`
+    query GetShippingMethodList {
+        shippingMethods {
+            items {
+                ...ShippingMethod
+            }
+            totalItems
+        }
+    }
+    ${SHIPPING_METHOD_FRAGMENT}
+`;

+ 1 - 12
packages/core/e2e/shipping-method.e2e-spec.ts

@@ -29,6 +29,7 @@ import {
 import {
     CREATE_SHIPPING_METHOD,
     DELETE_SHIPPING_METHOD,
+    GET_SHIPPING_METHOD_LIST,
     UPDATE_SHIPPING_METHOD,
 } from './graphql/shared-definitions';
 
@@ -339,18 +340,6 @@ describe('ShippingMethod resolver', () => {
     });
 });
 
-const GET_SHIPPING_METHOD_LIST = gql`
-    query GetShippingMethodList {
-        shippingMethods {
-            items {
-                ...ShippingMethod
-            }
-            totalItems
-        }
-    }
-    ${SHIPPING_METHOD_FRAGMENT}
-`;
-
 const GET_SHIPPING_METHOD = gql`
     query GetShippingMethod($id: ID!) {
         shippingMethod(id: $id) {

+ 148 - 1
packages/core/e2e/shop-order.e2e-spec.ts

@@ -1,26 +1,38 @@
 /* tslint:disable:no-non-null-assertion */
 import { pick } from '@vendure/common/lib/pick';
-import { Asset, mergeConfig } from '@vendure/core';
+import {
+    Asset,
+    defaultShippingCalculator,
+    defaultShippingEligibilityChecker,
+    mergeConfig,
+} from '@vendure/core';
 import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
 import gql from 'graphql-tag';
 import path from 'path';
 
 import { initialData } from '../../../e2e-common/e2e-initial-data';
 import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
+import { manualFulfillmentHandler } from '../src/index';
 
 import {
     testErrorPaymentMethod,
     testFailingPaymentMethod,
     testSuccessfulPaymentMethod,
 } from './fixtures/test-payment-methods';
+import { countryCodeShippingEligibilityChecker } from './fixtures/test-shipping-eligibility-checkers';
 import {
     AttemptLogin,
     CreateAddressInput,
+    CreateShippingMethod,
+    CreateShippingMethodInput,
     DeleteProduct,
     DeleteProductVariant,
+    DeleteShippingMethod,
     GetCountryList,
     GetCustomer,
     GetCustomerList,
+    GetShippingMethodList,
+    LanguageCode,
     UpdateCountry,
     UpdateProduct,
     UpdateProductVariants,
@@ -53,11 +65,14 @@ import {
 } from './graphql/generated-e2e-shop-types';
 import {
     ATTEMPT_LOGIN,
+    CREATE_SHIPPING_METHOD,
     DELETE_PRODUCT,
     DELETE_PRODUCT_VARIANT,
+    DELETE_SHIPPING_METHOD,
     GET_COUNTRY_LIST,
     GET_CUSTOMER,
     GET_CUSTOMER_LIST,
+    GET_SHIPPING_METHOD_LIST,
     UPDATE_COUNTRY,
     UPDATE_PRODUCT,
     UPDATE_PRODUCT_VARIANTS,
@@ -96,6 +111,12 @@ describe('Shop orders', () => {
                     testErrorPaymentMethod,
                 ],
             },
+            shippingOptions: {
+                shippingEligibilityCheckers: [
+                    defaultShippingEligibilityChecker,
+                    countryCodeShippingEligibilityChecker,
+                ],
+            },
             customFields: {
                 Order: [
                     { name: 'giftWrap', type: 'boolean', defaultValue: false },
@@ -1843,6 +1864,132 @@ describe('Shop orders', () => {
             expect(transitionOrderToState!.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
         });
     });
+
+    // https://github.com/vendure-ecommerce/vendure/issues/1195
+    describe('shipping method invalidation', () => {
+        let GBShippingMethodId: string;
+        let ATShippingMethodId: string;
+
+        beforeAll(async () => {
+            // First we will remove all ShippingMethods and set up 2 specialized ones
+            const { shippingMethods } = await adminClient.query<GetShippingMethodList.Query>(
+                GET_SHIPPING_METHOD_LIST,
+            );
+            for (const method of shippingMethods.items) {
+                await adminClient.query<DeleteShippingMethod.Mutation, DeleteShippingMethod.Variables>(
+                    DELETE_SHIPPING_METHOD,
+                    {
+                        id: method.id,
+                    },
+                );
+            }
+
+            function createCountryCodeShippingMethodInput(countryCode: string): CreateShippingMethodInput {
+                return {
+                    code: `${countryCode}-shipping`,
+                    translations: [
+                        { languageCode: LanguageCode.en, name: `${countryCode} shipping`, description: '' },
+                    ],
+                    fulfillmentHandler: manualFulfillmentHandler.code,
+                    checker: {
+                        code: countryCodeShippingEligibilityChecker.code,
+                        arguments: [{ name: 'countryCode', value: countryCode }],
+                    },
+                    calculator: {
+                        code: defaultShippingCalculator.code,
+                        arguments: [
+                            { name: 'rate', value: '1000' },
+                            { name: 'taxRate', value: '0' },
+                            { name: 'includesTax', value: 'auto' },
+                        ],
+                    },
+                };
+            }
+
+            // Now create 2 shipping methods, valid only for a single country
+            const result1 = await adminClient.query<
+                CreateShippingMethod.Mutation,
+                CreateShippingMethod.Variables
+            >(CREATE_SHIPPING_METHOD, {
+                input: createCountryCodeShippingMethodInput('GB'),
+            });
+            GBShippingMethodId = result1.createShippingMethod.id;
+            const result2 = await adminClient.query<
+                CreateShippingMethod.Mutation,
+                CreateShippingMethod.Variables
+            >(CREATE_SHIPPING_METHOD, {
+                input: createCountryCodeShippingMethodInput('AT'),
+            });
+            ATShippingMethodId = result2.createShippingMethod.id;
+
+            // Now create an order to GB and set the GB shipping method
+            const { addItemToOrder } = await shopClient.query<
+                AddItemToOrder.Mutation,
+                AddItemToOrder.Variables
+            >(ADD_ITEM_TO_ORDER, {
+                productVariantId: 'T_1',
+                quantity: 1,
+            });
+            await shopClient.query<SetCustomerForOrder.Mutation, SetCustomerForOrder.Variables>(
+                SET_CUSTOMER,
+                {
+                    input: {
+                        emailAddress: 'test-2@test.com',
+                        firstName: 'Test',
+                        lastName: 'Person 2',
+                    },
+                },
+            );
+            await shopClient.query<SetShippingAddress.Mutation, SetShippingAddress.Variables>(
+                SET_SHIPPING_ADDRESS,
+                {
+                    input: {
+                        streetLine1: '12 the street',
+                        countryCode: 'GB',
+                    },
+                },
+            );
+            await shopClient.query<SetShippingMethod.Mutation, SetShippingMethod.Variables>(
+                SET_SHIPPING_METHOD,
+                {
+                    id: GBShippingMethodId,
+                },
+            );
+        });
+
+        it('if selected method no longer eligible, next best is set automatically', async () => {
+            const result1 = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
+            expect(result1.activeOrder?.shippingLines[0].shippingMethod.id).toBe(GBShippingMethodId);
+
+            await shopClient.query<SetShippingAddress.Mutation, SetShippingAddress.Variables>(
+                SET_SHIPPING_ADDRESS,
+                {
+                    input: {
+                        streetLine1: '12 the street',
+                        countryCode: 'AT',
+                    },
+                },
+            );
+
+            const result2 = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
+            expect(result2.activeOrder?.shippingLines[0].shippingMethod.id).toBe(ATShippingMethodId);
+        });
+
+        it('if no method is eligible, shipping lines are cleared', async () => {
+            await shopClient.query<SetShippingAddress.Mutation, SetShippingAddress.Variables>(
+                SET_SHIPPING_ADDRESS,
+                {
+                    input: {
+                        streetLine1: '12 the street',
+                        countryCode: 'US',
+                    },
+                },
+            );
+
+            const result = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
+            expect(result.activeOrder?.shippingLines).toEqual([]);
+        });
+    });
 });
 
 const GET_ORDER_CUSTOM_FIELDS = gql`

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

@@ -411,6 +411,8 @@ export class OrderCalculator {
                     taxRate: cheapest.result.taxRate,
                 },
             ];
+        } else {
+            order.shippingLines = [];
         }
     }