Kaynağa Gözat

feat(core): Base custom payment process

Elorm Koto 5 yıl önce
ebeveyn
işleme
83af699b7f

+ 1 - 0
packages/core/src/config/index.ts

@@ -21,6 +21,7 @@ export * from './order/custom-order-process';
 export * from './order/order-code-strategy';
 export * from './order/order-code-strategy';
 export * from './order/order-merge-strategy';
 export * from './order/order-merge-strategy';
 export * from './order/price-calculation-strategy';
 export * from './order/price-calculation-strategy';
+export * from './payment-method/custom-payment-process';
 export * from './payment-method/example-payment-method-handler';
 export * from './payment-method/example-payment-method-handler';
 export * from './payment-method/payment-method-handler';
 export * from './payment-method/payment-method-handler';
 export * from './promotion';
 export * from './promotion';

+ 26 - 0
packages/core/src/config/payment/custom-payment-process.ts

@@ -0,0 +1,26 @@
+import {
+    OnTransitionEndFn,
+    OnTransitionErrorFn,
+    OnTransitionStartFn,
+    Transitions,
+} from '../../common/finite-state-machine/types';
+import { InjectableStrategy } from '../../common/types/injectable-strategy';
+import {
+    PaymentState,
+    PaymentTransitionData,
+} from '../../service/helpers/payment-state-machine/payment-state';
+
+/**
+ * @description
+ * Used to define extensions to or modifications of the default payment process.
+ *
+ * For detailed description of the interface members, see the {@link StateMachineConfig} docs.
+ *
+ * @docsCategory fulfillment
+ */
+export interface CustomPaymentProcess<State extends string> extends InjectableStrategy {
+    transitions?: Transitions<State, State | PaymentState> & Partial<Transitions<PaymentState | State>>;
+    onTransitionStart?: OnTransitionStartFn<State | PaymentState, PaymentTransitionData>;
+    onTransitionEnd?: OnTransitionEndFn<State | PaymentState, PaymentTransitionData>;
+    onTransitionError?: OnTransitionErrorFn<State | PaymentState>;
+}

+ 0 - 0
packages/core/src/config/payment-method/example-payment-method-handler.ts → packages/core/src/config/payment/example-payment-method-handler.ts


+ 0 - 0
packages/core/src/config/payment-method/payment-method-handler.ts → packages/core/src/config/payment/payment-method-handler.ts


+ 9 - 1
packages/core/src/config/vendure-config.ts

@@ -27,7 +27,8 @@ import { CustomOrderProcess } from './order/custom-order-process';
 import { OrderCodeStrategy } from './order/order-code-strategy';
 import { OrderCodeStrategy } from './order/order-code-strategy';
 import { OrderMergeStrategy } from './order/order-merge-strategy';
 import { OrderMergeStrategy } from './order/order-merge-strategy';
 import { PriceCalculationStrategy } from './order/price-calculation-strategy';
 import { PriceCalculationStrategy } from './order/price-calculation-strategy';
-import { PaymentMethodHandler } from './payment-method/payment-method-handler';
+import { CustomPaymentProcess } from './payment/custom-payment-process';
+import { PaymentMethodHandler } from './payment/payment-method-handler';
 import { PromotionAction } from './promotion/promotion-action';
 import { PromotionAction } from './promotion/promotion-action';
 import { PromotionCondition } from './promotion/promotion-condition';
 import { PromotionCondition } from './promotion/promotion-condition';
 import { SessionCacheStrategy } from './session-cache/session-cache-strategy';
 import { SessionCacheStrategy } from './session-cache/session-cache-strategy';
@@ -568,6 +569,13 @@ export interface PaymentOptions {
      * An array of {@link PaymentMethodHandler}s with which to process payments.
      * An array of {@link PaymentMethodHandler}s with which to process payments.
      */
      */
     paymentMethodHandlers: PaymentMethodHandler[];
     paymentMethodHandlers: PaymentMethodHandler[];
+
+    /**
+     * @description
+     * Allows the definition of custom states and transition logic for the payment process state machine.
+     * Takes an array of objects implementing the {@link CustomPaymentProcess} interface.
+     */
+    customPaymentProcess?: Array<CustomPaymentProcess<any>>;
 }
 }
 
 
 /**
 /**

+ 76 - 13
packages/core/src/service/helpers/payment-state-machine/payment-state-machine.ts

@@ -4,7 +4,9 @@ import { HistoryEntryType } from '@vendure/common/lib/generated-types';
 import { RequestContext } from '../../../api/common/request-context';
 import { RequestContext } from '../../../api/common/request-context';
 import { IllegalOperationError } from '../../../common/error/errors';
 import { IllegalOperationError } from '../../../common/error/errors';
 import { FSM } from '../../../common/finite-state-machine/finite-state-machine';
 import { FSM } from '../../../common/finite-state-machine/finite-state-machine';
-import { StateMachineConfig } from '../../../common/finite-state-machine/types';
+import { mergeTransitionDefinitions } from '../../../common/finite-state-machine/merge-transition-definitions';
+import { StateMachineConfig, Transitions } from '../../../common/finite-state-machine/types';
+import { validateTransitionDefinition } from '../../../common/finite-state-machine/validate-transition-definition';
 import { awaitPromiseOrObservable } from '../../../common/utils';
 import { awaitPromiseOrObservable } from '../../../common/utils';
 import { ConfigService } from '../../../config/config.service';
 import { ConfigService } from '../../../config/config.service';
 import { Order } from '../../../entity/order/order.entity';
 import { Order } from '../../../entity/order/order.entity';
@@ -17,11 +19,20 @@ import { PaymentState, paymentStateTransitions, PaymentTransitionData } from './
 @Injectable()
 @Injectable()
 export class PaymentStateMachine {
 export class PaymentStateMachine {
     private readonly config: StateMachineConfig<PaymentState, PaymentTransitionData>;
     private readonly config: StateMachineConfig<PaymentState, PaymentTransitionData>;
+    private readonly initialState: PaymentState = 'Created';
 
 
     constructor(private configService: ConfigService, private historyService: HistoryService) {
     constructor(private configService: ConfigService, private historyService: HistoryService) {
         this.config = this.initConfig();
         this.config = this.initConfig();
     }
     }
 
 
+    getInitialState(): PaymentState {
+        return this.initialState;
+    }
+
+    canTransition(currentState: PaymentState, newState: PaymentState): boolean {
+        return new FSM(this.config, currentState).canTransitionTo(newState);
+    }
+
     getNextStates(payment: Payment): ReadonlyArray<PaymentState> {
     getNextStates(payment: Payment): ReadonlyArray<PaymentState> {
         const fsm = new FSM(this.config, payment.state);
         const fsm = new FSM(this.config, payment.state);
         return fsm.getNextStates();
         return fsm.getNextStates();
@@ -33,11 +44,59 @@ export class PaymentStateMachine {
         payment.state = state;
         payment.state = state;
     }
     }
 
 
+    /**
+     * Specific business logic to be executed on Payment state transitions.
+     */
+    private async onTransitionStart(
+        fromState: PaymentState,
+        toState: PaymentState,
+        data: PaymentTransitionData,
+    ) {
+        /**/
+    }
+
+    private async onTransitionEnd(
+        fromState: PaymentState,
+        toState: PaymentState,
+        data: PaymentTransitionData,
+    ) {
+        await this.historyService.createHistoryEntryForOrder({
+            ctx: data.ctx,
+            orderId: data.order.id,
+            type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
+            data: {
+                paymentId: data.payment.id,
+                from: fromState,
+                to: toState,
+            },
+        });
+    }
+
     private initConfig(): StateMachineConfig<PaymentState, PaymentTransitionData> {
     private initConfig(): StateMachineConfig<PaymentState, PaymentTransitionData> {
         const { paymentMethodHandlers } = this.configService.paymentOptions;
         const { paymentMethodHandlers } = this.configService.paymentOptions;
+        const customProcesses = this.configService.paymentOptions.customPaymentProcess ?? [];
+
+        const allTransitions = customProcesses.reduce(
+            (transitions, process) =>
+                mergeTransitionDefinitions(transitions, process.transitions as Transitions<any>),
+            paymentStateTransitions,
+        );
+
+        validateTransitionDefinition(allTransitions, this.initialState);
+
         return {
         return {
-            transitions: paymentStateTransitions,
+            transitions: allTransitions,
             onTransitionStart: async (fromState, toState, data) => {
             onTransitionStart: async (fromState, toState, data) => {
+                for (const process of customProcesses) {
+                    if (typeof process.onTransitionStart === 'function') {
+                        const result = await awaitPromiseOrObservable(
+                            process.onTransitionStart(fromState, toState, data),
+                        );
+                        if (result === false || typeof result === 'string') {
+                            return result;
+                        }
+                    }
+                }
                 for (const handler of paymentMethodHandlers) {
                 for (const handler of paymentMethodHandlers) {
                     if (data.payment.method === handler.code) {
                     if (data.payment.method === handler.code) {
                         const result = await awaitPromiseOrObservable(
                         const result = await awaitPromiseOrObservable(
@@ -48,20 +107,24 @@ export class PaymentStateMachine {
                         }
                         }
                     }
                     }
                 }
                 }
+                return this.onTransitionStart(fromState, toState, data);
             },
             },
             onTransitionEnd: async (fromState, toState, data) => {
             onTransitionEnd: async (fromState, toState, data) => {
-                await this.historyService.createHistoryEntryForOrder({
-                    ctx: data.ctx,
-                    orderId: data.order.id,
-                    type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
-                    data: {
-                        paymentId: data.payment.id,
-                        from: fromState,
-                        to: toState,
-                    },
-                });
+                for (const process of customProcesses) {
+                    if (typeof process.onTransitionEnd === 'function') {
+                        await awaitPromiseOrObservable(process.onTransitionEnd(fromState, toState, data));
+                    }
+                }
+                await this.onTransitionEnd(fromState, toState, data);
             },
             },
-            onError: (fromState, toState, message) => {
+            onError: async (fromState, toState, message) => {
+                for (const process of customProcesses) {
+                    if (typeof process.onTransitionError === 'function') {
+                        await awaitPromiseOrObservable(
+                            process.onTransitionError(fromState, toState, message),
+                        );
+                    }
+                }
                 throw new IllegalOperationError(message || 'error.cannot-transition-payment-from-to', {
                 throw new IllegalOperationError(message || 'error.cannot-transition-payment-from-to', {
                     fromState,
                     fromState,
                     toState,
                     toState,