order.e2e-spec.ts 62 KB

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