order-modification.e2e-spec.ts 95 KB

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