order.e2e-spec.ts 91 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449
  1. /* tslint:disable:no-non-null-assertion */
  2. import { omit } from '@vendure/common/lib/omit';
  3. import { pick } from '@vendure/common/lib/pick';
  4. import {
  5. defaultShippingCalculator,
  6. defaultShippingEligibilityChecker,
  7. manualFulfillmentHandler,
  8. mergeConfig,
  9. } from '@vendure/core';
  10. import {
  11. createErrorResultGuard,
  12. createTestEnvironment,
  13. ErrorResultGuard,
  14. SimpleGraphQLClient,
  15. } from '@vendure/testing';
  16. import gql from 'graphql-tag';
  17. import path from 'path';
  18. import { initialData } from '../../../e2e-common/e2e-initial-data';
  19. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  20. import {
  21. failsToSettlePaymentMethod,
  22. onTransitionSpy,
  23. partialPaymentMethod,
  24. singleStageRefundablePaymentMethod,
  25. singleStageRefundFailingPaymentMethod,
  26. twoStagePaymentMethod,
  27. } from './fixtures/test-payment-methods';
  28. import { FULFILLMENT_FRAGMENT } from './graphql/fragments';
  29. import {
  30. AddNoteToOrder,
  31. CanceledOrderFragment,
  32. CancelOrder,
  33. CreateFulfillment,
  34. CreateShippingMethod,
  35. DeleteOrderNote,
  36. DeleteProduct,
  37. DeleteShippingMethod,
  38. ErrorCode,
  39. FulfillmentFragment,
  40. GetCustomerList,
  41. GetOrder,
  42. GetOrderFulfillmentItems,
  43. GetOrderFulfillments,
  44. GetOrderHistory,
  45. GetOrderList,
  46. GetOrderListFulfillments,
  47. GetOrderListWithQty,
  48. GetOrderWithPayments,
  49. GetProductWithVariants,
  50. GetStockMovement,
  51. GlobalFlag,
  52. HistoryEntryType,
  53. LanguageCode,
  54. OrderLineInput,
  55. PaymentFragment,
  56. RefundFragment,
  57. RefundOrder,
  58. SettlePayment,
  59. SettleRefund,
  60. SortOrder,
  61. StockMovementType,
  62. TransitFulfillment,
  63. UpdateOrderNote,
  64. UpdateProductVariants,
  65. } from './graphql/generated-e2e-admin-types';
  66. import {
  67. AddItemToOrder,
  68. AddPaymentToOrder,
  69. ApplyCouponCode,
  70. DeletionResult,
  71. GetActiveCustomerWithOrdersProductSlug,
  72. GetActiveOrder,
  73. GetOrderByCodeWithPayments,
  74. SetShippingAddress,
  75. SetShippingMethod,
  76. TestOrderFragmentFragment,
  77. UpdatedOrder,
  78. UpdatedOrderFragment,
  79. } from './graphql/generated-e2e-shop-types';
  80. import {
  81. CANCEL_ORDER,
  82. CREATE_FULFILLMENT,
  83. CREATE_SHIPPING_METHOD,
  84. DELETE_PRODUCT,
  85. DELETE_SHIPPING_METHOD,
  86. GET_CUSTOMER_LIST,
  87. GET_ORDER,
  88. GET_ORDERS_LIST,
  89. GET_ORDER_FULFILLMENTS,
  90. GET_ORDER_HISTORY,
  91. GET_PRODUCT_WITH_VARIANTS,
  92. GET_STOCK_MOVEMENT,
  93. SETTLE_PAYMENT,
  94. TRANSIT_FULFILLMENT,
  95. UPDATE_PRODUCT_VARIANTS,
  96. } from './graphql/shared-definitions';
  97. import {
  98. ADD_ITEM_TO_ORDER,
  99. ADD_PAYMENT,
  100. APPLY_COUPON_CODE,
  101. GET_ACTIVE_CUSTOMER_WITH_ORDERS_PRODUCT_SLUG,
  102. GET_ACTIVE_ORDER,
  103. GET_ORDER_BY_CODE_WITH_PAYMENTS,
  104. SET_SHIPPING_ADDRESS,
  105. SET_SHIPPING_METHOD,
  106. } from './graphql/shop-definitions';
  107. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  108. import { addPaymentToOrder, proceedToArrangingPayment, sortById } from './utils/test-order-utils';
  109. describe('Orders resolver', () => {
  110. const { server, adminClient, shopClient } = createTestEnvironment(
  111. mergeConfig(testConfig(), {
  112. paymentOptions: {
  113. paymentMethodHandlers: [
  114. twoStagePaymentMethod,
  115. failsToSettlePaymentMethod,
  116. singleStageRefundablePaymentMethod,
  117. partialPaymentMethod,
  118. singleStageRefundFailingPaymentMethod,
  119. ],
  120. },
  121. }),
  122. );
  123. let customers: GetCustomerList.Items[];
  124. const password = 'test';
  125. const orderGuard: ErrorResultGuard<
  126. TestOrderFragmentFragment | CanceledOrderFragment | UpdatedOrderFragment
  127. > = createErrorResultGuard(input => !!input.lines);
  128. const paymentGuard: ErrorResultGuard<PaymentFragment> = createErrorResultGuard(input => !!input.state);
  129. const fulfillmentGuard: ErrorResultGuard<FulfillmentFragment> = createErrorResultGuard(
  130. input => !!input.method,
  131. );
  132. const refundGuard: ErrorResultGuard<RefundFragment> = createErrorResultGuard(input => !!input.items);
  133. beforeAll(async () => {
  134. await server.init({
  135. initialData: {
  136. ...initialData,
  137. paymentMethods: [
  138. {
  139. name: twoStagePaymentMethod.code,
  140. handler: { code: twoStagePaymentMethod.code, arguments: [] },
  141. },
  142. {
  143. name: failsToSettlePaymentMethod.code,
  144. handler: { code: failsToSettlePaymentMethod.code, arguments: [] },
  145. },
  146. {
  147. name: singleStageRefundablePaymentMethod.code,
  148. handler: { code: singleStageRefundablePaymentMethod.code, arguments: [] },
  149. },
  150. {
  151. name: singleStageRefundFailingPaymentMethod.code,
  152. handler: { code: singleStageRefundFailingPaymentMethod.code, arguments: [] },
  153. },
  154. {
  155. name: partialPaymentMethod.code,
  156. handler: { code: partialPaymentMethod.code, arguments: [] },
  157. },
  158. ],
  159. },
  160. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  161. customerCount: 3,
  162. });
  163. await adminClient.asSuperAdmin();
  164. // Create a couple of orders to be queried
  165. const result = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
  166. GET_CUSTOMER_LIST,
  167. {
  168. options: {
  169. take: 3,
  170. },
  171. },
  172. );
  173. customers = result.customers.items;
  174. await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
  175. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  176. productVariantId: 'T_1',
  177. quantity: 1,
  178. });
  179. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  180. productVariantId: 'T_2',
  181. quantity: 1,
  182. });
  183. await shopClient.asUserWithCredentials(customers[1].emailAddress, password);
  184. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  185. productVariantId: 'T_2',
  186. quantity: 1,
  187. });
  188. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  189. productVariantId: 'T_3',
  190. quantity: 3,
  191. });
  192. }, TEST_SETUP_TIMEOUT_MS);
  193. afterAll(async () => {
  194. await server.destroy();
  195. });
  196. it('order history initially contains Created -> AddingItems transition', async () => {
  197. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  198. GET_ORDER_HISTORY,
  199. { id: 'T_1' },
  200. );
  201. expect(order!.history.totalItems).toBe(1);
  202. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  203. {
  204. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  205. data: {
  206. from: 'Created',
  207. to: 'AddingItems',
  208. },
  209. },
  210. ]);
  211. });
  212. describe('querying', () => {
  213. it('orders', async () => {
  214. const result = await adminClient.query<GetOrderList.Query>(GET_ORDERS_LIST);
  215. expect(result.orders.items.map(o => o.id).sort()).toEqual(['T_1', 'T_2']);
  216. });
  217. it('order', async () => {
  218. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  219. id: 'T_2',
  220. });
  221. expect(result.order!.id).toBe('T_2');
  222. });
  223. it('sort by total', async () => {
  224. const result = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  225. GET_ORDERS_LIST,
  226. {
  227. options: {
  228. sort: {
  229. total: SortOrder.DESC,
  230. },
  231. take: 10,
  232. },
  233. },
  234. );
  235. expect(result.orders.items.map(o => pick(o, ['id', 'total']))).toEqual([
  236. { id: 'T_2', total: 799600 },
  237. { id: 'T_1', total: 269800 },
  238. ]);
  239. });
  240. it('sort by totalWithTax', async () => {
  241. const result = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  242. GET_ORDERS_LIST,
  243. {
  244. options: {
  245. sort: {
  246. totalWithTax: SortOrder.DESC,
  247. },
  248. take: 10,
  249. },
  250. },
  251. );
  252. expect(result.orders.items.map(o => pick(o, ['id', 'totalWithTax']))).toEqual([
  253. { id: 'T_2', totalWithTax: 959520 },
  254. { id: 'T_1', totalWithTax: 323760 },
  255. ]);
  256. });
  257. it('sort by totalQuantity', async () => {
  258. const result = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  259. GET_ORDERS_LIST,
  260. {
  261. options: {
  262. sort: {
  263. totalQuantity: SortOrder.DESC,
  264. },
  265. take: 10,
  266. },
  267. },
  268. );
  269. expect(result.orders.items.map(o => pick(o, ['id', 'totalQuantity']))).toEqual([
  270. { id: 'T_2', totalQuantity: 4 },
  271. { id: 'T_1', totalQuantity: 2 },
  272. ]);
  273. });
  274. it('sort by customerLastName', async () => {
  275. async function sortOrdersByLastName(sortOrder: SortOrder) {
  276. const { orders } = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  277. GET_ORDERS_LIST,
  278. {
  279. options: {
  280. sort: {
  281. customerLastName: sortOrder,
  282. },
  283. },
  284. },
  285. );
  286. return orders;
  287. }
  288. const result1 = await sortOrdersByLastName(SortOrder.ASC);
  289. expect(result1.totalItems).toEqual(2);
  290. expect(result1.items.map(order => order.customer?.lastName)).toEqual(['Donnelly', 'Zieme']);
  291. const result2 = await sortOrdersByLastName(SortOrder.DESC);
  292. expect(result2.totalItems).toEqual(2);
  293. expect(result2.items.map(order => order.customer?.lastName)).toEqual(['Zieme', 'Donnelly']);
  294. });
  295. it('filter by total', async () => {
  296. const result = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  297. GET_ORDERS_LIST,
  298. {
  299. options: {
  300. filter: {
  301. total: { gt: 323760 },
  302. },
  303. take: 10,
  304. },
  305. },
  306. );
  307. expect(result.orders.items.map(o => pick(o, ['id', 'total']))).toEqual([
  308. { id: 'T_2', total: 799600 },
  309. ]);
  310. });
  311. it('filter by totalWithTax', async () => {
  312. const result = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  313. GET_ORDERS_LIST,
  314. {
  315. options: {
  316. filter: {
  317. totalWithTax: { gt: 323760 },
  318. },
  319. take: 10,
  320. },
  321. },
  322. );
  323. expect(result.orders.items.map(o => pick(o, ['id', 'totalWithTax']))).toEqual([
  324. { id: 'T_2', totalWithTax: 959520 },
  325. ]);
  326. });
  327. it('filter by totalQuantity', async () => {
  328. const result = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  329. GET_ORDERS_LIST,
  330. {
  331. options: {
  332. filter: {
  333. totalQuantity: { eq: 4 },
  334. },
  335. },
  336. },
  337. );
  338. expect(result.orders.items.map(o => pick(o, ['id', 'totalQuantity']))).toEqual([
  339. { id: 'T_2', totalQuantity: 4 },
  340. ]);
  341. });
  342. it('filter by customerLastName', async () => {
  343. const result = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  344. GET_ORDERS_LIST,
  345. {
  346. options: {
  347. filter: {
  348. customerLastName: {
  349. eq: customers[1].lastName,
  350. },
  351. },
  352. },
  353. },
  354. );
  355. expect(result.orders.totalItems).toEqual(1);
  356. expect(result.orders.items[0].customer?.lastName).toEqual(customers[1].lastName);
  357. });
  358. });
  359. describe('payments', () => {
  360. let firstOrderCode: string;
  361. let firstOrderId: string;
  362. it('settlePayment fails', async () => {
  363. await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
  364. await proceedToArrangingPayment(shopClient);
  365. const order = await addPaymentToOrder(shopClient, failsToSettlePaymentMethod);
  366. orderGuard.assertSuccess(order);
  367. expect(order.state).toBe('PaymentAuthorized');
  368. const payment = order.payments![0];
  369. const { settlePayment } = await adminClient.query<
  370. SettlePayment.Mutation,
  371. SettlePayment.Variables
  372. >(SETTLE_PAYMENT, {
  373. id: payment.id,
  374. });
  375. paymentGuard.assertErrorResult(settlePayment);
  376. expect(settlePayment.message).toBe('Settling the payment failed');
  377. expect(settlePayment.errorCode).toBe(ErrorCode.SETTLE_PAYMENT_ERROR);
  378. expect((settlePayment as any).paymentErrorMessage).toBe('Something went horribly wrong');
  379. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  380. id: order.id,
  381. });
  382. expect(result.order!.state).toBe('PaymentAuthorized');
  383. expect(result.order!.payments![0].state).toBe('Cancelled');
  384. firstOrderCode = order.code;
  385. firstOrderId = order.id;
  386. });
  387. it('public payment metadata available in Shop API', async () => {
  388. const { orderByCode } = await shopClient.query<
  389. GetOrderByCodeWithPayments.Query,
  390. GetOrderByCodeWithPayments.Variables
  391. >(GET_ORDER_BY_CODE_WITH_PAYMENTS, { code: firstOrderCode });
  392. expect(orderByCode?.payments?.[0].metadata).toEqual({
  393. public: {
  394. publicCreatePaymentData: 'public',
  395. publicSettlePaymentData: 'public',
  396. },
  397. });
  398. });
  399. it('public and private payment metadata available in Admin API', async () => {
  400. const { order } = await adminClient.query<
  401. GetOrderWithPayments.Query,
  402. GetOrderWithPayments.Variables
  403. >(GET_ORDER_WITH_PAYMENTS, { id: firstOrderId });
  404. expect(order?.payments?.[0].metadata).toEqual({
  405. privateCreatePaymentData: 'secret',
  406. privateSettlePaymentData: 'secret',
  407. public: {
  408. publicCreatePaymentData: 'public',
  409. publicSettlePaymentData: 'public',
  410. },
  411. });
  412. });
  413. it('settlePayment succeeds, onStateTransitionStart called', async () => {
  414. onTransitionSpy.mockClear();
  415. await shopClient.asUserWithCredentials(customers[1].emailAddress, password);
  416. await proceedToArrangingPayment(shopClient);
  417. const order = await addPaymentToOrder(shopClient, twoStagePaymentMethod);
  418. orderGuard.assertSuccess(order);
  419. expect(order.state).toBe('PaymentAuthorized');
  420. expect(onTransitionSpy).toHaveBeenCalledTimes(1);
  421. expect(onTransitionSpy.mock.calls[0][0]).toBe('Created');
  422. expect(onTransitionSpy.mock.calls[0][1]).toBe('Authorized');
  423. const payment = order.payments![0];
  424. const { settlePayment } = await adminClient.query<
  425. SettlePayment.Mutation,
  426. SettlePayment.Variables
  427. >(SETTLE_PAYMENT, {
  428. id: payment.id,
  429. });
  430. paymentGuard.assertSuccess(settlePayment);
  431. expect(settlePayment!.id).toBe(payment.id);
  432. expect(settlePayment!.state).toBe('Settled');
  433. // further metadata is combined into existing object
  434. expect(settlePayment!.metadata).toEqual({
  435. moreData: 42,
  436. public: {
  437. baz: 'quux',
  438. },
  439. });
  440. expect(onTransitionSpy).toHaveBeenCalledTimes(2);
  441. expect(onTransitionSpy.mock.calls[1][0]).toBe('Authorized');
  442. expect(onTransitionSpy.mock.calls[1][1]).toBe('Settled');
  443. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  444. id: order.id,
  445. });
  446. expect(result.order!.state).toBe('PaymentSettled');
  447. expect(result.order!.payments![0].state).toBe('Settled');
  448. });
  449. it('order history contains expected entries', async () => {
  450. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  451. GET_ORDER_HISTORY,
  452. { id: 'T_2', options: { sort: { id: SortOrder.ASC } } },
  453. );
  454. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  455. {
  456. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  457. data: {
  458. from: 'Created',
  459. to: 'AddingItems',
  460. },
  461. },
  462. {
  463. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  464. data: {
  465. from: 'AddingItems',
  466. to: 'ArrangingPayment',
  467. },
  468. },
  469. {
  470. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  471. data: {
  472. paymentId: 'T_2',
  473. from: 'Created',
  474. to: 'Authorized',
  475. },
  476. },
  477. {
  478. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  479. data: {
  480. from: 'ArrangingPayment',
  481. to: 'PaymentAuthorized',
  482. },
  483. },
  484. {
  485. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  486. data: {
  487. paymentId: 'T_2',
  488. from: 'Authorized',
  489. to: 'Settled',
  490. },
  491. },
  492. {
  493. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  494. data: {
  495. from: 'PaymentAuthorized',
  496. to: 'PaymentSettled',
  497. },
  498. },
  499. ]);
  500. });
  501. });
  502. describe('fulfillment', () => {
  503. const orderId = 'T_2';
  504. let f1Id: string;
  505. let f2Id: string;
  506. let f3Id: string;
  507. it('return error result if lines is empty', async () => {
  508. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  509. id: orderId,
  510. });
  511. expect(order!.state).toBe('PaymentSettled');
  512. const { addFulfillmentToOrder } = await adminClient.query<
  513. CreateFulfillment.Mutation,
  514. CreateFulfillment.Variables
  515. >(CREATE_FULFILLMENT, {
  516. input: {
  517. lines: [],
  518. handler: {
  519. code: manualFulfillmentHandler.code,
  520. arguments: [{ name: 'method', value: 'Test' }],
  521. },
  522. },
  523. });
  524. fulfillmentGuard.assertErrorResult(addFulfillmentToOrder);
  525. expect(addFulfillmentToOrder.message).toBe('At least one OrderLine must be specified');
  526. expect(addFulfillmentToOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  527. });
  528. it('returns error result if all quantities are zero', async () => {
  529. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  530. id: orderId,
  531. });
  532. expect(order!.state).toBe('PaymentSettled');
  533. const { addFulfillmentToOrder } = await adminClient.query<
  534. CreateFulfillment.Mutation,
  535. CreateFulfillment.Variables
  536. >(CREATE_FULFILLMENT, {
  537. input: {
  538. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
  539. handler: {
  540. code: manualFulfillmentHandler.code,
  541. arguments: [{ name: 'method', value: 'Test' }],
  542. },
  543. },
  544. });
  545. fulfillmentGuard.assertErrorResult(addFulfillmentToOrder);
  546. expect(addFulfillmentToOrder.message).toBe('At least one OrderLine must be specified');
  547. expect(addFulfillmentToOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  548. });
  549. it('creates the first fulfillment', async () => {
  550. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  551. id: orderId,
  552. });
  553. expect(order!.state).toBe('PaymentSettled');
  554. const lines = order!.lines;
  555. const { addFulfillmentToOrder } = await adminClient.query<
  556. CreateFulfillment.Mutation,
  557. CreateFulfillment.Variables
  558. >(CREATE_FULFILLMENT, {
  559. input: {
  560. lines: [{ orderLineId: lines[0].id, quantity: lines[0].quantity }],
  561. handler: {
  562. code: manualFulfillmentHandler.code,
  563. arguments: [
  564. { name: 'method', value: 'Test1' },
  565. { name: 'trackingCode', value: '111' },
  566. ],
  567. },
  568. },
  569. });
  570. fulfillmentGuard.assertSuccess(addFulfillmentToOrder);
  571. expect(addFulfillmentToOrder.id).toBe('T_1');
  572. expect(addFulfillmentToOrder.method).toBe('Test1');
  573. expect(addFulfillmentToOrder.trackingCode).toBe('111');
  574. expect(addFulfillmentToOrder.state).toBe('Pending');
  575. expect(addFulfillmentToOrder.orderItems).toEqual([{ id: lines[0].items[0].id }]);
  576. f1Id = addFulfillmentToOrder.id;
  577. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  578. id: orderId,
  579. });
  580. expect(result.order!.lines[0].items[0].fulfillment!.id).toBe(addFulfillmentToOrder!.id);
  581. expect(
  582. result.order!.lines[1].items.filter(
  583. i => i.fulfillment && i.fulfillment.id === addFulfillmentToOrder.id,
  584. ).length,
  585. ).toBe(0);
  586. expect(result.order!.lines[1].items.filter(i => i.fulfillment == null).length).toBe(3);
  587. });
  588. it('creates the second fulfillment', async () => {
  589. const lines = await getUnfulfilledOrderLineInput(adminClient, orderId);
  590. const { addFulfillmentToOrder } = await adminClient.query<
  591. CreateFulfillment.Mutation,
  592. CreateFulfillment.Variables
  593. >(CREATE_FULFILLMENT, {
  594. input: {
  595. lines,
  596. handler: {
  597. code: manualFulfillmentHandler.code,
  598. arguments: [
  599. { name: 'method', value: 'Test2' },
  600. { name: 'trackingCode', value: '222' },
  601. ],
  602. },
  603. },
  604. });
  605. fulfillmentGuard.assertSuccess(addFulfillmentToOrder);
  606. expect(addFulfillmentToOrder.id).toBe('T_2');
  607. expect(addFulfillmentToOrder.method).toBe('Test2');
  608. expect(addFulfillmentToOrder.trackingCode).toBe('222');
  609. expect(addFulfillmentToOrder.state).toBe('Pending');
  610. f2Id = addFulfillmentToOrder.id;
  611. });
  612. it('cancels second fulfillment', async () => {
  613. const { transitionFulfillmentToState } = await adminClient.query<
  614. TransitFulfillment.Mutation,
  615. TransitFulfillment.Variables
  616. >(TRANSIT_FULFILLMENT, {
  617. id: f2Id,
  618. state: 'Cancelled',
  619. });
  620. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  621. expect(transitionFulfillmentToState.id).toBe('T_2');
  622. expect(transitionFulfillmentToState.state).toBe('Cancelled');
  623. });
  624. it('order.fulfillments still lists second (cancelled) fulfillment', async () => {
  625. const { order } = await adminClient.query<
  626. GetOrderFulfillments.Query,
  627. GetOrderFulfillments.Variables
  628. >(GET_ORDER_FULFILLMENTS, {
  629. id: orderId,
  630. });
  631. expect(order?.fulfillments?.map(pick(['id', 'state']))).toEqual([
  632. { id: f1Id, state: 'Pending' },
  633. { id: f2Id, state: 'Cancelled' },
  634. ]);
  635. });
  636. it('creates third fulfillment with same items from second fulfillment', async () => {
  637. const lines = await getUnfulfilledOrderLineInput(adminClient, orderId);
  638. const { addFulfillmentToOrder } = await adminClient.query<
  639. CreateFulfillment.Mutation,
  640. CreateFulfillment.Variables
  641. >(CREATE_FULFILLMENT, {
  642. input: {
  643. lines,
  644. handler: {
  645. code: manualFulfillmentHandler.code,
  646. arguments: [
  647. { name: 'method', value: 'Test3' },
  648. { name: 'trackingCode', value: '333' },
  649. ],
  650. },
  651. },
  652. });
  653. fulfillmentGuard.assertSuccess(addFulfillmentToOrder);
  654. expect(addFulfillmentToOrder.id).toBe('T_3');
  655. expect(addFulfillmentToOrder.method).toBe('Test3');
  656. expect(addFulfillmentToOrder.trackingCode).toBe('333');
  657. expect(addFulfillmentToOrder.state).toBe('Pending');
  658. f3Id = addFulfillmentToOrder.id;
  659. });
  660. it('returns error result if an OrderItem already part of a Fulfillment', async () => {
  661. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  662. id: orderId,
  663. });
  664. const { addFulfillmentToOrder } = await adminClient.query<
  665. CreateFulfillment.Mutation,
  666. CreateFulfillment.Variables
  667. >(CREATE_FULFILLMENT, {
  668. input: {
  669. lines: [
  670. {
  671. orderLineId: order!.lines[0].id,
  672. quantity: 1,
  673. },
  674. ],
  675. handler: {
  676. code: manualFulfillmentHandler.code,
  677. arguments: [{ name: 'method', value: 'Test' }],
  678. },
  679. },
  680. });
  681. fulfillmentGuard.assertErrorResult(addFulfillmentToOrder);
  682. expect(addFulfillmentToOrder.message).toBe(
  683. 'One or more OrderItems are already part of a Fulfillment',
  684. );
  685. expect(addFulfillmentToOrder.errorCode).toBe(ErrorCode.ITEMS_ALREADY_FULFILLED_ERROR);
  686. });
  687. it('transitions the first fulfillment from created to Shipped and automatically change the order state to PartiallyShipped', async () => {
  688. const { transitionFulfillmentToState } = await adminClient.query<
  689. TransitFulfillment.Mutation,
  690. TransitFulfillment.Variables
  691. >(TRANSIT_FULFILLMENT, {
  692. id: f1Id,
  693. state: 'Shipped',
  694. });
  695. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  696. expect(transitionFulfillmentToState.id).toBe(f1Id);
  697. expect(transitionFulfillmentToState.state).toBe('Shipped');
  698. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  699. id: orderId,
  700. });
  701. expect(order?.state).toBe('PartiallyShipped');
  702. });
  703. it('transitions the third fulfillment from created to Shipped and automatically change the order state to Shipped', async () => {
  704. const { transitionFulfillmentToState } = await adminClient.query<
  705. TransitFulfillment.Mutation,
  706. TransitFulfillment.Variables
  707. >(TRANSIT_FULFILLMENT, {
  708. id: f3Id,
  709. state: 'Shipped',
  710. });
  711. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  712. expect(transitionFulfillmentToState.id).toBe(f3Id);
  713. expect(transitionFulfillmentToState.state).toBe('Shipped');
  714. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  715. id: orderId,
  716. });
  717. expect(order?.state).toBe('Shipped');
  718. });
  719. it('transitions the first fulfillment from Shipped to Delivered and change the order state to PartiallyDelivered', async () => {
  720. const { transitionFulfillmentToState } = await adminClient.query<
  721. TransitFulfillment.Mutation,
  722. TransitFulfillment.Variables
  723. >(TRANSIT_FULFILLMENT, {
  724. id: f1Id,
  725. state: 'Delivered',
  726. });
  727. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  728. expect(transitionFulfillmentToState.id).toBe(f1Id);
  729. expect(transitionFulfillmentToState.state).toBe('Delivered');
  730. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  731. id: orderId,
  732. });
  733. expect(order?.state).toBe('PartiallyDelivered');
  734. });
  735. it('transitions the third fulfillment from Shipped to Delivered and change the order state to Delivered', async () => {
  736. const { transitionFulfillmentToState } = await adminClient.query<
  737. TransitFulfillment.Mutation,
  738. TransitFulfillment.Variables
  739. >(TRANSIT_FULFILLMENT, {
  740. id: f3Id,
  741. state: 'Delivered',
  742. });
  743. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  744. expect(transitionFulfillmentToState.id).toBe(f3Id);
  745. expect(transitionFulfillmentToState.state).toBe('Delivered');
  746. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  747. id: orderId,
  748. });
  749. expect(order?.state).toBe('Delivered');
  750. });
  751. it('order history contains expected entries', async () => {
  752. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  753. GET_ORDER_HISTORY,
  754. {
  755. id: orderId,
  756. options: {
  757. skip: 6,
  758. },
  759. },
  760. );
  761. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  762. {
  763. data: {
  764. fulfillmentId: f1Id,
  765. },
  766. type: HistoryEntryType.ORDER_FULFILLMENT,
  767. },
  768. {
  769. data: {
  770. from: 'Created',
  771. fulfillmentId: f1Id,
  772. to: 'Pending',
  773. },
  774. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  775. },
  776. {
  777. data: {
  778. fulfillmentId: f2Id,
  779. },
  780. type: HistoryEntryType.ORDER_FULFILLMENT,
  781. },
  782. {
  783. data: {
  784. from: 'Created',
  785. fulfillmentId: f2Id,
  786. to: 'Pending',
  787. },
  788. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  789. },
  790. {
  791. data: {
  792. from: 'Pending',
  793. fulfillmentId: f2Id,
  794. to: 'Cancelled',
  795. },
  796. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  797. },
  798. {
  799. data: {
  800. fulfillmentId: f3Id,
  801. },
  802. type: HistoryEntryType.ORDER_FULFILLMENT,
  803. },
  804. {
  805. data: {
  806. from: 'Created',
  807. fulfillmentId: f3Id,
  808. to: 'Pending',
  809. },
  810. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  811. },
  812. {
  813. data: {
  814. from: 'Pending',
  815. fulfillmentId: f1Id,
  816. to: 'Shipped',
  817. },
  818. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  819. },
  820. {
  821. data: {
  822. from: 'PaymentSettled',
  823. to: 'PartiallyShipped',
  824. },
  825. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  826. },
  827. {
  828. data: {
  829. from: 'Pending',
  830. fulfillmentId: f3Id,
  831. to: 'Shipped',
  832. },
  833. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  834. },
  835. {
  836. data: {
  837. from: 'PartiallyShipped',
  838. to: 'Shipped',
  839. },
  840. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  841. },
  842. {
  843. data: {
  844. from: 'Shipped',
  845. fulfillmentId: f1Id,
  846. to: 'Delivered',
  847. },
  848. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  849. },
  850. {
  851. data: {
  852. from: 'Shipped',
  853. to: 'PartiallyDelivered',
  854. },
  855. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  856. },
  857. {
  858. data: {
  859. from: 'Shipped',
  860. fulfillmentId: f3Id,
  861. to: 'Delivered',
  862. },
  863. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  864. },
  865. {
  866. data: {
  867. from: 'PartiallyDelivered',
  868. to: 'Delivered',
  869. },
  870. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  871. },
  872. ]);
  873. });
  874. it('order.fulfillments resolver for single order', async () => {
  875. const { order } = await adminClient.query<
  876. GetOrderFulfillments.Query,
  877. GetOrderFulfillments.Variables
  878. >(GET_ORDER_FULFILLMENTS, {
  879. id: orderId,
  880. });
  881. expect(order!.fulfillments?.sort(sortById)).toEqual([
  882. { id: f1Id, method: 'Test1', state: 'Delivered', nextStates: ['Cancelled'] },
  883. { id: f2Id, method: 'Test2', state: 'Cancelled', nextStates: [] },
  884. { id: f3Id, method: 'Test3', state: 'Delivered', nextStates: ['Cancelled'] },
  885. ]);
  886. });
  887. it('order.fulfillments resolver for order list', async () => {
  888. const { orders } = await adminClient.query<GetOrderListFulfillments.Query>(
  889. GET_ORDER_LIST_FULFILLMENTS,
  890. );
  891. expect(orders.items[0].fulfillments).toEqual([]);
  892. expect(orders.items[1].fulfillments).toEqual([
  893. { id: f1Id, method: 'Test1', state: 'Delivered', nextStates: ['Cancelled'] },
  894. { id: f2Id, method: 'Test2', state: 'Cancelled', nextStates: [] },
  895. { id: f3Id, method: 'Test3', state: 'Delivered', nextStates: ['Cancelled'] },
  896. ]);
  897. });
  898. it('order.fulfillments.orderItems resolver', async () => {
  899. const { order } = await adminClient.query<
  900. GetOrderFulfillmentItems.Query,
  901. GetOrderFulfillmentItems.Variables
  902. >(GET_ORDER_FULFILLMENT_ITEMS, {
  903. id: orderId,
  904. });
  905. expect(order!.fulfillments![0].orderItems).toEqual([{ id: 'T_3' }]);
  906. expect(order!.fulfillments![1].orderItems).toEqual([{ id: 'T_4' }, { id: 'T_5' }, { id: 'T_6' }]);
  907. expect(order!.fulfillments![2].orderItems).toEqual([{ id: 'T_4' }, { id: 'T_5' }, { id: 'T_6' }]);
  908. });
  909. });
  910. describe('cancellation by orderId', () => {
  911. it('cancel from AddingItems state', async () => {
  912. const testOrder = await createTestOrder(
  913. adminClient,
  914. shopClient,
  915. customers[0].emailAddress,
  916. password,
  917. );
  918. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  919. id: testOrder.orderId,
  920. });
  921. expect(order!.state).toBe('AddingItems');
  922. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  923. CANCEL_ORDER,
  924. {
  925. input: {
  926. orderId: testOrder.orderId,
  927. },
  928. },
  929. );
  930. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  931. id: testOrder.orderId,
  932. });
  933. expect(order2!.state).toBe('Cancelled');
  934. expect(order2!.active).toBe(false);
  935. await assertNoStockMovementsCreated(testOrder.product.id);
  936. });
  937. it('cancel from ArrangingPayment state', async () => {
  938. const testOrder = await createTestOrder(
  939. adminClient,
  940. shopClient,
  941. customers[0].emailAddress,
  942. password,
  943. );
  944. await proceedToArrangingPayment(shopClient);
  945. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  946. id: testOrder.orderId,
  947. });
  948. expect(order!.state).toBe('ArrangingPayment');
  949. await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(CANCEL_ORDER, {
  950. input: {
  951. orderId: testOrder.orderId,
  952. },
  953. });
  954. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  955. id: testOrder.orderId,
  956. });
  957. expect(order2!.state).toBe('Cancelled');
  958. expect(order2!.active).toBe(false);
  959. await assertNoStockMovementsCreated(testOrder.product.id);
  960. });
  961. it('cancel from PaymentAuthorized state', async () => {
  962. const testOrder = await createTestOrder(
  963. adminClient,
  964. shopClient,
  965. customers[0].emailAddress,
  966. password,
  967. );
  968. await proceedToArrangingPayment(shopClient);
  969. const order = await addPaymentToOrder(shopClient, failsToSettlePaymentMethod);
  970. orderGuard.assertSuccess(order);
  971. expect(order.state).toBe('PaymentAuthorized');
  972. const result1 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  973. GET_STOCK_MOVEMENT,
  974. {
  975. id: 'T_3',
  976. },
  977. );
  978. let variant1 = result1.product!.variants[0];
  979. expect(variant1.stockOnHand).toBe(100);
  980. expect(variant1.stockAllocated).toBe(2);
  981. expect(variant1.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  982. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  983. { type: StockMovementType.ALLOCATION, quantity: 2 },
  984. ]);
  985. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  986. CANCEL_ORDER,
  987. {
  988. input: {
  989. orderId: testOrder.orderId,
  990. },
  991. },
  992. );
  993. orderGuard.assertSuccess(cancelOrder);
  994. expect(
  995. cancelOrder.lines.map(l =>
  996. l.items.map(pick(['id', 'cancelled'])).sort((a, b) => (a.id > b.id ? 1 : -1)),
  997. ),
  998. ).toEqual([
  999. [
  1000. { id: 'T_11', cancelled: true },
  1001. { id: 'T_12', cancelled: true },
  1002. ],
  1003. ]);
  1004. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1005. id: testOrder.orderId,
  1006. });
  1007. expect(order2!.active).toBe(false);
  1008. expect(order2!.state).toBe('Cancelled');
  1009. const result2 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  1010. GET_STOCK_MOVEMENT,
  1011. {
  1012. id: 'T_3',
  1013. },
  1014. );
  1015. variant1 = result2.product!.variants[0];
  1016. expect(variant1.stockOnHand).toBe(100);
  1017. expect(variant1.stockAllocated).toBe(0);
  1018. expect(variant1.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  1019. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  1020. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1021. { type: StockMovementType.RELEASE, quantity: 1 },
  1022. { type: StockMovementType.RELEASE, quantity: 1 },
  1023. ]);
  1024. });
  1025. async function assertNoStockMovementsCreated(productId: string) {
  1026. const result = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  1027. GET_STOCK_MOVEMENT,
  1028. {
  1029. id: productId,
  1030. },
  1031. );
  1032. const variant2 = result.product!.variants[0];
  1033. expect(variant2.stockOnHand).toBe(100);
  1034. expect(variant2.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  1035. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  1036. ]);
  1037. }
  1038. });
  1039. describe('cancellation by OrderLine', () => {
  1040. let orderId: string;
  1041. let product: GetProductWithVariants.Product;
  1042. let productVariantId: string;
  1043. beforeAll(async () => {
  1044. const result = await createTestOrder(
  1045. adminClient,
  1046. shopClient,
  1047. customers[0].emailAddress,
  1048. password,
  1049. );
  1050. orderId = result.orderId;
  1051. product = result.product;
  1052. productVariantId = result.productVariantId;
  1053. });
  1054. it('cannot cancel from AddingItems state', async () => {
  1055. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1056. id: orderId,
  1057. });
  1058. expect(order!.state).toBe('AddingItems');
  1059. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  1060. CANCEL_ORDER,
  1061. {
  1062. input: {
  1063. orderId,
  1064. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  1065. },
  1066. },
  1067. );
  1068. orderGuard.assertErrorResult(cancelOrder);
  1069. expect(cancelOrder.message).toBe(
  1070. 'Cannot cancel OrderLines from an Order in the "AddingItems" state',
  1071. );
  1072. expect(cancelOrder.errorCode).toBe(ErrorCode.CANCEL_ACTIVE_ORDER_ERROR);
  1073. });
  1074. it('cannot cancel from ArrangingPayment state', async () => {
  1075. await proceedToArrangingPayment(shopClient);
  1076. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1077. id: orderId,
  1078. });
  1079. expect(order!.state).toBe('ArrangingPayment');
  1080. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  1081. CANCEL_ORDER,
  1082. {
  1083. input: {
  1084. orderId,
  1085. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  1086. },
  1087. },
  1088. );
  1089. orderGuard.assertErrorResult(cancelOrder);
  1090. expect(cancelOrder.message).toBe(
  1091. 'Cannot cancel OrderLines from an Order in the "ArrangingPayment" state',
  1092. );
  1093. expect(cancelOrder.errorCode).toBe(ErrorCode.CANCEL_ACTIVE_ORDER_ERROR);
  1094. });
  1095. it('returns error result if lines are empty', async () => {
  1096. const order = await addPaymentToOrder(shopClient, twoStagePaymentMethod);
  1097. orderGuard.assertSuccess(order);
  1098. expect(order.state).toBe('PaymentAuthorized');
  1099. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  1100. CANCEL_ORDER,
  1101. {
  1102. input: {
  1103. orderId,
  1104. lines: [],
  1105. },
  1106. },
  1107. );
  1108. orderGuard.assertErrorResult(cancelOrder);
  1109. expect(cancelOrder.message).toBe('At least one OrderLine must be specified');
  1110. expect(cancelOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  1111. });
  1112. it('returns error result if all quantities zero', async () => {
  1113. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1114. id: orderId,
  1115. });
  1116. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  1117. CANCEL_ORDER,
  1118. {
  1119. input: {
  1120. orderId,
  1121. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
  1122. },
  1123. },
  1124. );
  1125. orderGuard.assertErrorResult(cancelOrder);
  1126. expect(cancelOrder.message).toBe('At least one OrderLine must be specified');
  1127. expect(cancelOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  1128. });
  1129. it('partial cancellation', async () => {
  1130. const result1 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  1131. GET_STOCK_MOVEMENT,
  1132. {
  1133. id: product.id,
  1134. },
  1135. );
  1136. const variant1 = result1.product!.variants[0];
  1137. expect(variant1.stockOnHand).toBe(100);
  1138. expect(variant1.stockAllocated).toBe(2);
  1139. expect(variant1.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  1140. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  1141. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1142. { type: StockMovementType.RELEASE, quantity: 1 },
  1143. { type: StockMovementType.RELEASE, quantity: 1 },
  1144. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1145. ]);
  1146. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1147. id: orderId,
  1148. });
  1149. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  1150. CANCEL_ORDER,
  1151. {
  1152. input: {
  1153. orderId,
  1154. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  1155. reason: 'cancel reason 1',
  1156. },
  1157. },
  1158. );
  1159. orderGuard.assertSuccess(cancelOrder);
  1160. expect(cancelOrder.lines[0].quantity).toBe(1);
  1161. expect(cancelOrder.lines[0].items.sort((a, b) => (a.id < b.id ? -1 : 1))).toEqual([
  1162. { id: 'T_13', cancelled: true },
  1163. { id: 'T_14', cancelled: false },
  1164. ]);
  1165. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1166. id: orderId,
  1167. });
  1168. expect(order2!.state).toBe('PaymentAuthorized');
  1169. expect(order2!.lines[0].quantity).toBe(1);
  1170. const result2 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  1171. GET_STOCK_MOVEMENT,
  1172. {
  1173. id: product.id,
  1174. },
  1175. );
  1176. const variant2 = result2.product!.variants[0];
  1177. expect(variant2.stockOnHand).toBe(100);
  1178. expect(variant2.stockAllocated).toBe(1);
  1179. expect(variant2.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  1180. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  1181. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1182. { type: StockMovementType.RELEASE, quantity: 1 },
  1183. { type: StockMovementType.RELEASE, quantity: 1 },
  1184. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1185. { type: StockMovementType.RELEASE, quantity: 1 },
  1186. ]);
  1187. });
  1188. it('returns error result if attempting to cancel already cancelled item', async () => {
  1189. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1190. id: orderId,
  1191. });
  1192. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  1193. CANCEL_ORDER,
  1194. {
  1195. input: {
  1196. orderId,
  1197. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 2 })),
  1198. },
  1199. },
  1200. );
  1201. orderGuard.assertErrorResult(cancelOrder);
  1202. expect(cancelOrder.message).toBe(
  1203. 'The specified quantity is greater than the available OrderItems',
  1204. );
  1205. expect(cancelOrder.errorCode).toBe(ErrorCode.QUANTITY_TOO_GREAT_ERROR);
  1206. });
  1207. it('complete cancellation', async () => {
  1208. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1209. id: orderId,
  1210. });
  1211. await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(CANCEL_ORDER, {
  1212. input: {
  1213. orderId,
  1214. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  1215. reason: 'cancel reason 2',
  1216. },
  1217. });
  1218. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1219. id: orderId,
  1220. });
  1221. expect(order2!.state).toBe('Cancelled');
  1222. const result = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  1223. GET_STOCK_MOVEMENT,
  1224. {
  1225. id: product.id,
  1226. },
  1227. );
  1228. const variant2 = result.product!.variants[0];
  1229. expect(variant2.stockOnHand).toBe(100);
  1230. expect(variant2.stockAllocated).toBe(0);
  1231. expect(variant2.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  1232. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  1233. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1234. { type: StockMovementType.RELEASE, quantity: 1 },
  1235. { type: StockMovementType.RELEASE, quantity: 1 },
  1236. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1237. { type: StockMovementType.RELEASE, quantity: 1 },
  1238. { type: StockMovementType.RELEASE, quantity: 1 },
  1239. ]);
  1240. });
  1241. it('order history contains expected entries', async () => {
  1242. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  1243. GET_ORDER_HISTORY,
  1244. {
  1245. id: orderId,
  1246. options: {
  1247. skip: 0,
  1248. },
  1249. },
  1250. );
  1251. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  1252. {
  1253. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1254. data: {
  1255. from: 'Created',
  1256. to: 'AddingItems',
  1257. },
  1258. },
  1259. {
  1260. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1261. data: {
  1262. from: 'AddingItems',
  1263. to: 'ArrangingPayment',
  1264. },
  1265. },
  1266. {
  1267. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  1268. data: {
  1269. paymentId: 'T_4',
  1270. from: 'Created',
  1271. to: 'Authorized',
  1272. },
  1273. },
  1274. {
  1275. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1276. data: {
  1277. from: 'ArrangingPayment',
  1278. to: 'PaymentAuthorized',
  1279. },
  1280. },
  1281. {
  1282. type: HistoryEntryType.ORDER_CANCELLATION,
  1283. data: {
  1284. orderItemIds: ['T_13'],
  1285. reason: 'cancel reason 1',
  1286. },
  1287. },
  1288. {
  1289. type: HistoryEntryType.ORDER_CANCELLATION,
  1290. data: {
  1291. orderItemIds: ['T_14'],
  1292. reason: 'cancel reason 2',
  1293. },
  1294. },
  1295. {
  1296. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1297. data: {
  1298. from: 'PaymentAuthorized',
  1299. to: 'Cancelled',
  1300. },
  1301. },
  1302. ]);
  1303. });
  1304. });
  1305. describe('refunds', () => {
  1306. let orderId: string;
  1307. let product: GetProductWithVariants.Product;
  1308. let productVariantId: string;
  1309. let paymentId: string;
  1310. let refundId: string;
  1311. beforeAll(async () => {
  1312. const result = await createTestOrder(
  1313. adminClient,
  1314. shopClient,
  1315. customers[0].emailAddress,
  1316. password,
  1317. );
  1318. orderId = result.orderId;
  1319. product = result.product;
  1320. productVariantId = result.productVariantId;
  1321. });
  1322. it('cannot refund from PaymentAuthorized state', async () => {
  1323. await proceedToArrangingPayment(shopClient);
  1324. const order = await addPaymentToOrder(shopClient, twoStagePaymentMethod);
  1325. orderGuard.assertSuccess(order);
  1326. expect(order.state).toBe('PaymentAuthorized');
  1327. paymentId = order.payments![0].id;
  1328. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1329. REFUND_ORDER,
  1330. {
  1331. input: {
  1332. lines: order.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  1333. shipping: 0,
  1334. adjustment: 0,
  1335. paymentId,
  1336. },
  1337. },
  1338. );
  1339. refundGuard.assertErrorResult(refundOrder);
  1340. expect(refundOrder.message).toBe('Cannot refund an Order in the "PaymentAuthorized" state');
  1341. expect(refundOrder.errorCode).toBe(ErrorCode.REFUND_ORDER_STATE_ERROR);
  1342. });
  1343. it('returns error result if no lines and no shipping', async () => {
  1344. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1345. id: orderId,
  1346. });
  1347. const { settlePayment } = await adminClient.query<
  1348. SettlePayment.Mutation,
  1349. SettlePayment.Variables
  1350. >(SETTLE_PAYMENT, {
  1351. id: order!.payments![0].id,
  1352. });
  1353. paymentGuard.assertSuccess(settlePayment);
  1354. expect(settlePayment!.state).toBe('Settled');
  1355. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1356. REFUND_ORDER,
  1357. {
  1358. input: {
  1359. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
  1360. shipping: 0,
  1361. adjustment: 0,
  1362. paymentId,
  1363. },
  1364. },
  1365. );
  1366. refundGuard.assertErrorResult(refundOrder);
  1367. expect(refundOrder.message).toBe('Nothing to refund');
  1368. expect(refundOrder.errorCode).toBe(ErrorCode.NOTHING_TO_REFUND_ERROR);
  1369. });
  1370. it(
  1371. 'throws if paymentId not valid',
  1372. assertThrowsWithMessage(async () => {
  1373. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1374. id: orderId,
  1375. });
  1376. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1377. REFUND_ORDER,
  1378. {
  1379. input: {
  1380. lines: [],
  1381. shipping: 100,
  1382. adjustment: 0,
  1383. paymentId: 'T_999',
  1384. },
  1385. },
  1386. );
  1387. }, `No Payment with the id '999' could be found`),
  1388. );
  1389. it('returns error result if payment and order lines do not belong to the same Order', async () => {
  1390. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1391. id: orderId,
  1392. });
  1393. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1394. REFUND_ORDER,
  1395. {
  1396. input: {
  1397. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  1398. shipping: 100,
  1399. adjustment: 0,
  1400. paymentId: 'T_1',
  1401. },
  1402. },
  1403. );
  1404. refundGuard.assertErrorResult(refundOrder);
  1405. expect(refundOrder.message).toBe('The Payment and OrderLines do not belong to the same Order');
  1406. expect(refundOrder.errorCode).toBe(ErrorCode.PAYMENT_ORDER_MISMATCH_ERROR);
  1407. });
  1408. it('creates a Refund to be manually settled', async () => {
  1409. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1410. id: orderId,
  1411. });
  1412. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1413. REFUND_ORDER,
  1414. {
  1415. input: {
  1416. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  1417. shipping: order!.shipping,
  1418. adjustment: 0,
  1419. reason: 'foo',
  1420. paymentId,
  1421. },
  1422. },
  1423. );
  1424. refundGuard.assertSuccess(refundOrder);
  1425. expect(refundOrder.shipping).toBe(order!.shipping);
  1426. expect(refundOrder.items).toBe(order!.subTotalWithTax);
  1427. expect(refundOrder.total).toBe(order!.totalWithTax);
  1428. expect(refundOrder.transactionId).toBe(null);
  1429. expect(refundOrder.state).toBe('Pending');
  1430. refundId = refundOrder.id;
  1431. });
  1432. it('manually settle a Refund', async () => {
  1433. const { settleRefund } = await adminClient.query<SettleRefund.Mutation, SettleRefund.Variables>(
  1434. SETTLE_REFUND,
  1435. {
  1436. input: {
  1437. id: refundId,
  1438. transactionId: 'aaabbb',
  1439. },
  1440. },
  1441. );
  1442. refundGuard.assertSuccess(settleRefund);
  1443. expect(settleRefund.state).toBe('Settled');
  1444. expect(settleRefund.transactionId).toBe('aaabbb');
  1445. });
  1446. it('returns error result if attempting to refund the same item more than once', async () => {
  1447. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1448. id: orderId,
  1449. });
  1450. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1451. REFUND_ORDER,
  1452. {
  1453. input: {
  1454. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  1455. shipping: order!.shipping,
  1456. adjustment: 0,
  1457. paymentId,
  1458. },
  1459. },
  1460. );
  1461. refundGuard.assertErrorResult(refundOrder);
  1462. expect(refundOrder.message).toBe(
  1463. 'The specified quantity is greater than the available OrderItems',
  1464. );
  1465. expect(refundOrder.errorCode).toBe(ErrorCode.QUANTITY_TOO_GREAT_ERROR);
  1466. });
  1467. it('order history contains expected entries', async () => {
  1468. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  1469. GET_ORDER_HISTORY,
  1470. {
  1471. id: orderId,
  1472. options: {
  1473. skip: 0,
  1474. },
  1475. },
  1476. );
  1477. expect(order!.history.items.sort(sortById).map(pick(['type', 'data']))).toEqual([
  1478. {
  1479. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1480. data: {
  1481. from: 'Created',
  1482. to: 'AddingItems',
  1483. },
  1484. },
  1485. {
  1486. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1487. data: {
  1488. from: 'AddingItems',
  1489. to: 'ArrangingPayment',
  1490. },
  1491. },
  1492. {
  1493. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  1494. data: {
  1495. paymentId: 'T_5',
  1496. from: 'Created',
  1497. to: 'Authorized',
  1498. },
  1499. },
  1500. {
  1501. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1502. data: {
  1503. from: 'ArrangingPayment',
  1504. to: 'PaymentAuthorized',
  1505. },
  1506. },
  1507. {
  1508. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  1509. data: {
  1510. paymentId: 'T_5',
  1511. from: 'Authorized',
  1512. to: 'Settled',
  1513. },
  1514. },
  1515. {
  1516. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1517. data: {
  1518. from: 'PaymentAuthorized',
  1519. to: 'PaymentSettled',
  1520. },
  1521. },
  1522. {
  1523. type: HistoryEntryType.ORDER_REFUND_TRANSITION,
  1524. data: {
  1525. refundId: 'T_1',
  1526. reason: 'foo',
  1527. from: 'Pending',
  1528. to: 'Settled',
  1529. },
  1530. },
  1531. ]);
  1532. });
  1533. // https://github.com/vendure-ecommerce/vendure/issues/873
  1534. it('can add another refund if the first one fails', async () => {
  1535. const orderResult = await createTestOrder(
  1536. adminClient,
  1537. shopClient,
  1538. customers[0].emailAddress,
  1539. password,
  1540. );
  1541. await proceedToArrangingPayment(shopClient);
  1542. const order = await addPaymentToOrder(shopClient, singleStageRefundFailingPaymentMethod);
  1543. orderGuard.assertSuccess(order);
  1544. expect(order.state).toBe('PaymentSettled');
  1545. const { refundOrder: refund1 } = await adminClient.query<
  1546. RefundOrder.Mutation,
  1547. RefundOrder.Variables
  1548. >(REFUND_ORDER, {
  1549. input: {
  1550. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  1551. shipping: order!.shipping,
  1552. adjustment: 0,
  1553. reason: 'foo',
  1554. paymentId: order.payments![0].id,
  1555. },
  1556. });
  1557. refundGuard.assertSuccess(refund1);
  1558. expect(refund1.state).toBe('Failed');
  1559. expect(refund1.total).toBe(order.totalWithTax);
  1560. const { refundOrder: refund2 } = await adminClient.query<
  1561. RefundOrder.Mutation,
  1562. RefundOrder.Variables
  1563. >(REFUND_ORDER, {
  1564. input: {
  1565. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  1566. shipping: order!.shipping,
  1567. adjustment: 0,
  1568. reason: 'foo',
  1569. paymentId: order.payments![0].id,
  1570. },
  1571. });
  1572. refundGuard.assertSuccess(refund2);
  1573. expect(refund2.state).toBe('Settled');
  1574. expect(refund2.total).toBe(order.totalWithTax);
  1575. });
  1576. });
  1577. describe('order notes', () => {
  1578. let orderId: string;
  1579. let firstNoteId: string;
  1580. beforeAll(async () => {
  1581. const result = await createTestOrder(
  1582. adminClient,
  1583. shopClient,
  1584. customers[2].emailAddress,
  1585. password,
  1586. );
  1587. orderId = result.orderId;
  1588. });
  1589. it('private note', async () => {
  1590. const { addNoteToOrder } = await adminClient.query<
  1591. AddNoteToOrder.Mutation,
  1592. AddNoteToOrder.Variables
  1593. >(ADD_NOTE_TO_ORDER, {
  1594. input: {
  1595. id: orderId,
  1596. note: 'A private note',
  1597. isPublic: false,
  1598. },
  1599. });
  1600. expect(addNoteToOrder.id).toBe(orderId);
  1601. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  1602. GET_ORDER_HISTORY,
  1603. {
  1604. id: orderId,
  1605. options: {
  1606. skip: 1,
  1607. },
  1608. },
  1609. );
  1610. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  1611. {
  1612. type: HistoryEntryType.ORDER_NOTE,
  1613. data: {
  1614. note: 'A private note',
  1615. },
  1616. },
  1617. ]);
  1618. firstNoteId = order!.history.items[0].id;
  1619. const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
  1620. expect(activeOrder!.history.items.map(pick(['type']))).toEqual([
  1621. { type: HistoryEntryType.ORDER_STATE_TRANSITION },
  1622. ]);
  1623. });
  1624. it('public note', async () => {
  1625. const { addNoteToOrder } = await adminClient.query<
  1626. AddNoteToOrder.Mutation,
  1627. AddNoteToOrder.Variables
  1628. >(ADD_NOTE_TO_ORDER, {
  1629. input: {
  1630. id: orderId,
  1631. note: 'A public note',
  1632. isPublic: true,
  1633. },
  1634. });
  1635. expect(addNoteToOrder.id).toBe(orderId);
  1636. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  1637. GET_ORDER_HISTORY,
  1638. {
  1639. id: orderId,
  1640. options: {
  1641. skip: 2,
  1642. },
  1643. },
  1644. );
  1645. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  1646. {
  1647. type: HistoryEntryType.ORDER_NOTE,
  1648. data: {
  1649. note: 'A public note',
  1650. },
  1651. },
  1652. ]);
  1653. const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
  1654. expect(activeOrder!.history.items.map(pick(['type', 'data']))).toEqual([
  1655. {
  1656. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1657. data: {
  1658. from: 'Created',
  1659. to: 'AddingItems',
  1660. },
  1661. },
  1662. {
  1663. type: HistoryEntryType.ORDER_NOTE,
  1664. data: {
  1665. note: 'A public note',
  1666. },
  1667. },
  1668. ]);
  1669. });
  1670. it('update note', async () => {
  1671. const { updateOrderNote } = await adminClient.query<
  1672. UpdateOrderNote.Mutation,
  1673. UpdateOrderNote.Variables
  1674. >(UPDATE_ORDER_NOTE, {
  1675. input: {
  1676. noteId: firstNoteId,
  1677. note: 'An updated note',
  1678. },
  1679. });
  1680. expect(updateOrderNote.data).toEqual({
  1681. note: 'An updated note',
  1682. });
  1683. });
  1684. it('delete note', async () => {
  1685. const { order: before } = await adminClient.query<
  1686. GetOrderHistory.Query,
  1687. GetOrderHistory.Variables
  1688. >(GET_ORDER_HISTORY, { id: orderId });
  1689. expect(before?.history.totalItems).toBe(3);
  1690. const { deleteOrderNote } = await adminClient.query<
  1691. DeleteOrderNote.Mutation,
  1692. DeleteOrderNote.Variables
  1693. >(DELETE_ORDER_NOTE, {
  1694. id: firstNoteId,
  1695. });
  1696. expect(deleteOrderNote.result).toBe(DeletionResult.DELETED);
  1697. const { order: after } = await adminClient.query<
  1698. GetOrderHistory.Query,
  1699. GetOrderHistory.Variables
  1700. >(GET_ORDER_HISTORY, { id: orderId });
  1701. expect(after?.history.totalItems).toBe(2);
  1702. });
  1703. });
  1704. describe('multiple payments', () => {
  1705. const PARTIAL_PAYMENT_AMOUNT = 1000;
  1706. let orderId: string;
  1707. let orderTotalWithTax: number;
  1708. let payment1Id: string;
  1709. let payment2Id: string;
  1710. let productInOrder: GetProductWithVariants.Product;
  1711. beforeAll(async () => {
  1712. const result = await createTestOrder(
  1713. adminClient,
  1714. shopClient,
  1715. customers[1].emailAddress,
  1716. password,
  1717. );
  1718. orderId = result.orderId;
  1719. productInOrder = result.product;
  1720. });
  1721. it('adds a partial payment', async () => {
  1722. await proceedToArrangingPayment(shopClient);
  1723. const { addPaymentToOrder: order } = await shopClient.query<
  1724. AddPaymentToOrder.Mutation,
  1725. AddPaymentToOrder.Variables
  1726. >(ADD_PAYMENT, {
  1727. input: {
  1728. method: partialPaymentMethod.code,
  1729. metadata: {
  1730. amount: PARTIAL_PAYMENT_AMOUNT,
  1731. },
  1732. },
  1733. });
  1734. orderGuard.assertSuccess(order);
  1735. orderTotalWithTax = order.totalWithTax;
  1736. expect(order.state).toBe('ArrangingPayment');
  1737. expect(order.payments?.length).toBe(1);
  1738. expect(omit(order.payments![0], ['id'])).toEqual({
  1739. amount: PARTIAL_PAYMENT_AMOUNT,
  1740. metadata: {
  1741. public: {
  1742. amount: PARTIAL_PAYMENT_AMOUNT,
  1743. },
  1744. },
  1745. method: partialPaymentMethod.code,
  1746. state: 'Settled',
  1747. transactionId: '12345',
  1748. });
  1749. payment1Id = order.payments![0].id;
  1750. });
  1751. it('adds another payment to make up order totalWithTax', async () => {
  1752. const { addPaymentToOrder: order } = await shopClient.query<
  1753. AddPaymentToOrder.Mutation,
  1754. AddPaymentToOrder.Variables
  1755. >(ADD_PAYMENT, {
  1756. input: {
  1757. method: singleStageRefundablePaymentMethod.code,
  1758. metadata: {},
  1759. },
  1760. });
  1761. orderGuard.assertSuccess(order);
  1762. expect(order.state).toBe('PaymentSettled');
  1763. expect(order.payments?.length).toBe(2);
  1764. expect(
  1765. omit(order.payments?.find(p => p.method === singleStageRefundablePaymentMethod.code)!, [
  1766. 'id',
  1767. ]),
  1768. ).toEqual({
  1769. amount: orderTotalWithTax - PARTIAL_PAYMENT_AMOUNT,
  1770. metadata: {},
  1771. method: singleStageRefundablePaymentMethod.code,
  1772. state: 'Settled',
  1773. transactionId: '12345',
  1774. });
  1775. payment2Id = order.payments![1].id;
  1776. });
  1777. it('partial refunding of order with multiple payments', async () => {
  1778. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1779. id: orderId,
  1780. });
  1781. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1782. REFUND_ORDER,
  1783. {
  1784. input: {
  1785. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  1786. shipping: 0,
  1787. adjustment: 0,
  1788. reason: 'foo',
  1789. paymentId: payment1Id,
  1790. },
  1791. },
  1792. );
  1793. refundGuard.assertSuccess(refundOrder);
  1794. expect(refundOrder.total).toBe(PARTIAL_PAYMENT_AMOUNT);
  1795. const { order: orderWithPayments } = await adminClient.query<
  1796. GetOrderWithPayments.Query,
  1797. GetOrderWithPayments.Variables
  1798. >(GET_ORDER_WITH_PAYMENTS, {
  1799. id: orderId,
  1800. });
  1801. expect(orderWithPayments?.payments![0].refunds.length).toBe(1);
  1802. expect(orderWithPayments?.payments![0].refunds[0].total).toBe(PARTIAL_PAYMENT_AMOUNT);
  1803. expect(orderWithPayments?.payments![1].refunds.length).toBe(1);
  1804. expect(orderWithPayments?.payments![1].refunds[0].total).toBe(
  1805. productInOrder.variants[0].priceWithTax - PARTIAL_PAYMENT_AMOUNT,
  1806. );
  1807. });
  1808. it('refunding remaining amount of order with multiple payments', async () => {
  1809. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1810. id: orderId,
  1811. });
  1812. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1813. REFUND_ORDER,
  1814. {
  1815. input: {
  1816. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  1817. shipping: order!.shippingWithTax,
  1818. adjustment: 0,
  1819. reason: 'foo',
  1820. paymentId: payment1Id,
  1821. },
  1822. },
  1823. );
  1824. refundGuard.assertSuccess(refundOrder);
  1825. expect(refundOrder.total).toBe(order!.totalWithTax - order!.lines[0].unitPriceWithTax);
  1826. const { order: orderWithPayments } = await adminClient.query<
  1827. GetOrderWithPayments.Query,
  1828. GetOrderWithPayments.Variables
  1829. >(GET_ORDER_WITH_PAYMENTS, {
  1830. id: orderId,
  1831. });
  1832. expect(orderWithPayments?.payments![0].refunds.length).toBe(1);
  1833. expect(orderWithPayments?.payments![0].refunds[0].total).toBe(PARTIAL_PAYMENT_AMOUNT);
  1834. expect(orderWithPayments?.payments![1].refunds.length).toBe(2);
  1835. expect(orderWithPayments?.payments![1].refunds[0].total).toBe(
  1836. productInOrder.variants[0].priceWithTax - PARTIAL_PAYMENT_AMOUNT,
  1837. );
  1838. expect(orderWithPayments?.payments![1].refunds[1].total).toBe(
  1839. productInOrder.variants[0].priceWithTax + order!.shippingWithTax,
  1840. );
  1841. });
  1842. // https://github.com/vendure-ecommerce/vendure/issues/847
  1843. it('manual call to settlePayment works with multiple payments', async () => {
  1844. const result = await createTestOrder(
  1845. adminClient,
  1846. shopClient,
  1847. customers[1].emailAddress,
  1848. password,
  1849. );
  1850. await proceedToArrangingPayment(shopClient);
  1851. await shopClient.query<AddPaymentToOrder.Mutation, AddPaymentToOrder.Variables>(ADD_PAYMENT, {
  1852. input: {
  1853. method: partialPaymentMethod.code,
  1854. metadata: {
  1855. amount: PARTIAL_PAYMENT_AMOUNT,
  1856. authorizeOnly: true,
  1857. },
  1858. },
  1859. });
  1860. const { addPaymentToOrder: order } = await shopClient.query<
  1861. AddPaymentToOrder.Mutation,
  1862. AddPaymentToOrder.Variables
  1863. >(ADD_PAYMENT, {
  1864. input: {
  1865. method: singleStageRefundablePaymentMethod.code,
  1866. metadata: {},
  1867. },
  1868. });
  1869. orderGuard.assertSuccess(order);
  1870. expect(order.state).toBe('PaymentAuthorized');
  1871. const { settlePayment } = await adminClient.query<
  1872. SettlePayment.Mutation,
  1873. SettlePayment.Variables
  1874. >(SETTLE_PAYMENT, {
  1875. id: order.payments!.find(p => p.method === partialPaymentMethod.code)!.id,
  1876. });
  1877. paymentGuard.assertSuccess(settlePayment);
  1878. expect(settlePayment.state).toBe('Settled');
  1879. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1880. id: order.id,
  1881. });
  1882. expect(order2?.state).toBe('PaymentSettled');
  1883. });
  1884. });
  1885. describe('issues', () => {
  1886. // https://github.com/vendure-ecommerce/vendure/issues/639
  1887. it('returns fulfillments for Order with no lines', async () => {
  1888. await shopClient.asAnonymousUser();
  1889. // Apply a coupon code just to create an active order with no OrderLines
  1890. await shopClient.query<ApplyCouponCode.Mutation, ApplyCouponCode.Variables>(APPLY_COUPON_CODE, {
  1891. couponCode: 'TEST',
  1892. });
  1893. const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
  1894. const { order } = await adminClient.query<
  1895. GetOrderFulfillments.Query,
  1896. GetOrderFulfillments.Variables
  1897. >(GET_ORDER_FULFILLMENTS, {
  1898. id: activeOrder!.id,
  1899. });
  1900. expect(order?.fulfillments).toEqual([]);
  1901. });
  1902. // https://github.com/vendure-ecommerce/vendure/issues/603
  1903. it('orders correctly resolves quantities and OrderItems', async () => {
  1904. await shopClient.asAnonymousUser();
  1905. const { addItemToOrder } = await shopClient.query<
  1906. AddItemToOrder.Mutation,
  1907. AddItemToOrder.Variables
  1908. >(ADD_ITEM_TO_ORDER, {
  1909. productVariantId: 'T_1',
  1910. quantity: 2,
  1911. });
  1912. orderGuard.assertSuccess(addItemToOrder);
  1913. const { orders } = await adminClient.query<
  1914. GetOrderListWithQty.Query,
  1915. GetOrderListWithQty.Variables
  1916. >(GET_ORDERS_LIST_WITH_QUANTITIES, {
  1917. options: {
  1918. filter: {
  1919. code: { eq: addItemToOrder.code },
  1920. },
  1921. },
  1922. });
  1923. expect(orders.items[0].totalQuantity).toBe(2);
  1924. expect(orders.items[0].lines[0].quantity).toBe(2);
  1925. });
  1926. // https://github.com/vendure-ecommerce/vendure/issues/716
  1927. it('get an Order with a deleted ShippingMethod', async () => {
  1928. const { createShippingMethod: shippingMethod } = await adminClient.query<
  1929. CreateShippingMethod.Mutation,
  1930. CreateShippingMethod.Variables
  1931. >(CREATE_SHIPPING_METHOD, {
  1932. input: {
  1933. code: 'royal-mail',
  1934. translations: [{ languageCode: LanguageCode.en, name: 'Royal Mail', description: '' }],
  1935. fulfillmentHandler: manualFulfillmentHandler.code,
  1936. checker: {
  1937. code: defaultShippingEligibilityChecker.code,
  1938. arguments: [{ name: 'orderMinimum', value: '0' }],
  1939. },
  1940. calculator: {
  1941. code: defaultShippingCalculator.code,
  1942. arguments: [
  1943. { name: 'rate', value: '500' },
  1944. { name: 'taxRate', value: '0' },
  1945. ],
  1946. },
  1947. },
  1948. });
  1949. await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
  1950. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  1951. productVariantId: 'T_1',
  1952. quantity: 2,
  1953. });
  1954. await shopClient.query<SetShippingAddress.Mutation, SetShippingAddress.Variables>(
  1955. SET_SHIPPING_ADDRESS,
  1956. {
  1957. input: {
  1958. fullName: 'name',
  1959. streetLine1: '12 the street',
  1960. city: 'foo',
  1961. postalCode: '123456',
  1962. countryCode: 'US',
  1963. },
  1964. },
  1965. );
  1966. const { setOrderShippingMethod: order } = await shopClient.query<
  1967. SetShippingMethod.Mutation,
  1968. SetShippingMethod.Variables
  1969. >(SET_SHIPPING_METHOD, {
  1970. id: shippingMethod.id,
  1971. });
  1972. orderGuard.assertSuccess(order);
  1973. await adminClient.query<DeleteShippingMethod.Mutation, DeleteShippingMethod.Variables>(
  1974. DELETE_SHIPPING_METHOD,
  1975. {
  1976. id: shippingMethod.id,
  1977. },
  1978. );
  1979. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1980. id: order.id,
  1981. });
  1982. expect(order2?.shippingLines[0]).toEqual({
  1983. priceWithTax: 500,
  1984. shippingMethod: pick(shippingMethod, ['id', 'name', 'code', 'description']),
  1985. });
  1986. });
  1987. // https://github.com/vendure-ecommerce/vendure/issues/868
  1988. it('allows multiple refunds of same OrderLine', async () => {
  1989. await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
  1990. const { addItemToOrder } = await shopClient.query<
  1991. AddItemToOrder.Mutation,
  1992. AddItemToOrder.Variables
  1993. >(ADD_ITEM_TO_ORDER, {
  1994. productVariantId: 'T_1',
  1995. quantity: 2,
  1996. });
  1997. await proceedToArrangingPayment(shopClient);
  1998. const order = await addPaymentToOrder(shopClient, singleStageRefundablePaymentMethod);
  1999. orderGuard.assertSuccess(order);
  2000. const { refundOrder: refund1 } = await adminClient.query<
  2001. RefundOrder.Mutation,
  2002. RefundOrder.Variables
  2003. >(REFUND_ORDER, {
  2004. input: {
  2005. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  2006. shipping: 0,
  2007. adjustment: 0,
  2008. reason: 'foo',
  2009. paymentId: order.payments![0].id,
  2010. },
  2011. });
  2012. refundGuard.assertSuccess(refund1);
  2013. const { refundOrder: refund2 } = await adminClient.query<
  2014. RefundOrder.Mutation,
  2015. RefundOrder.Variables
  2016. >(REFUND_ORDER, {
  2017. input: {
  2018. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  2019. shipping: 0,
  2020. adjustment: 0,
  2021. reason: 'foo',
  2022. paymentId: order.payments![0].id,
  2023. },
  2024. });
  2025. refundGuard.assertSuccess(refund2);
  2026. });
  2027. // https://github.com/vendure-ecommerce/vendure/issues/1125
  2028. it('resolves deleted Product of OrderLine ProductVariants', async () => {
  2029. await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
  2030. const { addItemToOrder } = await shopClient.query<
  2031. AddItemToOrder.Mutation,
  2032. AddItemToOrder.Variables
  2033. >(ADD_ITEM_TO_ORDER, {
  2034. productVariantId: 'T_7',
  2035. quantity: 1,
  2036. });
  2037. await proceedToArrangingPayment(shopClient);
  2038. const order = await addPaymentToOrder(shopClient, singleStageRefundablePaymentMethod);
  2039. orderGuard.assertSuccess(order);
  2040. await adminClient.query<DeleteProduct.Mutation, DeleteProduct.Variables>(DELETE_PRODUCT, {
  2041. id: 'T_3',
  2042. });
  2043. const { activeCustomer } = await shopClient.query<
  2044. GetActiveCustomerWithOrdersProductSlug.Query,
  2045. GetActiveCustomerWithOrdersProductSlug.Variables
  2046. >(GET_ACTIVE_CUSTOMER_WITH_ORDERS_PRODUCT_SLUG, {
  2047. options: {
  2048. sort: {
  2049. createdAt: SortOrder.ASC,
  2050. },
  2051. },
  2052. });
  2053. expect(
  2054. activeCustomer!.orders.items[activeCustomer!.orders.items.length - 1].lines[0].productVariant
  2055. .product.slug,
  2056. ).toBe('gaming-pc');
  2057. });
  2058. });
  2059. });
  2060. async function createTestOrder(
  2061. adminClient: SimpleGraphQLClient,
  2062. shopClient: SimpleGraphQLClient,
  2063. emailAddress: string,
  2064. password: string,
  2065. ): Promise<{
  2066. orderId: string;
  2067. product: GetProductWithVariants.Product;
  2068. productVariantId: string;
  2069. }> {
  2070. const result = await adminClient.query<GetProductWithVariants.Query, GetProductWithVariants.Variables>(
  2071. GET_PRODUCT_WITH_VARIANTS,
  2072. {
  2073. id: 'T_3',
  2074. },
  2075. );
  2076. const product = result.product!;
  2077. const productVariantId = product.variants[0].id;
  2078. // Set the ProductVariant to trackInventory
  2079. const { updateProductVariants } = await adminClient.query<
  2080. UpdateProductVariants.Mutation,
  2081. UpdateProductVariants.Variables
  2082. >(UPDATE_PRODUCT_VARIANTS, {
  2083. input: [
  2084. {
  2085. id: productVariantId,
  2086. trackInventory: GlobalFlag.TRUE,
  2087. },
  2088. ],
  2089. });
  2090. // Add the ProductVariant to the Order
  2091. await shopClient.asUserWithCredentials(emailAddress, password);
  2092. const { addItemToOrder } = await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(
  2093. ADD_ITEM_TO_ORDER,
  2094. {
  2095. productVariantId,
  2096. quantity: 2,
  2097. },
  2098. );
  2099. const orderId = (addItemToOrder as UpdatedOrder.Fragment).id;
  2100. return { product, productVariantId, orderId };
  2101. }
  2102. async function getUnfulfilledOrderLineInput(
  2103. client: SimpleGraphQLClient,
  2104. id: string,
  2105. ): Promise<OrderLineInput[]> {
  2106. const { order } = await client.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  2107. id,
  2108. });
  2109. const unfulfilledItems =
  2110. order?.lines.filter(l => {
  2111. const items = l.items.filter(i => i.fulfillment === null);
  2112. return items.length > 0 ? true : false;
  2113. }) || [];
  2114. return unfulfilledItems.map(l => ({
  2115. orderLineId: l.id,
  2116. quantity: l.items.length,
  2117. }));
  2118. }
  2119. export const GET_ORDER_LIST_FULFILLMENTS = gql`
  2120. query GetOrderListFulfillments {
  2121. orders {
  2122. items {
  2123. id
  2124. state
  2125. fulfillments {
  2126. id
  2127. state
  2128. nextStates
  2129. method
  2130. }
  2131. }
  2132. }
  2133. }
  2134. `;
  2135. export const GET_ORDER_FULFILLMENT_ITEMS = gql`
  2136. query GetOrderFulfillmentItems($id: ID!) {
  2137. order(id: $id) {
  2138. id
  2139. state
  2140. fulfillments {
  2141. ...Fulfillment
  2142. }
  2143. }
  2144. }
  2145. ${FULFILLMENT_FRAGMENT}
  2146. `;
  2147. const REFUND_FRAGMENT = gql`
  2148. fragment Refund on Refund {
  2149. id
  2150. state
  2151. items
  2152. transactionId
  2153. shipping
  2154. total
  2155. metadata
  2156. }
  2157. `;
  2158. export const REFUND_ORDER = gql`
  2159. mutation RefundOrder($input: RefundOrderInput!) {
  2160. refundOrder(input: $input) {
  2161. ...Refund
  2162. ... on ErrorResult {
  2163. errorCode
  2164. message
  2165. }
  2166. }
  2167. }
  2168. ${REFUND_FRAGMENT}
  2169. `;
  2170. export const SETTLE_REFUND = gql`
  2171. mutation SettleRefund($input: SettleRefundInput!) {
  2172. settleRefund(input: $input) {
  2173. ...Refund
  2174. ... on ErrorResult {
  2175. errorCode
  2176. message
  2177. }
  2178. }
  2179. }
  2180. ${REFUND_FRAGMENT}
  2181. `;
  2182. export const ADD_NOTE_TO_ORDER = gql`
  2183. mutation AddNoteToOrder($input: AddNoteToOrderInput!) {
  2184. addNoteToOrder(input: $input) {
  2185. id
  2186. }
  2187. }
  2188. `;
  2189. export const UPDATE_ORDER_NOTE = gql`
  2190. mutation UpdateOrderNote($input: UpdateOrderNoteInput!) {
  2191. updateOrderNote(input: $input) {
  2192. id
  2193. data
  2194. isPublic
  2195. }
  2196. }
  2197. `;
  2198. export const DELETE_ORDER_NOTE = gql`
  2199. mutation DeleteOrderNote($id: ID!) {
  2200. deleteOrderNote(id: $id) {
  2201. result
  2202. message
  2203. }
  2204. }
  2205. `;
  2206. const GET_ORDER_WITH_PAYMENTS = gql`
  2207. query GetOrderWithPayments($id: ID!) {
  2208. order(id: $id) {
  2209. id
  2210. payments {
  2211. id
  2212. errorMessage
  2213. metadata
  2214. refunds {
  2215. id
  2216. total
  2217. }
  2218. }
  2219. }
  2220. }
  2221. `;
  2222. const GET_ORDERS_LIST_WITH_QUANTITIES = gql`
  2223. query GetOrderListWithQty($options: OrderListOptions) {
  2224. orders(options: $options) {
  2225. items {
  2226. id
  2227. code
  2228. totalQuantity
  2229. lines {
  2230. id
  2231. quantity
  2232. }
  2233. }
  2234. }
  2235. }
  2236. `;