order-modification.e2e-spec.ts 96 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497
  1. /* tslint:disable:no-non-null-assertion */
  2. import { omit } from '@vendure/common/lib/omit';
  3. import { pick } from '@vendure/common/lib/pick';
  4. import { summate } from '@vendure/common/lib/shared-utils';
  5. import {
  6. defaultShippingCalculator,
  7. defaultShippingEligibilityChecker,
  8. freeShipping,
  9. manualFulfillmentHandler,
  10. mergeConfig,
  11. minimumOrderAmount,
  12. orderFixedDiscount,
  13. orderPercentageDiscount,
  14. productsPercentageDiscount,
  15. ShippingCalculator,
  16. } from '@vendure/core';
  17. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  18. import gql from 'graphql-tag';
  19. import path from 'path';
  20. import { initialData } from '../../../e2e-common/e2e-initial-data';
  21. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  22. import {
  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, sortById } 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(
  1288. modifyOrder?.payments?.find(p => p.id === additionalPaymentId)?.refunds.sort(sortById),
  1289. ).toEqual([
  1290. {
  1291. id: 'T_4',
  1292. paymentId: additionalPaymentId,
  1293. state: 'Pending',
  1294. total: 600,
  1295. },
  1296. {
  1297. id: 'T_5',
  1298. paymentId: additionalPaymentId,
  1299. state: 'Pending',
  1300. total: 16649,
  1301. },
  1302. ]);
  1303. expect(modifyOrder?.payments?.find(p => p.id !== additionalPaymentId)?.refunds).toEqual([
  1304. {
  1305. id: 'T_6',
  1306. paymentId: 'T_15',
  1307. state: 'Pending',
  1308. total: 300,
  1309. },
  1310. ]);
  1311. expect(modifyOrder.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder));
  1312. });
  1313. });
  1314. // https://github.com/vendure-ecommerce/vendure/issues/688 - 4th point
  1315. it('correct additional payment when discounts applied', async () => {
  1316. await adminClient.query<CreatePromotion.Mutation, CreatePromotion.Variables>(CREATE_PROMOTION, {
  1317. input: {
  1318. name: '$5 off',
  1319. couponCode: '5OFF',
  1320. enabled: true,
  1321. conditions: [],
  1322. actions: [
  1323. {
  1324. code: orderFixedDiscount.code,
  1325. arguments: [{ name: 'discount', value: '500' }],
  1326. },
  1327. ],
  1328. },
  1329. });
  1330. await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
  1331. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1332. productVariantId: 'T_1',
  1333. quantity: 1,
  1334. } as any);
  1335. await shopClient.query<ApplyCouponCode.Mutation, ApplyCouponCode.Variables>(APPLY_COUPON_CODE, {
  1336. couponCode: '5OFF',
  1337. });
  1338. await proceedToArrangingPayment(shopClient);
  1339. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1340. orderGuard.assertSuccess(order);
  1341. const originalTotalWithTax = order.totalWithTax;
  1342. const surcharge = 300;
  1343. const transitionOrderToState = await adminTransitionOrderToState(order.id, 'Modifying');
  1344. orderGuard.assertSuccess(transitionOrderToState);
  1345. expect(transitionOrderToState.state).toBe('Modifying');
  1346. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  1347. MODIFY_ORDER,
  1348. {
  1349. input: {
  1350. dryRun: false,
  1351. orderId: order.id,
  1352. surcharges: [
  1353. {
  1354. description: 'extra fee',
  1355. sku: '123',
  1356. price: surcharge,
  1357. priceIncludesTax: true,
  1358. taxRate: 20,
  1359. taxDescription: 'VAT',
  1360. },
  1361. ],
  1362. },
  1363. },
  1364. );
  1365. orderGuard.assertSuccess(modifyOrder);
  1366. expect(modifyOrder.totalWithTax).toBe(originalTotalWithTax + surcharge);
  1367. });
  1368. // https://github.com/vendure-ecommerce/vendure/issues/872
  1369. describe('correct price calculations when prices include tax', () => {
  1370. async function modifyOrderLineQuantity(order: TestOrderWithPaymentsFragment) {
  1371. const transitionOrderToState = await adminTransitionOrderToState(order.id, 'Modifying');
  1372. orderGuard.assertSuccess(transitionOrderToState);
  1373. expect(transitionOrderToState.state).toBe('Modifying');
  1374. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  1375. MODIFY_ORDER,
  1376. {
  1377. input: {
  1378. dryRun: true,
  1379. orderId: order.id,
  1380. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 2 }],
  1381. },
  1382. },
  1383. );
  1384. orderGuard.assertSuccess(modifyOrder);
  1385. return modifyOrder;
  1386. }
  1387. beforeAll(async () => {
  1388. await adminClient.query<UpdateChannel.Mutation, UpdateChannel.Variables>(UPDATE_CHANNEL, {
  1389. input: {
  1390. id: 'T_1',
  1391. pricesIncludeTax: true,
  1392. },
  1393. });
  1394. });
  1395. it('without promotion', async () => {
  1396. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  1397. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1398. productVariantId: 'T_1',
  1399. quantity: 1,
  1400. } as any);
  1401. await proceedToArrangingPayment(shopClient);
  1402. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1403. orderGuard.assertSuccess(order);
  1404. const modifyOrder = await modifyOrderLineQuantity(order);
  1405. expect(modifyOrder.lines[0].linePriceWithTax).toBe(order.lines[0].linePriceWithTax * 2);
  1406. });
  1407. it('with promotion', async () => {
  1408. await adminClient.query<CreatePromotion.Mutation, CreatePromotion.Variables>(CREATE_PROMOTION, {
  1409. input: {
  1410. name: 'half price',
  1411. couponCode: 'HALF',
  1412. enabled: true,
  1413. conditions: [],
  1414. actions: [
  1415. {
  1416. code: productsPercentageDiscount.code,
  1417. arguments: [
  1418. { name: 'discount', value: '50' },
  1419. { name: 'productVariantIds', value: JSON.stringify(['T_1']) },
  1420. ],
  1421. },
  1422. ],
  1423. },
  1424. });
  1425. await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
  1426. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1427. productVariantId: 'T_1',
  1428. quantity: 1,
  1429. } as any);
  1430. await shopClient.query<ApplyCouponCode.Mutation, ApplyCouponCode.Variables>(APPLY_COUPON_CODE, {
  1431. couponCode: 'HALF',
  1432. });
  1433. await proceedToArrangingPayment(shopClient);
  1434. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1435. orderGuard.assertSuccess(order);
  1436. const modifyOrder = await modifyOrderLineQuantity(order);
  1437. expect(modifyOrder.lines[0].discountedLinePriceWithTax).toBe(
  1438. modifyOrder.lines[0].linePriceWithTax / 2,
  1439. );
  1440. expect(modifyOrder.lines[0].linePriceWithTax).toBe(order.lines[0].linePriceWithTax * 2);
  1441. });
  1442. });
  1443. describe('refund handling when promotions are active on order', () => {
  1444. // https://github.com/vendure-ecommerce/vendure/issues/890
  1445. it('refunds correct amount when order-level promotion applied', async () => {
  1446. await adminClient.query<CreatePromotion.Mutation, CreatePromotion.Variables>(CREATE_PROMOTION, {
  1447. input: {
  1448. name: '$5 off',
  1449. couponCode: '5OFF2',
  1450. enabled: true,
  1451. conditions: [],
  1452. actions: [
  1453. {
  1454. code: orderFixedDiscount.code,
  1455. arguments: [{ name: 'discount', value: '500' }],
  1456. },
  1457. ],
  1458. },
  1459. });
  1460. await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
  1461. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1462. productVariantId: 'T_1',
  1463. quantity: 2,
  1464. } as any);
  1465. await shopClient.query<ApplyCouponCode.Mutation, ApplyCouponCode.Variables>(APPLY_COUPON_CODE, {
  1466. couponCode: '5OFF2',
  1467. });
  1468. await proceedToArrangingPayment(shopClient);
  1469. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1470. orderGuard.assertSuccess(order);
  1471. const originalTotalWithTax = order.totalWithTax;
  1472. const transitionOrderToState = await adminTransitionOrderToState(order.id, 'Modifying');
  1473. orderGuard.assertSuccess(transitionOrderToState);
  1474. expect(transitionOrderToState.state).toBe('Modifying');
  1475. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  1476. MODIFY_ORDER,
  1477. {
  1478. input: {
  1479. dryRun: false,
  1480. orderId: order.id,
  1481. adjustOrderLines: [{ orderLineId: order.lines[0].id, quantity: 1 }],
  1482. refund: {
  1483. paymentId: order.payments![0].id,
  1484. reason: 'requested',
  1485. },
  1486. },
  1487. },
  1488. );
  1489. orderGuard.assertSuccess(modifyOrder);
  1490. expect(modifyOrder.totalWithTax).toBe(
  1491. originalTotalWithTax - order.lines[0].proratedUnitPriceWithTax,
  1492. );
  1493. expect(modifyOrder.payments![0].refunds![0].total).toBe(order.lines[0].proratedUnitPriceWithTax);
  1494. expect(modifyOrder.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder));
  1495. });
  1496. // github.com/vendure-ecommerce/vendure/issues/1865
  1497. describe('issue 1865', () => {
  1498. const promoDiscount = 5000;
  1499. let promoId: string;
  1500. let orderId2: string;
  1501. beforeAll(async () => {
  1502. const { createPromotion } = await adminClient.query<
  1503. CreatePromotion.Mutation,
  1504. CreatePromotion.Variables
  1505. >(CREATE_PROMOTION, {
  1506. input: {
  1507. name: '50 off orders over 100',
  1508. enabled: true,
  1509. conditions: [
  1510. {
  1511. code: minimumOrderAmount.code,
  1512. arguments: [
  1513. { name: 'amount', value: '10000' },
  1514. { name: 'taxInclusive', value: 'true' },
  1515. ],
  1516. },
  1517. ],
  1518. actions: [
  1519. {
  1520. code: orderFixedDiscount.code,
  1521. arguments: [{ name: 'discount', value: JSON.stringify(promoDiscount) }],
  1522. },
  1523. ],
  1524. },
  1525. });
  1526. promoId = (createPromotion as any).id;
  1527. });
  1528. afterAll(async () => {
  1529. await adminClient.query<DeletePromotionMutation, DeletePromotionMutationVariables>(
  1530. DELETE_PROMOTION,
  1531. {
  1532. id: promoId,
  1533. },
  1534. );
  1535. });
  1536. it('refund handling when order-level promotion becomes invalid on modification', async () => {
  1537. const { productVariants } = await adminClient.query<
  1538. GetProductVariantListQuery,
  1539. GetProductVariantListQueryVariables
  1540. >(GET_PRODUCT_VARIANT_LIST, {
  1541. options: {
  1542. filter: {
  1543. name: { contains: 'football' },
  1544. },
  1545. },
  1546. });
  1547. const football = productVariants.items[0];
  1548. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1549. productVariantId: football.id,
  1550. quantity: 2,
  1551. } as any);
  1552. await proceedToArrangingPayment(shopClient);
  1553. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1554. orderGuard.assertSuccess(order);
  1555. orderId2 = order.id;
  1556. expect(order.discounts.length).toBe(1);
  1557. expect(order.discounts[0].amountWithTax).toBe(-promoDiscount);
  1558. const shippingPrice = order.shippingWithTax;
  1559. const expectedTotal = football.priceWithTax * 2 + shippingPrice - promoDiscount;
  1560. expect(order.totalWithTax).toBe(expectedTotal);
  1561. const originalTotalWithTax = order.totalWithTax;
  1562. const transitionOrderToState = await adminTransitionOrderToState(order.id, 'Modifying');
  1563. orderGuard.assertSuccess(transitionOrderToState);
  1564. expect(transitionOrderToState.state).toBe('Modifying');
  1565. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  1566. MODIFY_ORDER,
  1567. {
  1568. input: {
  1569. dryRun: false,
  1570. orderId: order.id,
  1571. adjustOrderLines: [{ orderLineId: order.lines[0].id, quantity: 1 }],
  1572. refund: {
  1573. paymentId: order.payments![0].id,
  1574. reason: 'requested',
  1575. },
  1576. },
  1577. },
  1578. );
  1579. orderGuard.assertSuccess(modifyOrder);
  1580. const expectedNewTotal = order.lines[0].unitPriceWithTax + shippingPrice;
  1581. expect(modifyOrder.totalWithTax).toBe(expectedNewTotal);
  1582. expect(modifyOrder.payments![0].refunds![0].total).toBe(expectedTotal - expectedNewTotal);
  1583. expect(modifyOrder.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder));
  1584. });
  1585. it('transition back to original state', async () => {
  1586. const transitionOrderToState2 = await adminTransitionOrderToState(orderId2, 'PaymentSettled');
  1587. orderGuard.assertSuccess(transitionOrderToState2);
  1588. expect(transitionOrderToState2!.state).toBe('PaymentSettled');
  1589. });
  1590. it('order no longer has promotions', async () => {
  1591. const { order } = await adminClient.query<
  1592. GetOrderWithModificationsQuery,
  1593. GetOrderWithModificationsQueryVariables
  1594. >(GET_ORDER_WITH_MODIFICATIONS, { id: orderId2 });
  1595. expect(order?.promotions).toEqual([]);
  1596. });
  1597. it('order no longer has discounts', async () => {
  1598. const { order } = await adminClient.query<
  1599. GetOrderWithModificationsQuery,
  1600. GetOrderWithModificationsQueryVariables
  1601. >(GET_ORDER_WITH_MODIFICATIONS, { id: orderId2 });
  1602. expect(order?.discounts).toEqual([]);
  1603. });
  1604. });
  1605. });
  1606. // https://github.com/vendure-ecommerce/vendure/issues/1197
  1607. describe('refund on shipping when change made to shippingAddress', () => {
  1608. let order: OrderWithModificationsFragment;
  1609. beforeAll(async () => {
  1610. const createdOrder = await createOrderAndTransitionToModifyingState([
  1611. {
  1612. productVariantId: 'T_1',
  1613. quantity: 1,
  1614. },
  1615. ]);
  1616. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  1617. MODIFY_ORDER,
  1618. {
  1619. input: {
  1620. dryRun: false,
  1621. orderId: createdOrder.id,
  1622. updateShippingAddress: {
  1623. countryCode: 'GB',
  1624. },
  1625. refund: {
  1626. paymentId: createdOrder.payments![0].id,
  1627. reason: 'discount',
  1628. },
  1629. },
  1630. },
  1631. );
  1632. orderGuard.assertSuccess(modifyOrder);
  1633. order = modifyOrder;
  1634. });
  1635. it('creates a Refund with the correct amount', async () => {
  1636. expect(order.payments?.[0].refunds[0].total).toBe(SHIPPING_OTHER - SHIPPING_GB);
  1637. });
  1638. it('allows transition to PaymentSettled', async () => {
  1639. const transitionOrderToState = await adminTransitionOrderToState(order.id, 'PaymentSettled');
  1640. orderGuard.assertSuccess(transitionOrderToState);
  1641. expect(transitionOrderToState.state).toBe('PaymentSettled');
  1642. });
  1643. });
  1644. // https://github.com/vendure-ecommerce/vendure/issues/1210
  1645. describe('updating stock levels', () => {
  1646. async function getVariant(id: 'T_1' | 'T_2' | 'T_3') {
  1647. const { product } = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  1648. GET_STOCK_MOVEMENT,
  1649. {
  1650. id: 'T_1',
  1651. },
  1652. );
  1653. return product?.variants.find(v => v.id === id)!;
  1654. }
  1655. let orderId4: string;
  1656. let orderId5: string;
  1657. it('updates stock when increasing quantity before fulfillment', async () => {
  1658. const variant1 = await getVariant('T_2');
  1659. expect(variant1.stockOnHand).toBe(100);
  1660. expect(variant1.stockAllocated).toBe(0);
  1661. const order = await createOrderAndTransitionToModifyingState([
  1662. {
  1663. productVariantId: 'T_2',
  1664. quantity: 1,
  1665. },
  1666. ]);
  1667. orderId4 = order.id;
  1668. const variant2 = await getVariant('T_2');
  1669. expect(variant2.stockOnHand).toBe(100);
  1670. expect(variant2.stockAllocated).toBe(1);
  1671. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  1672. MODIFY_ORDER,
  1673. {
  1674. input: {
  1675. dryRun: false,
  1676. orderId: order.id,
  1677. adjustOrderLines: [{ orderLineId: order.lines[0].id, quantity: 2 }],
  1678. },
  1679. },
  1680. );
  1681. orderGuard.assertSuccess(modifyOrder);
  1682. const variant3 = await getVariant('T_2');
  1683. expect(variant3.stockOnHand).toBe(100);
  1684. expect(variant3.stockAllocated).toBe(2);
  1685. });
  1686. it('updates stock when increasing quantity after fulfillment', async () => {
  1687. const result = await adminTransitionOrderToState(orderId4, 'ArrangingAdditionalPayment');
  1688. orderGuard.assertSuccess(result);
  1689. expect(result!.state).toBe('ArrangingAdditionalPayment');
  1690. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1691. id: orderId4,
  1692. });
  1693. const { addManualPaymentToOrder } = await adminClient.query<
  1694. AddManualPayment.Mutation,
  1695. AddManualPayment.Variables
  1696. >(ADD_MANUAL_PAYMENT, {
  1697. input: {
  1698. orderId: orderId4,
  1699. method: 'test',
  1700. transactionId: 'ABC123',
  1701. metadata: {
  1702. foo: 'bar',
  1703. },
  1704. },
  1705. });
  1706. orderGuard.assertSuccess(addManualPaymentToOrder);
  1707. await adminTransitionOrderToState(orderId4, 'PaymentSettled');
  1708. await adminClient.query<CreateFulfillment.Mutation, CreateFulfillment.Variables>(
  1709. CREATE_FULFILLMENT,
  1710. {
  1711. input: {
  1712. lines: order?.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })) ?? [],
  1713. handler: {
  1714. code: manualFulfillmentHandler.code,
  1715. arguments: [
  1716. { name: 'method', value: 'test method' },
  1717. { name: 'trackingCode', value: 'ABC123' },
  1718. ],
  1719. },
  1720. },
  1721. },
  1722. );
  1723. const variant1 = await getVariant('T_2');
  1724. expect(variant1.stockOnHand).toBe(98);
  1725. expect(variant1.stockAllocated).toBe(0);
  1726. await adminTransitionOrderToState(orderId4, 'Modifying');
  1727. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  1728. MODIFY_ORDER,
  1729. {
  1730. input: {
  1731. dryRun: false,
  1732. orderId: order!.id,
  1733. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 3 }],
  1734. },
  1735. },
  1736. );
  1737. orderGuard.assertSuccess(modifyOrder);
  1738. const variant2 = await getVariant('T_2');
  1739. expect(variant2.stockOnHand).toBe(98);
  1740. expect(variant2.stockAllocated).toBe(1);
  1741. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1742. id: orderId4,
  1743. });
  1744. });
  1745. it('updates stock when adding item before fulfillment', async () => {
  1746. const variant1 = await getVariant('T_3');
  1747. expect(variant1.stockOnHand).toBe(100);
  1748. expect(variant1.stockAllocated).toBe(0);
  1749. const order = await createOrderAndTransitionToModifyingState([
  1750. {
  1751. productVariantId: 'T_2',
  1752. quantity: 1,
  1753. },
  1754. ]);
  1755. orderId5 = order.id;
  1756. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  1757. MODIFY_ORDER,
  1758. {
  1759. input: {
  1760. dryRun: false,
  1761. orderId: order!.id,
  1762. addItems: [{ productVariantId: 'T_3', quantity: 1 }],
  1763. },
  1764. },
  1765. );
  1766. orderGuard.assertSuccess(modifyOrder);
  1767. const variant2 = await getVariant('T_3');
  1768. expect(variant2.stockOnHand).toBe(100);
  1769. expect(variant2.stockAllocated).toBe(1);
  1770. const result = await adminTransitionOrderToState(orderId5, 'ArrangingAdditionalPayment');
  1771. orderGuard.assertSuccess(result);
  1772. expect(result!.state).toBe('ArrangingAdditionalPayment');
  1773. const { addManualPaymentToOrder } = await adminClient.query<
  1774. AddManualPayment.Mutation,
  1775. AddManualPayment.Variables
  1776. >(ADD_MANUAL_PAYMENT, {
  1777. input: {
  1778. orderId: orderId5,
  1779. method: 'test',
  1780. transactionId: 'manual-extra-payment',
  1781. metadata: {
  1782. foo: 'bar',
  1783. },
  1784. },
  1785. });
  1786. orderGuard.assertSuccess(addManualPaymentToOrder);
  1787. const result2 = await adminTransitionOrderToState(orderId5, 'PaymentSettled');
  1788. orderGuard.assertSuccess(result2);
  1789. const result3 = await adminTransitionOrderToState(orderId5, 'Modifying');
  1790. orderGuard.assertSuccess(result3);
  1791. });
  1792. it('updates stock when removing item before fulfillment', async () => {
  1793. const variant1 = await getVariant('T_3');
  1794. expect(variant1.stockOnHand).toBe(100);
  1795. expect(variant1.stockAllocated).toBe(1);
  1796. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1797. id: orderId5,
  1798. });
  1799. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  1800. MODIFY_ORDER,
  1801. {
  1802. input: {
  1803. dryRun: false,
  1804. orderId: orderId5,
  1805. adjustOrderLines: [
  1806. {
  1807. orderLineId: order!.lines.find(l => l.productVariant.id === 'T_3')!.id,
  1808. quantity: 0,
  1809. },
  1810. ],
  1811. refund: {
  1812. paymentId: order!.payments![0].id,
  1813. },
  1814. },
  1815. },
  1816. );
  1817. orderGuard.assertSuccess(modifyOrder);
  1818. const variant2 = await getVariant('T_3');
  1819. expect(variant2.stockOnHand).toBe(100);
  1820. expect(variant2.stockAllocated).toBe(0);
  1821. });
  1822. it('updates stock when removing item after fulfillment', async () => {
  1823. const variant1 = await getVariant('T_3');
  1824. expect(variant1.stockOnHand).toBe(100);
  1825. expect(variant1.stockAllocated).toBe(0);
  1826. const order = await createOrderAndCheckout([
  1827. {
  1828. productVariantId: 'T_3',
  1829. quantity: 1,
  1830. },
  1831. ]);
  1832. const { addFulfillmentToOrder } = await adminClient.query<
  1833. CreateFulfillment.Mutation,
  1834. CreateFulfillment.Variables
  1835. >(CREATE_FULFILLMENT, {
  1836. input: {
  1837. lines: order?.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })) ?? [],
  1838. handler: {
  1839. code: manualFulfillmentHandler.code,
  1840. arguments: [
  1841. { name: 'method', value: 'test method' },
  1842. { name: 'trackingCode', value: 'ABC123' },
  1843. ],
  1844. },
  1845. },
  1846. });
  1847. orderGuard.assertSuccess(addFulfillmentToOrder);
  1848. const variant2 = await getVariant('T_3');
  1849. expect(variant2.stockOnHand).toBe(99);
  1850. expect(variant2.stockAllocated).toBe(0);
  1851. await adminTransitionOrderToState(order.id, 'Modifying');
  1852. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  1853. MODIFY_ORDER,
  1854. {
  1855. input: {
  1856. dryRun: false,
  1857. orderId: order.id,
  1858. adjustOrderLines: [
  1859. {
  1860. orderLineId: order!.lines.find(l => l.productVariant.id === 'T_3')!.id,
  1861. quantity: 0,
  1862. },
  1863. ],
  1864. refund: {
  1865. paymentId: order!.payments![0].id,
  1866. },
  1867. },
  1868. },
  1869. );
  1870. const variant3 = await getVariant('T_3');
  1871. expect(variant3.stockOnHand).toBe(100);
  1872. expect(variant3.stockAllocated).toBe(0);
  1873. });
  1874. });
  1875. describe('couponCode handling', () => {
  1876. const CODE_50PC_OFF = '50PC';
  1877. const CODE_FREE_SHIPPING = 'FREESHIP';
  1878. let order: TestOrderWithPaymentsFragment;
  1879. beforeAll(async () => {
  1880. await adminClient.query<CreatePromotionMutation, CreatePromotionMutationVariables>(
  1881. CREATE_PROMOTION,
  1882. {
  1883. input: {
  1884. name: '50% off',
  1885. couponCode: CODE_50PC_OFF,
  1886. enabled: true,
  1887. conditions: [],
  1888. actions: [
  1889. {
  1890. code: orderPercentageDiscount.code,
  1891. arguments: [{ name: 'discount', value: '50' }],
  1892. },
  1893. ],
  1894. },
  1895. },
  1896. );
  1897. await adminClient.query<CreatePromotionMutation, CreatePromotionMutationVariables>(
  1898. CREATE_PROMOTION,
  1899. {
  1900. input: {
  1901. name: 'Free shipping',
  1902. couponCode: CODE_FREE_SHIPPING,
  1903. enabled: true,
  1904. conditions: [],
  1905. actions: [{ code: freeShipping.code, arguments: [] }],
  1906. },
  1907. },
  1908. );
  1909. // create an order and check out
  1910. await shopClient.asUserWithCredentials('trevor_donnelly96@hotmail.com', 'test');
  1911. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1912. productVariantId: 'T_1',
  1913. quantity: 1,
  1914. customFields: {
  1915. color: 'green',
  1916. },
  1917. } as any);
  1918. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  1919. productVariantId: 'T_4',
  1920. quantity: 2,
  1921. });
  1922. await proceedToArrangingPayment(shopClient);
  1923. const result = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1924. orderGuard.assertSuccess(result);
  1925. order = result;
  1926. const result2 = await adminTransitionOrderToState(order.id, 'Modifying');
  1927. orderGuard.assertSuccess(result2);
  1928. expect(result2.state).toBe('Modifying');
  1929. });
  1930. it('invalid coupon code returns ErrorResult', async () => {
  1931. const { modifyOrder } = await adminClient.query<
  1932. ModifyOrderMutation,
  1933. ModifyOrderMutationVariables
  1934. >(MODIFY_ORDER, {
  1935. input: {
  1936. dryRun: false,
  1937. orderId: order.id,
  1938. couponCodes: ['BAD_CODE'],
  1939. },
  1940. });
  1941. orderGuard.assertErrorResult(modifyOrder);
  1942. expect(modifyOrder.message).toBe('Coupon code "BAD_CODE" is not valid');
  1943. });
  1944. it('valid coupon code applies Promotion', async () => {
  1945. const { modifyOrder } = await adminClient.query<
  1946. ModifyOrderMutation,
  1947. ModifyOrderMutationVariables
  1948. >(MODIFY_ORDER, {
  1949. input: {
  1950. dryRun: false,
  1951. orderId: order.id,
  1952. refund: {
  1953. paymentId: order.payments![0].id,
  1954. },
  1955. couponCodes: [CODE_50PC_OFF],
  1956. },
  1957. });
  1958. orderGuard.assertSuccess(modifyOrder);
  1959. expect(modifyOrder.subTotalWithTax).toBe(order.subTotalWithTax * 0.5);
  1960. });
  1961. it('adds order.discounts', async () => {
  1962. const { order: orderWithModifications } = await adminClient.query<
  1963. GetOrderWithModificationsQuery,
  1964. GetOrderWithModificationsQueryVariables
  1965. >(GET_ORDER_WITH_MODIFICATIONS, { id: order.id });
  1966. expect(orderWithModifications?.discounts.length).toBe(1);
  1967. expect(orderWithModifications?.discounts[0].description).toBe('50% off');
  1968. });
  1969. it('adds order.promotions', async () => {
  1970. const { order: orderWithModifications } = await adminClient.query<
  1971. GetOrderWithModificationsQuery,
  1972. GetOrderWithModificationsQueryVariables
  1973. >(GET_ORDER_WITH_MODIFICATIONS, { id: order.id });
  1974. expect(orderWithModifications?.promotions.length).toBe(1);
  1975. expect(orderWithModifications?.promotions[0].name).toBe('50% off');
  1976. });
  1977. it('creates correct refund amount', async () => {
  1978. const { order: orderWithModifications } = await adminClient.query<
  1979. GetOrderWithModificationsQuery,
  1980. GetOrderWithModificationsQueryVariables
  1981. >(GET_ORDER_WITH_MODIFICATIONS, { id: order.id });
  1982. expect(orderWithModifications?.payments![0].refunds.length).toBe(1);
  1983. expect(orderWithModifications!.totalWithTax).toBe(
  1984. getOrderPaymentsTotalWithRefunds(orderWithModifications!),
  1985. );
  1986. expect(orderWithModifications?.payments![0].refunds[0].total).toBe(
  1987. order.totalWithTax - orderWithModifications!.totalWithTax,
  1988. );
  1989. });
  1990. it('creates history entry for applying couponCode', async () => {
  1991. const { order: history } = await adminClient.query<
  1992. GetOrderHistory.Query,
  1993. GetOrderHistory.Variables
  1994. >(GET_ORDER_HISTORY, {
  1995. id: order.id,
  1996. options: { filter: { type: { eq: HistoryEntryType.ORDER_COUPON_APPLIED } } },
  1997. });
  1998. orderGuard.assertSuccess(history);
  1999. expect(history.history.items.length).toBe(1);
  2000. expect(pick(history.history.items[0]!, ['type', 'data'])).toEqual({
  2001. type: HistoryEntryType.ORDER_COUPON_APPLIED,
  2002. data: { couponCode: CODE_50PC_OFF, promotionId: 'T_6' },
  2003. });
  2004. });
  2005. it('removes coupon code', async () => {
  2006. const { modifyOrder } = await adminClient.query<
  2007. ModifyOrderMutation,
  2008. ModifyOrderMutationVariables
  2009. >(MODIFY_ORDER, {
  2010. input: {
  2011. dryRun: false,
  2012. orderId: order.id,
  2013. couponCodes: [],
  2014. },
  2015. });
  2016. orderGuard.assertSuccess(modifyOrder);
  2017. expect(modifyOrder.subTotalWithTax).toBe(order.subTotalWithTax);
  2018. });
  2019. it('removes order.discounts', async () => {
  2020. const { order: orderWithModifications } = await adminClient.query<
  2021. GetOrderWithModificationsQuery,
  2022. GetOrderWithModificationsQueryVariables
  2023. >(GET_ORDER_WITH_MODIFICATIONS, { id: order.id });
  2024. expect(orderWithModifications?.discounts.length).toBe(0);
  2025. });
  2026. it('removes order.promotions', async () => {
  2027. const { order: orderWithModifications } = await adminClient.query<
  2028. GetOrderWithModificationsQuery,
  2029. GetOrderWithModificationsQueryVariables
  2030. >(GET_ORDER_WITH_MODIFICATIONS, { id: order.id });
  2031. expect(orderWithModifications?.promotions.length).toBe(0);
  2032. });
  2033. it('creates history entry for removing couponCode', async () => {
  2034. const { order: history } = await adminClient.query<
  2035. GetOrderHistory.Query,
  2036. GetOrderHistory.Variables
  2037. >(GET_ORDER_HISTORY, {
  2038. id: order.id,
  2039. options: { filter: { type: { eq: HistoryEntryType.ORDER_COUPON_REMOVED } } },
  2040. });
  2041. orderGuard.assertSuccess(history);
  2042. expect(history.history.items.length).toBe(1);
  2043. expect(pick(history.history.items[0]!, ['type', 'data'])).toEqual({
  2044. type: HistoryEntryType.ORDER_COUPON_REMOVED,
  2045. data: { couponCode: CODE_50PC_OFF },
  2046. });
  2047. });
  2048. it('correct refund for free shipping couponCode', async () => {
  2049. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  2050. productVariantId: 'T_1',
  2051. quantity: 1,
  2052. } as any);
  2053. await proceedToArrangingPayment(shopClient);
  2054. const result = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  2055. orderGuard.assertSuccess(result);
  2056. const order2 = result;
  2057. const shippingWithTax = order2.shippingWithTax;
  2058. const result2 = await adminTransitionOrderToState(order2.id, 'Modifying');
  2059. orderGuard.assertSuccess(result2);
  2060. expect(result2.state).toBe('Modifying');
  2061. const { modifyOrder } = await adminClient.query<
  2062. ModifyOrderMutation,
  2063. ModifyOrderMutationVariables
  2064. >(MODIFY_ORDER, {
  2065. input: {
  2066. dryRun: false,
  2067. orderId: order2.id,
  2068. refund: {
  2069. paymentId: order2.payments![0].id,
  2070. },
  2071. couponCodes: [CODE_FREE_SHIPPING],
  2072. },
  2073. });
  2074. orderGuard.assertSuccess(modifyOrder);
  2075. expect(modifyOrder.shippingWithTax).toBe(0);
  2076. expect(modifyOrder!.totalWithTax).toBe(getOrderPaymentsTotalWithRefunds(modifyOrder!));
  2077. expect(modifyOrder.payments![0].refunds[0].total).toBe(shippingWithTax);
  2078. });
  2079. });
  2080. async function adminTransitionOrderToState(id: string, state: string) {
  2081. const result = await adminClient.query<AdminTransition.Mutation, AdminTransition.Variables>(
  2082. ADMIN_TRANSITION_TO_STATE,
  2083. {
  2084. id,
  2085. state,
  2086. },
  2087. );
  2088. return result.transitionOrderToState;
  2089. }
  2090. async function assertOrderIsUnchanged(order: OrderWithLinesFragment) {
  2091. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  2092. id: order.id,
  2093. });
  2094. expect(order2!.totalWithTax).toBe(order!.totalWithTax);
  2095. expect(order2!.lines.length).toBe(order!.lines.length);
  2096. expect(order2!.surcharges.length).toBe(order!.surcharges.length);
  2097. expect(order2!.totalQuantity).toBe(order!.totalQuantity);
  2098. }
  2099. async function createOrderAndCheckout(
  2100. items: Array<AddItemToOrderMutationVariables & { customFields?: any }>,
  2101. ) {
  2102. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  2103. for (const itemInput of items) {
  2104. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), itemInput);
  2105. }
  2106. await shopClient.query<SetShippingAddress.Mutation, SetShippingAddress.Variables>(
  2107. SET_SHIPPING_ADDRESS,
  2108. {
  2109. input: {
  2110. fullName: 'name',
  2111. streetLine1: '12 the street',
  2112. city: 'foo',
  2113. postalCode: '123456',
  2114. countryCode: 'AT',
  2115. },
  2116. },
  2117. );
  2118. await shopClient.query<SetShippingMethod.Mutation, SetShippingMethod.Variables>(SET_SHIPPING_METHOD, {
  2119. id: testShippingMethodId,
  2120. });
  2121. await shopClient.query<TransitionToState.Mutation, TransitionToState.Variables>(TRANSITION_TO_STATE, {
  2122. state: 'ArrangingPayment',
  2123. });
  2124. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  2125. orderGuard.assertSuccess(order);
  2126. return order;
  2127. }
  2128. async function createOrderAndTransitionToModifyingState(
  2129. items: Array<AddItemToOrderMutationVariables & { customFields?: any }>,
  2130. ): Promise<TestOrderWithPaymentsFragment> {
  2131. const order = await createOrderAndCheckout(items);
  2132. await adminTransitionOrderToState(order.id, 'Modifying');
  2133. return order;
  2134. }
  2135. function getOrderPaymentsTotalWithRefunds(_order: OrderWithModificationsFragment) {
  2136. return _order.payments?.reduce((sum, p) => sum + p.amount - summate(p?.refunds, 'total'), 0) ?? 0;
  2137. }
  2138. });
  2139. export const ORDER_WITH_MODIFICATION_FRAGMENT = gql`
  2140. fragment OrderWithModifications on Order {
  2141. id
  2142. state
  2143. subTotal
  2144. subTotalWithTax
  2145. shipping
  2146. shippingWithTax
  2147. total
  2148. totalWithTax
  2149. lines {
  2150. id
  2151. quantity
  2152. linePrice
  2153. linePriceWithTax
  2154. discountedLinePriceWithTax
  2155. proratedLinePriceWithTax
  2156. discounts {
  2157. description
  2158. amountWithTax
  2159. }
  2160. productVariant {
  2161. id
  2162. name
  2163. }
  2164. items {
  2165. id
  2166. createdAt
  2167. updatedAt
  2168. cancelled
  2169. unitPrice
  2170. }
  2171. }
  2172. surcharges {
  2173. id
  2174. description
  2175. sku
  2176. price
  2177. priceWithTax
  2178. taxRate
  2179. }
  2180. payments {
  2181. id
  2182. transactionId
  2183. state
  2184. amount
  2185. method
  2186. metadata
  2187. refunds {
  2188. id
  2189. state
  2190. total
  2191. paymentId
  2192. }
  2193. }
  2194. modifications {
  2195. id
  2196. note
  2197. priceChange
  2198. isSettled
  2199. orderItems {
  2200. id
  2201. }
  2202. surcharges {
  2203. id
  2204. }
  2205. payment {
  2206. id
  2207. state
  2208. amount
  2209. method
  2210. }
  2211. refund {
  2212. id
  2213. state
  2214. total
  2215. paymentId
  2216. }
  2217. }
  2218. promotions {
  2219. id
  2220. name
  2221. couponCode
  2222. }
  2223. discounts {
  2224. description
  2225. adjustmentSource
  2226. amount
  2227. amountWithTax
  2228. }
  2229. shippingAddress {
  2230. streetLine1
  2231. city
  2232. postalCode
  2233. province
  2234. countryCode
  2235. country
  2236. }
  2237. billingAddress {
  2238. streetLine1
  2239. city
  2240. postalCode
  2241. province
  2242. countryCode
  2243. country
  2244. }
  2245. }
  2246. `;
  2247. export const GET_ORDER_WITH_MODIFICATIONS = gql`
  2248. query GetOrderWithModifications($id: ID!) {
  2249. order(id: $id) {
  2250. ...OrderWithModifications
  2251. }
  2252. }
  2253. ${ORDER_WITH_MODIFICATION_FRAGMENT}
  2254. `;
  2255. export const MODIFY_ORDER = gql`
  2256. mutation ModifyOrder($input: ModifyOrderInput!) {
  2257. modifyOrder(input: $input) {
  2258. ...OrderWithModifications
  2259. ... on ErrorResult {
  2260. errorCode
  2261. message
  2262. }
  2263. }
  2264. }
  2265. ${ORDER_WITH_MODIFICATION_FRAGMENT}
  2266. `;
  2267. export const ADD_MANUAL_PAYMENT = gql`
  2268. mutation AddManualPayment($input: ManualPaymentInput!) {
  2269. addManualPaymentToOrder(input: $input) {
  2270. ...OrderWithModifications
  2271. ... on ErrorResult {
  2272. errorCode
  2273. message
  2274. }
  2275. }
  2276. }
  2277. ${ORDER_WITH_MODIFICATION_FRAGMENT}
  2278. `;
  2279. // Note, we don't use the gql tag around these due to the customFields which
  2280. // would cause a codegen error.
  2281. const ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS = `
  2282. mutation AddItemToOrder($productVariantId: ID!, $quantity: Int!, $customFields: OrderLineCustomFieldsInput) {
  2283. addItemToOrder(productVariantId: $productVariantId, quantity: $quantity, customFields: $customFields) {
  2284. ...on Order { id }
  2285. }
  2286. }
  2287. `;
  2288. const GET_ORDER_WITH_CUSTOM_FIELDS = `
  2289. query GetOrderCustomFields($id: ID!) {
  2290. order(id: $id) {
  2291. customFields { points }
  2292. lines { id, customFields { color } }
  2293. }
  2294. }
  2295. `;