@hartl-services/medusa-base 1.1.0 → 2.1.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 (50) hide show
  1. package/.medusa/server/src/admin/index.js +3 -1
  2. package/.medusa/server/src/admin/index.mjs +3 -1
  3. package/.medusa/server/src/api/customer-verification-request-throttle.js +50 -0
  4. package/.medusa/server/src/api/middlewares.js +31 -1
  5. package/.medusa/server/src/api/store/customer-verification/resend/route.js +17 -0
  6. package/.medusa/server/src/documents/utils/document-merchant.js +5 -5
  7. package/.medusa/server/src/email/application/handle-customer-verification-event.js +341 -0
  8. package/.medusa/server/src/email/application/resend-customer-verification.js +65 -0
  9. package/.medusa/server/src/email/application/start-customer-verification-snapshot.js +21 -0
  10. package/.medusa/server/src/email/application/throttle-verification-request.js +49 -0
  11. package/.medusa/server/src/email/customer-verification-snapshot.js +144 -0
  12. package/.medusa/server/src/email/domain/business-keys.js +12 -2
  13. package/.medusa/server/src/email/domain/customer-verification-resend.js +37 -0
  14. package/.medusa/server/src/email/domain/event-schemas.js +18 -2
  15. package/.medusa/server/src/email/plugin-features.js +9 -2
  16. package/.medusa/server/src/email/rendering/default-templates.js +20 -1
  17. package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +5 -1
  18. package/.medusa/server/src/email/types/email-kinds.js +14 -2
  19. package/.medusa/server/src/email/types/template-data.js +9 -1
  20. package/.medusa/server/src/modules/documents/domain/merchant.js +2 -33
  21. package/.medusa/server/src/modules/documents/domain/options.js +1 -7
  22. package/.medusa/server/src/modules/email/migrations/Migration20261001090000.js +16 -0
  23. package/.medusa/server/src/modules/email/service.js +30 -8
  24. package/.medusa/server/src/public/shop-master-data.js +6 -6
  25. package/.medusa/server/src/shop-config/build-food-shop-config.js +17 -6
  26. package/.medusa/server/src/shop-config/options.js +1 -15
  27. package/.medusa/server/src/subscribers/customer-verification.js +23 -0
  28. package/.medusa/server/src/workflows/documents/issue-cancellation-after-refund.js +2 -3
  29. package/.medusa/server/src/workflows/documents/issue-document.js +3 -4
  30. package/.medusa/server/src/workflows/documents/issue-packing-list.js +2 -2
  31. package/.medusa/server/src/workflows/email/dispatch-email-message.js +4 -2
  32. package/README.md +154 -49
  33. package/dist/documents/types/documents.d.ts +0 -22
  34. package/dist/email/customer-verification-snapshot.d.ts +53 -0
  35. package/dist/email/customer-verification-snapshot.js +143 -0
  36. package/dist/email/types/email-kinds.d.ts +9 -1
  37. package/dist/email/types/email-kinds.js +13 -1
  38. package/dist/email/types/shop-configuration.d.ts +8 -0
  39. package/dist/email/types/template-data.d.ts +9 -0
  40. package/dist/email/types/template-data.js +8 -0
  41. package/dist/modules/documents/domain/options.d.ts +1 -9
  42. package/dist/modules/documents/domain/options.js +0 -6
  43. package/dist/public/shop-master-data.d.ts +4 -5
  44. package/dist/public/shop-master-data.js +5 -5
  45. package/dist/shop-config/build-food-shop-config.js +16 -5
  46. package/dist/shop-config/options.d.ts +4 -6
  47. package/dist/shop-config/options.js +0 -14
  48. package/package.json +2 -2
  49. package/dist/modules/documents/domain/merchant.d.ts +0 -22
  50. package/dist/modules/documents/domain/merchant.js +0 -85
@@ -363,6 +363,9 @@ export declare const EmailPersistedDataSchemas: {
363
363
  locale: z.ZodNullable<z.ZodString>;
364
364
  }, z.core.$strict>;
365
365
  readonly "user.invite": z.ZodObject<{}, z.core.$strict>;
366
+ readonly "auth.verification": z.ZodObject<{
367
+ locale: z.ZodNullable<z.ZodString>;
368
+ }, z.core.$strict>;
366
369
  };
367
370
  export declare const EmailRenderDataSchemas: {
368
371
  readonly "auth.password_reset": z.ZodObject<{
@@ -374,6 +377,10 @@ export declare const EmailRenderDataSchemas: {
374
377
  invite_url: z.ZodString;
375
378
  expires_at: z.ZodOptional<z.ZodString>;
376
379
  }, z.core.$strict>;
380
+ readonly "auth.verification": z.ZodObject<{
381
+ locale: z.ZodNullable<z.ZodString>;
382
+ verify_url: z.ZodString;
383
+ }, z.core.$strict>;
377
384
  readonly "order.received": z.ZodObject<{
378
385
  order_id: z.ZodString;
379
386
  summary: z.ZodOptional<z.ZodObject<{
@@ -601,6 +608,8 @@ export type EmailEphemeralRenderContextMap = {
601
608
  }> : Kind extends "user.invite" ? Readonly<{
602
609
  invite_url: string;
603
610
  expires_at?: string;
611
+ }> : Kind extends "auth.verification" ? Readonly<{
612
+ verify_url: string;
604
613
  }> : EmptyRenderContext;
605
614
  };
606
615
  export type EmailRenderDataMap = {
@@ -215,6 +215,11 @@ exports.EmailPersistedDataSchemas = {
215
215
  })
216
216
  .strict(),
217
217
  "user.invite": zod_1.z.object({}).strict(),
218
+ "auth.verification": zod_1.z
219
+ .object({
220
+ locale: NullableLocaleSchema,
221
+ })
222
+ .strict(),
218
223
  };
219
224
  exports.EmailRenderDataSchemas = {
220
225
  ...exports.EmailPersistedDataSchemas,
@@ -228,4 +233,7 @@ exports.EmailRenderDataSchemas = {
228
233
  /** When the invite expires (ISO 8601), from the current invite. */
229
234
  expires_at: zod_1.z.string().datetime().optional(),
230
235
  }),
236
+ "auth.verification": exports.EmailPersistedDataSchemas["auth.verification"].extend({
237
+ verify_url: zod_1.z.string().url(),
238
+ }),
231
239
  };
@@ -1,6 +1,5 @@
1
1
  import type { DocumentHtmlFactory } from "../../../documents/types/html-factory";
2
- import type { DocumentMerchantOption, DocumentPdfOptions } from "../../../documents/types/documents";
3
- import { type DocumentMerchantValues } from "./merchant";
2
+ import type { DocumentPdfOptions } from "../../../documents/types/documents";
4
3
  /** Used when the `documents.numbering.timeZone` option is not given. */
5
4
  export declare const DEFAULT_NUMBERING_TIME_ZONE = "Europe/Vienna";
6
5
  /** The `documents` namespace of the base plugin options. */
@@ -10,11 +9,6 @@ export type DocumentPluginOptions = {
10
9
  * `createDefaultDocumentHtmlFactory` with the `numbering.timeZone`.
11
10
  */
12
11
  htmlFactory?: DocumentHtmlFactory;
13
- /**
14
- * Fallback for the shop master data of the base plugin: used per field
15
- * only where no value is stored there.
16
- */
17
- merchant?: DocumentMerchantOption;
18
12
  numbering?: {
19
13
  timeZone?: string;
20
14
  };
@@ -22,8 +16,6 @@ export type DocumentPluginOptions = {
22
16
  };
23
17
  export type ResolvedDocumentPluginOptions = {
24
18
  htmlFactory: DocumentHtmlFactory;
25
- /** The `merchant` option keyed by shop master data field. */
26
- merchantFallback: DocumentMerchantValues;
27
19
  numbering: {
28
20
  timeZone: string;
29
21
  };
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveDocumentPluginOptions = exports.DEFAULT_NUMBERING_TIME_ZONE = void 0;
4
4
  const utils_1 = require("@medusajs/framework/utils");
5
5
  const html_factory_1 = require("../../../documents/templates/html-factory");
6
- const merchant_1 = require("./merchant");
7
6
  /** Used when the `documents.numbering.timeZone` option is not given. */
8
7
  exports.DEFAULT_NUMBERING_TIME_ZONE = "Europe/Vienna";
9
8
  const resolveDocumentPluginOptions = (options) => {
@@ -24,14 +23,9 @@ const resolveDocumentPluginOptions = (options) => {
24
23
  typeof options.htmlFactory !== "function") {
25
24
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "The documents 'htmlFactory' option must be a function.");
26
25
  }
27
- if (options?.merchant !== undefined &&
28
- (typeof options.merchant !== "object" || options.merchant === null)) {
29
- throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "The documents 'merchant' option must be an object.");
30
- }
31
26
  const executablePath = options?.pdf?.executablePath?.trim() || "google-chrome";
32
27
  return {
33
28
  htmlFactory: options?.htmlFactory ?? (0, html_factory_1.createDefaultDocumentHtmlFactory)({ timeZone }),
34
- merchantFallback: (0, merchant_1.merchantOptionToFallback)(options?.merchant),
35
29
  numbering: { timeZone },
36
30
  pdf: { executablePath },
37
31
  };
@@ -1,5 +1,4 @@
1
1
  import type { MedusaContainer } from "@medusajs/framework/types";
2
- import type { PluginSettingsFallback } from "./types";
3
2
  /**
4
3
  * The base's own descriptor: the shop's master data (Firmenangaben), edited
5
4
  * on the generic plugin page like any plugin's settings. It cannot be
@@ -18,8 +17,8 @@ export type ResolvedShopMasterData = {
18
17
  missing: ShopMasterDataKey[];
19
18
  };
20
19
  /**
21
- * The shop's master data for server-side code. Precedence per key: stored in
22
- * the Admin > `fallback` (e.g. a plugin's own medusa-config option). A
23
- * missing base plugin fails with `UNEXPECTED_STATE`; read failures propagate.
20
+ * The shop's master data for server-side code, exactly as stored in the
21
+ * Admin: there is no config fallback. A missing base plugin fails with
22
+ * `UNEXPECTED_STATE`; read failures propagate.
24
23
  */
25
- export declare function getShopMasterData(container: MedusaContainer, fallback?: PluginSettingsFallback<ShopMasterDataKey>): Promise<ResolvedShopMasterData>;
24
+ export declare function getShopMasterData(container: MedusaContainer): Promise<ResolvedShopMasterData>;
@@ -74,12 +74,12 @@ exports.shopMasterDataDescriptor = (0, descriptor_1.definePluginFeatures)({
74
74
  ],
75
75
  });
76
76
  /**
77
- * The shop's master data for server-side code. Precedence per key: stored in
78
- * the Admin > `fallback` (e.g. a plugin's own medusa-config option). A
79
- * missing base plugin fails with `UNEXPECTED_STATE`; read failures propagate.
77
+ * The shop's master data for server-side code, exactly as stored in the
78
+ * Admin: there is no config fallback. A missing base plugin fails with
79
+ * `UNEXPECTED_STATE`; read failures propagate.
80
80
  */
81
- async function getShopMasterData(container, fallback) {
82
- const resolved = await (0, settings_1.getPluginSettings)(container, exports.shopMasterDataDescriptor, fallback);
81
+ async function getShopMasterData(container) {
82
+ const resolved = await (0, settings_1.getPluginSettings)(container, exports.shopMasterDataDescriptor);
83
83
  const values = {};
84
84
  for (const [key, value] of Object.entries(resolved.values)) {
85
85
  if (typeof value === "string") {
@@ -5,6 +5,7 @@ exports.defineFoodShopConfig = defineFoodShopConfig;
5
5
  const logger_1 = require("@medusajs/framework/logger");
6
6
  const utils_1 = require("@medusajs/framework/utils");
7
7
  const config_1 = require("../public/config");
8
+ const customer_verification_snapshot_1 = require("../email/customer-verification-snapshot");
8
9
  const sentry_logger_1 = require("../public/sentry-logger");
9
10
  const options_1 = require("./options");
10
11
  /**
@@ -18,7 +19,6 @@ const options_1 = require("./options");
18
19
  */
19
20
  function buildFoodShopConfig(options, environment = process.env) {
20
21
  (0, options_1.assertFoodShopConfigOptions)(options);
21
- const { merchant } = options;
22
22
  if (options.loadEnvFile !== false) {
23
23
  (0, utils_1.loadEnv)(process.env.NODE_ENV || "development", process.cwd());
24
24
  }
@@ -157,14 +157,14 @@ function buildFoodShopConfig(options, environment = process.env) {
157
157
  orderNotificationEmail: options.orderNotificationEmail,
158
158
  // The e-mail module appends "#token=<token>".
159
159
  passwordResetUrl: () => (0, config_1.shopUrl)(storefrontOrigin, `${storefrontPath}/reset-password`),
160
+ // Account confirmation (E-Mail toggle `customer_verification`);
161
+ // the e-mail module appends "#token=<token>".
162
+ verifyAccountUrl: () => (0, config_1.shopUrl)(storefrontOrigin, `${storefrontPath}/verify-account`),
160
163
  timeZone: options.timeZone,
161
164
  },
162
165
  },
163
166
  documents: {
164
167
  htmlFactory: options.documentHtmlFactory,
165
- // Fallback only: the shop master data in the Admin ("Hartl Services
166
- // Plugins" -> Shop-Stammdaten) wins per field.
167
- merchant,
168
168
  numbering: {
169
169
  // Chooses the annual document-number period; matches the date the
170
170
  // HTML factory prints.
@@ -179,7 +179,7 @@ function buildFoodShopConfig(options, environment = process.env) {
179
179
  },
180
180
  {
181
181
  resolve: "@hartl-services/medusa-tax-compliance",
182
- options: { merchant },
182
+ options: {},
183
183
  },
184
184
  {
185
185
  resolve: "@hartl-services/medusa-free-shipping",
@@ -231,6 +231,17 @@ function buildFoodShopConfig(options, environment = process.env) {
231
231
  authCors: env.AUTH_CORS,
232
232
  jwtSecret: env.JWT_SECRET,
233
233
  cookieSecret: env.COOKIE_SECRET,
234
+ // Medusa reads this synchronously on every login and keeps the
235
+ // `authVerificationsPerActor` object (defineConfig and ConfigManager
236
+ // copy `http` shallowly), so a getter switches e-mail confirmation of
237
+ // customer accounts at run time. It answers from an in-memory copy of
238
+ // the E-Mail plugin's `customer_verification` toggle, refreshed at
239
+ // most every 30 s (see customer-verification-snapshot.ts).
240
+ authVerificationsPerActor: {
241
+ get customer() {
242
+ return (0, customer_verification_snapshot_1.customerVerificationActors)();
243
+ },
244
+ },
234
245
  },
235
246
  // The storefront turns its customer JWT into a Medusa browser session
236
247
  // at login, so its browser-side cart and payment calls carry the
@@ -1,16 +1,14 @@
1
- import type { DocumentHtmlFactory, DocumentMerchantOption } from "../documents/types";
1
+ import type { DocumentHtmlFactory } from "../documents/types";
2
2
  /**
3
3
  * The shop-specific facts of a food shop. Deployment values and secrets are
4
4
  * not options: they come from the environment (see the README).
5
5
  */
6
6
  export type FoodShopConfigOptions = Readonly<{
7
7
  /**
8
- * Fallback for the shop master data (Admin -> "Hartl Services Plugins" ->
9
- * Shop-Stammdaten) that documents and tax compliance read per field. Its
10
- * `legalName` is also the seeded store name.
8
+ * Shop name in the branding of the default e-mail templates (and the store
9
+ * name of a shop's initial seed). Company name, address and UID are not
10
+ * options: they come only from the Shop-Stammdaten in the Admin.
11
11
  */
12
- merchant: DocumentMerchantOption;
13
- /** Shop name in the branding of the default e-mail templates. */
14
12
  displayName: string;
15
13
  /** Receives the shop's order notification e-mails. */
16
14
  orderNotificationEmail: string;
@@ -45,20 +45,6 @@ const requireObject = (value, name) => {
45
45
  */
46
46
  function assertFoodShopConfigOptions(options) {
47
47
  const candidate = requireObject(options, "options");
48
- const merchant = requireObject(candidate.merchant, "merchant");
49
- requireText(merchant.legalName, "merchant.legalName");
50
- requireCountryCode(merchant.countryCode, "merchant.countryCode");
51
- if (merchant.vatNumber !== undefined) {
52
- requireText(merchant.vatNumber, "merchant.vatNumber");
53
- }
54
- const address = requireObject(merchant.registeredAddress, "merchant.registeredAddress");
55
- requireText(address.address1, "merchant.registeredAddress.address1");
56
- if (address.address2 !== undefined) {
57
- requireText(address.address2, "merchant.registeredAddress.address2");
58
- }
59
- requireText(address.city, "merchant.registeredAddress.city");
60
- requireText(address.postalCode, "merchant.registeredAddress.postalCode");
61
- requireCountryCode(address.countryCode, "merchant.registeredAddress.countryCode");
62
48
  requireText(candidate.displayName, "displayName");
63
49
  requireEmail(candidate.orderNotificationEmail, "orderNotificationEmail");
64
50
  requireTimeZone(candidate.timeZone, "timeZone");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hartl-services/medusa-base",
3
- "version": "1.1.0",
3
+ "version": "2.1.0",
4
4
  "description": "Base plugin for Hartl Services Medusa shops: per-plugin feature toggles with a generic Admin settings page, a Store endpoint for storefronts, enforcement helpers, the complete-cart hook composition, the root redirect, cart-promotion link repair, the built-in E-Mail, Documents and S3 file storage modules, config helpers with the turnkey food-shop preset, and the Sentry logger wrapper and instrumentation.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -88,7 +88,7 @@
88
88
  "mime-types": "3.0.1",
89
89
  "nodemailer": "6.10.1",
90
90
  "ulid": "^2.4.0",
91
- "@hartl-services/medusa-food-supplements-contracts": "^0.17.0"
91
+ "@hartl-services/medusa-food-supplements-contracts": "^0.18.0"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@medusajs/admin-sdk": "2.19.0",
@@ -1,22 +0,0 @@
1
- import type { DocumentMerchant, DocumentMerchantOption } from "../../../documents/types/documents";
2
- /** Shop master data keys (base plugin, `plugin_key: "shop"`) documents read. */
3
- export type DocumentMerchantKey = keyof DocumentMerchant;
4
- export type DocumentMerchantValues = Partial<Record<DocumentMerchantKey, string>>;
5
- /** Without these a financial document or packing list has no legal sender. */
6
- export declare const DOCUMENT_MERCHANT_REQUIRED_FIELDS: readonly ["legal_name", "address_line1", "postal_code", "city", "country_code"];
7
- /** Development placeholder UID: never printed on a document. */
8
- export declare const MERCHANT_VAT_PLACEHOLDER = "ATU00000000";
9
- /**
10
- * The plugin's `merchant` option as a per-field fallback for the shop master
11
- * data. Empty values are left out so they never shadow a declared default.
12
- */
13
- export declare const merchantOptionToFallback: (option: DocumentMerchantOption | undefined) => DocumentMerchantValues;
14
- export type DocumentMerchantResolution = {
15
- merchant: DocumentMerchant;
16
- missing: [];
17
- } | {
18
- merchant: null;
19
- missing: DocumentMerchantKey[];
20
- };
21
- /** Maps resolved shop master data to the render input's `merchant`. */
22
- export declare const toDocumentMerchant: (values: DocumentMerchantValues) => DocumentMerchantResolution;
@@ -1,85 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toDocumentMerchant = exports.merchantOptionToFallback = exports.MERCHANT_VAT_PLACEHOLDER = exports.DOCUMENT_MERCHANT_REQUIRED_FIELDS = void 0;
4
- /** Without these a financial document or packing list has no legal sender. */
5
- exports.DOCUMENT_MERCHANT_REQUIRED_FIELDS = [
6
- "legal_name",
7
- "address_line1",
8
- "postal_code",
9
- "city",
10
- "country_code",
11
- ];
12
- /** Development placeholder UID: never printed on a document. */
13
- exports.MERCHANT_VAT_PLACEHOLDER = "ATU00000000";
14
- const text = (value) => {
15
- if (typeof value !== "string") {
16
- return null;
17
- }
18
- const trimmed = value.trim();
19
- return trimmed ? trimmed : null;
20
- };
21
- const vatNumber = (value) => {
22
- const normalized = text(value);
23
- return normalized &&
24
- normalized.replace(/\s+/g, "").toUpperCase() === exports.MERCHANT_VAT_PLACEHOLDER
25
- ? null
26
- : normalized;
27
- };
28
- /**
29
- * The plugin's `merchant` option as a per-field fallback for the shop master
30
- * data. Empty values are left out so they never shadow a declared default.
31
- */
32
- const merchantOptionToFallback = (option) => {
33
- if (!option) {
34
- return {};
35
- }
36
- const address = option.registeredAddress;
37
- const entries = [
38
- ["legal_name", option.legalName],
39
- ["address_line1", address?.address1],
40
- ["address_line2", address?.address2],
41
- ["postal_code", address?.postalCode],
42
- ["city", address?.city],
43
- [
44
- "country_code",
45
- text(address?.countryCode ?? option.countryCode)?.toLowerCase(),
46
- ],
47
- ["vat_number", option.vatNumber],
48
- ];
49
- const fallback = {};
50
- for (const [key, value] of entries) {
51
- const normalized = text(value);
52
- if (normalized) {
53
- fallback[key] = normalized;
54
- }
55
- }
56
- return fallback;
57
- };
58
- exports.merchantOptionToFallback = merchantOptionToFallback;
59
- /** Maps resolved shop master data to the render input's `merchant`. */
60
- const toDocumentMerchant = (values) => {
61
- const missing = exports.DOCUMENT_MERCHANT_REQUIRED_FIELDS.filter((key) => !text(values[key]));
62
- if (missing.length > 0) {
63
- return { merchant: null, missing };
64
- }
65
- return {
66
- merchant: {
67
- legal_name: text(values.legal_name),
68
- address_line1: text(values.address_line1),
69
- address_line2: text(values.address_line2),
70
- postal_code: text(values.postal_code),
71
- city: text(values.city),
72
- country_code: text(values.country_code).toLowerCase(),
73
- vat_number: vatNumber(values.vat_number),
74
- email: text(values.email),
75
- phone: text(values.phone),
76
- website: text(values.website),
77
- iban: text(values.iban),
78
- bic: text(values.bic),
79
- company_register_number: text(values.company_register_number),
80
- company_register_court: text(values.company_register_court),
81
- },
82
- missing: [],
83
- };
84
- };
85
- exports.toDocumentMerchant = toDocumentMerchant;