@stamhoofd/backend 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/.env.template.json +63 -0
  2. package/.eslintrc.js +61 -0
  3. package/README.md +40 -0
  4. package/index.ts +172 -0
  5. package/jest.config.js +11 -0
  6. package/migrations.ts +33 -0
  7. package/package.json +48 -0
  8. package/src/crons.ts +845 -0
  9. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +42 -0
  10. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +320 -0
  11. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +171 -0
  12. package/src/endpoints/auth/CreateAdminEndpoint.ts +137 -0
  13. package/src/endpoints/auth/CreateTokenEndpoint.test.ts +68 -0
  14. package/src/endpoints/auth/CreateTokenEndpoint.ts +200 -0
  15. package/src/endpoints/auth/DeleteTokenEndpoint.ts +31 -0
  16. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +70 -0
  17. package/src/endpoints/auth/GetUserEndpoint.test.ts +64 -0
  18. package/src/endpoints/auth/GetUserEndpoint.ts +57 -0
  19. package/src/endpoints/auth/PatchApiUserEndpoint.ts +90 -0
  20. package/src/endpoints/auth/PatchUserEndpoint.ts +122 -0
  21. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +37 -0
  22. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +41 -0
  23. package/src/endpoints/auth/SignupEndpoint.ts +107 -0
  24. package/src/endpoints/auth/VerifyEmailEndpoint.ts +89 -0
  25. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +95 -0
  26. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +31 -0
  27. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +101 -0
  28. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +53 -0
  29. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +57 -0
  30. package/src/endpoints/global/files/UploadFile.ts +147 -0
  31. package/src/endpoints/global/files/UploadImage.ts +119 -0
  32. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +76 -0
  33. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +43 -0
  34. package/src/endpoints/global/members/GetMembersEndpoint.ts +429 -0
  35. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +734 -0
  36. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +45 -0
  37. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +105 -0
  38. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +146 -0
  39. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +52 -0
  40. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +80 -0
  41. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +49 -0
  42. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +58 -0
  43. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +62 -0
  44. package/src/endpoints/global/payments/ExchangeSTPaymentEndpoint.ts +153 -0
  45. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +134 -0
  46. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +44 -0
  47. package/src/endpoints/global/platform/GetPlatformEnpoint.ts +39 -0
  48. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +63 -0
  49. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +68 -0
  50. package/src/endpoints/global/registration/GetUserBalanceEndpoint.ts +39 -0
  51. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +80 -0
  52. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +41 -0
  53. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +134 -0
  54. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +521 -0
  55. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +37 -0
  56. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +115 -0
  57. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +187 -0
  58. package/src/endpoints/organization/dashboard/billing/ActivatePackagesEndpoint.ts +424 -0
  59. package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +67 -0
  60. package/src/endpoints/organization/dashboard/billing/GetBillingStatusEndpoint.ts +39 -0
  61. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +57 -0
  62. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +50 -0
  63. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +50 -0
  64. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +129 -0
  65. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +114 -0
  66. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +50 -0
  67. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +234 -0
  68. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +62 -0
  69. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +85 -0
  70. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +80 -0
  71. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +54 -0
  72. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +49 -0
  73. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +63 -0
  74. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +61 -0
  75. package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.test.ts +64 -0
  76. package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.ts +84 -0
  77. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +43 -0
  78. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +42 -0
  79. package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +43 -0
  80. package/src/endpoints/organization/dashboard/organization/GetRegisterCodeEndpoint.ts +65 -0
  81. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +281 -0
  82. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +338 -0
  83. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +196 -0
  84. package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +50 -0
  85. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +48 -0
  86. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +207 -0
  87. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +202 -0
  88. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +233 -0
  89. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +66 -0
  90. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +210 -0
  91. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +93 -0
  92. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +59 -0
  93. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +78 -0
  94. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +40 -0
  95. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +69 -0
  96. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +52 -0
  97. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +73 -0
  98. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +60 -0
  99. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +47 -0
  100. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +41 -0
  101. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +217 -0
  102. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +51 -0
  103. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +47 -0
  104. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +83 -0
  105. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +68 -0
  106. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +69 -0
  107. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +125 -0
  108. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +204 -0
  109. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +278 -0
  110. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +80 -0
  111. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +60 -0
  112. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +379 -0
  113. package/src/endpoints/organization/shared/GetDocumentHtml.ts +54 -0
  114. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +45 -0
  115. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +78 -0
  116. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +34 -0
  117. package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +44 -0
  118. package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +82 -0
  119. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +59 -0
  120. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +51 -0
  121. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +40 -0
  122. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +124 -0
  123. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +130 -0
  124. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +50 -0
  125. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +450 -0
  126. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +335 -0
  127. package/src/helpers/AddressValidator.test.ts +40 -0
  128. package/src/helpers/AddressValidator.ts +256 -0
  129. package/src/helpers/AdminPermissionChecker.ts +1031 -0
  130. package/src/helpers/AuthenticatedStructures.ts +158 -0
  131. package/src/helpers/BuckarooHelper.ts +279 -0
  132. package/src/helpers/CheckSettlements.ts +215 -0
  133. package/src/helpers/Context.ts +202 -0
  134. package/src/helpers/CookieHelper.ts +45 -0
  135. package/src/helpers/ForwardHandler.test.ts +216 -0
  136. package/src/helpers/ForwardHandler.ts +140 -0
  137. package/src/helpers/OpenIDConnectHelper.ts +284 -0
  138. package/src/helpers/StripeHelper.ts +293 -0
  139. package/src/helpers/StripePayoutChecker.ts +188 -0
  140. package/src/middleware/ContextMiddleware.ts +16 -0
  141. package/src/migrations/1646578856-validate-addresses.ts +60 -0
  142. package/src/seeds/0000000000-example.ts +13 -0
  143. package/src/seeds/1715028563-user-permissions.ts +52 -0
  144. package/tests/e2e/stock.test.ts +2120 -0
  145. package/tests/e2e/tickets.test.ts +926 -0
  146. package/tests/helpers/StripeMocker.ts +362 -0
  147. package/tests/helpers/TestServer.ts +21 -0
  148. package/tests/jest.global.setup.ts +29 -0
  149. package/tests/jest.setup.ts +59 -0
  150. package/tsconfig.json +42 -0
@@ -0,0 +1,50 @@
1
+ import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
+ import { SimpleError } from '@simonbackx/simple-errors';
3
+ import { Webshop } from '@stamhoofd/models';
4
+ import { PrivateWebshop, Webshop as WebshopStruct } from "@stamhoofd/structures";
5
+
6
+ import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
7
+ import { Context } from "../../../helpers/Context";
8
+
9
+ type Params = { id: string };
10
+ type Query = undefined;
11
+ type Body = undefined
12
+ type ResponseBody = PrivateWebshop | WebshopStruct;
13
+
14
+ export class GetWebshopEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
15
+ protected doesMatch(request: Request): [true, Params] | [false] {
16
+ if (request.method != "GET") {
17
+ return [false];
18
+ }
19
+
20
+ const params = Endpoint.parseParameters(request.url, "/webshop/@id", { id: String });
21
+
22
+ if (params) {
23
+ return [true, params as Params];
24
+ }
25
+ return [false];
26
+ }
27
+
28
+ async handle(request: DecodedRequest<Params, Query, Body>) {
29
+ if (Context.version < 244) {
30
+ await Context.setOptionalOrganizationScope();
31
+ } else {
32
+ await Context.setOrganizationScope();
33
+ }
34
+
35
+ await Context.optionalAuthenticate()
36
+
37
+ const webshop = await Webshop.getByID(request.params.id)
38
+ if (!webshop) {
39
+ throw new SimpleError({
40
+ code: "not_found",
41
+ message: "Webshop not found",
42
+ human: "Deze webshop bestaat niet (meer)"
43
+ })
44
+ }
45
+
46
+ return new Response(
47
+ await AuthenticatedStructures.webshop(webshop)
48
+ );
49
+ }
50
+ }
@@ -0,0 +1,450 @@
1
+ /* eslint-disable jest/expect-expect */
2
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
3
+ /* eslint-disable jest/no-standalone-expect */
4
+ import { Request, Response } from "@simonbackx/simple-endpoints";
5
+ import { Organization, OrganizationFactory, StripeAccount, Token, User,UserFactory, Webshop, WebshopFactory } from "@stamhoofd/models";
6
+ import { Address, Cart, CartItem, CartItemOption, Country, Customer, Option, OptionMenu, OrderData, OrderResponse, PaymentConfiguration, PaymentMethod, PermissionLevel, Permissions, PrivatePaymentConfiguration, Product, ProductPrice, ProductType, SeatingPlan, SeatingPlanRow, SeatingPlanSeat, SeatingPlanSection, TransferSettings, WebshopDeliveryMethod, WebshopMetaData, WebshopOnSiteMethod, WebshopPrivateMetaData, WebshopTakeoutMethod, WebshopTimeSlot } from "@stamhoofd/structures";
7
+ import sinon from 'sinon';
8
+
9
+ import { StripeMocker } from "../../../../tests/helpers/StripeMocker";
10
+ import { testServer } from "../../../../tests/helpers/TestServer";
11
+ import { PatchWebshopOrdersEndpoint } from "../dashboard/webshops/PatchWebshopOrdersEndpoint";
12
+ import { PlaceOrderEndpoint } from './PlaceOrderEndpoint';
13
+
14
+ const address = Address.create({
15
+ street: 'Demostraat',
16
+ number: '15',
17
+ postalCode: '9000',
18
+ city: 'Gent',
19
+ country: Country.Belgium
20
+ })
21
+
22
+ const customer = Customer.create({
23
+ firstName: 'John',
24
+ lastName: 'Doe',
25
+ email: 'john@example.com',
26
+ phone: '+32412345678'
27
+ });
28
+
29
+ describe("Endpoint.PlaceOrderEndpoint", () => {
30
+ // Test endpoint
31
+ const endpoint = new PlaceOrderEndpoint();
32
+ const patchWebshopOrdersEndpoint = new PatchWebshopOrdersEndpoint();
33
+
34
+ let organization: Organization;
35
+ let webshop: Webshop;
36
+ let product: Product;
37
+ let seatProduct: Product;
38
+ let personProduct: Product;
39
+ let takeoutMethod: WebshopTakeoutMethod;
40
+ let deliveryMethod: WebshopDeliveryMethod;
41
+ let onSiteMethod: WebshopOnSiteMethod;
42
+ let slot1: WebshopTimeSlot;
43
+ let slot2: WebshopTimeSlot;
44
+ let slot3: WebshopTimeSlot;
45
+ let slot4: WebshopTimeSlot;
46
+
47
+ let productPrice1: ProductPrice;
48
+ let productPrice2: ProductPrice;
49
+ let freeProductPrice: ProductPrice;
50
+ let personProductPrice: ProductPrice;
51
+ let seatProductPrice: ProductPrice;
52
+ let seatingPlan: SeatingPlan;
53
+
54
+ let multipleChoiceOptionMenu: OptionMenu;
55
+ let chooseOneOptionMenu: OptionMenu;
56
+ let checkboxOption1: Option;
57
+ let checkboxOption2: Option;
58
+ let radioOption1: Option;
59
+ let radioOption2: Option;
60
+ let stripeMocker: StripeMocker
61
+ let stripeAccount: StripeAccount
62
+ let token: Token;
63
+
64
+ async function refreshAll() {
65
+ webshop = (await Webshop.getByID(webshop.id))!;
66
+ product = webshop.products.find(p => p.id == product.id)!;
67
+ seatProduct = webshop.products.find(p => p.id == seatProduct.id)!;
68
+ personProduct = webshop.products.find(p => p.id == personProduct.id)!;
69
+ takeoutMethod = webshop.meta.checkoutMethods.find(m => m.id == takeoutMethod.id)! as WebshopTakeoutMethod;
70
+ deliveryMethod = webshop.meta.checkoutMethods.find(m => m.id == deliveryMethod.id)! as WebshopDeliveryMethod;
71
+ onSiteMethod = webshop.meta.checkoutMethods.find(m => m.id == onSiteMethod.id)! as WebshopOnSiteMethod;
72
+ slot1 = takeoutMethod.timeSlots.timeSlots.find(s => s.id == slot1.id)!;
73
+ slot2 = takeoutMethod.timeSlots.timeSlots.find(s => s.id == slot2.id)!;
74
+ slot3 = deliveryMethod.timeSlots.timeSlots.find(s => s.id == slot3.id)!;
75
+ slot4 = onSiteMethod.timeSlots.timeSlots.find(s => s.id == slot4.id)!;
76
+ productPrice1 = product.prices.find(p => p.id == productPrice1.id)!;
77
+ productPrice2 = product.prices.find(p => p.id == productPrice2.id)!;
78
+ freeProductPrice = product.prices.find(p => p.id == freeProductPrice.id)!;
79
+ multipleChoiceOptionMenu = product.optionMenus.find(m => m.id == multipleChoiceOptionMenu.id)!;
80
+ chooseOneOptionMenu = product.optionMenus.find(m => m.id == chooseOneOptionMenu.id)!;
81
+ checkboxOption1 = multipleChoiceOptionMenu.options.find(o => o.id == checkboxOption1.id)!;
82
+ checkboxOption2 = multipleChoiceOptionMenu.options.find(o => o.id == checkboxOption2.id)!;
83
+ radioOption1 = chooseOneOptionMenu.options.find(o => o.id == radioOption1.id)!;
84
+ radioOption2 = chooseOneOptionMenu.options.find(o => o.id == radioOption2.id)!;
85
+ personProductPrice = personProduct.prices.find(p => p.id == personProductPrice.id)!;
86
+ seatProductPrice = seatProduct.prices.find(p => p.id == seatProductPrice.id)!;
87
+ seatingPlan = webshop.meta.seatingPlans.find(s => s.id === seatingPlan.id)!;
88
+ }
89
+
90
+ /** Allows to change the stock */
91
+ async function saveChanges() {
92
+ // Set products
93
+ webshop = (await Webshop.getByID(webshop.id))!;
94
+ webshop.products = [product, seatProduct, personProduct]
95
+ await webshop.save();
96
+ await refreshAll();
97
+ }
98
+
99
+ beforeAll(async () => {
100
+ stripeMocker = new StripeMocker();
101
+ stripeMocker.start();
102
+ organization = await new OrganizationFactory({}).create()
103
+ stripeAccount = await stripeMocker.createStripeAccount(organization.id);
104
+
105
+ const user = await new UserFactory({
106
+ organization,
107
+ permissions: Permissions.create({
108
+ level: PermissionLevel.Full
109
+ })
110
+ }).create()
111
+ token = await Token.createToken(user)
112
+ });
113
+
114
+ afterAll(() => {
115
+ stripeMocker.stop();
116
+ });
117
+
118
+ afterEach(() => {
119
+ sinon.restore();
120
+ });
121
+
122
+ beforeEach(async () => {
123
+ stripeMocker.reset();
124
+ let meta = WebshopMetaData.patch({});
125
+
126
+ productPrice1 = ProductPrice.create({
127
+ name: 'productPrice1',
128
+ price: 100,
129
+ stock: 100
130
+ })
131
+
132
+ productPrice2 = ProductPrice.create({
133
+ name: 'productPrice2',
134
+ price: 150,
135
+ stock: 100
136
+ })
137
+
138
+ freeProductPrice = ProductPrice.create({
139
+ name: 'freeProductPrice',
140
+ price: 0,
141
+ stock: 100
142
+ })
143
+
144
+ checkboxOption1 = Option.create({
145
+ name: 'checkboxOption1',
146
+ price: 10,
147
+ stock: 100
148
+ })
149
+
150
+ checkboxOption2 = Option.create({
151
+ name: 'checkboxOption2',
152
+ price: 0,
153
+ stock: 100
154
+ })
155
+
156
+ radioOption1 = Option.create({
157
+ name: 'radioOption1',
158
+ price: 10,
159
+ stock: 100
160
+ })
161
+
162
+ radioOption2 = Option.create({
163
+ name: 'radioOption2',
164
+ price: 0,
165
+ stock: 100
166
+ })
167
+
168
+ multipleChoiceOptionMenu = OptionMenu.create({
169
+ name: 'multipleChoiceOptionMenu',
170
+ multipleChoice: true,
171
+ options: [checkboxOption1, checkboxOption2]
172
+ })
173
+
174
+ chooseOneOptionMenu = OptionMenu.create({
175
+ name: 'chooseOneOptionMenu',
176
+ multipleChoice: false,
177
+ options: [radioOption1, radioOption2]
178
+ })
179
+
180
+ product = Product.create({
181
+ name: 'product',
182
+ stock: 100,
183
+ prices: [productPrice1, productPrice2, freeProductPrice],
184
+ optionMenus: [multipleChoiceOptionMenu, chooseOneOptionMenu]
185
+ })
186
+
187
+ personProduct = Product.create({
188
+ name: 'personProduct',
189
+ type: ProductType.Person,
190
+ stock: 100
191
+ })
192
+ personProductPrice = personProduct.prices[0]
193
+
194
+ seatingPlan = SeatingPlan.create({
195
+ name: 'Testzaal',
196
+ sections: [
197
+ SeatingPlanSection.create({
198
+ rows: [
199
+ SeatingPlanRow.create({
200
+ label: 'A',
201
+ seats: [
202
+ SeatingPlanSeat.create({
203
+ label: '1'
204
+ }),
205
+ SeatingPlanSeat.create({
206
+ label: '2'
207
+ }),
208
+ SeatingPlanSeat.create({
209
+ label: '3'
210
+ }),
211
+ SeatingPlanSeat.create({
212
+ label: '4'
213
+ })
214
+ ]
215
+ }),
216
+ SeatingPlanRow.create({
217
+ label: 'B',
218
+ seats: [
219
+ SeatingPlanSeat.create({
220
+ label: '1'
221
+ }),
222
+ SeatingPlanSeat.create({
223
+ label: '2'
224
+ }),
225
+ SeatingPlanSeat.create({
226
+ label: '3'
227
+ }),
228
+ SeatingPlanSeat.create({
229
+ label: '4'
230
+ })
231
+ ]
232
+ })
233
+ ]
234
+ })
235
+ ]
236
+ })
237
+ meta.seatingPlans.addPut(seatingPlan)
238
+
239
+ seatProduct = Product.create({
240
+ name: 'seatProduct',
241
+ type: ProductType.Ticket,
242
+ seatingPlanId: seatingPlan.id
243
+ })
244
+ seatProductPrice = seatProduct.prices[0]
245
+
246
+ // Takeout
247
+ takeoutMethod = WebshopTakeoutMethod.create({
248
+ name: 'Bakkerij Test',
249
+ address
250
+ })
251
+
252
+ slot1 = WebshopTimeSlot.create({
253
+ date: new Date(),
254
+ maxPersons: 100,
255
+ maxOrders: 100
256
+ });
257
+ takeoutMethod.timeSlots.timeSlots.push(slot1)
258
+
259
+ slot2 = WebshopTimeSlot.create({
260
+ date: new Date(),
261
+ maxPersons: 100,
262
+ maxOrders: 100,
263
+ startTime: 14*60,
264
+ endTime: 15*60
265
+ })
266
+ takeoutMethod.timeSlots.timeSlots.push(slot2)
267
+ meta.checkoutMethods.addPut(takeoutMethod)
268
+
269
+
270
+ // Delivery
271
+ deliveryMethod = WebshopDeliveryMethod.create({
272
+ name: 'Delivery',
273
+ countries: [Country.Belgium]
274
+ })
275
+
276
+ slot3 = WebshopTimeSlot.create({
277
+ date: new Date(),
278
+ maxPersons: 100,
279
+ maxOrders: 100
280
+ });
281
+
282
+ deliveryMethod.timeSlots.timeSlots.push(slot3)
283
+ meta.checkoutMethods.addPut(deliveryMethod)
284
+
285
+ // OnSite
286
+ onSiteMethod = WebshopOnSiteMethod.create({
287
+ name: 'Onsite',
288
+ address
289
+ })
290
+
291
+ slot4 = WebshopTimeSlot.create({
292
+ date: new Date(),
293
+ maxPersons: 100,
294
+ maxOrders: 100
295
+ });
296
+
297
+ onSiteMethod.timeSlots.timeSlots.push(slot4)
298
+ meta.checkoutMethods.addPut(onSiteMethod)
299
+
300
+ const paymentConfigurationPatch = PaymentConfiguration.patch({
301
+ transferSettings: TransferSettings.create({
302
+ iban: 'BE56587127952688' // = random IBAN
303
+ }),
304
+ })
305
+ paymentConfigurationPatch.paymentMethods.addPut(PaymentMethod.PointOfSale)
306
+ paymentConfigurationPatch.paymentMethods.addPut(PaymentMethod.Transfer)
307
+ paymentConfigurationPatch.paymentMethods.addPut(PaymentMethod.Bancontact)
308
+
309
+ const privatePaymentConfiguration = PrivatePaymentConfiguration.patch({
310
+ stripeAccountId: stripeAccount.id
311
+ })
312
+
313
+ meta = meta.patch({
314
+ paymentConfiguration: paymentConfigurationPatch
315
+ })
316
+
317
+ const privateMeta = WebshopPrivateMetaData.patch({
318
+ paymentConfiguration: privatePaymentConfiguration
319
+ })
320
+
321
+ webshop = await new WebshopFactory({
322
+ organizationId: organization.id,
323
+ name: 'Test webshop',
324
+ meta,
325
+ privateMeta,
326
+ products: [product, seatProduct, personProduct]
327
+ }).create()
328
+ });
329
+
330
+ describe('User authentication', () => {
331
+ test("Placing an order with a signed in user overrides the order customer", async () => {
332
+ const user = await new UserFactory({ organization, firstName: 'User', lastName: 'Tester' }).create()
333
+ const token = await Token.createToken(user)
334
+
335
+ const orderData = OrderData.create({
336
+ paymentMethod: PaymentMethod.Unknown,
337
+ checkoutMethod: onSiteMethod,
338
+ timeSlot: slot4,
339
+ cart: Cart.create({
340
+ items: [
341
+ CartItem.create({
342
+ product,
343
+ productPrice: freeProductPrice,
344
+ amount: 5,
345
+ options: [
346
+ CartItemOption.create({
347
+ optionMenu: multipleChoiceOptionMenu,
348
+ option: checkboxOption2
349
+ }),
350
+ CartItemOption.create({
351
+ optionMenu: chooseOneOptionMenu,
352
+ option: radioOption2
353
+ })
354
+ ]
355
+ })
356
+ ]
357
+ }),
358
+ customer
359
+ })
360
+
361
+ const r = Request.buildJson("POST", `/webshop/${webshop.id}/order`, organization.getApiHost(), orderData);
362
+ r.headers.authorization = "Bearer " + token.accessToken
363
+
364
+ const response = await testServer.test(endpoint, r);
365
+ expect(response.body).toBeDefined();
366
+ const order = response.body.order;
367
+
368
+ expect(order.data.customer.email).toEqual(user.email);
369
+ expect(order.data.customer.firstName).toEqual(user.firstName);
370
+ expect(order.data.customer.lastName).toEqual(user.lastName);
371
+ });
372
+
373
+ /**
374
+ * This checks if the authenticated context is passed when multiple orders are executed in a queue
375
+ */
376
+ test("Authenticated context is guarded with race conditions", async () => {
377
+ const orderData = OrderData.create({
378
+ paymentMethod: PaymentMethod.Unknown,
379
+ checkoutMethod: onSiteMethod,
380
+ timeSlot: slot4,
381
+ cart: Cart.create({
382
+ items: [
383
+ CartItem.create({
384
+ product,
385
+ productPrice: freeProductPrice,
386
+ amount: 5,
387
+ options: [
388
+ CartItemOption.create({
389
+ optionMenu: multipleChoiceOptionMenu,
390
+ option: checkboxOption2
391
+ }),
392
+ CartItemOption.create({
393
+ optionMenu: chooseOneOptionMenu,
394
+ option: radioOption2
395
+ })
396
+ ]
397
+ })
398
+ ]
399
+ }),
400
+ customer
401
+ })
402
+
403
+ let counter = 0;
404
+ let resolve: null | (() => void) = null;
405
+ const hangingPromise = new Promise<void>((r) => {
406
+ resolve = r;
407
+ });
408
+
409
+ const stub = sinon.stub(Webshop, 'getByID').callsFake(async function (...args) {
410
+ counter += 1;
411
+
412
+ if (counter === 1) {
413
+ setTimeout(() => {
414
+ // all waiting
415
+ resolve!();
416
+ }, 1000);
417
+ }
418
+
419
+ await hangingPromise;
420
+ return stub.wrappedMethod.apply(this, args);
421
+ });
422
+
423
+ const pendingResponses: {promise: Promise<Response<OrderResponse>>, user: User}[] = []
424
+ for (let i = 0; i < 10; i++) {
425
+ const user = await new UserFactory({ organization, firstName: 'User', lastName: 'Tester' }).create()
426
+ const token = await Token.createToken(user)
427
+
428
+ const r = Request.buildJson("POST", `/webshop/${webshop.id}/order`, organization.getApiHost(), orderData);
429
+ r.headers.authorization = "Bearer " + token.accessToken
430
+
431
+ pendingResponses.push({
432
+ promise: testServer.test(endpoint, r),
433
+ user
434
+ });
435
+ }
436
+
437
+ const responses = await Promise.all(pendingResponses.map(r => r.promise));
438
+
439
+ for (const [index, response] of responses.entries()) {
440
+ const user = pendingResponses[index].user;
441
+ expect(response.body).toBeDefined();
442
+ const order = response.body.order;
443
+
444
+ expect(order.data.customer.email).toEqual(user.email);
445
+ expect(order.data.customer.firstName).toEqual(user.firstName);
446
+ expect(order.data.customer.lastName).toEqual(user.lastName);
447
+ }
448
+ });
449
+ });
450
+ });