order.e2e-spec.ts 108 KB

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