order-modification.e2e-spec.ts 95 KB

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