order-modification.e2e-spec.ts 101 KB

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