Kaynağa Gözat

feat(payments-plugin): Add Mollie paymentmethod selection (#1825)

Allows a particular method to be specified before redirecting to the Mollie payment UI.
Martijn 3 yıl önce
ebeveyn
işleme
a7c4e64f20

+ 1 - 1
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -2435,7 +2435,7 @@ export type Mutation = {
   deleteChannel: DeletionResponse;
   /** Delete a Collection and all of its descendants */
   deleteCollection: DeletionResponse;
-  /** Delete a Collection and all of its descendants */
+  /** Delete multiple Collections and all of their descendants */
   deleteCollections: Array<DeletionResponse>;
   /** Delete a Country */
   deleteCountry: DeletionResponse;

+ 1 - 1
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -2296,7 +2296,7 @@ export type Mutation = {
   updateCollection: Collection;
   /** Delete a Collection and all of its descendants */
   deleteCollection: DeletionResponse;
-  /** Delete a Collection and all of its descendants */
+  /** Delete multiple Collections and all of their descendants */
   deleteCollections: Array<DeletionResponse>;
   /** Move a Collection to a different parent or index */
   moveCollection: Collection;

+ 1 - 1
packages/common/src/generated-types.ts

@@ -2373,7 +2373,7 @@ export type Mutation = {
   updateCollection: Collection;
   /** Delete a Collection and all of its descendants */
   deleteCollection: DeletionResponse;
-  /** Delete a Collection and all of its descendants */
+  /** Delete multiple Collections and all of their descendants */
   deleteCollections: Array<DeletionResponse>;
   /** Move a Collection to a different parent or index */
   moveCollection: Collection;

+ 1 - 1
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -2296,7 +2296,7 @@ export type Mutation = {
   updateCollection: Collection;
   /** Delete a Collection and all of its descendants */
   deleteCollection: DeletionResponse;
-  /** Delete a Collection and all of its descendants */
+  /** Delete multiple Collections and all of their descendants */
   deleteCollections: Array<DeletionResponse>;
   /** Move a Collection to a different parent or index */
   moveCollection: Collection;

+ 1 - 1
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -2296,7 +2296,7 @@ export type Mutation = {
   updateCollection: Collection;
   /** Delete a Collection and all of its descendants */
   deleteCollection: DeletionResponse;
-  /** Delete a Collection and all of its descendants */
+  /** Delete multiple Collections and all of their descendants */
   deleteCollections: Array<DeletionResponse>;
   /** Move a Collection to a different parent or index */
   moveCollection: Collection;

+ 1 - 1
packages/payments-plugin/e2e/graphql/generated-admin-types.ts

@@ -2296,7 +2296,7 @@ export type Mutation = {
   updateCollection: Collection;
   /** Delete a Collection and all of its descendants */
   deleteCollection: DeletionResponse;
-  /** Delete a Collection and all of its descendants */
+  /** Delete multiple Collections and all of their descendants */
   deleteCollections: Array<DeletionResponse>;
   /** Move a Collection to a different parent or index */
   moveCollection: Collection;

+ 124 - 13
packages/payments-plugin/e2e/mollie-payment.e2e-spec.ts

@@ -1,5 +1,5 @@
 import { PaymentStatus } from '@mollie/api-client';
-import { DefaultLogger, LogLevel, mergeConfig } from '@vendure/core';
+import { mergeConfig } from '@vendure/core';
 import { createTestEnvironment, E2E_DEFAULT_CHANNEL_TOKEN, SimpleGraphQLClient, TestServer } from '@vendure/testing';
 import gql from 'graphql-tag';
 import nock from 'nock';
@@ -30,13 +30,35 @@ export const CREATE_MOLLIE_PAYMENT_INTENT = gql`
         }
     }`;
 
+export const GET_MOLLIE_PAYMENT_METHODS = gql`
+    query molliePaymentMethods($input: MolliePaymentMethodsInput!) {
+        molliePaymentMethods(input: $input) {
+            id
+            code
+            description
+            minimumAmount {
+                value
+                currency
+            }
+            maximumAmount {
+                value
+                currency
+            }
+            image {
+                size1x
+                size2x
+                svg
+            }
+        }
+    }`;
+
 describe('Mollie payments', () => {
     const mockData = {
         host: 'https://my-vendure.io',
         redirectUrl: 'https://my-storefront/order',
         apiKey: 'myApiKey',
         methodCode: `mollie-payment-${E2E_DEFAULT_CHANNEL_TOKEN}`,
-        mollieResponse: {
+        molliePaymentResponse: {
             id: 'tr_mockId',
             _links: {
                 checkout: {
@@ -52,6 +74,46 @@ describe('Mollie payments', () => {
             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'
+                }
+            }
+        }
     };
     let shopClient: SimpleGraphQLClient;
     let adminClient: SimpleGraphQLClient;
@@ -134,14 +196,26 @@ describe('Mollie payments', () => {
         expect(result.errorCode).toBe('ORDER_PAYMENT_STATE_ERROR');
     });
 
-    it('Should get payment url', async () => {
+    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');
+    });
+
+    it('Should get payment url without Mollie method', async () => {
         let mollieRequest;
         nock('https://api.mollie.com/')
             .post(/.*/, body => {
                 mollieRequest = body;
                 return true;
             })
-            .reply(200, mockData.mollieResponse);
+            .reply(200, mockData.molliePaymentResponse);
         await shopClient.asUserWithCredentials(customers[0].emailAddress, 'test');
         await setShipping(shopClient);
         const { createMolliePaymentIntent } = await shopClient.query(CREATE_MOLLIE_PAYMENT_INTENT, {
@@ -159,17 +233,36 @@ describe('Mollie payments', () => {
         expect(mollieRequest?.amount?.currency).toBeDefined();
     });
 
+    it('Should get payment url with Mollie method', async () => {
+        let mollieRequest;
+        nock('https://api.mollie.com/')
+            .post(/.*/, body => {
+                mollieRequest = body;
+                return true;
+            })
+            .reply(200, mockData.molliePaymentResponse);
+        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' });
+    });
+
     it('Should settle payment for order', async () => {
         nock('https://api.mollie.com/')
             .get(/.*/)
             .reply(200, {
-                ...mockData.mollieResponse,
+                ...mockData.molliePaymentResponse,
                 status: PaymentStatus.paid,
                 metadata: { orderCode: order.code },
             });
         await fetch(`http://localhost:${serverPort}/payments/mollie/${E2E_DEFAULT_CHANNEL_TOKEN}/1`, {
             method: 'post',
-            body: JSON.stringify({ id: mockData.mollieResponse.id }),
+            body: JSON.stringify({ id: mockData.molliePaymentResponse.id }),
             headers: { 'Content-Type': 'application/json' },
         });
         const { orderByCode } = await shopClient.query<GetOrderByCode.Query, GetOrderByCode.Variables>(
@@ -185,13 +278,13 @@ describe('Mollie payments', () => {
 
     it('Should have Mollie metadata on payment', async () => {
         const { order: { payments: [{ metadata }] } } = await adminClient.query(GET_ORDER_PAYMENTS, { id: order.id });
-        expect(metadata.mode).toBe(mockData.mollieResponse.mode);
-        expect(metadata.method).toBe(mockData.mollieResponse.method);
-        expect(metadata.profileId).toBe(mockData.mollieResponse.profileId);
-        expect(metadata.settlementAmount).toBe(mockData.mollieResponse.settlementAmount);
-        expect(metadata.customerId).toBe(mockData.mollieResponse.customerId);
-        expect(metadata.authorizedAt).toEqual(mockData.mollieResponse.authorizedAt.toISOString());
-        expect(metadata.paidAt).toEqual(mockData.mollieResponse.paidAt.toISOString());
+        expect(metadata.mode).toBe(mockData.molliePaymentResponse.mode);
+        expect(metadata.method).toBe(mockData.molliePaymentResponse.method);
+        expect(metadata.profileId).toBe(mockData.molliePaymentResponse.profileId);
+        expect(metadata.settlementAmount).toBe(mockData.molliePaymentResponse.settlementAmount);
+        expect(metadata.customerId).toBe(mockData.molliePaymentResponse.customerId);
+        expect(metadata.authorizedAt).toEqual(mockData.molliePaymentResponse.authorizedAt.toISOString());
+        expect(metadata.paidAt).toEqual(mockData.molliePaymentResponse.paidAt.toISOString());
     });
 
     it('Should fail to refund', async () => {
@@ -219,4 +312,22 @@ describe('Mollie payments', () => {
         expect(refund.total).toBe(155880);
         expect(refund.state).toBe('Settled');
     });
+
+    it('Should get available paymentMethods', async () => {
+        nock('https://api.mollie.com/')
+            .get(/.*/)
+            .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()
+    });
+
 });

+ 46 - 11
packages/payments-plugin/src/mollie/graphql/generated-shop-types.ts

@@ -1551,6 +1551,12 @@ export type MissingPasswordError = ErrorResult & {
   message: Scalars['String'];
 };
 
+export type MollieAmount = {
+  __typename?: 'MollieAmount';
+  value?: Maybe<Scalars['String']>;
+  currency?: Maybe<Scalars['String']>;
+};
+
 export type MolliePaymentIntent = {
   __typename?: 'MolliePaymentIntent';
   url: Scalars['String'];
@@ -1564,10 +1570,33 @@ export type MolliePaymentIntentError = ErrorResult & {
 
 export type MolliePaymentIntentInput = {
   paymentMethodCode: Scalars['String'];
+  /** The molliePaymentMethod can be found using the 'code' field of one of the methods from the query 'molliePaymentMethods' */
+  molliePaymentMethodCode?: Maybe<Scalars['String']>;
 };
 
 export type MolliePaymentIntentResult = MolliePaymentIntent | MolliePaymentIntentError;
 
+export type MolliePaymentMethod = {
+  __typename?: 'MolliePaymentMethod';
+  id: Scalars['ID'];
+  code: Scalars['String'];
+  description?: Maybe<Scalars['String']>;
+  minimumAmount?: Maybe<MollieAmount>;
+  maximumAmount?: Maybe<MollieAmount>;
+  image?: Maybe<MolliePaymentMethodImages>;
+};
+
+export type MolliePaymentMethodImages = {
+  __typename?: 'MolliePaymentMethodImages';
+  size1x?: Maybe<Scalars['String']>;
+  size2x?: Maybe<Scalars['String']>;
+  svg?: Maybe<Scalars['String']>;
+};
+
+export type MolliePaymentMethodsInput = {
+  paymentMethodCode: Scalars['String'];
+};
+
 export type Mutation = {
   __typename?: 'Mutation';
   /** Adds an item to the order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available. */
@@ -2697,20 +2726,21 @@ export type Query = {
   activeOrder?: Maybe<Order>;
   /** An array of supported Countries */
   availableCountries: Array<Country>;
-  /** A list of Collections available to the shop */
-  collections: CollectionList;
   /** Returns a Collection either by its id or slug. If neither 'id' nor 'slug' is specified, an error will result. */
   collection?: Maybe<Collection>;
-  /** Returns a list of eligible shipping methods based on the current active Order */
-  eligibleShippingMethods: Array<ShippingMethodQuote>;
+  /** A list of Collections available to the shop */
+  collections: CollectionList;
   /** Returns a list of payment methods and their eligibility based on the current active Order */
   eligiblePaymentMethods: Array<PaymentMethodQuote>;
-  /** A list of Facets available to the shop */
-  facets: FacetList;
+  /** Returns a list of eligible shipping methods based on the current active Order */
+  eligibleShippingMethods: Array<ShippingMethodQuote>;
   /** Returns a Facet by its id */
   facet?: Maybe<Facet>;
+  /** A list of Facets available to the shop */
+  facets: FacetList;
   /** Returns information about the current authenticated User */
   me?: Maybe<CurrentUser>;
+  molliePaymentMethods: Array<MolliePaymentMethod>;
   /** Returns the possible next states that the activeOrder can transition to */
   nextOrderStates: Array<Scalars['String']>;
   /**
@@ -2734,14 +2764,19 @@ export type Query = {
 };
 
 
+export type QueryCollectionArgs = {
+  id?: Maybe<Scalars['ID']>;
+  slug?: Maybe<Scalars['String']>;
+};
+
+
 export type QueryCollectionsArgs = {
   options?: Maybe<CollectionListOptions>;
 };
 
 
-export type QueryCollectionArgs = {
-  id?: Maybe<Scalars['ID']>;
-  slug?: Maybe<Scalars['String']>;
+export type QueryFacetArgs = {
+  id: Scalars['ID'];
 };
 
 
@@ -2750,8 +2785,8 @@ export type QueryFacetsArgs = {
 };
 
 
-export type QueryFacetArgs = {
-  id: Scalars['ID'];
+export type QueryMolliePaymentMethodsArgs = {
+  input: MolliePaymentMethodsInput;
 };
 
 

+ 24 - 0
packages/payments-plugin/src/mollie/mollie-shop-schema.ts

@@ -5,14 +5,38 @@ export const shopSchema = gql`
         errorCode: ErrorCode!
         message: String!
     }
+    type MollieAmount {
+        value: String
+        currency: String
+    }
+    type MolliePaymentMethodImages {
+        size1x: String
+        size2x: String
+        svg: String
+    }
+    type MolliePaymentMethod {
+        id: ID!
+        code: String!
+        description: String
+        minimumAmount: MollieAmount
+        maximumAmount: MollieAmount
+        image: MolliePaymentMethodImages
+    }
     type MolliePaymentIntent {
         url: String!
     }
     union MolliePaymentIntentResult = MolliePaymentIntent | MolliePaymentIntentError
     input MolliePaymentIntentInput {
         paymentMethodCode: String!
+        molliePaymentMethodCode: String
+    }
+    input MolliePaymentMethodsInput {
+        paymentMethodCode: String!
     }
     extend type Mutation {
         createMolliePaymentIntent(input: MolliePaymentIntentInput!): MolliePaymentIntentResult!
     }
+    extend type Query {
+        molliePaymentMethods(input: MolliePaymentMethodsInput!): [MolliePaymentMethod!]!
+    }
 `;

+ 35 - 6
packages/payments-plugin/src/mollie/mollie.plugin.ts

@@ -64,21 +64,50 @@ export interface MolliePluginOptions {
  * mutation CreateMolliePaymentIntent {
  *   createMolliePaymentIntent(input: {
  *     paymentMethodCode: "mollie-payment-method"
+ *     molliePaymentMethodCode: "ideal"
  *   }) {
  *          ... on MolliePaymentIntent {
-                url
-            }
-            ... on MolliePaymentIntentError {
-                errorCode
-                message
-            }
+ *               url
+ *           }
+ *          ... on MolliePaymentIntentError {
+ *               errorCode
+ *               message
+ *          }
  *   }
  * }
  * ```
+ *
  * The response will contain
  * a redirectUrl, which can be used to redirect your customer to the Mollie
  * platform.
  *
+ * 'molliePaymentMethodCode' is an optional parameter that can be passed to skip Mollie's hosted payment method selection screen
+ * You can get available Mollie payment methods with the following query:
+ *
+ * ```GraphQL
+ * {
+ *  molliePaymentMethods(input: { paymentMethodCode: "mollie-payment-method" }) {
+ *    id
+ *    code
+ *    description
+ *    minimumAmount {
+ *      value
+ *      currency
+ *    }
+ *    maximumAmount {
+ *      value
+ *      currency
+ *    }
+ *    image {
+ *      size1x
+ *      size2x
+ *      svg
+ *    }
+ *  }
+ * }
+ * ```
+ * You can pass `MolliePaymentMethod.code` to the `createMolliePaymentIntent` mutation to skip the method selection.
+ *
  * After completing payment on the Mollie platform,
  * the user is redirected to the configured redirect url + orderCode: `https://storefront/order/CH234X5`
  *

+ 14 - 5
packages/payments-plugin/src/mollie/mollie.resolver.ts

@@ -1,10 +1,10 @@
-import { Args, Mutation, ResolveField, Resolver } from '@nestjs/graphql';
+import { Args, Mutation, Query, ResolveField, Resolver } from '@nestjs/graphql';
 import { Allow, Ctx, Permission, RequestContext } from '@vendure/core';
 
 import {
     MolliePaymentIntent,
-    MolliePaymentIntentError,
-    MolliePaymentIntentResult,
+    MolliePaymentIntentError, MolliePaymentIntentInput,
+    MolliePaymentIntentResult, MolliePaymentMethod, MolliePaymentMethodsInput,
 } from './graphql/generated-shop-types';
 import { MollieService } from './mollie.service';
 
@@ -17,9 +17,9 @@ export class MollieResolver {
     @Allow(Permission.Owner)
     async createMolliePaymentIntent(
         @Ctx() ctx: RequestContext,
-        @Args('input') input: { paymentMethodCode: string },
+        @Args('input') input: MolliePaymentIntentInput,
     ): Promise<MolliePaymentIntentResult> {
-        return this.mollieService.createPaymentIntent(ctx, input.paymentMethodCode);
+        return this.mollieService.createPaymentIntent(ctx, input);
     }
 
     @ResolveField()
@@ -31,4 +31,13 @@ export class MollieResolver {
             return 'MolliePaymentIntent';
         }
     }
+
+    @Query()
+    @Allow(Permission.Public)
+    async molliePaymentMethods(
+        @Ctx() ctx: RequestContext,
+        @Args('input') { paymentMethodCode }: MolliePaymentMethodsInput
+    ): Promise<MolliePaymentMethod[]> {
+        return this.mollieService.getEnabledPaymentMethods(ctx, paymentMethodCode);
+    }
 }

+ 57 - 12
packages/payments-plugin/src/mollie/mollie.service.ts

@@ -8,14 +8,23 @@ import {
     Logger,
     Order,
     OrderService,
+    PaymentMethod,
     PaymentMethodService,
     RequestContext,
 } from '@vendure/core';
 import { OrderStateTransitionError } from '@vendure/core/dist/common/error/generated-graphql-shop-errors';
 
 import { loggerCtx, PLUGIN_INIT_OPTIONS } from './constants';
-import { ErrorCode, MolliePaymentIntentError, MolliePaymentIntentResult } from './graphql/generated-shop-types';
+import {
+    ErrorCode,
+    MolliePaymentIntentError,
+    MolliePaymentIntentInput,
+    MolliePaymentIntentResult,
+    MolliePaymentMethod,
+} from './graphql/generated-shop-types';
 import { MolliePluginOptions } from './mollie.plugin';
+import { CreateParameters } from '@mollie/api-client/dist/types/src/binders/payments/parameters';
+import { PaymentMethod as MollieClientMethod } from '@mollie/api-client';
 
 interface SettlePaymentInput {
     channelToken: string;
@@ -25,12 +34,21 @@ interface SettlePaymentInput {
 
 class PaymentIntentError implements MolliePaymentIntentError {
     errorCode = ErrorCode.ORDER_PAYMENT_STATE_ERROR;
+
+    constructor(public message: string) {
+    }
+}
+
+class InvalidInput implements MolliePaymentIntentError {
+    errorCode = ErrorCode.INELIGIBLE_PAYMENT_METHOD_ERROR;
+
     constructor(public message: string) {
     }
 }
 
 @Injectable()
 export class MollieService {
+
     constructor(
         private paymentMethodService: PaymentMethodService,
         @Inject(PLUGIN_INIT_OPTIONS) private options: MolliePluginOptions,
@@ -44,10 +62,17 @@ export class MollieService {
     /**
      * Creates a redirectUrl to Mollie for the given paymentMethod and current activeOrder
      */
-    async createPaymentIntent(ctx: RequestContext, paymentMethodCode: string): Promise<MolliePaymentIntentResult> {
-        const [order, paymentMethods] = await Promise.all([
+    async createPaymentIntent(
+        ctx: RequestContext,
+        { paymentMethodCode, molliePaymentMethodCode }: MolliePaymentIntentInput,
+    ): Promise<MolliePaymentIntentResult> {
+        const allowedMethods = Object.values(MollieClientMethod) as string[];
+        if (molliePaymentMethodCode && !allowedMethods.includes(molliePaymentMethodCode)) {
+            return new InvalidInput(`molliePaymentMethodCode has to be one of "${allowedMethods.join(',')}"`);
+        }
+        const [order, paymentMethod] = await Promise.all([
             this.activeOrderService.getOrderFromContext(ctx),
-            this.paymentMethodService.findAll(ctx),
+            this.getPaymentMethod(ctx, paymentMethodCode),
         ]);
         if (!order) {
             return new PaymentIntentError('No active order found for session');
@@ -62,24 +87,21 @@ export class MollieService {
         if (!order.shippingLines?.length) {
             return new PaymentIntentError('Cannot create payment intent for order without shippingMethod');
         }
-        const paymentMethod = paymentMethods.items.find(pm => pm.code === paymentMethodCode);
         if (!paymentMethod) {
             return new PaymentIntentError(`No paymentMethod found with code ${paymentMethodCode}`);
         }
-        const apiKeyArg = paymentMethod.handler.args.find(arg => arg.name === 'apiKey');
-        const redirectUrlArg = paymentMethod.handler.args.find(arg => arg.name === 'redirectUrl');
-        if (!apiKeyArg || !redirectUrlArg) {
+        const apiKey = paymentMethod.handler.args.find(arg => arg.name === 'apiKey')?.value;
+        let redirectUrl = paymentMethod.handler.args.find(arg => arg.name === 'redirectUrl')?.value;
+        if (!apiKey || !redirectUrl) {
             Logger.warn(`CreatePaymentIntent failed, because no apiKey or redirect is configured for ${paymentMethod.code}`, loggerCtx);
             return new PaymentIntentError(`Paymentmethod ${paymentMethod.code} has no apiKey or redirectUrl configured`);
         }
-        const apiKey = apiKeyArg.value;
-        let redirectUrl = redirectUrlArg.value;
         const mollieClient = createMollieClient({ apiKey });
         redirectUrl = redirectUrl.endsWith('/') ? redirectUrl.slice(0, -1) : redirectUrl; // remove appending slash
         const vendureHost = this.options.vendureHost.endsWith('/')
             ? this.options.vendureHost.slice(0, -1)
             : this.options.vendureHost; // remove appending slash
-        const payment = await mollieClient.payments.create({
+        const paymentInput: CreateParameters = {
             amount: {
                 value: `${(order.totalWithTax / 100).toFixed(2)}`,
                 currency: order.currencyCode,
@@ -90,7 +112,11 @@ export class MollieService {
             description: `Order ${order.code}`,
             redirectUrl: `${redirectUrl}/${order.code}`,
             webhookUrl: `${vendureHost}/payments/mollie/${ctx.channel.token}/${paymentMethod.id}`,
-        });
+        };
+        if (molliePaymentMethodCode) {
+            paymentInput.method = molliePaymentMethodCode as MollieClientMethod;
+        }
+        const payment = await mollieClient.payments.create(paymentInput);
         const url = payment.getCheckoutUrl();
         if (!url) {
             throw Error(`Unable to getCheckoutUrl() from Mollie payment`);
@@ -167,6 +193,25 @@ export class MollieService {
         Logger.info(`Payment for order ${molliePayment.metadata.orderCode} settled`, loggerCtx);
     }
 
+    async getEnabledPaymentMethods(ctx: RequestContext, paymentMethodCode: string): Promise<MolliePaymentMethod[]> {
+        const paymentMethod = await this.getPaymentMethod(ctx, paymentMethodCode);
+        const apiKey = paymentMethod?.handler.args.find(arg => arg.name === 'apiKey')?.value;
+        if (!apiKey) {
+            throw Error(`No apiKey configured for payment method ${paymentMethodCode}`);
+        }
+        const client = createMollieClient({ apiKey });
+        const methods = await client.methods.list();
+        return methods.map(m => ({
+            ...m,
+            code: m.id,
+        }));
+    }
+
+    private async getPaymentMethod(ctx: RequestContext, paymentMethodCode: string): Promise<PaymentMethod | undefined> {
+        const paymentMethods = await this.paymentMethodService.findAll(ctx);
+        return paymentMethods.items.find(pm => pm.code === paymentMethodCode);
+    }
+
     private async createContext(channelToken: string): Promise<RequestContext> {
         const channel = await this.channelService.getChannelFromToken(channelToken);
         return new RequestContext({

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
schema-admin.json


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor