order.e2e-spec.ts 60 KB

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