order-modification.e2e-spec.ts 54 KB

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