order-modification.e2e-spec.ts 97 KB

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