payment-method.e2e-spec.ts 20 KB

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