Browse Source

refactor(admin-ui): Support modified config op def API

Michael Bromley 5 years ago
parent
commit
1a37148fb7
31 changed files with 310 additions and 208 deletions
  1. 0 1
      packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.ts
  2. 8 2
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  3. 3 6
      packages/admin-ui/src/lib/core/src/common/utilities/get-default-config-arg-value.ts
  4. 6 0
      packages/admin-ui/src/lib/core/src/data/definitions/settings-definitions.ts
  5. 1 0
      packages/admin-ui/src/lib/core/src/data/definitions/shared-definitions.ts
  6. 58 51
      packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.html
  7. 18 8
      packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.ts
  8. 37 18
      packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.ts
  9. 37 31
      packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.html
  10. 15 9
      packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.ts
  11. 12 12
      packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.ts
  12. 1 0
      packages/admin-ui/src/lib/settings/src/providers/routing/payment-method-resolver.ts
  13. 2 0
      packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts
  14. 2 0
      packages/common/src/generated-shop-types.ts
  15. 2 0
      packages/common/src/generated-types.ts
  16. 2 0
      packages/core/e2e/graphql/generated-e2e-admin-types.ts
  17. 2 0
      packages/core/e2e/graphql/generated-e2e-shop-types.ts
  18. 2 0
      packages/core/src/api/api-internal-modules.ts
  19. 20 0
      packages/core/src/api/resolvers/entity/payment-method-entity.resolver.ts
  20. 1 0
      packages/core/src/api/schema/common/common-types.graphql
  21. 1 0
      packages/core/src/api/schema/type/payment-method.type.graphql
  22. 43 32
      packages/core/src/common/configurable-operation.ts
  23. 1 0
      packages/core/src/config/promotion/default-promotion-conditions.ts
  24. 20 23
      packages/core/src/service/services/collection.service.ts
  25. 9 6
      packages/core/src/service/services/payment-method.service.ts
  26. 2 3
      packages/core/src/service/services/promotion.service.ts
  27. 2 5
      packages/core/src/service/services/shipping-method.service.ts
  28. 1 1
      packages/dev-server/dev-config.ts
  29. 2 0
      packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts
  30. 0 0
      schema-admin.json
  31. 0 0
      schema-shop.json

+ 0 - 1
packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.ts

@@ -296,7 +296,6 @@ export class CollectionDetailComponent extends BaseDetailComponent<Collection.Fr
                 arguments: Object.values(formValueOperations[i].args).map((value: any, j) => ({
                     name: o.args[j].name,
                     value: value.toString(),
-                    type: o.args[j].type,
                 })),
             };
         });

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

@@ -319,6 +319,7 @@ export type ConfigArgDefinition = {
    __typename?: 'ConfigArgDefinition';
   name: Scalars['String'];
   type: Scalars['String'];
+  list: Scalars['Boolean'];
   label?: Maybe<Scalars['String']>;
   description?: Maybe<Scalars['String']>;
   config?: Maybe<Scalars['JSON']>;
@@ -2618,6 +2619,7 @@ export type PaymentMethod = Node & {
   code: Scalars['String'];
   enabled: Scalars['Boolean'];
   configArgs: Array<ConfigArg>;
+  definition: ConfigurableOperationDefinition;
 };
 
 export type PaymentMethodFilterParameter = {
@@ -6105,7 +6107,10 @@ export type PaymentMethodFragment = (
   & { configArgs: Array<(
     { __typename?: 'ConfigArg' }
     & Pick<ConfigArg, 'name' | 'value'>
-  )> }
+  )>, definition: (
+    { __typename?: 'ConfigurableOperationDefinition' }
+    & ConfigurableOperationDefFragment
+  ) }
 );
 
 export type GetPaymentMethodListQueryVariables = {
@@ -6709,7 +6714,7 @@ export type ConfigurableOperationDefFragment = (
   & Pick<ConfigurableOperationDefinition, 'code' | 'description'>
   & { args: Array<(
     { __typename?: 'ConfigArgDefinition' }
-    & Pick<ConfigArgDefinition, 'name' | 'type' | 'config'>
+    & Pick<ConfigArgDefinition, 'name' | 'type' | 'list' | 'config'>
   )> }
 );
 
@@ -7851,6 +7856,7 @@ export namespace DeleteChannel {
 export namespace PaymentMethod {
   export type Fragment = PaymentMethodFragment;
   export type ConfigArgs = (NonNullable<PaymentMethodFragment['configArgs'][0]>);
+  export type Definition = ConfigurableOperationDefFragment;
 }
 
 export namespace GetPaymentMethodList {

+ 3 - 6
packages/admin-ui/src/lib/core/src/common/utilities/get-default-config-arg-value.ts

@@ -1,18 +1,15 @@
 import { ConfigArgType } from '@vendure/common/lib/shared-types';
 import { assertNever } from '@vendure/common/lib/shared-utils';
 
-import { ConfigArg, ConfigArgDefinition } from '../generated-types';
-
-export function getDefaultConfigArgValue(arg: ConfigArg | ConfigArgDefinition): any {
-    const type = arg.type as ConfigArgType;
+export function getDefaultConfigArgValue(type: ConfigArgType): any {
     switch (type) {
         case 'boolean':
             return false;
         case 'int':
         case 'float':
             return '0';
-        case 'facetValueIds':
-            return [];
+        case 'ID':
+            return '';
         case 'string':
             return '';
         case 'datetime':

+ 6 - 0
packages/admin-ui/src/lib/core/src/data/definitions/settings-definitions.ts

@@ -1,5 +1,7 @@
 import gql from 'graphql-tag';
 
+import { CONFIGURABLE_OPERATION_DEF_FRAGMENT } from './shared-definitions';
+
 export const COUNTRY_FRAGMENT = gql`
     fragment Country on Country {
         id
@@ -374,7 +376,11 @@ export const PAYMENT_METHOD_FRAGMENT = gql`
             name
             value
         }
+        definition {
+            ...ConfigurableOperationDef
+        }
     }
+    ${CONFIGURABLE_OPERATION_DEF_FRAGMENT}
 `;
 
 export const GET_PAYMENT_METHOD_LIST = gql`

+ 1 - 0
packages/admin-ui/src/lib/core/src/data/definitions/shared-definitions.ts

@@ -15,6 +15,7 @@ export const CONFIGURABLE_OPERATION_DEF_FRAGMENT = gql`
         args {
             name
             type
+            list
             config
         }
         code

+ 58 - 51
packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.html

@@ -3,60 +3,67 @@
     <div class="card-block" *ngIf="operation.args?.length">
         <form [formGroup]="form" *ngIf="operation" class="operation-inputs">
             <div *ngFor="let arg of operation.args" class="arg-row">
-                <label>{{ arg.name | sentenceCase }}</label>
-                <clr-checkbox-wrapper *ngIf="getArgType(arg) === 'boolean'">
+                <ng-container *ngIf="form.get(arg.name)">
+                    <label>{{ arg.name | sentenceCase }}</label>
+                    <clr-checkbox-wrapper *ngIf="getArgType(arg) === 'boolean'">
+                        <input
+                            type="checkbox"
+                            clrCheckbox
+                            [formControlName]="arg.name"
+                            [id]="arg.name"
+                            [vdrDisabled]="readonly"
+                        />
+                    </clr-checkbox-wrapper>
                     <input
-                        type="checkbox"
-                        clrCheckbox
+                        *ngIf="isIntInput(arg)"
+                        [name]="arg.name"
+                        type="number"
+                        step="1"
+                        [readonly]="readonly"
+                        [formControlName]="arg.name"
+                    />
+                    <input
+                        *ngIf="isStringWithoutOptions(arg)"
+                        [name]="arg.name"
+                        type="text"
+                        [readonly]="readonly"
+                        [formControlName]="arg.name"
+                    />
+                    <input
+                        *ngIf="getArgType(arg) === 'datetime'"
+                        [name]="arg.name"
+                        type="date"
+                        [readonly]="readonly"
                         [formControlName]="arg.name"
-                        [id]="arg.name"
-                        [vdrDisabled]="readonly"
                     />
-                </clr-checkbox-wrapper>
-                <input
-                    *ngIf="isIntInput(arg)"
-                    [name]="arg.name"
-                    type="number"
-                    step="1"
-                    [readonly]="readonly"
-                    [formControlName]="arg.name"
-                />
-                <input
-                    *ngIf="isStringWithoutOptions(arg)"
-                    [name]="arg.name"
-                    type="text"
-                    [readonly]="readonly"
-                    [formControlName]="arg.name"
-                />
-                <input
-                    *ngIf="getArgType(arg) === 'datetime'"
-                    [name]="arg.name"
-                    type="date"
-                    [readonly]="readonly"
-                    [formControlName]="arg.name"
-                />
-                <vdr-currency-input
-                    *ngIf="isMoneyInput(arg)"
-                    [formControlName]="arg.name"
-                    [readonly]="readonly"
-                    [currencyCode]="activeChannel?.currencyCode"
-                ></vdr-currency-input>
-                <vdr-percentage-suffix-input
-                    *ngIf="isPercentageInput(arg)"
-                    [readonly]="readonly"
-                    [formControlName]="arg.name"
-                ></vdr-percentage-suffix-input>
-                <vdr-facet-value-selector
-                    [readonly]="readonly"
-                    [facets]="facets"
-                    [formControlName]="arg.name"
-                    *ngIf="getArgType(arg) === 'facetValueIds' && facets"
-                ></vdr-facet-value-selector>
-                <select clrSelect [formControlName]="arg.name" *ngIf="isStringWithOptions(arg)" [vdrDisabled]="readonly">
-                    <option *ngFor="let option of getStringOptions(arg)" [value]="option.value">
-                        {{ option.label || option.value }}
-                    </option>
-                </select>
+                    <vdr-currency-input
+                        *ngIf="isMoneyInput(arg)"
+                        [formControlName]="arg.name"
+                        [readonly]="readonly"
+                        [currencyCode]="activeChannel?.currencyCode"
+                    ></vdr-currency-input>
+                    <vdr-percentage-suffix-input
+                        *ngIf="isPercentageInput(arg)"
+                        [readonly]="readonly"
+                        [formControlName]="arg.name"
+                    ></vdr-percentage-suffix-input>
+                    <vdr-facet-value-selector
+                        [readonly]="readonly"
+                        [facets]="facets"
+                        [formControlName]="arg.name"
+                        *ngIf="getArgType(arg) === 'facetValueIds' && facets"
+                    ></vdr-facet-value-selector>
+                    <select
+                        clrSelect
+                        [formControlName]="arg.name"
+                        *ngIf="isStringWithOptions(arg)"
+                        [vdrDisabled]="readonly"
+                    >
+                        <option *ngFor="let option of getStringOptions(arg)" [value]="option.value">
+                            {{ option.label || option.value }}
+                        </option>
+                    </select>
+                </ng-container>
             </div>
         </form>
     </div>

+ 18 - 8
packages/admin-ui/src/lib/core/src/shared/components/configurable-input/configurable-input.component.ts

@@ -1,4 +1,14 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, forwardRef, Input, OnChanges, OnDestroy, Output, SimpleChanges } from '@angular/core';
+import {
+    ChangeDetectionStrategy,
+    Component,
+    EventEmitter,
+    forwardRef,
+    Input,
+    OnChanges,
+    OnDestroy,
+    Output,
+    SimpleChanges,
+} from '@angular/core';
 import {
     AbstractControl,
     ControlValueAccessor,
@@ -67,7 +77,7 @@ export class ConfigurableInputComponent implements OnChanges, OnDestroy, Control
     }
 
     ngOnChanges(changes: SimpleChanges) {
-        if ('operation' in changes) {
+        if ('operation' in changes || 'operationDefinition' in changes) {
             this.createForm();
         }
     }
@@ -146,8 +156,8 @@ export class ConfigurableInputComponent implements OnChanges, OnDestroy, Control
         return [];
     }
 
-    getArgType(arg: ConfigArg): ConfigArgType {
-        return arg.type as ConfigArgType;
+    getArgType(arg: ConfigArg): ConfigArgType | undefined {
+        return this.operationDefinition?.args.find(argDef => argDef.name === arg.name)?.type as ConfigArgType;
     }
 
     private getArgConfig(arg: ConfigArg): Record<string, any> | undefined {
@@ -167,13 +177,13 @@ export class ConfigurableInputComponent implements OnChanges, OnDestroy, Control
         this.form = new FormGroup({});
 
         if (this.operation.args) {
-            for (const arg of this.operation.args) {
-                let value: any = arg.value;
+            for (const arg of this.operationDefinition?.args || []) {
+                let value: any = this.operation.args.find(a => a.name === arg.name)?.value;
                 if (value === undefined) {
-                    value = getDefaultConfigArgValue(arg);
+                    value = getDefaultConfigArgValue(arg.type as ConfigArgType);
                 } else {
                     if (arg.type === 'boolean') {
-                        value = arg.value === 'true';
+                        value = value === 'true';
                     }
                 }
                 this.form.addControl(arg.name, new FormControl(value, Validators.required));

+ 37 - 18
packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.ts

@@ -18,6 +18,7 @@ import {
     ServerConfigService,
     UpdatePromotionInput,
 } from '@vendure/admin-ui/core';
+import { ConfigArgType } from '@vendure/common/lib/shared-types';
 import { Observable } from 'rxjs';
 import { mergeMap, shareReplay, take } from 'rxjs/operators';
 
@@ -65,17 +66,17 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
         this.init();
         this.facets$ = this.dataService.facet
             .getAllFacets()
-            .mapSingle((data) => data.facets.items)
+            .mapSingle(data => data.facets.items)
             .pipe(shareReplay(1));
 
         this.promotion$ = this.entity$;
-        this.dataService.promotion.getPromotionActionsAndConditions().single$.subscribe((data) => {
+        this.dataService.promotion.getPromotionActionsAndConditions().single$.subscribe(data => {
             this.allActions = data.promotionActions;
             this.allConditions = data.promotionConditions;
         });
         this.activeChannel$ = this.dataService.settings
             .getActiveChannel()
-            .mapStream((data) => data.activeChannel);
+            .mapStream(data => data.activeChannel);
 
         // When creating a new Promotion, the initial bindings do not work
         // unless explicitly re-running the change detector. Don't know why.
@@ -87,19 +88,19 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
     }
 
     getAvailableConditions(): ConfigurableOperationDefinition[] {
-        return this.allConditions.filter((o) => !this.conditions.find((c) => c.code === o.code));
+        return this.allConditions.filter(o => !this.conditions.find(c => c.code === o.code));
     }
 
     getConditionDefinition(condition: ConfigurableOperation): ConfigurableOperationDefinition | undefined {
-        return this.allConditions.find((c) => c.code === condition.code);
+        return this.allConditions.find(c => c.code === condition.code);
     }
 
     getAvailableActions(): ConfigurableOperationDefinition[] {
-        return this.allActions.filter((o) => !this.actions.find((a) => a.code === o.code));
+        return this.allActions.filter(o => !this.actions.find(a => a.code === o.code));
     }
 
     getActionDefinition(action: ConfigurableOperation): ConfigurableOperationDefinition | undefined {
-        return this.allActions.find((c) => c.code === action.code);
+        return this.allActions.find(c => c.code === action.code);
     }
 
     saveButtonEnabled(): boolean {
@@ -151,13 +152,13 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
             actions: this.mapOperationsToInputs(this.actions, formValue.actions),
         };
         this.dataService.promotion.createPromotion(input).subscribe(
-            (data) => {
+            data => {
                 this.notificationService.success(_('common.notify-create-success'), { entity: 'Promotion' });
                 this.detailForm.markAsPristine();
                 this.changeDetector.markForCheck();
                 this.router.navigate(['../', data.createPromotion.id], { relativeTo: this.route });
             },
-            (err) => {
+            err => {
                 this.notificationService.error(_('common.notify-create-error'), {
                     entity: 'Promotion',
                 });
@@ -173,7 +174,7 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
         this.promotion$
             .pipe(
                 take(1),
-                mergeMap((promotion) => {
+                mergeMap(promotion => {
                     const input: UpdatePromotionInput = {
                         id: promotion.id,
                         name: formValue.name,
@@ -189,14 +190,14 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
                 }),
             )
             .subscribe(
-                (data) => {
+                data => {
                     this.notificationService.success(_('common.notify-update-success'), {
                         entity: 'Promotion',
                     });
                     this.detailForm.markAsPristine();
                     this.changeDetector.markForCheck();
                 },
-                (err) => {
+                err => {
                     this.notificationService.error(_('common.notify-update-error'), {
                         entity: 'Promotion',
                     });
@@ -216,10 +217,10 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
             startsAt: entity.startsAt,
             endsAt: entity.endsAt,
         });
-        entity.conditions.forEach((o) => {
+        entity.conditions.forEach(o => {
             this.addOperation('conditions', o);
         });
-        entity.actions.forEach((o) => this.addOperation('actions', o));
+        entity.actions.forEach(o => this.addOperation('actions', o));
     }
 
     /**
@@ -235,7 +236,6 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
                 arguments: Object.values<any>(formValueOperations[i].args).map((value, j) => ({
                     name: o.args[j].name,
                     value: value.toString(),
-                    type: o.args[j].type,
                 })),
             };
         });
@@ -247,12 +247,13 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
     private addOperation(key: 'conditions' | 'actions', operation: ConfigurableOperation) {
         const operationsArray = this.formArrayOf(key);
         const collection = key === 'conditions' ? this.conditions : this.actions;
-        const index = operationsArray.value.findIndex((o) => o.code === operation.code);
+        const index = operationsArray.value.findIndex(o => o.code === operation.code);
         if (index === -1) {
             const argsHash = operation.args.reduce(
                 (output, arg) => ({
                     ...output,
-                    [arg.name]: arg.value != null ? arg.value : getDefaultConfigArgValue(arg),
+                    [arg.name]:
+                        arg.value != null ? arg.value : this.getDefaultArgValue(key, operation, arg.name),
                 }),
                 {},
             );
@@ -266,13 +267,31 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
         }
     }
 
+    private getDefaultArgValue(
+        key: 'conditions' | 'actions',
+        operation: ConfigurableOperation,
+        argName: string,
+    ) {
+        const def =
+            key === 'conditions'
+                ? this.allConditions.find(c => c.code === operation.code)
+                : this.allActions.find(a => a.code === operation.code);
+        if (def) {
+            const argDef = def.args.find(a => a.name === argName);
+            if (argDef) {
+                return getDefaultConfigArgValue(argDef.type as ConfigArgType);
+            }
+        }
+        throw new Error(`Could not determine default value for "argName"`);
+    }
+
     /**
      * Removes a condition or action from the promotion.
      */
     private removeOperation(key: 'conditions' | 'actions', operation: ConfigurableOperation) {
         const operationsArray = this.formArrayOf(key);
         const collection = key === 'conditions' ? this.conditions : this.actions;
-        const index = operationsArray.value.findIndex((o) => o.code === operation.code);
+        const index = operationsArray.value.findIndex(o => o.code === operation.code);
         if (index !== -1) {
             operationsArray.removeAt(index);
             collection.splice(index, 1);

+ 37 - 31
packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.html

@@ -46,36 +46,42 @@
         </clr-toggle-wrapper>
     </vdr-form-field>
 
-    <div class="clr-row" formGroupName="configArgs" *ngIf="(entity$ | async)?.configArgs?.length && configArgsIsPopulated()">
-        <div class="clr-col">
-            <label>{{ 'settings.payment-method-config-options' | translate }}</label>
-            <section class="form-block" *ngFor="let arg of (entity$ | async)?.configArgs">
-                <vdr-form-field [label]="arg.name" [for]="arg.name" *ngIf="getType(arg) === 'string'">
-                    <input
-                        [id]="arg.name"
-                        type="text"
-                        [readonly]="!('UpdateSettings' | hasPermission)"
-                        [formControlName]="arg.name"
-                    />
-                </vdr-form-field>
-                <vdr-form-field [label]="arg.name" [for]="arg.name" *ngIf="getType(arg) === 'int'">
-                    <input
-                        [id]="arg.name"
-                        type="number"
-                        [readonly]="!('UpdateSettings' | hasPermission)"
-                        [formControlName]="arg.name"
-                    />
-                </vdr-form-field>
-                <vdr-form-field [label]="arg.name" [for]="arg.name" *ngIf="getType(arg) === 'boolean'">
-                    <input
-                        type="checkbox"
-                        [id]="arg.name"
-                        [vdrDisabled]="!('UpdateSettings' | hasPermission)"
-                        [formControlName]="arg.name"
-                        clrCheckbox
-                    />
-                </vdr-form-field>
-            </section>
+    <ng-container *ngIf="entity$ | async as paymentMethod">
+        <div
+            class="clr-row"
+            formGroupName="configArgs"
+            *ngIf="paymentMethod?.configArgs?.length && configArgsIsPopulated()"
+        >
+            <div class="clr-col">
+                <label>{{ 'settings.payment-method-config-options' | translate }}</label>
+                <section class="form-block" *ngFor="let arg of paymentMethod.configArgs">
+                    <vdr-form-field [label]="arg.name" [for]="arg.name" *ngIf="getType(paymentMethod, arg.name) === 'string'">
+                        <input
+                            [id]="arg.name"
+                            type="text"
+                            [readonly]="!('UpdateSettings' | hasPermission)"
+                            [formControlName]="arg.name"
+                        />
+                    </vdr-form-field>
+                    <vdr-form-field [label]="arg.name" [for]="arg.name" *ngIf="getType(paymentMethod, arg.name) === 'int'">
+                        <input
+                            [id]="arg.name"
+                            type="number"
+                            [readonly]="!('UpdateSettings' | hasPermission)"
+                            [formControlName]="arg.name"
+                        />
+                    </vdr-form-field>
+                    <vdr-form-field [label]="arg.name" [for]="arg.name" *ngIf="getType(paymentMethod, arg.name) === 'boolean'">
+                        <input
+                            type="checkbox"
+                            [id]="arg.name"
+                            [vdrDisabled]="!('UpdateSettings' | hasPermission)"
+                            [formControlName]="arg.name"
+                            clrCheckbox
+                        />
+                    </vdr-form-field>
+                </section>
+            </div>
         </div>
-    </div>
+    </ng-container>
 </form>

+ 15 - 9
packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.ts

@@ -45,8 +45,11 @@ export class PaymentMethodDetailComponent extends BaseDetailComponent<PaymentMet
         this.destroy();
     }
 
-    getType(arg: PaymentMethod.ConfigArgs): ConfigArgSubset<'int' | 'string' | 'boolean'> {
-        return arg.type as any;
+    getType(
+        paymentMethod: PaymentMethod.Fragment,
+        argName: string,
+    ): ConfigArgSubset<'int' | 'string' | 'boolean'> | undefined {
+        return paymentMethod.definition.args.find(a => a.name === argName)?.type as ConfigArgSubset<any>;
     }
 
     configArgsIsPopulated(): boolean {
@@ -70,21 +73,20 @@ export class PaymentMethodDetailComponent extends BaseDetailComponent<PaymentMet
                         configArgs: Object.entries<any>(formValue.configArgs).map(([name, value], i) => ({
                             name,
                             value: value.toString(),
-                            type: configArgs[i].type,
                         })),
                     };
                     return this.dataService.settings.updatePaymentMethod(input);
                 }),
             )
             .subscribe(
-                (data) => {
+                data => {
                     this.notificationService.success(_('common.notify-update-success'), {
                         entity: 'PaymentMethod',
                     });
                     this.detailForm.markAsPristine();
                     this.changeDetector.markForCheck();
                 },
-                (err) => {
+                err => {
                     this.notificationService.error(_('common.notify-update-error'), {
                         entity: 'PaymentMethod',
                     });
@@ -102,17 +104,21 @@ export class PaymentMethodDetailComponent extends BaseDetailComponent<PaymentMet
             for (const arg of paymentMethod.configArgs) {
                 const control = configArgsGroup.get(arg.name);
                 if (control) {
-                    control.patchValue(this.parseArgValue(arg));
+                    control.patchValue(this.parseArgValue(paymentMethod, arg));
                 } else {
-                    configArgsGroup.addControl(arg.name, this.formBuilder.control(this.parseArgValue(arg)));
+                    configArgsGroup.addControl(
+                        arg.name,
+                        this.formBuilder.control(this.parseArgValue(paymentMethod, arg)),
+                    );
                 }
             }
         }
         this.changeDetector.markForCheck();
     }
 
-    private parseArgValue(arg: ConfigArg): string | number | boolean {
-        switch (arg.type) {
+    private parseArgValue(paymentMethod: PaymentMethod.Fragment, arg: ConfigArg): string | number | boolean {
+        const type = paymentMethod.definition.args.find(a => (a.name = arg.name))?.type;
+        switch (type) {
             case 'int':
                 return Number.parseInt(arg.value || '0', 10);
             case 'boolean':

+ 12 - 12
packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.ts

@@ -19,6 +19,7 @@ import {
     UpdateShippingMethodInput,
 } from '@vendure/admin-ui/core';
 import { normalizeString } from '@vendure/common/lib/normalize-string';
+import { ConfigArgType } from '@vendure/common/lib/shared-types';
 import { combineLatest, merge, Observable, of, Subject } from 'rxjs';
 import { mergeMap, switchMap, take, takeUntil } from 'rxjs/operators';
 
@@ -75,16 +76,16 @@ export class ShippingMethodDetailComponent extends BaseDetailComponent<ShippingM
             this.calculators = data.shippingCalculators;
             this.changeDetector.markForCheck();
             this.selectedCheckerDefinition = data.shippingEligibilityCheckers.find(
-                (c) => c.code === (entity.checker && entity.checker.code),
+                c => c.code === (entity.checker && entity.checker.code),
             );
             this.selectedCalculatorDefinition = data.shippingCalculators.find(
-                (c) => c.code === (entity.calculator && entity.calculator.code),
+                c => c.code === (entity.calculator && entity.calculator.code),
             );
         });
 
         this.activeChannel$ = this.dataService.settings
             .getActiveChannel()
-            .mapStream((data) => data.activeChannel);
+            .mapStream(data => data.activeChannel);
 
         this.testResult$ = this.fetchTestResult$.pipe(
             switchMap(([address, lines]) => {
@@ -94,7 +95,7 @@ export class ShippingMethodDetailComponent extends BaseDetailComponent<ShippingM
                 const formValue = this.detailForm.value;
                 const input: TestShippingMethodInput = {
                     shippingAddress: { ...address, streetLine1: 'test' },
-                    lines: lines.map((l) => ({ productVariantId: l.id, quantity: l.quantity })),
+                    lines: lines.map(l => ({ productVariantId: l.id, quantity: l.quantity })),
                     checker: this.toAdjustmentOperationInput(this.selectedChecker, formValue.checker),
                     calculator: this.toAdjustmentOperationInput(
                         this.selectedCalculator,
@@ -103,7 +104,7 @@ export class ShippingMethodDetailComponent extends BaseDetailComponent<ShippingM
                 };
                 return this.dataService.shippingMethod
                     .testShippingMethod(input)
-                    .mapSingle((result) => result.testShippingMethod);
+                    .mapSingle(result => result.testShippingMethod);
             }),
         );
 
@@ -153,10 +154,10 @@ export class ShippingMethodDetailComponent extends BaseDetailComponent<ShippingM
     private configurableDefinitionToInstance(def: ConfigurableOperationDefinition): ConfigurableOperation {
         return {
             ...def,
-            args: def.args.map((arg) => {
+            args: def.args.map(arg => {
                 return {
                     ...arg,
-                    value: getDefaultConfigArgValue(arg),
+                    value: getDefaultConfigArgValue(arg.type as ConfigArgType),
                 };
             }),
         } as ConfigurableOperation;
@@ -174,7 +175,7 @@ export class ShippingMethodDetailComponent extends BaseDetailComponent<ShippingM
             calculator: this.toAdjustmentOperationInput(this.selectedCalculator, formValue.calculator),
         };
         this.dataService.shippingMethod.createShippingMethod(input).subscribe(
-            (data) => {
+            data => {
                 this.notificationService.success(_('common.notify-create-success'), {
                     entity: 'ShippingMethod',
                 });
@@ -182,7 +183,7 @@ export class ShippingMethodDetailComponent extends BaseDetailComponent<ShippingM
                 this.changeDetector.markForCheck();
                 this.router.navigate(['../', data.createShippingMethod.id], { relativeTo: this.route });
             },
-            (err) => {
+            err => {
                 this.notificationService.error(_('common.notify-create-error'), {
                     entity: 'ShippingMethod',
                 });
@@ -212,14 +213,14 @@ export class ShippingMethodDetailComponent extends BaseDetailComponent<ShippingM
                 }),
             )
             .subscribe(
-                (data) => {
+                data => {
                     this.notificationService.success(_('common.notify-update-success'), {
                         entity: 'ShippingMethod',
                     });
                     this.detailForm.markAsPristine();
                     this.changeDetector.markForCheck();
                 },
-                (err) => {
+                err => {
                     this.notificationService.error(_('common.notify-update-error'), {
                         entity: 'ShippingMethod',
                     });
@@ -264,7 +265,6 @@ export class ShippingMethodDetailComponent extends BaseDetailComponent<ShippingM
             arguments: Object.values<any>(formValueOperations.args || {}).map((value, j) => ({
                 name: operation.args[j].name,
                 value: value.hasOwnProperty('value') ? (value as any).value : value.toString(),
-                type: operation.args[j].type,
             })),
         };
     }

+ 1 - 0
packages/admin-ui/src/lib/settings/src/providers/routing/payment-method-resolver.ts

@@ -22,6 +22,7 @@ export class PaymentMethodResolver extends BaseEntityResolver<PaymentMethod.Frag
                 code: '',
                 enabled: true,
                 configArgs: [],
+                definition: {} as any,
             },
             id => dataService.settings.getPaymentMethod(id).mapStream(data => data.paymentMethod),
         );

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

@@ -318,6 +318,7 @@ export type ConfigArgDefinition = {
     __typename?: 'ConfigArgDefinition';
     name: Scalars['String'];
     type: Scalars['String'];
+    list: Scalars['Boolean'];
     label?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
     config?: Maybe<Scalars['JSON']>;
@@ -2490,6 +2491,7 @@ export type PaymentMethod = Node & {
     code: Scalars['String'];
     enabled: Scalars['Boolean'];
     configArgs: Array<ConfigArg>;
+    definition: ConfigurableOperationDefinition;
 };
 
 export type PaymentMethodFilterParameter = {

+ 2 - 0
packages/common/src/generated-shop-types.ts

@@ -233,6 +233,7 @@ export type ConfigArgDefinition = {
     __typename?: 'ConfigArgDefinition';
     name: Scalars['String'];
     type: Scalars['String'];
+    list: Scalars['Boolean'];
     label?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
     config?: Maybe<Scalars['JSON']>;
@@ -1740,6 +1741,7 @@ export type PaymentMethod = Node & {
     code: Scalars['String'];
     enabled: Scalars['Boolean'];
     configArgs: Array<ConfigArg>;
+    definition: ConfigurableOperationDefinition;
 };
 
 /**

+ 2 - 0
packages/common/src/generated-types.ts

@@ -318,6 +318,7 @@ export type ConfigArgDefinition = {
    __typename?: 'ConfigArgDefinition';
   name: Scalars['String'];
   type: Scalars['String'];
+  list: Scalars['Boolean'];
   label?: Maybe<Scalars['String']>;
   description?: Maybe<Scalars['String']>;
   config?: Maybe<Scalars['JSON']>;
@@ -2578,6 +2579,7 @@ export type PaymentMethod = Node & {
   code: Scalars['String'];
   enabled: Scalars['Boolean'];
   configArgs: Array<ConfigArg>;
+  definition: ConfigurableOperationDefinition;
 };
 
 export type PaymentMethodFilterParameter = {

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

@@ -318,6 +318,7 @@ export type ConfigArgDefinition = {
     __typename?: 'ConfigArgDefinition';
     name: Scalars['String'];
     type: Scalars['String'];
+    list: Scalars['Boolean'];
     label?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
     config?: Maybe<Scalars['JSON']>;
@@ -2490,6 +2491,7 @@ export type PaymentMethod = Node & {
     code: Scalars['String'];
     enabled: Scalars['Boolean'];
     configArgs: Array<ConfigArg>;
+    definition: ConfigurableOperationDefinition;
 };
 
 export type PaymentMethodFilterParameter = {

+ 2 - 0
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -233,6 +233,7 @@ export type ConfigArgDefinition = {
     __typename?: 'ConfigArgDefinition';
     name: Scalars['String'];
     type: Scalars['String'];
+    list: Scalars['Boolean'];
     label?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
     config?: Maybe<Scalars['JSON']>;
@@ -1740,6 +1741,7 @@ export type PaymentMethod = Node & {
     code: Scalars['String'];
     enabled: Scalars['Boolean'];
     configArgs: Array<ConfigArg>;
+    definition: ConfigurableOperationDefinition;
 };
 
 /**

+ 2 - 0
packages/core/src/api/api-internal-modules.ts

@@ -43,6 +43,7 @@ import { FulfillmentEntityResolver } from './resolvers/entity/fulfillment-entity
 import { OrderAdminEntityResolver, OrderEntityResolver } from './resolvers/entity/order-entity.resolver';
 import { OrderLineEntityResolver } from './resolvers/entity/order-line-entity.resolver';
 import { PaymentEntityResolver } from './resolvers/entity/payment-entity.resolver';
+import { PaymentMethodEntityResolver } from './resolvers/entity/payment-method-entity.resolver';
 import {
     ProductAdminEntityResolver,
     ProductEntityResolver,
@@ -106,6 +107,7 @@ export const entityResolvers = [
     OrderEntityResolver,
     OrderLineEntityResolver,
     PaymentEntityResolver,
+    PaymentMethodEntityResolver,
     ProductEntityResolver,
     ProductOptionEntityResolver,
     ProductOptionGroupEntityResolver,

+ 20 - 0
packages/core/src/api/resolvers/entity/payment-method-entity.resolver.ts

@@ -0,0 +1,20 @@
+import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
+import { ConfigurableOperationDefinition } from '@vendure/common/lib/generated-types';
+
+import { PaymentMethod } from '../../../entity/payment-method/payment-method.entity';
+import { PaymentMethodService } from '../../../service/services/payment-method.service';
+import { RequestContext } from '../../common/request-context';
+import { Ctx } from '../../decorators/request-context.decorator';
+
+@Resolver('PaymentMethod')
+export class PaymentMethodEntityResolver {
+    constructor(private paymentMethodService: PaymentMethodService) {}
+
+    @ResolveField()
+    async definition(
+        @Ctx() ctx: RequestContext,
+        @Parent() paymentMethod: PaymentMethod,
+    ): Promise<ConfigurableOperationDefinition> {
+        return this.paymentMethodService.getPaymentMethodHandler(paymentMethod.code).toGraphQlType(ctx);
+    }
+}

+ 1 - 0
packages/core/src/api/schema/common/common-types.graphql

@@ -28,6 +28,7 @@ type ConfigArg {
 type ConfigArgDefinition {
     name: String!
     type: String!
+    list: Boolean!
     label: String
     description: String
     config: JSON

+ 1 - 0
packages/core/src/api/schema/type/payment-method.type.graphql

@@ -5,4 +5,5 @@ type PaymentMethod implements Node {
     code: String!
     enabled: Boolean!
     configArgs: [ConfigArg!]!
+    definition: ConfigurableOperationDefinition!
 }

+ 43 - 32
packages/core/src/common/configurable-operation.ts

@@ -38,10 +38,16 @@ export type LocalizedStringArray = Array<Omit<LocalizedString, '__typename'>>;
 
 export interface ConfigArgCommonDef<T extends ConfigArgType> {
     type: T;
+    list?: boolean;
     label?: LocalizedStringArray;
     description?: LocalizedStringArray;
 }
 
+export type ConfigArgListDef<
+    T extends ConfigArgType,
+    C extends ConfigArgCommonDef<T> = ConfigArgCommonDef<T>
+> = C & { list: true };
+
 export type WithArgConfig<T> = {
     config?: T;
 };
@@ -89,21 +95,28 @@ export type ConfigArgs<T extends ConfigArgType> = {
     [name: string]: ConfigArgDef<T>;
 };
 
-// prettier-ignore
 /**
  * Represents the ConfigArgs once they have been coerced into JavaScript values for use
  * in business logic.
  */
 export type ConfigArgValues<T extends ConfigArgs<any>> = {
-    [K in keyof T]: T[K] extends ConfigArgDef<'int' | 'float'>
+    [K in keyof T]: T[K] extends ConfigArgListDef<'int' | 'float'>
+        ? number[]
+        : T[K] extends ConfigArgDef<'int' | 'float'>
         ? number
+        : T[K] extends ConfigArgListDef<'datetime'>
+        ? Date[]
         : T[K] extends ConfigArgDef<'datetime'>
-            ? Date
-            : T[K] extends ConfigArgDef<'boolean'>
-                ? boolean
-                : T[K] extends ConfigArgDef<'facetValueIds'>
-                    ? string[]
-                        : string
+        ? Date
+        : T[K] extends ConfigArgListDef<'boolean'>
+        ? boolean[]
+        : T[K] extends ConfigArgDef<'boolean'>
+        ? boolean
+        : T[K] extends ConfigArgDef<'facetValueIds'>
+        ? string[]
+        : T[K] extends ConfigArgListDef<'string'>
+        ? string[]
+        : string;
 };
 
 /**
@@ -175,6 +188,28 @@ export class ConfigurableOperationDef<T extends ConfigArgs<ConfigArgType>> {
         }
     }
 
+    /**
+     * Convert a ConfigurableOperationDef into a ConfigurableOperationDefinition object, typically
+     * so that it can be sent via the API.
+     */
+    toGraphQlType(ctx: RequestContext): ConfigurableOperationDefinition {
+        return {
+            code: this.code,
+            description: localizeString(this.description, ctx.languageCode),
+            args: Object.entries(this.args).map(
+                ([name, arg]) =>
+                    ({
+                        name,
+                        type: arg.type,
+                        list: arg.list ?? false,
+                        config: localizeConfig(arg, ctx.languageCode),
+                        label: arg.label && localizeString(arg.label, ctx.languageCode),
+                        description: arg.description && localizeString(arg.description, ctx.languageCode),
+                    } as Required<ConfigArgDefinition>),
+            ),
+        };
+    }
+
     /**
      * Coverts an array of ConfigArgs into a hash object:
      *
@@ -198,30 +233,6 @@ export class ConfigurableOperationDef<T extends ConfigArgs<ConfigArgType>> {
     }
 }
 
-/**
- * Convert a ConfigurableOperationDef into a ConfigurableOperation object, typically
- * so that it can be sent via the API.
- */
-export function configurableDefToOperation(
-    ctx: RequestContext,
-    def: ConfigurableOperationDef<ConfigArgs<any>>,
-): ConfigurableOperationDefinition {
-    return {
-        code: def.code,
-        description: localizeString(def.description, ctx.languageCode),
-        args: Object.entries(def.args).map(
-            ([name, arg]) =>
-                ({
-                    name,
-                    type: arg.type,
-                    config: localizeConfig(arg, ctx.languageCode),
-                    label: arg.label && localizeString(arg.label, ctx.languageCode),
-                    description: arg.description && localizeString(arg.description, ctx.languageCode),
-                } as Required<ConfigArgDefinition>),
-        ),
-    };
-}
-
 function localizeConfig(
     arg: StringArgConfig | IntArgConfig | WithArgConfig<undefined>,
     languageCode: LanguageCode,

+ 1 - 0
packages/core/src/config/promotion/default-promotion-conditions.ts

@@ -10,6 +10,7 @@ export const minimumOrderAmount = new PromotionCondition({
     args: {
         amount: { type: 'int', config: { inputType: 'money' } },
         taxInclusive: { type: 'boolean' },
+        ids: { type: 'string', list: true },
     },
     check(order, args) {
         if (args.taxInclusive) {

+ 20 - 23
packages/core/src/service/services/collection.service.ts

@@ -17,7 +17,6 @@ import { debounceTime } from 'rxjs/operators';
 import { Connection } from 'typeorm';
 
 import { RequestContext, SerializedRequestContext } from '../../api/common/request-context';
-import { configurableDefToOperation } from '../../common/configurable-operation';
 import { IllegalOperationError, UserInputError } from '../../common/error/errors';
 import { ListQueryOptions } from '../../common/types/common-types';
 import { Translated } from '../../common/types/locale-types';
@@ -73,18 +72,18 @@ export class CollectionService implements OnModuleInit {
 
         merge(productEvents$, variantEvents$)
             .pipe(debounceTime(50))
-            .subscribe(async (event) => {
+            .subscribe(async event => {
                 const collections = await this.connection.getRepository(Collection).find();
                 this.applyFiltersQueue.add({
                     ctx: event.ctx.serialize(),
-                    collectionIds: collections.map((c) => c.id),
+                    collectionIds: collections.map(c => c.id),
                 });
             });
 
         this.applyFiltersQueue = this.jobQueueService.createQueue({
             name: 'apply-collection-filters',
             concurrency: 1,
-            process: async (job) => {
+            process: async job => {
                 const collections = await this.connection
                     .getRepository(Collection)
                     .findByIds(job.data.collectionIds);
@@ -108,7 +107,7 @@ export class CollectionService implements OnModuleInit {
             })
             .getManyAndCount()
             .then(async ([collections, totalItems]) => {
-                const items = collections.map((collection) =>
+                const items = collections.map(collection =>
                     translateDeep(collection, ctx.languageCode, ['parent']),
                 );
                 return {
@@ -145,9 +144,7 @@ export class CollectionService implements OnModuleInit {
     }
 
     getAvailableFilters(ctx: RequestContext): ConfigurableOperationDefinition[] {
-        return this.configService.catalogOptions.collectionFilters.map((x) =>
-            configurableDefToOperation(ctx, x),
-        );
+        return this.configService.catalogOptions.collectionFilters.map(f => f.toGraphQlType(ctx));
     }
 
     async getParent(ctx: RequestContext, collectionId: ID): Promise<Collection | undefined> {
@@ -158,7 +155,7 @@ export class CollectionService implements OnModuleInit {
             .createQueryBuilder('collection')
             .leftJoinAndSelect('collection.translations', 'translation')
             .where(
-                (qb) =>
+                qb =>
                     `collection.id = ${qb
                         .subQuery()
                         .select(parentIdSelect)
@@ -207,7 +204,7 @@ export class CollectionService implements OnModuleInit {
         }
         const result = await qb.getMany();
 
-        return result.map((collection) => translateDeep(collection, ctx.languageCode));
+        return result.map(collection => translateDeep(collection, ctx.languageCode));
     }
 
     /**
@@ -233,7 +230,7 @@ export class CollectionService implements OnModuleInit {
         };
 
         const descendants = await getChildren(rootId);
-        return descendants.map((c) => translateDeep(c, ctx.languageCode));
+        return descendants.map(c => translateDeep(c, ctx.languageCode));
     }
 
     /**
@@ -265,9 +262,9 @@ export class CollectionService implements OnModuleInit {
 
         return this.connection
             .getRepository(Collection)
-            .findByIds(ancestors.map((c) => c.id))
-            .then((categories) => {
-                return ctx ? categories.map((c) => translateDeep(c, ctx.languageCode)) : categories;
+            .findByIds(ancestors.map(c => c.id))
+            .then(categories => {
+                return ctx ? categories.map(c => translateDeep(c, ctx.languageCode)) : categories;
             });
     }
 
@@ -277,7 +274,7 @@ export class CollectionService implements OnModuleInit {
             input,
             entityType: Collection,
             translationType: CollectionTranslation,
-            beforeSave: async (coll) => {
+            beforeSave: async coll => {
                 await this.channelService.assignToCurrentChannel(coll, ctx);
                 const parent = await this.getParentCollection(ctx, input.parentId);
                 if (parent) {
@@ -302,7 +299,7 @@ export class CollectionService implements OnModuleInit {
             input,
             entityType: Collection,
             translationType: CollectionTranslation,
-            beforeSave: async (coll) => {
+            beforeSave: async coll => {
                 if (input.filters) {
                     coll.filters = this.getCollectionFiltersFromInput(input);
                 }
@@ -346,7 +343,7 @@ export class CollectionService implements OnModuleInit {
 
         if (
             idsAreEqual(input.parentId, target.id) ||
-            descendants.some((cat) => idsAreEqual(input.parentId, cat.id))
+            descendants.some(cat => idsAreEqual(input.parentId, cat.id))
         ) {
             throw new IllegalOperationError(`error.cannot-move-collection-into-self`);
         }
@@ -403,13 +400,13 @@ export class CollectionService implements OnModuleInit {
         collections: Collection[],
         job: Job<ApplyCollectionFiltersJobData>,
     ): Promise<void> {
-        const collectionIds = collections.map((c) => c.id);
+        const collectionIds = collections.map(c => c.id);
         const requestContext = RequestContext.deserialize(ctx);
 
         this.workerService.send(new ApplyCollectionFiltersMessage({ collectionIds })).subscribe({
             next: ({ total, completed, duration, collectionId, affectedVariantIds }) => {
                 const progress = Math.ceil((completed / total) * 100);
-                const collection = collections.find((c) => idsAreEqual(c.id, collectionId));
+                const collection = collections.find(c => idsAreEqual(c.id, collectionId));
                 if (collection) {
                     this.eventBus.publish(
                         new CollectionModificationEvent(requestContext, collection, affectedVariantIds),
@@ -420,7 +417,7 @@ export class CollectionService implements OnModuleInit {
             complete: () => {
                 job.complete();
             },
-            error: (err) => {
+            error: err => {
                 Logger.error(err);
                 job.fail(err);
             },
@@ -432,14 +429,14 @@ export class CollectionService implements OnModuleInit {
      */
     async getCollectionProductVariantIds(collection: Collection): Promise<ID[]> {
         if (collection.productVariants) {
-            return collection.productVariants.map((v) => v.id);
+            return collection.productVariants.map(v => v.id);
         } else {
             const productVariants = await this.connection
                 .getRepository(ProductVariant)
                 .createQueryBuilder('variant')
                 .innerJoin('variant.collections', 'collection', 'collection.id = :id', { id: collection.id })
                 .getMany();
-            return productVariants.map((v) => v.id);
+            return productVariants.map(v => v.id);
         }
     }
 
@@ -519,7 +516,7 @@ export class CollectionService implements OnModuleInit {
     }
 
     private getFilterByCode(code: string): CollectionFilter<any> {
-        const match = this.configService.catalogOptions.collectionFilters.find((a) => a.code === code);
+        const match = this.configService.catalogOptions.collectionFilters.find(a => a.code === code);
         if (!match) {
             throw new UserInputError(`error.adjustment-operation-with-code-not-found`, { code });
         }

+ 9 - 6
packages/core/src/service/services/payment-method.service.ts

@@ -142,6 +142,14 @@ export class PaymentMethodService {
         return refund;
     }
 
+    getPaymentMethodHandler(code: string): PaymentMethodHandler {
+        const handler = this.configService.paymentOptions.paymentMethodHandlers.find(h => h.code === code);
+        if (!handler) {
+            throw new UserInputError(`error.no-payment-handler-with-code`, { code });
+        }
+        return handler;
+    }
+
     private async getMethodAndHandler(
         method: string,
     ): Promise<{ paymentMethod: PaymentMethod; handler: PaymentMethodHandler }> {
@@ -154,12 +162,7 @@ export class PaymentMethodService {
         if (!paymentMethod) {
             throw new UserInputError(`error.payment-method-not-found`, { method });
         }
-        const handler = this.configService.paymentOptions.paymentMethodHandlers.find(
-            h => h.code === paymentMethod.code,
-        );
-        if (!handler) {
-            throw new UserInputError(`error.no-payment-handler-with-code`, { code: paymentMethod.code });
-        }
+        const handler = this.getPaymentMethodHandler(paymentMethod.code);
         return { paymentMethod, handler };
     }
 

+ 2 - 3
packages/core/src/service/services/promotion.service.ts

@@ -17,7 +17,6 @@ import { unique } from '@vendure/common/lib/unique';
 import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
-import { configurableDefToOperation } from '../../common/configurable-operation';
 import {
     CouponCodeExpiredError,
     CouponCodeInvalidError,
@@ -81,11 +80,11 @@ export class PromotionService {
     }
 
     getPromotionConditions(ctx: RequestContext): ConfigurableOperationDefinition[] {
-        return this.availableConditions.map(x => configurableDefToOperation(ctx, x));
+        return this.availableConditions.map(x => x.toGraphQlType(ctx));
     }
 
     getPromotionActions(ctx: RequestContext): ConfigurableOperationDefinition[] {
-        return this.availableActions.map(x => configurableDefToOperation(ctx, x));
+        return this.availableActions.map(x => x.toGraphQlType(ctx));
     }
 
     /**

+ 2 - 5
packages/core/src/service/services/shipping-method.service.ts

@@ -12,7 +12,6 @@ import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
 import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
-import { configurableDefToOperation } from '../../common/configurable-operation';
 import { EntityNotFoundError } from '../../common/error/errors';
 import { ListQueryOptions } from '../../common/types/common-types';
 import { assertFound } from '../../common/utils';
@@ -112,13 +111,11 @@ export class ShippingMethodService {
     }
 
     getShippingEligibilityCheckers(ctx: RequestContext): ConfigurableOperationDefinition[] {
-        return this.shippingConfiguration.shippingEligibilityCheckers.map(x =>
-            configurableDefToOperation(ctx, x),
-        );
+        return this.shippingConfiguration.shippingEligibilityCheckers.map(x => x.toGraphQlType(ctx));
     }
 
     getShippingCalculators(ctx: RequestContext): ConfigurableOperationDefinition[] {
-        return this.shippingConfiguration.shippingCalculators.map(x => configurableDefToOperation(ctx, x));
+        return this.shippingConfiguration.shippingCalculators.map(x => x.toGraphQlType(ctx));
     }
 
     getActiveShippingMethods(channel: Channel): ShippingMethod[] {

+ 1 - 1
packages/dev-server/dev-config.ts

@@ -53,7 +53,7 @@ export const devConfig: VendureConfig = {
         paymentMethodHandlers: [examplePaymentHandler],
     },
     customFields: {},
-    logger: new DefaultLogger({ level: LogLevel.Debug }),
+    logger: new DefaultLogger({ level: LogLevel.Info }),
     importExportOptions: {
         importAssetsDir: path.join(__dirname, 'import-assets'),
     },

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

@@ -318,6 +318,7 @@ export type ConfigArgDefinition = {
     __typename?: 'ConfigArgDefinition';
     name: Scalars['String'];
     type: Scalars['String'];
+    list: Scalars['Boolean'];
     label?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
     config?: Maybe<Scalars['JSON']>;
@@ -2490,6 +2491,7 @@ export type PaymentMethod = Node & {
     code: Scalars['String'];
     enabled: Scalars['Boolean'];
     configArgs: Array<ConfigArg>;
+    definition: ConfigurableOperationDefinition;
 };
 
 export type PaymentMethodFilterParameter = {

File diff suppressed because it is too large
+ 0 - 0
schema-admin.json


File diff suppressed because it is too large
+ 0 - 0
schema-shop.json


Some files were not shown because too many files changed in this diff