order.e2e-spec.ts 113 KB

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