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

Merge branch 'master' into minor

Michael Bromley 3 лет назад
Родитель
Сommit
a9398ee664
56 измененных файлов с 687 добавлено и 284 удалено
  1. 19 0
      CHANGELOG.md
  2. 1 1
      docs/content/developer-guide/authentication.md
  3. 5 1
      docs/content/developer-guide/deployment.md
  4. 3 1
      docs/content/developer-guide/promotions.md
  5. 1 1
      lerna.json
  6. 3 3
      packages/admin-ui-plugin/package.json
  7. 2 2
      packages/admin-ui/package.json
  8. 4 0
      packages/admin-ui/src/lib/catalog/src/components/facet-detail/facet-detail.component.ts
  9. 1 1
      packages/admin-ui/src/lib/core/src/common/version.ts
  10. 7 0
      packages/admin-ui/src/lib/core/src/data/data.module.ts
  11. 1 0
      packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.ts
  12. 5 2
      packages/admin-ui/src/lib/customer/src/providers/routing/customer-resolver.ts
  13. 4 4
      packages/asset-server-plugin/package.json
  14. 6 2
      packages/asset-server-plugin/src/s3-asset-storage-strategy.ts
  15. 1 1
      packages/common/package.json
  16. 17 1
      packages/core/e2e/draft-order.e2e-spec.ts
  17. 1 1
      packages/core/e2e/facet.e2e-spec.ts
  18. 50 31
      packages/core/e2e/graphql/generated-e2e-admin-types.ts
  19. 23 0
      packages/core/e2e/graphql/shared-definitions.ts
  20. 281 4
      packages/core/e2e/order-modification.e2e-spec.ts
  21. 1 17
      packages/core/e2e/product.e2e-spec.ts
  22. 1 8
      packages/core/e2e/promotion.e2e-spec.ts
  23. 23 0
      packages/core/e2e/zone.e2e-spec.ts
  24. 3 3
      packages/core/package.json
  25. 22 5
      packages/core/src/api/api-internal-modules.ts
  26. 20 0
      packages/core/src/api/resolvers/entity/zone-entity.resolver.ts
  27. 5 5
      packages/core/src/connection/transactional-connection.ts
  28. 3 3
      packages/core/src/i18n/messages/de.json
  29. 1 1
      packages/core/src/i18n/messages/en.json
  30. 10 1
      packages/core/src/service/helpers/external-authentication/external-authentication.service.ts
  31. 29 42
      packages/core/src/service/helpers/order-modifier/order-modifier.ts
  32. 2 1
      packages/core/src/service/services/channel.service.ts
  33. 4 2
      packages/core/src/service/services/collection.service.ts
  34. 2 1
      packages/core/src/service/services/country.service.ts
  35. 2 1
      packages/core/src/service/services/customer-group.service.ts
  36. 2 1
      packages/core/src/service/services/customer.service.ts
  37. 14 5
      packages/core/src/service/services/facet-value.service.ts
  38. 3 1
      packages/core/src/service/services/facet.service.ts
  39. 4 2
      packages/core/src/service/services/history.service.ts
  40. 4 1
      packages/core/src/service/services/payment-method.service.ts
  41. 32 27
      packages/core/src/service/services/payment.service.ts
  42. 2 1
      packages/core/src/service/services/product-option-group.service.ts
  43. 2 1
      packages/core/src/service/services/product-option.service.ts
  44. 2 1
      packages/core/src/service/services/role.service.ts
  45. 2 1
      packages/core/src/service/services/tax-category.service.ts
  46. 2 1
      packages/core/src/service/services/tax-rate.service.ts
  47. 2 2
      packages/core/src/service/services/zone.service.ts
  48. 3 3
      packages/create/package.json
  49. 9 9
      packages/dev-server/package.json
  50. 3 3
      packages/elasticsearch-plugin/package.json
  51. 3 3
      packages/email-plugin/package.json
  52. 3 3
      packages/job-queue-plugin/package.json
  53. 4 4
      packages/payments-plugin/package.json
  54. 3 3
      packages/testing/package.json
  55. 4 4
      packages/ui-devkit/package.json
  56. 21 62
      yarn.lock

+ 19 - 0
CHANGELOG.md

@@ -1,3 +1,22 @@
+## <small>1.8.3 (2022-11-10)</small>
+
+
+#### Fixes
+
+* **admin-ui** Fix critical FacetValue deletion issue ([1e443e2](https://github.com/vendure-ecommerce/vendure/commit/1e443e2))
+* **asset-server-plugin** Better error message for s3 bucket errors ([adf58b4](https://github.com/vendure-ecommerce/vendure/commit/adf58b4))
+* **asset-server-plugin** Update Sharp version to fix mac m1 issue ([b76515b](https://github.com/vendure-ecommerce/vendure/commit/b76515b)), closes [#1866](https://github.com/vendure-ecommerce/vendure/issues/1866)
+* **core** Add resolver for `Zone.members` field ([3b67e61](https://github.com/vendure-ecommerce/vendure/commit/3b67e61))
+* **core** Allow ext. auth to find customer on any channel ([2445a89](https://github.com/vendure-ecommerce/vendure/commit/2445a89)), closes [#961](https://github.com/vendure-ecommerce/vendure/issues/961)
+* **core** Ensure deleted entities in events include ids ([265bb15](https://github.com/vendure-ecommerce/vendure/commit/265bb15))
+* **core** Fix foreign key violation error when removing draft order line ([403ab2c](https://github.com/vendure-ecommerce/vendure/commit/403ab2c)), closes [#1855](https://github.com/vendure-ecommerce/vendure/issues/1855)
+* **core** Fix multiple refunds bug when modifying orders ([f18fedd](https://github.com/vendure-ecommerce/vendure/commit/f18fedd)), closes [#1753](https://github.com/vendure-ecommerce/vendure/issues/1753)
+* **core** Fix order incorrect refund amount when modifying Order ([b1486e8](https://github.com/vendure-ecommerce/vendure/commit/b1486e8)), closes [#1865](https://github.com/vendure-ecommerce/vendure/issues/1865)
+* **core** Fix regression from OrderItem ordering change ([1d552b3](https://github.com/vendure-ecommerce/vendure/commit/1d552b3))
+* **core** Improved feedback on FacetValue deletion confirmation ([03419cb](https://github.com/vendure-ecommerce/vendure/commit/03419cb))
+* **core** Make order modification items deterministic ([14d0a22](https://github.com/vendure-ecommerce/vendure/commit/14d0a22)), closes [#1760](https://github.com/vendure-ecommerce/vendure/issues/1760)
+* **core** Publish event when deleting FacetValue ([0ece03b](https://github.com/vendure-ecommerce/vendure/commit/0ece03b))
+
 ## <small>1.8.2 (2022-11-01)</small>
 
 

+ 1 - 1
docs/content/developer-guide/authentication.md

@@ -329,7 +329,7 @@ This example uses [Keycloak](https://www.keycloak.org/), a popular open-source i
 
 ### Configure a login page & Admin UI
 
-In this example we'll assume the login page is hosted at `http://intranet/login`. We'll also assume that a "login to Vendure" button has been added to that pagem and that the page is using the [Keycloak JavaScript adapter](https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter), which can be used to get the current user's authorization token:
+In this example we'll assume the login page is hosted at `http://intranet/login`. We'll also assume that a "login to Vendure" button has been added to that page and that the page is using the [Keycloak JavaScript adapter](https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter), which can be used to get the current user's authorization token:
 
 ```JavaScript
 vendureLoginButton.addEventListener('click', () => {

+ 5 - 1
docs/content/developer-guide/deployment.md

@@ -14,7 +14,11 @@ The bare minimum requirements are:
 
 A typical pattern is to run the Vendure app on the server, e.g. at `http://localhost:3000` and then use [nginx as a reverse proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) to direct requests from the Internet to the Vendure application.
 
-Here is a good guide to setting up a production-ready server for an app such as Vendure: https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-18-04
+Here is a [general guide to setting up a production-ready server](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-18-04) for an app such as Vendure.
+
+{{< alert >}}
+You can find more information & discussion about platform-specific deployments in our [GitHub Discussions Deployment category](https://github.com/vendure-ecommerce/vendure/discussions/categories/deployment).
+{{< /alert >}}
 
 ## Database Timezone
 

+ 3 - 1
docs/content/developer-guide/promotions.md

@@ -190,7 +190,9 @@ export const freeGiftAction = new PromotionItemAction({
     // This part is responsible for ensuring the variants marked as 
     // "free gifts" have their price reduced to zero.  
     if (lineContainsIds(args.productVariantIds, orderLine)) {
-      const unitPrice = orderLine.unitPrice;
+      const unitPrice = orderLine.productVariant.listPriceIncludesTax
+        ? orderLine.unitPriceWithTax
+        : orderLine.unitPrice;
       return -unitPrice;
     }
     return 0;

+ 1 - 1
lerna.json

@@ -2,7 +2,7 @@
   "packages": [
     "packages/*"
   ],
-  "version": "1.8.2",
+  "version": "1.8.3",
   "npmClient": "yarn",
   "useWorkspaces": true,
   "command": {

+ 3 - 3
packages/admin-ui-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/admin-ui-plugin",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
   "files": [
@@ -21,8 +21,8 @@
   "devDependencies": {
     "@types/express": "^4.17.8",
     "@types/fs-extra": "^9.0.1",
-    "@vendure/common": "^1.8.2",
-    "@vendure/core": "^1.8.2",
+    "@vendure/common": "^1.8.3",
+    "@vendure/core": "^1.8.3",
     "express": "^4.17.1",
     "rimraf": "^3.0.2",
     "typescript": "4.3.5"

+ 2 - 2
packages/admin-ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/admin-ui",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "license": "MIT",
   "scripts": {
     "ng": "ng",
@@ -39,7 +39,7 @@
     "@ng-select/ng-select": "^7.2.0",
     "@ngx-translate/core": "^13.0.0",
     "@ngx-translate/http-loader": "^6.0.0",
-    "@vendure/common": "^1.8.2",
+    "@vendure/common": "^1.8.3",
     "@webcomponents/custom-elements": "^1.4.3",
     "apollo-angular": "^2.6.0",
     "apollo-upload-client": "^16.0.0",

+ 4 - 0
packages/admin-ui/src/lib/catalog/src/components/facet-detail/facet-detail.component.ts

@@ -244,6 +244,10 @@ export class FacetDetailComponent
             )
             .subscribe(
                 () => {
+                    const valuesFormArray = this.detailForm.get('values') as FormArray | null;
+                    if (valuesFormArray) {
+                        valuesFormArray.removeAt(index);
+                    }
                     this.notificationService.success(_('common.notify-delete-success'), {
                         entity: 'FacetValue',
                     });

+ 1 - 1
packages/admin-ui/src/lib/core/src/common/version.ts

@@ -1,2 +1,2 @@
 // Auto-generated by the set-version.js script.
-export const ADMIN_UI_VERSION = '1.8.2';
+export const ADMIN_UI_VERSION = '1.8.3';

+ 7 - 0
packages/admin-ui/src/lib/core/src/data/data.module.ts

@@ -39,6 +39,13 @@ export function createApollo(
                     },
                 },
             },
+            Facet: {
+                fields: {
+                    values: {
+                        merge: (existing, incoming) => incoming,
+                    },
+                },
+            },
         },
     });
     apolloCache.writeQuery({

+ 1 - 0
packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.ts

@@ -512,6 +512,7 @@ export class CustomerDetailComponent
             .getCustomer(this.id, {
                 take: this.ordersPerPage,
                 skip: (this.currentOrdersPage - 1) * this.ordersPerPage,
+                sort: { orderPlacedAt: SortOrder.DESC },
             })
             .single$.pipe(
                 map(data => data.customer),

+ 5 - 2
packages/admin-ui/src/lib/customer/src/providers/routing/customer-resolver.ts

@@ -1,6 +1,6 @@
 import { Injectable } from '@angular/core';
 import { Router } from '@angular/router';
-import { BaseEntityResolver } from '@vendure/admin-ui/core';
+import { BaseEntityResolver, SortOrder } from '@vendure/admin-ui/core';
 import { Customer } from '@vendure/admin-ui/core';
 import { DataService } from '@vendure/admin-ui/core';
 
@@ -24,7 +24,10 @@ export class CustomerResolver extends BaseEntityResolver<Customer.Fragment> {
                 addresses: null,
                 user: null,
             },
-            id => dataService.customer.getCustomer(id).mapStream(data => data.customer),
+            id =>
+                dataService.customer
+                    .getCustomer(id, { sort: { orderPlacedAt: SortOrder.DESC } })
+                    .mapStream(data => data.customer),
         );
     }
 }

+ 4 - 4
packages/asset-server-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/asset-server-plugin",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
   "files": [
@@ -24,8 +24,8 @@
     "@types/fs-extra": "^9.0.8",
     "@types/node-fetch": "^2.5.8",
     "@types/sharp": "^0.30.4",
-    "@vendure/common": "^1.8.2",
-    "@vendure/core": "^1.8.2",
+    "@vendure/common": "^1.8.3",
+    "@vendure/core": "^1.8.3",
     "aws-sdk": "^2.856.0",
     "express": "^4.17.1",
     "node-fetch": "^2.6.1",
@@ -35,6 +35,6 @@
   "dependencies": {
     "file-type": "^16.2.0",
     "fs-extra": "^10.0.0",
-    "sharp": "~0.30.7"
+    "sharp": "~0.31.2"
   }
 }

+ 6 - 2
packages/asset-server-plugin/src/s3-asset-storage-strategy.ts

@@ -294,14 +294,18 @@ export class S3AssetStorageStrategy implements AssetStorageStrategy {
             bucketExists = true;
             Logger.verbose(`Found S3 bucket "${bucket}"`, loggerCtx);
         } catch (e) {
-            Logger.verbose(`Could not find bucket "${bucket}". Attempting to create...`);
+            Logger.verbose(`Could not find bucket "${bucket}: ${e.message ?? ''}". Attempting to create...`);
         }
         if (!bucketExists) {
             try {
                 await this.s3.createBucket({ Bucket: bucket, ACL: 'private' }).promise();
                 Logger.verbose(`Created S3 bucket "${bucket}"`, loggerCtx);
             } catch (e) {
-                Logger.error(`Could not find nor create the S3 bucket "${bucket}"`, loggerCtx, e.stack);
+                Logger.error(
+                    `Could not find nor create the S3 bucket "${bucket}: ${e.message ?? ''}"`,
+                    loggerCtx,
+                    e.stack,
+                );
             }
         }
     }

+ 1 - 1
packages/common/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/common",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "main": "index.js",
   "license": "MIT",
   "scripts": {

+ 17 - 1
packages/core/e2e/draft-order.e2e-spec.ts

@@ -113,7 +113,7 @@ describe('Draft Orders resolver', () => {
         draftOrder = addItemToDraftOrder;
     });
 
-    it('adjustDraftOrderLine', async () => {
+    it('adjustDraftOrderLine up', async () => {
         const { adjustDraftOrderLine } = await adminClient.query<
             Codegen.AdjustDraftOrderLineMutation,
             Codegen.AdjustDraftOrderLineMutationVariables
@@ -129,6 +129,22 @@ describe('Draft Orders resolver', () => {
         expect(adjustDraftOrderLine.lines[0].quantity).toBe(5);
     });
 
+    it('adjustDraftOrderLine down', async () => {
+        const { adjustDraftOrderLine } = await adminClient.query<
+            Codegen.AdjustDraftOrderLineMutation,
+            Codegen.AdjustDraftOrderLineMutationVariables
+        >(ADJUST_DRAFT_ORDER_LINE, {
+            orderId: draftOrder.id,
+            input: {
+                orderLineId: draftOrder.lines[0]!.id,
+                quantity: 2,
+            },
+        });
+
+        orderGuard.assertSuccess(adjustDraftOrderLine);
+        expect(adjustDraftOrderLine.lines[0].quantity).toBe(2);
+    });
+
     it('removeDraftOrderLine', async () => {
         const { removeDraftOrderLine } = await adminClient.query<
             Codegen.RemoveDraftOrderLineMutation,

+ 1 - 1
packages/core/e2e/facet.e2e-spec.ts

@@ -314,7 +314,7 @@ describe('Facet resolver', () => {
             expect(result1.deleteFacetValues).toEqual([
                 {
                     result: DeletionResult.NOT_DELETED,
-                    message: `The selected FacetValue is assigned to 1 Product, 1 ProductVariant`,
+                    message: `The FacetValue "compact" is assigned to 1 Product, 1 ProductVariant`,
                 },
             ]);
 

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

@@ -6937,6 +6937,24 @@ export type TransitionPaymentToStateMutationVariables = Exact<{
 
 export type TransitionPaymentToStateMutation = { transitionPaymentToState: PaymentFragment | Pick<PaymentStateTransitionError, 'errorCode' | 'message' | 'transitionError'> };
 
+export type GetProductVariantListQueryVariables = Exact<{
+  options?: Maybe<ProductVariantListOptions>;
+  productId?: Maybe<Scalars['ID']>;
+}>;
+
+
+export type GetProductVariantListQuery = { productVariants: (
+    Pick<ProductVariantList, 'totalItems'>
+    & { items: Array<Pick<ProductVariant, 'id' | 'name' | 'sku' | 'price' | 'priceWithTax'>> }
+  ) };
+
+export type DeletePromotionMutationVariables = Exact<{
+  id: Scalars['ID'];
+}>;
+
+
+export type DeletePromotionMutation = { deletePromotion: Pick<DeletionResponse, 'result'> };
+
 export type CancelJobMutationVariables = Exact<{
   id: Scalars['ID'];
 }>;
@@ -7265,17 +7283,6 @@ export type GetProductVariantQueryVariables = Exact<{
 
 export type GetProductVariantQuery = { productVariant?: Maybe<Pick<ProductVariant, 'id' | 'name'>> };
 
-export type GetProductVariantListQueryVariables = Exact<{
-  options?: Maybe<ProductVariantListOptions>;
-  productId?: Maybe<Scalars['ID']>;
-}>;
-
-
-export type GetProductVariantListQuery = { productVariants: (
-    Pick<ProductVariantList, 'totalItems'>
-    & { items: Array<Pick<ProductVariant, 'id' | 'name' | 'sku' | 'price' | 'priceWithTax'>> }
-  ) };
-
 export type GetProductWithVariantListQueryVariables = Exact<{
   id?: Maybe<Scalars['ID']>;
   variantListOptions?: Maybe<ProductVariantListOptions>;
@@ -7290,13 +7297,6 @@ export type GetProductWithVariantListQuery = { product?: Maybe<(
     ) }
   )> };
 
-export type DeletePromotionMutationVariables = Exact<{
-  id: Scalars['ID'];
-}>;
-
-
-export type DeletePromotionMutation = { deletePromotion: Pick<DeletionResponse, 'result'> };
-
 export type GetPromotionListQueryVariables = Exact<{
   options?: Maybe<PromotionListOptions>;
 }>;
@@ -7623,6 +7623,17 @@ export type GetZoneQueryVariables = Exact<{
 
 export type GetZoneQuery = { zone?: Maybe<ZoneFragment> };
 
+export type GetActiveChannelWithZoneMembersQueryVariables = Exact<{ [key: string]: never; }>;
+
+
+export type GetActiveChannelWithZoneMembersQuery = { activeChannel: (
+    Pick<Channel, 'id'>
+    & { defaultShippingZone?: Maybe<(
+      Pick<Zone, 'id'>
+      & { members: Array<Pick<Country, 'name'>> }
+    )> }
+  ) };
+
 export type CreateZoneMutationVariables = Exact<{
   input: CreateZoneInput;
 }>;
@@ -8989,6 +9000,19 @@ export namespace TransitionPaymentToState {
   export type PaymentStateTransitionErrorInlineFragment = (DiscriminateUnion<(NonNullable<TransitionPaymentToStateMutation['transitionPaymentToState']>), { __typename?: 'PaymentStateTransitionError' }>);
 }
 
+export namespace GetProductVariantList {
+  export type Variables = GetProductVariantListQueryVariables;
+  export type Query = GetProductVariantListQuery;
+  export type ProductVariants = (NonNullable<GetProductVariantListQuery['productVariants']>);
+  export type Items = NonNullable<(NonNullable<(NonNullable<GetProductVariantListQuery['productVariants']>)['items']>)[number]>;
+}
+
+export namespace DeletePromotion {
+  export type Variables = DeletePromotionMutationVariables;
+  export type Mutation = DeletePromotionMutation;
+  export type DeletePromotion = (NonNullable<DeletePromotionMutation['deletePromotion']>);
+}
+
 export namespace CancelJob {
   export type Variables = CancelJobMutationVariables;
   export type Mutation = CancelJobMutation;
@@ -9268,13 +9292,6 @@ export namespace GetProductVariant {
   export type ProductVariant = (NonNullable<GetProductVariantQuery['productVariant']>);
 }
 
-export namespace GetProductVariantList {
-  export type Variables = GetProductVariantListQueryVariables;
-  export type Query = GetProductVariantListQuery;
-  export type ProductVariants = (NonNullable<GetProductVariantListQuery['productVariants']>);
-  export type Items = NonNullable<(NonNullable<(NonNullable<GetProductVariantListQuery['productVariants']>)['items']>)[number]>;
-}
-
 export namespace GetProductWithVariantList {
   export type Variables = GetProductWithVariantListQueryVariables;
   export type Query = GetProductWithVariantListQuery;
@@ -9283,12 +9300,6 @@ export namespace GetProductWithVariantList {
   export type Items = NonNullable<(NonNullable<(NonNullable<(NonNullable<GetProductWithVariantListQuery['product']>)['variantList']>)['items']>)[number]>;
 }
 
-export namespace DeletePromotion {
-  export type Variables = DeletePromotionMutationVariables;
-  export type Mutation = DeletePromotionMutation;
-  export type DeletePromotion = (NonNullable<DeletePromotionMutation['deletePromotion']>);
-}
-
 export namespace GetPromotionList {
   export type Variables = GetPromotionListQueryVariables;
   export type Query = GetPromotionListQuery;
@@ -9582,6 +9593,14 @@ export namespace GetZone {
   export type Zone = (NonNullable<GetZoneQuery['zone']>);
 }
 
+export namespace GetActiveChannelWithZoneMembers {
+  export type Variables = GetActiveChannelWithZoneMembersQueryVariables;
+  export type Query = GetActiveChannelWithZoneMembersQuery;
+  export type ActiveChannel = (NonNullable<GetActiveChannelWithZoneMembersQuery['activeChannel']>);
+  export type DefaultShippingZone = (NonNullable<(NonNullable<GetActiveChannelWithZoneMembersQuery['activeChannel']>)['defaultShippingZone']>);
+  export type Members = NonNullable<(NonNullable<(NonNullable<(NonNullable<GetActiveChannelWithZoneMembersQuery['activeChannel']>)['defaultShippingZone']>)['members']>)[number]>;
+}
+
 export namespace CreateZone {
   export type Variables = CreateZoneMutationVariables;
   export type Mutation = CreateZoneMutation;

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

@@ -972,3 +972,26 @@ export const TRANSITION_PAYMENT_TO_STATE = gql`
     }
     ${PAYMENT_FRAGMENT}
 `;
+
+export const GET_PRODUCT_VARIANT_LIST = gql`
+    query GetProductVariantLIST($options: ProductVariantListOptions, $productId: ID) {
+        productVariants(options: $options, productId: $productId) {
+            items {
+                id
+                name
+                sku
+                price
+                priceWithTax
+            }
+            totalItems
+        }
+    }
+`;
+
+export const DELETE_PROMOTION = gql`
+    mutation DeletePromotion($id: ID!) {
+        deletePromotion(id: $id) {
+            result
+        }
+    }
+`;

+ 281 - 4
packages/core/e2e/order-modification.e2e-spec.ts

@@ -8,6 +8,7 @@ import {
     freeShipping,
     manualFulfillmentHandler,
     mergeConfig,
+    minimumOrderAmount,
     orderFixedDiscount,
     orderPercentageDiscount,
     productsPercentageDiscount,
@@ -18,7 +19,7 @@ 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 { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
 
 import {
     failsToSettlePaymentMethod,
@@ -33,12 +34,18 @@ import {
     CreatePromotionMutation,
     CreatePromotionMutationVariables,
     CreateShippingMethod,
+    DeletePromotionMutation,
+    DeletePromotionMutationVariables,
     ErrorCode,
     GetOrder,
     GetOrderHistory,
+    GetOrderQuery,
+    GetOrderQueryVariables,
     GetOrderWithModifications,
     GetOrderWithModificationsQuery,
     GetOrderWithModificationsQueryVariables,
+    GetProductVariantListQuery,
+    GetProductVariantListQueryVariables,
     GetStockMovement,
     GlobalFlag,
     HistoryEntryType,
@@ -66,8 +73,10 @@ import {
     CREATE_FULFILLMENT,
     CREATE_PROMOTION,
     CREATE_SHIPPING_METHOD,
+    DELETE_PROMOTION,
     GET_ORDER,
     GET_ORDER_HISTORY,
+    GET_PRODUCT_VARIANT_LIST,
     GET_STOCK_MOVEMENT,
     UPDATE_CHANNEL,
     UPDATE_PRODUCT_VARIANTS,
@@ -153,7 +162,7 @@ describe('Order modification', () => {
                 ],
             },
             productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
-            customerCount: 2,
+            customerCount: 3,
         });
         await adminClient.asSuperAdmin();
 
@@ -1260,6 +1269,150 @@ describe('Order modification', () => {
         });
     });
 
+    // https://github.com/vendure-ecommerce/vendure/issues/1753
+    describe('refunds for multiple payments', () => {
+        let orderId2: string;
+        let orderLineId: string;
+        let additionalPaymentId: string;
+
+        beforeAll(async () => {
+            await adminClient.query<CreatePromotion.Mutation, CreatePromotion.Variables>(CREATE_PROMOTION, {
+                input: {
+                    name: '$5 off',
+                    couponCode: '5OFF',
+                    enabled: true,
+                    conditions: [],
+                    actions: [
+                        {
+                            code: orderFixedDiscount.code,
+                            arguments: [{ name: 'discount', value: '500' }],
+                        },
+                    ],
+                },
+            });
+            await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
+            await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
+                productVariantId: 'T_5',
+                quantity: 1,
+            } as any);
+            await proceedToArrangingPayment(shopClient);
+            const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
+            orderGuard.assertSuccess(order);
+            orderLineId = order.lines[0].id;
+            orderId2 = order.id;
+
+            const transitionOrderToState = await adminTransitionOrderToState(orderId2, 'Modifying');
+            orderGuard.assertSuccess(transitionOrderToState);
+            const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
+                MODIFY_ORDER,
+                {
+                    input: {
+                        dryRun: false,
+                        orderId: orderId2,
+                        adjustOrderLines: [{ orderLineId, quantity: 2 }],
+                    },
+                },
+            );
+            orderGuard.assertSuccess(modifyOrder);
+
+            await adminTransitionOrderToState(orderId2, 'ArrangingAdditionalPayment');
+
+            const { addManualPaymentToOrder } = await adminClient.query<
+                AddManualPayment.Mutation,
+                AddManualPayment.Variables
+            >(ADD_MANUAL_PAYMENT, {
+                input: {
+                    orderId: orderId2,
+                    method: 'test',
+                    transactionId: 'ABC123',
+                    metadata: {
+                        foo: 'bar',
+                    },
+                },
+            });
+            orderGuard.assertSuccess(addManualPaymentToOrder);
+            additionalPaymentId = addManualPaymentToOrder.payments?.[1].id!;
+
+            const transitionOrderToState2 = await adminTransitionOrderToState(orderId2, 'PaymentSettled');
+            orderGuard.assertSuccess(transitionOrderToState2);
+
+            expect(transitionOrderToState2.state).toBe('PaymentSettled');
+        });
+
+        it('apply couponCode to create first refund', async () => {
+            const transitionOrderToState = await adminTransitionOrderToState(orderId2, 'Modifying');
+            orderGuard.assertSuccess(transitionOrderToState);
+            const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
+                MODIFY_ORDER,
+                {
+                    input: {
+                        dryRun: false,
+                        orderId: orderId2,
+                        couponCodes: ['5OFF'],
+                        refund: {
+                            paymentId: additionalPaymentId,
+                            reason: 'test',
+                        },
+                    },
+                },
+            );
+            orderGuard.assertSuccess(modifyOrder);
+
+            expect(modifyOrder.payments?.length).toBe(2);
+            expect(modifyOrder?.payments?.find(p => p.id === additionalPaymentId)?.refunds).toEqual([
+                {
+                    id: 'T_4',
+                    paymentId: additionalPaymentId,
+                    state: 'Pending',
+                    total: 600,
+                },
+            ]);
+            expect(modifyOrder.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder));
+        });
+
+        it('reduce quantity to create second refund', async () => {
+            const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
+                MODIFY_ORDER,
+                {
+                    input: {
+                        dryRun: false,
+                        orderId: orderId2,
+                        adjustOrderLines: [{ orderLineId, quantity: 1 }],
+                        refund: {
+                            paymentId: additionalPaymentId,
+                            reason: 'test 2',
+                        },
+                    },
+                },
+            );
+            orderGuard.assertSuccess(modifyOrder);
+
+            expect(modifyOrder?.payments?.find(p => p.id === additionalPaymentId)?.refunds).toEqual([
+                {
+                    id: 'T_4',
+                    paymentId: additionalPaymentId,
+                    state: 'Pending',
+                    total: 600,
+                },
+                {
+                    id: 'T_5',
+                    paymentId: additionalPaymentId,
+                    state: 'Pending',
+                    total: 16649,
+                },
+            ]);
+            expect(modifyOrder?.payments?.find(p => p.id !== additionalPaymentId)?.refunds).toEqual([
+                {
+                    id: 'T_6',
+                    paymentId: 'T_15',
+                    state: 'Pending',
+                    total: 300,
+                },
+            ]);
+            expect(modifyOrder.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder));
+        });
+    });
+
     // https://github.com/vendure-ecommerce/vendure/issues/688 - 4th point
     it('correct additional payment when discounts applied', async () => {
         await adminClient.query<CreatePromotion.Mutation, CreatePromotion.Variables>(CREATE_PROMOTION, {
@@ -1405,8 +1558,8 @@ describe('Order modification', () => {
         });
     });
 
-    // https://github.com/vendure-ecommerce/vendure/issues/890
     describe('refund handling when promotions are active on order', () => {
+        // https://github.com/vendure-ecommerce/vendure/issues/890
         it('refunds correct amount when order-level promotion applied', async () => {
             await adminClient.query<CreatePromotion.Mutation, CreatePromotion.Variables>(CREATE_PROMOTION, {
                 input: {
@@ -1464,6 +1617,130 @@ describe('Order modification', () => {
             expect(modifyOrder.payments![0].refunds![0].total).toBe(order.lines[0].proratedUnitPriceWithTax);
             expect(modifyOrder.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder));
         });
+
+        // github.com/vendure-ecommerce/vendure/issues/1865
+        describe('issue 1865', () => {
+            const promoDiscount = 5000;
+            let promoId: string;
+            let orderId2: string;
+            beforeAll(async () => {
+                const { createPromotion } = await adminClient.query<
+                    CreatePromotion.Mutation,
+                    CreatePromotion.Variables
+                >(CREATE_PROMOTION, {
+                    input: {
+                        name: '50 off orders over 100',
+                        enabled: true,
+                        conditions: [
+                            {
+                                code: minimumOrderAmount.code,
+                                arguments: [
+                                    { name: 'amount', value: '10000' },
+                                    { name: 'taxInclusive', value: 'true' },
+                                ],
+                            },
+                        ],
+                        actions: [
+                            {
+                                code: orderFixedDiscount.code,
+                                arguments: [{ name: 'discount', value: JSON.stringify(promoDiscount) }],
+                            },
+                        ],
+                    },
+                });
+                promoId = (createPromotion as any).id;
+            });
+
+            afterAll(async () => {
+                await adminClient.query<DeletePromotionMutation, DeletePromotionMutationVariables>(
+                    DELETE_PROMOTION,
+                    {
+                        id: promoId,
+                    },
+                );
+            });
+
+            it('refund handling when order-level promotion becomes invalid on modification', async () => {
+                const { productVariants } = await adminClient.query<
+                    GetProductVariantListQuery,
+                    GetProductVariantListQueryVariables
+                >(GET_PRODUCT_VARIANT_LIST, {
+                    options: {
+                        filter: {
+                            name: { contains: 'football' },
+                        },
+                    },
+                });
+                const football = productVariants.items[0];
+
+                await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
+                    productVariantId: football.id,
+                    quantity: 2,
+                } as any);
+                await proceedToArrangingPayment(shopClient);
+                const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
+                orderGuard.assertSuccess(order);
+                orderId2 = order.id;
+
+                expect(order.discounts.length).toBe(1);
+                expect(order.discounts[0].amountWithTax).toBe(-promoDiscount);
+                const shippingPrice = order.shippingWithTax;
+                const expectedTotal = football.priceWithTax * 2 + shippingPrice - promoDiscount;
+                expect(order.totalWithTax).toBe(expectedTotal);
+
+                const originalTotalWithTax = order.totalWithTax;
+
+                const transitionOrderToState = await adminTransitionOrderToState(order.id, 'Modifying');
+                orderGuard.assertSuccess(transitionOrderToState);
+
+                expect(transitionOrderToState.state).toBe('Modifying');
+
+                const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
+                    MODIFY_ORDER,
+                    {
+                        input: {
+                            dryRun: false,
+                            orderId: order.id,
+                            adjustOrderLines: [{ orderLineId: order.lines[0].id, quantity: 1 }],
+                            refund: {
+                                paymentId: order.payments![0].id,
+                                reason: 'requested',
+                            },
+                        },
+                    },
+                );
+                orderGuard.assertSuccess(modifyOrder);
+
+                const expectedNewTotal = order.lines[0].unitPriceWithTax + shippingPrice;
+                expect(modifyOrder.totalWithTax).toBe(expectedNewTotal);
+                expect(modifyOrder.payments![0].refunds![0].total).toBe(expectedTotal - expectedNewTotal);
+                expect(modifyOrder.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder));
+            });
+
+            it('transition back to original state', async () => {
+                const transitionOrderToState2 = await adminTransitionOrderToState(orderId2, 'PaymentSettled');
+                orderGuard.assertSuccess(transitionOrderToState2);
+                expect(transitionOrderToState2!.state).toBe('PaymentSettled');
+            });
+
+            it('order no longer has promotions', async () => {
+                const { order } = await adminClient.query<
+                    GetOrderWithModificationsQuery,
+                    GetOrderWithModificationsQueryVariables
+                >(GET_ORDER_WITH_MODIFICATIONS, { id: orderId2 });
+
+                expect(order?.promotions).toEqual([]);
+            });
+
+            it('order no longer has discounts', async () => {
+                const { order } = await adminClient.query<
+                    GetOrderWithModificationsQuery,
+                    GetOrderWithModificationsQueryVariables
+                >(GET_ORDER_WITH_MODIFICATIONS, { id: orderId2 });
+
+                expect(order?.discounts).toEqual([]);
+            });
+        });
     });
 
     // https://github.com/vendure-ecommerce/vendure/issues/1197
@@ -1879,7 +2156,7 @@ describe('Order modification', () => {
             expect(history.history.items.length).toBe(1);
             expect(pick(history.history.items[0]!, ['type', 'data'])).toEqual({
                 type: HistoryEntryType.ORDER_COUPON_APPLIED,
-                data: { couponCode: CODE_50PC_OFF, promotionId: 'T_4' },
+                data: { couponCode: CODE_50PC_OFF, promotionId: 'T_6' },
             });
         });
 

+ 1 - 17
packages/core/e2e/product.e2e-spec.ts

@@ -1,7 +1,6 @@
 import { omit } from '@vendure/common/lib/omit';
 import { pick } from '@vendure/common/lib/pick';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
-import { DefaultLogger } from '@vendure/core';
 import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
 import gql from 'graphql-tag';
 import path from 'path';
@@ -14,7 +13,6 @@ import {
     AddOptionGroupToProduct,
     ChannelFragment,
     CreateProduct,
-    CreateProductOptionGroup,
     CreateProductOptionGroupMutation,
     CreateProductOptionGroupMutationVariables,
     CreateProductVariants,
@@ -54,6 +52,7 @@ import {
     GET_ASSET_LIST,
     GET_PRODUCT_LIST,
     GET_PRODUCT_SIMPLE,
+    GET_PRODUCT_VARIANT_LIST,
     GET_PRODUCT_WITH_VARIANTS,
     UPDATE_CHANNEL,
     UPDATE_GLOBAL_SETTINGS,
@@ -2146,21 +2145,6 @@ export const GET_PRODUCT_VARIANT = gql`
     }
 `;
 
-export const GET_PRODUCT_VARIANT_LIST = gql`
-    query GetProductVariantLIST($options: ProductVariantListOptions, $productId: ID) {
-        productVariants(options: $options, productId: $productId) {
-            items {
-                id
-                name
-                sku
-                price
-                priceWithTax
-            }
-            totalItems
-        }
-    }
-`;
-
 export const GET_PRODUCT_WITH_VARIANT_LIST = gql`
     query GetProductWithVariantList($id: ID, $variantListOptions: ProductVariantListOptions) {
         product(id: $id) {

+ 1 - 8
packages/core/e2e/promotion.e2e-spec.ts

@@ -35,6 +35,7 @@ import {
     ASSIGN_PROMOTIONS_TO_CHANNEL,
     CREATE_CHANNEL,
     CREATE_PROMOTION,
+    DELETE_PROMOTION,
     REMOVE_PROMOTIONS_FROM_CHANNEL,
 } from './graphql/shared-definitions';
 import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
@@ -393,14 +394,6 @@ function generateTestAction(code: string): PromotionAction<any> {
     });
 }
 
-const DELETE_PROMOTION = gql`
-    mutation DeletePromotion($id: ID!) {
-        deletePromotion(id: $id) {
-            result
-        }
-    }
-`;
-
 export const GET_PROMOTION_LIST = gql`
     query GetPromotionList($options: PromotionListOptions) {
         promotions(options: $options) {

+ 23 - 0
packages/core/e2e/zone.e2e-spec.ts

@@ -11,6 +11,7 @@ import {
     CreateZone,
     DeleteZone,
     DeletionResult,
+    GetActiveChannelWithZoneMembersQuery,
     GetCountryList,
     GetZone,
     GetZones,
@@ -60,6 +61,14 @@ describe('Zone resolver', () => {
         expect(result.zone!.name).toBe('Oceania');
     });
 
+    it('zone.members field resolver', async () => {
+        const { activeChannel } = await adminClient.query<GetActiveChannelWithZoneMembersQuery>(
+            GET_ACTIVE_CHANNEL_WITH_ZONE_MEMBERS,
+        );
+
+        expect(activeChannel.defaultShippingZone?.members.length).toBe(2);
+    });
+
     it('updateZone', async () => {
         const result = await adminClient.query<UpdateZone.Mutation, UpdateZone.Variables>(UPDATE_ZONE, {
             input: {
@@ -218,6 +227,20 @@ export const GET_ZONE = gql`
     ${ZONE_FRAGMENT}
 `;
 
+export const GET_ACTIVE_CHANNEL_WITH_ZONE_MEMBERS = gql`
+    query GetActiveChannelWithZoneMembers {
+        activeChannel {
+            id
+            defaultShippingZone {
+                id
+                members {
+                    name
+                }
+            }
+        }
+    }
+`;
+
 export const CREATE_ZONE = gql`
     mutation CreateZone($input: CreateZoneInput!) {
         createZone(input: $input) {

+ 3 - 3
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/core",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "description": "A modern, headless ecommerce framework",
   "repository": {
     "type": "git",
@@ -49,7 +49,7 @@
     "@nestjs/testing": "7.6.17",
     "@nestjs/typeorm": "7.1.5",
     "@types/fs-extra": "^9.0.1",
-    "@vendure/common": "^1.8.2",
+    "@vendure/common": "^1.8.3",
     "apollo-server-express": "2.24.1",
     "bcrypt": "^5.1.0",
     "body-parser": "^1.19.0",
@@ -92,7 +92,7 @@
     "@types/node": "^14.14.31",
     "@types/progress": "^2.0.3",
     "@types/prompts": "^2.0.9",
-    "better-sqlite3": "^7.1.1",
+    "better-sqlite3": "^7.6.2",
     "gulp": "^4.0.2",
     "mysql": "^2.18.1",
     "pg": "^8.4.0",

+ 22 - 5
packages/core/src/api/api-internal-modules.ts

@@ -40,27 +40,43 @@ import { AdministratorEntityResolver } from './resolvers/entity/administrator-en
 import { AssetEntityResolver } from './resolvers/entity/asset-entity.resolver';
 import { CollectionEntityResolver } from './resolvers/entity/collection-entity.resolver';
 import { CountryEntityResolver } from './resolvers/entity/country-entity.resolver';
-import { CustomerAdminEntityResolver, CustomerEntityResolver, } from './resolvers/entity/customer-entity.resolver';
+import {
+    CustomerAdminEntityResolver,
+    CustomerEntityResolver,
+} from './resolvers/entity/customer-entity.resolver';
 import { CustomerGroupEntityResolver } from './resolvers/entity/customer-group-entity.resolver';
 import { FacetEntityResolver } from './resolvers/entity/facet-entity.resolver';
 import { FacetValueEntityResolver } from './resolvers/entity/facet-value-entity.resolver';
-import { FulfillmentAdminEntityResolver, FulfillmentEntityResolver, } from './resolvers/entity/fulfillment-entity.resolver';
+import {
+    FulfillmentAdminEntityResolver,
+    FulfillmentEntityResolver,
+} from './resolvers/entity/fulfillment-entity.resolver';
 import { JobEntityResolver } from './resolvers/entity/job-entity.resolver';
 import { OrderAdminEntityResolver, OrderEntityResolver } from './resolvers/entity/order-entity.resolver';
 import { OrderItemEntityResolver } from './resolvers/entity/order-item-entity.resolver';
 import { OrderLineEntityResolver } from './resolvers/entity/order-line-entity.resolver';
-import { PaymentAdminEntityResolver, PaymentEntityResolver, } from './resolvers/entity/payment-entity.resolver';
+import {
+    PaymentAdminEntityResolver,
+    PaymentEntityResolver,
+} from './resolvers/entity/payment-entity.resolver';
 import { PaymentMethodEntityResolver } from './resolvers/entity/payment-method-entity.resolver';
-import { ProductAdminEntityResolver, ProductEntityResolver, } from './resolvers/entity/product-entity.resolver';
+import {
+    ProductAdminEntityResolver,
+    ProductEntityResolver,
+} from './resolvers/entity/product-entity.resolver';
 import { ProductOptionEntityResolver } from './resolvers/entity/product-option-entity.resolver';
 import { ProductOptionGroupEntityResolver } from './resolvers/entity/product-option-group-entity.resolver';
-import { ProductVariantAdminEntityResolver, ProductVariantEntityResolver, } from './resolvers/entity/product-variant-entity.resolver';
+import {
+    ProductVariantAdminEntityResolver,
+    ProductVariantEntityResolver,
+} from './resolvers/entity/product-variant-entity.resolver';
 import { RefundEntityResolver } from './resolvers/entity/refund-entity.resolver';
 import { RoleEntityResolver } from './resolvers/entity/role-entity.resolver';
 import { ShippingLineEntityResolver } from './resolvers/entity/shipping-line-entity.resolver';
 import { ShippingMethodEntityResolver } from './resolvers/entity/shipping-method-entity.resolver';
 import { TaxRateEntityResolver } from './resolvers/entity/tax-rate-entity.resolver';
 import { UserEntityResolver } from './resolvers/entity/user-entity.resolver';
+import { ZoneEntityResolver } from './resolvers/entity/zone-entity.resolver';
 import { ShopAuthResolver } from './resolvers/shop/shop-auth.resolver';
 import { ShopCustomerResolver } from './resolvers/shop/shop-customer.resolver';
 import { ShopEnvironmentResolver } from './resolvers/shop/shop-environment.resolver';
@@ -125,6 +141,7 @@ export const entityResolvers = [
     UserEntityResolver,
     TaxRateEntityResolver,
     ShippingMethodEntityResolver,
+    ZoneEntityResolver,
 ];
 
 export const adminEntityResolvers = [

+ 20 - 0
packages/core/src/api/resolvers/entity/zone-entity.resolver.ts

@@ -0,0 +1,20 @@
+import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
+
+import { Country, Zone } from '../../../entity/index';
+import { ZoneService } from '../../../service/index';
+import { RequestContext } from '../../common/request-context';
+import { Ctx } from '../../decorators/request-context.decorator';
+
+@Resolver('Zone')
+export class ZoneEntityResolver {
+    constructor(private zoneService: ZoneService) {}
+
+    @ResolveField()
+    async members(@Ctx() ctx: RequestContext, @Parent() zone: Zone): Promise<Country[]> {
+        if (Array.isArray(zone.members)) {
+            return zone.members;
+        }
+        const zoneWithMembers = await this.zoneService.findOne(ctx, zone.id);
+        return zoneWithMembers?.members ?? [];
+    }
+}

+ 5 - 5
packages/core/src/connection/transactional-connection.ts

@@ -113,18 +113,18 @@ export class TransactionalConnection {
      * @example
      * ```TypeScript
      * private async transferCredit(outerCtx: RequestContext, fromId: ID, toId: ID, amount: number) {
-     *   await this.connection.withTransaction(outerCtx, ctx => {
-     *     await this.giftCardService.updateCustomerCredit(fromId, -amount);
-     *
-     *     // Note you must not use outerCtx here, instead use ctx. Otherwise this query
+     *   await this.connection.withTransaction(outerCtx, async ctx => {
+     *     // Note you must not use `outerCtx` here, instead use `ctx`. Otherwise, this query
      *     // will be executed outside of transaction
+     *     await this.giftCardService.updateCustomerCredit(ctx, fromId, -amount);
+     *
      *     await this.connection.getRepository(ctx, GiftCard).update(fromId, { transferred: true })
      *
      *     // If some intermediate logic here throws an Error,
      *     // then all DB transactions will be rolled back and neither Customer's
      *     // credit balance will have changed.
      *
-     *     await this.giftCardService.updateCustomerCredit(toId, amount);
+     *     await this.giftCardService.updateCustomerCredit(ctx, toId, amount);
      *   })
      * }
      * ```

+ 3 - 3
packages/core/src/i18n/messages/de.json

@@ -70,7 +70,7 @@
     "NO_ACTIVE_ORDER_ERROR": "Es gibt keine aktive Bestellung in der aktuellen Sitzung",
     "NOTHING_TO_REFUND_ERROR": "Es gibt nichts zurückzuerstatten",
     "NOT_VERIFIED_ERROR": "Bitte bestätige deine E-Mail-Adresse, bevor du dich anmeldest",
-    "ORDER_LIMIT_ERROR": "Der Artikel konnte nicht hinzugefügt werden. Eine Bestellung kann maximal { maxItems } Artikel enthalten", 
+    "ORDER_LIMIT_ERROR": "Der Artikel konnte nicht hinzugefügt werden. Eine Bestellung kann maximal { maxItems } Artikel enthalten",
     "ORDER_MODIFICATION_ERROR": "Der Inhalt der Bestellung kann nur im Status \"AddingItems\" geändert werden",
     "ORDER_PAYMENT_STATE_ERROR": "Eine Zahlung kann nur im Status \"ArrangingPayment\" hinzugefügt werden",
     "ORDER_STATE_TRANSITION_ERROR": "Der Status der Bestellung kan nicht von \"{ fromState }\" zu \"{ toState }\" geändert werden",
@@ -108,9 +108,9 @@
     "facet-force-deleted": "Die Facette wurde gelöscht und ihre FacetValues wurden von {products, plural, =0 {} one {einem Produkt} other {# Produkten}}{both, select, both { und } single {}}{variants, plural, =0 {} one {einer Produktvariante} other {# Produktvariantem}} entfernt",
     "facet-used": "Die ausgewählte Facette enthält FacetValues, die {products, plural, =0 {} one {einem Produkt} other {# Produkten}}{both, select, both { und } single {}}{variants, plural, =0 {} one {einer Produktvariante} other {# Produktvarianten}} zugewiesen sind",
     "facet-value-force-deleted": "Der gewählte FacetValue wurde von {products, plural, =0 {} one {einem Produkt} other {# Produkten}}{both, select, both { und } single {}}{variants, plural, =0 {} one {einer Produktvariante} other {# Produktvarianten}} entfernt und gelöscht",
-    "facet-value-used": "Der gewählte FacetValue wurde {products, plural, =0 {} one {einem Produkt} other {# Produkten}}{both, select, both { und } single {}}{variants, plural, =0 {} one {einer Produktvariante} other {# Produktvarianten}} zugewiesen",
+    "facet-value-used": "Der FacetValue \"{ facetValueCode }\" wurde {products, plural, =0 {} one {einem Produkt} other {# Produkten}}{both, select, both { und } single {}}{variants, plural, =0 {} one {einer Produktvariante} other {# Produktvarianten}} zugewiesen",
     "payment-method-used-in-channels": "Die gewählte Zahlungsmethode wird von den folgenden Kanälen verwendet: { channelCodes }. Mit \"force: true\" wird sie von allen Kanälen entfernt.",
     "zone-used-in-channels": "Die ausgewählte Zone kann nicht gelöscht werden, da sie als Standard in den folgenden Kanälen verwendet wird: { channelCodes }",
     "zone-used-in-tax-rates": "Die ausgewählte Zone kann nicht gelöscht werden, da sie in den folgenden Steuersätzen verwendet wird: { taxRateNames }"
   }
-}
+}

+ 1 - 1
packages/core/src/i18n/messages/en.json

@@ -121,7 +121,7 @@
     "facet-force-deleted": "The Facet was deleted and its FacetValues were removed from {products, plural, =0 {} one {1 Product} other {# Products}}{both, select, both { , } single {}}{variants, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}}",
     "facet-used": "The Facet \"{ facetCode }\" includes FacetValues which are assigned to {products, plural, =0 {} one {1 Product} other {# Products}}{both, select, both { , } single {}}{variants, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}}",
     "facet-value-force-deleted": "The selected FacetValue was removed from {products, plural, =0 {} one {1 Product} other {# Products}}{both, select, both { , } single {}}{variants, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}} and deleted",
-    "facet-value-used": "The selected FacetValue is assigned to {products, plural, =0 {} one {1 Product} other {# Products}}{both, select, both { , } single {}}{variants, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}}",
+    "facet-value-used": "The FacetValue \"{ facetValueCode }\" is assigned to {products, plural, =0 {} one {1 Product} other {# Products}}{both, select, both { , } single {}}{variants, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}}",
     "payment-method-used-in-channels": "The selected PaymentMethod is assigned to the following Channels: { channelCodes }. Set \"force: true\" to delete from all Channels.",
     "product-option-used": "Cannot delete the option \"{code}\" as it is being used by {count, plural, =0 {} one {1 ProductVariant} other {# ProductVariants}}",
     "zone-used-in-channels": "The selected Zone cannot be deleted as it used as a default in the following Channels: { channelCodes }",

+ 10 - 1
packages/core/src/service/helpers/external-authentication/external-authentication.service.ts

@@ -36,17 +36,26 @@ export class ExternalAuthenticationService {
      * @description
      * Looks up a User based on their identifier from an external authentication
      * provider, ensuring this User is associated with a Customer account.
+     *
+     * By default, only customers in the currently-active Channel will be checked.
+     * By passing `false` as the `checkCurrentChannelOnly` argument, _all_ channels
+     * will be checked.
      */
     async findCustomerUser(
         ctx: RequestContext,
         strategy: string,
         externalIdentifier: string,
+        checkCurrentChannelOnly = true,
     ): Promise<User | undefined> {
         const user = await this.findUser(ctx, strategy, externalIdentifier);
 
         if (user) {
             // Ensure this User is associated with a Customer
-            const customer = await this.customerService.findOneByUserId(ctx, user.id);
+            const customer = await this.customerService.findOneByUserId(
+                ctx,
+                user.id,
+                checkCurrentChannelOnly,
+            );
             if (customer) {
                 return user;
             }

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

@@ -24,7 +24,6 @@ import {
     NegativeQuantityError,
     OrderLimitError,
 } from '../../../common/error/generated-graphql-shop-errors';
-import { AdjustmentSource } from '../../../common/types/adjustment-source';
 import { idsAreEqual } from '../../../common/utils';
 import { ConfigService } from '../../../config/config.service';
 import { CustomFieldConfig } from '../../../config/custom-field/custom-field-types';
@@ -36,7 +35,6 @@ import { OrderModification } from '../../../entity/order-modification/order-modi
 import { Order } from '../../../entity/order/order.entity';
 import { Payment } from '../../../entity/payment/payment.entity';
 import { ProductVariant } from '../../../entity/product-variant/product-variant.entity';
-import { Promotion } from '../../../entity/promotion/promotion.entity';
 import { ShippingLine } from '../../../entity/shipping-line/shipping-line.entity';
 import { Surcharge } from '../../../entity/surcharge/surcharge.entity';
 import { EventBus } from '../../../event-bus/event-bus';
@@ -213,7 +211,7 @@ export class OrderModifier {
             newOrderItems.forEach((item, i) => (item.id = identifiers[i].id));
             orderLine.items = await this.connection
                 .getRepository(ctx, OrderItem)
-                .find({ where: { line: orderLine } });
+                .find({ where: { line: orderLine }, order: { createdAt: 'ASC' } });
             if (!order.active && order.state !== 'Draft') {
                 await this.stockMovementService.createAllocationsForOrderLines(ctx, [
                     {
@@ -223,7 +221,7 @@ export class OrderModifier {
                 ]);
             }
         } else if (quantity < currentQuantity) {
-            if (order.active) {
+            if (order.active || order.state === 'Draft') {
                 // When an Order is still active, it is fine to just delete
                 // any OrderItems that are no longer needed
                 const keepItems = orderLine.items.slice(0, quantity);
@@ -465,9 +463,15 @@ export class OrderModifier {
         const updatedOrderLines = order.lines.filter(l => updatedOrderLineIds.includes(l.id));
         const promotions = await this.promotionService.getActivePromotionsInChannel(ctx);
         const activePromotionsPre = await this.promotionService.getActivePromotionsOnOrder(ctx, order.id);
-        await this.orderCalculator.applyPriceAdjustments(ctx, order, promotions, updatedOrderLines, {
-            recalculateShipping: input.options?.recalculateShipping,
-        });
+        const updatedOrderItems = await this.orderCalculator.applyPriceAdjustments(
+            ctx,
+            order,
+            promotions,
+            updatedOrderLines,
+            {
+                recalculateShipping: input.options?.recalculateShipping,
+            },
+        );
 
         const orderCustomFields = (input as any).customFields;
         if (orderCustomFields) {
@@ -491,11 +495,7 @@ export class OrderModifier {
             if (shippingDelta < 0) {
                 refundInput.shipping = shippingDelta * -1;
             }
-            refundInput.adjustment += await this.getAdjustmentFromNewlyAppliedPromotions(
-                ctx,
-                order,
-                activePromotionsPre,
-            );
+            refundInput.adjustment += this.calculateRefundAdjustment(delta, refundInput);
             const existingPayments = await this.getOrderPayments(ctx, order.id);
             const payment = existingPayments.find(p => idsAreEqual(p.id, input.refund?.paymentId));
             if (payment) {
@@ -526,9 +526,10 @@ export class OrderModifier {
             await this.connection.getRepository(ctx, OrderItem).save(orderItems, { reload: false });
         } else {
             // Otherwise, just save those OrderItems that were specifically added/removed
+            // or updated when applying `OrderCalculator.applyPriceAdjustments()`
             await this.connection
                 .getRepository(ctx, OrderItem)
-                .save(modification.orderItems, { reload: false });
+                .save([...modification.orderItems, ...updatedOrderItems], { reload: false });
         }
         await this.connection.getRepository(ctx, ShippingLine).save(order.shippingLines, { reload: false });
         return { order, modification: createdModification };
@@ -546,35 +547,21 @@ export class OrderModifier {
         return noChanges;
     }
 
-    private async getAdjustmentFromNewlyAppliedPromotions(
-        ctx: RequestContext,
-        order: Order,
-        promotionsPre: Promotion[],
-    ) {
-        const newPromotionDiscounts = order.discounts
-            .filter(discount => {
-                const promotionId = AdjustmentSource.decodeSourceId(discount.adjustmentSource).id;
-                return !promotionsPre.find(p => idsAreEqual(p.id, promotionId));
-            })
-            .filter(discount => {
-                // Filter out any discounts that originate from ShippingLine discounts,
-                // since they are already correctly accounted for in the refund calculation.
-                for (const shippingLine of order.shippingLines) {
-                    if (
-                        shippingLine.discounts.find(
-                            shippingDiscount =>
-                                shippingDiscount.adjustmentSource === discount.adjustmentSource,
-                        )
-                    ) {
-                        return false;
-                    }
-                }
-                return true;
-            });
-        if (newPromotionDiscounts.length) {
-            return -summate(newPromotionDiscounts, 'amountWithTax');
-        }
-        return 0;
+    /**
+     * @description
+     * Because a Refund's amount is calculated based on the orderItems changed, plus shipping change,
+     * we need to make sure the amount gets adjusted to match any changes caused by other factors,
+     * i.e. promotions that were previously active but are no longer.
+     */
+    private calculateRefundAdjustment(
+        delta: number,
+        refundInput: RefundOrderInput & { orderItems: OrderItem[] },
+    ): number {
+        const existingAdjustment = refundInput.adjustment;
+        const itemAmount = summate(refundInput.orderItems, 'proratedUnitPriceWithTax');
+        const calculatedDelta = itemAmount + refundInput.shipping + existingAdjustment;
+        const absDelta = Math.abs(delta);
+        return absDelta !== calculatedDelta ? absDelta - calculatedDelta : 0;
     }
 
     private getOrderPayments(ctx: RequestContext, orderId: ID): Promise<Payment[]> {

+ 2 - 1
packages/core/src/service/services/channel.service.ts

@@ -263,12 +263,13 @@ export class ChannelService {
 
     async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         const channel = await this.connection.getEntityOrThrow(ctx, Channel, id);
+        const deletedChannel = new Channel(channel);
         await this.connection.getRepository(ctx, Session).delete({ activeChannelId: id });
         await this.connection.getRepository(ctx, Channel).delete(id);
         await this.connection.getRepository(ctx, ProductVariantPrice).delete({
             channelId: id,
         });
-        this.eventBus.publish(new ChannelEvent(ctx, channel, 'deleted', id));
+        this.eventBus.publish(new ChannelEvent(ctx, deletedChannel, 'deleted', id));
 
         return {
             result: DeletionResult.DELETED,

+ 4 - 2
packages/core/src/service/services/collection.service.ts

@@ -494,13 +494,15 @@ export class CollectionService implements OnModuleInit {
         const collection = await this.connection.getEntityOrThrow(ctx, Collection, id, {
             channelId: ctx.channelId,
         });
+        const deletedCollection = new Collection(collection);
         const descendants = await this.getDescendants(ctx, collection.id);
         for (const coll of [...descendants.reverse(), collection]) {
             const affectedVariantIds = await this.getCollectionProductVariantIds(coll);
+            const deletedColl = new Collection(coll);
             await this.connection.getRepository(ctx, Collection).remove(coll);
-            this.eventBus.publish(new CollectionModificationEvent(ctx, coll, affectedVariantIds));
+            this.eventBus.publish(new CollectionModificationEvent(ctx, deletedColl, affectedVariantIds));
         }
-        this.eventBus.publish(new CollectionEvent(ctx, collection, 'deleted', id));
+        this.eventBus.publish(new CollectionEvent(ctx, deletedCollection, 'deleted', id));
         return {
             result: DeletionResult.DELETED,
         };

+ 2 - 1
packages/core/src/service/services/country.service.ts

@@ -130,8 +130,9 @@ export class CountryService {
                 message: ctx.translate('message.country-used-in-addresses', { count: addressesUsingCountry }),
             };
         } else {
+            const deletedCountry = new Country(country);
             await this.connection.getRepository(ctx, Country).remove(country);
-            this.eventBus.publish(new CountryEvent(ctx, country, 'deleted', id));
+            this.eventBus.publish(new CountryEvent(ctx, deletedCountry, 'deleted', id));
             return {
                 result: DeletionResult.DELETED,
                 message: '',

+ 2 - 1
packages/core/src/service/services/customer-group.service.ts

@@ -125,8 +125,9 @@ export class CustomerGroupService {
     async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         const group = await this.connection.getEntityOrThrow(ctx, CustomerGroup, id);
         try {
+            const deletedGroup = new CustomerGroup(group);
             await this.connection.getRepository(ctx, CustomerGroup).remove(group);
-            this.eventBus.publish(new CustomerGroupEntityEvent(ctx, group, 'deleted', id));
+            this.eventBus.publish(new CustomerGroupEntityEvent(ctx, deletedGroup, 'deleted', id));
             return {
                 result: DeletionResult.DELETED,
             };

+ 2 - 1
packages/core/src/service/services/customer.service.ts

@@ -771,9 +771,10 @@ export class CustomerService {
                 address: addressToLine(address),
             },
         });
+        const deletedAddress = new Address(address);
         await this.connection.getRepository(ctx, Address).remove(address);
         address.customer = customer;
-        this.eventBus.publish(new CustomerAddressEvent(ctx, address, 'deleted', id));
+        this.eventBus.publish(new CustomerAddressEvent(ctx, deletedAddress, 'deleted', id));
         return true;
     }
 

+ 14 - 5
packages/core/src/service/services/facet-value.service.ts

@@ -14,7 +14,7 @@ import { Translated } from '../../common/types/locale-types';
 import { assertFound } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
 import { TransactionalConnection } from '../../connection/transactional-connection';
-import { Product, ProductVariant } from '../../entity';
+import { Asset, Product, ProductVariant } from '../../entity';
 import { FacetValueTranslation } from '../../entity/facet-value/facet-value-translation.entity';
 import { FacetValue } from '../../entity/facet-value/facet-value.entity';
 import { Facet } from '../../entity/facet/facet.entity';
@@ -144,18 +144,27 @@ export class FacetValueService {
 
         const isInUse = !!(productCount || variantCount);
         const both = !!(productCount && variantCount) ? 'both' : 'single';
-        const i18nVars = { products: productCount, variants: variantCount, both };
         let message = '';
         let result: DeletionResult;
 
+        const facetValue = await this.connection.getEntityOrThrow(ctx, FacetValue, id);
+        const i18nVars = {
+            products: productCount,
+            variants: variantCount,
+            both,
+            facetValueCode: facetValue.code,
+        };
+        // Create a new facetValue so that the id is still available
+        // after deletion (the .remove() method sets it to undefined)
+        const deletedFacetValue = new FacetValue(facetValue);
+
         if (!isInUse) {
-            const facetValue = await this.connection.getEntityOrThrow(ctx, FacetValue, id);
             await this.connection.getRepository(ctx, FacetValue).remove(facetValue);
+            this.eventBus.publish(new FacetValueEvent(ctx, deletedFacetValue, 'deleted', id));
             result = DeletionResult.DELETED;
         } else if (force) {
-            const facetValue = await this.connection.getEntityOrThrow(ctx, FacetValue, id);
             await this.connection.getRepository(ctx, FacetValue).remove(facetValue);
-            this.eventBus.publish(new FacetValueEvent(ctx, facetValue, 'deleted', id));
+            this.eventBus.publish(new FacetValueEvent(ctx, deletedFacetValue, 'deleted', id));
             message = ctx.translate('message.facet-value-force-deleted', i18nVars);
             result = DeletionResult.DELETED;
         } else {

+ 3 - 1
packages/core/src/service/services/facet.service.ts

@@ -201,15 +201,17 @@ export class FacetService {
         const i18nVars = { products: productCount, variants: variantCount, both, facetCode: facet.code };
         let message = '';
         let result: DeletionResult;
+        const deletedFacet = new Facet(facet);
 
         if (!isInUse) {
             await this.connection.getRepository(ctx, Facet).remove(facet);
+            this.eventBus.publish(new FacetEvent(ctx, deletedFacet, 'deleted', id));
             result = DeletionResult.DELETED;
         } else if (force) {
             await this.connection.getRepository(ctx, Facet).remove(facet);
+            this.eventBus.publish(new FacetEvent(ctx, deletedFacet, 'deleted', id));
             message = ctx.translate('message.facet-force-deleted', i18nVars);
             result = DeletionResult.DELETED;
-            this.eventBus.publish(new FacetEvent(ctx, facet, 'deleted', id));
         } else {
             message = ctx.translate('message.facet-used', i18nVars);
             result = DeletionResult.NOT_DELETED;

+ 4 - 2
packages/core/src/service/services/history.service.ts

@@ -262,8 +262,9 @@ export class HistoryService {
 
     async deleteOrderHistoryEntry(ctx: RequestContext, id: ID): Promise<void> {
         const entry = await this.connection.getEntityOrThrow(ctx, OrderHistoryEntry, id);
+        const deletedEntry = new OrderHistoryEntry(entry);
         await this.connection.getRepository(ctx, OrderHistoryEntry).remove(entry);
-        this.eventBus.publish(new HistoryEntryEvent(ctx, entry, 'deleted', 'order', id));
+        this.eventBus.publish(new HistoryEntryEvent(ctx, deletedEntry, 'deleted', 'order', id));
     }
 
     async updateCustomerHistoryEntry<T extends keyof CustomerHistoryEntryData>(
@@ -288,8 +289,9 @@ export class HistoryService {
 
     async deleteCustomerHistoryEntry(ctx: RequestContext, id: ID): Promise<void> {
         const entry = await this.connection.getEntityOrThrow(ctx, CustomerHistoryEntry, id);
+        const deletedEntry = new CustomerHistoryEntry(entry);
         await this.connection.getRepository(ctx, CustomerHistoryEntry).remove(entry);
-        this.eventBus.publish(new HistoryEntryEvent(ctx, entry, 'deleted', 'customer', id));
+        this.eventBus.publish(new HistoryEntryEvent(ctx, deletedEntry, 'deleted', 'customer', id));
     }
 
     private async getAdministratorFromContext(ctx: RequestContext): Promise<Administrator | undefined> {

+ 4 - 1
packages/core/src/service/services/payment-method.service.ts

@@ -137,8 +137,11 @@ export class PaymentMethodService {
                 return { result, message };
             }
             try {
+                const deletedPaymentMethod = new PaymentMethod(paymentMethod);
                 await this.connection.getRepository(ctx, PaymentMethod).remove(paymentMethod);
-                this.eventBus.publish(new PaymentMethodEvent(ctx, paymentMethod, 'deleted', paymentMethodId));
+                this.eventBus.publish(
+                    new PaymentMethodEvent(ctx, deletedPaymentMethod, 'deleted', paymentMethodId),
+                );
                 return {
                     result: DeletionResult.DELETED,
                 };

+ 32 - 27
packages/core/src/service/services/payment.service.ts

@@ -1,25 +1,20 @@
 import { Injectable } from '@nestjs/common';
-import {
-    CancelPaymentResult,
-    ManualPaymentInput,
-    RefundOrderInput,
-    SettlePaymentResult,
-} from '@vendure/common/lib/generated-types';
+import { ManualPaymentInput, RefundOrderInput } from '@vendure/common/lib/generated-types';
 import { DeepPartial, ID } from '@vendure/common/lib/shared-types';
 import { summate } from '@vendure/common/lib/shared-utils';
 
 import { RequestContext } from '../../api/common/request-context';
-import { ErrorResultUnion } from '../../common/error/error-result';
 import { InternalServerError } from '../../common/error/errors';
 import {
     PaymentStateTransitionError,
     RefundStateTransitionError,
-    SettlePaymentError,
 } from '../../common/error/generated-graphql-admin-errors';
 import { IneligiblePaymentMethodError } from '../../common/error/generated-graphql-shop-errors';
 import { PaymentMetadata } from '../../common/types/common-types';
 import { idsAreEqual } from '../../common/utils';
+import { Logger, PaymentMethodHandler } from '../../config/index';
 import { TransactionalConnection } from '../../connection/transactional-connection';
+import { PaymentMethod } from '../../entity/index';
 import { OrderItem } from '../../entity/order-item/order-item.entity';
 import { Order } from '../../entity/order/order.entity';
 import { Payment } from '../../entity/payment/payment.entity';
@@ -275,10 +270,7 @@ export class PaymentService {
             return summate(nonFailedRefunds, 'total');
         }
 
-        const existingNonFailedRefunds =
-            orderWithRefunds.payments
-                ?.reduce((refunds, p) => [...refunds, ...p.refunds], [] as Refund[])
-                .filter(refund => refund.state !== 'Failed') ?? [];
+        const refundsCreated: Refund[] = [];
         const refundablePayments = orderWithRefunds.payments.filter(p => {
             return paymentRefundTotal(p) < p.amount;
         });
@@ -314,19 +306,32 @@ export class PaymentService {
                 state: 'Pending',
                 metadata: {},
             });
-            const { paymentMethod, handler } = await this.paymentMethodService.getMethodAndOperations(
-                ctx,
-                paymentToRefund.method,
-            );
-            const createRefundResult = await handler.createRefund(
-                ctx,
-                input,
-                total,
-                order,
-                paymentToRefund,
-                paymentMethod.handler.args,
-                paymentMethod,
-            );
+            let paymentMethod: PaymentMethod | undefined;
+            let handler: PaymentMethodHandler | undefined;
+            try {
+                const methodAndHandler = await this.paymentMethodService.getMethodAndOperations(
+                    ctx,
+                    paymentToRefund.method,
+                );
+                paymentMethod = methodAndHandler.paymentMethod;
+                handler = methodAndHandler.handler;
+            } catch (e) {
+                Logger.warn(
+                    `Could not find a corresponding PaymentMethodHandler when creating a refund for the Payment with method "${paymentToRefund.method}"`,
+                );
+            }
+            const createRefundResult =
+                paymentMethod && handler
+                    ? await handler.createRefund(
+                          ctx,
+                          input,
+                          total,
+                          order,
+                          paymentToRefund,
+                          paymentMethod.handler.args,
+                          paymentMethod,
+                      )
+                    : false;
             if (createRefundResult) {
                 refund.transactionId = createRefundResult.transactionId || '';
                 refund.metadata = createRefundResult.metadata || {};
@@ -347,9 +352,9 @@ export class PaymentService {
             if (primaryRefund == null) {
                 primaryRefund = refund;
             }
-            existingNonFailedRefunds.push(refund);
+            refundsCreated.push(refund);
             refundedPaymentIds.push(paymentToRefund.id);
-            refundOutstanding = refundTotal - summate(existingNonFailedRefunds, 'total');
+            refundOutstanding = refundTotal - summate(refundsCreated, 'total');
         } while (0 < refundOutstanding);
         // tslint:disable-next-line:no-non-null-assertion
         return primaryRefund!;

+ 2 - 1
packages/core/src/service/services/product-option-group.service.ts

@@ -133,6 +133,7 @@ export class ProductOptionGroupService {
         const optionGroup = await this.connection.getEntityOrThrow(ctx, ProductOptionGroup, id, {
             relations: ['options', 'product'],
         });
+        const deletedOptionGroup = new ProductOptionGroup(optionGroup);
         const inUseByActiveProducts = await this.isInUseByOtherProducts(ctx, optionGroup, productId);
         if (0 < inUseByActiveProducts) {
             return {
@@ -180,7 +181,7 @@ export class ProductOptionGroupService {
                 Logger.error(e.message, undefined, e.stack);
             }
         }
-        this.eventBus.publish(new ProductOptionGroupEvent(ctx, optionGroup, 'deleted', id));
+        this.eventBus.publish(new ProductOptionGroupEvent(ctx, deletedOptionGroup, 'deleted', id));
         return {
             result: DeletionResult.DELETED,
         };

+ 2 - 1
packages/core/src/service/services/product-option.service.ts

@@ -106,6 +106,7 @@ export class ProductOptionService {
      */
     async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         const productOption = await this.connection.getEntityOrThrow(ctx, ProductOption, id);
+        const deletedProductOption = new ProductOption(productOption);
         const inUseByActiveVariants = await this.isInUse(ctx, productOption, 'active');
         if (0 < inUseByActiveVariants) {
             return {
@@ -135,7 +136,7 @@ export class ProductOptionService {
                 Logger.error(e.message, undefined, e.stack);
             }
         }
-        this.eventBus.publish(new ProductOptionEvent(ctx, productOption, 'deleted', id));
+        this.eventBus.publish(new ProductOptionEvent(ctx, deletedProductOption, 'deleted', id));
         return {
             result: DeletionResult.DELETED,
         };

+ 2 - 1
packages/core/src/service/services/role.service.ts

@@ -205,8 +205,9 @@ export class RoleService {
         if (role.code === SUPER_ADMIN_ROLE_CODE || role.code === CUSTOMER_ROLE_CODE) {
             throw new InternalServerError(`error.cannot-delete-role`, { roleCode: role.code });
         }
+        const deletedRole = new Role(role);
         await this.connection.getRepository(ctx, Role).remove(role);
-        this.eventBus.publish(new RoleEvent(ctx, role, 'deleted', id));
+        this.eventBus.publish(new RoleEvent(ctx, deletedRole, 'deleted', id));
         return {
             result: DeletionResult.DELETED,
         };

+ 2 - 1
packages/core/src/service/services/tax-category.service.ts

@@ -81,8 +81,9 @@ export class TaxCategoryService {
         }
 
         try {
+            const deletedTaxCategory = new TaxCategory(taxCategory);
             await this.connection.getRepository(ctx, TaxCategory).remove(taxCategory);
-            this.eventBus.publish(new TaxCategoryEvent(ctx, taxCategory, 'deleted', id));
+            this.eventBus.publish(new TaxCategoryEvent(ctx, deletedTaxCategory, 'deleted', id));
             return {
                 result: DeletionResult.DELETED,
             };

+ 2 - 1
packages/core/src/service/services/tax-rate.service.ts

@@ -136,9 +136,10 @@ export class TaxRateService {
 
     async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         const taxRate = await this.connection.getEntityOrThrow(ctx, TaxRate, id);
+        const deletedTaxRate = new TaxRate(taxRate);
         try {
             await this.connection.getRepository(ctx, TaxRate).remove(taxRate);
-            this.eventBus.publish(new TaxRateEvent(ctx, taxRate, 'deleted', id));
+            this.eventBus.publish(new TaxRateEvent(ctx, deletedTaxRate, 'deleted', id));
             return {
                 result: DeletionResult.DELETED,
             };

+ 2 - 2
packages/core/src/service/services/zone.service.ts

@@ -113,7 +113,7 @@ export class ZoneService {
 
     async delete(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
         const zone = await this.connection.getEntityOrThrow(ctx, Zone, id);
-
+        const deletedZone = new Zone(zone);
         const channelsUsingZone = await this.connection
             .getRepository(ctx, Channel)
             .createQueryBuilder('channel')
@@ -146,7 +146,7 @@ export class ZoneService {
         } else {
             await this.connection.getRepository(ctx, Zone).remove(zone);
             await this.zones.refresh(ctx);
-            this.eventBus.publish(new ZoneEvent(ctx, zone, 'deleted', id));
+            this.eventBus.publish(new ZoneEvent(ctx, deletedZone, 'deleted', id));
             return {
                 result: DeletionResult.DELETED,
                 message: '',

+ 3 - 3
packages/create/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/create",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "license": "MIT",
   "bin": {
     "create": "./index.js"
@@ -28,13 +28,13 @@
     "@types/handlebars": "^4.1.0",
     "@types/listr": "^0.14.2",
     "@types/semver": "^6.2.2",
-    "@vendure/core": "^1.8.2",
+    "@vendure/core": "^1.8.3",
     "rimraf": "^3.0.2",
     "ts-node": "^10.2.1",
     "typescript": "4.3.5"
   },
   "dependencies": {
-    "@vendure/common": "^1.8.2",
+    "@vendure/common": "^1.8.3",
     "chalk": "^4.1.0",
     "commander": "^7.1.0",
     "cross-spawn": "^7.0.3",

+ 9 - 9
packages/dev-server/package.json

@@ -1,6 +1,6 @@
 {
   "name": "dev-server",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "main": "index.js",
   "license": "MIT",
   "private": true,
@@ -14,18 +14,18 @@
     "load-test:100k": "node -r ts-node/register load-testing/run-load-test.ts 100000"
   },
   "dependencies": {
-    "@vendure/admin-ui-plugin": "^1.8.2",
-    "@vendure/asset-server-plugin": "^1.8.2",
-    "@vendure/common": "^1.8.2",
-    "@vendure/core": "^1.8.2",
-    "@vendure/elasticsearch-plugin": "^1.8.2",
-    "@vendure/email-plugin": "^1.8.2",
+    "@vendure/admin-ui-plugin": "^1.8.3",
+    "@vendure/asset-server-plugin": "^1.8.3",
+    "@vendure/common": "^1.8.3",
+    "@vendure/core": "^1.8.3",
+    "@vendure/elasticsearch-plugin": "^1.8.3",
+    "@vendure/email-plugin": "^1.8.3",
     "typescript": "4.3.5"
   },
   "devDependencies": {
     "@types/csv-stringify": "^3.1.0",
-    "@vendure/testing": "^1.8.2",
-    "@vendure/ui-devkit": "^1.8.2",
+    "@vendure/testing": "^1.8.3",
+    "@vendure/ui-devkit": "^1.8.3",
     "commander": "^7.1.0",
     "concurrently": "^5.0.0",
     "csv-stringify": "^5.3.3",

+ 3 - 3
packages/elasticsearch-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/elasticsearch-plugin",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -25,8 +25,8 @@
     "fast-deep-equal": "^3.1.3"
   },
   "devDependencies": {
-    "@vendure/common": "^1.8.2",
-    "@vendure/core": "^1.8.2",
+    "@vendure/common": "^1.8.3",
+    "@vendure/core": "^1.8.3",
     "rimraf": "^3.0.2",
     "typescript": "4.3.5"
   }

+ 3 - 3
packages/email-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/email-plugin",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -35,8 +35,8 @@
     "@types/fs-extra": "^9.0.1",
     "@types/handlebars": "^4.1.0",
     "@types/mjml": "^4.0.4",
-    "@vendure/common": "^1.8.2",
-    "@vendure/core": "^1.8.2",
+    "@vendure/common": "^1.8.3",
+    "@vendure/core": "^1.8.3",
     "rimraf": "^3.0.2",
     "typescript": "4.3.5"
   }

+ 3 - 3
packages/job-queue-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/job-queue-plugin",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "license": "MIT",
   "main": "package/index.js",
   "types": "package/index.d.ts",
@@ -24,8 +24,8 @@
   "devDependencies": {
     "@google-cloud/pubsub": "^2.8.0",
     "@types/ioredis": "^4.28.10",
-    "@vendure/common": "^1.8.2",
-    "@vendure/core": "^1.8.2",
+    "@vendure/common": "^1.8.3",
+    "@vendure/core": "^1.8.3",
     "bullmq": "^1.86.7",
     "rimraf": "^3.0.2",
     "typescript": "4.3.5"

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

@@ -1,6 +1,6 @@
 {
     "name": "@vendure/payments-plugin",
-    "version": "1.8.2",
+    "version": "1.8.3",
     "license": "MIT",
     "main": "package/index.js",
     "types": "package/index.d.ts",
@@ -28,9 +28,9 @@
     "devDependencies": {
         "@mollie/api-client": "^3.6.0",
         "@types/braintree": "^2.22.15",
-        "@vendure/common": "^1.8.2",
-        "@vendure/core": "^1.8.2",
-        "@vendure/testing": "^1.8.2",
+        "@vendure/common": "^1.8.3",
+        "@vendure/core": "^1.8.3",
+        "@vendure/testing": "^1.8.3",
         "braintree": "^3.0.0",
         "nock": "^13.1.4",
         "rimraf": "^3.0.2",

+ 3 - 3
packages/testing/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/testing",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "description": "End-to-end testing tools for Vendure projects",
   "keywords": [
     "vendure",
@@ -34,7 +34,7 @@
   },
   "dependencies": {
     "@types/node-fetch": "^2.5.4",
-    "@vendure/common": "^1.8.2",
+    "@vendure/common": "^1.8.3",
     "faker": "^4.1.0",
     "form-data": "^3.0.0",
     "graphql": "15.5.1",
@@ -45,7 +45,7 @@
   "devDependencies": {
     "@types/mysql": "^2.15.15",
     "@types/pg": "^7.14.5",
-    "@vendure/core": "^1.8.2",
+    "@vendure/core": "^1.8.3",
     "mysql": "^2.18.1",
     "pg": "^8.4.0",
     "rimraf": "^3.0.0",

+ 4 - 4
packages/ui-devkit/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/ui-devkit",
-  "version": "1.8.2",
+  "version": "1.8.3",
   "description": "A library for authoring Vendure Admin UI extensions",
   "keywords": [
     "vendure",
@@ -40,8 +40,8 @@
     "@angular/cli": "12.2.16",
     "@angular/compiler": "12.2.16",
     "@angular/compiler-cli": "12.2.16",
-    "@vendure/admin-ui": "^1.8.2",
-    "@vendure/common": "^1.8.2",
+    "@vendure/admin-ui": "^1.8.3",
+    "@vendure/common": "^1.8.3",
     "chalk": "^4.1.0",
     "chokidar": "^3.5.1",
     "fs-extra": "^10.0.0",
@@ -52,7 +52,7 @@
     "@rollup/plugin-node-resolve": "^11.2.0",
     "@types/fs-extra": "^9.0.8",
     "@types/glob": "^7.1.3",
-    "@vendure/core": "^1.8.2",
+    "@vendure/core": "^1.8.3",
     "rimraf": "^3.0.2",
     "rollup": "^2.40.0",
     "rollup-plugin-terser": "^7.0.2",

+ 21 - 62
yarn.lock

@@ -5760,14 +5760,13 @@ before-after-hook@^2.2.0:
   resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"
   integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==
 
-better-sqlite3@^7.1.1:
-  version "7.4.3"
-  resolved "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.4.3.tgz#8e45a4164bf4b4e128d97375023550f780550997"
-  integrity sha512-07bKjClZg/f4KMVRkzWtoIvazVPcF1gsvVKVIXlxwleC2DxuIhnra3KCMlUT1rFeRYXXckot2a46UciF2d9KLw==
+better-sqlite3@^7.6.2:
+  version "7.6.2"
+  resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-7.6.2.tgz#47cd8cad5b9573cace535f950ac321166bc31384"
+  integrity sha512-S5zIU1Hink2AH4xPsN0W43T1/AJ5jrPh7Oy07ocuW/AKYYY02GWzz9NH0nbSMn/gw6fDZ5jZ1QsHt1BXAwJ6Lg==
   dependencies:
     bindings "^1.5.0"
-    prebuild-install "^6.0.1"
-    tar "^6.1.0"
+    prebuild-install "^7.1.0"
 
 big.js@^5.2.2:
   version "5.2.2"
@@ -7586,13 +7585,6 @@ decompress-response@^3.3.0:
   dependencies:
     mimic-response "^1.0.0"
 
-decompress-response@^4.2.0:
-  version "4.2.1"
-  resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
-  integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
-  dependencies:
-    mimic-response "^2.0.0"
-
 decompress-response@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
@@ -7815,7 +7807,7 @@ detect-indent@^6.0.0:
   resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
   integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==
 
-detect-libc@^1.0.2, detect-libc@^1.0.3:
+detect-libc@^1.0.2:
   version "1.0.3"
   resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
   integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
@@ -13036,11 +13028,6 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
   resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
   integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
 
-mimic-response@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
-  integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
-
 mimic-response@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
@@ -13802,13 +13789,6 @@ nock@^13.1.4:
     lodash.set "^4.3.2"
     propagate "^2.0.0"
 
-node-abi@^2.21.0:
-  version "2.30.0"
-  resolved "https://registry.npmjs.org/node-abi/-/node-abi-2.30.0.tgz#8be53bf3e7945a34eea10e0fc9a5982776cf550b"
-  integrity sha512-g6bZh3YCKQRdwuO/tSZZYJAw622SjsRfJ2X0Iy4sSOHZ34/sPPdVBn8fev2tj7njzLwuqPw9uMtGsGkO5kIQvg==
-  dependencies:
-    semver "^5.4.1"
-
 node-abi@^3.3.0:
   version "3.22.0"
   resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.22.0.tgz#00b8250e86a0816576258227edbce7bbe0039362"
@@ -14144,7 +14124,7 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1:
   dependencies:
     path-key "^3.0.0"
 
-"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2:
+"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.2, npmlog@^4.1.2:
   version "4.1.2"
   resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
   integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@@ -15690,26 +15670,7 @@ postgres-interval@^1.1.0:
   dependencies:
     xtend "^4.0.0"
 
-prebuild-install@^6.0.1:
-  version "6.1.4"
-  resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz#ae3c0142ad611d58570b89af4986088a4937e00f"
-  integrity sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==
-  dependencies:
-    detect-libc "^1.0.3"
-    expand-template "^2.0.3"
-    github-from-package "0.0.0"
-    minimist "^1.2.3"
-    mkdirp-classic "^0.5.3"
-    napi-build-utils "^1.0.1"
-    node-abi "^2.21.0"
-    npmlog "^4.0.1"
-    pump "^3.0.0"
-    rc "^1.2.7"
-    simple-get "^3.0.3"
-    tar-fs "^2.0.0"
-    tunnel-agent "^0.6.0"
-
-prebuild-install@^7.1.1:
+prebuild-install@^7.1.0, prebuild-install@^7.1.1:
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
   integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==
@@ -17058,7 +17019,7 @@ semver-regex@^3.1.2:
   resolved "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807"
   integrity sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==
 
-"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
+"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1:
   version "5.7.1"
   resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -17087,6 +17048,13 @@ semver@^7.3.7:
   dependencies:
     lru-cache "^6.0.0"
 
+semver@^7.3.8:
+  version "7.3.8"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
+  integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
+  dependencies:
+    lru-cache "^6.0.0"
+
 semver@~5.3.0:
   version "5.3.0"
   resolved "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
@@ -17212,16 +17180,16 @@ shallow-clone@^3.0.0:
   dependencies:
     kind-of "^6.0.2"
 
-sharp@~0.30.7:
-  version "0.30.7"
-  resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.7.tgz#7862bda98804fdd1f0d5659c85e3324b90d94c7c"
-  integrity sha512-G+MY2YW33jgflKPTXXptVO28HvNOo9G3j0MybYAHeEmby+QuD2U98dT6ueht9cv/XDqZspSpIhoSW+BAKJ7Hig==
+sharp@~0.31.2:
+  version "0.31.2"
+  resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.31.2.tgz#a8411c80512027f5a452b76d599268760c4e5dfa"
+  integrity sha512-DUdNVEXgS5A97cTagSLIIp8dUZ/lZtk78iNVZgHdHbx1qnQR7JAHY0BnXnwwH39Iw+VKhO08CTYhIg0p98vQ5Q==
   dependencies:
     color "^4.2.3"
     detect-libc "^2.0.1"
     node-addon-api "^5.0.0"
     prebuild-install "^7.1.1"
-    semver "^7.3.7"
+    semver "^7.3.8"
     simple-get "^4.0.1"
     tar-fs "^2.1.1"
     tunnel-agent "^0.6.0"
@@ -17279,15 +17247,6 @@ simple-concat@^1.0.0:
   resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
   integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
 
-simple-get@^3.0.3:
-  version "3.1.0"
-  resolved "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3"
-  integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
-  dependencies:
-    decompress-response "^4.2.0"
-    once "^1.3.1"
-    simple-concat "^1.0.0"
-
 simple-get@^4.0.0, simple-get@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"