Skip to content

Commit

Permalink
feat(core): Make PaymentMethod channel-aware
Browse files Browse the repository at this point in the history
Relates to #587

BREAKING CHANGE: The PaymentMethod entity is now channel-aware which will require a DB migration
to migrate existing PaymentMethods
  • Loading branch information
michaelbromley committed Mar 4, 2021
1 parent 2ee3278 commit 1a3b04f
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 9 deletions.
17 changes: 17 additions & 0 deletions packages/core/e2e/graphql/generated-e2e-admin-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6146,6 +6146,14 @@ export type GetPaymentMethodQueryVariables = Exact<{

export type GetPaymentMethodQuery = { paymentMethod?: Maybe<PaymentMethodFragment> };

export type GetPaymentMethodListQueryVariables = Exact<{
options?: Maybe<PaymentMethodListOptions>;
}>;

export type GetPaymentMethodListQuery = {
paymentMethods: Pick<PaymentMethodList, 'totalItems'> & { items: Array<PaymentMethodFragment> };
};

export type TransitionPaymentToStateMutationVariables = Exact<{
id: Scalars['ID'];
state: Scalars['String'];
Expand Down Expand Up @@ -8312,6 +8320,15 @@ export namespace GetPaymentMethod {
export type PaymentMethod = NonNullable<GetPaymentMethodQuery['paymentMethod']>;
}

export namespace GetPaymentMethodList {
export type Variables = GetPaymentMethodListQueryVariables;
export type Query = GetPaymentMethodListQuery;
export type PaymentMethods = NonNullable<GetPaymentMethodListQuery['paymentMethods']>;
export type Items = NonNullable<
NonNullable<NonNullable<GetPaymentMethodListQuery['paymentMethods']>['items']>[number]
>;
}

export namespace TransitionPaymentToState {
export type Variables = TransitionPaymentToStateMutationVariables;
export type Mutation = TransitionPaymentToStateMutation;
Expand Down
108 changes: 107 additions & 1 deletion packages/core/e2e/payment-method.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@ import {
LanguageCode,
PaymentMethodEligibilityChecker,
} from '@vendure/core';
import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
import {
createErrorResultGuard,
createTestEnvironment,
E2E_DEFAULT_CHANNEL_TOKEN,
ErrorResultGuard,
} from '@vendure/testing';
import gql from 'graphql-tag';
import path from 'path';

import { initialData } from '../../../e2e-common/e2e-initial-data';
import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';

import {
CreateChannel,
CreatePaymentMethod,
CurrencyCode,
GetPaymentMethod,
GetPaymentMethodCheckers,
GetPaymentMethodHandlers,
GetPaymentMethodList,
UpdatePaymentMethod,
} from './graphql/generated-e2e-admin-types';
import {
Expand All @@ -25,6 +33,7 @@ import {
GetEligiblePaymentMethods,
TestOrderWithPaymentsFragment,
} from './graphql/generated-e2e-shop-types';
import { CREATE_CHANNEL } from './graphql/shared-definitions';
import { ADD_ITEM_TO_ORDER, ADD_PAYMENT, GET_ELIGIBLE_PAYMENT_METHODS } from './graphql/shop-definitions';
import { proceedToArrangingPayment } from './utils/test-order-utils';

Expand Down Expand Up @@ -281,6 +290,91 @@ describe('PaymentMethod resolver', () => {
expect(checkerSpy).toHaveBeenCalledTimes(1);
});
});

describe('channels', () => {
const SECOND_CHANNEL_TOKEN = 'SECOND_CHANNEL_TOKEN';
const THIRD_CHANNEL_TOKEN = 'THIRD_CHANNEL_TOKEN';

beforeAll(async () => {
await adminClient.query<CreateChannel.Mutation, CreateChannel.Variables>(CREATE_CHANNEL, {
input: {
code: 'second-channel',
token: SECOND_CHANNEL_TOKEN,
defaultLanguageCode: LanguageCode.en,
currencyCode: CurrencyCode.GBP,
pricesIncludeTax: true,
defaultShippingZoneId: 'T_1',
defaultTaxZoneId: 'T_1',
},
});
await adminClient.query<CreateChannel.Mutation, CreateChannel.Variables>(CREATE_CHANNEL, {
input: {
code: 'third-channel',
token: THIRD_CHANNEL_TOKEN,
defaultLanguageCode: LanguageCode.en,
currencyCode: CurrencyCode.GBP,
pricesIncludeTax: true,
defaultShippingZoneId: 'T_1',
defaultTaxZoneId: 'T_1',
},
});
});

it('creates a PaymentMethod in channel2', async () => {
adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
const { createPaymentMethod } = await adminClient.query<
CreatePaymentMethod.Mutation,
CreatePaymentMethod.Variables
>(CREATE_PAYMENT_METHOD, {
input: {
code: 'channel-2-method',
name: 'Channel 2 method',
description: 'This is a test payment method',
enabled: true,
handler: {
code: dummyPaymentHandler.code,
arguments: [{ name: 'automaticSettle', value: 'true' }],
},
},
});

expect(createPaymentMethod.code).toBe('channel-2-method');
});

it('method is listed in channel2', async () => {
adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
const { paymentMethods } = await adminClient.query<GetPaymentMethodList.Query>(
GET_PAYMENT_METHOD_LIST,
);

expect(paymentMethods.totalItems).toBe(1);
expect(paymentMethods.items[0].code).toBe('channel-2-method');
});

it('method is not listed in channel3', async () => {
adminClient.setChannelToken(THIRD_CHANNEL_TOKEN);
const { paymentMethods } = await adminClient.query<GetPaymentMethodList.Query>(
GET_PAYMENT_METHOD_LIST,
);

expect(paymentMethods.totalItems).toBe(0);
});

it('method is listed in default channel', async () => {
adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
const { paymentMethods } = await adminClient.query<GetPaymentMethodList.Query>(
GET_PAYMENT_METHOD_LIST,
);

expect(paymentMethods.totalItems).toBe(4);
expect(paymentMethods.items.map(i => i.code).sort()).toEqual([
'channel-2-method',
'disabled-method',
'no-checks',
'price-check',
]);
});
});
});

export const PAYMENT_METHOD_FRAGMENT = gql`
Expand Down Expand Up @@ -357,3 +451,15 @@ export const GET_PAYMENT_METHOD = gql`
}
${PAYMENT_METHOD_FRAGMENT}
`;

export const GET_PAYMENT_METHOD_LIST = gql`
query GetPaymentMethodList($options: PaymentMethodListOptions) {
paymentMethods(options: $options) {
items {
...PaymentMethod
}
totalItems
}
}
${PAYMENT_METHOD_FRAGMENT}
`;
10 changes: 8 additions & 2 deletions packages/core/src/entity/payment-method/payment-method.entity.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ConfigArg, ConfigurableOperation } from '@vendure/common/lib/generated-types';
import { DeepPartial } from '@vendure/common/lib/shared-types';
import { Column, Entity } from 'typeorm';
import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';

import { ChannelAware } from '../../common/types/common-types';
import { VendureEntity } from '../base/base.entity';
import { Channel } from '../channel/channel.entity';

/**
* @description
Expand All @@ -12,7 +14,7 @@ import { VendureEntity } from '../base/base.entity';
* @docsCategory entities
*/
@Entity()
export class PaymentMethod extends VendureEntity {
export class PaymentMethod extends VendureEntity implements ChannelAware {
constructor(input?: DeepPartial<PaymentMethod>) {
super(input);
}
Expand All @@ -28,4 +30,8 @@ export class PaymentMethod extends VendureEntity {
@Column('simple-json', { nullable: true }) checker: ConfigurableOperation | null;

@Column('simple-json') handler: ConfigurableOperation;

@ManyToMany(type => Channel)
@JoinTable()
channels: Channel[];
}
16 changes: 12 additions & 4 deletions packages/core/src/service/services/payment-method.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
import { RequestContext } from '../../api/common/request-context';
import { UserInputError } from '../../common/error/errors';
import { ListQueryOptions } from '../../common/types/common-types';
import { idsAreEqual } from '../../common/utils';
import { ConfigService } from '../../config/config.service';
import { PaymentMethodEligibilityChecker } from '../../config/payment/payment-method-eligibility-checker';
import { PaymentMethodHandler } from '../../config/payment/payment-method-handler';
Expand All @@ -22,6 +23,8 @@ import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-build
import { patchEntity } from '../helpers/utils/patch-entity';
import { TransactionalConnection } from '../transaction/transactional-connection';

import { ChannelService } from './channel.service';

@Injectable()
export class PaymentMethodService {
constructor(
Expand All @@ -30,14 +33,15 @@ export class PaymentMethodService {
private listQueryBuilder: ListQueryBuilder,
private eventBus: EventBus,
private configArgService: ConfigArgService,
private channelService: ChannelService,
) {}

findAll(
ctx: RequestContext,
options?: ListQueryOptions<PaymentMethod>,
): Promise<PaginatedList<PaymentMethod>> {
return this.listQueryBuilder
.build(PaymentMethod, options, { ctx })
.build(PaymentMethod, options, { ctx, relations: ['channels'], channelId: ctx.channelId })
.getManyAndCount()
.then(([items, totalItems]) => ({
items,
Expand All @@ -46,7 +50,7 @@ export class PaymentMethodService {
}

findOne(ctx: RequestContext, paymentMethodId: ID): Promise<PaymentMethod | undefined> {
return this.connection.getRepository(ctx, PaymentMethod).findOne(paymentMethodId);
return this.connection.findOneInChannel(ctx, PaymentMethod, paymentMethodId, ctx.channelId);
}

async create(ctx: RequestContext, input: CreatePaymentMethodInput): Promise<PaymentMethod> {
Expand All @@ -58,6 +62,7 @@ export class PaymentMethodService {
input.checker,
);
}
this.channelService.assignToCurrentChannel(paymentMethod, ctx);
return this.connection.getRepository(ctx, PaymentMethod).save(paymentMethod);
}

Expand Down Expand Up @@ -92,9 +97,12 @@ export class PaymentMethodService {
async getEligiblePaymentMethods(ctx: RequestContext, order: Order): Promise<PaymentMethodQuote[]> {
const paymentMethods = await this.connection
.getRepository(ctx, PaymentMethod)
.find({ where: { enabled: true } });
.find({ where: { enabled: true }, relations: ['channels'] });
const results: PaymentMethodQuote[] = [];
for (const method of paymentMethods) {
const paymentMethodsInChannel = paymentMethods.filter(p =>
p.channels.find(pc => idsAreEqual(pc.id, ctx.channelId)),
);
for (const method of paymentMethodsInChannel) {
let isEligible = true;
let eligibilityMessage: string | undefined;
if (method.checker) {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/service/services/shipping-method.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ID, PaginatedList } from '@vendure/common/lib/shared-types';
import { RequestContext } from '../../api/common/request-context';
import { EntityNotFoundError } from '../../common/error/errors';
import { ListQueryOptions } from '../../common/types/common-types';
import { assertFound } from '../../common/utils';
import { assertFound, idsAreEqual } from '../../common/utils';
import { ConfigService } from '../../config/config.service';
import { Logger } from '../../config/logger/vendure-logger';
import { Channel } from '../../entity/channel/channel.entity';
Expand Down Expand Up @@ -183,7 +183,7 @@ export class ShippingMethodService {
}

getActiveShippingMethods(channel: Channel): ShippingMethod[] {
return this.activeShippingMethods.filter(sm => sm.channels.find(c => c.id === channel.id));
return this.activeShippingMethods.filter(sm => sm.channels.find(c => idsAreEqual(c.id, channel.id)));
}

/**
Expand Down

0 comments on commit 1a3b04f

Please sign in to comment.