| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
- import { summate } from '@vendure/common/lib/shared-utils';
- import {
- Channel,
- Injector,
- Order,
- RequestContext,
- TaxZoneStrategy,
- TransactionalConnection,
- Zone,
- } from '@vendure/core';
- import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
- import gql from 'graphql-tag';
- import path from 'path';
- import { afterAll, beforeAll, describe, expect, it } from 'vitest';
- import { initialData } from '../../../e2e-common/e2e-initial-data';
- import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
- import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
- import * as Codegen from './graphql/generated-e2e-admin-types';
- import * as CodegenShop from './graphql/generated-e2e-shop-types';
- import {
- GET_PRODUCTS_WITH_VARIANT_PRICES,
- UPDATE_CHANNEL,
- UPDATE_TAX_RATE,
- } from './graphql/shared-definitions';
- import {
- ADD_ITEM_TO_ORDER,
- GET_ACTIVE_ORDER_WITH_PRICE_DATA,
- SET_BILLING_ADDRESS,
- SET_SHIPPING_ADDRESS,
- } from './graphql/shop-definitions';
- import { sortById } from './utils/test-order-utils';
- /**
- * Determines active tax zone based on:
- *
- * 1. billingAddress country, if set
- * 2. else shippingAddress country, is set
- * 3. else channel default tax zone.
- */
- class TestTaxZoneStrategy implements TaxZoneStrategy {
- private connection: TransactionalConnection;
- init(injector: Injector): void | Promise<void> {
- this.connection = injector.get(TransactionalConnection);
- }
- async determineTaxZone(
- ctx: RequestContext,
- zones: Zone[],
- channel: Channel,
- order?: Order,
- ): Promise<Zone> {
- if (!order?.billingAddress?.countryCode && !order?.shippingAddress?.countryCode) {
- return channel.defaultTaxZone;
- }
- const countryCode = order?.billingAddress?.countryCode || order?.shippingAddress?.countryCode;
- const zoneForCountryCode = await this.getZoneForCountryCode(ctx, countryCode);
- return zoneForCountryCode ?? channel.defaultTaxZone;
- }
- private getZoneForCountryCode(ctx: RequestContext, countryCode?: string): Promise<Zone | null> {
- return this.connection
- .getRepository(ctx, Zone)
- .createQueryBuilder('zone')
- .leftJoin('zone.members', 'country')
- .where('country.code = :countryCode', {
- countryCode,
- })
- .getOne();
- }
- }
- describe('Order taxes', () => {
- const { server, adminClient, shopClient } = createTestEnvironment({
- ...testConfig(),
- taxOptions: {
- taxZoneStrategy: new TestTaxZoneStrategy(),
- },
- paymentOptions: {
- paymentMethodHandlers: [testSuccessfulPaymentMethod],
- },
- });
- type OrderSuccessResult = CodegenShop.UpdatedOrderFragment | CodegenShop.TestOrderFragmentFragment;
- const orderResultGuard: ErrorResultGuard<OrderSuccessResult> = createErrorResultGuard(
- input => !!input.lines,
- );
- let products: Codegen.GetProductsWithVariantPricesQuery['products']['items'];
- beforeAll(async () => {
- await server.init({
- initialData: {
- ...initialData,
- paymentMethods: [
- {
- name: testSuccessfulPaymentMethod.code,
- handler: { code: testSuccessfulPaymentMethod.code, arguments: [] },
- },
- ],
- },
- productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-order-taxes.csv'),
- customerCount: 2,
- });
- await adminClient.asSuperAdmin();
- const result = await adminClient.query<Codegen.GetProductsWithVariantPricesQuery>(
- GET_PRODUCTS_WITH_VARIANT_PRICES,
- );
- products = result.products.items;
- }, TEST_SETUP_TIMEOUT_MS);
- afterAll(async () => {
- await server.destroy();
- });
- describe('Channel.pricesIncludeTax = false', () => {
- beforeAll(async () => {
- await adminClient.query<Codegen.UpdateChannelMutation, Codegen.UpdateChannelMutationVariables>(
- UPDATE_CHANNEL,
- {
- input: {
- id: 'T_1',
- pricesIncludeTax: false,
- },
- },
- );
- await shopClient.asAnonymousUser();
- });
- it('prices are correct', async () => {
- const variant = products.sort(sortById)[0].variants.sort(sortById)[0];
- await shopClient.query<
- CodegenShop.AddItemToOrderMutation,
- CodegenShop.AddItemToOrderMutationVariables
- >(ADD_ITEM_TO_ORDER, {
- productVariantId: variant.id,
- quantity: 2,
- });
- const { activeOrder } = await shopClient.query<CodegenShop.GetActiveOrderWithPriceDataQuery>(
- GET_ACTIVE_ORDER_WITH_PRICE_DATA,
- );
- expect(activeOrder?.totalWithTax).toBe(240);
- expect(activeOrder?.total).toBe(200);
- expect(activeOrder?.lines[0].taxRate).toBe(20);
- expect(activeOrder?.lines[0].linePrice).toBe(200);
- expect(activeOrder?.lines[0].lineTax).toBe(40);
- expect(activeOrder?.lines[0].linePriceWithTax).toBe(240);
- expect(activeOrder?.lines[0].unitPrice).toBe(100);
- expect(activeOrder?.lines[0].unitPriceWithTax).toBe(120);
- expect(activeOrder?.lines[0].taxLines).toEqual([
- {
- description: 'Standard Tax Europe',
- taxRate: 20,
- },
- ]);
- });
- });
- describe('Channel.pricesIncludeTax = true', () => {
- beforeAll(async () => {
- await adminClient.query<Codegen.UpdateChannelMutation, Codegen.UpdateChannelMutationVariables>(
- UPDATE_CHANNEL,
- {
- input: {
- id: 'T_1',
- pricesIncludeTax: true,
- },
- },
- );
- await shopClient.asAnonymousUser();
- });
- it('prices are correct', async () => {
- const variant = products[0].variants[0];
- await shopClient.query<
- CodegenShop.AddItemToOrderMutation,
- CodegenShop.AddItemToOrderMutationVariables
- >(ADD_ITEM_TO_ORDER, {
- productVariantId: variant.id,
- quantity: 2,
- });
- const { activeOrder } = await shopClient.query<CodegenShop.GetActiveOrderWithPriceDataQuery>(
- GET_ACTIVE_ORDER_WITH_PRICE_DATA,
- );
- expect(activeOrder?.totalWithTax).toBe(200);
- expect(activeOrder?.total).toBe(167);
- expect(activeOrder?.lines[0].taxRate).toBe(20);
- expect(activeOrder?.lines[0].linePrice).toBe(167);
- expect(activeOrder?.lines[0].lineTax).toBe(33);
- expect(activeOrder?.lines[0].linePriceWithTax).toBe(200);
- expect(activeOrder?.lines[0].unitPrice).toBe(83);
- expect(activeOrder?.lines[0].unitPriceWithTax).toBe(100);
- expect(activeOrder?.lines[0].taxLines).toEqual([
- {
- description: 'Standard Tax Europe',
- taxRate: 20,
- },
- ]);
- });
- // https://github.com/vendurehq/vendure/issues/1216
- it('re-calculates OrderLine prices when shippingAddress causes activeTaxZone change', async () => {
- const { taxRates } = await adminClient.query<Codegen.GetTaxRateListQuery>(GET_TAX_RATE_LIST);
- // Set the TaxRates to Asia to 0%
- const taxRatesAsia = taxRates.items.filter(tr => tr.name.includes('Asia'));
- for (const taxRate of taxRatesAsia) {
- await adminClient.query<
- Codegen.UpdateTaxRateMutation,
- Codegen.UpdateTaxRateMutationVariables
- >(UPDATE_TAX_RATE, {
- input: {
- id: taxRate.id,
- value: 0,
- },
- });
- }
- await shopClient.query<
- CodegenShop.SetShippingAddressMutation,
- CodegenShop.SetShippingAddressMutationVariables
- >(SET_SHIPPING_ADDRESS, {
- input: {
- countryCode: 'CN',
- streetLine1: '123 Lugu St',
- city: 'Beijing',
- province: 'Beijing',
- postalCode: '12340',
- },
- });
- const { activeOrder } = await shopClient.query<CodegenShop.GetActiveOrderWithPriceDataQuery>(
- GET_ACTIVE_ORDER_WITH_PRICE_DATA,
- );
- expect(activeOrder?.totalWithTax).toBe(166);
- expect(activeOrder?.total).toBe(166);
- expect(activeOrder?.lines[0].taxRate).toBe(0);
- expect(activeOrder?.lines[0].linePrice).toBe(166);
- expect(activeOrder?.lines[0].lineTax).toBe(0);
- expect(activeOrder?.lines[0].linePriceWithTax).toBe(166);
- expect(activeOrder?.lines[0].unitPrice).toBe(83);
- expect(activeOrder?.lines[0].unitPriceWithTax).toBe(83);
- expect(activeOrder?.lines[0].taxLines).toEqual([
- {
- description: 'Standard Tax Asia',
- taxRate: 0,
- },
- ]);
- });
- // https://github.com/vendurehq/vendure/issues/1216
- it('re-calculates OrderLine prices when billingAddress causes activeTaxZone change', async () => {
- await shopClient.query<
- CodegenShop.SetBillingAddressMutation,
- CodegenShop.SetBillingAddressMutationVariables
- >(SET_BILLING_ADDRESS, {
- input: {
- countryCode: 'US',
- streetLine1: '123 Chad Street',
- city: 'Houston',
- province: 'Texas',
- postalCode: '12345',
- },
- });
- const { activeOrder } = await shopClient.query<CodegenShop.GetActiveOrderWithPriceDataQuery>(
- GET_ACTIVE_ORDER_WITH_PRICE_DATA,
- );
- expect(activeOrder?.totalWithTax).toBe(199);
- expect(activeOrder?.total).toBe(166);
- expect(activeOrder?.lines[0].taxRate).toBe(20);
- expect(activeOrder?.lines[0].linePrice).toBe(166);
- expect(activeOrder?.lines[0].lineTax).toBe(33);
- expect(activeOrder?.lines[0].linePriceWithTax).toBe(199);
- expect(activeOrder?.lines[0].unitPrice).toBe(83);
- expect(activeOrder?.lines[0].unitPriceWithTax).toBe(100);
- expect(activeOrder?.lines[0].taxLines).toEqual([
- {
- description: 'Standard Tax Americas',
- taxRate: 20,
- },
- ]);
- });
- });
- it('taxSummary works', async () => {
- await adminClient.query<Codegen.UpdateChannelMutation, Codegen.UpdateChannelMutationVariables>(
- UPDATE_CHANNEL,
- {
- input: {
- id: 'T_1',
- pricesIncludeTax: false,
- },
- },
- );
- await shopClient.asAnonymousUser();
- await shopClient.query<
- CodegenShop.AddItemToOrderMutation,
- CodegenShop.AddItemToOrderMutationVariables
- >(ADD_ITEM_TO_ORDER, {
- productVariantId: products[0].variants[0].id,
- quantity: 2,
- });
- await shopClient.query<
- CodegenShop.AddItemToOrderMutation,
- CodegenShop.AddItemToOrderMutationVariables
- >(ADD_ITEM_TO_ORDER, {
- productVariantId: products[1].variants[0].id,
- quantity: 2,
- });
- await shopClient.query<
- CodegenShop.AddItemToOrderMutation,
- CodegenShop.AddItemToOrderMutationVariables
- >(ADD_ITEM_TO_ORDER, {
- productVariantId: products[2].variants[0].id,
- quantity: 2,
- });
- const { activeOrder } = await shopClient.query<CodegenShop.GetActiveOrderWithPriceDataQuery>(
- GET_ACTIVE_ORDER_WITH_PRICE_DATA,
- );
- expect(activeOrder?.taxSummary).toEqual([
- {
- description: 'Standard Tax Europe',
- taxRate: 20,
- taxBase: 200,
- taxTotal: 40,
- },
- {
- description: 'Reduced Tax Europe',
- taxRate: 10,
- taxBase: 200,
- taxTotal: 20,
- },
- {
- description: 'Zero Tax Europe',
- taxRate: 0,
- taxBase: 200,
- taxTotal: 0,
- },
- ]);
- // ensure that the summary total add up to the overall totals
- const taxSummaryBaseTotal = summate(activeOrder!.taxSummary, 'taxBase');
- const taxSummaryTaxTotal = summate(activeOrder!.taxSummary, 'taxTotal');
- expect(taxSummaryBaseTotal).toBe(activeOrder?.total);
- expect(taxSummaryBaseTotal + taxSummaryTaxTotal).toBe(activeOrder?.totalWithTax);
- });
- });
- export const GET_TAX_RATE_LIST = gql`
- query GetTaxRateList($options: TaxRateListOptions) {
- taxRates(options: $options) {
- items {
- id
- name
- enabled
- value
- category {
- id
- name
- }
- zone {
- id
- name
- }
- }
- totalItems
- }
- }
- `;
|