order.e2e-spec.ts 101 KB

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