order.e2e-spec.ts 106 KB

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