Skip to content

Commit

Permalink
fix(core): Fix draft orders not getting correctly placed
Browse files Browse the repository at this point in the history
Fixes #2105
  • Loading branch information
michaelbromley committed Jul 12, 2023
1 parent 3f8a1da commit 4d01ab5
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 4 deletions.
85 changes: 81 additions & 4 deletions packages/core/e2e/draft-order.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,65 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { LanguageCode } from '@vendure/common/lib/generated-types';
import { DefaultLogger, mergeConfig, orderPercentageDiscount } from '@vendure/core';
import {
DefaultLogger,
DefaultOrderPlacedStrategy,
mergeConfig,
Order,
orderPercentageDiscount,
OrderState,
RequestContext,
} from '@vendure/core';
import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
import gql from 'graphql-tag';
import path from 'path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';

import { initialData } from '../../../e2e-common/e2e-initial-data';
import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';

import { singleStageRefundablePaymentMethod } from './fixtures/test-payment-methods';
import { ORDER_WITH_LINES_FRAGMENT } from './graphql/fragments';
import * as Codegen from './graphql/generated-e2e-admin-types';
import { CanceledOrderFragment, OrderWithLinesFragment } from './graphql/generated-e2e-admin-types';
import {
AddManualPaymentDocument,
AdminTransitionDocument,
CanceledOrderFragment,
GetOrderDocument,
GetOrderPlacedAtDocument,
OrderWithLinesFragment,
} from './graphql/generated-e2e-admin-types';
import {
GetActiveCustomerOrdersQuery,
TestOrderFragmentFragment,
TransitionToStateDocument,
UpdatedOrderFragment,
} from './graphql/generated-e2e-shop-types';
import { CREATE_PROMOTION, GET_CUSTOMER_LIST } from './graphql/shared-definitions';
import { GET_ACTIVE_CUSTOMER_ORDERS } from './graphql/shop-definitions';

class TestOrderPlacedStrategy extends DefaultOrderPlacedStrategy {
static spy = vi.fn();
shouldSetAsPlaced(
ctx: RequestContext,
fromState: OrderState,
toState: OrderState,
order: Order,
): boolean {
TestOrderPlacedStrategy.spy(order);
return super.shouldSetAsPlaced(ctx, fromState, toState, order);
}
}

describe('Draft Orders resolver', () => {
const { server, adminClient, shopClient } = createTestEnvironment(
mergeConfig(testConfig(), {
logger: new DefaultLogger(),
paymentOptions: {
paymentMethodHandlers: [singleStageRefundablePaymentMethod],
},
orderOptions: {
orderPlacedStrategy: new TestOrderPlacedStrategy(),
},
dbConnectionOptions: {
logging: true,
},
Expand All @@ -39,7 +71,7 @@ describe('Draft Orders resolver', () => {

const orderGuard: ErrorResultGuard<
TestOrderFragmentFragment | CanceledOrderFragment | UpdatedOrderFragment
> = createErrorResultGuard(input => !!input.lines);
> = createErrorResultGuard(input => !!input.lines || !!input.state);

beforeAll(async () => {
await server.init({
Expand Down Expand Up @@ -332,6 +364,39 @@ describe('Draft Orders resolver', () => {
expect(setDraftOrderShippingMethod.shippingLines.length).toBe(1);
expect(setDraftOrderShippingMethod.shippingLines[0].shippingMethod.id).toBe('T_2');
});

// https://github.com/vendure-ecommerce/vendure/issues/2105
it('sets order as placed when payment is settled', async () => {
TestOrderPlacedStrategy.spy.mockClear();
expect(TestOrderPlacedStrategy.spy.mock.calls.length).toBe(0);

const { transitionOrderToState } = await adminClient.query(AdminTransitionDocument, {
id: draftOrder.id,
state: 'ArrangingPayment',
});

orderGuard.assertSuccess(transitionOrderToState);
expect(transitionOrderToState.state).toBe('ArrangingPayment');

const { addManualPaymentToOrder } = await adminClient.query(AddManualPaymentDocument, {
input: {
orderId: draftOrder.id,
metadata: {},
method: singleStageRefundablePaymentMethod.code,
transactionId: '12345',
},
});

orderGuard.assertSuccess(addManualPaymentToOrder);
expect(addManualPaymentToOrder.state).toBe('PaymentSettled');

const { order } = await adminClient.query(GetOrderPlacedAtDocument, {
id: draftOrder.id,
});
expect(order?.orderPlacedAt).not.toBeNull();
expect(TestOrderPlacedStrategy.spy.mock.calls.length).toBe(1);
expect(TestOrderPlacedStrategy.spy.mock.calls[0][0].code).toBe(draftOrder.code);
});
});

export const CREATE_DRAFT_ORDER = gql`
Expand Down Expand Up @@ -470,3 +535,15 @@ export const SET_DRAFT_ORDER_SHIPPING_METHOD = gql`
}
${ORDER_WITH_LINES_FRAGMENT}
`;

export const GET_ORDER_PLACED_AT = gql`
query GetOrderPlacedAt($id: ID!) {
order(id: $id) {
id
createdAt
updatedAt
state
orderPlacedAt
}
}
`;
54 changes: 54 additions & 0 deletions packages/core/e2e/graphql/generated-e2e-admin-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7599,6 +7599,14 @@ export type SetDraftOrderShippingMethodMutation = {
| { errorCode: ErrorCode; message: string };
};

export type GetOrderPlacedAtQueryVariables = Exact<{
id: Scalars['ID'];
}>;

export type GetOrderPlacedAtQuery = {
order?: { id: string; createdAt: any; updatedAt: any; state: string; orderPlacedAt?: any | null } | null;
};

export type IdTest1QueryVariables = Exact<{ [key: string]: never }>;

export type IdTest1Query = { products: { items: Array<{ id: string }> } };
Expand Down Expand Up @@ -21066,6 +21074,52 @@ export const SetDraftOrderShippingMethodDocument = {
SetDraftOrderShippingMethodMutation,
SetDraftOrderShippingMethodMutationVariables
>;
export const GetOrderPlacedAtDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
name: { kind: 'Name', value: 'GetOrderPlacedAt' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: { kind: 'Variable', name: { kind: 'Name', value: 'id' } },
type: {
kind: 'NonNullType',
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'order' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'id' },
value: { kind: 'Variable', name: { kind: 'Name', value: 'id' } },
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
{ kind: 'Field', name: { kind: 'Name', value: 'updatedAt' } },
{ kind: 'Field', name: { kind: 'Name', value: 'state' } },
{ kind: 'Field', name: { kind: 'Name', value: 'orderPlacedAt' } },
],
},
},
],
},
},
],
} as unknown as DocumentNode<GetOrderPlacedAtQuery, GetOrderPlacedAtQueryVariables>;
export const IdTest1Document = {
kind: 'Document',
definitions: [
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/config/order/default-order-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,11 @@ export function configureDefaultOrderProcess(options: DefaultOrderProcessOptions
if (toState === 'Cancelled') {
order.active = false;
}
if (fromState === 'Draft' && toState === 'ArrangingPayment') {
// Once we exit the Draft state, we can consider the order active,
// which will allow us to run the OrderPlacedStrategy at the correct point.
order.active = true;
}
await historyService.createHistoryEntryForOrder({
orderId: order.id,
type: HistoryEntryType.ORDER_STATE_TRANSITION,
Expand Down

0 comments on commit 4d01ab5

Please sign in to comment.