order.e2e-spec.ts 59 KB

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