|
|
@@ -40,88 +40,90 @@ import {
|
|
|
setShipping,
|
|
|
} from './payment-helpers';
|
|
|
|
|
|
-describe('Mollie payments', () => {
|
|
|
- const mockData = {
|
|
|
- host: 'https://my-vendure.io',
|
|
|
- redirectUrl: 'https://my-storefront/order',
|
|
|
- apiKey: 'myApiKey',
|
|
|
- methodCode: `mollie-payment-${E2E_DEFAULT_CHANNEL_TOKEN}`,
|
|
|
- mollieOrderResponse: {
|
|
|
- id: 'ord_mockId',
|
|
|
- _links: {
|
|
|
- checkout: {
|
|
|
- href: 'https://www.mollie.com/payscreen/select-method/mock-payment',
|
|
|
- },
|
|
|
- },
|
|
|
- lines: [],
|
|
|
- _embedded: {
|
|
|
- payments: [
|
|
|
- {
|
|
|
- id: 'tr_mockPayment',
|
|
|
- status: 'paid',
|
|
|
- resource: 'payment',
|
|
|
- },
|
|
|
- ],
|
|
|
+const mockData = {
|
|
|
+ host: 'https://my-vendure.io',
|
|
|
+ redirectUrl: 'https://my-storefront/order',
|
|
|
+ apiKey: 'myApiKey',
|
|
|
+ methodCode: `mollie-payment-${E2E_DEFAULT_CHANNEL_TOKEN}`,
|
|
|
+ methodCodeBroken: `mollie-payment-broken-${E2E_DEFAULT_CHANNEL_TOKEN}`,
|
|
|
+ mollieOrderResponse: {
|
|
|
+ id: 'ord_mockId',
|
|
|
+ _links: {
|
|
|
+ checkout: {
|
|
|
+ href: 'https://www.mollie.com/payscreen/select-method/mock-payment',
|
|
|
},
|
|
|
- resource: 'order',
|
|
|
- mode: 'test',
|
|
|
- method: 'test-method',
|
|
|
- profileId: '123',
|
|
|
- settlementAmount: 'test amount',
|
|
|
- customerId: '456',
|
|
|
- authorizedAt: new Date(),
|
|
|
- paidAt: new Date(),
|
|
|
},
|
|
|
- molliePaymentMethodsResponse: {
|
|
|
- count: 1,
|
|
|
- _embedded: {
|
|
|
- methods: [
|
|
|
- {
|
|
|
- resource: 'method',
|
|
|
- id: 'ideal',
|
|
|
- description: 'iDEAL',
|
|
|
- minimumAmount: {
|
|
|
- value: '0.01',
|
|
|
- currency: 'EUR',
|
|
|
- },
|
|
|
- maximumAmount: {
|
|
|
- value: '50000.00',
|
|
|
- currency: 'EUR',
|
|
|
- },
|
|
|
- image: {
|
|
|
- size1x: 'https://www.mollie.com/external/icons/payment-methods/ideal.png',
|
|
|
- size2x: 'https://www.mollie.com/external/icons/payment-methods/ideal%402x.png',
|
|
|
- svg: 'https://www.mollie.com/external/icons/payment-methods/ideal.svg',
|
|
|
- },
|
|
|
- _links: {
|
|
|
- self: {
|
|
|
- href: 'https://api.mollie.com/v2/methods/ideal',
|
|
|
- type: 'application/hal+json',
|
|
|
- },
|
|
|
+ lines: [],
|
|
|
+ _embedded: {
|
|
|
+ payments: [
|
|
|
+ {
|
|
|
+ id: 'tr_mockPayment',
|
|
|
+ status: 'paid',
|
|
|
+ resource: 'payment',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ resource: 'order',
|
|
|
+ mode: 'test',
|
|
|
+ method: 'test-method',
|
|
|
+ profileId: '123',
|
|
|
+ settlementAmount: 'test amount',
|
|
|
+ customerId: '456',
|
|
|
+ authorizedAt: new Date(),
|
|
|
+ paidAt: new Date(),
|
|
|
+ },
|
|
|
+ molliePaymentMethodsResponse: {
|
|
|
+ count: 1,
|
|
|
+ _embedded: {
|
|
|
+ methods: [
|
|
|
+ {
|
|
|
+ resource: 'method',
|
|
|
+ id: 'ideal',
|
|
|
+ description: 'iDEAL',
|
|
|
+ minimumAmount: {
|
|
|
+ value: '0.01',
|
|
|
+ currency: 'EUR',
|
|
|
+ },
|
|
|
+ maximumAmount: {
|
|
|
+ value: '50000.00',
|
|
|
+ currency: 'EUR',
|
|
|
+ },
|
|
|
+ image: {
|
|
|
+ size1x: 'https://www.mollie.com/external/icons/payment-methods/ideal.png',
|
|
|
+ size2x: 'https://www.mollie.com/external/icons/payment-methods/ideal%402x.png',
|
|
|
+ svg: 'https://www.mollie.com/external/icons/payment-methods/ideal.svg',
|
|
|
+ },
|
|
|
+ _links: {
|
|
|
+ self: {
|
|
|
+ href: 'https://api.mollie.com/v2/methods/ideal',
|
|
|
+ type: 'application/hal+json',
|
|
|
},
|
|
|
},
|
|
|
- ],
|
|
|
- },
|
|
|
- _links: {
|
|
|
- self: {
|
|
|
- href: 'https://api.mollie.com/v2/methods',
|
|
|
- type: 'application/hal+json',
|
|
|
- },
|
|
|
- documentation: {
|
|
|
- href: 'https://docs.mollie.com/reference/v2/methods-api/list-methods',
|
|
|
- type: 'text/html',
|
|
|
},
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ _links: {
|
|
|
+ self: {
|
|
|
+ href: 'https://api.mollie.com/v2/methods',
|
|
|
+ type: 'application/hal+json',
|
|
|
+ },
|
|
|
+ documentation: {
|
|
|
+ href: 'https://docs.mollie.com/reference/v2/methods-api/list-methods',
|
|
|
+ type: 'text/html',
|
|
|
},
|
|
|
},
|
|
|
- };
|
|
|
- let shopClient: SimpleGraphQLClient;
|
|
|
- let adminClient: SimpleGraphQLClient;
|
|
|
- let server: TestServer;
|
|
|
- let started = false;
|
|
|
- let customers: GetCustomerListQuery['customers']['items'];
|
|
|
- let order: TestOrderFragmentFragment;
|
|
|
- let serverPort: number;
|
|
|
- const SURCHARGE_AMOUNT = -20000;
|
|
|
+ },
|
|
|
+};
|
|
|
+let shopClient: SimpleGraphQLClient;
|
|
|
+let adminClient: SimpleGraphQLClient;
|
|
|
+let server: TestServer;
|
|
|
+let started = false;
|
|
|
+let customers: GetCustomerListQuery['customers']['items'];
|
|
|
+let order: TestOrderFragmentFragment;
|
|
|
+let serverPort: number;
|
|
|
+const SURCHARGE_AMOUNT = -20000;
|
|
|
+
|
|
|
+describe('Mollie payments (with useDynamicRedirectUrl set to true)', () => {
|
|
|
beforeAll(async () => {
|
|
|
const devConfig = mergeConfig(testConfig(), {
|
|
|
plugins: [MolliePlugin.init({ vendureHost: mockData.host })],
|
|
|
@@ -151,6 +153,10 @@ describe('Mollie payments', () => {
|
|
|
await server.destroy();
|
|
|
});
|
|
|
|
|
|
+ afterEach(async () => {
|
|
|
+ nock.cleanAll();
|
|
|
+ });
|
|
|
+
|
|
|
it('Should start successfully', async () => {
|
|
|
expect(started).toEqual(true);
|
|
|
expect(customers).toHaveLength(2);
|
|
|
@@ -527,4 +533,212 @@ describe('Mollie payments', () => {
|
|
|
expect(order.state).toBe('PaymentSettled');
|
|
|
});
|
|
|
});
|
|
|
+
|
|
|
+ it('Should add an unusable Mollie paymentMethod (missing redirectUrl)', async () => {
|
|
|
+ const { createPaymentMethod } = await adminClient.query<
|
|
|
+ CreatePaymentMethod.Mutation,
|
|
|
+ CreatePaymentMethod.Variables
|
|
|
+ >(CREATE_PAYMENT_METHOD, {
|
|
|
+ input: {
|
|
|
+ code: mockData.methodCodeBroken,
|
|
|
+ name: 'Mollie payment test',
|
|
|
+ description: 'This is a Mollie test payment method',
|
|
|
+ enabled: true,
|
|
|
+ handler: {
|
|
|
+ code: molliePaymentHandler.code,
|
|
|
+ arguments: [
|
|
|
+ { name: 'apiKey', value: mockData.apiKey },
|
|
|
+ { name: 'autoCapture', value: 'false' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ });
|
|
|
+ expect(createPaymentMethod.code).toBe(mockData.methodCodeBroken);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('Should prepare an order', async () => {
|
|
|
+ await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
|
|
|
+ const { addItemToOrder } = await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(
|
|
|
+ ADD_ITEM_TO_ORDER,
|
|
|
+ {
|
|
|
+ productVariantId: 'T_5',
|
|
|
+ quantity: 10,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ order = addItemToOrder as TestOrderFragmentFragment;
|
|
|
+ // Add surcharge
|
|
|
+ const ctx = new RequestContext({
|
|
|
+ apiType: 'admin',
|
|
|
+ isAuthorized: true,
|
|
|
+ authorizedAsOwnerOnly: false,
|
|
|
+ channel: await server.app.get(ChannelService).getDefaultChannel(),
|
|
|
+ });
|
|
|
+ await server.app.get(OrderService).addSurchargeToOrder(ctx, 1, {
|
|
|
+ description: 'Negative test surcharge',
|
|
|
+ listPrice: SURCHARGE_AMOUNT,
|
|
|
+ });
|
|
|
+ expect(order.code).toBeDefined();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('Should fail to get payment url with Mollie method without redirectUrl configured', async () => {
|
|
|
+ nock('https://api.mollie.com/').post('/v2/orders').reply(200, mockData.mollieOrderResponse);
|
|
|
+ await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
|
|
|
+ await setShipping(shopClient);
|
|
|
+ const { createMolliePaymentIntent } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, {
|
|
|
+ input: {
|
|
|
+ paymentMethodCode: mockData.methodCodeBroken,
|
|
|
+ molliePaymentMethodCode: 'ideal',
|
|
|
+ },
|
|
|
+ });
|
|
|
+ expect(createMolliePaymentIntent.message).toContain('Cannot create payment intent without redirectUrl specified in paymentMethod');
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+describe('Mollie payments (with useDynamicRedirectUrl set to true)', () => {
|
|
|
+ beforeAll(async () => {
|
|
|
+ const devConfig = mergeConfig(testConfig(), {
|
|
|
+ plugins: [MolliePlugin.init({ vendureHost: mockData.host, useDynamicRedirectUrl: true })],
|
|
|
+ });
|
|
|
+ const env = createTestEnvironment(devConfig);
|
|
|
+ serverPort = devConfig.apiOptions.port;
|
|
|
+ shopClient = env.shopClient;
|
|
|
+ adminClient = env.adminClient;
|
|
|
+ server = env.server;
|
|
|
+ await server.init({
|
|
|
+ initialData,
|
|
|
+ productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
|
|
|
+ customerCount: 2,
|
|
|
+ });
|
|
|
+ started = true;
|
|
|
+ await adminClient.asSuperAdmin();
|
|
|
+ ({
|
|
|
+ customers: { items: customers },
|
|
|
+ } = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(GET_CUSTOMER_LIST, {
|
|
|
+ options: {
|
|
|
+ take: 2,
|
|
|
+ },
|
|
|
+ }));
|
|
|
+ }, TEST_SETUP_TIMEOUT_MS);
|
|
|
+
|
|
|
+ afterAll(async () => {
|
|
|
+ await server.destroy();
|
|
|
+ });
|
|
|
+
|
|
|
+ afterEach(async () => {
|
|
|
+ nock.cleanAll();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('Should start successfully', async () => {
|
|
|
+ expect(started).toEqual(true);
|
|
|
+ expect(customers).toHaveLength(2);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('Should prepare an order', async () => {
|
|
|
+ await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
|
|
|
+ const { addItemToOrder } = await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(
|
|
|
+ ADD_ITEM_TO_ORDER,
|
|
|
+ {
|
|
|
+ productVariantId: 'T_5',
|
|
|
+ quantity: 10,
|
|
|
+ },
|
|
|
+ );
|
|
|
+ order = addItemToOrder as TestOrderFragmentFragment;
|
|
|
+ // Add surcharge
|
|
|
+ const ctx = new RequestContext({
|
|
|
+ apiType: 'admin',
|
|
|
+ isAuthorized: true,
|
|
|
+ authorizedAsOwnerOnly: false,
|
|
|
+ channel: await server.app.get(ChannelService).getDefaultChannel(),
|
|
|
+ });
|
|
|
+ await server.app.get(OrderService).addSurchargeToOrder(ctx, 1, {
|
|
|
+ description: 'Negative test surcharge',
|
|
|
+ listPrice: SURCHARGE_AMOUNT,
|
|
|
+ });
|
|
|
+ expect(order.code).toBeDefined();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('Should add a working Mollie paymentMethod without specifying redirectUrl', async () => {
|
|
|
+ const { createPaymentMethod } = await adminClient.query<
|
|
|
+ CreatePaymentMethod.Mutation,
|
|
|
+ CreatePaymentMethod.Variables
|
|
|
+ >(CREATE_PAYMENT_METHOD, {
|
|
|
+ input: {
|
|
|
+ code: mockData.methodCode,
|
|
|
+ name: 'Mollie payment test',
|
|
|
+ description: 'This is a Mollie test payment method',
|
|
|
+ enabled: true,
|
|
|
+ handler: {
|
|
|
+ code: molliePaymentHandler.code,
|
|
|
+ arguments: [
|
|
|
+ { name: 'apiKey', value: mockData.apiKey },
|
|
|
+ { name: 'autoCapture', value: 'false' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ });
|
|
|
+ expect(createPaymentMethod.code).toBe(mockData.methodCode);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('Should get payment url without Mollie method', async () => {
|
|
|
+ await setShipping(shopClient);
|
|
|
+ let mollieRequest: any | undefined;
|
|
|
+ nock('https://api.mollie.com/')
|
|
|
+ .post('/v2/orders', body => {
|
|
|
+ mollieRequest = body;
|
|
|
+ return true;
|
|
|
+ })
|
|
|
+ .reply(200, mockData.mollieOrderResponse);
|
|
|
+ const { createMolliePaymentIntent } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, {
|
|
|
+ input: {
|
|
|
+ paymentMethodCode: mockData.methodCode,
|
|
|
+ redirectUrl: mockData.redirectUrl,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ expect(createMolliePaymentIntent).toEqual({
|
|
|
+ url: 'https://www.mollie.com/payscreen/select-method/mock-payment',
|
|
|
+ });
|
|
|
+ expect(mollieRequest?.orderNumber).toEqual(order.code);
|
|
|
+ expect(mollieRequest?.redirectUrl).toEqual(mockData.redirectUrl);
|
|
|
+ expect(mollieRequest?.webhookUrl).toEqual(
|
|
|
+ `${mockData.host}/payments/mollie/${E2E_DEFAULT_CHANNEL_TOKEN}/1`,
|
|
|
+ );
|
|
|
+ expect(mollieRequest?.amount?.value).toBe('1009.90');
|
|
|
+ expect(mollieRequest?.amount?.currency).toBe('USD');
|
|
|
+ expect(mollieRequest.lines[0].vatAmount.value).toEqual('199.98');
|
|
|
+ let totalLineAmount = 0;
|
|
|
+ for (const line of mollieRequest.lines) {
|
|
|
+ totalLineAmount += Number(line.totalAmount.value);
|
|
|
+ }
|
|
|
+ // Sum of lines should equal order total
|
|
|
+ expect(mollieRequest.amount.value).toEqual(totalLineAmount.toFixed(2));
|
|
|
+ });
|
|
|
+
|
|
|
+ it('Should get payment url with Mollie method', async () => {
|
|
|
+ nock('https://api.mollie.com/').post('/v2/orders').reply(200, mockData.mollieOrderResponse);
|
|
|
+ await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
|
|
|
+ await setShipping(shopClient);
|
|
|
+ const { createMolliePaymentIntent } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, {
|
|
|
+ input: {
|
|
|
+ paymentMethodCode: mockData.methodCode,
|
|
|
+ molliePaymentMethodCode: 'ideal',
|
|
|
+ redirectUrl: mockData.redirectUrl,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ expect(createMolliePaymentIntent).toEqual({
|
|
|
+ url: 'https://www.mollie.com/payscreen/select-method/mock-payment',
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('Should fail to get payment url without specifying redirectUrl in the createMolliePaymentIntent mutation', async () => {
|
|
|
+ nock('https://api.mollie.com/').post('/v2/orders').reply(200, mockData.mollieOrderResponse);
|
|
|
+ await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
|
|
|
+ await setShipping(shopClient);
|
|
|
+ const { createMolliePaymentIntent } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, {
|
|
|
+ input: {
|
|
|
+ paymentMethodCode: mockData.methodCode,
|
|
|
+ molliePaymentMethodCode: 'ideal',
|
|
|
+ },
|
|
|
+ });
|
|
|
+ expect(createMolliePaymentIntent.message).toContain('Cannot create payment intent without redirectUrl specified');
|
|
|
+ });
|
|
|
});
|