Browse Source

feat(payments-plugin): Setup of preventing duplicate payments

Martijn 1 year ago
parent
commit
0cb2df83e2

+ 11 - 3
packages/payments-plugin/src/mollie/mollie.plugin.ts

@@ -115,8 +115,9 @@ export interface MolliePluginOptions {
  *       MolliePlugin.init({ vendureHost: 'https://yourhost.io/', useDynamicRedirectUrl: true }),
  *     ]
  *     ```
- * 2. Create a new PaymentMethod in the Admin UI, and select "Mollie payments" as the handler.
- * 3. Set your Mollie apiKey in the `API Key` field.
+ * 2. Run a database migration to add the `mollieOrderId` custom field to the order entity.
+ * 3. Create a new PaymentMethod in the Admin UI, and select "Mollie payments" as the handler.
+ * 4. Set your Mollie apiKey in the `API Key` field.
  *
  * ## Specifying the redirectUrl
  *
@@ -128,7 +129,6 @@ export interface MolliePluginOptions {
  * By default, this option is set to `false` for backwards compatibility. In a future version, this option will be deprecated.
  * Upon deprecation, the `redirectUrl` will always be passed as an argument to the `createPaymentIntent` mutation.
  *
- * TODO toevoegen van /code weggehaald..!
  * ## Storefront usage
  *
  * In your storefront you add a payment to an order using the `createMolliePaymentIntent` mutation. In this example, our Mollie
@@ -196,6 +196,14 @@ export interface MolliePluginOptions {
  * If you don't want this behaviour (Authorized first), you can set 'autoCapture=true' on the payment method. This option will immediately
  * capture the payment after a customer authorizes the payment.
  *
+ * ## ArrangingAdditionalPayment state
+ *
+ * In some rare cases, a customer can add items to the active order, while a Mollie payment is still open,
+ * for example by opening your storefront in another browser tab.
+ * This could result in an order being in `ArrangingAdditionalPayment` status after the customer finished payment.
+ * You should check if there is still an active order with status `ArrangingAdditionalPayment` on your order confirmation page,
+ * and if so, allow your customer to pay for the additional items.
+ *
  * @docsCategory core plugins/PaymentsPlugin
  * @docsPage MolliePlugin
  * @docsWeight 0

+ 20 - 4
packages/payments-plugin/src/mollie/mollie.service.ts

@@ -22,6 +22,7 @@ import {
     ProductVariantService,
     RequestContext,
 } from '@vendure/core';
+import { OrderStateMachine } from '@vendure/core/dist/service/helpers/order-state-machine/order-state-machine';
 import { totalCoveredByPayments } from '@vendure/core/dist/service/helpers/utils/order-utils';
 
 import { loggerCtx, PLUGIN_INIT_OPTIONS } from './constants';
@@ -55,6 +56,8 @@ class InvalidInputError implements MolliePaymentIntentError {
 
 @Injectable()
 export class MollieService {
+    private readonly injector: Injector;
+
     constructor(
         private paymentMethodService: PaymentMethodService,
         @Inject(PLUGIN_INIT_OPTIONS) private options: MolliePluginOptions,
@@ -63,7 +66,9 @@ export class MollieService {
         private entityHydrator: EntityHydrator,
         private variantService: ProductVariantService,
         private moduleRef: ModuleRef,
-    ) {}
+    ) {
+        this.injector = new Injector(this.moduleRef);
+    }
 
     /**
      * Creates a redirectUrl to Mollie for the given paymentMethod and current activeOrder
@@ -139,6 +144,10 @@ export class MollieService {
             }
             redirectUrl = paymentMethodRedirectUrl;
         }
+        if (order.state !== 'ArrangingPayment' && order.state !== 'ArrangingAdditionalPayment') {
+            // TODO get order state machine and check if transitionable to ArrangingPayment
+            // const orderStateMachine = this.injector.get(OrderStateMachine);
+        }
         const variantsWithInsufficientSaleableStock = await this.getVariantsWithInsufficientStock(ctx, order);
         if (variantsWithInsufficientSaleableStock.length) {
             return new PaymentIntentError(
@@ -231,7 +240,14 @@ export class MollieService {
                 `Unable to find order ${mollieOrder.orderNumber}, unable to process Mollie order ${mollieOrder.id}`,
             );
         }
-        if (order.state === 'PaymentSettled' || order.state === 'Shipped' || order.state === 'Delivered') {
+        if (
+            order.state === 'PaymentSettled' ||
+            order.state === 'Cancelled' ||
+            order.state === 'Shipped' ||
+            order.state === 'PartiallyShipped' ||
+            order.state === 'Delivered' ||
+            order.state === 'PartiallyDelivered'
+        ) {
             Logger.info(
                 `Order ${order.code} is already '${order.state}', no need for handling Mollie status '${mollieOrder.status}'`,
                 loggerCtx,
@@ -280,7 +296,7 @@ export class MollieService {
         paymentMethodCode: string,
         status: 'Authorized' | 'Settled',
     ): Promise<Order> {
-        if (order.state !== 'ArrangingPayment') {
+        if (order.state !== 'ArrangingPayment' && order.state !== 'ArrangingAdditionalPayment') {
             const transitionToStateResult = await this.orderService.transitionToState(
                 ctx,
                 order.id,
@@ -347,7 +363,7 @@ export class MollieService {
         const client = createMollieClient({ apiKey });
         const activeOrder = await this.activeOrderService.getActiveOrder(ctx, undefined);
         const additionalParams = await this.options.enabledPaymentMethodsParams?.(
-            new Injector(this.moduleRef),
+            this.injector,
             ctx,
             activeOrder ?? null,
         );