mv-order-seller-strategy.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import {
  2. ChannelService,
  3. EntityHydrator,
  4. ID,
  5. idsAreEqual,
  6. Injector,
  7. InternalServerError,
  8. isGraphQlErrorResult,
  9. Order,
  10. OrderLine,
  11. OrderSellerStrategy,
  12. OrderService,
  13. PaymentMethod,
  14. PaymentMethodService,
  15. PaymentService,
  16. RequestContext,
  17. SplitOrderContents,
  18. Surcharge,
  19. TransactionalConnection,
  20. } from '@vendure/core';
  21. import { CONNECTED_PAYMENT_METHOD_CODE, MULTIVENDOR_PLUGIN_OPTIONS } from '../constants';
  22. import { MultivendorPluginOptions } from '../types';
  23. declare module '@vendure/core/dist/entity/custom-entity-fields' {
  24. interface CustomSellerFields {
  25. connectedAccountId: string;
  26. }
  27. }
  28. export class MultivendorSellerStrategy implements OrderSellerStrategy {
  29. private entityHydrator: EntityHydrator;
  30. private channelService: ChannelService;
  31. private paymentService: PaymentService;
  32. private paymentMethodService: PaymentMethodService;
  33. private connection: TransactionalConnection;
  34. private orderService: OrderService;
  35. private options: MultivendorPluginOptions;
  36. init(injector: Injector) {
  37. this.entityHydrator = injector.get(EntityHydrator);
  38. this.channelService = injector.get(ChannelService);
  39. this.paymentService = injector.get(PaymentService);
  40. this.paymentMethodService = injector.get(PaymentMethodService);
  41. this.connection = injector.get(TransactionalConnection);
  42. this.orderService = injector.get(OrderService);
  43. this.options = injector.get(MULTIVENDOR_PLUGIN_OPTIONS);
  44. }
  45. async setOrderLineSellerChannel(ctx: RequestContext, orderLine: OrderLine) {
  46. await this.entityHydrator.hydrate(ctx, orderLine.productVariant, { relations: ['channels'] });
  47. const defaultChannel = await this.channelService.getDefaultChannel();
  48. // If a ProductVariant is assigned to exactly 2 Channels, then one is the default Channel
  49. // and the other is the seller's Channel.
  50. if (orderLine.productVariant.channels.length === 2) {
  51. const sellerChannel = orderLine.productVariant.channels.find(
  52. c => !idsAreEqual(c.id, defaultChannel.id),
  53. );
  54. if (sellerChannel) {
  55. return sellerChannel;
  56. }
  57. }
  58. }
  59. async splitOrder(ctx: RequestContext, order: Order): Promise<SplitOrderContents[]> {
  60. const partialOrders = new Map<ID, SplitOrderContents>();
  61. for (const line of order.lines) {
  62. const sellerChannelId = line.sellerChannelId;
  63. if (sellerChannelId) {
  64. let partialOrder = partialOrders.get(sellerChannelId);
  65. if (!partialOrder) {
  66. partialOrder = {
  67. channelId: sellerChannelId,
  68. shippingLines: [],
  69. lines: [],
  70. state: 'ArrangingPayment',
  71. };
  72. partialOrders.set(sellerChannelId, partialOrder);
  73. }
  74. partialOrder.lines.push(line);
  75. }
  76. }
  77. for (const partialOrder of partialOrders.values()) {
  78. const shippingLineIds = new Set(partialOrder.lines.map(l => l.shippingLineId));
  79. partialOrder.shippingLines = order.shippingLines.filter(shippingLine =>
  80. shippingLineIds.has(shippingLine.id),
  81. );
  82. }
  83. return [...partialOrders.values()];
  84. }
  85. async afterSellerOrdersCreated(ctx: RequestContext, aggregateOrder: Order, sellerOrders: Order[]) {
  86. const paymentMethod = await this.connection.rawConnection.getRepository(PaymentMethod).findOne({
  87. where: {
  88. code: CONNECTED_PAYMENT_METHOD_CODE,
  89. },
  90. });
  91. if (!paymentMethod) {
  92. return;
  93. }
  94. const defaultChannel = await this.channelService.getDefaultChannel();
  95. for (const sellerOrder of sellerOrders) {
  96. const sellerChannel = sellerOrder.channels.find(c => !idsAreEqual(c.id, defaultChannel.id));
  97. if (!sellerChannel) {
  98. throw new InternalServerError(
  99. `Could not determine Seller Channel for Order ${sellerOrder.code}`,
  100. );
  101. }
  102. sellerOrder.surcharges = [await this.createPlatformFeeSurcharge(ctx, sellerOrder)];
  103. await this.orderService.applyPriceAdjustments(ctx, sellerOrder);
  104. await this.entityHydrator.hydrate(ctx, sellerChannel, { relations: ['seller'] });
  105. const result = await this.orderService.addPaymentToOrder(ctx, sellerOrder.id, {
  106. method: paymentMethod.code,
  107. metadata: {
  108. transfer_group: aggregateOrder.code,
  109. connectedAccountId: sellerChannel.seller?.customFields.connectedAccountId,
  110. },
  111. });
  112. if (isGraphQlErrorResult(result)) {
  113. throw new InternalServerError(result.message);
  114. }
  115. }
  116. }
  117. private async createPlatformFeeSurcharge(ctx: RequestContext, sellerOrder: Order) {
  118. const platformFee = Math.round(sellerOrder.totalWithTax * -(this.options.platformFeePercent / 100));
  119. return this.connection.getRepository(ctx, Surcharge).save(
  120. new Surcharge({
  121. taxLines: [],
  122. sku: this.options.platformFeeSKU,
  123. description: 'Platform fee',
  124. listPrice: platformFee,
  125. listPriceIncludesTax: true,
  126. order: sellerOrder,
  127. }),
  128. );
  129. }
  130. }