payment-method.e2e-spec.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import {
  3. DefaultLogger,
  4. dummyPaymentHandler,
  5. LanguageCode,
  6. PaymentMethodEligibilityChecker,
  7. } from '@vendure/core';
  8. import {
  9. createErrorResultGuard,
  10. createTestEnvironment,
  11. E2E_DEFAULT_CHANNEL_TOKEN,
  12. ErrorResultGuard,
  13. } from '@vendure/testing';
  14. import gql from 'graphql-tag';
  15. import path from 'path';
  16. import { vi } from 'vitest';
  17. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  18. import { initialData } from '../../../e2e-common/e2e-initial-data';
  19. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  20. import { CurrencyCode, DeletionResult } from './graphql/generated-e2e-admin-types';
  21. import * as Codegen from './graphql/generated-e2e-admin-types';
  22. import { ErrorCode } from './graphql/generated-e2e-shop-types';
  23. import * as CodegenShop from './graphql/generated-e2e-shop-types';
  24. import { CREATE_CHANNEL } from './graphql/shared-definitions';
  25. import { ADD_ITEM_TO_ORDER, ADD_PAYMENT, GET_ELIGIBLE_PAYMENT_METHODS } from './graphql/shop-definitions';
  26. import { proceedToArrangingPayment } from './utils/test-order-utils';
  27. const checkerSpy = vi.fn();
  28. const minPriceChecker = new PaymentMethodEligibilityChecker({
  29. code: 'min-price-checker',
  30. description: [{ languageCode: LanguageCode.en, value: 'Min price checker' }],
  31. args: {
  32. minPrice: {
  33. type: 'int',
  34. },
  35. },
  36. check(ctx, order, args) {
  37. checkerSpy();
  38. if (order.totalWithTax >= args.minPrice) {
  39. return true;
  40. } else {
  41. return 'Order total too low';
  42. }
  43. },
  44. });
  45. describe('PaymentMethod resolver', () => {
  46. const orderGuard: ErrorResultGuard<CodegenShop.TestOrderWithPaymentsFragment> = createErrorResultGuard(
  47. input => !!input.lines,
  48. );
  49. const { server, adminClient, shopClient } = createTestEnvironment({
  50. ...testConfig(),
  51. // logger: new DefaultLogger(),
  52. paymentOptions: {
  53. paymentMethodEligibilityCheckers: [minPriceChecker],
  54. paymentMethodHandlers: [dummyPaymentHandler],
  55. },
  56. });
  57. beforeAll(async () => {
  58. await server.init({
  59. initialData,
  60. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  61. customerCount: 2,
  62. });
  63. await adminClient.asSuperAdmin();
  64. }, TEST_SETUP_TIMEOUT_MS);
  65. afterAll(async () => {
  66. await server.destroy();
  67. });
  68. it('create', async () => {
  69. const { createPaymentMethod } = await adminClient.query<
  70. Codegen.CreatePaymentMethodMutation,
  71. Codegen.CreatePaymentMethodMutationVariables
  72. >(CREATE_PAYMENT_METHOD, {
  73. input: {
  74. code: 'no-checks',
  75. enabled: true,
  76. handler: {
  77. code: dummyPaymentHandler.code,
  78. arguments: [{ name: 'automaticSettle', value: 'true' }],
  79. },
  80. translations: [
  81. {
  82. languageCode: LanguageCode.en,
  83. name: 'No Checker',
  84. description: 'This is a test payment method',
  85. },
  86. ],
  87. },
  88. });
  89. expect(createPaymentMethod).toEqual({
  90. id: 'T_1',
  91. name: 'No Checker',
  92. code: 'no-checks',
  93. description: 'This is a test payment method',
  94. enabled: true,
  95. checker: null,
  96. handler: {
  97. args: [
  98. {
  99. name: 'automaticSettle',
  100. value: 'true',
  101. },
  102. ],
  103. code: 'dummy-payment-handler',
  104. },
  105. });
  106. });
  107. it('update', async () => {
  108. const { updatePaymentMethod } = await adminClient.query<
  109. Codegen.UpdatePaymentMethodMutation,
  110. Codegen.UpdatePaymentMethodMutationVariables
  111. >(UPDATE_PAYMENT_METHOD, {
  112. input: {
  113. id: 'T_1',
  114. checker: {
  115. code: minPriceChecker.code,
  116. arguments: [{ name: 'minPrice', value: '0' }],
  117. },
  118. handler: {
  119. code: dummyPaymentHandler.code,
  120. arguments: [{ name: 'automaticSettle', value: 'false' }],
  121. },
  122. translations: [
  123. {
  124. languageCode: LanguageCode.en,
  125. description: 'modified',
  126. },
  127. ],
  128. },
  129. });
  130. expect(updatePaymentMethod).toEqual({
  131. id: 'T_1',
  132. name: 'No Checker',
  133. code: 'no-checks',
  134. description: 'modified',
  135. enabled: true,
  136. checker: {
  137. args: [{ name: 'minPrice', value: '0' }],
  138. code: minPriceChecker.code,
  139. },
  140. handler: {
  141. args: [
  142. {
  143. name: 'automaticSettle',
  144. value: 'false',
  145. },
  146. ],
  147. code: dummyPaymentHandler.code,
  148. },
  149. });
  150. });
  151. it('unset checker', async () => {
  152. const { updatePaymentMethod } = await adminClient.query<
  153. Codegen.UpdatePaymentMethodMutation,
  154. Codegen.UpdatePaymentMethodMutationVariables
  155. >(UPDATE_PAYMENT_METHOD, {
  156. input: {
  157. id: 'T_1',
  158. checker: null,
  159. },
  160. });
  161. expect(updatePaymentMethod.checker).toEqual(null);
  162. const { paymentMethod } = await adminClient.query<
  163. Codegen.GetPaymentMethodQuery,
  164. Codegen.GetPaymentMethodQueryVariables
  165. >(GET_PAYMENT_METHOD, { id: 'T_1' });
  166. expect(paymentMethod!.checker).toEqual(null);
  167. });
  168. it('paymentMethodEligibilityCheckers', async () => {
  169. const { paymentMethodEligibilityCheckers } =
  170. await adminClient.query<Codegen.GetPaymentMethodCheckersQuery>(GET_PAYMENT_METHOD_CHECKERS);
  171. expect(paymentMethodEligibilityCheckers).toEqual([
  172. {
  173. code: minPriceChecker.code,
  174. args: [{ name: 'minPrice', type: 'int' }],
  175. },
  176. ]);
  177. });
  178. it('paymentMethodHandlers', async () => {
  179. const { paymentMethodHandlers } = await adminClient.query<Codegen.GetPaymentMethodHandlersQuery>(
  180. GET_PAYMENT_METHOD_HANDLERS,
  181. );
  182. expect(paymentMethodHandlers).toEqual([
  183. {
  184. code: dummyPaymentHandler.code,
  185. args: [{ name: 'automaticSettle', type: 'boolean' }],
  186. },
  187. ]);
  188. });
  189. describe('eligibility checks', () => {
  190. beforeAll(async () => {
  191. await adminClient.query<
  192. Codegen.CreatePaymentMethodMutation,
  193. Codegen.CreatePaymentMethodMutationVariables
  194. >(CREATE_PAYMENT_METHOD, {
  195. input: {
  196. code: 'price-check',
  197. enabled: true,
  198. checker: {
  199. code: minPriceChecker.code,
  200. arguments: [{ name: 'minPrice', value: '200000' }],
  201. },
  202. handler: {
  203. code: dummyPaymentHandler.code,
  204. arguments: [{ name: 'automaticSettle', value: 'true' }],
  205. },
  206. translations: [
  207. {
  208. languageCode: LanguageCode.en,
  209. name: 'With Min Price Checker',
  210. description: 'Order total must be more than 2k',
  211. },
  212. ],
  213. },
  214. });
  215. await adminClient.query<
  216. Codegen.CreatePaymentMethodMutation,
  217. Codegen.CreatePaymentMethodMutationVariables
  218. >(CREATE_PAYMENT_METHOD, {
  219. input: {
  220. code: 'disabled-method',
  221. enabled: false,
  222. handler: {
  223. code: dummyPaymentHandler.code,
  224. arguments: [{ name: 'automaticSettle', value: 'true' }],
  225. },
  226. translations: [
  227. {
  228. languageCode: LanguageCode.en,
  229. name: 'Disabled ones',
  230. description: 'This method is disabled',
  231. },
  232. ],
  233. },
  234. });
  235. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  236. await shopClient.query<
  237. CodegenShop.AddItemToOrderMutation,
  238. CodegenShop.AddItemToOrderMutationVariables
  239. >(ADD_ITEM_TO_ORDER, {
  240. productVariantId: 'T_1',
  241. quantity: 1,
  242. });
  243. await proceedToArrangingPayment(shopClient);
  244. });
  245. it('eligiblePaymentMethods', async () => {
  246. const { eligiblePaymentMethods } =
  247. await shopClient.query<CodegenShop.GetEligiblePaymentMethodsQuery>(
  248. GET_ELIGIBLE_PAYMENT_METHODS,
  249. );
  250. expect(eligiblePaymentMethods).toEqual([
  251. {
  252. id: 'T_1',
  253. code: 'no-checks',
  254. isEligible: true,
  255. eligibilityMessage: null,
  256. },
  257. {
  258. id: 'T_2',
  259. code: 'price-check',
  260. isEligible: false,
  261. eligibilityMessage: 'Order total too low',
  262. },
  263. ]);
  264. });
  265. it('addPaymentToOrder does not allow ineligible method', async () => {
  266. checkerSpy.mockClear();
  267. const { addPaymentToOrder } = await shopClient.query<
  268. CodegenShop.AddPaymentToOrderMutation,
  269. CodegenShop.AddPaymentToOrderMutationVariables
  270. >(ADD_PAYMENT, {
  271. input: {
  272. method: 'price-check',
  273. metadata: {},
  274. },
  275. });
  276. orderGuard.assertErrorResult(addPaymentToOrder);
  277. expect(addPaymentToOrder.errorCode).toBe(ErrorCode.INELIGIBLE_PAYMENT_METHOD_ERROR);
  278. expect(addPaymentToOrder.eligibilityCheckerMessage).toBe('Order total too low');
  279. expect(checkerSpy).toHaveBeenCalledTimes(1);
  280. });
  281. });
  282. describe('channels', () => {
  283. const SECOND_CHANNEL_TOKEN = 'SECOND_CHANNEL_TOKEN';
  284. const THIRD_CHANNEL_TOKEN = 'THIRD_CHANNEL_TOKEN';
  285. beforeAll(async () => {
  286. await adminClient.query<Codegen.CreateChannelMutation, Codegen.CreateChannelMutationVariables>(
  287. CREATE_CHANNEL,
  288. {
  289. input: {
  290. code: 'second-channel',
  291. token: SECOND_CHANNEL_TOKEN,
  292. defaultLanguageCode: LanguageCode.en,
  293. currencyCode: CurrencyCode.GBP,
  294. pricesIncludeTax: true,
  295. defaultShippingZoneId: 'T_1',
  296. defaultTaxZoneId: 'T_1',
  297. },
  298. },
  299. );
  300. await adminClient.query<Codegen.CreateChannelMutation, Codegen.CreateChannelMutationVariables>(
  301. CREATE_CHANNEL,
  302. {
  303. input: {
  304. code: 'third-channel',
  305. token: THIRD_CHANNEL_TOKEN,
  306. defaultLanguageCode: LanguageCode.en,
  307. currencyCode: CurrencyCode.GBP,
  308. pricesIncludeTax: true,
  309. defaultShippingZoneId: 'T_1',
  310. defaultTaxZoneId: 'T_1',
  311. },
  312. },
  313. );
  314. });
  315. it('creates a PaymentMethod in channel2', async () => {
  316. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  317. const { createPaymentMethod } = await adminClient.query<
  318. Codegen.CreatePaymentMethodMutation,
  319. Codegen.CreatePaymentMethodMutationVariables
  320. >(CREATE_PAYMENT_METHOD, {
  321. input: {
  322. code: 'channel-2-method',
  323. enabled: true,
  324. handler: {
  325. code: dummyPaymentHandler.code,
  326. arguments: [{ name: 'automaticSettle', value: 'true' }],
  327. },
  328. translations: [
  329. {
  330. languageCode: LanguageCode.en,
  331. name: 'Channel 2 method',
  332. description: 'This is a test payment method',
  333. },
  334. ],
  335. },
  336. });
  337. expect(createPaymentMethod.code).toBe('channel-2-method');
  338. });
  339. it('method is listed in channel2', async () => {
  340. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  341. const { paymentMethods } = await adminClient.query<Codegen.GetPaymentMethodListQuery>(
  342. GET_PAYMENT_METHOD_LIST,
  343. );
  344. expect(paymentMethods.totalItems).toBe(1);
  345. expect(paymentMethods.items[0].code).toBe('channel-2-method');
  346. });
  347. it('method is not listed in channel3', async () => {
  348. adminClient.setChannelToken(THIRD_CHANNEL_TOKEN);
  349. const { paymentMethods } = await adminClient.query<Codegen.GetPaymentMethodListQuery>(
  350. GET_PAYMENT_METHOD_LIST,
  351. );
  352. expect(paymentMethods.totalItems).toBe(0);
  353. });
  354. it('method is listed in default channel', async () => {
  355. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  356. const { paymentMethods } = await adminClient.query<Codegen.GetPaymentMethodListQuery>(
  357. GET_PAYMENT_METHOD_LIST,
  358. );
  359. expect(paymentMethods.totalItems).toBe(4);
  360. expect(paymentMethods.items.map(i => i.code).sort()).toEqual([
  361. 'channel-2-method',
  362. 'disabled-method',
  363. 'no-checks',
  364. 'price-check',
  365. ]);
  366. });
  367. it('delete from channel', async () => {
  368. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  369. const { paymentMethods } = await adminClient.query<Codegen.GetPaymentMethodListQuery>(
  370. GET_PAYMENT_METHOD_LIST,
  371. );
  372. expect(paymentMethods.totalItems).toBe(1);
  373. const { deletePaymentMethod } = await adminClient.query<
  374. Codegen.DeletePaymentMethodMutation,
  375. Codegen.DeletePaymentMethodMutationVariables
  376. >(DELETE_PAYMENT_METHOD, {
  377. id: paymentMethods.items[0].id,
  378. });
  379. expect(deletePaymentMethod.result).toBe(DeletionResult.DELETED);
  380. const { paymentMethods: checkChannel } =
  381. await adminClient.query<Codegen.GetPaymentMethodListQuery>(GET_PAYMENT_METHOD_LIST);
  382. expect(checkChannel.totalItems).toBe(0);
  383. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  384. const { paymentMethods: checkDefault } =
  385. await adminClient.query<Codegen.GetPaymentMethodListQuery>(GET_PAYMENT_METHOD_LIST);
  386. expect(checkDefault.totalItems).toBe(4);
  387. });
  388. it('delete from default channel', async () => {
  389. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  390. const { createPaymentMethod } = await adminClient.query<
  391. Codegen.CreatePaymentMethodMutation,
  392. Codegen.CreatePaymentMethodMutationVariables
  393. >(CREATE_PAYMENT_METHOD, {
  394. input: {
  395. code: 'channel-2-method2',
  396. enabled: true,
  397. handler: {
  398. code: dummyPaymentHandler.code,
  399. arguments: [{ name: 'automaticSettle', value: 'true' }],
  400. },
  401. translations: [
  402. {
  403. languageCode: LanguageCode.en,
  404. name: 'Channel 2 method 2',
  405. description: 'This is a test payment method',
  406. },
  407. ],
  408. },
  409. });
  410. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  411. const { deletePaymentMethod: delete1 } = await adminClient.query<
  412. Codegen.DeletePaymentMethodMutation,
  413. Codegen.DeletePaymentMethodMutationVariables
  414. >(DELETE_PAYMENT_METHOD, {
  415. id: createPaymentMethod.id,
  416. });
  417. expect(delete1.result).toBe(DeletionResult.NOT_DELETED);
  418. expect(delete1.message).toBe(
  419. 'The selected PaymentMethod is assigned to the following Channels: second-channel. Set "force: true" to delete from all Channels.',
  420. );
  421. const { paymentMethods: check1 } = await adminClient.query<Codegen.GetPaymentMethodListQuery>(
  422. GET_PAYMENT_METHOD_LIST,
  423. );
  424. expect(check1.totalItems).toBe(5);
  425. const { deletePaymentMethod: delete2 } = await adminClient.query<
  426. Codegen.DeletePaymentMethodMutation,
  427. Codegen.DeletePaymentMethodMutationVariables
  428. >(DELETE_PAYMENT_METHOD, {
  429. id: createPaymentMethod.id,
  430. force: true,
  431. });
  432. expect(delete2.result).toBe(DeletionResult.DELETED);
  433. const { paymentMethods: check2 } = await adminClient.query<Codegen.GetPaymentMethodListQuery>(
  434. GET_PAYMENT_METHOD_LIST,
  435. );
  436. expect(check2.totalItems).toBe(4);
  437. });
  438. });
  439. });
  440. export const PAYMENT_METHOD_FRAGMENT = gql`
  441. fragment PaymentMethod on PaymentMethod {
  442. id
  443. code
  444. name
  445. description
  446. enabled
  447. checker {
  448. code
  449. args {
  450. name
  451. value
  452. }
  453. }
  454. handler {
  455. code
  456. args {
  457. name
  458. value
  459. }
  460. }
  461. }
  462. `;
  463. export const CREATE_PAYMENT_METHOD = gql`
  464. mutation CreatePaymentMethod($input: CreatePaymentMethodInput!) {
  465. createPaymentMethod(input: $input) {
  466. ...PaymentMethod
  467. }
  468. }
  469. ${PAYMENT_METHOD_FRAGMENT}
  470. `;
  471. export const UPDATE_PAYMENT_METHOD = gql`
  472. mutation UpdatePaymentMethod($input: UpdatePaymentMethodInput!) {
  473. updatePaymentMethod(input: $input) {
  474. ...PaymentMethod
  475. }
  476. }
  477. ${PAYMENT_METHOD_FRAGMENT}
  478. `;
  479. export const GET_PAYMENT_METHOD_HANDLERS = gql`
  480. query GetPaymentMethodHandlers {
  481. paymentMethodHandlers {
  482. code
  483. args {
  484. name
  485. type
  486. }
  487. }
  488. }
  489. `;
  490. export const GET_PAYMENT_METHOD_CHECKERS = gql`
  491. query GetPaymentMethodCheckers {
  492. paymentMethodEligibilityCheckers {
  493. code
  494. args {
  495. name
  496. type
  497. }
  498. }
  499. }
  500. `;
  501. export const GET_PAYMENT_METHOD = gql`
  502. query GetPaymentMethod($id: ID!) {
  503. paymentMethod(id: $id) {
  504. ...PaymentMethod
  505. }
  506. }
  507. ${PAYMENT_METHOD_FRAGMENT}
  508. `;
  509. export const GET_PAYMENT_METHOD_LIST = gql`
  510. query GetPaymentMethodList($options: PaymentMethodListOptions) {
  511. paymentMethods(options: $options) {
  512. items {
  513. ...PaymentMethod
  514. }
  515. totalItems
  516. }
  517. }
  518. ${PAYMENT_METHOD_FRAGMENT}
  519. `;
  520. export const DELETE_PAYMENT_METHOD = gql`
  521. mutation DeletePaymentMethod($id: ID!, $force: Boolean) {
  522. deletePaymentMethod(id: $id, force: $force) {
  523. message
  524. result
  525. }
  526. }
  527. `;