order.e2e-spec.ts 79 KB

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