Browse Source

feat(core): Allow setting PaymentState on failure to settle Payment

Closes #809
Michael Bromley 4 years ago
parent
commit
0241ade326

+ 1 - 0
packages/core/e2e/fixtures/test-payment-methods.ts

@@ -121,6 +121,7 @@ export const failsToSettlePaymentMethod = new PaymentMethodHandler({
     settlePayment: () => {
         return {
             success: false,
+            state: 'Cancelled',
             errorMessage: 'Something went horribly wrong',
             metadata: {
                 privateSettlePaymentData: 'secret',

+ 3 - 2
packages/core/e2e/order.e2e-spec.ts

@@ -17,7 +17,7 @@ import gql from 'graphql-tag';
 import path from 'path';
 
 import { initialData } from '../../../e2e-common/e2e-initial-data';
-import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
+import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
 
 import {
     failsToSettlePaymentMethod,
@@ -83,9 +83,9 @@ import {
     DELETE_SHIPPING_METHOD,
     GET_CUSTOMER_LIST,
     GET_ORDER,
+    GET_ORDERS_LIST,
     GET_ORDER_FULFILLMENTS,
     GET_ORDER_HISTORY,
-    GET_ORDERS_LIST,
     GET_PRODUCT_WITH_VARIANTS,
     GET_STOCK_MOVEMENT,
     SETTLE_PAYMENT,
@@ -356,6 +356,7 @@ describe('Orders resolver', () => {
             });
 
             expect(result.order!.state).toBe('PaymentAuthorized');
+            expect(result.order!.payments![0].state).toBe('Cancelled');
             firstOrderCode = order.code;
             firstOrderId = order.id;
         });

+ 34 - 3
packages/core/src/config/payment/payment-method-handler.ts

@@ -42,6 +42,9 @@ export interface CreatePaymentResult {
      *
      * In a single-step payment flow, this should be set to `'Settled'`.
      * In a two-step flow, this should be set to `'Authorized'`.
+     *
+     * If using a {@link CustomOrderProcess}, may be something else
+     * entirely according to your business logic.
      */
     state: Exclude<PaymentState, 'Error'>;
     /**
@@ -101,13 +104,41 @@ export interface CreateRefundResult {
 
 /**
  * @description
- * This object is the return value of the {@link SettlePaymentFn}
+ * This object is the return value of the {@link SettlePaymentFn} when the Payment
+ * has been successfully settled.
  *
  * @docsCategory payment
  * @docsPage Payment Method Types
  */
 export interface SettlePaymentResult {
-    success: boolean;
+    success: true;
+    metadata?: PaymentMetadata;
+}
+
+/**
+ * @description
+ * This object is the return value of the {@link SettlePaymentFn} when the Payment
+ * could not be settled.
+ *
+ * @docsCategory payment
+ * @docsPage Payment Method Types
+ */
+export interface SettlePaymentErrorResult {
+    success: false;
+    /**
+     * @description
+     * The state to transition this Payment to upon unsuccessful settlement.
+     * Defaults to `Error`. Note that if using a different state, it must be
+     * legal to transition to that state from the `Authorized` state according
+     * to the PaymentState config (which can be customized using the
+     * {@link CustomPaymentProcess}).
+     */
+    state?: Exclude<PaymentState, 'Settled'>;
+    /**
+     * @description
+     * The message that will be returned when attempting to settle the payment, and will
+     * also be persisted as `Payment.errorMessage`.
+     */
     errorMessage?: string;
     metadata?: PaymentMetadata;
 }
@@ -141,7 +172,7 @@ export type SettlePaymentFn<T extends ConfigArgs> = (
     order: Order,
     payment: Payment,
     args: ConfigArgValues<T>,
-) => SettlePaymentResult | Promise<SettlePaymentResult>;
+) => SettlePaymentResult | SettlePaymentErrorResult | Promise<SettlePaymentResult | SettlePaymentErrorResult>;
 
 /**
  * @description

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

@@ -150,6 +150,12 @@ export class PaymentService {
         } else {
             payment.errorMessage = settlePaymentResult.errorMessage;
             payment.metadata = this.mergePaymentMetadata(payment.metadata, settlePaymentResult.metadata);
+            await this.paymentStateMachine.transition(
+                ctx,
+                payment.order,
+                payment,
+                settlePaymentResult.state || 'Error',
+            );
             await this.connection.getRepository(ctx, Payment).save(payment, { reload: false });
         }
         return payment;