payment-method.e2e-spec.ts 18 KB

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