From 7398a7211c9c836a575e3c428cad96a2d2cd543d Mon Sep 17 00:00:00 2001 From: Martijn Date: Fri, 24 Mar 2023 10:08:01 +0100 Subject: [PATCH 1/4] security(payment-plugin): Correctly handle partial payments in Mollie (#2092) This commit fixes a potential issue which could allow a malicious user to create a payment intent, then modify the order contents to a more valuable order total, and then successfully checkout after only paying the original (lower) amount through Mollie. --- .../e2e/graphql/admin-queries.ts | 2 + .../e2e/mollie-payment.e2e-spec.ts | 622 +++++++++--------- packages/payments-plugin/package.json | 3 + .../src/mollie/mollie.handler.ts | 6 +- .../src/mollie/mollie.helpers.ts | 8 + .../src/mollie/mollie.service.ts | 3 +- yarn.lock | 5 + 7 files changed, 352 insertions(+), 297 deletions(-) diff --git a/packages/payments-plugin/e2e/graphql/admin-queries.ts b/packages/payments-plugin/e2e/graphql/admin-queries.ts index 79d1158f99..0ac877e385 100644 --- a/packages/payments-plugin/e2e/graphql/admin-queries.ts +++ b/packages/payments-plugin/e2e/graphql/admin-queries.ts @@ -83,6 +83,8 @@ export const GET_ORDER_PAYMENTS = gql` query order($id: ID!) { order(id: $id) { id + state + totalWithTax payments { id transactionId diff --git a/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts b/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts index b688015a81..f63ab0c01c 100644 --- a/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts +++ b/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts @@ -156,334 +156,370 @@ describe('Mollie payments', () => { expect(customers).toHaveLength(2); }); - it('Should prepare an order', async () => { - await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); - const { addItemToOrder } = await shopClient.query( - 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(), + describe('Payment intent creation', () => { + + it('Should prepare an order', async () => { + await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); + const { addItemToOrder } = await shopClient.query( + 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(); }); - await server.app.get(OrderService).addSurchargeToOrder(ctx, 1, { - description: 'Negative test surcharge', - listPrice: SURCHARGE_AMOUNT, + + it('Should add a Mollie paymentMethod', 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: 'redirectUrl', value: mockData.redirectUrl }, + { name: 'apiKey', value: mockData.apiKey }, + { name: 'autoCapture', value: 'false' }, + ], + }, + }, + }); + expect(createPaymentMethod.code).toBe(mockData.methodCode); }); - expect(order.code).toBeDefined(); - }); - it('Should add a Mollie paymentMethod', 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: 'redirectUrl', value: mockData.redirectUrl }, - { name: 'apiKey', value: mockData.apiKey }, - { name: 'autoCapture', value: 'false' }, - ], + it('Should fail to create payment intent without shippingmethod', async () => { + await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); + const { createMolliePaymentIntent: result } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, { + input: { + paymentMethodCode: mockData.methodCode, }, - }, + }); + expect(result.errorCode).toBe('ORDER_PAYMENT_STATE_ERROR'); }); - expect(createPaymentMethod.code).toBe(mockData.methodCode); - }); - it('Should fail to create payment intent without shippingmethod', async () => { - await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); - const { createMolliePaymentIntent: result } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, { - input: { - paymentMethodCode: mockData.methodCode, - }, + it('Should fail to create payment intent with invalid Mollie method', async () => { + await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); + await setShipping(shopClient); + const { createMolliePaymentIntent: result } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, { + input: { + paymentMethodCode: mockData.methodCode, + molliePaymentMethodCode: 'invalid', + }, + }); + expect(result.errorCode).toBe('INELIGIBLE_PAYMENT_METHOD_ERROR'); }); - expect(result.errorCode).toBe('ORDER_PAYMENT_STATE_ERROR'); - }); - it('Should fail to create payment intent with invalid Mollie method', async () => { - await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); - await setShipping(shopClient); - const { createMolliePaymentIntent: result } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, { - input: { - paymentMethodCode: mockData.methodCode, - molliePaymentMethodCode: 'invalid', - }, + it('Should fail to get payment url when items are out of stock', async () => { + let { updateProductVariants } = await adminClient.query(UPDATE_PRODUCT_VARIANTS, { + input: { + id: 'T_5', + trackInventory: 'TRUE', + outOfStockThreshold: 0, + stockOnHand: 1, + }, + }); + expect(updateProductVariants[0].stockOnHand).toBe(1); + const { createMolliePaymentIntent: result } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, { + input: { + paymentMethodCode: mockData.methodCode, + }, + }); + expect(result.message).toContain('The following variants are out of stock'); + // Set stock back to not tracking + ({ updateProductVariants } = await adminClient.query(UPDATE_PRODUCT_VARIANTS, { + input: { + id: 'T_5', + trackInventory: 'FALSE', + }, + })); + expect(updateProductVariants[0].trackInventory).toBe('FALSE'); }); - expect(result.errorCode).toBe('INELIGIBLE_PAYMENT_METHOD_ERROR'); - }); - it('Should fail to get payment url when items are out of stock', async () => { - let { updateProductVariants } = await adminClient.query(UPDATE_PRODUCT_VARIANTS, { - input: { - id: 'T_5', - trackInventory: 'TRUE', - outOfStockThreshold: 0, - stockOnHand: 1, - }, + it('Should get payment url without Mollie method', async () => { + 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, + }, + }); + 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}/${order.code}`); + 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)); }); - expect(updateProductVariants[0].stockOnHand).toBe(1); - const { createMolliePaymentIntent: result } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, { - input: { - paymentMethodCode: mockData.methodCode, - }, + + 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', + }, + }); + expect(createMolliePaymentIntent).toEqual({ + url: 'https://www.mollie.com/payscreen/select-method/mock-payment', + }); }); - expect(result.message).toContain('The following variants are out of stock'); - // Set stock back to not tracking - ({ updateProductVariants } = await adminClient.query(UPDATE_PRODUCT_VARIANTS, { - input: { - id: 'T_5', - trackInventory: 'FALSE', - }, - })); - expect(updateProductVariants[0].trackInventory).toBe('FALSE'); - }); - it('Should get payment url without Mollie method', async () => { - 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, - }, + it('Should get payment url with deducted amount if a payment is already made', async () => { + let mollieRequest: any | undefined; + nock('https://api.mollie.com/') + .post('/v2/orders', body => { + mollieRequest = body; + return true; + }) + .reply(200, mockData.mollieOrderResponse); + await addManualPayment(server, 1, 10000); + await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, { + input: { + paymentMethodCode: mockData.methodCode, + }, + }); + expect(mollieRequest.amount?.value).toBe('909.90'); // minus 100,00 from manual payment + 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)); }); - expect(createMolliePaymentIntent).toEqual({ - url: 'https://www.mollie.com/payscreen/select-method/mock-payment', + + it('Should get available paymentMethods', async () => { + nock('https://api.mollie.com/').get('/v2/methods').reply(200, mockData.molliePaymentMethodsResponse); + await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); + const { molliePaymentMethods } = await shopClient.query(GET_MOLLIE_PAYMENT_METHODS, { + input: { + paymentMethodCode: mockData.methodCode, + }, + }); + const method = molliePaymentMethods[0]; + expect(method.code).toEqual('ideal'); + expect(method.minimumAmount).toBeDefined(); + expect(method.maximumAmount).toBeDefined(); + expect(method.image).toBeDefined(); }); - expect(mollieRequest?.orderNumber).toEqual(order.code); - expect(mollieRequest?.redirectUrl).toEqual(`${mockData.redirectUrl}/${order.code}`); - 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', - }, + describe('Handle standard payment methods', () => { + + it('Should transition to ArrangingPayment when partially paid', async () => { + nock('https://api.mollie.com/') + .get('/v2/orders/ord_mockId') + .reply(200, { + ...mockData.mollieOrderResponse, + // Add a payment of 20.00 + amount: { value: '20.00', currency: 'EUR' }, + orderNumber: order.code, + status: OrderStatus.paid, + }); + await fetch(`http://localhost:${serverPort}/payments/mollie/${E2E_DEFAULT_CHANNEL_TOKEN}/1`, { + method: 'post', + body: JSON.stringify({ id: mockData.mollieOrderResponse.id }), + headers: { 'Content-Type': 'application/json' }, + }); + // tslint:disable-next-line:no-non-null-assertion + const { order: adminOrder } = await adminClient.query(GET_ORDER_PAYMENTS, { id: order!.id }); + expect(adminOrder.state).toBe('ArrangingPayment'); }); - expect(createMolliePaymentIntent).toEqual({ - url: 'https://www.mollie.com/payscreen/select-method/mock-payment', + + it('Should place order after paying outstanding amount', async () => { + nock('https://api.mollie.com/') + .get('/v2/orders/ord_mockId') + .reply(200, { + ...mockData.mollieOrderResponse, + // Add a payment of 1089.90 + amount: { value: '1089.90', currency: 'EUR' }, // 1109.90 minus the previously paid 20.00 + orderNumber: order.code, + status: OrderStatus.paid, + }); + await fetch(`http://localhost:${serverPort}/payments/mollie/${E2E_DEFAULT_CHANNEL_TOKEN}/1`, { + method: 'post', + body: JSON.stringify({ id: mockData.mollieOrderResponse.id }), + headers: { 'Content-Type': 'application/json' }, + }); + const { orderByCode } = await shopClient.query( + GET_ORDER_BY_CODE, + { + code: order.code, + }, + ); + // tslint:disable-next-line:no-non-null-assertion + order = orderByCode!; + expect(order.state).toBe('PaymentSettled'); }); - }); - it('Should get payment url with deducted amount if a payment is already made', async () => { - let mollieRequest: any | undefined; - nock('https://api.mollie.com/') - .post('/v2/orders', body => { - mollieRequest = body; - return true; - }) - .reply(200, mockData.mollieOrderResponse); - await addManualPayment(server, 1, 10000); - await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, { - input: { - paymentMethodCode: mockData.methodCode, - }, + it('Should have Mollie metadata on payment', async () => { + const { + order: { payments }, + } = await adminClient.query(GET_ORDER_PAYMENTS, { id: order.id }); + const metadata = payments[1].metadata; + expect(metadata.mode).toBe(mockData.mollieOrderResponse.mode); + expect(metadata.method).toBe(mockData.mollieOrderResponse.method); + expect(metadata.profileId).toBe(mockData.mollieOrderResponse.profileId); + expect(metadata.authorizedAt).toEqual(mockData.mollieOrderResponse.authorizedAt.toISOString()); + expect(metadata.paidAt).toEqual(mockData.mollieOrderResponse.paidAt.toISOString()); }); - expect(mollieRequest.amount?.value).toBe('909.90'); // minus 100,00 from manual payment - 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 immediately settle payment for standard payment methods', async () => { - nock('https://api.mollie.com/') - .get('/v2/orders/ord_mockId') - .reply(200, { - ...mockData.mollieOrderResponse, - orderNumber: order.code, - status: OrderStatus.paid, - }); - await fetch(`http://localhost:${serverPort}/payments/mollie/${E2E_DEFAULT_CHANNEL_TOKEN}/1`, { - method: 'post', - body: JSON.stringify({ id: mockData.mollieOrderResponse.id }), - headers: { 'Content-Type': 'application/json' }, + it('Should fail to refund', async () => { + nock('https://api.mollie.com/') + .get('/v2/orders/ord_mockId?embed=payments') + .reply(200, mockData.mollieOrderResponse); + nock('https://api.mollie.com/') + .post('/v2/payments/tr_mockPayment/refunds') + .reply(200, { status: 'failed', resource: 'payment' }); + const refund = await refundOrderLine( + adminClient, + order.lines[0].id, + 1, + // tslint:disable-next-line:no-non-null-assertion + order!.payments[1].id, + SURCHARGE_AMOUNT, + ); + expect(refund.state).toBe('Failed'); }); - const { orderByCode } = await shopClient.query( - GET_ORDER_BY_CODE, - { - code: order.code, - }, - ); - // tslint:disable-next-line:no-non-null-assertion - order = orderByCode!; - expect(order.state).toBe('PaymentSettled'); - }); - it('Should have Mollie metadata on payment', async () => { - const { - order: { payments }, - } = await adminClient.query(GET_ORDER_PAYMENTS, { id: order.id }); - const metadata = payments[1].metadata; - expect(metadata.mode).toBe(mockData.mollieOrderResponse.mode); - expect(metadata.method).toBe(mockData.mollieOrderResponse.method); - expect(metadata.profileId).toBe(mockData.mollieOrderResponse.profileId); - expect(metadata.authorizedAt).toEqual(mockData.mollieOrderResponse.authorizedAt.toISOString()); - expect(metadata.paidAt).toEqual(mockData.mollieOrderResponse.paidAt.toISOString()); - }); + it('Should successfully refund the Mollie payment', async () => { + let mollieRequest; + nock('https://api.mollie.com/') + .get('/v2/orders/ord_mockId?embed=payments') + .reply(200, mockData.mollieOrderResponse); + nock('https://api.mollie.com/') + .post('/v2/payments/tr_mockPayment/refunds', body => { + mollieRequest = body; + return true; + }) + .reply(200, { status: 'pending', resource: 'payment' }); + const refund = await refundOrderLine( + adminClient, + order.lines[0].id, + 10, + order.payments[2].id, + SURCHARGE_AMOUNT, + ); + expect(mollieRequest?.amount.value).toBe('999.90'); // Only refund mollie amount, not the gift card + expect(refund.total).toBe(99990); + expect(refund.state).toBe('Settled'); + }); - it('Should fail to refund', async () => { - nock('https://api.mollie.com/') - .get('/v2/orders/ord_mockId?embed=payments') - .reply(200, mockData.mollieOrderResponse); - nock('https://api.mollie.com/') - .post('/v2/payments/tr_mockPayment/refunds') - .reply(200, { status: 'failed', resource: 'payment' }); - const refund = await refundOrderLine( - adminClient, - order.lines[0].id, - 1, - // tslint:disable-next-line:no-non-null-assertion - order!.payments[1].id, - SURCHARGE_AMOUNT, - ); - expect(refund.state).toBe('Failed'); }); - it('Should successfully refund the Mollie payment', async () => { - let mollieRequest; - nock('https://api.mollie.com/') - .get('/v2/orders/ord_mockId?embed=payments') - .reply(200, mockData.mollieOrderResponse); - nock('https://api.mollie.com/') - .post('/v2/payments/tr_mockPayment/refunds', body => { - mollieRequest = body; - return true; - }) - .reply(200, { status: 'pending', resource: 'payment' }); - const refund = await refundOrderLine( - adminClient, - order.lines[0].id, - 10, - order.payments[1].id, - SURCHARGE_AMOUNT, - ); - expect(mollieRequest?.amount.value).toBe('909.90'); // Only refund mollie amount, not the gift card - expect(refund.total).toBe(90990); - expect(refund.state).toBe('Settled'); - }); + describe('Handle pay-later methods', () => { - it('Should get available paymentMethods', async () => { - nock('https://api.mollie.com/').get('/v2/methods').reply(200, mockData.molliePaymentMethodsResponse); - await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); - const { molliePaymentMethods } = await shopClient.query(GET_MOLLIE_PAYMENT_METHODS, { - input: { - paymentMethodCode: mockData.methodCode, - }, + it('Should prepare a new order', async () => { + await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); + const { addItemToOrder } = await shopClient.query( + ADD_ITEM_TO_ORDER, + { + productVariantId: 'T_1', + quantity: 2, + }, + ); + order = addItemToOrder as TestOrderFragmentFragment; + await setShipping(shopClient); + expect(order.code).toBeDefined(); }); - const method = molliePaymentMethods[0]; - expect(method.code).toEqual('ideal'); - expect(method.minimumAmount).toBeDefined(); - expect(method.maximumAmount).toBeDefined(); - expect(method.image).toBeDefined(); - }); - - it('Should prepare a new order', async () => { - await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); - const { addItemToOrder } = await shopClient.query( - ADD_ITEM_TO_ORDER, - { - productVariantId: 'T_1', - quantity: 2, - }, - ); - order = addItemToOrder as TestOrderFragmentFragment; - await setShipping(shopClient); - expect(order.code).toBeDefined(); - }); - it('Should authorize payment for pay-later payment methods', async () => { - nock('https://api.mollie.com/') - .get('/v2/orders/ord_mockId') - .reply(200, { - ...mockData.mollieOrderResponse, - orderNumber: order.code, - status: OrderStatus.authorized, + it('Should authorize payment for pay-later payment methods', async () => { + nock('https://api.mollie.com/') + .get('/v2/orders/ord_mockId') + .reply(200, { + ...mockData.mollieOrderResponse, + amount: { value: '3127.60', currency: 'EUR' }, + orderNumber: order.code, + status: OrderStatus.authorized, + }); + await fetch(`http://localhost:${serverPort}/payments/mollie/${E2E_DEFAULT_CHANNEL_TOKEN}/1`, { + method: 'post', + body: JSON.stringify({ id: mockData.mollieOrderResponse.id }), + headers: { 'Content-Type': 'application/json' }, }); - await fetch(`http://localhost:${serverPort}/payments/mollie/${E2E_DEFAULT_CHANNEL_TOKEN}/1`, { - method: 'post', - body: JSON.stringify({ id: mockData.mollieOrderResponse.id }), - headers: { 'Content-Type': 'application/json' }, + const { orderByCode } = await shopClient.query( + GET_ORDER_BY_CODE, + { + code: order.code, + }, + ); + // tslint:disable-next-line:no-non-null-assertion + order = orderByCode!; + expect(order.state).toBe('PaymentAuthorized'); }); - const { orderByCode } = await shopClient.query( - GET_ORDER_BY_CODE, - { - code: order.code, - }, - ); - // tslint:disable-next-line:no-non-null-assertion - order = orderByCode!; - expect(order.state).toBe('PaymentAuthorized'); - }); - it('Should settle payment via settlePayment mutation', async () => { - // Mock the getOrder Mollie call - nock('https://api.mollie.com/') - .get('/v2/orders/ord_mockId') - .reply(200, { - ...mockData.mollieOrderResponse, - orderNumber: order.code, - status: OrderStatus.authorized, + it('Should settle payment via settlePayment mutation', async () => { + // Mock the getOrder Mollie call + nock('https://api.mollie.com/') + .get('/v2/orders/ord_mockId') + .reply(200, { + ...mockData.mollieOrderResponse, + orderNumber: order.code, + status: OrderStatus.authorized, + }); + // Mock the createShipment call + let createShipmentBody; + nock('https://api.mollie.com/') + .post('/v2/orders/ord_mockId/shipments', body => { + createShipmentBody = body; + return true; + }) + .reply(200, { resource: 'shipment', lines: [] }); + const { settlePayment } = await adminClient.query< + SettlePaymentMutation, + SettlePaymentMutationVariables + >(SETTLE_PAYMENT, { + // tslint:disable-next-line:no-non-null-assertion + id: order.payments![0].id, }); - // Mock the createShipment call - let createShipmentBody; - nock('https://api.mollie.com/') - .post('/v2/orders/ord_mockId/shipments', body => { - createShipmentBody = body; - return true; - }) - .reply(200, { resource: 'shipment', lines: [] }); - const { settlePayment } = await adminClient.query< - SettlePaymentMutation, - SettlePaymentMutationVariables - >(SETTLE_PAYMENT, { + const { orderByCode } = await shopClient.query( + GET_ORDER_BY_CODE, + { + code: order.code, + }, + ); // tslint:disable-next-line:no-non-null-assertion - id: order.payments![0].id, + order = orderByCode!; + expect(createShipmentBody).toBeDefined(); + expect(order.state).toBe('PaymentSettled'); }); - const { orderByCode } = await shopClient.query( - GET_ORDER_BY_CODE, - { - code: order.code, - }, - ); - // tslint:disable-next-line:no-non-null-assertion - order = orderByCode!; - expect(createShipmentBody).toBeDefined(); - expect(order.state).toBe('PaymentSettled'); + }); + }); diff --git a/packages/payments-plugin/package.json b/packages/payments-plugin/package.json index bb95b1ea8b..ba5caeb08f 100644 --- a/packages/payments-plugin/package.json +++ b/packages/payments-plugin/package.json @@ -26,6 +26,9 @@ "braintree": "3.x", "stripe": "8.x" }, + "dependencies": { + "currency.js":"2.0.4" + }, "devDependencies": { "@mollie/api-client": "^3.6.0", "@types/braintree": "^2.22.15", diff --git a/packages/payments-plugin/src/mollie/mollie.handler.ts b/packages/payments-plugin/src/mollie/mollie.handler.ts index e56858cd93..491720bd90 100644 --- a/packages/payments-plugin/src/mollie/mollie.handler.ts +++ b/packages/payments-plugin/src/mollie/mollie.handler.ts @@ -49,7 +49,7 @@ export const molliePaymentHandler = new PaymentMethodHandler({ createPayment: async ( ctx, order, - amount, + _amount, // Don't use this amount, but the amount from the metadata args, metadata, ): Promise => { @@ -60,9 +60,9 @@ export const molliePaymentHandler = new PaymentMethodHandler({ if (metadata.status !== 'Authorized' && metadata.status !== 'Settled') { throw Error(`Cannot create payment for status ${metadata.status} for order ${order.code}. Only Authorized or Settled are allowed.`); } - Logger.info(`Payment for order ${order.code} created with state '${metadata.status}'`, loggerCtx); + Logger.info(`Payment for order ${order.code} with amount ${metadata.amount} created with state '${metadata.status}'`, loggerCtx); return { - amount, + amount: metadata.amount, state: metadata.status, transactionId: metadata.orderId, // The plugin now only supports 1 payment per order, so a mollie order equals a payment metadata, // Store all given metadata on a payment diff --git a/packages/payments-plugin/src/mollie/mollie.helpers.ts b/packages/payments-plugin/src/mollie/mollie.helpers.ts index 0e1da17efe..9f1a461c7f 100644 --- a/packages/payments-plugin/src/mollie/mollie.helpers.ts +++ b/packages/payments-plugin/src/mollie/mollie.helpers.ts @@ -2,6 +2,7 @@ import { CreateParameters } from '@mollie/api-client/dist/types/src/binders/orde import { Amount } from '@mollie/api-client/dist/types/src/data/global'; import { OrderAddress as MollieOrderAddress } from '@mollie/api-client/dist/types/src/data/orders/data'; import { Customer, Order } from '@vendure/core'; +import currency from 'currency.js'; import { OrderAddress } from './graphql/generated-shop-types'; @@ -79,6 +80,13 @@ export function toAmount(value: number, orderCurrency: string): Amount { }; } +/** + * Return to number of cents + */ +export function amountToCents(amount: Amount): number { + return currency(amount.value).intValue; +} + /** * Recalculate tax amount per order line instead of per unit for Mollie. * Vendure calculates tax per unit, but Mollie expects the tax to be calculated per order line (the total of the quantities). diff --git a/packages/payments-plugin/src/mollie/mollie.service.ts b/packages/payments-plugin/src/mollie/mollie.service.ts index 9dcaddebe3..4293facfb7 100644 --- a/packages/payments-plugin/src/mollie/mollie.service.ts +++ b/packages/payments-plugin/src/mollie/mollie.service.ts @@ -31,7 +31,7 @@ import { MolliePaymentIntentResult, MolliePaymentMethod, } from './graphql/generated-shop-types'; -import { getLocale, toAmount, toMollieAddress, toMollieOrderLines } from './mollie.helpers'; +import { amountToCents, getLocale, toAmount, toMollieAddress, toMollieOrderLines } from './mollie.helpers'; import { MolliePluginOptions } from './mollie.plugin'; interface OrderStatusInput { @@ -219,6 +219,7 @@ export class MollieService { const addPaymentToOrderResult = await this.orderService.addPaymentToOrder(ctx, order.id, { method: paymentMethodCode, metadata: { + amount: amountToCents(mollieOrder.amount), status, orderId: mollieOrder.id, mode: mollieOrder.mode, diff --git a/yarn.lock b/yarn.lock index 1ef4cc8004..595b5656df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7431,6 +7431,11 @@ cuint@^0.2.2: resolved "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= +currency.js@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/currency.js/-/currency.js-2.0.4.tgz#a8a4d69be3b2e509bf67a560c78220bc04809cf1" + integrity sha512-6/OplJYgJ0RUlli74d93HJ/OsKVBi8lB1+Z6eJYS1YZzBuIp4qKKHpJ7ad+GvTlWmLR/hLJOWTykN5Nm8NJ7+w== + custom-event@~1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" From d92d22f3c890ceb5ea68ad808e2d1cf99abd6c18 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Fri, 24 Mar 2023 10:10:45 +0100 Subject: [PATCH 2/4] test(payments-plugin): Fix failing e2e test in mysql Relates to #2092 --- .../e2e/mollie-payment.e2e-spec.ts | 77 ++++++++++--------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts b/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts index f63ab0c01c..e50b27b064 100644 --- a/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts +++ b/packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts @@ -157,16 +157,15 @@ describe('Mollie payments', () => { }); describe('Payment intent creation', () => { - it('Should prepare an order', async () => { await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); - const { addItemToOrder } = await shopClient.query( - ADD_ITEM_TO_ORDER, - { - productVariantId: 'T_5', - quantity: 10, - }, - ); + 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({ @@ -207,23 +206,29 @@ describe('Mollie payments', () => { it('Should fail to create payment intent without shippingmethod', async () => { await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); - const { createMolliePaymentIntent: result } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, { - input: { - paymentMethodCode: mockData.methodCode, + const { createMolliePaymentIntent: result } = await shopClient.query( + CREATE_MOLLIE_PAYMENT_INTENT, + { + input: { + paymentMethodCode: mockData.methodCode, + }, }, - }); + ); expect(result.errorCode).toBe('ORDER_PAYMENT_STATE_ERROR'); }); it('Should fail to create payment intent with invalid Mollie method', async () => { await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); await setShipping(shopClient); - const { createMolliePaymentIntent: result } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, { - input: { - paymentMethodCode: mockData.methodCode, - molliePaymentMethodCode: 'invalid', + const { createMolliePaymentIntent: result } = await shopClient.query( + CREATE_MOLLIE_PAYMENT_INTENT, + { + input: { + paymentMethodCode: mockData.methodCode, + molliePaymentMethodCode: 'invalid', + }, }, - }); + ); expect(result.errorCode).toBe('INELIGIBLE_PAYMENT_METHOD_ERROR'); }); @@ -237,11 +242,14 @@ describe('Mollie payments', () => { }, }); expect(updateProductVariants[0].stockOnHand).toBe(1); - const { createMolliePaymentIntent: result } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, { - input: { - paymentMethodCode: mockData.methodCode, + const { createMolliePaymentIntent: result } = await shopClient.query( + CREATE_MOLLIE_PAYMENT_INTENT, + { + input: { + paymentMethodCode: mockData.methodCode, + }, }, - }); + ); expect(result.message).toContain('The following variants are out of stock'); // Set stock back to not tracking ({ updateProductVariants } = await adminClient.query(UPDATE_PRODUCT_VARIANTS, { @@ -324,7 +332,9 @@ describe('Mollie payments', () => { }); it('Should get available paymentMethods', async () => { - nock('https://api.mollie.com/').get('/v2/methods').reply(200, mockData.molliePaymentMethodsResponse); + nock('https://api.mollie.com/') + .get('/v2/methods') + .reply(200, mockData.molliePaymentMethodsResponse); await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); const { molliePaymentMethods } = await shopClient.query(GET_MOLLIE_PAYMENT_METHODS, { input: { @@ -337,11 +347,9 @@ describe('Mollie payments', () => { expect(method.maximumAmount).toBeDefined(); expect(method.image).toBeDefined(); }); - }); describe('Handle standard payment methods', () => { - it('Should transition to ArrangingPayment when partially paid', async () => { nock('https://api.mollie.com/') .get('/v2/orders/ord_mockId') @@ -433,27 +441,26 @@ describe('Mollie payments', () => { adminClient, order.lines[0].id, 10, - order.payments[2].id, + // tslint:disable-next-line:no-non-null-assertion + order.payments!.find(p => p.amount === 108990)!.id, SURCHARGE_AMOUNT, ); expect(mollieRequest?.amount.value).toBe('999.90'); // Only refund mollie amount, not the gift card expect(refund.total).toBe(99990); expect(refund.state).toBe('Settled'); }); - }); describe('Handle pay-later methods', () => { - it('Should prepare a new order', async () => { await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test'); - const { addItemToOrder } = await shopClient.query( - ADD_ITEM_TO_ORDER, - { - productVariantId: 'T_1', - quantity: 2, - }, - ); + const { addItemToOrder } = await shopClient.query< + AddItemToOrder.Mutation, + AddItemToOrder.Variables + >(ADD_ITEM_TO_ORDER, { + productVariantId: 'T_1', + quantity: 2, + }); order = addItemToOrder as TestOrderFragmentFragment; await setShipping(shopClient); expect(order.code).toBeDefined(); @@ -519,7 +526,5 @@ describe('Mollie payments', () => { expect(createShipmentBody).toBeDefined(); expect(order.state).toBe('PaymentSettled'); }); - }); - }); From 09ae64ec5050d81b500ab7bcff27a92a322098da Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Fri, 24 Mar 2023 10:17:42 +0100 Subject: [PATCH 3/4] chore: Publish v1.9.5 --- CHANGELOG.md | 3 +++ lerna.json | 2 +- packages/admin-ui-plugin/package.json | 6 +++--- packages/admin-ui/package.json | 4 ++-- .../src/lib/core/src/common/version.ts | 2 +- packages/asset-server-plugin/package.json | 6 +++--- packages/common/package.json | 2 +- packages/core/package.json | 4 ++-- packages/create/package.json | 6 +++--- packages/dev-server/package.json | 18 +++++++++--------- packages/elasticsearch-plugin/package.json | 6 +++--- packages/email-plugin/package.json | 6 +++--- packages/harden-plugin/package.json | 6 +++--- packages/job-queue-plugin/package.json | 6 +++--- packages/payments-plugin/package.json | 10 +++++----- packages/testing/package.json | 6 +++--- packages/ui-devkit/package.json | 8 ++++---- 17 files changed, 52 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a98b00397..1b1315d70b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.9.5 (2023-03-24) + + ## 1.9.4 (2023-03-22) diff --git a/lerna.json b/lerna.json index 67e1b922b1..7968697250 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "1.9.4", + "version": "1.9.5", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/packages/admin-ui-plugin/package.json b/packages/admin-ui-plugin/package.json index 63c8cf576f..d44c56fcf3 100644 --- a/packages/admin-ui-plugin/package.json +++ b/packages/admin-ui-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/admin-ui-plugin", - "version": "1.9.4", + "version": "1.9.5", "main": "lib/index.js", "types": "lib/index.d.ts", "files": [ @@ -21,8 +21,8 @@ "devDependencies": { "@types/express": "^4.17.8", "@types/fs-extra": "^9.0.1", - "@vendure/common": "^1.9.4", - "@vendure/core": "^1.9.4", + "@vendure/common": "^1.9.5", + "@vendure/core": "^1.9.5", "express": "^4.17.1", "rimraf": "^3.0.2", "typescript": "4.3.5" diff --git a/packages/admin-ui/package.json b/packages/admin-ui/package.json index 4d1dbc76e8..8bf8f9f696 100644 --- a/packages/admin-ui/package.json +++ b/packages/admin-ui/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/admin-ui", - "version": "1.9.4", + "version": "1.9.5", "license": "MIT", "scripts": { "ng": "ng", @@ -39,7 +39,7 @@ "@ng-select/ng-select": "^7.2.0", "@ngx-translate/core": "^13.0.0", "@ngx-translate/http-loader": "^6.0.0", - "@vendure/common": "^1.9.4", + "@vendure/common": "^1.9.5", "@webcomponents/custom-elements": "^1.4.3", "apollo-angular": "^2.6.0", "apollo-upload-client": "^16.0.0", diff --git a/packages/admin-ui/src/lib/core/src/common/version.ts b/packages/admin-ui/src/lib/core/src/common/version.ts index 393b1cebfe..c8b872617b 100644 --- a/packages/admin-ui/src/lib/core/src/common/version.ts +++ b/packages/admin-ui/src/lib/core/src/common/version.ts @@ -1,2 +1,2 @@ // Auto-generated by the set-version.js script. -export const ADMIN_UI_VERSION = '1.9.4'; +export const ADMIN_UI_VERSION = '1.9.5'; diff --git a/packages/asset-server-plugin/package.json b/packages/asset-server-plugin/package.json index 454c2cf146..17547c32fb 100644 --- a/packages/asset-server-plugin/package.json +++ b/packages/asset-server-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/asset-server-plugin", - "version": "1.9.4", + "version": "1.9.5", "main": "lib/index.js", "types": "lib/index.d.ts", "files": [ @@ -24,8 +24,8 @@ "@types/fs-extra": "^9.0.8", "@types/node-fetch": "^2.5.8", "@types/sharp": "^0.30.4", - "@vendure/common": "^1.9.4", - "@vendure/core": "^1.9.4", + "@vendure/common": "^1.9.5", + "@vendure/core": "^1.9.5", "aws-sdk": "^2.856.0", "express": "^4.17.1", "node-fetch": "^2.6.1", diff --git a/packages/common/package.json b/packages/common/package.json index 50e96f2b09..400a907527 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/common", - "version": "1.9.4", + "version": "1.9.5", "main": "index.js", "license": "MIT", "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index a1695d4ab7..3ed4bf1d81 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/core", - "version": "1.9.4", + "version": "1.9.5", "description": "A modern, headless ecommerce framework", "repository": { "type": "git", @@ -49,7 +49,7 @@ "@nestjs/testing": "7.6.17", "@nestjs/typeorm": "7.1.5", "@types/fs-extra": "^9.0.1", - "@vendure/common": "^1.9.4", + "@vendure/common": "^1.9.5", "apollo-server-express": "2.24.1", "bcrypt": "^5.1.0", "body-parser": "^1.19.0", diff --git a/packages/create/package.json b/packages/create/package.json index 937616cb25..6af095568a 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/create", - "version": "1.9.4", + "version": "1.9.5", "license": "MIT", "bin": { "create": "./index.js" @@ -28,13 +28,13 @@ "@types/handlebars": "^4.1.0", "@types/listr": "^0.14.2", "@types/semver": "^6.2.2", - "@vendure/core": "^1.9.4", + "@vendure/core": "^1.9.5", "rimraf": "^3.0.2", "ts-node": "^10.2.1", "typescript": "4.3.5" }, "dependencies": { - "@vendure/common": "^1.9.4", + "@vendure/common": "^1.9.5", "chalk": "^4.1.0", "commander": "^7.1.0", "cross-spawn": "^7.0.3", diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 45fd73f677..1ecdf15f5b 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "dev-server", - "version": "1.9.4", + "version": "1.9.5", "main": "index.js", "license": "MIT", "private": true, @@ -14,18 +14,18 @@ "load-test:100k": "node -r ts-node/register load-testing/run-load-test.ts 100000" }, "dependencies": { - "@vendure/admin-ui-plugin": "^1.9.4", - "@vendure/asset-server-plugin": "^1.9.4", - "@vendure/common": "^1.9.4", - "@vendure/core": "^1.9.4", - "@vendure/elasticsearch-plugin": "^1.9.4", - "@vendure/email-plugin": "^1.9.4", + "@vendure/admin-ui-plugin": "^1.9.5", + "@vendure/asset-server-plugin": "^1.9.5", + "@vendure/common": "^1.9.5", + "@vendure/core": "^1.9.5", + "@vendure/elasticsearch-plugin": "^1.9.5", + "@vendure/email-plugin": "^1.9.5", "typescript": "4.3.5" }, "devDependencies": { "@types/csv-stringify": "^3.1.0", - "@vendure/testing": "^1.9.4", - "@vendure/ui-devkit": "^1.9.4", + "@vendure/testing": "^1.9.5", + "@vendure/ui-devkit": "^1.9.5", "commander": "^7.1.0", "concurrently": "^5.0.0", "csv-stringify": "^5.3.3", diff --git a/packages/elasticsearch-plugin/package.json b/packages/elasticsearch-plugin/package.json index fec9ad70c4..56a6f52d90 100644 --- a/packages/elasticsearch-plugin/package.json +++ b/packages/elasticsearch-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/elasticsearch-plugin", - "version": "1.9.4", + "version": "1.9.5", "license": "MIT", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -25,8 +25,8 @@ "fast-deep-equal": "^3.1.3" }, "devDependencies": { - "@vendure/common": "^1.9.4", - "@vendure/core": "^1.9.4", + "@vendure/common": "^1.9.5", + "@vendure/core": "^1.9.5", "rimraf": "^3.0.2", "typescript": "4.3.5" } diff --git a/packages/email-plugin/package.json b/packages/email-plugin/package.json index e810c8b2ec..ab84fab9a6 100644 --- a/packages/email-plugin/package.json +++ b/packages/email-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/email-plugin", - "version": "1.9.4", + "version": "1.9.5", "license": "MIT", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,8 +35,8 @@ "@types/fs-extra": "^9.0.1", "@types/handlebars": "^4.1.0", "@types/mjml": "^4.0.4", - "@vendure/common": "^1.9.4", - "@vendure/core": "^1.9.4", + "@vendure/common": "^1.9.5", + "@vendure/core": "^1.9.5", "rimraf": "^3.0.2", "typescript": "4.3.5" } diff --git a/packages/harden-plugin/package.json b/packages/harden-plugin/package.json index 45ddb7e227..38298fdb62 100644 --- a/packages/harden-plugin/package.json +++ b/packages/harden-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/harden-plugin", - "version": "1.9.4", + "version": "1.9.5", "license": "MIT", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -21,7 +21,7 @@ "graphql-query-complexity": "^0.12.0" }, "devDependencies": { - "@vendure/common": "^1.9.4", - "@vendure/core": "^1.9.4" + "@vendure/common": "^1.9.5", + "@vendure/core": "^1.9.5" } } diff --git a/packages/job-queue-plugin/package.json b/packages/job-queue-plugin/package.json index 59fe7e533f..aaaab34375 100644 --- a/packages/job-queue-plugin/package.json +++ b/packages/job-queue-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/job-queue-plugin", - "version": "1.9.4", + "version": "1.9.5", "license": "MIT", "main": "package/index.js", "types": "package/index.d.ts", @@ -24,8 +24,8 @@ "devDependencies": { "@google-cloud/pubsub": "^2.8.0", "@types/ioredis": "^4.28.10", - "@vendure/common": "^1.9.4", - "@vendure/core": "^1.9.4", + "@vendure/common": "^1.9.5", + "@vendure/core": "^1.9.5", "bullmq": "^1.86.7", "rimraf": "^3.0.2", "typescript": "4.3.5" diff --git a/packages/payments-plugin/package.json b/packages/payments-plugin/package.json index ba5caeb08f..41ad8e90c2 100644 --- a/packages/payments-plugin/package.json +++ b/packages/payments-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/payments-plugin", - "version": "1.9.4", + "version": "1.9.5", "license": "MIT", "main": "package/index.js", "types": "package/index.d.ts", @@ -27,15 +27,15 @@ "stripe": "8.x" }, "dependencies": { - "currency.js":"2.0.4" + "currency.js": "2.0.4" }, "devDependencies": { "@mollie/api-client": "^3.6.0", "@types/braintree": "^2.22.15", "@types/localtunnel": "2.0.1", - "@vendure/common": "^1.9.4", - "@vendure/core": "^1.9.4", - "@vendure/testing": "^1.9.4", + "@vendure/common": "^1.9.5", + "@vendure/core": "^1.9.5", + "@vendure/testing": "^1.9.5", "braintree": "^3.0.0", "localtunnel": "2.0.1", "nock": "^13.1.4", diff --git a/packages/testing/package.json b/packages/testing/package.json index 7a7c341fc4..0aeea31a4e 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/testing", - "version": "1.9.4", + "version": "1.9.5", "description": "End-to-end testing tools for Vendure projects", "keywords": [ "vendure", @@ -34,7 +34,7 @@ }, "dependencies": { "@types/node-fetch": "^2.5.4", - "@vendure/common": "^1.9.4", + "@vendure/common": "^1.9.5", "faker": "^4.1.0", "form-data": "^3.0.0", "graphql": "15.5.1", @@ -45,7 +45,7 @@ "devDependencies": { "@types/mysql": "^2.15.15", "@types/pg": "^7.14.5", - "@vendure/core": "^1.9.4", + "@vendure/core": "^1.9.5", "mysql": "^2.18.1", "pg": "^8.4.0", "rimraf": "^3.0.0", diff --git a/packages/ui-devkit/package.json b/packages/ui-devkit/package.json index 45a63b51da..4e4c71782a 100644 --- a/packages/ui-devkit/package.json +++ b/packages/ui-devkit/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/ui-devkit", - "version": "1.9.4", + "version": "1.9.5", "description": "A library for authoring Vendure Admin UI extensions", "keywords": [ "vendure", @@ -40,8 +40,8 @@ "@angular/cli": "12.2.16", "@angular/compiler": "12.2.16", "@angular/compiler-cli": "12.2.16", - "@vendure/admin-ui": "^1.9.4", - "@vendure/common": "^1.9.4", + "@vendure/admin-ui": "^1.9.5", + "@vendure/common": "^1.9.5", "chalk": "^4.1.0", "chokidar": "^3.5.1", "fs-extra": "^10.0.0", @@ -52,7 +52,7 @@ "@rollup/plugin-node-resolve": "^11.2.0", "@types/fs-extra": "^9.0.8", "@types/glob": "^7.1.3", - "@vendure/core": "^1.9.4", + "@vendure/core": "^1.9.5", "rimraf": "^3.0.2", "rollup": "^2.40.0", "rollup-plugin-terser": "^7.0.2", From 2df139b889ff3b9b6051a4d4fa89d9338e90f6ba Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Fri, 24 Mar 2023 10:22:55 +0100 Subject: [PATCH 4/4] chore: Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b1315d70b..d1472fd7bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## 1.9.5 (2023-03-24) +#### Fixes +* **payments-plugin** Fix issue with handling of partial payments in Mollie. If you are using the MolliePlugin you should update as a priority. ([#2092](https://github.com/vendure-ecommerce/vendure/pull/2092)) ## 1.9.4 (2023-03-22)