Browse Source

fix(core): Fix double-allocation of stock on 2-stage payments

Relates to #550
Michael Bromley 5 years ago
parent
commit
c43a3435e4

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

@@ -753,3 +753,23 @@ export const CREATE_SHIPPING_METHOD = gql`
     }
     ${SHIPPING_METHOD_FRAGMENT}
 `;
+
+export const SETTLE_PAYMENT = gql`
+    mutation SettlePayment($id: ID!) {
+        settlePayment(id: $id) {
+            ...Payment
+            ... on ErrorResult {
+                errorCode
+                message
+            }
+            ... on SettlePaymentError {
+                paymentErrorMessage
+            }
+        }
+    }
+    fragment Payment on Payment {
+        id
+        state
+        metadata
+    }
+`;

+ 1 - 20
packages/core/e2e/order.e2e-spec.ts

@@ -67,6 +67,7 @@ import {
     GET_ORDER_FULFILLMENTS,
     GET_PRODUCT_WITH_VARIANTS,
     GET_STOCK_MOVEMENT,
+    SETTLE_PAYMENT,
     TRANSIT_FULFILLMENT,
     UPDATE_PRODUCT_VARIANTS,
 } from './graphql/shared-definitions';
@@ -1562,26 +1563,6 @@ async function createTestOrder(
     return { product, productVariantId, orderId };
 }
 
-export const SETTLE_PAYMENT = gql`
-    mutation SettlePayment($id: ID!) {
-        settlePayment(id: $id) {
-            ...Payment
-            ... on ErrorResult {
-                errorCode
-                message
-            }
-            ... on SettlePaymentError {
-                paymentErrorMessage
-            }
-        }
-    }
-    fragment Payment on Payment {
-        id
-        state
-        metadata
-    }
-`;
-
 export const GET_ORDER_LIST_FULFILLMENTS = gql`
     query GetOrderListFulfillments {
         orders {

+ 26 - 3
packages/core/e2e/stock-control.e2e-spec.ts

@@ -7,7 +7,7 @@ import path from 'path';
 import { initialData } from '../../../e2e-common/e2e-initial-data';
 import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
 
-import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
+import { testSuccessfulPaymentMethod, twoStagePaymentMethod } from './fixtures/test-payment-methods';
 import { VARIANT_WITH_STOCK_FRAGMENT } from './graphql/fragments';
 import {
     CancelOrder,
@@ -18,6 +18,7 @@ import {
     GetOrder,
     GetStockMovement,
     GlobalFlag,
+    SettlePayment,
     StockMovementType,
     UpdateGlobalSettings,
     UpdateProductVariantInput,
@@ -41,6 +42,7 @@ import {
     CREATE_FULFILLMENT,
     GET_ORDER,
     GET_STOCK_MOVEMENT,
+    SETTLE_PAYMENT,
     UPDATE_GLOBAL_SETTINGS,
     UPDATE_PRODUCT_VARIANTS,
 } from './graphql/shared-definitions';
@@ -57,7 +59,7 @@ describe('Stock control', () => {
     const { server, adminClient, shopClient } = createTestEnvironment(
         mergeConfig(testConfig, {
             paymentOptions: {
-                paymentMethodHandlers: [testSuccessfulPaymentMethod],
+                paymentMethodHandlers: [testSuccessfulPaymentMethod, twoStagePaymentMethod],
             },
         }),
     );
@@ -543,7 +545,7 @@ describe('Stock control', () => {
 
         it('allocates stock', async () => {
             await proceedToArrangingPayment(shopClient);
-            const result = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
+            const result = await addPaymentToOrder(shopClient, twoStagePaymentMethod);
             orderGuard.assertSuccess(result);
             order = result;
 
@@ -563,6 +565,27 @@ describe('Stock control', () => {
             expect(variant4.stockOnHand).toBe(3);
         });
 
+        it('does not re-allocate stock when transitioning Payment from Authorized -> Settled', async () => {
+            await adminClient.query<SettlePayment.Mutation, SettlePayment.Variables>(SETTLE_PAYMENT, {
+                id: order.id,
+            });
+
+            const product = await getProductWithStockMovement('T_1');
+            const [variant1, variant2, variant3, variant4] = product!.variants;
+
+            expect(variant1.stockAllocated).toBe(3);
+            expect(variant1.stockOnHand).toBe(3);
+
+            expect(variant2.stockAllocated).toBe(0); // inventory not tracked
+            expect(variant2.stockOnHand).toBe(3);
+
+            expect(variant3.stockAllocated).toBe(1);
+            expect(variant3.stockOnHand).toBe(3);
+
+            expect(variant4.stockAllocated).toBe(8);
+            expect(variant4.stockOnHand).toBe(3);
+        });
+
         it('addFulfillmentToOrder returns ErrorResult when insufficient stock on hand', async () => {
             const { addFulfillmentToOrder } = await adminClient.query<
                 CreateFulfillment.Mutation,

+ 4 - 1
packages/core/src/service/helpers/order-state-machine/order-state-machine.ts

@@ -119,7 +119,10 @@ export class OrderStateMachine {
      * Specific business logic to be executed after Order state transition completes.
      */
     private async onTransitionEnd(fromState: OrderState, toState: OrderState, data: OrderTransitionData) {
-        if (toState === 'PaymentAuthorized' || toState === 'PaymentSettled') {
+        if (
+            fromState === 'ArrangingPayment' &&
+            (toState === 'PaymentAuthorized' || toState === 'PaymentSettled')
+        ) {
             data.order.active = false;
             data.order.orderPlacedAt = new Date();
             await this.stockMovementService.createAllocationsForOrder(data.ctx, data.order);