Kaynağa Gözat

feat(payments-plugin): Check for eligibility of Mollie method (#3200)

Martijn 1 yıl önce
ebeveyn
işleme
a12dedcc29

+ 45 - 1
packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts

@@ -42,12 +42,15 @@ import {
 import {
     AddItemToOrderMutation,
     AddItemToOrderMutationVariables,
+    AdjustOrderLineMutation,
+    AdjustOrderLineMutationVariables,
     GetOrderByCodeQuery,
     GetOrderByCodeQueryVariables,
     TestOrderFragmentFragment,
 } from './graphql/generated-shop-types';
 import {
     ADD_ITEM_TO_ORDER,
+    ADJUST_ORDER_LINE,
     APPLY_COUPON_CODE,
     GET_ACTIVE_ORDER,
     GET_ORDER_BY_CODE,
@@ -60,6 +63,7 @@ import {
     GET_MOLLIE_PAYMENT_METHODS,
     refundOrderLine,
     setShipping,
+    testPaymentEligibilityChecker,
 } from './payment-helpers';
 
 const mockData = {
@@ -181,6 +185,9 @@ describe('Mollie payments', () => {
     beforeAll(async () => {
         const devConfig = mergeConfig(testConfig(), {
             plugins: [MolliePlugin.init({ vendureHost: mockData.host })],
+            paymentOptions: {
+                paymentMethodEligibilityCheckers: [testPaymentEligibilityChecker],
+            },
         });
         const env = createTestEnvironment(devConfig);
         serverPort = devConfig.apiOptions.port;
@@ -224,6 +231,10 @@ describe('Mollie payments', () => {
             input: {
                 code: mockData.methodCode,
                 enabled: true,
+                checker: {
+                    code: testPaymentEligibilityChecker.code,
+                    arguments: [],
+                },
                 handler: {
                     code: molliePaymentHandler.code,
                     arguments: [
@@ -390,7 +401,41 @@ describe('Mollie payments', () => {
             });
         });
 
+        it('Should not allow creating intent if payment method is not eligible', async () => {
+            // Set quantity to 9, which is not allowe by our test eligibility checker
+            await shopClient.query<AdjustOrderLineMutation, AdjustOrderLineMutationVariables>(
+                ADJUST_ORDER_LINE,
+                {
+                    orderLineId: order.lines[0].id,
+                    quantity: 9,
+                },
+            );
+            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: 'given-storefront-redirect-url',
+                },
+            });
+            expect(createMolliePaymentIntent.errorCode).toBe('INELIGIBLE_PAYMENT_METHOD_ERROR');
+            expect(createMolliePaymentIntent.message).toContain('is not eligible for order');
+        });
+
         it('Should get payment url with deducted amount if a payment is already made', async () => {
+            // Change quantity back to 10
+            await shopClient.query<AdjustOrderLineMutation, AdjustOrderLineMutationVariables>(
+                ADJUST_ORDER_LINE,
+                {
+                    orderLineId: order.lines[0].id,
+                    quantity: 10,
+                },
+            );
             let mollieRequest: any | undefined;
             nock('https://api.mollie.com/')
                 .post('/v2/orders', body => {
@@ -702,7 +747,6 @@ describe('Mollie payments', () => {
                 >(CREATE_PAYMENT_METHOD, {
                     input: {
                         code: mockData.methodCodeBroken,
-
                         enabled: true,
                         handler: {
                             code: molliePaymentHandler.code,

+ 20 - 0
packages/payments-plugin/e2e/payment-helpers.ts

@@ -2,7 +2,9 @@ import { ID } from '@vendure/common/lib/shared-types';
 import {
     ChannelService,
     ErrorResult,
+    LanguageCode,
     OrderService,
+    PaymentMethodEligibilityChecker,
     PaymentService,
     RequestContext,
     assertFound,
@@ -189,6 +191,24 @@ export async function createFreeShippingCoupon(
     }
 }
 
+/**
+ * Test payment eligibility checker that doesn't allow orders with quantity 9 on an order line,
+ * just so that we can easily mock non-eligibility
+ */
+export const testPaymentEligibilityChecker = new PaymentMethodEligibilityChecker({
+    code: 'test-payment-eligibility-checker',
+    description: [{ languageCode: LanguageCode.en, value: 'Do not allow 9 items' }],
+    args: {},
+    check: (ctx, order, args) => {
+        const hasLineWithQuantity9 = order.lines.find(line => line.quantity === 9);
+        if (hasLineWithQuantity9) {
+            return false;
+        } else {
+            return true;
+        }
+    },
+});
+
 export const CREATE_MOLLIE_PAYMENT_INTENT = gql`
     mutation createMolliePaymentIntent($input: MolliePaymentIntentInput!) {
         createMolliePaymentIntent(input: $input) {

+ 28 - 13
packages/payments-plugin/src/mollie/mollie.service.ts

@@ -13,6 +13,7 @@ import {
     EntityHydrator,
     ErrorResult,
     ID,
+    idsAreEqual,
     Injector,
     LanguageCode,
     Logger,
@@ -94,16 +95,33 @@ export class MollieService {
         if (order instanceof PaymentIntentError) {
             return order;
         }
-        await this.entityHydrator.hydrate(ctx, order, {
-            relations: [
-                'customer',
-                'surcharges',
-                'lines.productVariant',
-                'lines.productVariant.translations',
-                'shippingLines.shippingMethod',
-                'payments',
-            ],
-        });
+        if (!paymentMethod) {
+            return new PaymentIntentError(`No paymentMethod found with code ${String(paymentMethodCode)}`);
+        }
+        const [eligiblePaymentMethods] = await Promise.all([
+            this.orderService.getEligiblePaymentMethods(ctx, order.id),
+            await this.entityHydrator.hydrate(ctx, order, {
+                relations: [
+                    'customer',
+                    'surcharges',
+                    'lines.productVariant',
+                    'lines.productVariant.translations',
+                    'shippingLines.shippingMethod',
+                    'payments',
+                ],
+            }),
+        ]);
+        if (
+            !eligiblePaymentMethods.find(
+                eligibleMethod =>
+                    idsAreEqual(eligibleMethod.id, paymentMethod?.id) && eligibleMethod.isEligible,
+            )
+        ) {
+            // Given payment method code is not eligible for this order
+            return new InvalidInputError(
+                `Payment method ${paymentMethod?.code} is not eligible for order ${order.code}`,
+            );
+        }
         if (order.state !== 'ArrangingPayment' && order.state !== 'ArrangingAdditionalPayment') {
             // Pre-check if order is transitionable to ArrangingPayment, because that will happen after Mollie payment
             try {
@@ -125,9 +143,6 @@ export class MollieService {
                 'Cannot create payment intent for order with customer that has no lastName set',
             );
         }
-        if (!paymentMethod) {
-            return new PaymentIntentError(`No paymentMethod found with code ${String(paymentMethodCode)}`);
-        }
         let redirectUrl = input.redirectUrl;
         if (!redirectUrl) {
             // Use fallback redirect if no redirectUrl is given