order-modification.e2e-spec.ts 96 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548
  1. /* tslint:disable:no-non-null-assertion */
  2. import { omit } from '@vendure/common/lib/omit';
  3. import { pick } from '@vendure/common/lib/pick';
  4. import { summate } from '@vendure/common/lib/shared-utils';
  5. import {
  6. defaultShippingCalculator,
  7. defaultShippingEligibilityChecker,
  8. freeShipping,
  9. manualFulfillmentHandler,
  10. mergeConfig,
  11. minimumOrderAmount,
  12. orderFixedDiscount,
  13. orderPercentageDiscount,
  14. productsPercentageDiscount,
  15. ShippingCalculator,
  16. } from '@vendure/core';
  17. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  18. import gql from 'graphql-tag';
  19. import path from 'path';
  20. import { initialData } from '../../../e2e-common/e2e-initial-data';
  21. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  22. import { manualFulfillmentHandler } from '../src/config/fulfillment/manual-fulfillment-handler';
  23. import { orderFixedDiscount } from '../src/config/promotion/actions/order-fixed-discount-action';
  24. import {
  25. failsToSettlePaymentMethod,
  26. testFailingPaymentMethod,
  27. testSuccessfulPaymentMethod,
  28. } from './fixtures/test-payment-methods';
  29. import * as Codegen from './graphql/generated-e2e-admin-types';
  30. import {
  31. ErrorCode,
  32. GlobalFlag,
  33. HistoryEntryType,
  34. LanguageCode,
  35. OrderFragment,
  36. OrderWithLinesFragment,
  37. OrderWithModificationsFragment,
  38. } from './graphql/generated-e2e-admin-types';
  39. import * as CodegenShop from './graphql/generated-e2e-shop-types';
  40. import {
  41. AddItemToOrderMutationVariables,
  42. TestOrderWithPaymentsFragment,
  43. UpdatedOrderFragment,
  44. } from './graphql/generated-e2e-shop-types';
  45. import {
  46. ADMIN_TRANSITION_TO_STATE,
  47. CREATE_FULFILLMENT,
  48. CREATE_PROMOTION,
  49. CREATE_SHIPPING_METHOD,
  50. DELETE_PROMOTION,
  51. GET_ORDER,
  52. GET_ORDER_HISTORY,
  53. GET_PRODUCT_VARIANT_LIST,
  54. GET_STOCK_MOVEMENT,
  55. UPDATE_CHANNEL,
  56. UPDATE_PRODUCT_VARIANTS,
  57. } from './graphql/shared-definitions';
  58. import {
  59. APPLY_COUPON_CODE,
  60. SET_SHIPPING_ADDRESS,
  61. SET_SHIPPING_METHOD,
  62. TRANSITION_TO_STATE,
  63. } from './graphql/shop-definitions';
  64. import { addPaymentToOrder, proceedToArrangingPayment } from './utils/test-order-utils';
  65. const SHIPPING_GB = 500;
  66. const SHIPPING_US = 1000;
  67. const SHIPPING_OTHER = 750;
  68. const testCalculator = new ShippingCalculator({
  69. code: 'test-calculator',
  70. description: [{ languageCode: LanguageCode.en, value: 'Has metadata' }],
  71. args: {},
  72. calculate: (ctx, order, args) => {
  73. let price;
  74. switch (order.shippingAddress.countryCode) {
  75. case 'GB':
  76. price = SHIPPING_GB;
  77. break;
  78. case 'US':
  79. price = SHIPPING_US;
  80. break;
  81. default:
  82. price = SHIPPING_OTHER;
  83. }
  84. return {
  85. price,
  86. priceIncludesTax: true,
  87. taxRate: 20,
  88. };
  89. },
  90. });
  91. describe('Order modification', () => {
  92. const { server, adminClient, shopClient } = createTestEnvironment(
  93. mergeConfig(testConfig(), {
  94. paymentOptions: {
  95. paymentMethodHandlers: [
  96. testSuccessfulPaymentMethod,
  97. failsToSettlePaymentMethod,
  98. testFailingPaymentMethod,
  99. ],
  100. },
  101. shippingOptions: {
  102. shippingCalculators: [defaultShippingCalculator, testCalculator],
  103. },
  104. customFields: {
  105. Order: [{ name: 'points', type: 'int', defaultValue: 0 }],
  106. OrderLine: [{ name: 'color', type: 'string', nullable: true }],
  107. },
  108. }),
  109. );
  110. let orderId: string;
  111. let testShippingMethodId: string;
  112. const orderGuard: ErrorResultGuard<
  113. UpdatedOrderFragment | OrderWithModificationsFragment | OrderFragment
  114. > = createErrorResultGuard(input => !!input.id);
  115. beforeAll(async () => {
  116. await server.init({
  117. initialData: {
  118. ...initialData,
  119. paymentMethods: [
  120. {
  121. name: testSuccessfulPaymentMethod.code,
  122. handler: { code: testSuccessfulPaymentMethod.code, arguments: [] },
  123. },
  124. {
  125. name: failsToSettlePaymentMethod.code,
  126. handler: { code: failsToSettlePaymentMethod.code, arguments: [] },
  127. },
  128. {
  129. name: testFailingPaymentMethod.code,
  130. handler: { code: testFailingPaymentMethod.code, arguments: [] },
  131. },
  132. ],
  133. },
  134. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  135. customerCount: 3,
  136. });
  137. await adminClient.asSuperAdmin();
  138. await adminClient.query<
  139. Codegen.UpdateProductVariantsMutation,
  140. Codegen.UpdateProductVariantsMutationVariables
  141. >(UPDATE_PRODUCT_VARIANTS, {
  142. input: [
  143. {
  144. id: 'T_1',
  145. trackInventory: GlobalFlag.TRUE,
  146. },
  147. {
  148. id: 'T_2',
  149. trackInventory: GlobalFlag.TRUE,
  150. },
  151. {
  152. id: 'T_3',
  153. trackInventory: GlobalFlag.TRUE,
  154. },
  155. ],
  156. });
  157. const { createShippingMethod } = await adminClient.query<
  158. Codegen.CreateShippingMethodMutation,
  159. Codegen.CreateShippingMethodMutationVariables
  160. >(CREATE_SHIPPING_METHOD, {
  161. input: {
  162. code: 'new-method',
  163. fulfillmentHandler: manualFulfillmentHandler.code,
  164. checker: {
  165. code: defaultShippingEligibilityChecker.code,
  166. arguments: [
  167. {
  168. name: 'orderMinimum',
  169. value: '0',
  170. },
  171. ],
  172. },
  173. calculator: {
  174. code: testCalculator.code,
  175. arguments: [],
  176. },
  177. translations: [{ languageCode: LanguageCode.en, name: 'test method', description: '' }],
  178. },
  179. });
  180. testShippingMethodId = createShippingMethod.id;
  181. // create an order and check out
  182. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  183. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  184. productVariantId: 'T_1',
  185. quantity: 1,
  186. customFields: {
  187. color: 'green',
  188. },
  189. } as any);
  190. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  191. productVariantId: 'T_4',
  192. quantity: 2,
  193. });
  194. await proceedToArrangingPayment(shopClient);
  195. const result = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  196. orderGuard.assertSuccess(result);
  197. orderId = result.id;
  198. }, TEST_SETUP_TIMEOUT_MS);
  199. afterAll(async () => {
  200. await server.destroy();
  201. });
  202. it('modifyOrder returns error result when not in Modifying state', async () => {
  203. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  204. GET_ORDER,
  205. {
  206. id: orderId,
  207. },
  208. );
  209. const { modifyOrder } = await adminClient.query<
  210. Codegen.ModifyOrderMutation,
  211. Codegen.ModifyOrderMutationVariables
  212. >(MODIFY_ORDER, {
  213. input: {
  214. dryRun: false,
  215. orderId,
  216. adjustOrderLines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 3 })),
  217. },
  218. });
  219. orderGuard.assertErrorResult(modifyOrder);
  220. expect(modifyOrder.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_STATE_ERROR);
  221. });
  222. it('transition to Modifying state', async () => {
  223. const transitionOrderToState = await adminTransitionOrderToState(orderId, 'Modifying');
  224. orderGuard.assertSuccess(transitionOrderToState);
  225. expect(transitionOrderToState.state).toBe('Modifying');
  226. });
  227. describe('error cases', () => {
  228. it('no changes specified error', async () => {
  229. const { modifyOrder } = await adminClient.query<
  230. Codegen.ModifyOrderMutation,
  231. Codegen.ModifyOrderMutationVariables
  232. >(MODIFY_ORDER, {
  233. input: {
  234. dryRun: false,
  235. orderId,
  236. },
  237. });
  238. orderGuard.assertErrorResult(modifyOrder);
  239. expect(modifyOrder.errorCode).toBe(ErrorCode.NO_CHANGES_SPECIFIED_ERROR);
  240. });
  241. it('no refund paymentId specified', async () => {
  242. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  243. GET_ORDER,
  244. {
  245. id: orderId,
  246. },
  247. );
  248. const { modifyOrder } = await adminClient.query<
  249. Codegen.ModifyOrderMutation,
  250. Codegen.ModifyOrderMutationVariables
  251. >(MODIFY_ORDER, {
  252. input: {
  253. dryRun: false,
  254. orderId,
  255. surcharges: [{ price: -500, priceIncludesTax: true, description: 'Discount' }],
  256. },
  257. });
  258. orderGuard.assertErrorResult(modifyOrder);
  259. expect(modifyOrder.errorCode).toBe(ErrorCode.REFUND_PAYMENT_ID_MISSING_ERROR);
  260. await assertOrderIsUnchanged(order!);
  261. });
  262. it('addItems negative quantity', async () => {
  263. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  264. GET_ORDER,
  265. {
  266. id: orderId,
  267. },
  268. );
  269. const { modifyOrder } = await adminClient.query<
  270. Codegen.ModifyOrderMutation,
  271. Codegen.ModifyOrderMutationVariables
  272. >(MODIFY_ORDER, {
  273. input: {
  274. dryRun: false,
  275. orderId,
  276. addItems: [{ productVariantId: 'T_3', quantity: -1 }],
  277. },
  278. });
  279. orderGuard.assertErrorResult(modifyOrder);
  280. expect(modifyOrder.errorCode).toBe(ErrorCode.NEGATIVE_QUANTITY_ERROR);
  281. await assertOrderIsUnchanged(order!);
  282. });
  283. it('adjustOrderLines negative quantity', async () => {
  284. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  285. GET_ORDER,
  286. {
  287. id: orderId,
  288. },
  289. );
  290. const { modifyOrder } = await adminClient.query<
  291. Codegen.ModifyOrderMutation,
  292. Codegen.ModifyOrderMutationVariables
  293. >(MODIFY_ORDER, {
  294. input: {
  295. dryRun: false,
  296. orderId,
  297. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: -1 }],
  298. },
  299. });
  300. orderGuard.assertErrorResult(modifyOrder);
  301. expect(modifyOrder.errorCode).toBe(ErrorCode.NEGATIVE_QUANTITY_ERROR);
  302. await assertOrderIsUnchanged(order!);
  303. });
  304. it('addItems insufficient stock', async () => {
  305. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  306. GET_ORDER,
  307. {
  308. id: orderId,
  309. },
  310. );
  311. const { modifyOrder } = await adminClient.query<
  312. Codegen.ModifyOrderMutation,
  313. Codegen.ModifyOrderMutationVariables
  314. >(MODIFY_ORDER, {
  315. input: {
  316. dryRun: false,
  317. orderId,
  318. addItems: [{ productVariantId: 'T_3', quantity: 500 }],
  319. },
  320. });
  321. orderGuard.assertErrorResult(modifyOrder);
  322. expect(modifyOrder.errorCode).toBe(ErrorCode.INSUFFICIENT_STOCK_ERROR);
  323. await assertOrderIsUnchanged(order!);
  324. });
  325. it('adjustOrderLines insufficient stock', async () => {
  326. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  327. GET_ORDER,
  328. {
  329. id: orderId,
  330. },
  331. );
  332. const { modifyOrder } = await adminClient.query<
  333. Codegen.ModifyOrderMutation,
  334. Codegen.ModifyOrderMutationVariables
  335. >(MODIFY_ORDER, {
  336. input: {
  337. dryRun: false,
  338. orderId,
  339. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 500 }],
  340. },
  341. });
  342. orderGuard.assertErrorResult(modifyOrder);
  343. expect(modifyOrder.errorCode).toBe(ErrorCode.INSUFFICIENT_STOCK_ERROR);
  344. await assertOrderIsUnchanged(order!);
  345. });
  346. it('addItems order limit', async () => {
  347. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  348. GET_ORDER,
  349. {
  350. id: orderId,
  351. },
  352. );
  353. const { modifyOrder } = await adminClient.query<
  354. Codegen.ModifyOrderMutation,
  355. Codegen.ModifyOrderMutationVariables
  356. >(MODIFY_ORDER, {
  357. input: {
  358. dryRun: false,
  359. orderId,
  360. addItems: [{ productVariantId: 'T_4', quantity: 9999 }],
  361. },
  362. });
  363. orderGuard.assertErrorResult(modifyOrder);
  364. expect(modifyOrder.errorCode).toBe(ErrorCode.ORDER_LIMIT_ERROR);
  365. await assertOrderIsUnchanged(order!);
  366. });
  367. it('adjustOrderLines order limit', async () => {
  368. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  369. GET_ORDER,
  370. {
  371. id: orderId,
  372. },
  373. );
  374. const { modifyOrder } = await adminClient.query<
  375. Codegen.ModifyOrderMutation,
  376. Codegen.ModifyOrderMutationVariables
  377. >(MODIFY_ORDER, {
  378. input: {
  379. dryRun: false,
  380. orderId,
  381. adjustOrderLines: [{ orderLineId: order!.lines[1].id, quantity: 9999 }],
  382. },
  383. });
  384. orderGuard.assertErrorResult(modifyOrder);
  385. expect(modifyOrder.errorCode).toBe(ErrorCode.ORDER_LIMIT_ERROR);
  386. await assertOrderIsUnchanged(order!);
  387. });
  388. });
  389. describe('dry run', () => {
  390. it('addItems', async () => {
  391. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  392. GET_ORDER,
  393. {
  394. id: orderId,
  395. },
  396. );
  397. const { modifyOrder } = await adminClient.query<
  398. Codegen.ModifyOrderMutation,
  399. Codegen.ModifyOrderMutationVariables
  400. >(MODIFY_ORDER, {
  401. input: {
  402. dryRun: true,
  403. orderId,
  404. addItems: [{ productVariantId: 'T_5', quantity: 1 }],
  405. },
  406. });
  407. orderGuard.assertSuccess(modifyOrder);
  408. const expectedTotal = order!.totalWithTax + Math.round(14374 * 1.2); // price of variant T_5
  409. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  410. expect(modifyOrder.lines.length).toBe(order!.lines.length + 1);
  411. await assertOrderIsUnchanged(order!);
  412. });
  413. it('addItems with existing variant id increments existing OrderLine', async () => {
  414. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  415. GET_ORDER,
  416. {
  417. id: orderId,
  418. },
  419. );
  420. const { modifyOrder } = await adminClient.query<
  421. Codegen.ModifyOrderMutation,
  422. Codegen.ModifyOrderMutationVariables
  423. >(MODIFY_ORDER, {
  424. input: {
  425. dryRun: true,
  426. orderId,
  427. addItems: [
  428. { productVariantId: 'T_1', quantity: 1, customFields: { color: 'green' } } as any,
  429. ],
  430. },
  431. });
  432. orderGuard.assertSuccess(modifyOrder);
  433. const lineT1 = modifyOrder.lines.find(l => l.productVariant.id === 'T_1');
  434. expect(modifyOrder.lines.length).toBe(2);
  435. expect(lineT1?.quantity).toBe(2);
  436. await assertOrderIsUnchanged(order!);
  437. });
  438. it('addItems with existing variant id but different customFields adds new OrderLine', async () => {
  439. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  440. GET_ORDER,
  441. {
  442. id: orderId,
  443. },
  444. );
  445. const { modifyOrder } = await adminClient.query<
  446. Codegen.ModifyOrderMutation,
  447. Codegen.ModifyOrderMutationVariables
  448. >(MODIFY_ORDER, {
  449. input: {
  450. dryRun: true,
  451. orderId,
  452. addItems: [
  453. { productVariantId: 'T_1', quantity: 1, customFields: { color: 'blue' } } as any,
  454. ],
  455. },
  456. });
  457. orderGuard.assertSuccess(modifyOrder);
  458. const lineT1 = modifyOrder.lines.find(l => l.productVariant.id === 'T_1');
  459. expect(modifyOrder.lines.length).toBe(3);
  460. expect(
  461. modifyOrder.lines.map(l => ({ variantId: l.productVariant.id, quantity: l.quantity })),
  462. ).toEqual([
  463. { variantId: 'T_1', quantity: 1 },
  464. { variantId: 'T_4', quantity: 2 },
  465. { variantId: 'T_1', quantity: 1 },
  466. ]);
  467. await assertOrderIsUnchanged(order!);
  468. });
  469. it('adjustOrderLines up', async () => {
  470. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  471. GET_ORDER,
  472. {
  473. id: orderId,
  474. },
  475. );
  476. const { modifyOrder } = await adminClient.query<
  477. Codegen.ModifyOrderMutation,
  478. Codegen.ModifyOrderMutationVariables
  479. >(MODIFY_ORDER, {
  480. input: {
  481. dryRun: true,
  482. orderId,
  483. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 3 }],
  484. },
  485. });
  486. orderGuard.assertSuccess(modifyOrder);
  487. const expectedTotal = order!.totalWithTax + order!.lines[0].unitPriceWithTax * 2;
  488. expect(modifyOrder.lines[0].items.length).toBe(3);
  489. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  490. await assertOrderIsUnchanged(order!);
  491. });
  492. it('adjustOrderLines down', async () => {
  493. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  494. GET_ORDER,
  495. {
  496. id: orderId,
  497. },
  498. );
  499. const { modifyOrder } = await adminClient.query<
  500. Codegen.ModifyOrderMutation,
  501. Codegen.ModifyOrderMutationVariables
  502. >(MODIFY_ORDER, {
  503. input: {
  504. dryRun: true,
  505. orderId,
  506. adjustOrderLines: [{ orderLineId: order!.lines[1].id, quantity: 1 }],
  507. },
  508. });
  509. orderGuard.assertSuccess(modifyOrder);
  510. const expectedTotal = order!.totalWithTax - order!.lines[1].unitPriceWithTax;
  511. expect(modifyOrder.lines[1].items.filter(i => i.cancelled).length).toBe(1);
  512. expect(modifyOrder.lines[1].items.filter(i => !i.cancelled).length).toBe(1);
  513. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  514. await assertOrderIsUnchanged(order!);
  515. });
  516. it('adjustOrderLines to zero', async () => {
  517. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  518. GET_ORDER,
  519. {
  520. id: orderId,
  521. },
  522. );
  523. const { modifyOrder } = await adminClient.query<
  524. Codegen.ModifyOrderMutation,
  525. Codegen.ModifyOrderMutationVariables
  526. >(MODIFY_ORDER, {
  527. input: {
  528. dryRun: true,
  529. orderId,
  530. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 0 }],
  531. },
  532. });
  533. orderGuard.assertSuccess(modifyOrder);
  534. const expectedTotal = order!.totalWithTax - order!.lines[0].linePriceWithTax;
  535. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  536. expect(modifyOrder.lines[0].items.every(i => i.cancelled)).toBe(true);
  537. await assertOrderIsUnchanged(order!);
  538. });
  539. it('surcharge positive', async () => {
  540. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  541. GET_ORDER,
  542. {
  543. id: orderId,
  544. },
  545. );
  546. const { modifyOrder } = await adminClient.query<
  547. Codegen.ModifyOrderMutation,
  548. Codegen.ModifyOrderMutationVariables
  549. >(MODIFY_ORDER, {
  550. input: {
  551. dryRun: true,
  552. orderId,
  553. surcharges: [
  554. {
  555. description: 'extra fee',
  556. sku: '123',
  557. price: 300,
  558. priceIncludesTax: true,
  559. taxRate: 20,
  560. taxDescription: 'VAT',
  561. },
  562. ],
  563. },
  564. });
  565. orderGuard.assertSuccess(modifyOrder);
  566. const expectedTotal = order!.totalWithTax + 300;
  567. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  568. expect(modifyOrder.surcharges.map(s => omit(s, ['id']))).toEqual([
  569. {
  570. description: 'extra fee',
  571. sku: '123',
  572. price: 250,
  573. priceWithTax: 300,
  574. taxRate: 20,
  575. },
  576. ]);
  577. await assertOrderIsUnchanged(order!);
  578. });
  579. it('surcharge negative', async () => {
  580. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  581. GET_ORDER,
  582. {
  583. id: orderId,
  584. },
  585. );
  586. const { modifyOrder } = await adminClient.query<
  587. Codegen.ModifyOrderMutation,
  588. Codegen.ModifyOrderMutationVariables
  589. >(MODIFY_ORDER, {
  590. input: {
  591. dryRun: true,
  592. orderId,
  593. surcharges: [
  594. {
  595. description: 'special discount',
  596. sku: '123',
  597. price: -300,
  598. priceIncludesTax: true,
  599. taxRate: 20,
  600. taxDescription: 'VAT',
  601. },
  602. ],
  603. },
  604. });
  605. orderGuard.assertSuccess(modifyOrder);
  606. const expectedTotal = order!.totalWithTax + -300;
  607. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  608. expect(modifyOrder.surcharges.map(s => omit(s, ['id']))).toEqual([
  609. {
  610. description: 'special discount',
  611. sku: '123',
  612. price: -250,
  613. priceWithTax: -300,
  614. taxRate: 20,
  615. },
  616. ]);
  617. await assertOrderIsUnchanged(order!);
  618. });
  619. it('does not add a history entry', async () => {
  620. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  621. GET_ORDER,
  622. {
  623. id: orderId,
  624. },
  625. );
  626. const { modifyOrder } = await adminClient.query<
  627. Codegen.ModifyOrderMutation,
  628. Codegen.ModifyOrderMutationVariables
  629. >(MODIFY_ORDER, {
  630. input: {
  631. dryRun: true,
  632. orderId,
  633. addItems: [{ productVariantId: 'T_5', quantity: 1 }],
  634. },
  635. });
  636. orderGuard.assertSuccess(modifyOrder);
  637. const { order: history } = await adminClient.query<
  638. Codegen.GetOrderHistoryQuery,
  639. Codegen.GetOrderHistoryQueryVariables
  640. >(GET_ORDER_HISTORY, {
  641. id: orderId,
  642. options: { filter: { type: { eq: HistoryEntryType.ORDER_MODIFIED } } },
  643. });
  644. orderGuard.assertSuccess(history);
  645. expect(history.history.totalItems).toBe(0);
  646. });
  647. });
  648. describe('wet run', () => {
  649. async function assertModifiedOrderIsPersisted(order: OrderWithModificationsFragment) {
  650. const { order: order2 } = await adminClient.query<
  651. Codegen.GetOrderQuery,
  652. Codegen.GetOrderQueryVariables
  653. >(GET_ORDER, {
  654. id: order.id,
  655. });
  656. expect(order2!.totalWithTax).toBe(order!.totalWithTax);
  657. expect(order2!.lines.length).toBe(order!.lines.length);
  658. expect(order2!.surcharges.length).toBe(order!.surcharges.length);
  659. expect(order2!.payments!.length).toBe(order!.payments!.length);
  660. expect(order2!.payments!.map(p => pick(p, ['id', 'amount', 'method']))).toEqual(
  661. order!.payments!.map(p => pick(p, ['id', 'amount', 'method'])),
  662. );
  663. }
  664. it('addItems', async () => {
  665. const order = await createOrderAndTransitionToModifyingState([
  666. {
  667. productVariantId: 'T_1',
  668. quantity: 1,
  669. },
  670. ]);
  671. const { modifyOrder } = await adminClient.query<
  672. Codegen.ModifyOrderMutation,
  673. Codegen.ModifyOrderMutationVariables
  674. >(MODIFY_ORDER, {
  675. input: {
  676. dryRun: false,
  677. orderId: order.id,
  678. addItems: [{ productVariantId: 'T_5', quantity: 1 }],
  679. },
  680. });
  681. orderGuard.assertSuccess(modifyOrder);
  682. const priceDelta = Math.round(14374 * 1.2); // price of variant T_5
  683. const expectedTotal = order!.totalWithTax + priceDelta;
  684. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  685. expect(modifyOrder.lines.length).toBe(order!.lines.length + 1);
  686. expect(modifyOrder.modifications.length).toBe(1);
  687. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  688. expect(modifyOrder.modifications[0].orderItems?.length).toBe(1);
  689. expect(modifyOrder.modifications[0].orderItems?.map(i => i.id)).toEqual([
  690. modifyOrder.lines[1].items[0].id,
  691. ]);
  692. await assertModifiedOrderIsPersisted(modifyOrder);
  693. });
  694. it('adjustOrderLines up', async () => {
  695. const order = await createOrderAndTransitionToModifyingState([
  696. {
  697. productVariantId: 'T_1',
  698. quantity: 1,
  699. },
  700. ]);
  701. const { modifyOrder } = await adminClient.query<
  702. Codegen.ModifyOrderMutation,
  703. Codegen.ModifyOrderMutationVariables
  704. >(MODIFY_ORDER, {
  705. input: {
  706. dryRun: false,
  707. orderId: order.id,
  708. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 2 }],
  709. },
  710. });
  711. orderGuard.assertSuccess(modifyOrder);
  712. const priceDelta = order!.lines[0].unitPriceWithTax;
  713. const expectedTotal = order!.totalWithTax + priceDelta;
  714. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  715. expect(modifyOrder.lines[0].quantity).toBe(2);
  716. expect(modifyOrder.modifications.length).toBe(1);
  717. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  718. expect(modifyOrder.modifications[0].orderItems?.length).toBe(1);
  719. expect(
  720. modifyOrder.lines[0].items
  721. .map(i => i.id)
  722. .includes(modifyOrder.modifications?.[0].orderItems?.[0].id as string),
  723. ).toBe(true);
  724. await assertModifiedOrderIsPersisted(modifyOrder);
  725. });
  726. it('adjustOrderLines down', async () => {
  727. const order = await createOrderAndTransitionToModifyingState([
  728. {
  729. productVariantId: 'T_1',
  730. quantity: 2,
  731. },
  732. ]);
  733. const { modifyOrder } = await adminClient.query<
  734. Codegen.ModifyOrderMutation,
  735. Codegen.ModifyOrderMutationVariables
  736. >(MODIFY_ORDER, {
  737. input: {
  738. dryRun: false,
  739. orderId: order.id,
  740. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 1 }],
  741. refund: { paymentId: order!.payments![0].id },
  742. },
  743. });
  744. orderGuard.assertSuccess(modifyOrder);
  745. const priceDelta = -order!.lines[0].unitPriceWithTax;
  746. const expectedTotal = order!.totalWithTax + priceDelta;
  747. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  748. expect(modifyOrder.lines[0].quantity).toBe(1);
  749. expect(modifyOrder.payments?.length).toBe(1);
  750. expect(modifyOrder.payments?.[0].refunds.length).toBe(1);
  751. expect(modifyOrder.payments?.[0].refunds[0]).toEqual({
  752. id: 'T_1',
  753. state: 'Pending',
  754. total: -priceDelta,
  755. paymentId: modifyOrder.payments?.[0].id,
  756. });
  757. expect(modifyOrder.modifications.length).toBe(1);
  758. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  759. expect(modifyOrder.modifications[0].surcharges).toEqual(modifyOrder.surcharges.map(pick(['id'])));
  760. expect(modifyOrder.modifications[0].orderItems?.length).toBe(1);
  761. expect(
  762. modifyOrder.lines[0].items
  763. .map(i => i.id)
  764. .includes(modifyOrder.modifications?.[0].orderItems?.[0].id as string),
  765. ).toBe(true);
  766. await assertModifiedOrderIsPersisted(modifyOrder);
  767. });
  768. it('adjustOrderLines with changed customField value', async () => {
  769. const order = await createOrderAndTransitionToModifyingState([
  770. {
  771. productVariantId: 'T_1',
  772. quantity: 1,
  773. customFields: {
  774. color: 'green',
  775. },
  776. },
  777. ]);
  778. const { modifyOrder } = await adminClient.query<
  779. Codegen.ModifyOrderMutation,
  780. Codegen.ModifyOrderMutationVariables
  781. >(MODIFY_ORDER, {
  782. input: {
  783. dryRun: false,
  784. orderId: order.id,
  785. adjustOrderLines: [
  786. {
  787. orderLineId: order!.lines[0].id,
  788. quantity: 1,
  789. customFields: { color: 'black' },
  790. } as any,
  791. ],
  792. },
  793. });
  794. orderGuard.assertSuccess(modifyOrder);
  795. expect(modifyOrder.lines.length).toBe(1);
  796. const { order: orderWithLines } = await adminClient.query(gql(GET_ORDER_WITH_CUSTOM_FIELDS), {
  797. id: order.id,
  798. });
  799. expect(orderWithLines.lines[0]).toEqual({
  800. id: order!.lines[0].id,
  801. customFields: { color: 'black' },
  802. });
  803. });
  804. it('adjustOrderLines handles quantity correctly', async () => {
  805. await adminClient.query<
  806. Codegen.UpdateProductVariantsMutation,
  807. Codegen.UpdateProductVariantsMutationVariables
  808. >(UPDATE_PRODUCT_VARIANTS, {
  809. input: [
  810. {
  811. id: 'T_6',
  812. stockOnHand: 1,
  813. trackInventory: GlobalFlag.TRUE,
  814. },
  815. ],
  816. });
  817. const order = await createOrderAndTransitionToModifyingState([
  818. {
  819. productVariantId: 'T_6',
  820. quantity: 1,
  821. },
  822. ]);
  823. const { modifyOrder } = await adminClient.query<
  824. Codegen.ModifyOrderMutation,
  825. Codegen.ModifyOrderMutationVariables
  826. >(MODIFY_ORDER, {
  827. input: {
  828. dryRun: false,
  829. orderId: order.id,
  830. adjustOrderLines: [
  831. {
  832. orderLineId: order.lines[0].id,
  833. quantity: 1,
  834. },
  835. ],
  836. updateShippingAddress: {
  837. fullName: 'Jim',
  838. },
  839. },
  840. });
  841. orderGuard.assertSuccess(modifyOrder);
  842. });
  843. it('surcharge positive', async () => {
  844. const order = await createOrderAndTransitionToModifyingState([
  845. {
  846. productVariantId: 'T_1',
  847. quantity: 1,
  848. },
  849. ]);
  850. const { modifyOrder } = await adminClient.query<
  851. Codegen.ModifyOrderMutation,
  852. Codegen.ModifyOrderMutationVariables
  853. >(MODIFY_ORDER, {
  854. input: {
  855. dryRun: false,
  856. orderId: order.id,
  857. surcharges: [
  858. {
  859. description: 'extra fee',
  860. sku: '123',
  861. price: 300,
  862. priceIncludesTax: true,
  863. taxRate: 20,
  864. taxDescription: 'VAT',
  865. },
  866. ],
  867. },
  868. });
  869. orderGuard.assertSuccess(modifyOrder);
  870. const priceDelta = 300;
  871. const expectedTotal = order!.totalWithTax + priceDelta;
  872. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  873. expect(modifyOrder.surcharges.map(s => omit(s, ['id']))).toEqual([
  874. {
  875. description: 'extra fee',
  876. sku: '123',
  877. price: 250,
  878. priceWithTax: 300,
  879. taxRate: 20,
  880. },
  881. ]);
  882. expect(modifyOrder.modifications.length).toBe(1);
  883. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  884. expect(modifyOrder.modifications[0].surcharges).toEqual(modifyOrder.surcharges.map(pick(['id'])));
  885. await assertModifiedOrderIsPersisted(modifyOrder);
  886. });
  887. it('surcharge negative', async () => {
  888. const order = await createOrderAndTransitionToModifyingState([
  889. {
  890. productVariantId: 'T_1',
  891. quantity: 1,
  892. },
  893. ]);
  894. const { modifyOrder } = await adminClient.query<
  895. Codegen.ModifyOrderMutation,
  896. Codegen.ModifyOrderMutationVariables
  897. >(MODIFY_ORDER, {
  898. input: {
  899. dryRun: false,
  900. orderId: order!.id,
  901. surcharges: [
  902. {
  903. description: 'special discount',
  904. sku: '123',
  905. price: -300,
  906. priceIncludesTax: true,
  907. taxRate: 20,
  908. taxDescription: 'VAT',
  909. },
  910. ],
  911. refund: {
  912. paymentId: order!.payments![0].id,
  913. },
  914. },
  915. });
  916. orderGuard.assertSuccess(modifyOrder);
  917. const expectedTotal = order!.totalWithTax + -300;
  918. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  919. expect(modifyOrder.surcharges.map(s => omit(s, ['id']))).toEqual([
  920. {
  921. description: 'special discount',
  922. sku: '123',
  923. price: -250,
  924. priceWithTax: -300,
  925. taxRate: 20,
  926. },
  927. ]);
  928. expect(modifyOrder.modifications.length).toBe(1);
  929. expect(modifyOrder.modifications[0].priceChange).toBe(-300);
  930. await assertModifiedOrderIsPersisted(modifyOrder);
  931. });
  932. it('update updateShippingAddress, recalculate shipping', async () => {
  933. const order = await createOrderAndTransitionToModifyingState([
  934. {
  935. productVariantId: 'T_1',
  936. quantity: 1,
  937. },
  938. ]);
  939. const { modifyOrder } = await adminClient.query<
  940. Codegen.ModifyOrderMutation,
  941. Codegen.ModifyOrderMutationVariables
  942. >(MODIFY_ORDER, {
  943. input: {
  944. dryRun: false,
  945. orderId: order!.id,
  946. updateShippingAddress: {
  947. countryCode: 'US',
  948. },
  949. options: {
  950. recalculateShipping: true,
  951. },
  952. },
  953. });
  954. orderGuard.assertSuccess(modifyOrder);
  955. const priceDelta = SHIPPING_US - SHIPPING_OTHER;
  956. const expectedTotal = order!.totalWithTax + priceDelta;
  957. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  958. expect(modifyOrder.shippingAddress?.countryCode).toBe('US');
  959. expect(modifyOrder.modifications.length).toBe(1);
  960. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  961. await assertModifiedOrderIsPersisted(modifyOrder);
  962. });
  963. it('update updateShippingAddress, do not recalculate shipping', async () => {
  964. const order = await createOrderAndTransitionToModifyingState([
  965. {
  966. productVariantId: 'T_1',
  967. quantity: 1,
  968. },
  969. ]);
  970. const { modifyOrder } = await adminClient.query<
  971. Codegen.ModifyOrderMutation,
  972. Codegen.ModifyOrderMutationVariables
  973. >(MODIFY_ORDER, {
  974. input: {
  975. dryRun: false,
  976. orderId: order!.id,
  977. updateShippingAddress: {
  978. countryCode: 'US',
  979. },
  980. options: {
  981. recalculateShipping: false,
  982. },
  983. },
  984. });
  985. orderGuard.assertSuccess(modifyOrder);
  986. const priceDelta = 0;
  987. const expectedTotal = order!.totalWithTax + priceDelta;
  988. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  989. expect(modifyOrder.shippingAddress?.countryCode).toBe('US');
  990. expect(modifyOrder.modifications.length).toBe(1);
  991. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  992. await assertModifiedOrderIsPersisted(modifyOrder);
  993. });
  994. it('update Order customFields', async () => {
  995. const order = await createOrderAndTransitionToModifyingState([
  996. {
  997. productVariantId: 'T_1',
  998. quantity: 1,
  999. },
  1000. ]);
  1001. const { modifyOrder } = await adminClient.query<
  1002. Codegen.ModifyOrderMutation,
  1003. Codegen.ModifyOrderMutationVariables
  1004. >(MODIFY_ORDER, {
  1005. input: {
  1006. dryRun: false,
  1007. orderId: order.id,
  1008. customFields: {
  1009. points: 42,
  1010. },
  1011. } as any,
  1012. });
  1013. orderGuard.assertSuccess(modifyOrder);
  1014. const { order: orderWithCustomFields } = await adminClient.query(
  1015. gql(GET_ORDER_WITH_CUSTOM_FIELDS),
  1016. { id: order.id },
  1017. );
  1018. expect(orderWithCustomFields.customFields).toEqual({
  1019. points: 42,
  1020. });
  1021. });
  1022. it('adds a history entry', async () => {
  1023. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  1024. GET_ORDER,
  1025. {
  1026. id: orderId,
  1027. },
  1028. );
  1029. const { modifyOrder } = await adminClient.query<
  1030. Codegen.ModifyOrderMutation,
  1031. Codegen.ModifyOrderMutationVariables
  1032. >(MODIFY_ORDER, {
  1033. input: {
  1034. dryRun: false,
  1035. orderId: order!.id,
  1036. addItems: [{ productVariantId: 'T_5', quantity: 1 }],
  1037. },
  1038. });
  1039. orderGuard.assertSuccess(modifyOrder);
  1040. const { order: history } = await adminClient.query<
  1041. Codegen.GetOrderHistoryQuery,
  1042. Codegen.GetOrderHistoryQueryVariables
  1043. >(GET_ORDER_HISTORY, {
  1044. id: orderId,
  1045. options: { filter: { type: { eq: HistoryEntryType.ORDER_MODIFIED } } },
  1046. });
  1047. orderGuard.assertSuccess(history);
  1048. expect(history.history.totalItems).toBe(1);
  1049. expect(history.history.items[0].data).toEqual({
  1050. modificationId: modifyOrder.modifications[0].id,
  1051. });
  1052. });
  1053. });
  1054. describe('additional payment handling', () => {
  1055. let orderId2: string;
  1056. beforeAll(async () => {
  1057. const order = await createOrderAndTransitionToModifyingState([
  1058. {
  1059. productVariantId: 'T_1',
  1060. quantity: 1,
  1061. },
  1062. ]);
  1063. const { modifyOrder } = await adminClient.query<
  1064. Codegen.ModifyOrderMutation,
  1065. Codegen.ModifyOrderMutationVariables
  1066. >(MODIFY_ORDER, {
  1067. input: {
  1068. dryRun: false,
  1069. orderId: order.id,
  1070. surcharges: [
  1071. {
  1072. description: 'extra fee',
  1073. sku: '123',
  1074. price: 300,
  1075. priceIncludesTax: true,
  1076. taxRate: 20,
  1077. taxDescription: 'VAT',
  1078. },
  1079. ],
  1080. },
  1081. });
  1082. orderGuard.assertSuccess(modifyOrder);
  1083. orderId2 = modifyOrder.id;
  1084. });
  1085. it('cannot transition back to original state if no payment is set', async () => {
  1086. const transitionOrderToState = await adminTransitionOrderToState(orderId2, 'PaymentSettled');
  1087. orderGuard.assertErrorResult(transitionOrderToState);
  1088. expect(transitionOrderToState!.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
  1089. expect(transitionOrderToState!.transitionError).toBe(
  1090. `Can only transition to the "ArrangingAdditionalPayment" state`,
  1091. );
  1092. });
  1093. it('can transition to ArrangingAdditionalPayment state', async () => {
  1094. const transitionOrderToState = await adminTransitionOrderToState(
  1095. orderId2,
  1096. 'ArrangingAdditionalPayment',
  1097. );
  1098. orderGuard.assertSuccess(transitionOrderToState);
  1099. expect(transitionOrderToState!.state).toBe('ArrangingAdditionalPayment');
  1100. });
  1101. it('cannot transition from ArrangingAdditionalPayment when total not covered by Payments', async () => {
  1102. const transitionOrderToState = await adminTransitionOrderToState(orderId2, 'PaymentSettled');
  1103. orderGuard.assertErrorResult(transitionOrderToState);
  1104. expect(transitionOrderToState!.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
  1105. expect(transitionOrderToState!.transitionError).toBe(
  1106. `Cannot transition away from "ArrangingAdditionalPayment" unless Order total is covered by Payments`,
  1107. );
  1108. });
  1109. it('addManualPaymentToOrder', async () => {
  1110. const { addManualPaymentToOrder } = await adminClient.query<
  1111. Codegen.AddManualPaymentMutation,
  1112. Codegen.AddManualPaymentMutationVariables
  1113. >(ADD_MANUAL_PAYMENT, {
  1114. input: {
  1115. orderId: orderId2,
  1116. method: 'test',
  1117. transactionId: 'ABC123',
  1118. metadata: {
  1119. foo: 'bar',
  1120. },
  1121. },
  1122. });
  1123. orderGuard.assertSuccess(addManualPaymentToOrder);
  1124. expect(addManualPaymentToOrder.payments?.length).toBe(2);
  1125. expect(omit(addManualPaymentToOrder.payments![1], ['id'])).toEqual({
  1126. transactionId: 'ABC123',
  1127. state: 'Settled',
  1128. amount: 300,
  1129. method: 'test',
  1130. metadata: {
  1131. foo: 'bar',
  1132. },
  1133. refunds: [],
  1134. });
  1135. expect(addManualPaymentToOrder.modifications[0].isSettled).toBe(true);
  1136. expect(addManualPaymentToOrder.modifications[0].payment?.id).toBe(
  1137. addManualPaymentToOrder.payments![1].id,
  1138. );
  1139. });
  1140. it('transition back to original state', async () => {
  1141. const transitionOrderToState = await adminTransitionOrderToState(orderId2, 'PaymentSettled');
  1142. orderGuard.assertSuccess(transitionOrderToState);
  1143. expect(transitionOrderToState.state).toBe('PaymentSettled');
  1144. });
  1145. });
  1146. describe('refund handling', () => {
  1147. let orderId3: string;
  1148. beforeAll(async () => {
  1149. const order = await createOrderAndTransitionToModifyingState([
  1150. {
  1151. productVariantId: 'T_1',
  1152. quantity: 1,
  1153. },
  1154. ]);
  1155. const { modifyOrder } = await adminClient.query<
  1156. Codegen.ModifyOrderMutation,
  1157. Codegen.ModifyOrderMutationVariables
  1158. >(MODIFY_ORDER, {
  1159. input: {
  1160. dryRun: false,
  1161. orderId: order.id,
  1162. surcharges: [
  1163. {
  1164. description: 'discount',
  1165. sku: '123',
  1166. price: -300,
  1167. priceIncludesTax: true,
  1168. taxRate: 20,
  1169. taxDescription: 'VAT',
  1170. },
  1171. ],
  1172. refund: {
  1173. paymentId: order.payments![0].id,
  1174. reason: 'discount',
  1175. },
  1176. },
  1177. });
  1178. orderGuard.assertSuccess(modifyOrder);
  1179. orderId3 = modifyOrder.id;
  1180. });
  1181. it('modification is settled', async () => {
  1182. const { order } = await adminClient.query<
  1183. Codegen.GetOrderWithModificationsQuery,
  1184. Codegen.GetOrderWithModificationsQueryVariables
  1185. >(GET_ORDER_WITH_MODIFICATIONS, { id: orderId3 });
  1186. expect(order?.modifications.length).toBe(1);
  1187. expect(order?.modifications[0].isSettled).toBe(true);
  1188. });
  1189. it('cannot transition to ArrangingAdditionalPayment state if no payment is needed', async () => {
  1190. const transitionOrderToState = await adminTransitionOrderToState(
  1191. orderId3,
  1192. 'ArrangingAdditionalPayment',
  1193. );
  1194. orderGuard.assertErrorResult(transitionOrderToState);
  1195. expect(transitionOrderToState!.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
  1196. expect(transitionOrderToState!.transitionError).toBe(
  1197. `Cannot transition Order to the \"ArrangingAdditionalPayment\" state as no additional payments are needed`,
  1198. );
  1199. });
  1200. it('can transition to original state', async () => {
  1201. const transitionOrderToState = await adminTransitionOrderToState(orderId3, 'PaymentSettled');
  1202. orderGuard.assertSuccess(transitionOrderToState);
  1203. expect(transitionOrderToState!.state).toBe('PaymentSettled');
  1204. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  1205. GET_ORDER,
  1206. {
  1207. id: orderId3,
  1208. },
  1209. );
  1210. expect(order?.payments![0].refunds.length).toBe(1);
  1211. expect(order?.payments![0].refunds[0].total).toBe(300);
  1212. expect(order?.payments![0].refunds[0].reason).toBe('discount');
  1213. });
  1214. });
  1215. // https://github.com/vendure-ecommerce/vendure/issues/1753
  1216. describe('refunds for multiple payments', () => {
  1217. let orderId2: string;
  1218. let orderLineId: string;
  1219. let additionalPaymentId: string;
  1220. beforeAll(async () => {
  1221. await adminClient.query<Codegen.CreatePromotionMutation, Codegen.CreatePromotionMutationVariables>(CREATE_PROMOTION, {
  1222. input: {
  1223. name: '$5 off',
  1224. couponCode: '5OFF',
  1225. enabled: true,
  1226. conditions: [],
  1227. actions: [
  1228. {
  1229. code: orderFixedDiscount.code,
  1230. arguments: [{ name: 'discount', value: '500' }],
  1231. },
  1232. ],
  1233. },
  1234. });
  1235. await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
  1236. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1237. productVariantId: 'T_5',
  1238. quantity: 1,
  1239. } as any);
  1240. await proceedToArrangingPayment(shopClient);
  1241. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1242. orderGuard.assertSuccess(order);
  1243. orderLineId = order.lines[0].id;
  1244. orderId2 = order.id;
  1245. const transitionOrderToState = await adminTransitionOrderToState(orderId2, 'Modifying');
  1246. orderGuard.assertSuccess(transitionOrderToState);
  1247. const { modifyOrder } = await adminClient.query<Codegen.ModifyOrderMutation, Codegen.ModifyOrderMutationVariables>(
  1248. MODIFY_ORDER,
  1249. {
  1250. input: {
  1251. dryRun: false,
  1252. orderId: orderId2,
  1253. adjustOrderLines: [{ orderLineId, quantity: 2 }],
  1254. },
  1255. },
  1256. );
  1257. orderGuard.assertSuccess(modifyOrder);
  1258. await adminTransitionOrderToState(orderId2, 'ArrangingAdditionalPayment');
  1259. const { addManualPaymentToOrder } = await adminClient.query<
  1260. Codegen.AddManualPaymentMutation,
  1261. Codegen.AddManualPaymentMutationVariables
  1262. >(ADD_MANUAL_PAYMENT, {
  1263. input: {
  1264. orderId: orderId2,
  1265. method: 'test',
  1266. transactionId: 'ABC123',
  1267. metadata: {
  1268. foo: 'bar',
  1269. },
  1270. },
  1271. });
  1272. orderGuard.assertSuccess(addManualPaymentToOrder);
  1273. additionalPaymentId = addManualPaymentToOrder.payments?.[1].id!;
  1274. const transitionOrderToState2 = await adminTransitionOrderToState(orderId2, 'PaymentSettled');
  1275. orderGuard.assertSuccess(transitionOrderToState2);
  1276. expect(transitionOrderToState2.state).toBe('PaymentSettled');
  1277. });
  1278. it('apply couponCode to create first refund', async () => {
  1279. const transitionOrderToState = await adminTransitionOrderToState(orderId2, 'Modifying');
  1280. orderGuard.assertSuccess(transitionOrderToState);
  1281. const { modifyOrder } = await adminClient.query<Codegen.ModifyOrderMutation, Codegen.ModifyOrderMutationVariables>(
  1282. MODIFY_ORDER,
  1283. {
  1284. input: {
  1285. dryRun: false,
  1286. orderId: orderId2,
  1287. couponCodes: ['5OFF'],
  1288. refund: {
  1289. paymentId: additionalPaymentId,
  1290. reason: 'test',
  1291. },
  1292. },
  1293. },
  1294. );
  1295. orderGuard.assertSuccess(modifyOrder);
  1296. expect(modifyOrder.payments?.length).toBe(2);
  1297. expect(modifyOrder?.payments?.find(p => p.id === additionalPaymentId)?.refunds).toEqual([
  1298. {
  1299. id: 'T_4',
  1300. paymentId: additionalPaymentId,
  1301. state: 'Pending',
  1302. total: 600,
  1303. },
  1304. ]);
  1305. expect(modifyOrder.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder));
  1306. });
  1307. it('reduce quantity to create second refund', async () => {
  1308. const { modifyOrder } = await adminClient.query<Codegen.ModifyOrderMutation, Codegen.ModifyOrderMutationVariables>(
  1309. MODIFY_ORDER,
  1310. {
  1311. input: {
  1312. dryRun: false,
  1313. orderId: orderId2,
  1314. adjustOrderLines: [{ orderLineId, quantity: 1 }],
  1315. refund: {
  1316. paymentId: additionalPaymentId,
  1317. reason: 'test 2',
  1318. },
  1319. },
  1320. },
  1321. );
  1322. orderGuard.assertSuccess(modifyOrder);
  1323. expect(modifyOrder?.payments?.find(p => p.id === additionalPaymentId)?.refunds).toEqual([
  1324. {
  1325. id: 'T_4',
  1326. paymentId: additionalPaymentId,
  1327. state: 'Pending',
  1328. total: 600,
  1329. },
  1330. {
  1331. id: 'T_5',
  1332. paymentId: additionalPaymentId,
  1333. state: 'Pending',
  1334. total: 16649,
  1335. },
  1336. ]);
  1337. expect(modifyOrder?.payments?.find(p => p.id !== additionalPaymentId)?.refunds).toEqual([
  1338. {
  1339. id: 'T_6',
  1340. paymentId: 'T_15',
  1341. state: 'Pending',
  1342. total: 300,
  1343. },
  1344. ]);
  1345. expect(modifyOrder.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder));
  1346. });
  1347. });
  1348. // https://github.com/vendure-ecommerce/vendure/issues/688 - 4th point
  1349. it('correct additional payment when discounts applied', async () => {
  1350. await adminClient.query<Codegen.CreatePromotionMutation, Codegen.CreatePromotionMutationVariables>(
  1351. CREATE_PROMOTION,
  1352. {
  1353. input: {
  1354. name: '$5 off',
  1355. couponCode: '5OFF',
  1356. enabled: true,
  1357. conditions: [],
  1358. actions: [
  1359. {
  1360. code: orderFixedDiscount.code,
  1361. arguments: [{ name: 'discount', value: '500' }],
  1362. },
  1363. ],
  1364. },
  1365. },
  1366. );
  1367. await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
  1368. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1369. productVariantId: 'T_1',
  1370. quantity: 1,
  1371. } as any);
  1372. await shopClient.query<
  1373. CodegenShop.ApplyCouponCodeMutation,
  1374. CodegenShop.ApplyCouponCodeMutationVariables
  1375. >(APPLY_COUPON_CODE, {
  1376. couponCode: '5OFF',
  1377. });
  1378. await proceedToArrangingPayment(shopClient);
  1379. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1380. orderGuard.assertSuccess(order);
  1381. const originalTotalWithTax = order.totalWithTax;
  1382. const surcharge = 300;
  1383. const transitionOrderToState = await adminTransitionOrderToState(order.id, 'Modifying');
  1384. orderGuard.assertSuccess(transitionOrderToState);
  1385. expect(transitionOrderToState.state).toBe('Modifying');
  1386. const { modifyOrder } = await adminClient.query<
  1387. Codegen.ModifyOrderMutation,
  1388. Codegen.ModifyOrderMutationVariables
  1389. >(MODIFY_ORDER, {
  1390. input: {
  1391. dryRun: false,
  1392. orderId: order.id,
  1393. surcharges: [
  1394. {
  1395. description: 'extra fee',
  1396. sku: '123',
  1397. price: surcharge,
  1398. priceIncludesTax: true,
  1399. taxRate: 20,
  1400. taxDescription: 'VAT',
  1401. },
  1402. ],
  1403. },
  1404. });
  1405. orderGuard.assertSuccess(modifyOrder);
  1406. expect(modifyOrder.totalWithTax).toBe(originalTotalWithTax + surcharge);
  1407. });
  1408. // https://github.com/vendure-ecommerce/vendure/issues/872
  1409. describe('correct price calculations when prices include tax', () => {
  1410. async function modifyOrderLineQuantity(order: TestOrderWithPaymentsFragment) {
  1411. const transitionOrderToState = await adminTransitionOrderToState(order.id, 'Modifying');
  1412. orderGuard.assertSuccess(transitionOrderToState);
  1413. expect(transitionOrderToState.state).toBe('Modifying');
  1414. const { modifyOrder } = await adminClient.query<
  1415. Codegen.ModifyOrderMutation,
  1416. Codegen.ModifyOrderMutationVariables
  1417. >(MODIFY_ORDER, {
  1418. input: {
  1419. dryRun: true,
  1420. orderId: order.id,
  1421. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 2 }],
  1422. },
  1423. });
  1424. orderGuard.assertSuccess(modifyOrder);
  1425. return modifyOrder;
  1426. }
  1427. beforeAll(async () => {
  1428. await adminClient.query<Codegen.UpdateChannelMutation, Codegen.UpdateChannelMutationVariables>(
  1429. UPDATE_CHANNEL,
  1430. {
  1431. input: {
  1432. id: 'T_1',
  1433. pricesIncludeTax: true,
  1434. },
  1435. },
  1436. );
  1437. });
  1438. it('without promotion', async () => {
  1439. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  1440. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1441. productVariantId: 'T_1',
  1442. quantity: 1,
  1443. } as any);
  1444. await proceedToArrangingPayment(shopClient);
  1445. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1446. orderGuard.assertSuccess(order);
  1447. const modifyOrder = await modifyOrderLineQuantity(order);
  1448. expect(modifyOrder.lines[0].linePriceWithTax).toBe(order.lines[0].linePriceWithTax * 2);
  1449. });
  1450. it('with promotion', async () => {
  1451. await adminClient.query<
  1452. Codegen.CreatePromotionMutation,
  1453. Codegen.CreatePromotionMutationVariables
  1454. >(CREATE_PROMOTION, {
  1455. input: {
  1456. name: 'half price',
  1457. couponCode: 'HALF',
  1458. enabled: true,
  1459. conditions: [],
  1460. actions: [
  1461. {
  1462. code: productsPercentageDiscount.code,
  1463. arguments: [
  1464. { name: 'discount', value: '50' },
  1465. { name: 'productVariantIds', value: JSON.stringify(['T_1']) },
  1466. ],
  1467. },
  1468. ],
  1469. },
  1470. });
  1471. await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
  1472. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1473. productVariantId: 'T_1',
  1474. quantity: 1,
  1475. } as any);
  1476. await shopClient.query<
  1477. CodegenShop.ApplyCouponCodeMutation,
  1478. CodegenShop.ApplyCouponCodeMutationVariables
  1479. >(APPLY_COUPON_CODE, {
  1480. couponCode: 'HALF',
  1481. });
  1482. await proceedToArrangingPayment(shopClient);
  1483. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1484. orderGuard.assertSuccess(order);
  1485. const modifyOrder = await modifyOrderLineQuantity(order);
  1486. expect(modifyOrder.lines[0].discountedLinePriceWithTax).toBe(
  1487. modifyOrder.lines[0].linePriceWithTax / 2,
  1488. );
  1489. expect(modifyOrder.lines[0].linePriceWithTax).toBe(order.lines[0].linePriceWithTax * 2);
  1490. });
  1491. });
  1492. describe('refund handling when promotions are active on order', () => {
  1493. // https://github.com/vendure-ecommerce/vendure/issues/890
  1494. it('refunds correct amount when order-level promotion applied', async () => {
  1495. await adminClient.query<
  1496. Codegen.CreatePromotionMutation,
  1497. Codegen.CreatePromotionMutationVariables
  1498. >(CREATE_PROMOTION, {
  1499. input: {
  1500. name: '$5 off',
  1501. couponCode: '5OFF2',
  1502. enabled: true,
  1503. conditions: [],
  1504. actions: [
  1505. {
  1506. code: orderFixedDiscount.code,
  1507. arguments: [{ name: 'discount', value: '500' }],
  1508. },
  1509. ],
  1510. },
  1511. });
  1512. await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
  1513. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1514. productVariantId: 'T_1',
  1515. quantity: 2,
  1516. } as any);
  1517. await shopClient.query<
  1518. CodegenShop.ApplyCouponCodeMutation,
  1519. CodegenShop.ApplyCouponCodeMutationVariables
  1520. >(APPLY_COUPON_CODE, {
  1521. couponCode: '5OFF2',
  1522. });
  1523. await proceedToArrangingPayment(shopClient);
  1524. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1525. orderGuard.assertSuccess(order);
  1526. const originalTotalWithTax = order.totalWithTax;
  1527. const transitionOrderToState = await adminTransitionOrderToState(order.id, 'Modifying');
  1528. orderGuard.assertSuccess(transitionOrderToState);
  1529. expect(transitionOrderToState.state).toBe('Modifying');
  1530. const { modifyOrder } = await adminClient.query<
  1531. Codegen.ModifyOrderMutation,
  1532. Codegen.ModifyOrderMutationVariables
  1533. >(MODIFY_ORDER, {
  1534. input: {
  1535. dryRun: false,
  1536. orderId: order.id,
  1537. adjustOrderLines: [{ orderLineId: order.lines[0].id, quantity: 1 }],
  1538. refund: {
  1539. paymentId: order.payments![0].id,
  1540. reason: 'requested',
  1541. },
  1542. },
  1543. });
  1544. orderGuard.assertSuccess(modifyOrder);
  1545. expect(modifyOrder.totalWithTax).toBe(
  1546. originalTotalWithTax - order.lines[0].proratedUnitPriceWithTax,
  1547. );
  1548. expect(modifyOrder.payments![0].refunds![0].total).toBe(order.lines[0].proratedUnitPriceWithTax);
  1549. expect(modifyOrder.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder));
  1550. });
  1551. // github.com/vendure-ecommerce/vendure/issues/1865
  1552. describe('issue 1865', () => {
  1553. const promoDiscount = 5000;
  1554. let promoId: string;
  1555. let orderId2: string;
  1556. beforeAll(async () => {
  1557. const { createPromotion } = await adminClient.query<
  1558. Codegen.CreatePromotionMutation,
  1559. Codegen.CreatePromotionMutationVariables
  1560. >(CREATE_PROMOTION, {
  1561. input: {
  1562. name: '50 off orders over 100',
  1563. enabled: true,
  1564. conditions: [
  1565. {
  1566. code: minimumOrderAmount.code,
  1567. arguments: [
  1568. { name: 'amount', value: '10000' },
  1569. { name: 'taxInclusive', value: 'true' },
  1570. ],
  1571. },
  1572. ],
  1573. actions: [
  1574. {
  1575. code: orderFixedDiscount.code,
  1576. arguments: [{ name: 'discount', value: JSON.stringify(promoDiscount) }],
  1577. },
  1578. ],
  1579. },
  1580. });
  1581. promoId = (createPromotion as any).id;
  1582. });
  1583. afterAll(async () => {
  1584. await adminClient.query<Codegen.DeletePromotionMutation, Codegen.DeletePromotionMutationVariables>(
  1585. DELETE_PROMOTION,
  1586. {
  1587. id: promoId,
  1588. },
  1589. );
  1590. });
  1591. it('refund handling when order-level promotion becomes invalid on modification', async () => {
  1592. const { productVariants } = await adminClient.query<
  1593. Codegen.GetProductVariantListQuery,
  1594. Codegen.GetProductVariantListQueryVariables
  1595. >(GET_PRODUCT_VARIANT_LIST, {
  1596. options: {
  1597. filter: {
  1598. name: { contains: 'football' },
  1599. },
  1600. },
  1601. });
  1602. const football = productVariants.items[0];
  1603. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1604. productVariantId: football.id,
  1605. quantity: 2,
  1606. } as any);
  1607. await proceedToArrangingPayment(shopClient);
  1608. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1609. orderGuard.assertSuccess(order);
  1610. orderId2 = order.id;
  1611. expect(order.discounts.length).toBe(1);
  1612. expect(order.discounts[0].amountWithTax).toBe(-promoDiscount);
  1613. const shippingPrice = order.shippingWithTax;
  1614. const expectedTotal = football.priceWithTax * 2 + shippingPrice - promoDiscount;
  1615. expect(order.totalWithTax).toBe(expectedTotal);
  1616. const originalTotalWithTax = order.totalWithTax;
  1617. const transitionOrderToState = await adminTransitionOrderToState(order.id, 'Modifying');
  1618. orderGuard.assertSuccess(transitionOrderToState);
  1619. expect(transitionOrderToState.state).toBe('Modifying');
  1620. const { modifyOrder } = await adminClient.query<Codegen.ModifyOrderMutation, Codegen.ModifyOrderMutationVariables>(
  1621. MODIFY_ORDER,
  1622. {
  1623. input: {
  1624. dryRun: false,
  1625. orderId: order.id,
  1626. adjustOrderLines: [{ orderLineId: order.lines[0].id, quantity: 1 }],
  1627. refund: {
  1628. paymentId: order.payments![0].id,
  1629. reason: 'requested',
  1630. },
  1631. },
  1632. },
  1633. );
  1634. orderGuard.assertSuccess(modifyOrder);
  1635. const expectedNewTotal = order.lines[0].unitPriceWithTax + shippingPrice;
  1636. expect(modifyOrder.totalWithTax).toBe(expectedNewTotal);
  1637. expect(modifyOrder.payments![0].refunds![0].total).toBe(expectedTotal - expectedNewTotal);
  1638. expect(modifyOrder.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder));
  1639. });
  1640. it('transition back to original state', async () => {
  1641. const transitionOrderToState2 = await adminTransitionOrderToState(orderId2, 'PaymentSettled');
  1642. orderGuard.assertSuccess(transitionOrderToState2);
  1643. expect(transitionOrderToState2!.state).toBe('PaymentSettled');
  1644. });
  1645. it('order no longer has promotions', async () => {
  1646. const { order } = await adminClient.query<
  1647. Codegen.GetOrderWithModificationsQuery,
  1648. Codegen.GetOrderWithModificationsQueryVariables
  1649. >(GET_ORDER_WITH_MODIFICATIONS, { id: orderId2 });
  1650. expect(order?.promotions).toEqual([]);
  1651. });
  1652. it('order no longer has discounts', async () => {
  1653. const { order } = await adminClient.query<
  1654. Codegen.GetOrderWithModificationsQuery,
  1655. Codegen.GetOrderWithModificationsQueryVariables
  1656. >(GET_ORDER_WITH_MODIFICATIONS, { id: orderId2 });
  1657. expect(order?.discounts).toEqual([]);
  1658. });
  1659. });
  1660. });
  1661. // https://github.com/vendure-ecommerce/vendure/issues/1197
  1662. describe('refund on shipping when change made to shippingAddress', () => {
  1663. let order: OrderWithModificationsFragment;
  1664. beforeAll(async () => {
  1665. const createdOrder = await createOrderAndTransitionToModifyingState([
  1666. {
  1667. productVariantId: 'T_1',
  1668. quantity: 1,
  1669. },
  1670. ]);
  1671. const { modifyOrder } = await adminClient.query<
  1672. Codegen.ModifyOrderMutation,
  1673. Codegen.ModifyOrderMutationVariables
  1674. >(MODIFY_ORDER, {
  1675. input: {
  1676. dryRun: false,
  1677. orderId: createdOrder.id,
  1678. updateShippingAddress: {
  1679. countryCode: 'GB',
  1680. },
  1681. refund: {
  1682. paymentId: createdOrder.payments![0].id,
  1683. reason: 'discount',
  1684. },
  1685. },
  1686. });
  1687. orderGuard.assertSuccess(modifyOrder);
  1688. order = modifyOrder;
  1689. });
  1690. it('creates a Refund with the correct amount', async () => {
  1691. expect(order.payments?.[0].refunds[0].total).toBe(SHIPPING_OTHER - SHIPPING_GB);
  1692. });
  1693. it('allows transition to PaymentSettled', async () => {
  1694. const transitionOrderToState = await adminTransitionOrderToState(order.id, 'PaymentSettled');
  1695. orderGuard.assertSuccess(transitionOrderToState);
  1696. expect(transitionOrderToState.state).toBe('PaymentSettled');
  1697. });
  1698. });
  1699. // https://github.com/vendure-ecommerce/vendure/issues/1210
  1700. describe('updating stock levels', () => {
  1701. async function getVariant(id: 'T_1' | 'T_2' | 'T_3') {
  1702. const { product } = await adminClient.query<
  1703. Codegen.GetStockMovementQuery,
  1704. Codegen.GetStockMovementQueryVariables
  1705. >(GET_STOCK_MOVEMENT, {
  1706. id: 'T_1',
  1707. });
  1708. return product?.variants.find(v => v.id === id)!;
  1709. }
  1710. let orderId4: string;
  1711. let orderId5: string;
  1712. it('updates stock when increasing quantity before fulfillment', async () => {
  1713. const variant1 = await getVariant('T_2');
  1714. expect(variant1.stockOnHand).toBe(100);
  1715. expect(variant1.stockAllocated).toBe(0);
  1716. const order = await createOrderAndTransitionToModifyingState([
  1717. {
  1718. productVariantId: 'T_2',
  1719. quantity: 1,
  1720. },
  1721. ]);
  1722. orderId4 = order.id;
  1723. const variant2 = await getVariant('T_2');
  1724. expect(variant2.stockOnHand).toBe(100);
  1725. expect(variant2.stockAllocated).toBe(1);
  1726. const { modifyOrder } = await adminClient.query<
  1727. Codegen.ModifyOrderMutation,
  1728. Codegen.ModifyOrderMutationVariables
  1729. >(MODIFY_ORDER, {
  1730. input: {
  1731. dryRun: false,
  1732. orderId: order.id,
  1733. adjustOrderLines: [{ orderLineId: order.lines[0].id, quantity: 2 }],
  1734. },
  1735. });
  1736. orderGuard.assertSuccess(modifyOrder);
  1737. const variant3 = await getVariant('T_2');
  1738. expect(variant3.stockOnHand).toBe(100);
  1739. expect(variant3.stockAllocated).toBe(2);
  1740. });
  1741. it('updates stock when increasing quantity after fulfillment', async () => {
  1742. const result = await adminTransitionOrderToState(orderId4, 'ArrangingAdditionalPayment');
  1743. orderGuard.assertSuccess(result);
  1744. expect(result!.state).toBe('ArrangingAdditionalPayment');
  1745. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  1746. GET_ORDER,
  1747. {
  1748. id: orderId4,
  1749. },
  1750. );
  1751. const { addManualPaymentToOrder } = await adminClient.query<
  1752. Codegen.AddManualPaymentMutation,
  1753. Codegen.AddManualPaymentMutationVariables
  1754. >(ADD_MANUAL_PAYMENT, {
  1755. input: {
  1756. orderId: orderId4,
  1757. method: 'test',
  1758. transactionId: 'ABC123',
  1759. metadata: {
  1760. foo: 'bar',
  1761. },
  1762. },
  1763. });
  1764. orderGuard.assertSuccess(addManualPaymentToOrder);
  1765. await adminTransitionOrderToState(orderId4, 'PaymentSettled');
  1766. await adminClient.query<
  1767. Codegen.CreateFulfillmentMutation,
  1768. Codegen.CreateFulfillmentMutationVariables
  1769. >(CREATE_FULFILLMENT, {
  1770. input: {
  1771. lines: order?.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })) ?? [],
  1772. handler: {
  1773. code: manualFulfillmentHandler.code,
  1774. arguments: [
  1775. { name: 'method', value: 'test method' },
  1776. { name: 'trackingCode', value: 'ABC123' },
  1777. ],
  1778. },
  1779. },
  1780. });
  1781. const variant1 = await getVariant('T_2');
  1782. expect(variant1.stockOnHand).toBe(98);
  1783. expect(variant1.stockAllocated).toBe(0);
  1784. await adminTransitionOrderToState(orderId4, 'Modifying');
  1785. const { modifyOrder } = await adminClient.query<
  1786. Codegen.ModifyOrderMutation,
  1787. Codegen.ModifyOrderMutationVariables
  1788. >(MODIFY_ORDER, {
  1789. input: {
  1790. dryRun: false,
  1791. orderId: order!.id,
  1792. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 3 }],
  1793. },
  1794. });
  1795. orderGuard.assertSuccess(modifyOrder);
  1796. const variant2 = await getVariant('T_2');
  1797. expect(variant2.stockOnHand).toBe(98);
  1798. expect(variant2.stockAllocated).toBe(1);
  1799. const { order: order2 } = await adminClient.query<
  1800. Codegen.GetOrderQuery,
  1801. Codegen.GetOrderQueryVariables
  1802. >(GET_ORDER, {
  1803. id: orderId4,
  1804. });
  1805. });
  1806. it('updates stock when adding item before fulfillment', async () => {
  1807. const variant1 = await getVariant('T_3');
  1808. expect(variant1.stockOnHand).toBe(100);
  1809. expect(variant1.stockAllocated).toBe(0);
  1810. const order = await createOrderAndTransitionToModifyingState([
  1811. {
  1812. productVariantId: 'T_2',
  1813. quantity: 1,
  1814. },
  1815. ]);
  1816. orderId5 = order.id;
  1817. const { modifyOrder } = await adminClient.query<
  1818. Codegen.ModifyOrderMutation,
  1819. Codegen.ModifyOrderMutationVariables
  1820. >(MODIFY_ORDER, {
  1821. input: {
  1822. dryRun: false,
  1823. orderId: order!.id,
  1824. addItems: [{ productVariantId: 'T_3', quantity: 1 }],
  1825. },
  1826. });
  1827. orderGuard.assertSuccess(modifyOrder);
  1828. const variant2 = await getVariant('T_3');
  1829. expect(variant2.stockOnHand).toBe(100);
  1830. expect(variant2.stockAllocated).toBe(1);
  1831. });
  1832. it('updates stock when removing item before fulfillment', async () => {
  1833. const variant1 = await getVariant('T_3');
  1834. expect(variant1.stockOnHand).toBe(100);
  1835. expect(variant1.stockAllocated).toBe(1);
  1836. const { order } = await adminClient.query<Codegen.GetOrderQuery, Codegen.GetOrderQueryVariables>(
  1837. GET_ORDER,
  1838. {
  1839. id: orderId5,
  1840. },
  1841. );
  1842. const { modifyOrder } = await adminClient.query<
  1843. Codegen.ModifyOrderMutation,
  1844. Codegen.ModifyOrderMutationVariables
  1845. >(MODIFY_ORDER, {
  1846. input: {
  1847. dryRun: false,
  1848. orderId: orderId5,
  1849. adjustOrderLines: [
  1850. {
  1851. orderLineId: order!.lines.find(l => l.productVariant.id === 'T_3')!.id,
  1852. quantity: 0,
  1853. },
  1854. ],
  1855. refund: {
  1856. paymentId: order!.payments![0].id,
  1857. },
  1858. },
  1859. });
  1860. orderGuard.assertSuccess(modifyOrder);
  1861. const variant2 = await getVariant('T_3');
  1862. expect(variant2.stockOnHand).toBe(100);
  1863. expect(variant2.stockAllocated).toBe(0);
  1864. });
  1865. it('updates stock when removing item after fulfillment', async () => {
  1866. const variant1 = await getVariant('T_3');
  1867. expect(variant1.stockOnHand).toBe(100);
  1868. expect(variant1.stockAllocated).toBe(0);
  1869. const order = await createOrderAndCheckout([
  1870. {
  1871. productVariantId: 'T_3',
  1872. quantity: 1,
  1873. },
  1874. ]);
  1875. const { addFulfillmentToOrder } = await adminClient.query<
  1876. Codegen.CreateFulfillmentMutation,
  1877. Codegen.CreateFulfillmentMutationVariables
  1878. >(CREATE_FULFILLMENT, {
  1879. input: {
  1880. lines: order?.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })) ?? [],
  1881. handler: {
  1882. code: manualFulfillmentHandler.code,
  1883. arguments: [
  1884. { name: 'method', value: 'test method' },
  1885. { name: 'trackingCode', value: 'ABC123' },
  1886. ],
  1887. },
  1888. },
  1889. });
  1890. orderGuard.assertSuccess(addFulfillmentToOrder);
  1891. const variant2 = await getVariant('T_3');
  1892. expect(variant2.stockOnHand).toBe(99);
  1893. expect(variant2.stockAllocated).toBe(0);
  1894. await adminTransitionOrderToState(order.id, 'Modifying');
  1895. const { modifyOrder } = await adminClient.query<
  1896. Codegen.ModifyOrderMutation,
  1897. Codegen.ModifyOrderMutationVariables
  1898. >(MODIFY_ORDER, {
  1899. input: {
  1900. dryRun: false,
  1901. orderId: order.id,
  1902. adjustOrderLines: [
  1903. {
  1904. orderLineId: order!.lines.find(l => l.productVariant.id === 'T_3')!.id,
  1905. quantity: 0,
  1906. },
  1907. ],
  1908. refund: {
  1909. paymentId: order!.payments![0].id,
  1910. },
  1911. },
  1912. });
  1913. const variant3 = await getVariant('T_3');
  1914. expect(variant3.stockOnHand).toBe(100);
  1915. expect(variant3.stockAllocated).toBe(0);
  1916. });
  1917. });
  1918. describe('couponCode handling', () => {
  1919. const CODE_50PC_OFF = '50PC';
  1920. const CODE_FREE_SHIPPING = 'FREESHIP';
  1921. let order: TestOrderWithPaymentsFragment;
  1922. beforeAll(async () => {
  1923. await adminClient.query<CreatePromotionMutation, CreatePromotionMutationVariables>(
  1924. CREATE_PROMOTION,
  1925. {
  1926. input: {
  1927. name: '50% off',
  1928. couponCode: CODE_50PC_OFF,
  1929. enabled: true,
  1930. conditions: [],
  1931. actions: [
  1932. {
  1933. code: orderPercentageDiscount.code,
  1934. arguments: [{ name: 'discount', value: '50' }],
  1935. },
  1936. ],
  1937. },
  1938. },
  1939. );
  1940. await adminClient.query<CreatePromotionMutation, CreatePromotionMutationVariables>(
  1941. CREATE_PROMOTION,
  1942. {
  1943. input: {
  1944. name: 'Free shipping',
  1945. couponCode: CODE_FREE_SHIPPING,
  1946. enabled: true,
  1947. conditions: [],
  1948. actions: [{ code: freeShipping.code, arguments: [] }],
  1949. },
  1950. },
  1951. );
  1952. // create an order and check out
  1953. await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
  1954. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1955. productVariantId: 'T_1',
  1956. quantity: 1,
  1957. customFields: {
  1958. color: 'green',
  1959. },
  1960. } as any);
  1961. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1962. productVariantId: 'T_4',
  1963. quantity: 2,
  1964. });
  1965. await proceedToArrangingPayment(shopClient);
  1966. const result = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1967. orderGuard.assertSuccess(result);
  1968. order = result;
  1969. const result2 = await adminTransitionOrderToState(order.id, 'Modifying');
  1970. orderGuard.assertSuccess(result2);
  1971. expect(result2.state).toBe('Modifying');
  1972. });
  1973. it('invalid coupon code returns ErrorResult', async () => {
  1974. const { modifyOrder } = await adminClient.query<
  1975. ModifyOrderMutation,
  1976. ModifyOrderMutationVariables
  1977. >(MODIFY_ORDER, {
  1978. input: {
  1979. dryRun: false,
  1980. orderId: order.id,
  1981. couponCodes: ['BAD_CODE'],
  1982. },
  1983. });
  1984. orderGuard.assertErrorResult(modifyOrder);
  1985. expect(modifyOrder.message).toBe('Coupon code "BAD_CODE" is not valid');
  1986. });
  1987. it('valid coupon code applies Promotion', async () => {
  1988. const { modifyOrder } = await adminClient.query<
  1989. ModifyOrderMutation,
  1990. ModifyOrderMutationVariables
  1991. >(MODIFY_ORDER, {
  1992. input: {
  1993. dryRun: false,
  1994. orderId: order.id,
  1995. refund: {
  1996. paymentId: order.payments![0].id,
  1997. },
  1998. couponCodes: [CODE_50PC_OFF],
  1999. },
  2000. });
  2001. orderGuard.assertSuccess(modifyOrder);
  2002. expect(modifyOrder.subTotalWithTax).toBe(order.subTotalWithTax * 0.5);
  2003. });
  2004. it('adds order.discounts', async () => {
  2005. const { order: orderWithModifications } = await adminClient.query<
  2006. GetOrderWithModificationsQuery,
  2007. GetOrderWithModificationsQueryVariables
  2008. >(GET_ORDER_WITH_MODIFICATIONS, { id: order.id });
  2009. expect(orderWithModifications?.discounts.length).toBe(1);
  2010. expect(orderWithModifications?.discounts[0].description).toBe('50% off');
  2011. });
  2012. it('adds order.promotions', async () => {
  2013. const { order: orderWithModifications } = await adminClient.query<
  2014. GetOrderWithModificationsQuery,
  2015. GetOrderWithModificationsQueryVariables
  2016. >(GET_ORDER_WITH_MODIFICATIONS, { id: order.id });
  2017. expect(orderWithModifications?.promotions.length).toBe(1);
  2018. expect(orderWithModifications?.promotions[0].name).toBe('50% off');
  2019. });
  2020. it('creates correct refund amount', async () => {
  2021. const { order: orderWithModifications } = await adminClient.query<
  2022. GetOrderWithModificationsQuery,
  2023. GetOrderWithModificationsQueryVariables
  2024. >(GET_ORDER_WITH_MODIFICATIONS, { id: order.id });
  2025. expect(orderWithModifications?.payments![0].refunds.length).toBe(1);
  2026. expect(orderWithModifications!.totalWithTax).toBe(
  2027. getOrderPaymentsTotalWithRefunds(orderWithModifications!),
  2028. );
  2029. expect(orderWithModifications?.payments![0].refunds[0].total).toBe(
  2030. order.totalWithTax - orderWithModifications!.totalWithTax,
  2031. );
  2032. });
  2033. it('creates history entry for applying couponCode', async () => {
  2034. const { order: history } = await adminClient.query<
  2035. GetOrderHistory.Query,
  2036. GetOrderHistory.Variables
  2037. >(GET_ORDER_HISTORY, {
  2038. id: order.id,
  2039. options: { filter: { type: { eq: HistoryEntryType.ORDER_COUPON_APPLIED } } },
  2040. });
  2041. orderGuard.assertSuccess(history);
  2042. expect(history.history.items.length).toBe(1);
  2043. expect(pick(history.history.items[0]!, ['type', 'data'])).toEqual({
  2044. type: HistoryEntryType.ORDER_COUPON_APPLIED,
  2045. data: { couponCode: CODE_50PC_OFF, promotionId: 'T_6' },
  2046. });
  2047. });
  2048. it('removes coupon code', async () => {
  2049. const { modifyOrder } = await adminClient.query<
  2050. ModifyOrderMutation,
  2051. ModifyOrderMutationVariables
  2052. >(MODIFY_ORDER, {
  2053. input: {
  2054. dryRun: false,
  2055. orderId: order.id,
  2056. couponCodes: [],
  2057. },
  2058. });
  2059. orderGuard.assertSuccess(modifyOrder);
  2060. expect(modifyOrder.subTotalWithTax).toBe(order.subTotalWithTax);
  2061. });
  2062. it('removes order.discounts', async () => {
  2063. const { order: orderWithModifications } = await adminClient.query<
  2064. GetOrderWithModificationsQuery,
  2065. GetOrderWithModificationsQueryVariables
  2066. >(GET_ORDER_WITH_MODIFICATIONS, { id: order.id });
  2067. expect(orderWithModifications?.discounts.length).toBe(0);
  2068. });
  2069. it('removes order.promotions', async () => {
  2070. const { order: orderWithModifications } = await adminClient.query<
  2071. GetOrderWithModificationsQuery,
  2072. GetOrderWithModificationsQueryVariables
  2073. >(GET_ORDER_WITH_MODIFICATIONS, { id: order.id });
  2074. expect(orderWithModifications?.promotions.length).toBe(0);
  2075. });
  2076. it('creates history entry for removing couponCode', async () => {
  2077. const { order: history } = await adminClient.query<
  2078. GetOrderHistory.Query,
  2079. GetOrderHistory.Variables
  2080. >(GET_ORDER_HISTORY, {
  2081. id: order.id,
  2082. options: { filter: { type: { eq: HistoryEntryType.ORDER_COUPON_REMOVED } } },
  2083. });
  2084. orderGuard.assertSuccess(history);
  2085. expect(history.history.items.length).toBe(1);
  2086. expect(pick(history.history.items[0]!, ['type', 'data'])).toEqual({
  2087. type: HistoryEntryType.ORDER_COUPON_REMOVED,
  2088. data: { couponCode: CODE_50PC_OFF },
  2089. });
  2090. });
  2091. it('correct refund for free shipping couponCode', async () => {
  2092. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  2093. productVariantId: 'T_1',
  2094. quantity: 1,
  2095. } as any);
  2096. await proceedToArrangingPayment(shopClient);
  2097. const result = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  2098. orderGuard.assertSuccess(result);
  2099. const order2 = result;
  2100. const shippingWithTax = order2.shippingWithTax;
  2101. const result2 = await adminTransitionOrderToState(order2.id, 'Modifying');
  2102. orderGuard.assertSuccess(result2);
  2103. expect(result2.state).toBe('Modifying');
  2104. const { modifyOrder } = await adminClient.query<
  2105. ModifyOrderMutation,
  2106. ModifyOrderMutationVariables
  2107. >(MODIFY_ORDER, {
  2108. input: {
  2109. dryRun: false,
  2110. orderId: order2.id,
  2111. refund: {
  2112. paymentId: order2.payments![0].id,
  2113. },
  2114. couponCodes: [CODE_FREE_SHIPPING],
  2115. },
  2116. });
  2117. orderGuard.assertSuccess(modifyOrder);
  2118. expect(modifyOrder.shippingWithTax).toBe(0);
  2119. expect(modifyOrder!.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder!));
  2120. expect(modifyOrder.payments![0].refunds[0].total).toBe(shippingWithTax);
  2121. });
  2122. });
  2123. async function adminTransitionOrderToState(id: string, state: string) {
  2124. const result = await adminClient.query<
  2125. Codegen.AdminTransitionMutation,
  2126. Codegen.AdminTransitionMutationVariables
  2127. >(ADMIN_TRANSITION_TO_STATE, {
  2128. id,
  2129. state,
  2130. });
  2131. return result.transitionOrderToState;
  2132. }
  2133. async function assertOrderIsUnchanged(order: OrderWithLinesFragment) {
  2134. const { order: order2 } = await adminClient.query<
  2135. Codegen.GetOrderQuery,
  2136. Codegen.GetOrderQueryVariables
  2137. >(GET_ORDER, {
  2138. id: order.id,
  2139. });
  2140. expect(order2!.totalWithTax).toBe(order!.totalWithTax);
  2141. expect(order2!.lines.length).toBe(order!.lines.length);
  2142. expect(order2!.surcharges.length).toBe(order!.surcharges.length);
  2143. expect(order2!.totalQuantity).toBe(order!.totalQuantity);
  2144. }
  2145. async function createOrderAndCheckout(
  2146. items: Array<AddItemToOrderMutationVariables & { customFields?: any }>,
  2147. ) {
  2148. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  2149. for (const itemInput of items) {
  2150. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), itemInput);
  2151. }
  2152. await shopClient.query<
  2153. CodegenShop.SetShippingAddressMutation,
  2154. CodegenShop.SetShippingAddressMutationVariables
  2155. >(SET_SHIPPING_ADDRESS, {
  2156. input: {
  2157. fullName: 'name',
  2158. streetLine1: '12 the street',
  2159. city: 'foo',
  2160. postalCode: '123456',
  2161. countryCode: 'AT',
  2162. },
  2163. });
  2164. await shopClient.query<
  2165. CodegenShop.SetShippingMethodMutation,
  2166. CodegenShop.SetShippingMethodMutationVariables
  2167. >(SET_SHIPPING_METHOD, {
  2168. id: testShippingMethodId,
  2169. });
  2170. await shopClient.query<Codegen.TransitionToStateMutation, Codegen.TransitionToStateMutationVariables>(
  2171. TRANSITION_TO_STATE,
  2172. {
  2173. state: 'ArrangingPayment',
  2174. },
  2175. );
  2176. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  2177. orderGuard.assertSuccess(order);
  2178. return order;
  2179. }
  2180. async function createOrderAndTransitionToModifyingState(
  2181. items: Array<AddItemToOrderMutationVariables & { customFields?: any }>,
  2182. ): Promise<TestOrderWithPaymentsFragment> {
  2183. const order = await createOrderAndCheckout(items);
  2184. await adminTransitionOrderToState(order.id, 'Modifying');
  2185. return order;
  2186. }
  2187. function getOrderPaymentsTotalWithRefunds(_order: OrderWithModificationsFragment) {
  2188. return _order.payments?.reduce((sum, p) => sum + p.amount - summate(p?.refunds, 'total'), 0) ?? 0;
  2189. }
  2190. });
  2191. export const ORDER_WITH_MODIFICATION_FRAGMENT = gql`
  2192. fragment OrderWithModifications on Order {
  2193. id
  2194. state
  2195. subTotal
  2196. subTotalWithTax
  2197. shipping
  2198. shippingWithTax
  2199. total
  2200. totalWithTax
  2201. lines {
  2202. id
  2203. quantity
  2204. linePrice
  2205. linePriceWithTax
  2206. discountedLinePriceWithTax
  2207. proratedLinePriceWithTax
  2208. discounts {
  2209. description
  2210. amountWithTax
  2211. }
  2212. productVariant {
  2213. id
  2214. name
  2215. }
  2216. items {
  2217. id
  2218. createdAt
  2219. updatedAt
  2220. cancelled
  2221. unitPrice
  2222. }
  2223. }
  2224. surcharges {
  2225. id
  2226. description
  2227. sku
  2228. price
  2229. priceWithTax
  2230. taxRate
  2231. }
  2232. payments {
  2233. id
  2234. transactionId
  2235. state
  2236. amount
  2237. method
  2238. metadata
  2239. refunds {
  2240. id
  2241. state
  2242. total
  2243. paymentId
  2244. }
  2245. }
  2246. modifications {
  2247. id
  2248. note
  2249. priceChange
  2250. isSettled
  2251. orderItems {
  2252. id
  2253. }
  2254. surcharges {
  2255. id
  2256. }
  2257. payment {
  2258. id
  2259. state
  2260. amount
  2261. method
  2262. }
  2263. refund {
  2264. id
  2265. state
  2266. total
  2267. paymentId
  2268. }
  2269. }
  2270. promotions {
  2271. id
  2272. name
  2273. couponCode
  2274. }
  2275. discounts {
  2276. description
  2277. adjustmentSource
  2278. amount
  2279. amountWithTax
  2280. }
  2281. shippingAddress {
  2282. streetLine1
  2283. city
  2284. postalCode
  2285. province
  2286. countryCode
  2287. country
  2288. }
  2289. billingAddress {
  2290. streetLine1
  2291. city
  2292. postalCode
  2293. province
  2294. countryCode
  2295. country
  2296. }
  2297. }
  2298. `;
  2299. export const GET_ORDER_WITH_MODIFICATIONS = gql`
  2300. query GetOrderWithModifications($id: ID!) {
  2301. order(id: $id) {
  2302. ...OrderWithModifications
  2303. }
  2304. }
  2305. ${ORDER_WITH_MODIFICATION_FRAGMENT}
  2306. `;
  2307. export const MODIFY_ORDER = gql`
  2308. mutation ModifyOrder($input: ModifyOrderInput!) {
  2309. modifyOrder(input: $input) {
  2310. ...OrderWithModifications
  2311. ... on ErrorResult {
  2312. errorCode
  2313. message
  2314. }
  2315. }
  2316. }
  2317. ${ORDER_WITH_MODIFICATION_FRAGMENT}
  2318. `;
  2319. export const ADD_MANUAL_PAYMENT = gql`
  2320. mutation AddManualPayment($input: ManualPaymentInput!) {
  2321. addManualPaymentToOrder(input: $input) {
  2322. ...OrderWithModifications
  2323. ... on ErrorResult {
  2324. errorCode
  2325. message
  2326. }
  2327. }
  2328. }
  2329. ${ORDER_WITH_MODIFICATION_FRAGMENT}
  2330. `;
  2331. // Note, we don't use the gql tag around these due to the customFields which
  2332. // would cause a codegen error.
  2333. const ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS = `
  2334. mutation AddItemToOrder($productVariantId: ID!, $quantity: Int!, $customFields: OrderLineCustomFieldsInput) {
  2335. addItemToOrder(productVariantId: $productVariantId, quantity: $quantity, customFields: $customFields) {
  2336. ...on Order { id }
  2337. }
  2338. }
  2339. `;
  2340. const GET_ORDER_WITH_CUSTOM_FIELDS = `
  2341. query GetOrderCustomFields($id: ID!) {
  2342. order(id: $id) {
  2343. customFields { points }
  2344. lines { id, customFields { color } }
  2345. }
  2346. }
  2347. `;