Kaynağa Gözat

feat(core): Implement Order surcharges

Relates to #583. This commit defines the entity and relations, as well as making sure surcharges
are used in calculating the Order subTotal. It does not include any special APIs for adding or
removing surcharges, however.
Michael Bromley 5 yıl önce
ebeveyn
işleme
b608e145bf

+ 20 - 0
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -1421,6 +1421,13 @@ export type Order = Node & {
   shippingAddress?: Maybe<OrderAddress>;
   billingAddress?: Maybe<OrderAddress>;
   lines: Array<OrderLine>;
+  /**
+   * Surcharges are arbitrary modifications to the Order total which are neither
+   * ProductVariants nor discounts resulting from applied Promotions. For example,
+   * one-off discounts based on customer interaction, or surcharges based on payment
+   * methods.
+   */
+  surcharges: Array<Surcharge>;
   /**
    * Order-level adjustments to the order total, such as discounts from promotions
    * @deprecated Use `discounts` instead
@@ -3710,6 +3717,19 @@ export type Refund = Node & {
   metadata?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+  __typename?: 'Surcharge';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  description: Scalars['String'];
+  sku?: Maybe<Scalars['String']>;
+  taxLines: Array<TaxLine>;
+  price: Scalars['Int'];
+  priceWithTax: Scalars['Int'];
+  taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
   __typename?: 'ProductOptionGroup';
   id: Scalars['ID'];

+ 1 - 0
packages/admin-ui/src/lib/core/src/common/introspection-result.ts

@@ -104,6 +104,7 @@ const result: PossibleTypesResultData = {
             'OrderLine',
             'Payment',
             'Refund',
+            'Surcharge',
             'ProductOptionGroup',
             'ProductOption',
             'Promotion',

+ 19 - 0
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -1234,6 +1234,13 @@ export type Order = Node & {
     shippingAddress?: Maybe<OrderAddress>;
     billingAddress?: Maybe<OrderAddress>;
     lines: Array<OrderLine>;
+    /**
+     * Surcharges are arbitrary modifications to the Order total which are neither
+     * ProductVariants nor discounts resulting from applied Promotions. For example,
+     * one-off discounts based on customer interaction, or surcharges based on payment
+     * methods.
+     */
+    surcharges: Array<Surcharge>;
     /**
      * Order-level adjustments to the order total, such as discounts from promotions
      * @deprecated Use `discounts` instead
@@ -3468,6 +3475,18 @@ export type Refund = Node & {
     metadata?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+    id: Scalars['ID'];
+    createdAt: Scalars['DateTime'];
+    updatedAt: Scalars['DateTime'];
+    description: Scalars['String'];
+    sku?: Maybe<Scalars['String']>;
+    taxLines: Array<TaxLine>;
+    price: Scalars['Int'];
+    priceWithTax: Scalars['Int'];
+    taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
     id: Scalars['ID'];
     createdAt: Scalars['DateTime'];

+ 20 - 0
packages/common/src/generated-shop-types.ts

@@ -1722,6 +1722,13 @@ export type Order = Node & {
     shippingAddress?: Maybe<OrderAddress>;
     billingAddress?: Maybe<OrderAddress>;
     lines: Array<OrderLine>;
+    /**
+     * Surcharges are arbitrary modifications to the Order total which are neither
+     * ProductVariants nor discounts resulting from applied Promotions. For example,
+     * one-off discounts based on customer interaction, or surcharges based on payment
+     * methods.
+     */
+    surcharges: Array<Surcharge>;
     /**
      * Order-level adjustments to the order total, such as discounts from promotions
      * @deprecated Use `discounts` instead
@@ -1962,6 +1969,19 @@ export type Fulfillment = Node & {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+    __typename?: 'Surcharge';
+    id: Scalars['ID'];
+    createdAt: Scalars['DateTime'];
+    updatedAt: Scalars['DateTime'];
+    description: Scalars['String'];
+    sku?: Maybe<Scalars['String']>;
+    taxLines: Array<TaxLine>;
+    price: Scalars['Int'];
+    priceWithTax: Scalars['Int'];
+    taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
     __typename?: 'ProductOptionGroup';
     id: Scalars['ID'];

+ 20 - 0
packages/common/src/generated-types.ts

@@ -1390,6 +1390,13 @@ export type Order = Node & {
   shippingAddress?: Maybe<OrderAddress>;
   billingAddress?: Maybe<OrderAddress>;
   lines: Array<OrderLine>;
+  /**
+   * Surcharges are arbitrary modifications to the Order total which are neither
+   * ProductVariants nor discounts resulting from applied Promotions. For example,
+   * one-off discounts based on customer interaction, or surcharges based on payment
+   * methods.
+   */
+  surcharges: Array<Surcharge>;
   /**
    * Order-level adjustments to the order total, such as discounts from promotions
    * @deprecated Use `discounts` instead
@@ -3678,6 +3685,19 @@ export type Refund = Node & {
   metadata?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+  __typename?: 'Surcharge';
+  id: Scalars['ID'];
+  createdAt: Scalars['DateTime'];
+  updatedAt: Scalars['DateTime'];
+  description: Scalars['String'];
+  sku?: Maybe<Scalars['String']>;
+  taxLines: Array<TaxLine>;
+  price: Scalars['Int'];
+  priceWithTax: Scalars['Int'];
+  taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
   __typename?: 'ProductOptionGroup';
   id: Scalars['ID'];

+ 19 - 0
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -1234,6 +1234,13 @@ export type Order = Node & {
     shippingAddress?: Maybe<OrderAddress>;
     billingAddress?: Maybe<OrderAddress>;
     lines: Array<OrderLine>;
+    /**
+     * Surcharges are arbitrary modifications to the Order total which are neither
+     * ProductVariants nor discounts resulting from applied Promotions. For example,
+     * one-off discounts based on customer interaction, or surcharges based on payment
+     * methods.
+     */
+    surcharges: Array<Surcharge>;
     /**
      * Order-level adjustments to the order total, such as discounts from promotions
      * @deprecated Use `discounts` instead
@@ -3468,6 +3475,18 @@ export type Refund = Node & {
     metadata?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+    id: Scalars['ID'];
+    createdAt: Scalars['DateTime'];
+    updatedAt: Scalars['DateTime'];
+    description: Scalars['String'];
+    sku?: Maybe<Scalars['String']>;
+    taxLines: Array<TaxLine>;
+    price: Scalars['Int'];
+    priceWithTax: Scalars['Int'];
+    taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
     id: Scalars['ID'];
     createdAt: Scalars['DateTime'];

+ 19 - 0
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -1671,6 +1671,13 @@ export type Order = Node & {
     shippingAddress?: Maybe<OrderAddress>;
     billingAddress?: Maybe<OrderAddress>;
     lines: Array<OrderLine>;
+    /**
+     * Surcharges are arbitrary modifications to the Order total which are neither
+     * ProductVariants nor discounts resulting from applied Promotions. For example,
+     * one-off discounts based on customer interaction, or surcharges based on payment
+     * methods.
+     */
+    surcharges: Array<Surcharge>;
     /**
      * Order-level adjustments to the order total, such as discounts from promotions
      * @deprecated Use `discounts` instead
@@ -1901,6 +1908,18 @@ export type Fulfillment = Node & {
     customFields?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+    id: Scalars['ID'];
+    createdAt: Scalars['DateTime'];
+    updatedAt: Scalars['DateTime'];
+    description: Scalars['String'];
+    sku?: Maybe<Scalars['String']>;
+    taxLines: Array<TaxLine>;
+    price: Scalars['Int'];
+    priceWithTax: Scalars['Int'];
+    taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
     id: Scalars['ID'];
     createdAt: Scalars['DateTime'];

+ 8 - 0
packages/core/src/api/resolvers/entity/order-entity.resolver.ts

@@ -32,6 +32,14 @@ export class OrderEntityResolver {
         return this.orderService.getOrderFulfillments(ctx, order);
     }
 
+    @ResolveField()
+    async surcharges(@Ctx() ctx: RequestContext, @Parent() order: Order) {
+        if (order.surcharges) {
+            return order.surcharges;
+        }
+        return this.orderService.getOrderSurcharges(ctx, order.id);
+    }
+
     @ResolveField()
     async history(
         @Ctx() ctx: RequestContext,

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

@@ -16,6 +16,13 @@ type Order implements Node {
     shippingAddress: OrderAddress
     billingAddress: OrderAddress
     lines: [OrderLine!]!
+    """
+    Surcharges are arbitrary modifications to the Order total which are neither
+    ProductVariants nor discounts resulting from applied Promotions. For example,
+    one-off discounts based on customer interaction, or surcharges based on payment
+    methods.
+    """
+    surcharges: [Surcharge!]!
     "Order-level adjustments to the order total, such as discounts from promotions"
     adjustments: [Adjustment!]! @deprecated(reason: "Use `discounts` instead")
     discounts: [Adjustment!]!
@@ -242,3 +249,15 @@ type Fulfillment implements Node {
     method: String!
     trackingCode: String
 }
+
+type Surcharge implements Node {
+    id: ID!
+    createdAt: DateTime!
+    updatedAt: DateTime!
+    description: String!
+    sku: String
+    taxLines: [TaxLine!]!
+    price: Int!
+    priceWithTax: Int!
+    taxRate: Float!
+}

+ 2 - 0
packages/core/src/entity/entities.ts

@@ -52,6 +52,7 @@ import { Release } from './stock-movement/release.entity';
 import { Sale } from './stock-movement/sale.entity';
 import { StockAdjustment } from './stock-movement/stock-adjustment.entity';
 import { StockMovement } from './stock-movement/stock-movement.entity';
+import { Surcharge } from './surcharge/surcharge.entity';
 import { TaxCategory } from './tax-category/tax-category.entity';
 import { TaxRate } from './tax-rate/tax-rate.entity';
 import { User } from './user/user.entity';
@@ -115,6 +116,7 @@ export const coreEntitiesMap = {
     ShippingMethodTranslation,
     StockAdjustment,
     StockMovement,
+    Surcharge,
     TaxCategory,
     TaxRate,
     User,

+ 1 - 0
packages/core/src/entity/index.ts

@@ -40,6 +40,7 @@ export * from './role/role.entity';
 export * from './session/session.entity';
 export * from './session/anonymous-session.entity';
 export * from './session/authenticated-session.entity';
+export * from './surcharge/surcharge.entity';
 export * from './shipping-method/shipping-method.entity';
 export * from './tax-category/tax-category.entity';
 export * from './tax-rate/tax-rate.entity';

+ 4 - 0
packages/core/src/entity/order/order.entity.ts

@@ -25,6 +25,7 @@ import { Payment } from '../payment/payment.entity';
 import { Promotion } from '../promotion/promotion.entity';
 import { ShippingLine } from '../shipping-line/shipping-line.entity';
 import { ShippingMethod } from '../shipping-method/shipping-method.entity';
+import { Surcharge } from '../surcharge/surcharge.entity';
 
 /**
  * @description
@@ -59,6 +60,9 @@ export class Order extends VendureEntity implements ChannelAware, HasCustomField
     @OneToMany(type => OrderLine, line => line.order)
     lines: OrderLine[];
 
+    @OneToMany(type => Surcharge, surcharge => surcharge.order)
+    surcharges: Surcharge[];
+
     @Column('simple-array')
     couponCodes: string[];
 

+ 56 - 0
packages/core/src/entity/surcharge/surcharge.entity.ts

@@ -0,0 +1,56 @@
+import { TaxLine } from '@vendure/common/lib/generated-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
+import { summate } from '@vendure/common/lib/shared-utils';
+import { Column, Entity, ManyToOne } from 'typeorm';
+
+import { Calculated } from '../../common/calculated-decorator';
+import { grossPriceOf, netPriceOf } from '../../common/tax-utils';
+import { VendureEntity } from '../base/base.entity';
+import { Order } from '../order/order.entity';
+
+/**
+ * @description
+ * A Surcharge represents an arbitrary extra item on an {@link Order} which is not
+ * a ProductVariant. It can be used to e.g. represent payment-related surcharges.
+ *
+ * @docsCategory entities
+ */
+@Entity()
+export class Surcharge extends VendureEntity {
+    constructor(input?: DeepPartial<Surcharge>) {
+        super(input);
+    }
+
+    @Column()
+    description: string;
+
+    @Column()
+    listPrice: number;
+
+    @Column()
+    listPriceIncludesTax: boolean;
+
+    @Column()
+    sku: string;
+
+    @Column('simple-json')
+    taxLines: TaxLine[];
+
+    @ManyToOne(type => Order, order => order.surcharges)
+    order: Order;
+
+    @Calculated()
+    get price(): number {
+        return this.listPriceIncludesTax ? netPriceOf(this.listPrice, this.taxRate) : this.listPrice;
+    }
+
+    @Calculated()
+    get priceWithTax(): number {
+        return this.listPriceIncludesTax ? this.listPrice : grossPriceOf(this.listPrice, this.taxRate);
+    }
+
+    @Calculated()
+    get taxRate(): number {
+        return summate(this.taxLines, 'taxRate');
+    }
+}

+ 251 - 2
packages/core/src/service/helpers/order-calculator/order-calculator.spec.ts

@@ -17,6 +17,7 @@ import { Promotion } from '../../../entity';
 import { OrderItem } from '../../../entity/order-item/order-item.entity';
 import { Order } from '../../../entity/order/order.entity';
 import { ShippingLine } from '../../../entity/shipping-line/shipping-line.entity';
+import { Surcharge } from '../../../entity/surcharge/surcharge.entity';
 import { EventBus } from '../../../event-bus/event-bus';
 import {
     createOrder,
@@ -1068,6 +1069,252 @@ describe('OrderCalculator', () => {
             });
         });
     });
+
+    describe('surcharges', () => {
+        describe('positive surcharge without tax', () => {
+            it('prices exclude tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: false });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 2,
+                        },
+                        {
+                            listPrice: 3499,
+                            taxCategory: taxCategoryReduced,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'payment surcharge',
+                        listPrice: 240,
+                        listPriceIncludesTax: false,
+                        taxLines: [],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(5739);
+                expect(order.subTotalWithTax).toBe(6489);
+                assertOrderTotalsAddUp(order);
+            });
+
+            it('prices include tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: true });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 2,
+                        },
+                        {
+                            listPrice: 3499,
+                            taxCategory: taxCategoryReduced,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'payment surcharge',
+                        listPrice: 240,
+                        listPriceIncludesTax: true,
+                        taxLines: [],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(5087);
+                expect(order.subTotalWithTax).toBe(5739);
+                assertOrderTotalsAddUp(order);
+            });
+        });
+
+        describe('positive surcharge with tax', () => {
+            it('prices exclude tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: false });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'payment surcharge',
+                        listPrice: 240,
+                        listPriceIncludesTax: false,
+                        taxLines: [
+                            {
+                                description: 'standard tax',
+                                taxRate: 20,
+                            },
+                        ],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(1240);
+                expect(order.subTotalWithTax).toBe(1488);
+                assertOrderTotalsAddUp(order);
+            });
+
+            it('prices include tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: true });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'payment surcharge',
+                        listPrice: 240,
+                        listPriceIncludesTax: true,
+                        taxLines: [
+                            {
+                                description: 'standard tax',
+                                taxRate: 20,
+                            },
+                        ],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(1033);
+                expect(order.subTotalWithTax).toBe(1240);
+                assertOrderTotalsAddUp(order);
+            });
+        });
+
+        describe('negative surcharge with tax', () => {
+            it('prices exclude tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: false });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'custom discount',
+                        listPrice: -240,
+                        listPriceIncludesTax: false,
+                        taxLines: [
+                            {
+                                description: 'standard tax',
+                                taxRate: 20,
+                            },
+                        ],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(760);
+                expect(order.subTotalWithTax).toBe(912);
+                assertOrderTotalsAddUp(order);
+            });
+
+            it('prices include tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: true });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'custom discount',
+                        listPrice: -240,
+                        listPriceIncludesTax: true,
+                        taxLines: [
+                            {
+                                description: 'standard tax',
+                                taxRate: 20,
+                            },
+                        ],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(633);
+                expect(order.subTotalWithTax).toBe(760);
+                assertOrderTotalsAddUp(order);
+            });
+
+            it('prices exclude tax but surcharge includes tax', async () => {
+                const ctx = createRequestContext({ pricesIncludeTax: false });
+                const order = createOrder({
+                    ctx,
+                    lines: [
+                        {
+                            listPrice: 1000,
+                            taxCategory: taxCategoryStandard,
+                            quantity: 1,
+                        },
+                    ],
+                });
+                order.surcharges = [
+                    new Surcharge({
+                        description: 'custom discount',
+                        listPrice: -240,
+                        listPriceIncludesTax: true,
+                        taxLines: [
+                            {
+                                description: 'standard tax',
+                                taxRate: 20,
+                            },
+                        ],
+                        sku: 'PSC',
+                    }),
+                ];
+
+                await orderCalculator.applyPriceAdjustments(ctx, order, []);
+
+                expect(order.subTotal).toBe(800);
+                expect(order.subTotalWithTax).toBe(960);
+                assertOrderTotalsAddUp(order);
+            });
+        });
+    });
 });
 
 describe('OrderCalculator with custom TaxLineCalculationStrategy', () => {
@@ -1265,10 +1512,12 @@ function assertOrderTotalsAddUp(order: Order) {
         expect(line.linePriceWithTax).toBe(itemUnitPriceWithTaxSum);
     }
     const taxableLinePriceSum = summate(order.lines, 'proratedLinePrice');
-    expect(order.subTotal).toBe(taxableLinePriceSum);
+    const surchargeSum = summate(order.surcharges, 'price');
+    expect(order.subTotal).toBe(taxableLinePriceSum + surchargeSum);
 
     // Make sure the customer-facing totals also add up
     const displayPriceWithTaxSum = summate(order.lines, 'discountedLinePriceWithTax');
+    const surchargeWithTaxSum = summate(order.surcharges, 'priceWithTax');
     const orderDiscountsSum = order.discounts
         .filter(d => d.type === AdjustmentType.DISTRIBUTED_ORDER_PROMOTION)
         .reduce((sum, d) => sum + d.amount, 0);
@@ -1277,7 +1526,7 @@ function assertOrderTotalsAddUp(order: Order) {
     // equal the subTotalWithTax. In practice, there are occasionally 1cent differences
     // cause by rounding errors. This should be tolerable.
     const differenceBetweenSumAndActual = Math.abs(
-        displayPriceWithTaxSum + orderDiscountsSum - order.subTotalWithTax,
+        displayPriceWithTaxSum + orderDiscountsSum + surchargeWithTaxSum - order.subTotalWithTax,
     );
     expect(differenceBetweenSumAndActual).toBeLessThanOrEqual(1);
 }

+ 4 - 0
packages/core/src/service/helpers/order-calculator/order-calculator.ts

@@ -377,6 +377,10 @@ export class OrderCalculator {
             totalPrice += line.proratedLinePrice;
             totalPriceWithTax += line.proratedLinePriceWithTax;
         }
+        for (const surcharge of order.surcharges) {
+            totalPrice += surcharge.price;
+            totalPriceWithTax += surcharge.priceWithTax;
+        }
 
         order.subTotal = totalPrice;
         order.subTotalWithTax = totalPriceWithTax;

+ 1 - 0
packages/core/src/service/services/order-testing.service.ts

@@ -107,6 +107,7 @@ export class OrderTestingService {
         const { orderItemPriceCalculationStrategy } = this.configService.orderOptions;
         const mockOrder = new Order({
             lines: [],
+            surcharges: [],
         });
         mockOrder.shippingAddress = shippingAddress;
         for (const line of lines) {

+ 11 - 0
packages/core/src/service/services/order.service.ts

@@ -75,6 +75,7 @@ import { ProductVariant } from '../../entity/product-variant/product-variant.ent
 import { Promotion } from '../../entity/promotion/promotion.entity';
 import { Refund } from '../../entity/refund/refund.entity';
 import { ShippingLine } from '../../entity/shipping-line/shipping-line.entity';
+import { Surcharge } from '../../entity/surcharge/surcharge.entity';
 import { User } from '../../entity/user/user.entity';
 import { EventBus } from '../../event-bus/event-bus';
 import { OrderStateTransitionEvent } from '../../event-bus/events/order-state-transition-event';
@@ -163,6 +164,7 @@ export class OrderService {
             .leftJoin('order.channels', 'channel')
             .leftJoinAndSelect('order.customer', 'customer')
             .leftJoinAndSelect('order.shippingLines', 'shippingLines')
+            .leftJoinAndSelect('order.surcharges', 'surcharges')
             .leftJoinAndSelect('customer.user', 'user')
             .leftJoinAndSelect('order.lines', 'lines')
             .leftJoinAndSelect('lines.productVariant', 'productVariant')
@@ -284,6 +286,7 @@ export class OrderService {
             code: await this.configService.orderOptions.orderCodeStrategy.generate(ctx),
             state: this.orderStateMachine.getInitialState(),
             lines: [],
+            surcharges: [],
             couponCodes: [],
             shippingAddress: {},
             billingAddress: {},
@@ -826,6 +829,14 @@ export class OrderService {
         return unique(fulfillments, 'id');
     }
 
+    async getOrderSurcharges(ctx: RequestContext, orderId: ID): Promise<Surcharge[]> {
+        const order = await this.connection.getEntityOrThrow(ctx, Order, orderId, {
+            channelId: ctx.channelId,
+            relations: ['surcharges'],
+        });
+        return order.surcharges || [];
+    }
+
     async cancelOrder(
         ctx: RequestContext,
         input: CancelOrderInput,

+ 1 - 0
packages/core/src/testing/order-test-utils.ts

@@ -156,5 +156,6 @@ export function createOrder(
         couponCodes: [],
         lines,
         shippingLines: [],
+        surcharges: [],
     });
 }

+ 19 - 0
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -1234,6 +1234,13 @@ export type Order = Node & {
     shippingAddress?: Maybe<OrderAddress>;
     billingAddress?: Maybe<OrderAddress>;
     lines: Array<OrderLine>;
+    /**
+     * Surcharges are arbitrary modifications to the Order total which are neither
+     * ProductVariants nor discounts resulting from applied Promotions. For example,
+     * one-off discounts based on customer interaction, or surcharges based on payment
+     * methods.
+     */
+    surcharges: Array<Surcharge>;
     /**
      * Order-level adjustments to the order total, such as discounts from promotions
      * @deprecated Use `discounts` instead
@@ -3468,6 +3475,18 @@ export type Refund = Node & {
     metadata?: Maybe<Scalars['JSON']>;
 };
 
+export type Surcharge = Node & {
+    id: Scalars['ID'];
+    createdAt: Scalars['DateTime'];
+    updatedAt: Scalars['DateTime'];
+    description: Scalars['String'];
+    sku?: Maybe<Scalars['String']>;
+    taxLines: Array<TaxLine>;
+    price: Scalars['Int'];
+    priceWithTax: Scalars['Int'];
+    taxRate: Scalars['Float'];
+};
+
 export type ProductOptionGroup = Node & {
     id: Scalars['ID'];
     createdAt: Scalars['DateTime'];

+ 1 - 0
packages/email-plugin/src/mock-events.ts

@@ -96,6 +96,7 @@ export const mockOrderStateTransitionEvent = new OrderStateTransitionEvent(
                 },
             },
         ],
+        surcharges: [],
         shippingAddress: {
             fullName: 'Test Customer',
             company: '',

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
schema-admin.json


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
schema-shop.json


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor