瀏覽代碼

fix(core): Correctly handle changing Order currencyCode

Fixes #2469, relates to #GHSA-wm63-7627-ch33
Michael Bromley 2 年之前
父節點
當前提交
f544cf375f

文件差異過大導致無法顯示
+ 18 - 18
packages/core/e2e/graphql/generated-e2e-shop-types.ts


+ 4 - 0
packages/core/e2e/graphql/shop-definitions.ts

@@ -12,6 +12,7 @@ export const TEST_ORDER_FRAGMENT = gql`
         shippingWithTax
         total
         totalWithTax
+        currencyCode
         couponCodes
         discounts {
             adjustmentSource
@@ -74,12 +75,15 @@ export const UPDATED_ORDER_FRAGMENT = gql`
         active
         total
         totalWithTax
+        currencyCode
         lines {
             id
             quantity
             productVariant {
                 id
             }
+            unitPrice
+            unitPriceWithTax
             linePrice
             linePriceWithTax
             discounts {

+ 118 - 1
packages/core/e2e/product-prices.e2e-spec.ts

@@ -1,5 +1,5 @@
 /* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { createTestEnvironment } from '@vendure/testing';
+import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
 import path from 'path';
 import { afterAll, beforeAll, describe, expect, it } from 'vitest';
 
@@ -16,6 +16,13 @@ import {
     UpdateChannelDocument,
     UpdateProductVariantsDocument,
 } from './graphql/generated-e2e-admin-types';
+import {
+    AddItemToOrderDocument,
+    AdjustItemQuantityDocument,
+    GetActiveOrderDocument,
+    TestOrderFragmentFragment,
+    UpdatedOrderFragment,
+} from './graphql/generated-e2e-shop-types';
 import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
 
 describe('Product prices', () => {
@@ -26,6 +33,9 @@ describe('Product prices', () => {
         Codegen.CreateProductVariantsMutation['createProductVariants'][number]
     >;
 
+    const orderResultGuard: ErrorResultGuard<TestOrderFragmentFragment | UpdatedOrderFragment> =
+        createErrorResultGuard(input => !!input.lines);
+
     beforeAll(async () => {
         await server.init({
             initialData,
@@ -177,4 +187,111 @@ describe('Product prices', () => {
             }, 'The currency "JPY" is not available in the current Channel'),
         );
     });
+
+    describe('changing Order currencyCode', () => {
+        beforeAll(async () => {
+            await adminClient.query(UpdateProductVariantsDocument, {
+                input: [
+                    {
+                        id: 'T_1',
+                        prices: [
+                            { currencyCode: CurrencyCode.USD, price: 1000 },
+                            { currencyCode: CurrencyCode.GBP, price: 900 },
+                            { currencyCode: CurrencyCode.EUR, price: 1100 },
+                        ],
+                    },
+                    {
+                        id: 'T_2',
+                        prices: [
+                            { currencyCode: CurrencyCode.USD, price: 2000 },
+                            { currencyCode: CurrencyCode.GBP, price: 1900 },
+                            { currencyCode: CurrencyCode.EUR, price: 2100 },
+                        ],
+                    },
+                    {
+                        id: 'T_3',
+                        prices: [
+                            { currencyCode: CurrencyCode.USD, price: 3000 },
+                            { currencyCode: CurrencyCode.GBP, price: 2900 },
+                            { currencyCode: CurrencyCode.EUR, price: 3100 },
+                        ],
+                    },
+                ],
+            });
+        });
+
+        it('create order in default currency', async () => {
+            await shopClient.query(AddItemToOrderDocument, {
+                productVariantId: 'T_1',
+                quantity: 1,
+            });
+            await shopClient.query(AddItemToOrderDocument, {
+                productVariantId: 'T_2',
+                quantity: 1,
+            });
+
+            const { activeOrder } = await shopClient.query(GetActiveOrderDocument);
+
+            expect(activeOrder?.lines[0]?.unitPrice).toBe(1000);
+            expect(activeOrder?.lines[0]?.unitPriceWithTax).toBe(1200);
+            expect(activeOrder?.lines[1]?.unitPrice).toBe(2000);
+            expect(activeOrder?.lines[1]?.unitPriceWithTax).toBe(2400);
+            expect(activeOrder?.currencyCode).toBe(CurrencyCode.USD);
+        });
+
+        it(
+            'updating an order in an unsupported currency throws',
+            assertThrowsWithMessage(async () => {
+                await shopClient.query(
+                    AddItemToOrderDocument,
+                    {
+                        productVariantId: 'T_1',
+                        quantity: 1,
+                    },
+                    { currencyCode: 'JPY' },
+                );
+            }, 'The currency "JPY" is not available in the current Channel'),
+        );
+
+        it('updating an order line with a new currency updates all lines to that currency', async () => {
+            const { activeOrder } = await shopClient.query(GetActiveOrderDocument);
+            const { adjustOrderLine } = await shopClient.query(
+                AdjustItemQuantityDocument,
+                {
+                    orderLineId: activeOrder!.lines[0]?.id,
+                    quantity: 2,
+                },
+                { currencyCode: 'GBP' },
+            );
+
+            orderResultGuard.assertSuccess(adjustOrderLine);
+
+            expect(adjustOrderLine?.lines[0]?.unitPrice).toBe(900);
+            expect(adjustOrderLine?.lines[0]?.unitPriceWithTax).toBe(1080);
+            expect(adjustOrderLine?.lines[1]?.unitPrice).toBe(1900);
+            expect(adjustOrderLine?.lines[1]?.unitPriceWithTax).toBe(2280);
+            expect(adjustOrderLine.currencyCode).toBe('GBP');
+        });
+
+        it('adding a new order line with a new currency updates all lines to that currency', async () => {
+            const { addItemToOrder } = await shopClient.query(
+                AddItemToOrderDocument,
+                {
+                    productVariantId: 'T_3',
+                    quantity: 1,
+                },
+                { currencyCode: 'EUR' },
+            );
+
+            orderResultGuard.assertSuccess(addItemToOrder);
+
+            expect(addItemToOrder?.lines[0]?.unitPrice).toBe(1100);
+            expect(addItemToOrder?.lines[0]?.unitPriceWithTax).toBe(1320);
+            expect(addItemToOrder?.lines[1]?.unitPrice).toBe(2100);
+            expect(addItemToOrder?.lines[1]?.unitPriceWithTax).toBe(2520);
+            expect(addItemToOrder?.lines[2]?.unitPrice).toBe(3100);
+            expect(addItemToOrder?.lines[2]?.unitPriceWithTax).toBe(3720);
+            expect(addItemToOrder.currencyCode).toBe('EUR');
+        });
+    });
 });

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

@@ -1668,6 +1668,14 @@ export class OrderService {
         const promotions = await this.promotionService.getActivePromotionsInChannel(ctx);
         const activePromotionsPre = await this.promotionService.getActivePromotionsOnOrder(ctx, order.id);
 
+        // When changing the Order's currencyCode (on account of passing
+        // a different currencyCode into the RequestContext), we need to make sure
+        // to update all existing OrderLines to use prices in this new currency.
+        if (ctx.currencyCode !== order.currencyCode) {
+            updatedOrderLines = order.lines;
+            order.currencyCode = ctx.currencyCode;
+        }
+
         if (updatedOrderLines?.length) {
             const { orderItemPriceCalculationStrategy, changedPriceHandlingStrategy } =
                 this.configService.orderOptions;

部分文件因文件數量過多而無法顯示