Explorar el Código

feat(core): Add error handling to payments

Michael Bromley hace 6 años
padre
commit
cba63e1553

+ 4 - 3
admin-ui/src/app/common/generated-types.ts

@@ -2283,6 +2283,7 @@ export type Payment = Node & {
   amount: Scalars['Int'],
   state: Scalars['String'],
   transactionId?: Maybe<Scalars['String']>,
+  errorMessage?: Maybe<Scalars['String']>,
   refunds: Array<Refund>,
   metadata?: Maybe<Scalars['JSON']>,
 };
@@ -2973,10 +2974,10 @@ export type SearchResult = {
   priceWithTax: SearchResultPrice,
   currencyCode: CurrencyCode,
   description: Scalars['String'],
-  facetIds: Array<Scalars['String']>,
-  facetValueIds: Array<Scalars['String']>,
+  facetIds: Array<Scalars['ID']>,
+  facetValueIds: Array<Scalars['ID']>,
   /** An array of ids of the Collections in which this result appears */
-  collectionIds: Array<Scalars['String']>,
+  collectionIds: Array<Scalars['ID']>,
   /** A relevence score for the result. Differs between database implementations */
   score: Scalars['Float'],
 };

+ 4 - 3
packages/common/src/generated-shop-types.ts

@@ -1577,6 +1577,7 @@ export type Payment = Node & {
     amount: Scalars['Int'];
     state: Scalars['String'];
     transactionId?: Maybe<Scalars['String']>;
+    errorMessage?: Maybe<Scalars['String']>;
     refunds: Array<Refund>;
     metadata?: Maybe<Scalars['JSON']>;
 };
@@ -1976,10 +1977,10 @@ export type SearchResult = {
     priceWithTax: SearchResultPrice;
     currencyCode: CurrencyCode;
     description: Scalars['String'];
-    facetIds: Array<Scalars['String']>;
-    facetValueIds: Array<Scalars['String']>;
+    facetIds: Array<Scalars['ID']>;
+    facetValueIds: Array<Scalars['ID']>;
     /** An array of ids of the Collections in which this result appears */
-    collectionIds: Array<Scalars['String']>;
+    collectionIds: Array<Scalars['ID']>;
     /** A relevence score for the result. Differs between database implementations */
     score: Scalars['Float'];
 };

+ 4 - 3
packages/common/src/generated-types.ts

@@ -2261,6 +2261,7 @@ export type Payment = Node & {
   amount: Scalars['Int'],
   state: Scalars['String'],
   transactionId?: Maybe<Scalars['String']>,
+  errorMessage?: Maybe<Scalars['String']>,
   refunds: Array<Refund>,
   metadata?: Maybe<Scalars['JSON']>,
 };
@@ -2948,10 +2949,10 @@ export type SearchResult = {
   priceWithTax: SearchResultPrice,
   currencyCode: CurrencyCode,
   description: Scalars['String'],
-  facetIds: Array<Scalars['String']>,
-  facetValueIds: Array<Scalars['String']>,
+  facetIds: Array<Scalars['ID']>,
+  facetValueIds: Array<Scalars['ID']>,
   /** An array of ids of the Collections in which this result appears */
-  collectionIds: Array<Scalars['String']>,
+  collectionIds: Array<Scalars['ID']>,
   /** A relevence score for the result. Differs between database implementations */
   score: Scalars['Float'],
 };

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

@@ -2196,6 +2196,7 @@ export type Payment = Node & {
     amount: Scalars['Int'];
     state: Scalars['String'];
     transactionId?: Maybe<Scalars['String']>;
+    errorMessage?: Maybe<Scalars['String']>;
     refunds: Array<Refund>;
     metadata?: Maybe<Scalars['JSON']>;
 };
@@ -2849,10 +2850,10 @@ export type SearchResult = {
     priceWithTax: SearchResultPrice;
     currencyCode: CurrencyCode;
     description: Scalars['String'];
-    facetIds: Array<Scalars['String']>;
-    facetValueIds: Array<Scalars['String']>;
+    facetIds: Array<Scalars['ID']>;
+    facetValueIds: Array<Scalars['ID']>;
     /** An array of ids of the Collections in which this result appears */
-    collectionIds: Array<Scalars['String']>;
+    collectionIds: Array<Scalars['ID']>;
     /** A relevence score for the result. Differs between database implementations */
     score: Scalars['Float'];
 };
@@ -3642,6 +3643,18 @@ export type IdTest8Mutation = { __typename?: 'Mutation' } & {
     updateProduct: { __typename?: 'Product' } & Pick<Product, 'id' | 'name'>;
 };
 
+export type IdTest9QueryVariables = {};
+
+export type IdTest9Query = { __typename?: 'Query' } & {
+    products: { __typename?: 'ProductList' } & {
+        items: Array<{ __typename?: 'Product' } & ProdFragmentFragment>;
+    };
+};
+
+export type ProdFragmentFragment = { __typename?: 'Product' } & Pick<Product, 'id'> & {
+        featuredAsset: Maybe<{ __typename?: 'Asset' } & Pick<Asset, 'id'>>;
+    };
+
 export type GetFacetWithValuesQueryVariables = {
     id: Scalars['ID'];
 };
@@ -5050,6 +5063,18 @@ export namespace IdTest8 {
     export type UpdateProduct = IdTest8Mutation['updateProduct'];
 }
 
+export namespace IdTest9 {
+    export type Variables = IdTest9QueryVariables;
+    export type Query = IdTest9Query;
+    export type Products = IdTest9Query['products'];
+    export type Items = ProdFragmentFragment;
+}
+
+export namespace ProdFragment {
+    export type Fragment = ProdFragmentFragment;
+    export type FeaturedAsset = NonNullable<ProdFragmentFragment['featuredAsset']>;
+}
+
 export namespace GetFacetWithValues {
     export type Variables = GetFacetWithValuesQueryVariables;
     export type Query = GetFacetWithValuesQuery;

+ 36 - 3
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -1577,6 +1577,7 @@ export type Payment = Node & {
     amount: Scalars['Int'];
     state: Scalars['String'];
     transactionId?: Maybe<Scalars['String']>;
+    errorMessage?: Maybe<Scalars['String']>;
     refunds: Array<Refund>;
     metadata?: Maybe<Scalars['JSON']>;
 };
@@ -1976,10 +1977,10 @@ export type SearchResult = {
     priceWithTax: SearchResultPrice;
     currencyCode: CurrencyCode;
     description: Scalars['String'];
-    facetIds: Array<Scalars['String']>;
-    facetValueIds: Array<Scalars['String']>;
+    facetIds: Array<Scalars['ID']>;
+    facetValueIds: Array<Scalars['ID']>;
     /** An array of ids of the Collections in which this result appears */
-    collectionIds: Array<Scalars['String']>;
+    collectionIds: Array<Scalars['ID']>;
     /** A relevence score for the result. Differs between database implementations */
     score: Scalars['Float'];
 };
@@ -2455,6 +2456,29 @@ export type AddPaymentToOrderMutation = { __typename?: 'Mutation' } & {
     >;
 };
 
+export type GetActiveOrderPaymentsQueryVariables = {};
+
+export type GetActiveOrderPaymentsQuery = { __typename?: 'Query' } & {
+    activeOrder: Maybe<
+        { __typename?: 'Order' } & Pick<Order, 'id'> & {
+                payments: Maybe<
+                    Array<
+                        { __typename?: 'Payment' } & Pick<
+                            Payment,
+                            | 'id'
+                            | 'transactionId'
+                            | 'method'
+                            | 'amount'
+                            | 'state'
+                            | 'errorMessage'
+                            | 'metadata'
+                        >
+                    >
+                >;
+            }
+    >;
+};
+
 export type GetNextOrderStatesQueryVariables = {};
 
 export type GetNextOrderStatesQuery = { __typename?: 'Query' } & Pick<Query, 'nextOrderStates'>;
@@ -2650,6 +2674,15 @@ export namespace AddPaymentToOrder {
     >;
 }
 
+export namespace GetActiveOrderPayments {
+    export type Variables = GetActiveOrderPaymentsQueryVariables;
+    export type Query = GetActiveOrderPaymentsQuery;
+    export type ActiveOrder = NonNullable<GetActiveOrderPaymentsQuery['activeOrder']>;
+    export type Payments = NonNullable<
+        (NonNullable<(NonNullable<GetActiveOrderPaymentsQuery['activeOrder']>)['payments']>)[0]
+    >;
+}
+
 export namespace GetNextOrderStates {
     export type Variables = GetNextOrderStatesQueryVariables;
     export type Query = GetNextOrderStatesQuery;

+ 23 - 6
packages/core/e2e/graphql/shop-definitions.ts

@@ -95,7 +95,7 @@ export const RESET_PASSWORD = gql`
     }
 `;
 export const REQUEST_UPDATE_EMAIL_ADDRESS = gql`
-    mutation RequestUpdateEmailAddress($password: String! $newEmailAddress: String!) {
+    mutation RequestUpdateEmailAddress($password: String!, $newEmailAddress: String!) {
         requestUpdateCustomerEmailAddress(password: $password, newEmailAddress: $newEmailAddress)
     }
 `;
@@ -105,7 +105,7 @@ export const UPDATE_EMAIL_ADDRESS = gql`
     }
 `;
 export const GET_ACTIVE_CUSTOMER = gql`
-    query GetActiveCustomer{
+    query GetActiveCustomer {
         activeCustomer {
             id
             emailAddress
@@ -156,7 +156,7 @@ export const UPDATE_PASSWORD = gql`
 `;
 
 export const GET_ACTIVE_ORDER = gql`
-    query GetActiveOrder{
+    query GetActiveOrder {
         activeOrder {
             ...TestOrderFragment
         }
@@ -183,7 +183,7 @@ export const REMOVE_ITEM_FROM_ORDER = gql`
 `;
 
 export const GET_ELIGIBLE_SHIPPING_METHODS = gql`
-    query GetShippingMethods{
+    query GetShippingMethods {
         eligibleShippingMethods {
             id
             price
@@ -229,7 +229,7 @@ export const GET_ORDER_BY_CODE = gql`
 `;
 
 export const GET_AVAILABLE_COUNTRIES = gql`
-    query GetAvailableCountries{
+    query GetAvailableCountries {
         availableCountries {
             id
             code
@@ -281,6 +281,23 @@ export const ADD_PAYMENT = gql`
     ${TEST_ORDER_FRAGMENT}
 `;
 
+export const GET_ACTIVE_ORDER_PAYMENTS = gql`
+    query GetActiveOrderPayments {
+        activeOrder {
+            id
+            payments {
+                id
+                transactionId
+                method
+                amount
+                state
+                errorMessage
+                metadata
+            }
+        }
+    }
+`;
+
 export const GET_NEXT_STATES = gql`
     query GetNextOrderStates {
         nextOrderStates
@@ -288,7 +305,7 @@ export const GET_NEXT_STATES = gql`
 `;
 
 export const GET_ACTIVE_ORDER_ADDRESSES = gql`
-    query GetCustomerAddresses{
+    query GetCustomerAddresses {
         activeOrder {
             customer {
                 addresses {

+ 79 - 17
packages/core/e2e/shop-order.e2e-spec.ts

@@ -4,12 +4,19 @@ import path from 'path';
 import { PaymentMethodHandler } from '../src/config/payment-method/payment-method-handler';
 
 import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
-import { CreateAddressInput, GetCountryList, GetCustomer, GetCustomerList, UpdateCountry } from './graphql/generated-e2e-admin-types';
+import {
+    CreateAddressInput,
+    GetCountryList,
+    GetCustomer,
+    GetCustomerList,
+    UpdateCountry,
+} from './graphql/generated-e2e-admin-types';
 import {
     AddItemToOrder,
     AddPaymentToOrder,
     AdjustItemQuantity,
     GetActiveOrder,
+    GetActiveOrderPayments,
     GetAvailableCountries,
     GetCustomerAddresses,
     GetNextOrderStates,
@@ -21,13 +28,19 @@ import {
     SetShippingMethod,
     TransitionToState,
 } from './graphql/generated-e2e-shop-types';
-import { GET_COUNTRY_LIST, GET_CUSTOMER, GET_CUSTOMER_LIST, UPDATE_COUNTRY } from './graphql/shared-definitions';
+import {
+    GET_COUNTRY_LIST,
+    GET_CUSTOMER,
+    GET_CUSTOMER_LIST,
+    UPDATE_COUNTRY,
+} from './graphql/shared-definitions';
 import {
     ADD_ITEM_TO_ORDER,
     ADD_PAYMENT,
     ADJUST_ITEM_QUANTITY,
     GET_ACTIVE_ORDER,
     GET_ACTIVE_ORDER_ADDRESSES,
+    GET_ACTIVE_ORDER_PAYMENTS,
     GET_AVAILABLE_COUNTRIES,
     GET_ELIGIBLE_SHIPPING_METHODS,
     GET_NEXT_STATES,
@@ -55,7 +68,11 @@ describe('Shop orders', () => {
             },
             {
                 paymentOptions: {
-                    paymentMethodHandlers: [testPaymentMethod, testFailingPaymentMethod],
+                    paymentMethodHandlers: [
+                        testPaymentMethod,
+                        testFailingPaymentMethod,
+                        testErrorPaymentMethod,
+                    ],
                 },
                 orderOptions: {
                     orderItemsLimit: 99,
@@ -376,15 +393,15 @@ describe('Shop orders', () => {
         });
 
         it('adds a successful payment and transitions Order state', async () => {
-            const { addPaymentToOrder } = await shopClient.query<AddPaymentToOrder.Mutation, AddPaymentToOrder.Variables>(
-                ADD_PAYMENT,
-                {
-                    input: {
-                        method: testPaymentMethod.code,
-                        metadata: {},
-                    },
+            const { addPaymentToOrder } = await shopClient.query<
+                AddPaymentToOrder.Mutation,
+                AddPaymentToOrder.Variables
+            >(ADD_PAYMENT, {
+                input: {
+                    method: testPaymentMethod.code,
+                    metadata: {},
                 },
-            );
+            });
 
             const payment = addPaymentToOrder!.payments![0];
             expect(addPaymentToOrder!.state).toBe('PaymentSettled');
@@ -629,9 +646,7 @@ describe('Shop orders', () => {
 
                 expect(adjustOrderLine!.shipping).toBe(shippingMethods[1].price);
                 expect(adjustOrderLine!.shippingMethod!.id).toBe(shippingMethods[1].id);
-                expect(adjustOrderLine!.shippingMethod!.description).toBe(
-                    shippingMethods[1].description,
-                );
+                expect(adjustOrderLine!.shippingMethod!.description).toBe(shippingMethods[1].description);
             });
         });
 
@@ -747,6 +762,33 @@ describe('Shop orders', () => {
                 });
             });
 
+            it('adds an error payment and returns error response', async () => {
+                try {
+                    await shopClient.query<AddPaymentToOrder.Mutation, AddPaymentToOrder.Variables>(
+                        ADD_PAYMENT,
+                        {
+                            input: {
+                                method: testErrorPaymentMethod.code,
+                                metadata: {
+                                    foo: 'bar',
+                                },
+                            },
+                        },
+                    );
+                    fail('should have thrown');
+                } catch (err) {
+                    expect(err.message).toEqual('Something went horribly wrong');
+                }
+                const result = await shopClient.query<GetActiveOrderPayments.Query>(
+                    GET_ACTIVE_ORDER_PAYMENTS,
+                );
+                const payment = result.activeOrder!.payments![1];
+                expect(result.activeOrder!.payments!.length).toBe(2);
+                expect(payment.method).toBe(testErrorPaymentMethod.code);
+                expect(payment.state).toBe('Error');
+                expect(payment.errorMessage).toBe('Something went horribly wrong');
+            });
+
             it('adds a successful payment and transitions Order state', async () => {
                 const { addPaymentToOrder } = await shopClient.query<
                     AddPaymentToOrder.Mutation,
@@ -760,10 +802,10 @@ describe('Shop orders', () => {
                     },
                 });
 
-                const payment = addPaymentToOrder!.payments![0];
+                const payment = addPaymentToOrder!.payments![2];
                 expect(addPaymentToOrder!.state).toBe('PaymentSettled');
                 expect(addPaymentToOrder!.active).toBe(false);
-                expect(addPaymentToOrder!.payments!.length).toBe(1);
+                expect(addPaymentToOrder!.payments!.length).toBe(3);
                 expect(payment.method).toBe(testPaymentMethod.code);
                 expect(payment.state).toBe('Settled');
                 expect(payment.transactionId).toBe('12345');
@@ -773,7 +815,10 @@ describe('Shop orders', () => {
             });
 
             it('does not create new address when Customer already has address', async () => {
-                const { customer } = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(GET_CUSTOMER, { id: customers[0].id });
+                const { customer } = await adminClient.query<GetCustomer.Query, GetCustomer.Variables>(
+                    GET_CUSTOMER,
+                    { id: customers[0].id },
+                );
                 expect(customer!.addresses!.length).toBe(1);
             });
         });
@@ -853,3 +898,20 @@ const testFailingPaymentMethod = new PaymentMethodHandler({
         success: true,
     }),
 });
+
+const testErrorPaymentMethod = new PaymentMethodHandler({
+    code: 'test-error-payment-method',
+    description: 'Test Error Payment Method',
+    args: {},
+    createPayment: (order, args, metadata) => {
+        return {
+            amount: order.total,
+            state: 'Error',
+            errorMessage: 'Something went horribly wrong',
+            metadata,
+        };
+    },
+    settlePayment: order => ({
+        success: true,
+    }),
+});

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

@@ -85,6 +85,7 @@ type Payment implements Node {
     amount: Int!
     state: String!
     transactionId: String
+    errorMessage: String
     refunds: [Refund!]!
     metadata: JSON
 }

+ 27 - 10
packages/core/src/config/payment-method/payment-method-handler.ts

@@ -26,8 +26,6 @@ export type OnTransitionStartReturnType = ReturnType<Required<StateMachineConfig
  * This function is called before the state of a Payment is transitioned. Its
  * return value used to determine whether the transition can occur.
  *
- * TODO: This is currently not called by Vendure. Needs to be implemented.
- *
  * @docsCategory payment
  */
 export type OnTransitionStartFn<T extends PaymentMethodArgs> = (
@@ -45,8 +43,23 @@ export type OnTransitionStartFn<T extends PaymentMethodArgs> = (
  */
 export interface CreatePaymentResult {
     amount: number;
-    state: Exclude<PaymentState, 'Refunded'>;
+    state: Exclude<PaymentState, 'Refunded' | 'Error'>;
     transactionId?: string;
+    errorMessage?: string;
+    metadata?: PaymentMetadata;
+}
+
+/**
+ * @description
+ * This object is the return value of the {@link CreatePaymentFn} when there has been an error.
+ *
+ * @docsCategory payment
+ */
+export interface CreatePaymentErrorResult {
+    amount: number;
+    state: 'Error';
+    transactionId?: string;
+    errorMessage: string;
     metadata?: PaymentMetadata;
 }
 
@@ -78,7 +91,7 @@ export type CreatePaymentFn<T extends PaymentMethodArgs> = (
     order: Order,
     args: ConfigArgValues<T>,
     metadata: PaymentMetadata,
-) => CreatePaymentResult | Promise<CreatePaymentResult>;
+) => CreatePaymentResult | CreatePaymentErrorResult | Promise<CreatePaymentResult | CreatePaymentErrorResult>;
 
 /**
  * @description
@@ -265,12 +278,16 @@ export class PaymentMethodHandler<T extends PaymentMethodArgs = PaymentMethodArg
      *
      * @internal
      */
-    async createRefund(input: RefundOrderInput,
-                       total: number,
-                       order: Order,
-                       payment: Payment,
-                       args: ConfigArg[]) {
-        return this.createRefundFn ? this.createRefundFn(input, total, order, payment, argsArrayToHash(args)) : false;
+    async createRefund(
+        input: RefundOrderInput,
+        total: number,
+        order: Order,
+        payment: Payment,
+        args: ConfigArg[],
+    ) {
+        return this.createRefundFn
+            ? this.createRefundFn(input, total, order, payment, argsArrayToHash(args))
+            : false;
     }
 
     /**

+ 3 - 0
packages/core/src/entity/payment/payment.entity.ts

@@ -27,6 +27,9 @@ export class Payment extends VendureEntity {
 
     @Column('varchar') state: PaymentState;
 
+    @Column({ nullable: true })
+    errorMessage: string;
+
     @Column({ nullable: true })
     transactionId: string;
 

+ 6 - 3
packages/core/src/service/helpers/payment-state-machine/payment-state.ts

@@ -9,14 +9,14 @@ import { Payment } from '../../../entity/payment/payment.entity';
  *
  * @docsCategory payment
  */
-export type PaymentState = 'Created' | 'Authorized' | 'Settled' | 'Declined';
+export type PaymentState = 'Created' | 'Authorized' | 'Settled' | 'Declined' | 'Error';
 
 export const paymentStateTransitions: Transitions<PaymentState> = {
     Created: {
-        to: ['Authorized', 'Settled', 'Declined'],
+        to: ['Authorized', 'Settled', 'Declined', 'Error'],
     },
     Authorized: {
-        to: ['Settled'],
+        to: ['Settled', 'Error'],
     },
     Settled: {
         to: [],
@@ -24,6 +24,9 @@ export const paymentStateTransitions: Transitions<PaymentState> = {
     Declined: {
         to: [],
     },
+    Error: {
+        to: [],
+    },
 };
 
 /**

+ 57 - 17
packages/core/src/service/services/order.service.ts

@@ -17,7 +17,13 @@ import { unique } from '@vendure/common/lib/unique';
 import { Connection } from 'typeorm';
 
 import { RequestContext } from '../../api/common/request-context';
-import { EntityNotFoundError, IllegalOperationError, InternalServerError, OrderItemsLimitError, UserInputError } from '../../common/error/errors';
+import {
+    EntityNotFoundError,
+    IllegalOperationError,
+    InternalServerError,
+    OrderItemsLimitError,
+    UserInputError,
+} from '../../common/error/errors';
 import { generatePublicId } from '../../common/generate-public-id';
 import { ListQueryOptions } from '../../common/types/common-types';
 import { assertFound, idsAreEqual } from '../../common/utils';
@@ -37,6 +43,7 @@ import { OrderCalculator } from '../helpers/order-calculator/order-calculator';
 import { OrderMerger } from '../helpers/order-merger/order-merger';
 import { OrderState } from '../helpers/order-state-machine/order-state';
 import { OrderStateMachine } from '../helpers/order-state-machine/order-state-machine';
+import { PaymentState } from '../helpers/payment-state-machine/payment-state';
 import { PaymentStateMachine } from '../helpers/payment-state-machine/payment-state-machine';
 import { RefundStateMachine } from '../helpers/refund-state-machine/refund-state-machine';
 import { ShippingCalculator } from '../helpers/shipping-calculator/shipping-calculator';
@@ -150,7 +157,9 @@ export class OrderService {
     }
 
     async getRefundOrderItems(refundId: ID): Promise<OrderItem[]> {
-        const refund = await getEntityOrThrow(this.connection, Refund, refundId, { relations: ['orderItems'] });
+        const refund = await getEntityOrThrow(this.connection, Refund, refundId, {
+            relations: ['orderItems'],
+        });
         return refund.orderItems;
     }
 
@@ -203,7 +212,7 @@ export class OrderService {
         orderId: ID,
         productVariantId: ID,
         quantity: number,
-        customFields?: { [key: string]: any; },
+        customFields?: { [key: string]: any },
     ): Promise<Order> {
         this.assertQuantityIsPositive(quantity);
         const order = await this.getOrderOrThrow(ctx, orderId);
@@ -231,7 +240,7 @@ export class OrderService {
         orderId: ID,
         orderLineId: ID,
         quantity?: number | null,
-        customFields?: { [key: string]: any; },
+        customFields?: { [key: string]: any },
     ): Promise<Order> {
         const order = await this.getOrderOrThrow(ctx, orderId);
         const orderLine = this.getOrderLineOrThrow(order, orderLineId);
@@ -251,7 +260,9 @@ export class OrderService {
                             unitPrice: productVariant.price,
                             pendingAdjustments: [],
                             unitPriceIncludesTax: productVariant.priceIncludesTax,
-                            taxRate: productVariant.priceIncludesTax ? productVariant.taxRateApplied.value : 0,
+                            taxRate: productVariant.priceIncludesTax
+                                ? productVariant.taxRateApplied.value
+                                : 0,
                         }),
                     );
                     orderLine.items.push(orderItem);
@@ -325,15 +336,32 @@ export class OrderService {
         if (order.state !== 'ArrangingPayment') {
             throw new IllegalOperationError(`error.payment-may-only-be-added-in-arrangingpayment-state`);
         }
-        const payment = await this.paymentMethodService.createPayment(ctx, order, input.method, input.metadata);
-        order.payments = [...(order.payments || []), payment];
+        const payment = await this.paymentMethodService.createPayment(
+            ctx,
+            order,
+            input.method,
+            input.metadata,
+        );
+
+        const existingPayments = await this.getOrderPayments(orderId);
+        order.payments = [...existingPayments, payment];
         await this.connection.getRepository(Order).save(order);
 
-        const orderTotalCovered = order.payments.reduce((sum, p) => sum + p.amount, 0) === order.total;
-        if (orderTotalCovered && order.payments.every(p => p.state === 'Settled')) {
+        if (payment.state === 'Error') {
+            throw new InternalServerError(payment.errorMessage);
+        }
+
+        function totalIsCovered(state: PaymentState): boolean {
+            return (
+                order.payments.filter(p => p.state === state).reduce((sum, p) => sum + p.amount, 0) ===
+                order.total
+            );
+        }
+
+        if (totalIsCovered('Settled')) {
             return this.transitionToState(ctx, orderId, 'PaymentSettled');
         }
-        if (orderTotalCovered && order.payments.every(p => p.state === 'Authorized')) {
+        if (totalIsCovered('Authorized')) {
             return this.transitionToState(ctx, orderId, 'PaymentAuthorized');
         }
         return order;
@@ -457,7 +485,9 @@ export class OrderService {
         }
         const order = orders[0];
         if (order.state === 'AddingItems' || order.state === 'ArrangingPayment') {
-            throw new IllegalOperationError('error.cancel-order-lines-invalid-order-state', { state: order.state });
+            throw new IllegalOperationError('error.cancel-order-lines-invalid-order-state', {
+                state: order.state,
+            });
         }
         await this.stockMovementService.createCancellationsForOrderItems(items);
         await this.historyService.createHistoryEntryForOrder({
@@ -488,8 +518,8 @@ export class OrderService {
     async refundOrder(ctx: RequestContext, input: RefundOrderInput): Promise<Refund> {
         if (
             (!input.lines ||
-            input.lines.length === 0 ||
-            input.lines.reduce((total, line) => total + line.quantity, 0) === 0) &&
+                input.lines.length === 0 ||
+                input.lines.reduce((total, line) => total + line.quantity, 0) === 0) &&
             input.shipping === 0
         ) {
             throw new UserInputError('error.refund-order-lines-nothing-to-refund');
@@ -502,13 +532,21 @@ export class OrderService {
         if (1 < orders.length) {
             throw new IllegalOperationError('error.order-lines-must-belong-to-same-order');
         }
-        const payment = await getEntityOrThrow(this.connection, Payment, input.paymentId, { relations: ['order'] });
+        const payment = await getEntityOrThrow(this.connection, Payment, input.paymentId, {
+            relations: ['order'],
+        });
         if (orders && orders.length && !idsAreEqual(payment.order.id, orders[0].id)) {
             throw new IllegalOperationError('error.refund-order-payment-lines-mismatch');
         }
         const order = payment.order;
-        if (order.state === 'AddingItems' || order.state === 'ArrangingPayment' || order.state === 'PaymentAuthorized') {
-            throw new IllegalOperationError('error.refund-order-lines-invalid-order-state', {state: order.state});
+        if (
+            order.state === 'AddingItems' ||
+            order.state === 'ArrangingPayment' ||
+            order.state === 'PaymentAuthorized'
+        ) {
+            throw new IllegalOperationError('error.refund-order-lines-invalid-order-state', {
+                state: order.state,
+            });
         }
         if (items.some(i => !!i.refundId)) {
             throw new IllegalOperationError('error.refund-order-item-already-refunded');
@@ -518,7 +556,9 @@ export class OrderService {
     }
 
     async settleRefund(ctx: RequestContext, input: SettleRefundInput): Promise<Refund> {
-        const refund = await getEntityOrThrow(this.connection, Refund, input.id, { relations: ['payment', 'payment.order'] });
+        const refund = await getEntityOrThrow(this.connection, Refund, input.id, {
+            relations: ['payment', 'payment.order'],
+        });
         refund.transactionId = input.transactionId;
         await this.refundStateMachine.transition(ctx, refund.payment.order, refund, 'Settled');
         return this.connection.getRepository(Refund).save(refund);

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
schema-admin.json


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
schema-shop.json


Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio