mollie-payment.e2e-spec.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import { PaymentStatus } from '@mollie/api-client';
  2. import { DefaultLogger, LogLevel, mergeConfig } from '@vendure/core';
  3. import { createTestEnvironment, E2E_DEFAULT_CHANNEL_TOKEN, SimpleGraphQLClient, TestServer } from '@vendure/testing';
  4. import gql from 'graphql-tag';
  5. import nock from 'nock';
  6. import fetch from 'node-fetch';
  7. import path from 'path';
  8. import { initialData } from '../../../e2e-common/e2e-initial-data';
  9. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  10. import { MolliePlugin } from '../src/mollie';
  11. import { molliePaymentHandler } from '../src/mollie/mollie.handler';
  12. import { CREATE_PAYMENT_METHOD, GET_CUSTOMER_LIST, GET_ORDER_PAYMENTS } from './graphql/admin-queries';
  13. import { CreatePaymentMethod, GetCustomerList, GetCustomerListQuery } from './graphql/generated-admin-types';
  14. import { AddItemToOrder, GetOrderByCode, TestOrderFragmentFragment } from './graphql/generated-shop-types';
  15. import { ADD_ITEM_TO_ORDER, GET_ORDER_BY_CODE } from './graphql/shop-queries';
  16. import { refundOne, setShipping } from './payment-helpers';
  17. export const CREATE_MOLLIE_PAYMENT_INTENT = gql`
  18. mutation createMolliePaymentIntent($input: MolliePaymentIntentInput!) {
  19. createMolliePaymentIntent(input: $input) {
  20. ... on MolliePaymentIntent {
  21. url
  22. }
  23. ... on MolliePaymentIntentError {
  24. errorCode
  25. message
  26. }
  27. }
  28. }`;
  29. describe('Mollie payments', () => {
  30. const mockData = {
  31. host: 'https://my-vendure.io',
  32. redirectUrl: 'https://my-storefront/order',
  33. apiKey: 'myApiKey',
  34. methodCode: `mollie-payment-${E2E_DEFAULT_CHANNEL_TOKEN}`,
  35. mollieResponse: {
  36. id: 'tr_mockId',
  37. _links: {
  38. checkout: {
  39. href: 'https://www.mollie.com/payscreen/select-method/mock-payment',
  40. },
  41. },
  42. resource: 'payment',
  43. mode: 'test',
  44. method: 'test-method',
  45. profileId: '123',
  46. settlementAmount: 'test amount',
  47. customerId: '456',
  48. authorizedAt: new Date(),
  49. paidAt: new Date(),
  50. },
  51. };
  52. let shopClient: SimpleGraphQLClient;
  53. let adminClient: SimpleGraphQLClient;
  54. let server: TestServer;
  55. let started = false;
  56. let customers: GetCustomerListQuery['customers']['items'];
  57. let order: TestOrderFragmentFragment;
  58. let serverPort: number;
  59. beforeAll(async () => {
  60. const devConfig = mergeConfig(testConfig(), {
  61. plugins: [MolliePlugin.init({ vendureHost: mockData.host })],
  62. });
  63. const env = createTestEnvironment(devConfig);
  64. serverPort = devConfig.apiOptions.port;
  65. shopClient = env.shopClient;
  66. adminClient = env.adminClient;
  67. server = env.server;
  68. await server.init({
  69. initialData,
  70. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  71. customerCount: 2,
  72. });
  73. started = true;
  74. await adminClient.asSuperAdmin();
  75. ({
  76. customers: { items: customers },
  77. } = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(GET_CUSTOMER_LIST, {
  78. options: {
  79. take: 2,
  80. },
  81. }));
  82. }, TEST_SETUP_TIMEOUT_MS);
  83. afterAll(async () => {
  84. await server.destroy();
  85. });
  86. it('Should start successfully', async () => {
  87. expect(started).toEqual(true);
  88. expect(customers).toHaveLength(2);
  89. });
  90. it('Should prepare an order', async () => {
  91. await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
  92. const { addItemToOrder } = await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  93. productVariantId: 'T_1',
  94. quantity: 2,
  95. });
  96. order = addItemToOrder as TestOrderFragmentFragment;
  97. expect(order.code).toBeDefined();
  98. });
  99. it('Should add a Mollie paymentMethod', async () => {
  100. const { createPaymentMethod } = await adminClient.query<CreatePaymentMethod.Mutation,
  101. CreatePaymentMethod.Variables>(CREATE_PAYMENT_METHOD, {
  102. input: {
  103. code: mockData.methodCode,
  104. name: 'Mollie payment test',
  105. description: 'This is a Mollie test payment method',
  106. enabled: true,
  107. handler: {
  108. code: molliePaymentHandler.code,
  109. arguments: [
  110. { name: 'redirectUrl', value: mockData.redirectUrl },
  111. { name: 'apiKey', value: mockData.apiKey },
  112. ],
  113. },
  114. },
  115. });
  116. expect(createPaymentMethod.code).toBe(mockData.methodCode);
  117. });
  118. it('Should fail to create payment intent without shippingmethod', async () => {
  119. await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
  120. const { createMolliePaymentIntent: result } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, {
  121. input: {
  122. paymentMethodCode: mockData.methodCode,
  123. },
  124. });
  125. expect(result.errorCode).toBe('ORDER_PAYMENT_STATE_ERROR');
  126. });
  127. it('Should get payment url', async () => {
  128. let mollieRequest;
  129. nock('https://api.mollie.com/')
  130. .post(/.*/, body => {
  131. mollieRequest = body;
  132. return true;
  133. })
  134. .reply(200, mockData.mollieResponse);
  135. await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
  136. await setShipping(shopClient);
  137. const { createMolliePaymentIntent } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, {
  138. input: {
  139. paymentMethodCode: mockData.methodCode,
  140. },
  141. });
  142. expect(createMolliePaymentIntent).toEqual({ url: 'https://www.mollie.com/payscreen/select-method/mock-payment' });
  143. expect(mollieRequest?.metadata.orderCode).toEqual(order.code);
  144. expect(mollieRequest?.redirectUrl).toEqual(`${mockData.redirectUrl}/${order.code}`);
  145. expect(mollieRequest?.webhookUrl).toEqual(
  146. `${mockData.host}/payments/mollie/${E2E_DEFAULT_CHANNEL_TOKEN}/1`,
  147. );
  148. expect(mollieRequest?.amount?.value).toBe('3127.60');
  149. expect(mollieRequest?.amount?.currency).toBeDefined();
  150. });
  151. it('Should settle payment for order', async () => {
  152. nock('https://api.mollie.com/')
  153. .get(/.*/)
  154. .reply(200, {
  155. ...mockData.mollieResponse,
  156. status: PaymentStatus.paid,
  157. metadata: { orderCode: order.code },
  158. });
  159. await fetch(`http://localhost:${serverPort}/payments/mollie/${E2E_DEFAULT_CHANNEL_TOKEN}/1`, {
  160. method: 'post',
  161. body: JSON.stringify({ id: mockData.mollieResponse.id }),
  162. headers: { 'Content-Type': 'application/json' },
  163. });
  164. const { orderByCode } = await shopClient.query<GetOrderByCode.Query, GetOrderByCode.Variables>(
  165. GET_ORDER_BY_CODE,
  166. {
  167. code: order.code,
  168. },
  169. );
  170. // tslint:disable-next-line:no-non-null-assertion
  171. order = orderByCode!;
  172. expect(order.state).toBe('PaymentSettled');
  173. });
  174. it('Should have Mollie metadata on payment', async () => {
  175. const { order: { payments: [{ metadata }] } } = await adminClient.query(GET_ORDER_PAYMENTS, { id: order.id });
  176. expect(metadata.mode).toBe(mockData.mollieResponse.mode);
  177. expect(metadata.method).toBe(mockData.mollieResponse.method);
  178. expect(metadata.profileId).toBe(mockData.mollieResponse.profileId);
  179. expect(metadata.settlementAmount).toBe(mockData.mollieResponse.settlementAmount);
  180. expect(metadata.customerId).toBe(mockData.mollieResponse.customerId);
  181. expect(metadata.authorizedAt).toEqual(mockData.mollieResponse.authorizedAt.toISOString());
  182. expect(metadata.paidAt).toEqual(mockData.mollieResponse.paidAt.toISOString());
  183. });
  184. it('Should fail to refund', async () => {
  185. let mollieRequest;
  186. nock('https://api.mollie.com/')
  187. .post(/.*/, body => {
  188. mollieRequest = body;
  189. return true;
  190. })
  191. .reply(200, { status: 'failed', resource: 'payment' });
  192. const refund = await refundOne(adminClient, order.lines[0].id, order.payments[0].id);
  193. expect(refund.state).toBe('Failed');
  194. });
  195. it('Should successfully refund', async () => {
  196. let mollieRequest;
  197. nock('https://api.mollie.com/')
  198. .post(/.*/, body => {
  199. mollieRequest = body;
  200. return true;
  201. })
  202. .reply(200, { status: 'pending', resource: 'payment' });
  203. const refund = await refundOne(adminClient, order.lines[0].id, order.payments[0].id);
  204. expect(mollieRequest?.amount.value).toBe('1558.80');
  205. expect(refund.total).toBe(155880);
  206. expect(refund.state).toBe('Settled');
  207. });
  208. });