order-modification.e2e-spec.ts 96 KB

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