order-multi-vendor.e2e-spec.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import { mergeConfig, OrderService } from '@vendure/core';
  3. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  4. import { multivendorPaymentMethodHandler } from 'dev-server/example-plugins/multivendor-plugin/config/mv-payment-handler';
  5. import { CONNECTED_PAYMENT_METHOD_CODE } from 'dev-server/example-plugins/multivendor-plugin/constants';
  6. import { MultivendorPlugin } from 'dev-server/example-plugins/multivendor-plugin/multivendor.plugin';
  7. import gql from 'graphql-tag';
  8. import path from 'path';
  9. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  10. import { initialData } from '../../../e2e-common/e2e-initial-data';
  11. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  12. import {
  13. AssignProductsToChannelDocument,
  14. GetOrderWithSellerOrdersDocument,
  15. } from './graphql/generated-e2e-admin-types';
  16. import * as CodegenShop from './graphql/generated-e2e-shop-types';
  17. import { SetShippingMethodDocument } from './graphql/generated-e2e-shop-types';
  18. import {
  19. ADD_ITEM_TO_ORDER,
  20. ADD_PAYMENT,
  21. GET_ELIGIBLE_SHIPPING_METHODS,
  22. SET_SHIPPING_ADDRESS,
  23. TRANSITION_TO_STATE,
  24. } from './graphql/shop-definitions';
  25. declare module '@vendure/core/dist/entity/custom-entity-fields' {
  26. interface CustomShippingMethodFields {
  27. minPrice: number;
  28. maxPrice: number;
  29. }
  30. }
  31. describe('Multi-vendor orders', () => {
  32. const { server, adminClient, shopClient } = createTestEnvironment(
  33. mergeConfig(testConfig(), {
  34. plugins: [
  35. MultivendorPlugin.init({
  36. platformFeePercent: 10,
  37. platformFeeSKU: 'FEE',
  38. }),
  39. ],
  40. }),
  41. );
  42. let bobsPartsChannel: { id: string; token: string; variantIds: string[] };
  43. let alicesWaresChannel: { id: string; token: string; variantIds: string[] };
  44. let orderId: string;
  45. type OrderSuccessResult =
  46. | CodegenShop.UpdatedOrderFragment
  47. | CodegenShop.TestOrderFragmentFragment
  48. | CodegenShop.TestOrderWithPaymentsFragment
  49. | CodegenShop.ActiveOrderCustomerFragment;
  50. const orderResultGuard: ErrorResultGuard<OrderSuccessResult> = createErrorResultGuard(
  51. input => !!input.lines,
  52. );
  53. beforeAll(async () => {
  54. await server.init({
  55. initialData,
  56. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  57. customerCount: 3,
  58. });
  59. await adminClient.asSuperAdmin();
  60. }, TEST_SETUP_TIMEOUT_MS);
  61. afterAll(async () => {
  62. await server.destroy();
  63. });
  64. it('setup sellers', async () => {
  65. const result1 = await shopClient.query(REGISTER_SELLER, {
  66. input: {
  67. shopName: "Bob's Parts",
  68. seller: {
  69. firstName: 'Bob',
  70. lastName: 'Dobalina',
  71. emailAddress: 'bob@bobs-parts.com',
  72. password: 'test',
  73. },
  74. },
  75. });
  76. bobsPartsChannel = result1.registerNewSeller;
  77. expect(bobsPartsChannel.token).toBe('bobs-parts-token');
  78. const result2 = await shopClient.query(REGISTER_SELLER, {
  79. input: {
  80. shopName: "Alice's Wares",
  81. seller: {
  82. firstName: 'Alice',
  83. lastName: 'Smith',
  84. emailAddress: 'alice@alices-wares.com',
  85. password: 'test',
  86. },
  87. },
  88. });
  89. alicesWaresChannel = result2.registerNewSeller;
  90. expect(alicesWaresChannel.token).toBe('alices-wares-token');
  91. });
  92. it('assign products to sellers', async () => {
  93. const { assignProductsToChannel } = await adminClient.query(AssignProductsToChannelDocument, {
  94. input: {
  95. channelId: bobsPartsChannel.id,
  96. productIds: ['T_1'],
  97. priceFactor: 1,
  98. },
  99. });
  100. expect(assignProductsToChannel[0].channels.map(c => c.code)).toEqual([
  101. '__default_channel__',
  102. 'bobs-parts',
  103. ]);
  104. bobsPartsChannel.variantIds = assignProductsToChannel[0].variants.map(v => v.id);
  105. expect(bobsPartsChannel.variantIds).toEqual(['T_1', 'T_2', 'T_3', 'T_4']);
  106. const { assignProductsToChannel: result2 } = await adminClient.query(
  107. AssignProductsToChannelDocument,
  108. {
  109. input: {
  110. channelId: alicesWaresChannel.id,
  111. productIds: ['T_11'],
  112. priceFactor: 1,
  113. },
  114. },
  115. );
  116. expect(result2[0].channels.map(c => c.code)).toEqual(['__default_channel__', 'alices-wares']);
  117. alicesWaresChannel.variantIds = result2[0].variants.map(v => v.id);
  118. expect(alicesWaresChannel.variantIds).toEqual(['T_22']);
  119. });
  120. it('adds items and sets shipping methods', async () => {
  121. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  122. await shopClient.query<
  123. CodegenShop.AddItemToOrderMutation,
  124. CodegenShop.AddItemToOrderMutationVariables
  125. >(ADD_ITEM_TO_ORDER, {
  126. productVariantId: bobsPartsChannel.variantIds[0],
  127. quantity: 1,
  128. });
  129. await shopClient.query<
  130. CodegenShop.AddItemToOrderMutation,
  131. CodegenShop.AddItemToOrderMutationVariables
  132. >(ADD_ITEM_TO_ORDER, {
  133. productVariantId: alicesWaresChannel.variantIds[0],
  134. quantity: 1,
  135. });
  136. await shopClient.query<
  137. CodegenShop.SetShippingAddressMutation,
  138. CodegenShop.SetShippingAddressMutationVariables
  139. >(SET_SHIPPING_ADDRESS, {
  140. input: {
  141. streetLine1: '12 the street',
  142. postalCode: '123456',
  143. countryCode: 'US',
  144. },
  145. });
  146. const { eligibleShippingMethods } = await shopClient.query<CodegenShop.GetShippingMethodsQuery>(
  147. GET_ELIGIBLE_SHIPPING_METHODS,
  148. );
  149. expect(eligibleShippingMethods.map(m => m.code).sort()).toEqual([
  150. 'alices-wares-shipping',
  151. 'bobs-parts-shipping',
  152. 'express-shipping',
  153. 'standard-shipping',
  154. ]);
  155. const { setOrderShippingMethod } = await shopClient.query(SetShippingMethodDocument, {
  156. id: [
  157. eligibleShippingMethods.find(m => m.code === 'bobs-parts-shipping')!.id,
  158. eligibleShippingMethods.find(m => m.code === 'alices-wares-shipping')!.id,
  159. ],
  160. });
  161. orderResultGuard.assertSuccess(setOrderShippingMethod);
  162. expect(setOrderShippingMethod.shippingLines.map(l => l.shippingMethod.code).sort()).toEqual([
  163. 'alices-wares-shipping',
  164. 'bobs-parts-shipping',
  165. ]);
  166. });
  167. it('completing checkout splits order', async () => {
  168. const { transitionOrderToState } = await shopClient.query<
  169. CodegenShop.TransitionToStateMutation,
  170. CodegenShop.TransitionToStateMutationVariables
  171. >(TRANSITION_TO_STATE, { state: 'ArrangingPayment' });
  172. orderResultGuard.assertSuccess(transitionOrderToState);
  173. const { addPaymentToOrder } = await shopClient.query<
  174. CodegenShop.AddPaymentToOrderMutation,
  175. CodegenShop.AddPaymentToOrderMutationVariables
  176. >(ADD_PAYMENT, {
  177. input: {
  178. method: CONNECTED_PAYMENT_METHOD_CODE,
  179. metadata: {},
  180. },
  181. });
  182. orderResultGuard.assertSuccess(addPaymentToOrder);
  183. expect(addPaymentToOrder.state).toBe('PaymentSettled');
  184. const { order } = await adminClient.query(GetOrderWithSellerOrdersDocument, {
  185. id: addPaymentToOrder.id,
  186. });
  187. orderId = order!.id;
  188. expect(order?.sellerOrders?.length).toBe(2);
  189. });
  190. it('order lines get split', async () => {
  191. const { order } = await adminClient.query(GetOrderWithSellerOrdersDocument, {
  192. id: orderId,
  193. });
  194. expect(order?.sellerOrders?.[0].lines.map(l => l.productVariant.name)).toEqual([
  195. 'Laptop 13 inch 8GB',
  196. ]);
  197. expect(order?.sellerOrders?.[1].lines.map(l => l.productVariant.name)).toEqual(['Road Bike']);
  198. });
  199. it('shippingLines get split', async () => {
  200. const { order } = await adminClient.query(GetOrderWithSellerOrdersDocument, {
  201. id: orderId,
  202. });
  203. expect(order?.sellerOrders?.[0]?.shippingLines.length).toBe(1);
  204. expect(order?.sellerOrders?.[1]?.shippingLines.length).toBe(1);
  205. expect(order?.sellerOrders?.[0]?.shippingLines[0].shippingMethod.code).toBe('bobs-parts-shipping');
  206. expect(order?.sellerOrders?.[1]?.shippingLines[0].shippingMethod.code).toBe('alices-wares-shipping');
  207. });
  208. });
  209. export const REGISTER_SELLER = gql`
  210. mutation RegisterSeller($input: RegisterSellerInput!) {
  211. registerNewSeller(input: $input) {
  212. id
  213. code
  214. token
  215. }
  216. }
  217. `;