order.e2e-spec.ts 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656
  1. /* tslint:disable:no-non-null-assertion */
  2. import { pick } from '@vendure/common/lib/pick';
  3. import {
  4. createErrorResultGuard,
  5. createTestEnvironment,
  6. ErrorResultGuard,
  7. SimpleGraphQLClient,
  8. } from '@vendure/testing';
  9. import gql from 'graphql-tag';
  10. import path from 'path';
  11. import { initialData } from '../../../e2e-common/e2e-initial-data';
  12. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  13. import {
  14. failsToSettlePaymentMethod,
  15. onTransitionSpy,
  16. singleStageRefundablePaymentMethod,
  17. twoStagePaymentMethod,
  18. } from './fixtures/test-payment-methods';
  19. import { FULFILLMENT_FRAGMENT } from './graphql/fragments';
  20. import {
  21. AddNoteToOrder,
  22. CanceledOrderFragment,
  23. CancelOrder,
  24. CreateFulfillment,
  25. DeleteOrderNote,
  26. ErrorCode,
  27. FulfillmentFragment,
  28. GetCustomerList,
  29. GetOrder,
  30. GetOrderFulfillmentItems,
  31. GetOrderFulfillments,
  32. GetOrderHistory,
  33. GetOrderList,
  34. GetOrderListFulfillments,
  35. GetOrderWithPayments,
  36. GetProductWithVariants,
  37. GetStockMovement,
  38. GlobalFlag,
  39. HistoryEntryType,
  40. PaymentFragment,
  41. RefundFragment,
  42. RefundOrder,
  43. SettlePayment,
  44. SettleRefund,
  45. SortOrder,
  46. StockMovementType,
  47. TransitFulfillment,
  48. UpdateOrderNote,
  49. UpdateProductVariants,
  50. } from './graphql/generated-e2e-admin-types';
  51. import {
  52. AddItemToOrder,
  53. DeletionResult,
  54. GetActiveOrder,
  55. GetOrderByCodeWithPayments,
  56. TestOrderFragmentFragment,
  57. UpdatedOrder,
  58. } from './graphql/generated-e2e-shop-types';
  59. import {
  60. CANCEL_ORDER,
  61. CREATE_FULFILLMENT,
  62. GET_CUSTOMER_LIST,
  63. GET_ORDER,
  64. GET_ORDERS_LIST,
  65. GET_ORDER_FULFILLMENTS,
  66. GET_PRODUCT_WITH_VARIANTS,
  67. GET_STOCK_MOVEMENT,
  68. TRANSIT_FULFILLMENT,
  69. UPDATE_PRODUCT_VARIANTS,
  70. } from './graphql/shared-definitions';
  71. import {
  72. ADD_ITEM_TO_ORDER,
  73. GET_ACTIVE_ORDER,
  74. GET_ORDER_BY_CODE_WITH_PAYMENTS,
  75. } from './graphql/shop-definitions';
  76. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  77. import { addPaymentToOrder, proceedToArrangingPayment } from './utils/test-order-utils';
  78. describe('Orders resolver', () => {
  79. const { server, adminClient, shopClient } = createTestEnvironment({
  80. ...testConfig,
  81. paymentOptions: {
  82. paymentMethodHandlers: [
  83. twoStagePaymentMethod,
  84. failsToSettlePaymentMethod,
  85. singleStageRefundablePaymentMethod,
  86. ],
  87. },
  88. });
  89. let customers: GetCustomerList.Items[];
  90. const password = 'test';
  91. const orderGuard: ErrorResultGuard<
  92. TestOrderFragmentFragment | CanceledOrderFragment
  93. > = createErrorResultGuard<TestOrderFragmentFragment | CanceledOrderFragment>(input => !!input.lines);
  94. const paymentGuard: ErrorResultGuard<PaymentFragment> = createErrorResultGuard<PaymentFragment>(
  95. input => !!input.state,
  96. );
  97. const fulfillmentGuard: ErrorResultGuard<FulfillmentFragment> = createErrorResultGuard<
  98. FulfillmentFragment
  99. >(input => !!input.method);
  100. const refundGuard: ErrorResultGuard<RefundFragment> = createErrorResultGuard<RefundFragment>(
  101. input => !!input.items,
  102. );
  103. beforeAll(async () => {
  104. await server.init({
  105. initialData,
  106. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  107. customerCount: 3,
  108. });
  109. await adminClient.asSuperAdmin();
  110. // Create a couple of orders to be queried
  111. const result = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
  112. GET_CUSTOMER_LIST,
  113. {
  114. options: {
  115. take: 3,
  116. },
  117. },
  118. );
  119. customers = result.customers.items;
  120. await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
  121. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  122. productVariantId: 'T_1',
  123. quantity: 1,
  124. });
  125. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  126. productVariantId: 'T_2',
  127. quantity: 1,
  128. });
  129. await shopClient.asUserWithCredentials(customers[1].emailAddress, password);
  130. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  131. productVariantId: 'T_2',
  132. quantity: 1,
  133. });
  134. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  135. productVariantId: 'T_3',
  136. quantity: 3,
  137. });
  138. }, TEST_SETUP_TIMEOUT_MS);
  139. afterAll(async () => {
  140. await server.destroy();
  141. });
  142. it('orders', async () => {
  143. const result = await adminClient.query<GetOrderList.Query>(GET_ORDERS_LIST);
  144. expect(result.orders.items.map(o => o.id)).toEqual(['T_1', 'T_2']);
  145. });
  146. it('order', async () => {
  147. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, { id: 'T_2' });
  148. expect(result.order!.id).toBe('T_2');
  149. });
  150. it('order history initially empty', async () => {
  151. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  152. GET_ORDER_HISTORY,
  153. { id: 'T_1' },
  154. );
  155. expect(order!.history.totalItems).toBe(0);
  156. expect(order!.history.items).toEqual([]);
  157. });
  158. describe('payments', () => {
  159. let firstOrderCode: string;
  160. let firstOrderId: string;
  161. it('settlePayment fails', async () => {
  162. await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
  163. await proceedToArrangingPayment(shopClient);
  164. const order = await addPaymentToOrder(shopClient, failsToSettlePaymentMethod);
  165. orderGuard.assertSuccess(order);
  166. expect(order.state).toBe('PaymentAuthorized');
  167. const payment = order.payments![0];
  168. const { settlePayment } = await adminClient.query<
  169. SettlePayment.Mutation,
  170. SettlePayment.Variables
  171. >(SETTLE_PAYMENT, {
  172. id: payment.id,
  173. });
  174. paymentGuard.assertErrorResult(settlePayment);
  175. expect(settlePayment.message).toBe('Settling the payment failed');
  176. expect(settlePayment.errorCode).toBe(ErrorCode.SETTLE_PAYMENT_ERROR);
  177. expect((settlePayment as any).paymentErrorMessage).toBe('Something went horribly wrong');
  178. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  179. id: order.id,
  180. });
  181. expect(result.order!.state).toBe('PaymentAuthorized');
  182. firstOrderCode = order.code;
  183. firstOrderId = order.id;
  184. });
  185. it('public payment metadata available in Shop API', async () => {
  186. const { orderByCode } = await shopClient.query<
  187. GetOrderByCodeWithPayments.Query,
  188. GetOrderByCodeWithPayments.Variables
  189. >(GET_ORDER_BY_CODE_WITH_PAYMENTS, { code: firstOrderCode });
  190. expect(orderByCode?.payments?.[0].metadata).toEqual({
  191. public: {
  192. publicCreatePaymentData: 'public',
  193. publicSettlePaymentData: 'public',
  194. },
  195. });
  196. });
  197. it('public and private payment metadata available in Admin API', async () => {
  198. const { order } = await adminClient.query<
  199. GetOrderWithPayments.Query,
  200. GetOrderWithPayments.Variables
  201. >(GET_ORDER_WITH_PAYMENTS, { id: firstOrderId });
  202. expect(order?.payments?.[0].metadata).toEqual({
  203. privateCreatePaymentData: 'secret',
  204. privateSettlePaymentData: 'secret',
  205. public: {
  206. publicCreatePaymentData: 'public',
  207. publicSettlePaymentData: 'public',
  208. },
  209. });
  210. });
  211. it('settlePayment succeeds, onStateTransitionStart called', async () => {
  212. onTransitionSpy.mockClear();
  213. await shopClient.asUserWithCredentials(customers[1].emailAddress, password);
  214. await proceedToArrangingPayment(shopClient);
  215. const order = await addPaymentToOrder(shopClient, twoStagePaymentMethod);
  216. orderGuard.assertSuccess(order);
  217. expect(order.state).toBe('PaymentAuthorized');
  218. expect(onTransitionSpy).toHaveBeenCalledTimes(1);
  219. expect(onTransitionSpy.mock.calls[0][0]).toBe('Created');
  220. expect(onTransitionSpy.mock.calls[0][1]).toBe('Authorized');
  221. const payment = order.payments![0];
  222. const { settlePayment } = await adminClient.query<
  223. SettlePayment.Mutation,
  224. SettlePayment.Variables
  225. >(SETTLE_PAYMENT, {
  226. id: payment.id,
  227. });
  228. paymentGuard.assertSuccess(settlePayment);
  229. expect(settlePayment!.id).toBe(payment.id);
  230. expect(settlePayment!.state).toBe('Settled');
  231. // further metadata is combined into existing object
  232. expect(settlePayment!.metadata).toEqual({
  233. moreData: 42,
  234. public: {
  235. baz: 'quux',
  236. },
  237. });
  238. expect(onTransitionSpy).toHaveBeenCalledTimes(2);
  239. expect(onTransitionSpy.mock.calls[1][0]).toBe('Authorized');
  240. expect(onTransitionSpy.mock.calls[1][1]).toBe('Settled');
  241. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  242. id: order.id,
  243. });
  244. expect(result.order!.state).toBe('PaymentSettled');
  245. expect(result.order!.payments![0].state).toBe('Settled');
  246. });
  247. it('order history contains expected entries', async () => {
  248. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  249. GET_ORDER_HISTORY,
  250. { id: 'T_2', options: { sort: { id: SortOrder.ASC } } },
  251. );
  252. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  253. {
  254. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  255. data: {
  256. from: 'AddingItems',
  257. to: 'ArrangingPayment',
  258. },
  259. },
  260. {
  261. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  262. data: {
  263. paymentId: 'T_2',
  264. from: 'Created',
  265. to: 'Authorized',
  266. },
  267. },
  268. {
  269. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  270. data: {
  271. from: 'ArrangingPayment',
  272. to: 'PaymentAuthorized',
  273. },
  274. },
  275. {
  276. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  277. data: {
  278. paymentId: 'T_2',
  279. from: 'Authorized',
  280. to: 'Settled',
  281. },
  282. },
  283. {
  284. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  285. data: {
  286. from: 'PaymentAuthorized',
  287. to: 'PaymentSettled',
  288. },
  289. },
  290. ]);
  291. });
  292. });
  293. describe('fulfillment', () => {
  294. it('return error result if lines is empty', async () => {
  295. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  296. id: 'T_2',
  297. });
  298. expect(order!.state).toBe('PaymentSettled');
  299. const { addFulfillmentToOrder } = await adminClient.query<
  300. CreateFulfillment.Mutation,
  301. CreateFulfillment.Variables
  302. >(CREATE_FULFILLMENT, {
  303. input: {
  304. lines: [],
  305. method: 'Test',
  306. },
  307. });
  308. fulfillmentGuard.assertErrorResult(addFulfillmentToOrder);
  309. expect(addFulfillmentToOrder.message).toBe('At least one OrderLine must be specified');
  310. expect(addFulfillmentToOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  311. });
  312. it('returns error result if all quantities are zero', async () => {
  313. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  314. id: 'T_2',
  315. });
  316. expect(order!.state).toBe('PaymentSettled');
  317. const { addFulfillmentToOrder } = await adminClient.query<
  318. CreateFulfillment.Mutation,
  319. CreateFulfillment.Variables
  320. >(CREATE_FULFILLMENT, {
  321. input: {
  322. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
  323. method: 'Test',
  324. },
  325. });
  326. fulfillmentGuard.assertErrorResult(addFulfillmentToOrder);
  327. expect(addFulfillmentToOrder.message).toBe('At least one OrderLine must be specified');
  328. expect(addFulfillmentToOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  329. });
  330. it('creates the first fulfillment', async () => {
  331. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  332. id: 'T_2',
  333. });
  334. expect(order!.state).toBe('PaymentSettled');
  335. const lines = order!.lines;
  336. const { addFulfillmentToOrder } = await adminClient.query<
  337. CreateFulfillment.Mutation,
  338. CreateFulfillment.Variables
  339. >(CREATE_FULFILLMENT, {
  340. input: {
  341. lines: [{ orderLineId: lines[0].id, quantity: lines[0].quantity }],
  342. method: 'Test1',
  343. trackingCode: '111',
  344. },
  345. });
  346. fulfillmentGuard.assertSuccess(addFulfillmentToOrder);
  347. expect(addFulfillmentToOrder.id).toBe('T_1');
  348. expect(addFulfillmentToOrder.method).toBe('Test1');
  349. expect(addFulfillmentToOrder.trackingCode).toBe('111');
  350. expect(addFulfillmentToOrder.state).toBe('Pending');
  351. expect(addFulfillmentToOrder.orderItems).toEqual([{ id: lines[0].items[0].id }]);
  352. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  353. id: 'T_2',
  354. });
  355. expect(result.order!.lines[0].items[0].fulfillment!.id).toBe(addFulfillmentToOrder!.id);
  356. expect(
  357. result.order!.lines[1].items.filter(
  358. i => i.fulfillment && i.fulfillment.id === addFulfillmentToOrder.id,
  359. ).length,
  360. ).toBe(0);
  361. expect(result.order!.lines[1].items.filter(i => i.fulfillment == null).length).toBe(3);
  362. });
  363. it('creates the second fulfillment', async () => {
  364. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  365. id: 'T_2',
  366. });
  367. const unfulfilledItems =
  368. order?.lines.filter(l => {
  369. const items = l.items.filter(i => i.fulfillment === null);
  370. return items.length > 0 ? true : false;
  371. }) || [];
  372. const { addFulfillmentToOrder } = await adminClient.query<
  373. CreateFulfillment.Mutation,
  374. CreateFulfillment.Variables
  375. >(CREATE_FULFILLMENT, {
  376. input: {
  377. lines: unfulfilledItems.map(l => ({
  378. orderLineId: l.id,
  379. quantity: l.items.length,
  380. })),
  381. method: 'Test2',
  382. trackingCode: '222',
  383. },
  384. });
  385. fulfillmentGuard.assertSuccess(addFulfillmentToOrder);
  386. expect(addFulfillmentToOrder.id).toBe('T_2');
  387. expect(addFulfillmentToOrder.method).toBe('Test2');
  388. expect(addFulfillmentToOrder.trackingCode).toBe('222');
  389. expect(addFulfillmentToOrder.state).toBe('Pending');
  390. });
  391. it('returns error result if an OrderItem already part of a Fulfillment', async () => {
  392. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  393. id: 'T_2',
  394. });
  395. const { addFulfillmentToOrder } = await adminClient.query<
  396. CreateFulfillment.Mutation,
  397. CreateFulfillment.Variables
  398. >(CREATE_FULFILLMENT, {
  399. input: {
  400. method: 'Test',
  401. lines: [
  402. {
  403. orderLineId: order!.lines[0].id,
  404. quantity: 1,
  405. },
  406. ],
  407. },
  408. });
  409. fulfillmentGuard.assertErrorResult(addFulfillmentToOrder);
  410. expect(addFulfillmentToOrder.message).toBe(
  411. 'One or more OrderItems are already part of a Fulfillment',
  412. );
  413. expect(addFulfillmentToOrder.errorCode).toBe(ErrorCode.ITEMS_ALREADY_FULFILLED_ERROR);
  414. });
  415. it('transits the first fulfillment from created to Shipped and automatically change the order state to PartiallyShipped', async () => {
  416. const { transitionFulfillmentToState } = await adminClient.query<
  417. TransitFulfillment.Mutation,
  418. TransitFulfillment.Variables
  419. >(TRANSIT_FULFILLMENT, {
  420. id: 'T_1',
  421. state: 'Shipped',
  422. });
  423. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  424. expect(transitionFulfillmentToState.id).toBe('T_1');
  425. expect(transitionFulfillmentToState.state).toBe('Shipped');
  426. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  427. id: 'T_2',
  428. });
  429. expect(order?.state).toBe('PartiallyShipped');
  430. });
  431. it('transits the second fulfillment from created to Shipped and automatically change the order state to Shipped', async () => {
  432. const { transitionFulfillmentToState } = await adminClient.query<
  433. TransitFulfillment.Mutation,
  434. TransitFulfillment.Variables
  435. >(TRANSIT_FULFILLMENT, {
  436. id: 'T_2',
  437. state: 'Shipped',
  438. });
  439. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  440. expect(transitionFulfillmentToState.id).toBe('T_2');
  441. expect(transitionFulfillmentToState.state).toBe('Shipped');
  442. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  443. id: 'T_2',
  444. });
  445. expect(order?.state).toBe('Shipped');
  446. });
  447. it('transits the first fulfillment from Shipped to Delivered and change the order state to PartiallyDelivered', async () => {
  448. const { transitionFulfillmentToState } = await adminClient.query<
  449. TransitFulfillment.Mutation,
  450. TransitFulfillment.Variables
  451. >(TRANSIT_FULFILLMENT, {
  452. id: 'T_1',
  453. state: 'Delivered',
  454. });
  455. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  456. expect(transitionFulfillmentToState.id).toBe('T_1');
  457. expect(transitionFulfillmentToState.state).toBe('Delivered');
  458. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  459. id: 'T_2',
  460. });
  461. expect(order?.state).toBe('PartiallyDelivered');
  462. });
  463. it('transits the second fulfillment from Shipped to Delivered and change the order state to Delivered', async () => {
  464. const { transitionFulfillmentToState } = await adminClient.query<
  465. TransitFulfillment.Mutation,
  466. TransitFulfillment.Variables
  467. >(TRANSIT_FULFILLMENT, {
  468. id: 'T_2',
  469. state: 'Delivered',
  470. });
  471. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  472. expect(transitionFulfillmentToState.id).toBe('T_2');
  473. expect(transitionFulfillmentToState.state).toBe('Delivered');
  474. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  475. id: 'T_2',
  476. });
  477. expect(order?.state).toBe('Delivered');
  478. });
  479. it('order history contains expected entries', async () => {
  480. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  481. GET_ORDER_HISTORY,
  482. {
  483. id: 'T_2',
  484. options: {
  485. skip: 5,
  486. },
  487. },
  488. );
  489. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  490. {
  491. data: {
  492. fulfillmentId: 'T_1',
  493. },
  494. type: HistoryEntryType.ORDER_FULFILLMENT,
  495. },
  496. {
  497. data: {
  498. fulfillmentId: 'T_2',
  499. },
  500. type: HistoryEntryType.ORDER_FULFILLMENT,
  501. },
  502. {
  503. data: {
  504. from: 'Pending',
  505. fulfillmentId: 'T_1',
  506. to: 'Shipped',
  507. },
  508. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  509. },
  510. {
  511. data: {
  512. from: 'PaymentSettled',
  513. to: 'PartiallyShipped',
  514. },
  515. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  516. },
  517. {
  518. data: {
  519. from: 'Pending',
  520. fulfillmentId: 'T_2',
  521. to: 'Shipped',
  522. },
  523. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  524. },
  525. {
  526. data: {
  527. from: 'PartiallyShipped',
  528. to: 'Shipped',
  529. },
  530. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  531. },
  532. {
  533. data: {
  534. from: 'Shipped',
  535. fulfillmentId: 'T_1',
  536. to: 'Delivered',
  537. },
  538. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  539. },
  540. {
  541. data: {
  542. from: 'Shipped',
  543. to: 'PartiallyDelivered',
  544. },
  545. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  546. },
  547. {
  548. data: {
  549. from: 'Shipped',
  550. fulfillmentId: 'T_2',
  551. to: 'Delivered',
  552. },
  553. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  554. },
  555. {
  556. data: {
  557. from: 'PartiallyDelivered',
  558. to: 'Delivered',
  559. },
  560. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  561. },
  562. ]);
  563. });
  564. it('order.fulfillments resolver for single order', async () => {
  565. const { order } = await adminClient.query<
  566. GetOrderFulfillments.Query,
  567. GetOrderFulfillments.Variables
  568. >(GET_ORDER_FULFILLMENTS, {
  569. id: 'T_2',
  570. });
  571. expect(order!.fulfillments).toEqual([
  572. { id: 'T_1', method: 'Test1', state: 'Delivered', nextStates: ['Cancelled'] },
  573. { id: 'T_2', method: 'Test2', state: 'Delivered', nextStates: ['Cancelled'] },
  574. ]);
  575. });
  576. it('order.fulfillments resolver for order list', async () => {
  577. const { orders } = await adminClient.query<GetOrderListFulfillments.Query>(
  578. GET_ORDER_LIST_FULFILLMENTS,
  579. );
  580. expect(orders.items[0].fulfillments).toEqual([]);
  581. expect(orders.items[1].fulfillments).toEqual([
  582. { id: 'T_1', method: 'Test1', state: 'Delivered', nextStates: ['Cancelled'] },
  583. { id: 'T_2', method: 'Test2', state: 'Delivered', nextStates: ['Cancelled'] },
  584. ]);
  585. });
  586. it('order.fulfillments.orderItems resolver', async () => {
  587. const { order } = await adminClient.query<
  588. GetOrderFulfillmentItems.Query,
  589. GetOrderFulfillmentItems.Variables
  590. >(GET_ORDER_FULFILLMENT_ITEMS, {
  591. id: 'T_2',
  592. });
  593. expect(order!.fulfillments![0].orderItems).toEqual([{ id: 'T_3' }]);
  594. expect(order!.fulfillments![1].orderItems).toEqual([{ id: 'T_4' }, { id: 'T_5' }, { id: 'T_6' }]);
  595. });
  596. });
  597. describe('cancellation by orderId', () => {
  598. it('cancel from AddingItems state', async () => {
  599. const testOrder = await createTestOrder(
  600. adminClient,
  601. shopClient,
  602. customers[0].emailAddress,
  603. password,
  604. );
  605. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  606. id: testOrder.orderId,
  607. });
  608. expect(order!.state).toBe('AddingItems');
  609. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  610. CANCEL_ORDER,
  611. {
  612. input: {
  613. orderId: testOrder.orderId,
  614. },
  615. },
  616. );
  617. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  618. id: testOrder.orderId,
  619. });
  620. expect(order2!.state).toBe('Cancelled');
  621. expect(order2!.active).toBe(false);
  622. await assertNoStockMovementsCreated(testOrder.product.id);
  623. });
  624. it('cancel from ArrangingPayment state', async () => {
  625. const testOrder = await createTestOrder(
  626. adminClient,
  627. shopClient,
  628. customers[0].emailAddress,
  629. password,
  630. );
  631. await proceedToArrangingPayment(shopClient);
  632. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  633. id: testOrder.orderId,
  634. });
  635. expect(order!.state).toBe('ArrangingPayment');
  636. await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(CANCEL_ORDER, {
  637. input: {
  638. orderId: testOrder.orderId,
  639. },
  640. });
  641. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  642. id: testOrder.orderId,
  643. });
  644. expect(order2!.state).toBe('Cancelled');
  645. expect(order2!.active).toBe(false);
  646. await assertNoStockMovementsCreated(testOrder.product.id);
  647. });
  648. it('cancel from PaymentAuthorized state', async () => {
  649. const testOrder = await createTestOrder(
  650. adminClient,
  651. shopClient,
  652. customers[0].emailAddress,
  653. password,
  654. );
  655. await proceedToArrangingPayment(shopClient);
  656. const order = await addPaymentToOrder(shopClient, failsToSettlePaymentMethod);
  657. orderGuard.assertSuccess(order);
  658. expect(order.state).toBe('PaymentAuthorized');
  659. const result1 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  660. GET_STOCK_MOVEMENT,
  661. {
  662. id: 'T_3',
  663. },
  664. );
  665. let variant1 = result1.product!.variants[0];
  666. expect(variant1.stockOnHand).toBe(100);
  667. expect(variant1.stockAllocated).toBe(2);
  668. expect(variant1.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  669. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  670. { type: StockMovementType.ALLOCATION, quantity: 2 },
  671. ]);
  672. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  673. CANCEL_ORDER,
  674. {
  675. input: {
  676. orderId: testOrder.orderId,
  677. },
  678. },
  679. );
  680. orderGuard.assertSuccess(cancelOrder);
  681. expect(
  682. cancelOrder.lines.map(l =>
  683. l.items.map(pick(['id', 'cancelled'])).sort((a, b) => (a.id > b.id ? 1 : -1)),
  684. ),
  685. ).toEqual([
  686. [
  687. { id: 'T_11', cancelled: true },
  688. { id: 'T_12', cancelled: true },
  689. ],
  690. ]);
  691. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  692. id: testOrder.orderId,
  693. });
  694. expect(order2!.active).toBe(false);
  695. expect(order2!.state).toBe('Cancelled');
  696. const result2 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  697. GET_STOCK_MOVEMENT,
  698. {
  699. id: 'T_3',
  700. },
  701. );
  702. variant1 = result2.product!.variants[0];
  703. expect(variant1.stockOnHand).toBe(100);
  704. expect(variant1.stockAllocated).toBe(0);
  705. expect(variant1.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  706. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  707. { type: StockMovementType.ALLOCATION, quantity: 2 },
  708. { type: StockMovementType.RELEASE, quantity: 1 },
  709. { type: StockMovementType.RELEASE, quantity: 1 },
  710. ]);
  711. });
  712. async function assertNoStockMovementsCreated(productId: string) {
  713. const result = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  714. GET_STOCK_MOVEMENT,
  715. {
  716. id: productId,
  717. },
  718. );
  719. const variant2 = result.product!.variants[0];
  720. expect(variant2.stockOnHand).toBe(100);
  721. expect(variant2.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  722. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  723. ]);
  724. }
  725. });
  726. describe('cancellation by OrderLine', () => {
  727. let orderId: string;
  728. let product: GetProductWithVariants.Product;
  729. let productVariantId: string;
  730. beforeAll(async () => {
  731. const result = await createTestOrder(
  732. adminClient,
  733. shopClient,
  734. customers[0].emailAddress,
  735. password,
  736. );
  737. orderId = result.orderId;
  738. product = result.product;
  739. productVariantId = result.productVariantId;
  740. });
  741. it('cannot cancel from AddingItems state', async () => {
  742. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  743. id: orderId,
  744. });
  745. expect(order!.state).toBe('AddingItems');
  746. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  747. CANCEL_ORDER,
  748. {
  749. input: {
  750. orderId,
  751. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  752. },
  753. },
  754. );
  755. orderGuard.assertErrorResult(cancelOrder);
  756. expect(cancelOrder.message).toBe(
  757. 'Cannot cancel OrderLines from an Order in the "AddingItems" state',
  758. );
  759. expect(cancelOrder.errorCode).toBe(ErrorCode.CANCEL_ACTIVE_ORDER_ERROR);
  760. });
  761. it('cannot cancel from ArrangingPayment state', async () => {
  762. await proceedToArrangingPayment(shopClient);
  763. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  764. id: orderId,
  765. });
  766. expect(order!.state).toBe('ArrangingPayment');
  767. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  768. CANCEL_ORDER,
  769. {
  770. input: {
  771. orderId,
  772. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  773. },
  774. },
  775. );
  776. orderGuard.assertErrorResult(cancelOrder);
  777. expect(cancelOrder.message).toBe(
  778. 'Cannot cancel OrderLines from an Order in the "ArrangingPayment" state',
  779. );
  780. expect(cancelOrder.errorCode).toBe(ErrorCode.CANCEL_ACTIVE_ORDER_ERROR);
  781. });
  782. it('returns error result if lines are empty', async () => {
  783. const order = await addPaymentToOrder(shopClient, twoStagePaymentMethod);
  784. orderGuard.assertSuccess(order);
  785. expect(order.state).toBe('PaymentAuthorized');
  786. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  787. CANCEL_ORDER,
  788. {
  789. input: {
  790. orderId,
  791. lines: [],
  792. },
  793. },
  794. );
  795. orderGuard.assertErrorResult(cancelOrder);
  796. expect(cancelOrder.message).toBe('At least one OrderLine must be specified');
  797. expect(cancelOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  798. });
  799. it('returns error result if all quantities zero', async () => {
  800. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  801. id: orderId,
  802. });
  803. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  804. CANCEL_ORDER,
  805. {
  806. input: {
  807. orderId,
  808. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
  809. },
  810. },
  811. );
  812. orderGuard.assertErrorResult(cancelOrder);
  813. expect(cancelOrder.message).toBe('At least one OrderLine must be specified');
  814. expect(cancelOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  815. });
  816. it('partial cancellation', async () => {
  817. const result1 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  818. GET_STOCK_MOVEMENT,
  819. {
  820. id: product.id,
  821. },
  822. );
  823. const variant1 = result1.product!.variants[0];
  824. expect(variant1.stockOnHand).toBe(100);
  825. expect(variant1.stockAllocated).toBe(2);
  826. expect(variant1.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  827. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  828. { type: StockMovementType.ALLOCATION, quantity: 2 },
  829. { type: StockMovementType.RELEASE, quantity: 1 },
  830. { type: StockMovementType.RELEASE, quantity: 1 },
  831. { type: StockMovementType.ALLOCATION, quantity: 2 },
  832. ]);
  833. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  834. id: orderId,
  835. });
  836. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  837. CANCEL_ORDER,
  838. {
  839. input: {
  840. orderId,
  841. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  842. reason: 'cancel reason 1',
  843. },
  844. },
  845. );
  846. orderGuard.assertSuccess(cancelOrder);
  847. expect(cancelOrder.lines[0].quantity).toBe(1);
  848. expect(cancelOrder.lines[0].items.sort((a, b) => (a.id < b.id ? -1 : 1))).toEqual([
  849. { id: 'T_13', cancelled: true },
  850. { id: 'T_14', cancelled: false },
  851. ]);
  852. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  853. id: orderId,
  854. });
  855. expect(order2!.state).toBe('PaymentAuthorized');
  856. expect(order2!.lines[0].quantity).toBe(1);
  857. const result2 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  858. GET_STOCK_MOVEMENT,
  859. {
  860. id: product.id,
  861. },
  862. );
  863. const variant2 = result2.product!.variants[0];
  864. expect(variant2.stockOnHand).toBe(100);
  865. expect(variant2.stockAllocated).toBe(1);
  866. expect(variant2.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  867. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  868. { type: StockMovementType.ALLOCATION, quantity: 2 },
  869. { type: StockMovementType.RELEASE, quantity: 1 },
  870. { type: StockMovementType.RELEASE, quantity: 1 },
  871. { type: StockMovementType.ALLOCATION, quantity: 2 },
  872. { type: StockMovementType.RELEASE, quantity: 1 },
  873. ]);
  874. });
  875. it('returns error result if attempting to cancel already cancelled item', async () => {
  876. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  877. id: orderId,
  878. });
  879. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  880. CANCEL_ORDER,
  881. {
  882. input: {
  883. orderId,
  884. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 2 })),
  885. },
  886. },
  887. );
  888. orderGuard.assertErrorResult(cancelOrder);
  889. expect(cancelOrder.message).toBe(
  890. 'The specified quantity is greater than the available OrderItems',
  891. );
  892. expect(cancelOrder.errorCode).toBe(ErrorCode.QUANTITY_TOO_GREAT_ERROR);
  893. });
  894. it('complete cancellation', async () => {
  895. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  896. id: orderId,
  897. });
  898. await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(CANCEL_ORDER, {
  899. input: {
  900. orderId,
  901. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  902. reason: 'cancel reason 2',
  903. },
  904. });
  905. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  906. id: orderId,
  907. });
  908. expect(order2!.state).toBe('Cancelled');
  909. const result = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  910. GET_STOCK_MOVEMENT,
  911. {
  912. id: product.id,
  913. },
  914. );
  915. const variant2 = result.product!.variants[0];
  916. expect(variant2.stockOnHand).toBe(100);
  917. expect(variant2.stockAllocated).toBe(0);
  918. expect(variant2.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  919. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  920. { type: StockMovementType.ALLOCATION, quantity: 2 },
  921. { type: StockMovementType.RELEASE, quantity: 1 },
  922. { type: StockMovementType.RELEASE, quantity: 1 },
  923. { type: StockMovementType.ALLOCATION, quantity: 2 },
  924. { type: StockMovementType.RELEASE, quantity: 1 },
  925. { type: StockMovementType.RELEASE, quantity: 1 },
  926. ]);
  927. });
  928. it('order history contains expected entries', async () => {
  929. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  930. GET_ORDER_HISTORY,
  931. {
  932. id: orderId,
  933. options: {
  934. skip: 0,
  935. },
  936. },
  937. );
  938. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  939. {
  940. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  941. data: {
  942. from: 'AddingItems',
  943. to: 'ArrangingPayment',
  944. },
  945. },
  946. {
  947. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  948. data: {
  949. paymentId: 'T_4',
  950. from: 'Created',
  951. to: 'Authorized',
  952. },
  953. },
  954. {
  955. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  956. data: {
  957. from: 'ArrangingPayment',
  958. to: 'PaymentAuthorized',
  959. },
  960. },
  961. {
  962. type: HistoryEntryType.ORDER_CANCELLATION,
  963. data: {
  964. orderItemIds: ['T_13'],
  965. reason: 'cancel reason 1',
  966. },
  967. },
  968. {
  969. type: HistoryEntryType.ORDER_CANCELLATION,
  970. data: {
  971. orderItemIds: ['T_14'],
  972. reason: 'cancel reason 2',
  973. },
  974. },
  975. {
  976. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  977. data: {
  978. from: 'PaymentAuthorized',
  979. to: 'Cancelled',
  980. },
  981. },
  982. ]);
  983. });
  984. });
  985. describe('refunds', () => {
  986. let orderId: string;
  987. let product: GetProductWithVariants.Product;
  988. let productVariantId: string;
  989. let paymentId: string;
  990. let refundId: string;
  991. beforeAll(async () => {
  992. const result = await createTestOrder(
  993. adminClient,
  994. shopClient,
  995. customers[0].emailAddress,
  996. password,
  997. );
  998. orderId = result.orderId;
  999. product = result.product;
  1000. productVariantId = result.productVariantId;
  1001. });
  1002. it('cannot refund from PaymentAuthorized state', async () => {
  1003. await proceedToArrangingPayment(shopClient);
  1004. const order = await addPaymentToOrder(shopClient, twoStagePaymentMethod);
  1005. orderGuard.assertSuccess(order);
  1006. expect(order.state).toBe('PaymentAuthorized');
  1007. paymentId = order.payments![0].id;
  1008. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1009. REFUND_ORDER,
  1010. {
  1011. input: {
  1012. lines: order.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  1013. shipping: 0,
  1014. adjustment: 0,
  1015. paymentId,
  1016. },
  1017. },
  1018. );
  1019. refundGuard.assertErrorResult(refundOrder);
  1020. expect(refundOrder.message).toBe('Cannot refund an Order in the "PaymentAuthorized" state');
  1021. expect(refundOrder.errorCode).toBe(ErrorCode.REFUND_ORDER_STATE_ERROR);
  1022. });
  1023. it('returns error result if no lines and no shipping', async () => {
  1024. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1025. id: orderId,
  1026. });
  1027. const { settlePayment } = await adminClient.query<
  1028. SettlePayment.Mutation,
  1029. SettlePayment.Variables
  1030. >(SETTLE_PAYMENT, {
  1031. id: order!.payments![0].id,
  1032. });
  1033. paymentGuard.assertSuccess(settlePayment);
  1034. expect(settlePayment!.state).toBe('Settled');
  1035. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1036. REFUND_ORDER,
  1037. {
  1038. input: {
  1039. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
  1040. shipping: 0,
  1041. adjustment: 0,
  1042. paymentId,
  1043. },
  1044. },
  1045. );
  1046. refundGuard.assertErrorResult(refundOrder);
  1047. expect(refundOrder.message).toBe('Nothing to refund');
  1048. expect(refundOrder.errorCode).toBe(ErrorCode.NOTHING_TO_REFUND_ERROR);
  1049. });
  1050. it(
  1051. 'throws if paymentId not valid',
  1052. assertThrowsWithMessage(async () => {
  1053. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1054. id: orderId,
  1055. });
  1056. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1057. REFUND_ORDER,
  1058. {
  1059. input: {
  1060. lines: [],
  1061. shipping: 100,
  1062. adjustment: 0,
  1063. paymentId: 'T_999',
  1064. },
  1065. },
  1066. );
  1067. }, `No Payment with the id '999' could be found`),
  1068. );
  1069. it('returns error result if payment and order lines do not belong to the same Order', async () => {
  1070. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1071. id: orderId,
  1072. });
  1073. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1074. REFUND_ORDER,
  1075. {
  1076. input: {
  1077. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  1078. shipping: 100,
  1079. adjustment: 0,
  1080. paymentId: 'T_1',
  1081. },
  1082. },
  1083. );
  1084. refundGuard.assertErrorResult(refundOrder);
  1085. expect(refundOrder.message).toBe('The Payment and OrderLines do not belong to the same Order');
  1086. expect(refundOrder.errorCode).toBe(ErrorCode.PAYMENT_ORDER_MISMATCH_ERROR);
  1087. });
  1088. it('creates a Refund to be manually settled', async () => {
  1089. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1090. id: orderId,
  1091. });
  1092. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1093. REFUND_ORDER,
  1094. {
  1095. input: {
  1096. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  1097. shipping: order!.shipping,
  1098. adjustment: 0,
  1099. reason: 'foo',
  1100. paymentId,
  1101. },
  1102. },
  1103. );
  1104. refundGuard.assertSuccess(refundOrder);
  1105. expect(refundOrder.shipping).toBe(order!.shipping);
  1106. expect(refundOrder.items).toBe(order!.subTotal);
  1107. expect(refundOrder.total).toBe(order!.total);
  1108. expect(refundOrder.transactionId).toBe(null);
  1109. expect(refundOrder.state).toBe('Pending');
  1110. refundId = refundOrder.id;
  1111. });
  1112. it('returns error result if attempting to refund the same item more than once', async () => {
  1113. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1114. id: orderId,
  1115. });
  1116. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1117. REFUND_ORDER,
  1118. {
  1119. input: {
  1120. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  1121. shipping: order!.shipping,
  1122. adjustment: 0,
  1123. paymentId,
  1124. },
  1125. },
  1126. );
  1127. refundGuard.assertErrorResult(refundOrder);
  1128. expect(refundOrder.message).toBe('Cannot refund an OrderItem which has already been refunded');
  1129. expect(refundOrder.errorCode).toBe(ErrorCode.ALREADY_REFUNDED_ERROR);
  1130. });
  1131. it('manually settle a Refund', async () => {
  1132. const { settleRefund } = await adminClient.query<SettleRefund.Mutation, SettleRefund.Variables>(
  1133. SETTLE_REFUND,
  1134. {
  1135. input: {
  1136. id: refundId,
  1137. transactionId: 'aaabbb',
  1138. },
  1139. },
  1140. );
  1141. refundGuard.assertSuccess(settleRefund);
  1142. expect(settleRefund.state).toBe('Settled');
  1143. expect(settleRefund.transactionId).toBe('aaabbb');
  1144. });
  1145. it('order history contains expected entries', async () => {
  1146. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  1147. GET_ORDER_HISTORY,
  1148. {
  1149. id: orderId,
  1150. options: {
  1151. skip: 0,
  1152. },
  1153. },
  1154. );
  1155. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  1156. {
  1157. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1158. data: {
  1159. from: 'AddingItems',
  1160. to: 'ArrangingPayment',
  1161. },
  1162. },
  1163. {
  1164. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  1165. data: {
  1166. paymentId: 'T_5',
  1167. from: 'Created',
  1168. to: 'Authorized',
  1169. },
  1170. },
  1171. {
  1172. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1173. data: {
  1174. from: 'ArrangingPayment',
  1175. to: 'PaymentAuthorized',
  1176. },
  1177. },
  1178. {
  1179. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  1180. data: {
  1181. paymentId: 'T_5',
  1182. from: 'Authorized',
  1183. to: 'Settled',
  1184. },
  1185. },
  1186. {
  1187. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1188. data: {
  1189. from: 'PaymentAuthorized',
  1190. to: 'PaymentSettled',
  1191. },
  1192. },
  1193. {
  1194. type: HistoryEntryType.ORDER_REFUND_TRANSITION,
  1195. data: {
  1196. refundId: 'T_1',
  1197. reason: 'foo',
  1198. from: 'Pending',
  1199. to: 'Settled',
  1200. },
  1201. },
  1202. ]);
  1203. });
  1204. });
  1205. describe('order notes', () => {
  1206. let orderId: string;
  1207. let firstNoteId: string;
  1208. beforeAll(async () => {
  1209. const result = await createTestOrder(
  1210. adminClient,
  1211. shopClient,
  1212. customers[2].emailAddress,
  1213. password,
  1214. );
  1215. orderId = result.orderId;
  1216. });
  1217. it('private note', async () => {
  1218. const { addNoteToOrder } = await adminClient.query<
  1219. AddNoteToOrder.Mutation,
  1220. AddNoteToOrder.Variables
  1221. >(ADD_NOTE_TO_ORDER, {
  1222. input: {
  1223. id: orderId,
  1224. note: 'A private note',
  1225. isPublic: false,
  1226. },
  1227. });
  1228. expect(addNoteToOrder.id).toBe(orderId);
  1229. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  1230. GET_ORDER_HISTORY,
  1231. {
  1232. id: orderId,
  1233. options: {
  1234. skip: 0,
  1235. },
  1236. },
  1237. );
  1238. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  1239. {
  1240. type: HistoryEntryType.ORDER_NOTE,
  1241. data: {
  1242. note: 'A private note',
  1243. },
  1244. },
  1245. ]);
  1246. firstNoteId = order!.history.items[0].id;
  1247. const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
  1248. expect(activeOrder!.history.items.map(pick(['type', 'data']))).toEqual([]);
  1249. });
  1250. it('public note', async () => {
  1251. const { addNoteToOrder } = await adminClient.query<
  1252. AddNoteToOrder.Mutation,
  1253. AddNoteToOrder.Variables
  1254. >(ADD_NOTE_TO_ORDER, {
  1255. input: {
  1256. id: orderId,
  1257. note: 'A public note',
  1258. isPublic: true,
  1259. },
  1260. });
  1261. expect(addNoteToOrder.id).toBe(orderId);
  1262. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  1263. GET_ORDER_HISTORY,
  1264. {
  1265. id: orderId,
  1266. options: {
  1267. skip: 1,
  1268. },
  1269. },
  1270. );
  1271. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  1272. {
  1273. type: HistoryEntryType.ORDER_NOTE,
  1274. data: {
  1275. note: 'A public note',
  1276. },
  1277. },
  1278. ]);
  1279. const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
  1280. expect(activeOrder!.history.items.map(pick(['type', 'data']))).toEqual([
  1281. {
  1282. type: HistoryEntryType.ORDER_NOTE,
  1283. data: {
  1284. note: 'A public note',
  1285. },
  1286. },
  1287. ]);
  1288. });
  1289. it('update note', async () => {
  1290. const { updateOrderNote } = await adminClient.query<
  1291. UpdateOrderNote.Mutation,
  1292. UpdateOrderNote.Variables
  1293. >(UPDATE_ORDER_NOTE, {
  1294. input: {
  1295. noteId: firstNoteId,
  1296. note: 'An updated note',
  1297. },
  1298. });
  1299. expect(updateOrderNote.data).toEqual({
  1300. note: 'An updated note',
  1301. });
  1302. });
  1303. it('delete note', async () => {
  1304. const { order: before } = await adminClient.query<
  1305. GetOrderHistory.Query,
  1306. GetOrderHistory.Variables
  1307. >(GET_ORDER_HISTORY, { id: orderId });
  1308. expect(before?.history.totalItems).toBe(2);
  1309. const { deleteOrderNote } = await adminClient.query<
  1310. DeleteOrderNote.Mutation,
  1311. DeleteOrderNote.Variables
  1312. >(DELETE_ORDER_NOTE, {
  1313. id: firstNoteId,
  1314. });
  1315. expect(deleteOrderNote.result).toBe(DeletionResult.DELETED);
  1316. const { order: after } = await adminClient.query<
  1317. GetOrderHistory.Query,
  1318. GetOrderHistory.Variables
  1319. >(GET_ORDER_HISTORY, { id: orderId });
  1320. expect(after?.history.totalItems).toBe(1);
  1321. });
  1322. });
  1323. });
  1324. async function createTestOrder(
  1325. adminClient: SimpleGraphQLClient,
  1326. shopClient: SimpleGraphQLClient,
  1327. emailAddress: string,
  1328. password: string,
  1329. ): Promise<{
  1330. orderId: string;
  1331. product: GetProductWithVariants.Product;
  1332. productVariantId: string;
  1333. }> {
  1334. const result = await adminClient.query<GetProductWithVariants.Query, GetProductWithVariants.Variables>(
  1335. GET_PRODUCT_WITH_VARIANTS,
  1336. {
  1337. id: 'T_3',
  1338. },
  1339. );
  1340. const product = result.product!;
  1341. const productVariantId = product.variants[0].id;
  1342. // Set the ProductVariant to trackInventory
  1343. const { updateProductVariants } = await adminClient.query<
  1344. UpdateProductVariants.Mutation,
  1345. UpdateProductVariants.Variables
  1346. >(UPDATE_PRODUCT_VARIANTS, {
  1347. input: [
  1348. {
  1349. id: productVariantId,
  1350. trackInventory: GlobalFlag.TRUE,
  1351. },
  1352. ],
  1353. });
  1354. // Add the ProductVariant to the Order
  1355. await shopClient.asUserWithCredentials(emailAddress, password);
  1356. const { addItemToOrder } = await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(
  1357. ADD_ITEM_TO_ORDER,
  1358. {
  1359. productVariantId,
  1360. quantity: 2,
  1361. },
  1362. );
  1363. const orderId = (addItemToOrder as UpdatedOrder.Fragment).id;
  1364. return { product, productVariantId, orderId };
  1365. }
  1366. export const SETTLE_PAYMENT = gql`
  1367. mutation SettlePayment($id: ID!) {
  1368. settlePayment(id: $id) {
  1369. ...Payment
  1370. ... on ErrorResult {
  1371. errorCode
  1372. message
  1373. }
  1374. ... on SettlePaymentError {
  1375. paymentErrorMessage
  1376. }
  1377. }
  1378. }
  1379. fragment Payment on Payment {
  1380. id
  1381. state
  1382. metadata
  1383. }
  1384. `;
  1385. export const GET_ORDER_LIST_FULFILLMENTS = gql`
  1386. query GetOrderListFulfillments {
  1387. orders {
  1388. items {
  1389. id
  1390. state
  1391. fulfillments {
  1392. id
  1393. state
  1394. nextStates
  1395. method
  1396. }
  1397. }
  1398. }
  1399. }
  1400. `;
  1401. export const GET_ORDER_FULFILLMENT_ITEMS = gql`
  1402. query GetOrderFulfillmentItems($id: ID!) {
  1403. order(id: $id) {
  1404. id
  1405. state
  1406. fulfillments {
  1407. ...Fulfillment
  1408. }
  1409. }
  1410. }
  1411. ${FULFILLMENT_FRAGMENT}
  1412. `;
  1413. const REFUND_FRAGMENT = gql`
  1414. fragment Refund on Refund {
  1415. id
  1416. state
  1417. items
  1418. transactionId
  1419. shipping
  1420. total
  1421. metadata
  1422. }
  1423. `;
  1424. export const REFUND_ORDER = gql`
  1425. mutation RefundOrder($input: RefundOrderInput!) {
  1426. refundOrder(input: $input) {
  1427. ...Refund
  1428. ... on ErrorResult {
  1429. errorCode
  1430. message
  1431. }
  1432. }
  1433. }
  1434. ${REFUND_FRAGMENT}
  1435. `;
  1436. export const SETTLE_REFUND = gql`
  1437. mutation SettleRefund($input: SettleRefundInput!) {
  1438. settleRefund(input: $input) {
  1439. ...Refund
  1440. ... on ErrorResult {
  1441. errorCode
  1442. message
  1443. }
  1444. }
  1445. }
  1446. ${REFUND_FRAGMENT}
  1447. `;
  1448. export const GET_ORDER_HISTORY = gql`
  1449. query GetOrderHistory($id: ID!, $options: HistoryEntryListOptions) {
  1450. order(id: $id) {
  1451. id
  1452. history(options: $options) {
  1453. totalItems
  1454. items {
  1455. id
  1456. type
  1457. administrator {
  1458. id
  1459. }
  1460. data
  1461. }
  1462. }
  1463. }
  1464. }
  1465. `;
  1466. export const ADD_NOTE_TO_ORDER = gql`
  1467. mutation AddNoteToOrder($input: AddNoteToOrderInput!) {
  1468. addNoteToOrder(input: $input) {
  1469. id
  1470. }
  1471. }
  1472. `;
  1473. export const UPDATE_ORDER_NOTE = gql`
  1474. mutation UpdateOrderNote($input: UpdateOrderNoteInput!) {
  1475. updateOrderNote(input: $input) {
  1476. id
  1477. data
  1478. isPublic
  1479. }
  1480. }
  1481. `;
  1482. export const DELETE_ORDER_NOTE = gql`
  1483. mutation DeleteOrderNote($id: ID!) {
  1484. deleteOrderNote(id: $id) {
  1485. result
  1486. message
  1487. }
  1488. }
  1489. `;
  1490. const GET_ORDER_WITH_PAYMENTS = gql`
  1491. query GetOrderWithPayments($id: ID!) {
  1492. order(id: $id) {
  1493. id
  1494. payments {
  1495. id
  1496. errorMessage
  1497. metadata
  1498. }
  1499. }
  1500. }
  1501. `;