Skip to content

Commit

Permalink
feat(core): Add settlePayment mutation
Browse files Browse the repository at this point in the history
Relates to #117
  • Loading branch information
michaelbromley committed Jun 24, 2019
1 parent c90a2a4 commit f2b9a12
Show file tree
Hide file tree
Showing 19 changed files with 468 additions and 120 deletions.
109 changes: 97 additions & 12 deletions admin-ui/src/app/common/generated-types.ts

Large diffs are not rendered by default.

78 changes: 76 additions & 2 deletions packages/common/src/generated-shop-types.ts

Large diffs are not rendered by default.

248 changes: 165 additions & 83 deletions packages/common/src/generated-types.ts

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions packages/core/e2e/graphql/generated-e2e-admin-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1518,6 +1518,7 @@ export type Mutation = {
deleteFacetValues: Array<DeletionResponse>;
updateGlobalSettings: GlobalSettings;
importProducts?: Maybe<ImportInfo>;
settlePayment?: Maybe<Payment>;
/** Update an existing PaymentMethod */
updatePaymentMethod: PaymentMethod;
/** Create a new ProductOptionGroup */
Expand Down Expand Up @@ -1703,6 +1704,10 @@ export type MutationImportProductsArgs = {
csvFile: Scalars['Upload'];
};

export type MutationSettlePaymentArgs = {
id: Scalars['ID'];
};

export type MutationUpdatePaymentMethodArgs = {
input: UpdatePaymentMethodInput;
};
Expand Down Expand Up @@ -3884,6 +3889,14 @@ export type GetOrderQuery = { __typename?: 'Query' } & {
order: Maybe<{ __typename?: 'Order' } & OrderWithLinesFragment>;
};

export type SettlePaymentMutationVariables = {
id: Scalars['ID'];
};

export type SettlePaymentMutation = { __typename?: 'Mutation' } & {
settlePayment: Maybe<{ __typename?: 'Payment' } & Pick<Payment, 'id' | 'state'>>;
};

export type AddOptionGroupToProductMutationVariables = {
productId: Scalars['ID'];
optionGroupId: Scalars['ID'];
Expand Down Expand Up @@ -4809,6 +4822,12 @@ export namespace GetOrder {
export type Order = OrderWithLinesFragment;
}

export namespace SettlePayment {
export type Variables = SettlePaymentMutationVariables;
export type Mutation = SettlePaymentMutation;
export type SettlePayment = NonNullable<SettlePaymentMutation['settlePayment']>;
}

export namespace AddOptionGroupToProduct {
export type Variables = AddOptionGroupToProductMutationVariables;
export type Mutation = AddOptionGroupToProductMutation;
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/api/resolvers/admin/order.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Args, Query, Resolver } from '@nestjs/graphql';
import { QueryOrderArgs, QueryOrdersArgs, Permission } from '@vendure/common/lib/generated-types';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { MutationSettlePaymentArgs, Permission, QueryOrderArgs, QueryOrdersArgs } from '@vendure/common/lib/generated-types';
import { PaginatedList } from '@vendure/common/lib/shared-types';

import { Order } from '../../../entity/order/order.entity';
Expand All @@ -24,4 +24,10 @@ export class OrderResolver {
async order(@Ctx() ctx: RequestContext, @Args() args: QueryOrderArgs): Promise<Order | undefined> {
return this.orderService.findOne(ctx, args.id);
}

@Mutation()
@Allow(Permission.UpdateOrder)
async settlePayment(@Ctx() ctx: RequestContext, @Args() args: MutationSettlePaymentArgs) {
return this.orderService.settlePayment(ctx, args.id);
}
}
4 changes: 4 additions & 0 deletions packages/core/src/api/schema/admin-api/order.api.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@ type Query {
orders(options: OrderListOptions): OrderList!
}

type Mutation {
settlePayment(id: ID!): Payment
}

# generated by generateListOptions function
input OrderListOptions
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export type OnTransitionStartFn<T extends PaymentMethodArgs> = (
*/
export interface PaymentConfig {
amount: number;
state: PaymentState;
state: Exclude<PaymentState, 'Refunded'>;
transactionId?: string;
metadata?: PaymentMetadata;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,4 @@ export class PaymentMethod extends VendureEntity {
@Column() enabled: boolean;

@Column('simple-json') configArgs: ConfigArg[];

async createPayment(order: Order, metadata: PaymentMetadata) {
const handler = getConfig().paymentOptions.paymentMethodHandlers.find(h => h.code === this.code);
if (!handler) {
throw new UserInputError(`error.no-payment-handler-with-code`, { code: this.code });
}
const result = await handler.createPayment(order, this.configArgs, metadata || {});
return new Payment(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { RequestContext } from '../../api/common/request-context';
import { Order } from '../../entity/order/order.entity';
import { Payment } from '../../entity/payment/payment.entity';
import { PaymentState } from '../../service/helpers/payment-state-machine/payment-state';
import { VendureEvent } from '../vendure-event';

/**
* @description
* This event is fired whenever a {@link Payment} transitions from one {@link PaymentState} to another, e.g.
* a Payment is authorized by the payment provider.
*
* @docsCategory events
*/
export class PaymentStateTransitionEvent extends VendureEvent {
constructor(
public fromState: PaymentState,
public toState: PaymentState,
public ctx: RequestContext,
public payment: Payment,
public order: Order,
) {
super();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ export class OrderStateMachine {
} = this.configService.orderOptions.process;

const allTransitions = this.mergeTransitionDefinitions(orderStateTransitions, transtitions);
const initialState = 'AddingItems';

return {
transitions: allTransitions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const orderStateTransitions: Transitions<OrderState> = {
to: ['PaymentSettled'],
},
PaymentSettled: {
to: ['OrderComplete'],
to: ['OrderComplete', 'Cancelled'],
},
OrderComplete: {
to: ['Cancelled'],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Injectable } from '@nestjs/common';

import { RequestContext } from '../../../api/common/request-context';
import { IllegalOperationError } from '../../../common/error/errors';
import { FSM, StateMachineConfig } from '../../../common/finite-state-machine';
import { ConfigService } from '../../../config/config.service';
import { Order } from '../../../entity/order/order.entity';
import { Payment } from '../../../entity/payment/payment.entity';
import { EventBus } from '../../../event-bus/event-bus';
import { PaymentStateTransitionEvent } from '../../../event-bus/events/payment-state-transition-event';

import { PaymentState, paymentStateTransitions, PaymentTransitionData } from './payment-state';

@Injectable()
export class PaymentStateMachine {

private readonly config: StateMachineConfig<PaymentState, PaymentTransitionData> = {
transitions: paymentStateTransitions,
onTransitionStart: async (fromState, toState, data) => {
return true;
},
onTransitionEnd: (fromState, toState, data) => {
this.eventBus.publish(new PaymentStateTransitionEvent(fromState, toState, data.ctx, data.payment, data.order));
},
onError: (fromState, toState, message) => {
throw new IllegalOperationError(message || 'error.cannot-transition-order-from-to', {
fromState,
toState,
});
},
};

constructor(private configService: ConfigService,
private eventBus: EventBus) {}

getNextStates(payment: Payment): PaymentState[] {
const fsm = new FSM(this.config, payment.state);
return fsm.getNextStates();
}

async transition(ctx: RequestContext, order: Order, payment: Payment, state: PaymentState) {
const fsm = new FSM(this.config, payment.state);
await fsm.transitionTo(state, { ctx, order, payment });
payment.state = state;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { RequestContext } from '../../../api/common/request-context';
import { Transitions } from '../../../common/finite-state-machine';
import { Order } from '../../../entity/order/order.entity';
import { Payment } from '../../../entity/payment/payment.entity';
Expand Down Expand Up @@ -36,6 +37,7 @@ export const paymentStateTransitions: Transitions<PaymentState> = {
* @docsCategory payment
*/
export interface PaymentTransitionData {
ctx: RequestContext;
payment: Payment;
order: Order;
}
2 changes: 2 additions & 0 deletions packages/core/src/service/service.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { OrderCalculator } from './helpers/order-calculator/order-calculator';
import { OrderMerger } from './helpers/order-merger/order-merger';
import { OrderStateMachine } from './helpers/order-state-machine/order-state-machine';
import { PasswordCiper } from './helpers/password-cipher/password-ciper';
import { PaymentStateMachine } from './helpers/payment-state-machine/payment-state-machine';
import { ShippingCalculator } from './helpers/shipping-calculator/shipping-calculator';
import { TaxCalculator } from './helpers/tax-calculator/tax-calculator';
import { TranslatableSaver } from './helpers/translatable-saver/translatable-saver';
Expand Down Expand Up @@ -96,6 +97,7 @@ let workerTypeOrmModule: DynamicModule;
OrderCalculator,
OrderStateMachine,
OrderMerger,
PaymentStateMachine,
ListQueryBuilder,
ShippingCalculator,
AssetUpdater,
Expand Down
19 changes: 14 additions & 5 deletions packages/core/src/service/services/order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ 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 { PaymentStateMachine } from '../helpers/payment-state-machine/payment-state-machine';
import { ShippingCalculator } from '../helpers/shipping-calculator/shipping-calculator';
import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
import { translateDeep } from '../helpers/utils/translate-entity';

import { CountryService } from './country.service';
Expand All @@ -47,6 +49,7 @@ export class OrderService {
private shippingCalculator: ShippingCalculator,
private orderStateMachine: OrderStateMachine,
private orderMerger: OrderMerger,
private paymentStateMachine: PaymentStateMachine,
private paymentMethodService: PaymentMethodService,
private listQueryBuilder: ListQueryBuilder,
) {}
Expand Down Expand Up @@ -291,12 +294,9 @@ export class OrderService {
throw new IllegalOperationError(`error.payment-may-only-be-added-in-arrangingpayment-state`);
}
const payment = await this.paymentMethodService.createPayment(order, input.method, input.metadata);
if (order.payments) {
order.payments.push(payment);
} else {
order.payments = [payment];
}
order.payments = [...(order.payments || []), 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')) {
return this.transitionToState(ctx, orderId, 'PaymentSettled');
Expand All @@ -307,6 +307,15 @@ export class OrderService {
return order;
}

async settlePayment(ctx: RequestContext, paymentId: ID): Promise<Payment> {
const payment = await getEntityOrThrow(this.connection, Payment, paymentId, { relations: ['order'] });
await this.paymentStateMachine.transition(ctx, payment.order, payment, 'Settled');
if (payment.amount === payment.order.total) {
await this.transitionToState(ctx, payment.order.id, 'PaymentSettled');
}
return payment;
}

async addCustomerToOrder(ctx: RequestContext, orderId: ID, customer: Customer): Promise<Order> {
const order = await this.getOrderOrThrow(ctx, orderId);
if (order.customer && !idsAreEqual(order.customer.id, customer.id)) {
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/service/services/payment-method.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Connection } from 'typeorm';

import { UserInputError } from '../../common/error/errors';
import { ListQueryOptions } from '../../common/types/common-types';
import { getConfig } from '../../config/config-helpers';
import { ConfigService } from '../../config/config.service';
import {
PaymentMethodArgs,
Expand Down Expand Up @@ -71,7 +72,12 @@ export class PaymentMethodService {
if (!paymentMethod) {
throw new UserInputError(`error.payment-method-not-found`, { method });
}
const payment = await paymentMethod.createPayment(order, metadata);
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 result = await handler.createPayment(order, paymentMethod.configArgs, metadata || {});
const payment = new Payment(result);
return this.connection.getRepository(Payment).save(payment);
}

Expand Down
2 changes: 1 addition & 1 deletion schema-admin.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion schema-shop.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion scripts/codegen/generate-graphql-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { downloadIntrospectionSchema } from './download-introspection-schema';
const CLIENT_QUERY_FILES = path.join(__dirname, '../../admin-ui/src/app/data/definitions/**/*.ts');
const E2E_ADMIN_QUERY_FILES = path.join(__dirname, '../../packages/core/e2e/**/!(plugin.e2e-spec|shop-definitions).ts');
const E2E_SHOP_QUERY_FILES = [
// path.join(__dirname, '../../packages/core/e2e/graphql/fragments.ts'),
path.join(__dirname, '../../packages/core/e2e/graphql/shop-definitions.ts'),
];
const ADMIN_SCHEMA_OUTPUT_FILE = path.join(__dirname, '../../schema-admin.json');
Expand Down

0 comments on commit f2b9a12

Please sign in to comment.