order.e2e-spec.ts 102 KB

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