order-modification.e2e-spec.ts 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450
  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 {
  5. defaultShippingCalculator,
  6. defaultShippingEligibilityChecker,
  7. mergeConfig,
  8. ShippingCalculator,
  9. } from '@vendure/core';
  10. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  11. import gql from 'graphql-tag';
  12. import path from 'path';
  13. import { initialData } from '../../../e2e-common/e2e-initial-data';
  14. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  15. import { manualFulfillmentHandler } from '../src/config/fulfillment/manual-fulfillment-handler';
  16. import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
  17. import {
  18. AddManualPayment,
  19. AdminTransition,
  20. CreateShippingMethod,
  21. ErrorCode,
  22. GetOrder,
  23. GetOrderHistory,
  24. GetOrderWithModifications,
  25. GlobalFlag,
  26. HistoryEntryType,
  27. LanguageCode,
  28. ModifyOrder,
  29. OrderFragment,
  30. OrderWithLinesFragment,
  31. OrderWithModificationsFragment,
  32. UpdateProductVariants,
  33. } from './graphql/generated-e2e-admin-types';
  34. import {
  35. AddItemToOrderMutationVariables,
  36. SetShippingAddress,
  37. SetShippingMethod,
  38. TestOrderWithPaymentsFragment,
  39. TransitionToState,
  40. UpdatedOrderFragment,
  41. } from './graphql/generated-e2e-shop-types';
  42. import {
  43. ADMIN_TRANSITION_TO_STATE,
  44. CREATE_SHIPPING_METHOD,
  45. GET_ORDER,
  46. GET_ORDER_HISTORY,
  47. UPDATE_PRODUCT_VARIANTS,
  48. } from './graphql/shared-definitions';
  49. import { SET_SHIPPING_ADDRESS, SET_SHIPPING_METHOD, TRANSITION_TO_STATE } from './graphql/shop-definitions';
  50. import { addPaymentToOrder, proceedToArrangingPayment } from './utils/test-order-utils';
  51. const SHIPPING_GB = 500;
  52. const SHIPPING_US = 1000;
  53. const SHIPPING_OTHER = 750;
  54. const testCalculator = new ShippingCalculator({
  55. code: 'test-calculator',
  56. description: [{ languageCode: LanguageCode.en, value: 'Has metadata' }],
  57. args: {},
  58. calculate: (ctx, order, args) => {
  59. let price;
  60. switch (order.shippingAddress.countryCode) {
  61. case 'GB':
  62. price = SHIPPING_GB;
  63. break;
  64. case 'US':
  65. price = SHIPPING_US;
  66. break;
  67. default:
  68. price = SHIPPING_OTHER;
  69. }
  70. return {
  71. price,
  72. priceIncludesTax: true,
  73. taxRate: 20,
  74. };
  75. },
  76. });
  77. describe('Order modification', () => {
  78. const { server, adminClient, shopClient } = createTestEnvironment(
  79. mergeConfig(testConfig, {
  80. paymentOptions: {
  81. paymentMethodHandlers: [testSuccessfulPaymentMethod],
  82. },
  83. shippingOptions: {
  84. shippingCalculators: [defaultShippingCalculator, testCalculator],
  85. },
  86. customFields: {
  87. Order: [{ name: 'points', type: 'int', defaultValue: 0 }],
  88. OrderLine: [{ name: 'color', type: 'string', nullable: true }],
  89. },
  90. }),
  91. );
  92. let orderId: string;
  93. let testShippingMethodId: string;
  94. const orderGuard: ErrorResultGuard<
  95. UpdatedOrderFragment | OrderWithModificationsFragment | OrderFragment
  96. > = createErrorResultGuard(input => !!input.id);
  97. beforeAll(async () => {
  98. await server.init({
  99. initialData: {
  100. ...initialData,
  101. paymentMethods: [
  102. {
  103. name: testSuccessfulPaymentMethod.code,
  104. handler: { code: testSuccessfulPaymentMethod.code, arguments: [] },
  105. },
  106. ],
  107. },
  108. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  109. customerCount: 2,
  110. });
  111. await adminClient.asSuperAdmin();
  112. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  113. UPDATE_PRODUCT_VARIANTS,
  114. {
  115. input: [
  116. {
  117. id: 'T_1',
  118. trackInventory: GlobalFlag.TRUE,
  119. },
  120. {
  121. id: 'T_2',
  122. trackInventory: GlobalFlag.TRUE,
  123. },
  124. {
  125. id: 'T_3',
  126. trackInventory: GlobalFlag.TRUE,
  127. },
  128. ],
  129. },
  130. );
  131. const { createShippingMethod } = await adminClient.query<
  132. CreateShippingMethod.Mutation,
  133. CreateShippingMethod.Variables
  134. >(CREATE_SHIPPING_METHOD, {
  135. input: {
  136. code: 'new-method',
  137. fulfillmentHandler: manualFulfillmentHandler.code,
  138. checker: {
  139. code: defaultShippingEligibilityChecker.code,
  140. arguments: [
  141. {
  142. name: 'orderMinimum',
  143. value: '0',
  144. },
  145. ],
  146. },
  147. calculator: {
  148. code: testCalculator.code,
  149. arguments: [],
  150. },
  151. translations: [{ languageCode: LanguageCode.en, name: 'test method', description: '' }],
  152. },
  153. });
  154. testShippingMethodId = createShippingMethod.id;
  155. // create an order and check out
  156. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  157. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  158. productVariantId: 'T_1',
  159. quantity: 1,
  160. customFields: {
  161. color: 'green',
  162. },
  163. } as any);
  164. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), {
  165. productVariantId: 'T_4',
  166. quantity: 2,
  167. });
  168. await proceedToArrangingPayment(shopClient);
  169. const result = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  170. orderGuard.assertSuccess(result);
  171. orderId = result.id;
  172. }, TEST_SETUP_TIMEOUT_MS);
  173. afterAll(async () => {
  174. await server.destroy();
  175. });
  176. it('modifyOrder returns error result when not in Modifying state', async () => {
  177. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  178. id: orderId,
  179. });
  180. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  181. MODIFY_ORDER,
  182. {
  183. input: {
  184. dryRun: false,
  185. orderId,
  186. adjustOrderLines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 3 })),
  187. },
  188. },
  189. );
  190. orderGuard.assertErrorResult(modifyOrder);
  191. expect(modifyOrder.errorCode).toBe(ErrorCode.ORDER_MODIFICATION_STATE_ERROR);
  192. });
  193. it('transition to Modifying state', async () => {
  194. const { transitionOrderToState } = await adminClient.query<
  195. AdminTransition.Mutation,
  196. AdminTransition.Variables
  197. >(ADMIN_TRANSITION_TO_STATE, {
  198. id: orderId,
  199. state: 'Modifying',
  200. });
  201. orderGuard.assertSuccess(transitionOrderToState);
  202. expect(transitionOrderToState.state).toBe('Modifying');
  203. });
  204. describe('error cases', () => {
  205. it('no changes specified error', async () => {
  206. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  207. MODIFY_ORDER,
  208. {
  209. input: {
  210. dryRun: false,
  211. orderId,
  212. },
  213. },
  214. );
  215. orderGuard.assertErrorResult(modifyOrder);
  216. expect(modifyOrder.errorCode).toBe(ErrorCode.NO_CHANGES_SPECIFIED_ERROR);
  217. });
  218. it('no refund paymentId specified', async () => {
  219. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  220. id: orderId,
  221. });
  222. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  223. MODIFY_ORDER,
  224. {
  225. input: {
  226. dryRun: false,
  227. orderId,
  228. surcharges: [{ price: -500, priceIncludesTax: true, description: 'Discount' }],
  229. },
  230. },
  231. );
  232. orderGuard.assertErrorResult(modifyOrder);
  233. expect(modifyOrder.errorCode).toBe(ErrorCode.REFUND_PAYMENT_ID_MISSING_ERROR);
  234. await assertOrderIsUnchanged(order!);
  235. });
  236. it('addItems negative quantity', async () => {
  237. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  238. id: orderId,
  239. });
  240. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  241. MODIFY_ORDER,
  242. {
  243. input: {
  244. dryRun: false,
  245. orderId,
  246. addItems: [{ productVariantId: 'T_3', quantity: -1 }],
  247. },
  248. },
  249. );
  250. orderGuard.assertErrorResult(modifyOrder);
  251. expect(modifyOrder.errorCode).toBe(ErrorCode.NEGATIVE_QUANTITY_ERROR);
  252. await assertOrderIsUnchanged(order!);
  253. });
  254. it('adjustOrderLines negative quantity', async () => {
  255. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  256. id: orderId,
  257. });
  258. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  259. MODIFY_ORDER,
  260. {
  261. input: {
  262. dryRun: false,
  263. orderId,
  264. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: -1 }],
  265. },
  266. },
  267. );
  268. orderGuard.assertErrorResult(modifyOrder);
  269. expect(modifyOrder.errorCode).toBe(ErrorCode.NEGATIVE_QUANTITY_ERROR);
  270. await assertOrderIsUnchanged(order!);
  271. });
  272. it('addItems insufficient stock', async () => {
  273. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  274. id: orderId,
  275. });
  276. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  277. MODIFY_ORDER,
  278. {
  279. input: {
  280. dryRun: false,
  281. orderId,
  282. addItems: [{ productVariantId: 'T_3', quantity: 500 }],
  283. },
  284. },
  285. );
  286. orderGuard.assertErrorResult(modifyOrder);
  287. expect(modifyOrder.errorCode).toBe(ErrorCode.INSUFFICIENT_STOCK_ERROR);
  288. await assertOrderIsUnchanged(order!);
  289. });
  290. it('adjustOrderLines insufficient stock', async () => {
  291. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  292. id: orderId,
  293. });
  294. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  295. MODIFY_ORDER,
  296. {
  297. input: {
  298. dryRun: false,
  299. orderId,
  300. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 500 }],
  301. },
  302. },
  303. );
  304. orderGuard.assertErrorResult(modifyOrder);
  305. expect(modifyOrder.errorCode).toBe(ErrorCode.INSUFFICIENT_STOCK_ERROR);
  306. await assertOrderIsUnchanged(order!);
  307. });
  308. it('addItems order limit', async () => {
  309. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  310. id: orderId,
  311. });
  312. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  313. MODIFY_ORDER,
  314. {
  315. input: {
  316. dryRun: false,
  317. orderId,
  318. addItems: [{ productVariantId: 'T_4', quantity: 9999 }],
  319. },
  320. },
  321. );
  322. orderGuard.assertErrorResult(modifyOrder);
  323. expect(modifyOrder.errorCode).toBe(ErrorCode.ORDER_LIMIT_ERROR);
  324. await assertOrderIsUnchanged(order!);
  325. });
  326. it('adjustOrderLines order limit', async () => {
  327. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  328. id: orderId,
  329. });
  330. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  331. MODIFY_ORDER,
  332. {
  333. input: {
  334. dryRun: false,
  335. orderId,
  336. adjustOrderLines: [{ orderLineId: order!.lines[1].id, quantity: 9999 }],
  337. },
  338. },
  339. );
  340. orderGuard.assertErrorResult(modifyOrder);
  341. expect(modifyOrder.errorCode).toBe(ErrorCode.ORDER_LIMIT_ERROR);
  342. await assertOrderIsUnchanged(order!);
  343. });
  344. });
  345. describe('dry run', () => {
  346. it('addItems', async () => {
  347. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  348. id: orderId,
  349. });
  350. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  351. MODIFY_ORDER,
  352. {
  353. input: {
  354. dryRun: true,
  355. orderId,
  356. addItems: [{ productVariantId: 'T_5', quantity: 1 }],
  357. },
  358. },
  359. );
  360. orderGuard.assertSuccess(modifyOrder);
  361. const expectedTotal = order!.totalWithTax + Math.round(14374 * 1.2); // price of variant T_5
  362. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  363. expect(modifyOrder.lines.length).toBe(order!.lines.length + 1);
  364. await assertOrderIsUnchanged(order!);
  365. });
  366. it('addItems with existing variant id increments existing OrderLine', async () => {
  367. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  368. id: orderId,
  369. });
  370. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  371. MODIFY_ORDER,
  372. {
  373. input: {
  374. dryRun: true,
  375. orderId,
  376. addItems: [
  377. { productVariantId: 'T_1', quantity: 1, customFields: { color: 'green' } } as any,
  378. ],
  379. },
  380. },
  381. );
  382. orderGuard.assertSuccess(modifyOrder);
  383. const lineT1 = modifyOrder.lines.find(l => l.productVariant.id === 'T_1');
  384. expect(modifyOrder.lines.length).toBe(2);
  385. expect(lineT1?.quantity).toBe(2);
  386. await assertOrderIsUnchanged(order!);
  387. });
  388. it('addItems with existing variant id but different customFields adds new OrderLine', async () => {
  389. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  390. id: orderId,
  391. });
  392. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  393. MODIFY_ORDER,
  394. {
  395. input: {
  396. dryRun: true,
  397. orderId,
  398. addItems: [
  399. { productVariantId: 'T_1', quantity: 1, customFields: { color: 'blue' } } as any,
  400. ],
  401. },
  402. },
  403. );
  404. orderGuard.assertSuccess(modifyOrder);
  405. const lineT1 = modifyOrder.lines.find(l => l.productVariant.id === 'T_1');
  406. expect(modifyOrder.lines.length).toBe(3);
  407. expect(
  408. modifyOrder.lines.map(l => ({ variantId: l.productVariant.id, quantity: l.quantity })),
  409. ).toEqual([
  410. { variantId: 'T_1', quantity: 1 },
  411. { variantId: 'T_4', quantity: 2 },
  412. { variantId: 'T_1', quantity: 1 },
  413. ]);
  414. await assertOrderIsUnchanged(order!);
  415. });
  416. it('adjustOrderLines up', async () => {
  417. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  418. id: orderId,
  419. });
  420. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  421. MODIFY_ORDER,
  422. {
  423. input: {
  424. dryRun: true,
  425. orderId,
  426. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 3 }],
  427. },
  428. },
  429. );
  430. orderGuard.assertSuccess(modifyOrder);
  431. const expectedTotal = order!.totalWithTax + order!.lines[0].unitPriceWithTax * 2;
  432. expect(modifyOrder.lines[0].items.length).toBe(3);
  433. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  434. await assertOrderIsUnchanged(order!);
  435. });
  436. it('adjustOrderLines down', async () => {
  437. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  438. id: orderId,
  439. });
  440. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  441. MODIFY_ORDER,
  442. {
  443. input: {
  444. dryRun: true,
  445. orderId,
  446. adjustOrderLines: [{ orderLineId: order!.lines[1].id, quantity: 1 }],
  447. },
  448. },
  449. );
  450. orderGuard.assertSuccess(modifyOrder);
  451. const expectedTotal = order!.totalWithTax - order!.lines[1].unitPriceWithTax;
  452. expect(modifyOrder.lines[1].items.filter(i => i.cancelled).length).toBe(1);
  453. expect(modifyOrder.lines[1].items.filter(i => !i.cancelled).length).toBe(1);
  454. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  455. await assertOrderIsUnchanged(order!);
  456. });
  457. it('adjustOrderLines to zero', async () => {
  458. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  459. id: orderId,
  460. });
  461. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  462. MODIFY_ORDER,
  463. {
  464. input: {
  465. dryRun: true,
  466. orderId,
  467. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 0 }],
  468. },
  469. },
  470. );
  471. orderGuard.assertSuccess(modifyOrder);
  472. const expectedTotal = order!.totalWithTax - order!.lines[0].linePriceWithTax;
  473. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  474. expect(modifyOrder.lines[0].items.every(i => i.cancelled)).toBe(true);
  475. await assertOrderIsUnchanged(order!);
  476. });
  477. it('surcharge positive', async () => {
  478. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  479. id: orderId,
  480. });
  481. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  482. MODIFY_ORDER,
  483. {
  484. input: {
  485. dryRun: true,
  486. orderId,
  487. surcharges: [
  488. {
  489. description: 'extra fee',
  490. sku: '123',
  491. price: 300,
  492. priceIncludesTax: true,
  493. taxRate: 20,
  494. taxDescription: 'VAT',
  495. },
  496. ],
  497. },
  498. },
  499. );
  500. orderGuard.assertSuccess(modifyOrder);
  501. const expectedTotal = order!.totalWithTax + 300;
  502. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  503. expect(modifyOrder.surcharges.map(s => omit(s, ['id']))).toEqual([
  504. {
  505. description: 'extra fee',
  506. sku: '123',
  507. price: 250,
  508. priceWithTax: 300,
  509. taxRate: 20,
  510. },
  511. ]);
  512. await assertOrderIsUnchanged(order!);
  513. });
  514. it('surcharge negative', async () => {
  515. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  516. id: orderId,
  517. });
  518. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  519. MODIFY_ORDER,
  520. {
  521. input: {
  522. dryRun: true,
  523. orderId,
  524. surcharges: [
  525. {
  526. description: 'special discount',
  527. sku: '123',
  528. price: -300,
  529. priceIncludesTax: true,
  530. taxRate: 20,
  531. taxDescription: 'VAT',
  532. },
  533. ],
  534. },
  535. },
  536. );
  537. orderGuard.assertSuccess(modifyOrder);
  538. const expectedTotal = order!.totalWithTax + -300;
  539. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  540. expect(modifyOrder.surcharges.map(s => omit(s, ['id']))).toEqual([
  541. {
  542. description: 'special discount',
  543. sku: '123',
  544. price: -250,
  545. priceWithTax: -300,
  546. taxRate: 20,
  547. },
  548. ]);
  549. await assertOrderIsUnchanged(order!);
  550. });
  551. it('does not add a history entry', async () => {
  552. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  553. id: orderId,
  554. });
  555. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  556. MODIFY_ORDER,
  557. {
  558. input: {
  559. dryRun: true,
  560. orderId,
  561. addItems: [{ productVariantId: 'T_5', quantity: 1 }],
  562. },
  563. },
  564. );
  565. orderGuard.assertSuccess(modifyOrder);
  566. const { order: history } = await adminClient.query<
  567. GetOrderHistory.Query,
  568. GetOrderHistory.Variables
  569. >(GET_ORDER_HISTORY, {
  570. id: orderId,
  571. options: { filter: { type: { eq: HistoryEntryType.ORDER_MODIFIED } } },
  572. });
  573. orderGuard.assertSuccess(history);
  574. expect(history.history.totalItems).toBe(0);
  575. });
  576. });
  577. describe('wet run', () => {
  578. async function assertModifiedOrderIsPersisted(order: OrderWithModificationsFragment) {
  579. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  580. id: order.id,
  581. });
  582. expect(order2!.totalWithTax).toBe(order!.totalWithTax);
  583. expect(order2!.lines.length).toBe(order!.lines.length);
  584. expect(order2!.surcharges.length).toBe(order!.surcharges.length);
  585. expect(order2!.payments!.length).toBe(order!.payments!.length);
  586. expect(order2!.payments!.map(p => pick(p, ['id', 'amount', 'method']))).toEqual(
  587. order!.payments!.map(p => pick(p, ['id', 'amount', 'method'])),
  588. );
  589. }
  590. it('addItems', async () => {
  591. const order = await createOrderAndTransitionToModifyingState([
  592. {
  593. productVariantId: 'T_1',
  594. quantity: 1,
  595. },
  596. ]);
  597. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  598. MODIFY_ORDER,
  599. {
  600. input: {
  601. dryRun: false,
  602. orderId: order.id,
  603. addItems: [{ productVariantId: 'T_5', quantity: 1 }],
  604. },
  605. },
  606. );
  607. orderGuard.assertSuccess(modifyOrder);
  608. const priceDelta = Math.round(14374 * 1.2); // price of variant T_5
  609. const expectedTotal = order!.totalWithTax + priceDelta;
  610. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  611. expect(modifyOrder.lines.length).toBe(order!.lines.length + 1);
  612. expect(modifyOrder.modifications.length).toBe(1);
  613. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  614. expect(modifyOrder.modifications[0].orderItems?.length).toBe(1);
  615. expect(modifyOrder.modifications[0].orderItems?.map(i => i.id)).toEqual([
  616. modifyOrder.lines[1].items[0].id,
  617. ]);
  618. await assertModifiedOrderIsPersisted(modifyOrder);
  619. });
  620. it('adjustOrderLines up', async () => {
  621. const order = await createOrderAndTransitionToModifyingState([
  622. {
  623. productVariantId: 'T_1',
  624. quantity: 1,
  625. },
  626. ]);
  627. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  628. MODIFY_ORDER,
  629. {
  630. input: {
  631. dryRun: false,
  632. orderId: order.id,
  633. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 2 }],
  634. },
  635. },
  636. );
  637. orderGuard.assertSuccess(modifyOrder);
  638. const priceDelta = order!.lines[0].unitPriceWithTax;
  639. const expectedTotal = order!.totalWithTax + priceDelta;
  640. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  641. expect(modifyOrder.lines[0].quantity).toBe(2);
  642. expect(modifyOrder.modifications.length).toBe(1);
  643. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  644. expect(modifyOrder.modifications[0].orderItems?.length).toBe(1);
  645. expect(
  646. modifyOrder.lines[0].items
  647. .map(i => i.id)
  648. .includes(modifyOrder.modifications?.[0].orderItems?.[0].id as string),
  649. ).toBe(true);
  650. await assertModifiedOrderIsPersisted(modifyOrder);
  651. });
  652. it('adjustOrderLines down', async () => {
  653. const order = await createOrderAndTransitionToModifyingState([
  654. {
  655. productVariantId: 'T_1',
  656. quantity: 2,
  657. },
  658. ]);
  659. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  660. MODIFY_ORDER,
  661. {
  662. input: {
  663. dryRun: false,
  664. orderId: order.id,
  665. adjustOrderLines: [{ orderLineId: order!.lines[0].id, quantity: 1 }],
  666. refund: { paymentId: order!.payments![0].id },
  667. },
  668. },
  669. );
  670. orderGuard.assertSuccess(modifyOrder);
  671. const priceDelta = -order!.lines[0].unitPriceWithTax;
  672. const expectedTotal = order!.totalWithTax + priceDelta;
  673. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  674. expect(modifyOrder.lines[0].quantity).toBe(1);
  675. expect(modifyOrder.payments?.length).toBe(1);
  676. expect(modifyOrder.payments?.[0].refunds.length).toBe(1);
  677. expect(modifyOrder.payments?.[0].refunds[0]).toEqual({
  678. id: 'T_1',
  679. state: 'Pending',
  680. total: -priceDelta,
  681. paymentId: modifyOrder.payments?.[0].id,
  682. });
  683. expect(modifyOrder.modifications.length).toBe(1);
  684. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  685. expect(modifyOrder.modifications[0].surcharges).toEqual(modifyOrder.surcharges.map(pick(['id'])));
  686. expect(modifyOrder.modifications[0].orderItems?.length).toBe(1);
  687. expect(
  688. modifyOrder.lines[0].items
  689. .map(i => i.id)
  690. .includes(modifyOrder.modifications?.[0].orderItems?.[0].id as string),
  691. ).toBe(true);
  692. await assertModifiedOrderIsPersisted(modifyOrder);
  693. });
  694. it('adjustOrderLines with changed customField value', async () => {
  695. const order = await createOrderAndTransitionToModifyingState([
  696. {
  697. productVariantId: 'T_1',
  698. quantity: 1,
  699. customFields: {
  700. color: 'green',
  701. },
  702. },
  703. ]);
  704. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  705. MODIFY_ORDER,
  706. {
  707. input: {
  708. dryRun: false,
  709. orderId: order.id,
  710. adjustOrderLines: [
  711. {
  712. orderLineId: order!.lines[0].id,
  713. quantity: 1,
  714. customFields: { color: 'black' },
  715. } as any,
  716. ],
  717. },
  718. },
  719. );
  720. orderGuard.assertSuccess(modifyOrder);
  721. expect(modifyOrder.lines.length).toBe(1);
  722. const { order: orderWithLines } = await adminClient.query(gql(GET_ORDER_WITH_CUSTOM_FIELDS), {
  723. id: order.id,
  724. });
  725. expect(orderWithLines.lines[0]).toEqual({
  726. id: order!.lines[0].id,
  727. customFields: { color: 'black' },
  728. });
  729. });
  730. it('adjustOrderLines handles quantity correctly', async () => {
  731. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  732. UPDATE_PRODUCT_VARIANTS,
  733. {
  734. input: [
  735. {
  736. id: 'T_6',
  737. stockOnHand: 1,
  738. trackInventory: GlobalFlag.TRUE,
  739. },
  740. ],
  741. },
  742. );
  743. const order = await createOrderAndTransitionToModifyingState([
  744. {
  745. productVariantId: 'T_6',
  746. quantity: 1,
  747. },
  748. ]);
  749. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  750. MODIFY_ORDER,
  751. {
  752. input: {
  753. dryRun: false,
  754. orderId: order.id,
  755. adjustOrderLines: [
  756. {
  757. orderLineId: order.lines[0].id,
  758. quantity: 1,
  759. },
  760. ],
  761. updateShippingAddress: {
  762. fullName: 'Jim',
  763. },
  764. },
  765. },
  766. );
  767. orderGuard.assertSuccess(modifyOrder);
  768. });
  769. it('surcharge positive', async () => {
  770. const order = await createOrderAndTransitionToModifyingState([
  771. {
  772. productVariantId: 'T_1',
  773. quantity: 1,
  774. },
  775. ]);
  776. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  777. MODIFY_ORDER,
  778. {
  779. input: {
  780. dryRun: false,
  781. orderId: order.id,
  782. surcharges: [
  783. {
  784. description: 'extra fee',
  785. sku: '123',
  786. price: 300,
  787. priceIncludesTax: true,
  788. taxRate: 20,
  789. taxDescription: 'VAT',
  790. },
  791. ],
  792. },
  793. },
  794. );
  795. orderGuard.assertSuccess(modifyOrder);
  796. const priceDelta = 300;
  797. const expectedTotal = order!.totalWithTax + priceDelta;
  798. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  799. expect(modifyOrder.surcharges.map(s => omit(s, ['id']))).toEqual([
  800. {
  801. description: 'extra fee',
  802. sku: '123',
  803. price: 250,
  804. priceWithTax: 300,
  805. taxRate: 20,
  806. },
  807. ]);
  808. expect(modifyOrder.modifications.length).toBe(1);
  809. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  810. expect(modifyOrder.modifications[0].surcharges).toEqual(modifyOrder.surcharges.map(pick(['id'])));
  811. await assertModifiedOrderIsPersisted(modifyOrder);
  812. });
  813. it('surcharge negative', 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: 'special discount',
  829. sku: '123',
  830. price: -300,
  831. priceIncludesTax: true,
  832. taxRate: 20,
  833. taxDescription: 'VAT',
  834. },
  835. ],
  836. refund: {
  837. paymentId: order!.payments![0].id,
  838. },
  839. },
  840. },
  841. );
  842. orderGuard.assertSuccess(modifyOrder);
  843. const expectedTotal = order!.totalWithTax + -300;
  844. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  845. expect(modifyOrder.surcharges.map(s => omit(s, ['id']))).toEqual([
  846. {
  847. description: 'special discount',
  848. sku: '123',
  849. price: -250,
  850. priceWithTax: -300,
  851. taxRate: 20,
  852. },
  853. ]);
  854. expect(modifyOrder.modifications.length).toBe(1);
  855. expect(modifyOrder.modifications[0].priceChange).toBe(-300);
  856. await assertModifiedOrderIsPersisted(modifyOrder);
  857. });
  858. it('update updateShippingAddress, recalculate shipping', async () => {
  859. const order = await createOrderAndTransitionToModifyingState([
  860. {
  861. productVariantId: 'T_1',
  862. quantity: 1,
  863. },
  864. ]);
  865. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  866. MODIFY_ORDER,
  867. {
  868. input: {
  869. dryRun: false,
  870. orderId: order!.id,
  871. updateShippingAddress: {
  872. countryCode: 'US',
  873. },
  874. options: {
  875. recalculateShipping: true,
  876. },
  877. },
  878. },
  879. );
  880. orderGuard.assertSuccess(modifyOrder);
  881. const priceDelta = SHIPPING_US - SHIPPING_OTHER;
  882. const expectedTotal = order!.totalWithTax + priceDelta;
  883. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  884. expect(modifyOrder.shippingAddress?.countryCode).toBe('US');
  885. expect(modifyOrder.modifications.length).toBe(1);
  886. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  887. await assertModifiedOrderIsPersisted(modifyOrder);
  888. });
  889. it('update updateShippingAddress, do not recalculate shipping', async () => {
  890. const order = await createOrderAndTransitionToModifyingState([
  891. {
  892. productVariantId: 'T_1',
  893. quantity: 1,
  894. },
  895. ]);
  896. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  897. MODIFY_ORDER,
  898. {
  899. input: {
  900. dryRun: false,
  901. orderId: order!.id,
  902. updateShippingAddress: {
  903. countryCode: 'US',
  904. },
  905. options: {
  906. recalculateShipping: false,
  907. },
  908. },
  909. },
  910. );
  911. orderGuard.assertSuccess(modifyOrder);
  912. const priceDelta = 0;
  913. const expectedTotal = order!.totalWithTax + priceDelta;
  914. expect(modifyOrder.totalWithTax).toBe(expectedTotal);
  915. expect(modifyOrder.shippingAddress?.countryCode).toBe('US');
  916. expect(modifyOrder.modifications.length).toBe(1);
  917. expect(modifyOrder.modifications[0].priceChange).toBe(priceDelta);
  918. await assertModifiedOrderIsPersisted(modifyOrder);
  919. });
  920. it('update Order customFields', async () => {
  921. const order = await createOrderAndTransitionToModifyingState([
  922. {
  923. productVariantId: 'T_1',
  924. quantity: 1,
  925. },
  926. ]);
  927. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  928. MODIFY_ORDER,
  929. {
  930. input: {
  931. dryRun: false,
  932. orderId: order.id,
  933. customFields: {
  934. points: 42,
  935. },
  936. } as any,
  937. },
  938. );
  939. orderGuard.assertSuccess(modifyOrder);
  940. const { order: orderWithCustomFields } = await adminClient.query(
  941. gql(GET_ORDER_WITH_CUSTOM_FIELDS),
  942. { id: order.id },
  943. );
  944. expect(orderWithCustomFields.customFields).toEqual({
  945. points: 42,
  946. });
  947. });
  948. it('adds a history entry', async () => {
  949. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  950. id: orderId,
  951. });
  952. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  953. MODIFY_ORDER,
  954. {
  955. input: {
  956. dryRun: false,
  957. orderId: order!.id,
  958. addItems: [{ productVariantId: 'T_5', quantity: 1 }],
  959. },
  960. },
  961. );
  962. orderGuard.assertSuccess(modifyOrder);
  963. const { order: history } = await adminClient.query<
  964. GetOrderHistory.Query,
  965. GetOrderHistory.Variables
  966. >(GET_ORDER_HISTORY, {
  967. id: orderId,
  968. options: { filter: { type: { eq: HistoryEntryType.ORDER_MODIFIED } } },
  969. });
  970. orderGuard.assertSuccess(history);
  971. expect(history.history.totalItems).toBe(1);
  972. expect(history.history.items[0].data).toEqual({
  973. modificationId: modifyOrder.modifications[0].id,
  974. });
  975. });
  976. });
  977. describe('additional payment handling', () => {
  978. let orderId2: string;
  979. beforeAll(async () => {
  980. const order = await createOrderAndTransitionToModifyingState([
  981. {
  982. productVariantId: 'T_1',
  983. quantity: 1,
  984. },
  985. ]);
  986. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  987. MODIFY_ORDER,
  988. {
  989. input: {
  990. dryRun: false,
  991. orderId: order.id,
  992. surcharges: [
  993. {
  994. description: 'extra fee',
  995. sku: '123',
  996. price: 300,
  997. priceIncludesTax: true,
  998. taxRate: 20,
  999. taxDescription: 'VAT',
  1000. },
  1001. ],
  1002. },
  1003. },
  1004. );
  1005. orderGuard.assertSuccess(modifyOrder);
  1006. orderId2 = modifyOrder.id;
  1007. });
  1008. it('cannot transition back to original state if no payment is set', async () => {
  1009. const { transitionOrderToState } = await adminClient.query<
  1010. AdminTransition.Mutation,
  1011. AdminTransition.Variables
  1012. >(ADMIN_TRANSITION_TO_STATE, {
  1013. id: orderId2,
  1014. state: 'PaymentSettled',
  1015. });
  1016. orderGuard.assertErrorResult(transitionOrderToState);
  1017. expect(transitionOrderToState!.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
  1018. expect(transitionOrderToState!.transitionError).toBe(
  1019. `Can only transition to the "ArrangingAdditionalPayment" state`,
  1020. );
  1021. });
  1022. it('can transition to ArrangingAdditionalPayment state', async () => {
  1023. const { transitionOrderToState } = await adminClient.query<
  1024. AdminTransition.Mutation,
  1025. AdminTransition.Variables
  1026. >(ADMIN_TRANSITION_TO_STATE, {
  1027. id: orderId2,
  1028. state: 'ArrangingAdditionalPayment',
  1029. });
  1030. orderGuard.assertSuccess(transitionOrderToState);
  1031. expect(transitionOrderToState!.state).toBe('ArrangingAdditionalPayment');
  1032. });
  1033. it('cannot transition from ArrangingAdditionalPayment when total not covered by Payments', async () => {
  1034. const { transitionOrderToState } = await adminClient.query<
  1035. AdminTransition.Mutation,
  1036. AdminTransition.Variables
  1037. >(ADMIN_TRANSITION_TO_STATE, {
  1038. id: orderId2,
  1039. state: 'PaymentSettled',
  1040. });
  1041. orderGuard.assertErrorResult(transitionOrderToState);
  1042. expect(transitionOrderToState!.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
  1043. expect(transitionOrderToState!.transitionError).toBe(
  1044. `Cannot transition away from "ArrangingAdditionalPayment" unless Order total is covered by Payments`,
  1045. );
  1046. });
  1047. it('addManualPaymentToOrder', async () => {
  1048. const { addManualPaymentToOrder } = await adminClient.query<
  1049. AddManualPayment.Mutation,
  1050. AddManualPayment.Variables
  1051. >(ADD_MANUAL_PAYMENT, {
  1052. input: {
  1053. orderId: orderId2,
  1054. method: 'test',
  1055. transactionId: 'ABC123',
  1056. metadata: {
  1057. foo: 'bar',
  1058. },
  1059. },
  1060. });
  1061. orderGuard.assertSuccess(addManualPaymentToOrder);
  1062. expect(addManualPaymentToOrder.payments?.length).toBe(2);
  1063. expect(omit(addManualPaymentToOrder.payments![1], ['id'])).toEqual({
  1064. transactionId: 'ABC123',
  1065. state: 'Settled',
  1066. amount: 300,
  1067. method: 'test',
  1068. metadata: {
  1069. foo: 'bar',
  1070. },
  1071. refunds: [],
  1072. });
  1073. expect(addManualPaymentToOrder.modifications[0].isSettled).toBe(true);
  1074. expect(addManualPaymentToOrder.modifications[0].payment?.id).toBe(
  1075. addManualPaymentToOrder.payments![1].id,
  1076. );
  1077. });
  1078. it('transition back to original state', async () => {
  1079. const { transitionOrderToState } = await adminClient.query<
  1080. AdminTransition.Mutation,
  1081. AdminTransition.Variables
  1082. >(ADMIN_TRANSITION_TO_STATE, {
  1083. id: orderId2,
  1084. state: 'PaymentSettled',
  1085. });
  1086. orderGuard.assertSuccess(transitionOrderToState);
  1087. expect(transitionOrderToState.state).toBe('PaymentSettled');
  1088. });
  1089. });
  1090. describe('refund handling', () => {
  1091. let orderId3: string;
  1092. beforeAll(async () => {
  1093. const order = await createOrderAndTransitionToModifyingState([
  1094. {
  1095. productVariantId: 'T_1',
  1096. quantity: 1,
  1097. },
  1098. ]);
  1099. const { modifyOrder } = await adminClient.query<ModifyOrder.Mutation, ModifyOrder.Variables>(
  1100. MODIFY_ORDER,
  1101. {
  1102. input: {
  1103. dryRun: false,
  1104. orderId: order.id,
  1105. surcharges: [
  1106. {
  1107. description: 'discount',
  1108. sku: '123',
  1109. price: -300,
  1110. priceIncludesTax: true,
  1111. taxRate: 20,
  1112. taxDescription: 'VAT',
  1113. },
  1114. ],
  1115. refund: {
  1116. paymentId: order.payments![0].id,
  1117. reason: 'discount',
  1118. },
  1119. },
  1120. },
  1121. );
  1122. orderGuard.assertSuccess(modifyOrder);
  1123. orderId3 = modifyOrder.id;
  1124. });
  1125. it('modification is settled', async () => {
  1126. const { order } = await adminClient.query<
  1127. GetOrderWithModifications.Query,
  1128. GetOrderWithModifications.Variables
  1129. >(GET_ORDER_WITH_MODIFICATIONS, { id: orderId3 });
  1130. expect(order?.modifications.length).toBe(1);
  1131. expect(order?.modifications[0].isSettled).toBe(true);
  1132. });
  1133. it('cannot transition to ArrangingAdditionalPayment state if no payment is needed', async () => {
  1134. const { transitionOrderToState } = await adminClient.query<
  1135. AdminTransition.Mutation,
  1136. AdminTransition.Variables
  1137. >(ADMIN_TRANSITION_TO_STATE, {
  1138. id: orderId3,
  1139. state: 'ArrangingAdditionalPayment',
  1140. });
  1141. orderGuard.assertErrorResult(transitionOrderToState);
  1142. expect(transitionOrderToState!.errorCode).toBe(ErrorCode.ORDER_STATE_TRANSITION_ERROR);
  1143. expect(transitionOrderToState!.transitionError).toBe(
  1144. `Cannot transition Order to the \"ArrangingAdditionalPayment\" state as no additional payments are needed`,
  1145. );
  1146. });
  1147. it('can transition to original state', async () => {
  1148. const { transitionOrderToState } = await adminClient.query<
  1149. AdminTransition.Mutation,
  1150. AdminTransition.Variables
  1151. >(ADMIN_TRANSITION_TO_STATE, {
  1152. id: orderId3,
  1153. state: 'PaymentSettled',
  1154. });
  1155. orderGuard.assertSuccess(transitionOrderToState);
  1156. expect(transitionOrderToState!.state).toBe('PaymentSettled');
  1157. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1158. id: orderId3,
  1159. });
  1160. expect(order?.payments![0].refunds.length).toBe(1);
  1161. expect(order?.payments![0].refunds[0].total).toBe(300);
  1162. expect(order?.payments![0].refunds[0].reason).toBe('discount');
  1163. });
  1164. });
  1165. async function assertOrderIsUnchanged(order: OrderWithLinesFragment) {
  1166. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1167. id: order.id,
  1168. });
  1169. expect(order2!.totalWithTax).toBe(order!.totalWithTax);
  1170. expect(order2!.lines.length).toBe(order!.lines.length);
  1171. expect(order2!.surcharges.length).toBe(order!.surcharges.length);
  1172. expect(order2!.totalQuantity).toBe(order!.totalQuantity);
  1173. }
  1174. async function createOrderAndTransitionToModifyingState(
  1175. items: Array<AddItemToOrderMutationVariables & { customFields?: any }>,
  1176. ): Promise<TestOrderWithPaymentsFragment> {
  1177. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  1178. for (const itemInput of items) {
  1179. await shopClient.query(gql(ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS), itemInput);
  1180. }
  1181. await shopClient.query<SetShippingAddress.Mutation, SetShippingAddress.Variables>(
  1182. SET_SHIPPING_ADDRESS,
  1183. {
  1184. input: {
  1185. fullName: 'name',
  1186. streetLine1: '12 the street',
  1187. city: 'foo',
  1188. postalCode: '123456',
  1189. countryCode: 'AT',
  1190. },
  1191. },
  1192. );
  1193. await shopClient.query<SetShippingMethod.Mutation, SetShippingMethod.Variables>(SET_SHIPPING_METHOD, {
  1194. id: testShippingMethodId,
  1195. });
  1196. await shopClient.query<TransitionToState.Mutation, TransitionToState.Variables>(TRANSITION_TO_STATE, {
  1197. state: 'ArrangingPayment',
  1198. });
  1199. const order = await addPaymentToOrder(shopClient, testSuccessfulPaymentMethod);
  1200. orderGuard.assertSuccess(order);
  1201. const { transitionOrderToState } = await adminClient.query<
  1202. AdminTransition.Mutation,
  1203. AdminTransition.Variables
  1204. >(ADMIN_TRANSITION_TO_STATE, {
  1205. id: order.id,
  1206. state: 'Modifying',
  1207. });
  1208. return order;
  1209. }
  1210. });
  1211. export const ORDER_WITH_MODIFICATION_FRAGMENT = gql`
  1212. fragment OrderWithModifications on Order {
  1213. id
  1214. state
  1215. total
  1216. totalWithTax
  1217. lines {
  1218. id
  1219. quantity
  1220. linePrice
  1221. linePriceWithTax
  1222. productVariant {
  1223. id
  1224. name
  1225. }
  1226. items {
  1227. id
  1228. createdAt
  1229. updatedAt
  1230. cancelled
  1231. unitPrice
  1232. }
  1233. }
  1234. surcharges {
  1235. id
  1236. description
  1237. sku
  1238. price
  1239. priceWithTax
  1240. taxRate
  1241. }
  1242. payments {
  1243. id
  1244. transactionId
  1245. state
  1246. amount
  1247. method
  1248. metadata
  1249. refunds {
  1250. id
  1251. state
  1252. total
  1253. paymentId
  1254. }
  1255. }
  1256. modifications {
  1257. id
  1258. note
  1259. priceChange
  1260. isSettled
  1261. orderItems {
  1262. id
  1263. }
  1264. surcharges {
  1265. id
  1266. }
  1267. payment {
  1268. id
  1269. state
  1270. amount
  1271. method
  1272. }
  1273. refund {
  1274. id
  1275. state
  1276. total
  1277. paymentId
  1278. }
  1279. }
  1280. shippingAddress {
  1281. streetLine1
  1282. city
  1283. postalCode
  1284. province
  1285. countryCode
  1286. country
  1287. }
  1288. billingAddress {
  1289. streetLine1
  1290. city
  1291. postalCode
  1292. province
  1293. countryCode
  1294. country
  1295. }
  1296. }
  1297. `;
  1298. export const GET_ORDER_WITH_MODIFICATIONS = gql`
  1299. query GetOrderWithModifications($id: ID!) {
  1300. order(id: $id) {
  1301. ...OrderWithModifications
  1302. }
  1303. }
  1304. ${ORDER_WITH_MODIFICATION_FRAGMENT}
  1305. `;
  1306. export const MODIFY_ORDER = gql`
  1307. mutation ModifyOrder($input: ModifyOrderInput!) {
  1308. modifyOrder(input: $input) {
  1309. ...OrderWithModifications
  1310. ... on ErrorResult {
  1311. errorCode
  1312. message
  1313. }
  1314. }
  1315. }
  1316. ${ORDER_WITH_MODIFICATION_FRAGMENT}
  1317. `;
  1318. export const ADD_MANUAL_PAYMENT = gql`
  1319. mutation AddManualPayment($input: ManualPaymentInput!) {
  1320. addManualPaymentToOrder(input: $input) {
  1321. ...OrderWithModifications
  1322. ... on ErrorResult {
  1323. errorCode
  1324. message
  1325. }
  1326. }
  1327. }
  1328. ${ORDER_WITH_MODIFICATION_FRAGMENT}
  1329. `;
  1330. // Note, we don't use the gql tag around these due to the customFields which
  1331. // would cause a codegen error.
  1332. const ADD_ITEM_TO_ORDER_WITH_CUSTOM_FIELDS = `
  1333. mutation AddItemToOrder($productVariantId: ID!, $quantity: Int!, $customFields: OrderLineCustomFieldsInput) {
  1334. addItemToOrder(productVariantId: $productVariantId, quantity: $quantity, customFields: $customFields) {
  1335. ...on Order { id }
  1336. }
  1337. }
  1338. `;
  1339. const GET_ORDER_WITH_CUSTOM_FIELDS = `
  1340. query GetOrderCustomFields($id: ID!) {
  1341. order(id: $id) {
  1342. customFields { points }
  1343. lines { id, customFields { color } }
  1344. }
  1345. }
  1346. `;