@hartl-services/medusa-base 4.0.0 → 5.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.
- package/.medusa/server/src/admin/index.js +8 -8
- package/.medusa/server/src/admin/index.mjs +8 -8
- package/.medusa/server/src/free-shipping/free-shipping-settings.js +38 -0
- package/.medusa/server/src/free-shipping/plugin-features.js +45 -0
- package/.medusa/server/src/jobs/free-shipping-promotion-sync.js +22 -0
- package/.medusa/server/src/modules/free-shipping/index.js +13 -0
- package/.medusa/server/src/modules/free-shipping/migrations/Migration20260911114515.js +16 -0
- package/.medusa/server/src/modules/free-shipping/migrations/Migration20260928201511.js +19 -0
- package/.medusa/server/src/modules/free-shipping/migrations/Migration20260928211756.js +110 -0
- package/.medusa/server/src/modules/free-shipping/service.js +17 -0
- package/.medusa/server/src/shop-config/build-shop-config.js +5 -9
- package/.medusa/server/src/subscribers/free-shipping-plugin-features-updated.js +25 -0
- package/.medusa/server/src/subscribers/free-shipping-plugin-settings-updated.js +26 -0
- package/.medusa/server/src/workflows/free-shipping/sync-free-shipping-promotions.js +143 -0
- package/.medusa/server/src/workflows/hooks/set-free-shipping-promotion-context.js +86 -0
- package/README.md +712 -530
- package/dist/shop-config/build-shop-config.d.ts +2 -2
- package/dist/shop-config/build-shop-config.js +4 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,70 +1,123 @@
|
|
|
1
1
|
# Medusa Base
|
|
2
2
|
|
|
3
|
-
Medusa v2.19 base plugin for Hartl Services shops. Every shop installs it
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
composition
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
3
|
+
Medusa v2.19 base plugin for Hartl Services shops. Every shop installs it,
|
|
4
|
+
first in its plugin list, because the other Hartl Services plugins read their
|
|
5
|
+
feature toggles through it: it provides per-plugin feature toggles, runtime
|
|
6
|
+
settings and actions, a generic Admin hub under **Settings → Hartl Services
|
|
7
|
+
Plugins** with one page per plugin, a Store endpoint for storefronts and
|
|
8
|
+
helpers that plugins use to enforce their toggles and read their settings.
|
|
9
|
+
Since 1.0.0 it also contains the shop's transactional E-Mail, order Documents
|
|
10
|
+
(invoices, cancellations, packing lists), S3 file storage and the turnkey
|
|
11
|
+
`medusa-config.ts` preset, since 3.0.0 the EU tax compliance (UID check, Tax
|
|
12
|
+
provider) and since 5.0.0 the free-shipping thresholds. The "Overview" lists
|
|
13
|
+
every area with its section; "Upgrading" collects the version notes.
|
|
14
|
+
|
|
15
|
+
## Table of contents
|
|
16
|
+
|
|
17
|
+
- [Overview](#overview)
|
|
18
|
+
- [Installation in a shop](#installation-in-a-shop)
|
|
19
|
+
- [Upgrading](#upgrading)
|
|
20
|
+
- [Upgrading to 0.4.0](#upgrading-to-040)
|
|
21
|
+
- [Upgrading to 0.4.1](#upgrading-to-041)
|
|
22
|
+
- [Upgrading to 1.0.0](#upgrading-to-100)
|
|
23
|
+
- [Upgrading to 3.0.0](#upgrading-to-300)
|
|
24
|
+
- [Upgrading to 4.0.0](#upgrading-to-400)
|
|
25
|
+
- [Upgrading to 5.0.0](#upgrading-to-500)
|
|
26
|
+
- [Shop-Config-Preset](#shop-config-preset)
|
|
27
|
+
- [Installing the preset](#installing-the-preset)
|
|
28
|
+
- [Usage](#usage)
|
|
29
|
+
- [API](#api)
|
|
30
|
+
- [Options](#options)
|
|
31
|
+
- [Environment variables](#environment-variables)
|
|
32
|
+
- [Fixed configuration](#fixed-configuration)
|
|
33
|
+
- [Plugin platform](#plugin-platform)
|
|
34
|
+
- [HTTP endpoints](#http-endpoints)
|
|
35
|
+
- [Declaring features in a plugin](#declaring-features-in-a-plugin)
|
|
36
|
+
- [Runtime settings](#runtime-settings)
|
|
37
|
+
- [Actions](#actions)
|
|
38
|
+
- [Shop-Stammdaten](#shop-stammdaten)
|
|
39
|
+
- [Admin pages](#admin-pages)
|
|
40
|
+
- [Semantics](#semantics)
|
|
41
|
+
- [Complete-cart hook composition](#complete-cart-hook-composition)
|
|
42
|
+
- [Built-in features](#built-in-features)
|
|
43
|
+
- [Kunden-Login](#kunden-login)
|
|
44
|
+
- [E-Mail](#e-mail)
|
|
45
|
+
- [Dokumente](#dokumente)
|
|
46
|
+
- [Steuer-Compliance](#steuer-compliance)
|
|
47
|
+
- [Versandkostenfrei](#versandkostenfrei)
|
|
48
|
+
- [Dateispeicher (S3)](#dateispeicher-s3)
|
|
49
|
+
- [Shop behaviour and operations](#shop-behaviour-and-operations)
|
|
50
|
+
- [Root redirect](#root-redirect)
|
|
51
|
+
- [Cart-promotion link repair](#cart-promotion-link-repair)
|
|
52
|
+
- [Draft orders and 0 € orders](#draft-orders-and-0--orders)
|
|
53
|
+
- [Password policy](#password-policy)
|
|
54
|
+
- [Config helpers](#config-helpers)
|
|
55
|
+
- [Sentry logger wrapper](#sentry-logger-wrapper)
|
|
56
|
+
- [Sentry instrumentation](#sentry-instrumentation)
|
|
57
|
+
- [Development](#development)
|
|
58
|
+
- [Local e-mail development](#local-e-mail-development)
|
|
59
|
+
- [Build output of `/admin-components`](#build-output-of-admin-components)
|
|
60
|
+
|
|
61
|
+
## Overview
|
|
62
|
+
|
|
63
|
+
- **Feature toggles** (see "Plugin platform"): discovers every registered
|
|
64
|
+
module whose service declares `pluginFeatures`, so no plugin has to register
|
|
65
|
+
itself anywhere else; stores one row per plugin in `plugin_feature_state`
|
|
66
|
+
(module `plugin_features`) with the plugin switch, the configured feature
|
|
67
|
+
values and a `version` for optimistic concurrency; serves the effective
|
|
68
|
+
values to storefronts and the configured plus effective values to the Admin
|
|
69
|
+
(see "HTTP endpoints"); exports `definePluginFeatures`,
|
|
70
|
+
`requirePluginFeature` and `isPluginFeatureEnabled` from
|
|
71
|
+
`@hartl-services/medusa-base` for plugins (see "Declaring features in a
|
|
72
|
+
plugin") and emits `plugin-features.updated.v1` after every saved Admin
|
|
73
|
+
toggle update (see "Toggle-change event").
|
|
74
|
+
- **Runtime settings**: stores runtime settings that plugins declare (secrets
|
|
75
|
+
encrypted), resolves them with the medusa-config fallback and exports
|
|
76
|
+
`getPluginSettings` (see "Runtime settings").
|
|
77
|
+
- **Actions**: runs actions that plugins declare as workflow ids, records every
|
|
78
|
+
run with its progress and outcome and rejects a second start while one runs
|
|
79
|
+
(see "Actions").
|
|
80
|
+
- **Shop-Stammdaten**: holds the shop's master data (Firmenangaben) as its own
|
|
81
|
+
settings and exports `getShopMasterData` (see "Shop-Stammdaten").
|
|
82
|
+
- **Admin components and server helpers**: exports Admin components from
|
|
83
|
+
`@hartl-services/medusa-base/admin-components` (also `sdk`,
|
|
84
|
+
`getErrorMessage` and the rich-text editor) and server helpers from
|
|
85
|
+
`@hartl-services/medusa-base/lib` (`toWireResponse`) so a plugin composes
|
|
86
|
+
the base's sections into its own page and hides widgets of disabled
|
|
87
|
+
features (see "Admin pages" and "Admin components for plugin pages").
|
|
88
|
+
- **Complete-cart hooks**: owns the `completeCartWorkflow` hooks `validate`
|
|
89
|
+
and `orderCreated` and runs the workflows that plugins declare for them (see
|
|
90
|
+
"Complete-cart hook composition").
|
|
91
|
+
- **Kunden-Login**: declares which login methods customers may use (module
|
|
92
|
+
`customer_auth`: e-mail + password and a one-time code by e-mail), provides
|
|
93
|
+
the `email-otp` auth provider and the code request route (see
|
|
94
|
+
"Kunden-Login"); requires passwords of at least 15 characters for Admin
|
|
95
|
+
users and, by default, customers, and lets shops set their own customer
|
|
96
|
+
rules (see "Password policy").
|
|
97
|
+
- **E-Mail**: sends the shop's transactional e-mails over SMTP or Microsoft
|
|
98
|
+
365, chosen in the Admin (module `email`, see "E-Mail").
|
|
99
|
+
- **Dokumente**: issues invoices, cancellation documents and packing lists as
|
|
100
|
+
private PDFs (module `documents`, see "Dokumente").
|
|
101
|
+
- **Steuer-Compliance**: checks B2B customers' VAT IDs (FinanzOnline),
|
|
102
|
+
provides the Medusa Tax provider `tp_tax_compliance` for reverse-charge and
|
|
103
|
+
apportioned shipping tax and freezes the tax evidence of an order at
|
|
104
|
+
checkout (module `tax_compliance`, see "Steuer-Compliance").
|
|
105
|
+
- **Versandkostenfrei**: keeps the two automatic free-shipping promotions
|
|
106
|
+
(shop-wide and affiliate threshold) in sync with their Admin settings
|
|
107
|
+
(module `free_shipping`, see "Versandkostenfrei").
|
|
108
|
+
- **Dateispeicher (S3)**: stores files in S3-compatible storage behind the
|
|
109
|
+
`/files` gateway (see "Dateispeicher (S3)").
|
|
110
|
+
- **Shop behaviour**: redirects `GET /` to the admin dashboard, repairs
|
|
111
|
+
dangling `cart_promotion` links, starts Admin draft orders without a
|
|
112
|
+
customer order confirmation and marks placed 0 € orders without payment as
|
|
113
|
+
paid (see "Root redirect", "Cart-promotion link repair" and "Draft orders
|
|
114
|
+
and 0 € orders").
|
|
115
|
+
- **Shop configuration**: builds a shop's complete `medusa-config.ts` from a
|
|
116
|
+
few shop facts (`@hartl-services/medusa-base/shop-config`, see
|
|
117
|
+
"Shop-Config-Preset"); exports the generic `medusa-config.ts` helpers from
|
|
52
118
|
`@hartl-services/medusa-base/config` and Sentry's `register()` from
|
|
53
|
-
`@hartl-services/medusa-base/instrumentation` (see "Config helpers"
|
|
54
|
-
"Sentry instrumentation").
|
|
55
|
-
- Sends the shop's transactional e-mails over SMTP or Microsoft 365, chosen in the Admin (module `email`, see
|
|
56
|
-
"E-Mail"), issues invoices, cancellation documents and packing lists as
|
|
57
|
-
private PDFs (module `documents`, see "Dokumente") and stores files in
|
|
58
|
-
S3-compatible storage behind the `/files` gateway (see "Dateispeicher (S3)").
|
|
59
|
-
- Checks B2B customers' VAT IDs (FinanzOnline), provides the Medusa Tax
|
|
60
|
-
provider `tp_tax_compliance` for reverse-charge and apportioned shipping tax
|
|
61
|
-
and freezes the tax evidence of an order at checkout (module
|
|
62
|
-
`tax_compliance`, see "Steuer-Compliance").
|
|
63
|
-
- Declares which login methods customers may use (module `customer_auth`:
|
|
64
|
-
e-mail + password and a one-time code by e-mail), provides the `email-otp`
|
|
65
|
-
auth provider and the code request route (see "Kunden-Login").
|
|
66
|
-
- Builds a shop's complete `medusa-config.ts` from a few shop facts
|
|
67
|
-
(`@hartl-services/medusa-base/shop-config`, see "Shop-Config-Preset").
|
|
119
|
+
`@hartl-services/medusa-base/instrumentation` (see "Config helpers",
|
|
120
|
+
"Sentry logger wrapper" and "Sentry instrumentation").
|
|
68
121
|
|
|
69
122
|
## Installation in a shop
|
|
70
123
|
|
|
@@ -105,11 +158,35 @@ every module of the plugin; each module reads only its own namespace:
|
|
|
105
158
|
`options: {}` is valid: the base then only provides the feature toggles,
|
|
106
159
|
settings, actions and hooks, the e-mail module cannot dispatch, documents use
|
|
107
160
|
the defaults and the `/files` routes answer that signing is not configured.
|
|
108
|
-
Run `medusa db:migrate` after installing it
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
161
|
+
Run `medusa db:migrate` after installing it and after every upgrade (see
|
|
162
|
+
"Upgrading").
|
|
163
|
+
|
|
164
|
+
Peer dependencies are the host's Medusa 2.19 packages (`@medusajs/framework`,
|
|
165
|
+
`@medusajs/medusa`, `@medusajs/admin-sdk`, `@medusajs/js-sdk`, `@medusajs/ui`, `@medusajs/icons`)
|
|
166
|
+
plus React, React Router and TanStack Query in the versions the Medusa 2.19
|
|
167
|
+
dashboard uses.
|
|
168
|
+
|
|
169
|
+
## Upgrading
|
|
170
|
+
|
|
171
|
+
Run `medusa db:migrate` after every upgrade. The versions below add tables or
|
|
172
|
+
migration scripts, merge former packages into the base or rename exports.
|
|
173
|
+
|
|
174
|
+
### Upgrading to 0.4.0
|
|
175
|
+
|
|
176
|
+
0.4.0 adds the tables `plugin_setting_state` and `plugin_action_run`.
|
|
177
|
+
|
|
178
|
+
### Upgrading to 0.4.1
|
|
179
|
+
|
|
180
|
+
`medusa db:migrate` runs the migration script
|
|
181
|
+
`dismiss-orphaned-cart-promotion-links` once (see "Cart-promotion link
|
|
182
|
+
repair"); nothing to configure.
|
|
183
|
+
|
|
184
|
+
### Upgrading to 1.0.0
|
|
185
|
+
|
|
186
|
+
1.0.0 brings the `email` and `documents` tables of the former
|
|
187
|
+
`@hartl-services/medusa-email` and `@hartl-services/medusa-documents` packages
|
|
188
|
+
with unchanged table and migration names, so a shop that used them needs no
|
|
189
|
+
data migration.
|
|
113
190
|
|
|
114
191
|
Upgrading from the separate packages: remove `@hartl-services/medusa-email`,
|
|
115
192
|
`@hartl-services/medusa-documents`, `@hartl-services/medusa-s3-cache-storage`
|
|
@@ -122,7 +199,9 @@ exports from the matching `@hartl-services/medusa-base` subpath. The
|
|
|
122
199
|
`documentsEnabled` option is gone; Documents is always installed, and the
|
|
123
200
|
e-mail module follows the Documents `invoices` toggle.
|
|
124
201
|
|
|
125
|
-
Upgrading to 3.0.0
|
|
202
|
+
### Upgrading to 3.0.0
|
|
203
|
+
|
|
204
|
+
Steuer-Compliance was merged into the base: remove
|
|
126
205
|
`@hartl-services/medusa-tax-compliance` from `package.json` and from the
|
|
127
206
|
`plugins` list and change the Tax provider resolve path to
|
|
128
207
|
`@hartl-services/medusa-base/providers/tax-compliance` (the provider id
|
|
@@ -133,7 +212,9 @@ reached `tax_compliance` by container key keep working; the typed helper is
|
|
|
133
212
|
Hartl Services plugins need the versions released with 3.0.0 (their peer
|
|
134
213
|
range is `^3`).
|
|
135
214
|
|
|
136
|
-
Upgrading to 4.0.0
|
|
215
|
+
### Upgrading to 4.0.0
|
|
216
|
+
|
|
217
|
+
Renames: the Contracts package is
|
|
137
218
|
`@hartl-services/medusa-contracts` (1.0.0, formerly
|
|
138
219
|
`@hartl-services/medusa-food-supplements-contracts`; same subpaths), and the
|
|
139
220
|
Shop-Config-Preset exports are `buildShopConfig`, `defineShopConfig`,
|
|
@@ -143,12 +224,146 @@ Shop-Config-Preset exports are `buildShopConfig`, `defineShopConfig`,
|
|
|
143
224
|
Storefront; nothing else changes. The other Hartl Services plugins need the
|
|
144
225
|
versions released with 4.0.0 (peer range `^4`).
|
|
145
226
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
227
|
+
### Upgrading to 5.0.0
|
|
228
|
+
|
|
229
|
+
Versandkostenfrei was merged into the base: remove
|
|
230
|
+
`@hartl-services/medusa-free-shipping` from `package.json` and from the
|
|
231
|
+
`plugins` list (the module key `free_shipping`, its migrations, settings,
|
|
232
|
+
promotions, job and subscriber ids are unchanged, so no data migration is
|
|
233
|
+
needed; a shop that keeps the old package registered fails at boot with a
|
|
234
|
+
duplicate `free_shipping` module). The other Hartl Services plugins need the
|
|
235
|
+
versions released with 5.0.0 (peer range `^5`).
|
|
236
|
+
|
|
237
|
+
## Shop-Config-Preset
|
|
238
|
+
|
|
239
|
+
`@hartl-services/medusa-base/shop-config` turns a shop's configuration
|
|
240
|
+
from about 290 lines into about 30: the plugin list and order, the Redis event
|
|
241
|
+
bus, SMTP notifications, S3 file storage, the tax and Stripe payment providers,
|
|
242
|
+
the Admin path, the Sentry logger bridge and the environment validation are
|
|
243
|
+
assembled there; the shop supplies only its own facts. It reuses the `/config`
|
|
244
|
+
helpers (see "Config helpers") for environment parsing. It is a separate subpath because it
|
|
245
|
+
loads the Sentry logger wrapper and with it `@sentry/node`; `/config` stays
|
|
246
|
+
free of it.
|
|
247
|
+
|
|
248
|
+
The preset registers the base (with its E-Mail, Documents and `/files`
|
|
249
|
+
options, and its Tax provider) plus three feature plugins, in this order:
|
|
250
|
+
`medusa-base`, `medusa-batch-management`,
|
|
251
|
+
`medusa-affiliate`, `medusa-payload-sync`.
|
|
252
|
+
|
|
253
|
+
### Installing the preset
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
pnpm add @hartl-services/medusa-base \
|
|
257
|
+
@hartl-services/medusa-batch-management \
|
|
258
|
+
@hartl-services/medusa-affiliate \
|
|
259
|
+
@hartl-services/medusa-payload-sync \
|
|
260
|
+
@medusajs/event-bus-redis @medusajs/payment-stripe \
|
|
261
|
+
@sentry/node
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Usage
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
// medusa-config.ts
|
|
268
|
+
import { defineShopConfig } from "@hartl-services/medusa-base/shop-config"
|
|
269
|
+
|
|
270
|
+
module.exports = defineShopConfig({
|
|
271
|
+
displayName: "EXAMPLE SHOP",
|
|
272
|
+
orderNotificationEmail: "orders@example.com",
|
|
273
|
+
timeZone: "Europe/Vienna",
|
|
274
|
+
storefrontCountryCode: "de",
|
|
275
|
+
})
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
A shop that has to adjust the result (a test harness, for example) builds the
|
|
279
|
+
plain input, changes it and calls `defineConfig` itself:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import { defineConfig } from "@medusajs/framework/utils"
|
|
283
|
+
import { buildShopConfig } from "@hartl-services/medusa-base/shop-config"
|
|
284
|
+
|
|
285
|
+
const config = buildShopConfig(options)
|
|
286
|
+
config.plugins = config.plugins.filter(
|
|
287
|
+
(plugin) => plugin.resolve !== "@hartl-services/medusa-batch-management",
|
|
288
|
+
)
|
|
289
|
+
module.exports = defineConfig(config)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### API
|
|
293
|
+
|
|
294
|
+
- `defineShopConfig(options, environment = process.env)` returns
|
|
295
|
+
`defineConfig(buildShopConfig(options, environment))`.
|
|
296
|
+
- `buildShopConfig(options, environment = process.env)` returns the plain
|
|
297
|
+
config input (`admin`, `logger`, `projectConfig`, `modules`, `plugins`)
|
|
298
|
+
before `defineConfig`. `modules` and `plugins` are ordinary arrays.
|
|
299
|
+
- Types: `ShopConfigOptions`, `ShopConfigInput`, `ShopPlugin`,
|
|
300
|
+
`ShopModule`.
|
|
301
|
+
|
|
302
|
+
Both functions validate the options first and throw a `TypeError` naming the
|
|
303
|
+
option (`Shop option "timeZone" must be ...`), so a misconfigured shop
|
|
304
|
+
fails at boot. They then load the `.env` file (unless disabled) and read and
|
|
305
|
+
validate the environment; a missing variable fails with the variable's name.
|
|
306
|
+
|
|
307
|
+
### Options
|
|
308
|
+
|
|
309
|
+
| Option | Meaning |
|
|
310
|
+
| --- | --- |
|
|
311
|
+
| `displayName` | Shop name in the default e-mail templates (a shop's initial seed may use it as store name). Company name, address and UID are no options: documents and tax compliance read them only from the Shop-Stammdaten (Admin, "Hartl Services Plugins"). |
|
|
312
|
+
| `orderNotificationEmail` | Receives order notification e-mails; must look like an e-mail address. |
|
|
313
|
+
| `timeZone` | IANA time zone (validated with `Intl`). Chooses the annual document-number period, the printed dates on documents and the affiliate consignment reminder time zone. |
|
|
314
|
+
| `storefrontCountryCode` | 2-letter lowercase country code of the storefront route. Links: `<STOREFRONT_URL>/<cc>/partnerportal` (affiliate portal), `<STOREFRONT_URL>/<cc>/reset-password` and `<STOREFRONT_URL>/<cc>/verify-account` (the e-mail plugin appends `#token=`), referral base `<STOREFRONT_URL>/<cc>`. The Admin invite link is `<MEDUSA_BACKEND_URL>/app/invite?token=`. |
|
|
315
|
+
| `documentHtmlFactory` | Optional `DocumentHtmlFactory` that replaces the default document markup, typically `createDefaultDocumentHtmlFactory({ timeZone, theme })` from `/config` with the shop's CSS and watermark (see "Layout, logo, payment terms and shop theme"). Must be a function. |
|
|
316
|
+
| `loadEnvFile` | Default `true`: calls `loadEnv(NODE_ENV \|\| "development", process.cwd())`. Pass `false` when the environment is already complete (tests, isolated child processes). |
|
|
317
|
+
|
|
318
|
+
### Environment variables
|
|
319
|
+
|
|
320
|
+
Required at runtime (during `medusa build`, with `MEDUSA_BUILD_PHASE=true`, the
|
|
321
|
+
plugins' build placeholders stand in for them):
|
|
322
|
+
|
|
323
|
+
- Medusa core: `DATABASE_URL`, `STORE_CORS`, `ADMIN_CORS`, `AUTH_CORS`,
|
|
324
|
+
`JWT_SECRET`, `COOKIE_SECRET`, `MEDUSA_BACKEND_URL`, `STOREFRONT_URL` (plain
|
|
325
|
+
HTTP origins are accepted only in development and test).
|
|
326
|
+
- Redis: `REDIS_URL` (optional in development, where Medusa's local event bus
|
|
327
|
+
is used; required elsewhere, `rediss://` with authentication in production
|
|
328
|
+
unless `REDIS_ALLOW_INSECURE_PRIVATE_NETWORK=true`). Never used during a
|
|
329
|
+
build.
|
|
330
|
+
- SMTP: `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE`, `SMTP_USER`, `SMTP_PASSWORD`,
|
|
331
|
+
`SMTP_FROM`, `SMTP_REQUIRE_TLS` (outside development and test either
|
|
332
|
+
`SMTP_SECURE` or `SMTP_REQUIRE_TLS` must be `true`).
|
|
333
|
+
- Stripe: `STRIPE_API_KEY` (`sk_...`), `STRIPE_WEBHOOK_SECRET` (`whsec_...`).
|
|
334
|
+
- S3: `S3_CACHE_STORAGE_BUCKET`, `S3_CACHE_STORAGE_REGION`.
|
|
335
|
+
- Documents: `DOCUMENT_PDF_EXECUTABLE`.
|
|
336
|
+
|
|
337
|
+
Optional: `S3_CACHE_STORAGE_ENDPOINT`, `S3_CACHE_STORAGE_FORCE_PATH_STYLE`,
|
|
338
|
+
`S3_CACHE_STORAGE_PREFIX`, `S3_CACHE_STORAGE_ACCESS_KEY_ID`,
|
|
339
|
+
`S3_CACHE_STORAGE_SECRET_ACCESS_KEY`, `S3_CACHE_STORAGE_SESSION_TOKEN` (omit
|
|
340
|
+
the keys to use the AWS credential chain), `S3_CACHE_STORAGE_CACHE_DIR`
|
|
341
|
+
(default `./.medusa-s3-cache`), `S3_CACHE_STORAGE_TTL_MS`,
|
|
342
|
+
`S3_CACHE_STORAGE_CLEANUP_INTERVAL_MS`,
|
|
343
|
+
`S3_CACHE_STORAGE_EXPIRATION_STRATEGY`,
|
|
344
|
+
`FILE_GATEWAY_PRIVATE_DOWNLOAD_DURATION_SECONDS`, and `SENTRY_DSN` /
|
|
345
|
+
`SENTRY_ENVIRONMENT` for error tracking (see the base's
|
|
346
|
+
`/instrumentation`).
|
|
347
|
+
|
|
348
|
+
### Fixed configuration
|
|
349
|
+
|
|
350
|
+
Not configurable on purpose: the plugin order (base first), the Redis event
|
|
351
|
+
bus job policy (3 attempts, exponential backoff from 1 s), S3 cache defaults
|
|
352
|
+
(15 min cleanup, 1 h TTL, absolute expiration), Stripe immediate capture with
|
|
353
|
+
automatic payment methods, the 24 h session lifetime, Admin path `/app`,
|
|
354
|
+
consignment reminders due on day 10, the Documents module always being part of the base, and
|
|
355
|
+
`http.authVerificationsPerActor.customer` (a getter that follows the E-Mail `customer_verification` toggle, see
|
|
356
|
+
"Kontobestätigung per E-Mail"), `http.authMethodsPerActor` (customers: a getter that follows the `customer_auth` switches; Admin users: `["emailpass"]`) and the auth module entry
|
|
357
|
+
(providers `emailpass` and `email-otp`, the latter with `hmacSecret` = `JWT_SECRET`, the same value as `http.jwtSecret`; see
|
|
358
|
+
"Kunden-Login").
|
|
359
|
+
|
|
360
|
+
## Plugin platform
|
|
150
361
|
|
|
151
|
-
|
|
362
|
+
The mechanics every plugin builds on: the HTTP surface, the feature
|
|
363
|
+
declaration, runtime settings, actions, the shop master data, the Admin and
|
|
364
|
+
server helpers, the toggle semantics and the complete-cart hooks.
|
|
365
|
+
|
|
366
|
+
### HTTP endpoints
|
|
152
367
|
|
|
153
368
|
| Method | Path | Auth | Response |
|
|
154
369
|
|---|---|---|---|
|
|
@@ -193,7 +408,7 @@ const { plugins } = await sdk.client.fetch<StorePluginFeaturesListResponse>(
|
|
|
193
408
|
)
|
|
194
409
|
```
|
|
195
410
|
|
|
196
|
-
|
|
411
|
+
### Declaring features in a plugin
|
|
197
412
|
|
|
198
413
|
A plugin depends on `@hartl-services/medusa-base` (peer dependency), declares
|
|
199
414
|
its features once and exposes the declaration as `pluginFeatures` on its
|
|
@@ -233,7 +448,7 @@ base's own shop master data): the Admin shows no plugin switch, `enabled` is
|
|
|
233
448
|
always `true` whatever is stored, and `POST /admin/plugin-features` with
|
|
234
449
|
`enabled: false` is 400. It cannot be combined with `default_enabled: false`.
|
|
235
450
|
|
|
236
|
-
|
|
451
|
+
#### Presentation fields for the Admin page
|
|
237
452
|
|
|
238
453
|
All optional; the Admin response always carries them (`null`, `[]` or
|
|
239
454
|
`"full"` when not declared). Texts are non-empty trimmed strings;
|
|
@@ -276,7 +491,7 @@ All optional; the Admin response always carries them (`null`, `[]` or
|
|
|
276
491
|
share a two-column row; not for `boolean`) and `section` (sub-heading inside
|
|
277
492
|
the group, shared by consecutive settings with the same value).
|
|
278
493
|
|
|
279
|
-
|
|
494
|
+
#### Gating routes
|
|
280
495
|
|
|
281
496
|
Put gate entries at the start of the plugin's `routes` array. An entry without
|
|
282
497
|
`methods` is a prefix match and runs before every method-specific middleware:
|
|
@@ -303,7 +518,7 @@ export default defineMiddlewares({
|
|
|
303
518
|
A disabled plugin or feature answers 404. With several keys, all must be
|
|
304
519
|
enabled. The toggles are read once per request.
|
|
305
520
|
|
|
306
|
-
|
|
521
|
+
#### Gating subscribers and jobs
|
|
307
522
|
|
|
308
523
|
Return early:
|
|
309
524
|
|
|
@@ -324,7 +539,7 @@ To check another plugin without importing it, use its plugin key:
|
|
|
324
539
|
the descriptor among the registered modules; an unknown or uninstalled plugin
|
|
325
540
|
key (and an undeclared feature key) is `false`.
|
|
326
541
|
|
|
327
|
-
|
|
542
|
+
#### Toggle-change event
|
|
328
543
|
|
|
329
544
|
After a successful `POST /admin/plugin-features` the base emits
|
|
330
545
|
`PLUGIN_FEATURES_UPDATED_EVENT` (`plugin-features.updated.v1`) through the
|
|
@@ -356,7 +571,7 @@ export const config: SubscriberConfig = { event: PLUGIN_FEATURES_UPDATED_EVENT }
|
|
|
356
571
|
Events can arrive late or twice; re-read the current toggles instead of
|
|
357
572
|
trusting the payload for anything that writes.
|
|
358
573
|
|
|
359
|
-
|
|
574
|
+
#### Never on the cart path
|
|
360
575
|
|
|
361
576
|
Code on the cart and checkout hot path (cart middlewares, cart workflow hooks,
|
|
362
577
|
`orderCreated` handling, cart subscribers) must never read toggles. It stays
|
|
@@ -366,7 +581,7 @@ existing data. Login is not the cart path: the E-Mail plugin's
|
|
|
366
581
|
`customer_verification` toggle does switch customer logins, through an
|
|
367
582
|
in-memory copy of the toggle (see "Kontobestätigung per E-Mail").
|
|
368
583
|
|
|
369
|
-
|
|
584
|
+
### Runtime settings
|
|
370
585
|
|
|
371
586
|
Configuration a plugin reads at RUN time (for example an external system's
|
|
372
587
|
URL and API key) is declared in the descriptor and edited on the plugin's
|
|
@@ -460,7 +675,7 @@ class DemoModuleService extends MedusaService({ /* models */ }) {
|
|
|
460
675
|
(`plugin-settings.updated.v1`) with `{ plugin_key, version, changed_keys }`
|
|
461
676
|
(no values). Direct service calls emit nothing.
|
|
462
677
|
|
|
463
|
-
|
|
678
|
+
#### Secrets
|
|
464
679
|
|
|
465
680
|
Secrets are stored AES-256-GCM encrypted (`{ enc: "<base64 iv|tag|ciphertext>" }`
|
|
466
681
|
in `plugin_setting_state.values`) with a key derived by HKDF-SHA256 from the
|
|
@@ -475,7 +690,7 @@ applies in the meantime. Secrets saved by an unpublished development build
|
|
|
475
690
|
before the key binding existed count as "nicht gesetzt" the same way and must
|
|
476
691
|
be entered again.
|
|
477
692
|
|
|
478
|
-
|
|
693
|
+
### Actions
|
|
479
694
|
|
|
480
695
|
A plugin declares actions as registered workflow ids; the base runs them from
|
|
481
696
|
the plugin's Admin page:
|
|
@@ -529,7 +744,7 @@ Admin user id, `run_id` the recorded run).
|
|
|
529
744
|
workflow is not registered is 500 (`UNEXPECTED_STATE` naming the workflow,
|
|
530
745
|
no run is recorded).
|
|
531
746
|
|
|
532
|
-
|
|
747
|
+
#### Reporting progress
|
|
533
748
|
|
|
534
749
|
A long-running action reports progress from its workflow; the Admin shows it
|
|
535
750
|
as a bar ("current / total") with the label:
|
|
@@ -558,7 +773,7 @@ characters. Without a `run_id` (the same workflow run from a script) the
|
|
|
558
773
|
helper does nothing; invalid progress, an unknown or finished run and a
|
|
559
774
|
failed write are logged as a warning and dropped, and never fail the action.
|
|
560
775
|
|
|
561
|
-
|
|
776
|
+
### Shop-Stammdaten
|
|
562
777
|
|
|
563
778
|
The base declares its own descriptor on its module service
|
|
564
779
|
(`plugin_key: "shop"`, label "Shop-Stammdaten", `switchable: false`, no
|
|
@@ -626,7 +841,7 @@ Documents plugin into the appearance: the Documents logo, else the E-Mail
|
|
|
626
841
|
logo; a `logo` stored in the Shop-Stammdaten by an earlier release is kept
|
|
627
842
|
only when neither plugin had one.
|
|
628
843
|
|
|
629
|
-
|
|
844
|
+
### Admin pages
|
|
630
845
|
|
|
631
846
|
- `/settings/hartl-services` (sidebar entry "Hartl Services Plugins") is the
|
|
632
847
|
hub: a banner for the Shop-Stammdaten (icon, summary, "Nicht konfiguriert"
|
|
@@ -655,7 +870,7 @@ only when neither plugin had one.
|
|
|
655
870
|
rejects a path outside `/settings/hartl-services/` or under
|
|
656
871
|
`/settings/hartl-services/plugin/`.
|
|
657
872
|
|
|
658
|
-
|
|
873
|
+
#### Admin components for plugin pages
|
|
659
874
|
|
|
660
875
|
`@hartl-services/medusa-base/admin-components` (ESM for the Admin's vite
|
|
661
876
|
build, with types) exports:
|
|
@@ -754,7 +969,7 @@ The consuming plugin lists `@hartl-services/medusa-base` in its
|
|
|
754
969
|
external and the shop's Admin resolves one shared copy. The components use
|
|
755
970
|
the host Admin's React, React Query, React Router and `@medusajs/ui`.
|
|
756
971
|
|
|
757
|
-
|
|
972
|
+
##### Rich-text editor
|
|
758
973
|
|
|
759
974
|
`useRichTextEditor(config, initialContent, deps)` returns the TipTap
|
|
760
975
|
`Editor | null` (`null` until it exists), `<RichTextEditor editor
|
|
@@ -847,7 +1062,7 @@ policy (`link.isAllowedHref`) are read on every use and always see the latest
|
|
|
847
1062
|
render, so they need no stable reference. Pass `[]` for an editor that lives as
|
|
848
1063
|
long as its component.
|
|
849
1064
|
|
|
850
|
-
|
|
1065
|
+
#### Server helpers
|
|
851
1066
|
|
|
852
1067
|
`@hartl-services/medusa-base/lib` (server and isomorphic code; a separate
|
|
853
1068
|
subpath because unit tests mock `@hartl-services/medusa-base` wholesale)
|
|
@@ -893,13 +1108,7 @@ The main entry (`@hartl-services/medusa-base`) additionally exports, next to
|
|
|
893
1108
|
| `isOwnPluginEvent(data, pluginKey)` | `true` when the event payload's `plugin_key` is `pluginKey`; never throws. For subscribers of `plugin-features.updated.v1` and `plugin-settings.updated.v1` |
|
|
894
1109
|
| `isBasePluginRegistered(container)` / `resolveBaseModule(container)` | Whether the base module is registered (boot-time check) / its service, or `UNEXPECTED_STATE` |
|
|
895
1110
|
|
|
896
|
-
|
|
897
|
-
`.medusa/server/src/admin/admin-components`, not into `dist`: the host Admin's
|
|
898
|
-
Tailwind scans only `.medusa/server/src/admin/**` of a plugin, so the classes of
|
|
899
|
-
the shared components are generated there. Do not move the outDir of
|
|
900
|
-
`tsconfig.admin-components.json`.
|
|
901
|
-
|
|
902
|
-
## Semantics
|
|
1111
|
+
### Semantics
|
|
903
1112
|
|
|
904
1113
|
- No stored row: every value is its declared default, `version` is 0.
|
|
905
1114
|
- `effective(feature) = plugin enabled && configured(feature) && effective(parent)`.
|
|
@@ -911,7 +1120,7 @@ the shared components are generated there. Do not move the outDir of
|
|
|
911
1120
|
value updates exactly that version. A concurrent change answers 409; the
|
|
912
1121
|
Admin page reloads and shows a notice.
|
|
913
1122
|
|
|
914
|
-
|
|
1123
|
+
### Complete-cart hook composition
|
|
915
1124
|
|
|
916
1125
|
**Why.** Medusa 2.19.0 allows exactly one handler per workflow hook; a second
|
|
917
1126
|
registration throws "Cannot define multiple hook handlers" at boot. Several
|
|
@@ -996,42 +1205,12 @@ Storefront calls it right before confirming a payment. Plugins that guard
|
|
|
996
1205
|
`/store/carts/:id/complete` with a middleware register it for this route too
|
|
997
1206
|
(the affiliate plugin's registered-email gate does).
|
|
998
1207
|
|
|
1208
|
+
## Built-in features
|
|
999
1209
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
`@hartl-services/medusa-base/sentry-logger` exports `withSentryErrorReporting(logger)`,
|
|
1003
|
-
formerly published as `@hartl-services/medusa-sentry-logger`. Medusa's HTTP
|
|
1004
|
-
error handler catches thrown route and workflow errors and only logs them, so
|
|
1005
|
-
Sentry's default Node integrations never see them; wrapping the logger reports
|
|
1006
|
-
every `logger.error(...)` and every `Error` passed to `logger.warn(...)` (at
|
|
1007
|
-
level `warning`). It is a plain library import for `medusa-config.ts`, not a
|
|
1008
|
-
Medusa plugin feature, and needs `@sentry/node` (an optional peer dependency):
|
|
1009
|
-
|
|
1010
|
-
```ts
|
|
1011
|
-
import { logger } from "@medusajs/framework/logger";
|
|
1012
|
-
import { withSentryErrorReporting } from "@hartl-services/medusa-base/sentry-logger";
|
|
1013
|
-
|
|
1014
|
-
module.exports = defineConfig({
|
|
1015
|
-
logger: withSentryErrorReporting(logger),
|
|
1016
|
-
// ...
|
|
1017
|
-
});
|
|
1018
|
-
```
|
|
1019
|
-
|
|
1020
|
-
Initialize Sentry in `instrumentation.ts` as usual; without a `SENTRY_DSN` the
|
|
1021
|
-
wrapper is inert.
|
|
1022
|
-
|
|
1023
|
-
## Root redirect
|
|
1024
|
-
|
|
1025
|
-
Medusa serves nothing at `/`, so a deployed domain answers it with a 404. The
|
|
1026
|
-
base registers `GET /` and answers with a `302` to the shop's `admin.path`
|
|
1027
|
-
(read from the config module, fallback `/app`), so opening the bare domain
|
|
1028
|
-
lands on the admin dashboard in every shop and in local development without a
|
|
1029
|
-
reverse-proxy rule. The redirect is temporary on purpose: browsers cache a
|
|
1030
|
-
`301` on the bare domain essentially forever, which would be painful to undo
|
|
1031
|
-
should `/` ever get real content. A shop that needs its own `/` defines
|
|
1032
|
-
`src/api/route.ts` itself.
|
|
1210
|
+
The modules and providers the base ships besides the platform, each with its
|
|
1211
|
+
own configuration, toggles and Admin surface.
|
|
1033
1212
|
|
|
1034
|
-
|
|
1213
|
+
### Kunden-Login
|
|
1035
1214
|
|
|
1036
1215
|
Module `customer_auth` (plugin key `customer_auth`, label "Kunden-Login")
|
|
1037
1216
|
decides which login methods customers may use. The plugin cannot be switched
|
|
@@ -1228,231 +1407,55 @@ Storefront contract: DTOs, schemas and paths come from the Contracts package
|
|
|
1228
1407
|
read `GET /store/plugin-features/customer_auth` and render only the enabled
|
|
1229
1408
|
forms.
|
|
1230
1409
|
|
|
1231
|
-
|
|
1410
|
+
### E-Mail
|
|
1232
1411
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
and `/auth/Customer/emailpass/register` reach Medusa's handler too, and
|
|
1240
|
-
`Customer` counts as the customer actor here.
|
|
1241
|
-
A password that breaks the rules of the actor is answered with
|
|
1242
|
-
`400 { type: "invalid_data", message }`; the one German message lists every
|
|
1243
|
-
unmet rule, e.g. `Das Passwort erfüllt nicht alle Regeln: mindestens 12
|
|
1244
|
-
Zeichen, eine Ziffer.` The check runs before Medusa's reset-token check, so a
|
|
1245
|
-
rejected password leaves the single-use reset link valid for a second attempt.
|
|
1412
|
+
The base owns transactional e-mail for the shop: logical email messages,
|
|
1413
|
+
attempts, rendering, the Medusa Notification integration, the SMTP and
|
|
1414
|
+
Microsoft 365 transports, retries and e-mail-specific Admin operations. It is
|
|
1415
|
+
the `email` module, its notification provider (`providers/smtp`, id `smtp`),
|
|
1416
|
+
its subscribers, jobs and
|
|
1417
|
+
workflows, and the Admin page `/settings/hartl-services/email`.
|
|
1246
1418
|
|
|
1247
|
-
|
|
1248
|
-
characters, counted as Unicode code points, and no composition rules. This
|
|
1249
|
-
follows NIST SP 800-63B Rev. 4 (the US guideline for digital identity) for a
|
|
1250
|
-
password that is the only factor.
|
|
1419
|
+
#### Configuration
|
|
1251
1420
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
`
|
|
1421
|
+
The notification provider is a `modules` entry built by
|
|
1422
|
+
`createEmailNotificationModuleConfig` (from
|
|
1423
|
+
`@hartl-services/medusa-base/config`); which service it sends through and the
|
|
1424
|
+
credentials are set in the Admin (see "Versand"). The branding and rendering
|
|
1425
|
+
configuration goes into the `email` namespace of the base options:
|
|
1255
1426
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
| `password_min_length` | Mindestlänge | 15 | 8 to 128 characters, whole numbers only (`integer`); NIST SP 800-63B Rev. 4 recommends at least 15 when the password is the only factor |
|
|
1259
|
-
| `password_require_mixed_case` | Groß- und Kleinbuchstaben verlangen | off | at least one uppercase and one lowercase letter |
|
|
1260
|
-
| `password_require_digit` | Ziffer verlangen | off | at least one digit |
|
|
1261
|
-
| `password_require_symbol` | Sonderzeichen verlangen | off | at least one punctuation mark or symbol (Unicode category P or S; whitespace, combining marks and invisible characters do not count) |
|
|
1427
|
+
```ts
|
|
1428
|
+
import { createEmailNotificationModuleConfig } from "@hartl-services/medusa-base/config";
|
|
1262
1429
|
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
Login of a customer is not checked, so existing passwords keep working until their next
|
|
1294
|
-
reset, whatever the rules are. Accounts created without HTTP (`medusa user`,
|
|
1295
|
-
seed scripts calling the Auth module) are not checked either.
|
|
1296
|
-
|
|
1297
|
-
## Cart-promotion link repair
|
|
1298
|
-
|
|
1299
|
-
Medusa's `deletePromotionsWorkflow` only soft-deletes a promotion and leaves
|
|
1300
|
-
its `cart_promotion` link rows behind, so every cart that had it applied
|
|
1301
|
-
reads back a `null` entry inside `cart.promotions`. The base removes those
|
|
1302
|
-
links itself:
|
|
1303
|
-
|
|
1304
|
-
- The `deletePromotionsWorkflow.hooks.promotionsDeleted` hook
|
|
1305
|
-
(`workflows/hooks/dismiss-deleted-promotion-cart-links`) dismisses the
|
|
1306
|
-
links of the deleted promotions and restores them if a later step fails.
|
|
1307
|
-
- The migration script `dismiss-orphaned-cart-promotion-links` runs once in
|
|
1308
|
-
`medusa db:migrate` and removes the links that already dangle from earlier
|
|
1309
|
-
deletions. Medusa tracks migration scripts by file name in
|
|
1310
|
-
`script_migrations`, so the name must not change.
|
|
1311
|
-
|
|
1312
|
-
Nothing to configure; `medusa db:migrate` after installing or upgrading to
|
|
1313
|
-
0.4.1 is enough.
|
|
1314
|
-
|
|
1315
|
-
## Draft orders and 0 € orders
|
|
1316
|
-
|
|
1317
|
-
Draft orders (Medusa's `@medusajs/draft-order` plugin, loaded by default) are
|
|
1318
|
-
the shop's way to enter an order in the Admin: sample shipments, orders taken
|
|
1319
|
-
by phone, replacements. Item prices and the shipping amount are set directly
|
|
1320
|
-
in the draft; converting it never involves a payment provider.
|
|
1321
|
-
|
|
1322
|
-
- **Customer order confirmation off by default.** The
|
|
1323
|
-
`createOrderWorkflow.hooks.orderCreated` hook
|
|
1324
|
-
(`workflows/hooks/draft-order-confirmation-default`) sets Medusa's
|
|
1325
|
-
`no_notification` on every new draft, also when an API caller sent
|
|
1326
|
-
`no_notification_order: false`. The Admin switches it per draft in the
|
|
1327
|
-
**Bestellbestätigung** widget on the draft's detail page
|
|
1328
|
-
(`GET`/`POST /admin/draft-orders/:id/order-confirmation`, see the E-Mail
|
|
1329
|
-
"Admin API"). On conversion the `order-placed` subscriber records the
|
|
1330
|
-
customer confirmation as `suppressed` (`notifications_disabled`); the copy
|
|
1331
|
-
to the shop's order address is still sent. The shipment mail follows the
|
|
1332
|
-
Admin's "notify customer" choice per shipment, not this switch.
|
|
1333
|
-
- **0 € orders count as paid.** Medusa derives an order's payment status from
|
|
1334
|
-
its payment collections only, so a converted 0 € draft would stay
|
|
1335
|
-
"Nicht bezahlt". The subscriber `order-zero-total-paid` runs
|
|
1336
|
-
`order-mark-zero-total-paid` on `order.placed`: an order with a total of 0
|
|
1337
|
-
and no payment collection gets a completed 0 € payment collection and reads
|
|
1338
|
-
"Bezahlt" (`payment_status: captured`). Orders that already carry a
|
|
1339
|
-
collection, i.e. every checkout order, are left alone. In SQL, a paid
|
|
1340
|
-
order is one whose linked `payment_collection` (via
|
|
1341
|
-
`order_payment_collection`) has `status = 'completed'`.
|
|
1342
|
-
|
|
1343
|
-
Orders placed before installing this version keep their state.
|
|
1344
|
-
|
|
1345
|
-
## Config helpers
|
|
1346
|
-
|
|
1347
|
-
`@hartl-services/medusa-base/config` holds the generic environment helpers a
|
|
1348
|
-
shop's `medusa-config.ts` needs, so every shop fails at startup with the name
|
|
1349
|
-
of the missing variable instead of a runtime crash. The environment helpers
|
|
1350
|
-
have no dependency on Medusa; the subpath does not load `@sentry/node`.
|
|
1351
|
-
|
|
1352
|
-
| Export | Purpose |
|
|
1353
|
-
|---|---|
|
|
1354
|
-
| `ConfigurationPhase` | `"build" \| "runtime"` |
|
|
1355
|
-
| `MEDUSA_BUILD_PLACEHOLDERS` | Placeholders for `ADMIN_CORS`, `AUTH_CORS`, `COOKIE_SECRET`, `DATABASE_URL`, `JWT_SECRET`, `MEDUSA_BACKEND_URL`, `STOREFRONT_URL`, `STORE_CORS`, `STRIPE_API_KEY`, `STRIPE_WEBHOOK_SECRET` |
|
|
1356
|
-
| `requireEnvironment(environment, names, phase, buildPlaceholders)` | Returns every named value (trimmed) or throws one error listing all missing names; in the build phase `buildPlaceholders` fill in for deployment values |
|
|
1357
|
-
| `optionalEnvironment`, `integerEnvironment`, `booleanEnvironment` | Trimmed optional string, range-checked integer, strict `"true"`/`"false"` boolean |
|
|
1358
|
-
| `allowsInsecureLocalServices(environment, phase)` | `true` for build, development and test |
|
|
1359
|
-
| `resolvePublicOrigin(value, name, allowHttp)`, `shopUrl(origin, path)` | Validate a bare HTTP(S) origin; build an absolute URL that cannot leave the origin |
|
|
1360
|
-
| `resolveStripe(values)` | Checks the `sk_` key and the `whsec_` webhook secret |
|
|
1361
|
-
| `resolveRedisEventBus(environment, phase)` | `{ url } \| undefined`: optional in development, required at runtime elsewhere, never used in a build, authenticated `rediss://` in production |
|
|
1362
|
-
| `createEmailNotificationModuleConfig`, `defineShopEmailConfiguration` (deprecated: `createSmtpNotificationModuleConfig`, `resolveSmtp`, `SMTP_ENVIRONMENT_VARIABLES`, `SMTP_BUILD_PLACEHOLDERS`) | Mail notification provider and E-Mail configuration (see "E-Mail") |
|
|
1363
|
-
| `createS3CacheStorageConfig`, `createS3CacheFileModuleConfig`, `resolveS3Credentials`, `resolveExpirationStrategy`, `S3_BUILD_PLACEHOLDERS` | S3 file storage (see "Dateispeicher (S3)") |
|
|
1364
|
-
| `createDefaultDocumentHtmlFactory`, `DOCUMENTS_BUILD_PLACEHOLDERS` | Documents markup and build placeholder (see "Dokumente") |
|
|
1365
|
-
|
|
1366
|
-
`medusa build` loads the configuration without deployment secrets. Each
|
|
1367
|
-
plugin's own placeholders (SMTP, S3, PDF executable, ...) live with that
|
|
1368
|
-
plugin; the shop merges them:
|
|
1369
|
-
|
|
1370
|
-
```ts
|
|
1371
|
-
// medusa-config.ts
|
|
1372
|
-
import {
|
|
1373
|
-
MEDUSA_BUILD_PLACEHOLDERS,
|
|
1374
|
-
requireEnvironment,
|
|
1375
|
-
resolveRedisEventBus,
|
|
1376
|
-
type ConfigurationPhase,
|
|
1377
|
-
} from "@hartl-services/medusa-base/config"
|
|
1378
|
-
|
|
1379
|
-
const phase: ConfigurationPhase =
|
|
1380
|
-
process.env.MEDUSA_BUILD_PHASE === "true" ? "build" : "runtime"
|
|
1381
|
-
const env = requireEnvironment(
|
|
1382
|
-
process.env,
|
|
1383
|
-
["DATABASE_URL", "JWT_SECRET", "COOKIE_SECRET"],
|
|
1384
|
-
phase,
|
|
1385
|
-
{ ...MEDUSA_BUILD_PLACEHOLDERS /* , ...other plugins' placeholders */ },
|
|
1386
|
-
)
|
|
1387
|
-
const redis = resolveRedisEventBus(process.env, phase)
|
|
1388
|
-
```
|
|
1389
|
-
|
|
1390
|
-
## Sentry instrumentation
|
|
1391
|
-
|
|
1392
|
-
`@hartl-services/medusa-base/instrumentation` exports `register()`, the hook
|
|
1393
|
-
Medusa's start command calls from the shop's `instrumentation.ts`. It runs
|
|
1394
|
-
`Sentry.init` with `SENTRY_DSN`, `SENTRY_ENVIRONMENT` (fallback `NODE_ENV`)
|
|
1395
|
-
and `tracesSampleRate: 0`; without a `SENTRY_DSN` Sentry stays inert. The
|
|
1396
|
-
shop's `instrumentation.ts` is one line:
|
|
1397
|
-
|
|
1398
|
-
```ts
|
|
1399
|
-
export { register } from "@hartl-services/medusa-base/instrumentation"
|
|
1400
|
-
```
|
|
1401
|
-
|
|
1402
|
-
`@sentry/node` stays an optional peer dependency of the base: a shop that uses
|
|
1403
|
-
this export (or `sentry-logger`) lists `@sentry/node` in its own
|
|
1404
|
-
`dependencies`.
|
|
1405
|
-
|
|
1406
|
-
## E-Mail
|
|
1407
|
-
|
|
1408
|
-
The base owns transactional e-mail for the shop: logical email messages,
|
|
1409
|
-
attempts, rendering, the Medusa Notification integration, the SMTP and
|
|
1410
|
-
Microsoft 365 transports, retries and e-mail-specific Admin operations. It is
|
|
1411
|
-
the `email` module, its notification provider (`providers/smtp`, id `smtp`),
|
|
1412
|
-
its subscribers, jobs and
|
|
1413
|
-
workflows, and the Admin page `/settings/hartl-services/email`. There is no
|
|
1414
|
-
separate e-mail package since 1.0.0.
|
|
1415
|
-
|
|
1416
|
-
### Configuration
|
|
1417
|
-
|
|
1418
|
-
The notification provider is a `modules` entry built by
|
|
1419
|
-
`createEmailNotificationModuleConfig` (from
|
|
1420
|
-
`@hartl-services/medusa-base/config`); which service it sends through and the
|
|
1421
|
-
credentials are set in the Admin (see "Versand"). The branding and rendering
|
|
1422
|
-
configuration goes into the `email` namespace of the base options:
|
|
1423
|
-
|
|
1424
|
-
```ts
|
|
1425
|
-
import { createEmailNotificationModuleConfig } from "@hartl-services/medusa-base/config";
|
|
1426
|
-
|
|
1427
|
-
export default defineConfig({
|
|
1428
|
-
modules: [
|
|
1429
|
-
createEmailNotificationModuleConfig({
|
|
1430
|
-
// Permits "Verschlüsselung: Keine" (a local SMTP server); never in production.
|
|
1431
|
-
allowInsecure:
|
|
1432
|
-
process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test",
|
|
1433
|
-
}),
|
|
1434
|
-
],
|
|
1435
|
-
plugins: [
|
|
1436
|
-
{
|
|
1437
|
-
resolve: "@hartl-services/medusa-base",
|
|
1438
|
-
options: {
|
|
1439
|
-
email: {
|
|
1440
|
-
jwtSecret: process.env.JWT_SECRET!,
|
|
1441
|
-
shop: {
|
|
1442
|
-
displayName: "Mein Shop",
|
|
1443
|
-
affiliatePortalUrl: "https://shop.example.com/de/partner",
|
|
1444
|
-
inviteUrl: ({ token }) =>
|
|
1445
|
-
`https://shop.example.com/app/invite?token=${token}`,
|
|
1446
|
-
orderNotificationEmail: "orders@shop.example.com",
|
|
1447
|
-
passwordResetUrl: ({ locale }) =>
|
|
1448
|
-
`https://shop.example.com/${locale ?? "de"}/passwort-zuruecksetzen`,
|
|
1449
|
-
}, // a ShopEmailConfiguration, see `ShopEmailConfiguration` in `@hartl-services/medusa-base/types`
|
|
1450
|
-
},
|
|
1451
|
-
},
|
|
1452
|
-
},
|
|
1453
|
-
],
|
|
1454
|
-
});
|
|
1455
|
-
```
|
|
1430
|
+
export default defineConfig({
|
|
1431
|
+
modules: [
|
|
1432
|
+
createEmailNotificationModuleConfig({
|
|
1433
|
+
// Permits "Verschlüsselung: Keine" (a local SMTP server); never in production.
|
|
1434
|
+
allowInsecure:
|
|
1435
|
+
process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test",
|
|
1436
|
+
}),
|
|
1437
|
+
],
|
|
1438
|
+
plugins: [
|
|
1439
|
+
{
|
|
1440
|
+
resolve: "@hartl-services/medusa-base",
|
|
1441
|
+
options: {
|
|
1442
|
+
email: {
|
|
1443
|
+
jwtSecret: process.env.JWT_SECRET!,
|
|
1444
|
+
shop: {
|
|
1445
|
+
displayName: "Mein Shop",
|
|
1446
|
+
affiliatePortalUrl: "https://shop.example.com/de/partner",
|
|
1447
|
+
inviteUrl: ({ token }) =>
|
|
1448
|
+
`https://shop.example.com/app/invite?token=${token}`,
|
|
1449
|
+
orderNotificationEmail: "orders@shop.example.com",
|
|
1450
|
+
passwordResetUrl: ({ locale }) =>
|
|
1451
|
+
`https://shop.example.com/${locale ?? "de"}/passwort-zuruecksetzen`,
|
|
1452
|
+
}, // a ShopEmailConfiguration, see `ShopEmailConfiguration` in `@hartl-services/medusa-base/types`
|
|
1453
|
+
},
|
|
1454
|
+
},
|
|
1455
|
+
},
|
|
1456
|
+
],
|
|
1457
|
+
});
|
|
1458
|
+
```
|
|
1456
1459
|
|
|
1457
1460
|
Options of the `email` namespace:
|
|
1458
1461
|
|
|
@@ -1482,8 +1485,7 @@ fact only while the Documents `invoices` feature is on when the message is
|
|
|
1482
1485
|
created (with `invoices` off, or the Documents plugin switched off, Documents
|
|
1483
1486
|
publishes no fact, so the mail is sent without a document). Refund e-mails
|
|
1484
1487
|
come only from the Documents refund fact, so there are none while `invoices`
|
|
1485
|
-
is off.
|
|
1486
|
-
always part of the base.
|
|
1488
|
+
is off.
|
|
1487
1489
|
|
|
1488
1490
|
The event contracts the module consumes for shipment, cancellation, refund and
|
|
1489
1491
|
affiliate-reminder and partner-application e-mails come from
|
|
@@ -1493,11 +1495,7 @@ affiliate-reminder and partner-application e-mails come from
|
|
|
1493
1495
|
The provider reads no environment variables (since 2.9.0; the former
|
|
1494
1496
|
`SMTP_*` variables are ignored).
|
|
1495
1497
|
|
|
1496
|
-
|
|
1497
|
-
Event Bus; production requires an authenticated `rediss://` URL. See "Event
|
|
1498
|
-
Bus" below for retry/backoff behavior.
|
|
1499
|
-
|
|
1500
|
-
### Versand
|
|
1498
|
+
#### Versand
|
|
1501
1499
|
|
|
1502
1500
|
The page E-Mail → "Versand" (`/settings/hartl-services/email/delivery`, linked
|
|
1503
1501
|
under "Verwaltung") edits the plugin's settings. It stays reachable while the
|
|
@@ -1557,7 +1555,7 @@ to the e-mail of the Shop-Stammdaten, once per client id and expiry date (a
|
|
|
1557
1555
|
log warning when the Shop-Stammdaten have no e-mail). A new secret with a new
|
|
1558
1556
|
date warns again.
|
|
1559
1557
|
|
|
1560
|
-
|
|
1558
|
+
#### Feature toggles
|
|
1561
1559
|
|
|
1562
1560
|
The E-Mail module declares its toggles to `@hartl-services/medusa-base` under the
|
|
1563
1561
|
plugin key `email`. An Admin switches them under Settings → "Hartl Services
|
|
@@ -1593,7 +1591,7 @@ rows are dimmed and their switches disabled then. All E-Mail sub-pages
|
|
|
1593
1591
|
switch and show `PluginFeatureDisabledNotice` while it is off (the backend
|
|
1594
1592
|
answers `404` for `/admin/email/*` then).
|
|
1595
1593
|
|
|
1596
|
-
|
|
1594
|
+
#### Kontobestätigung per E-Mail
|
|
1597
1595
|
|
|
1598
1596
|
Feature `customer_verification` (child of `account_mails`, default **off**)
|
|
1599
1597
|
makes customers confirm their e-mail address through a link before their
|
|
@@ -1672,7 +1670,8 @@ loads (`.medusa/server`) are compiled separately. Only the preset
|
|
|
1672
1670
|
hand-written config must set `authVerificationsPerActor` itself (a fixed list,
|
|
1673
1671
|
changed by a restart) for the toggle to have any effect on logins.
|
|
1674
1672
|
|
|
1675
|
-
Login is not on the cart path, so the
|
|
1673
|
+
Login is not on the cart path, so the rule in "Never on the cart path" is
|
|
1674
|
+
not affected.
|
|
1676
1675
|
|
|
1677
1676
|
Things to know when switching it on:
|
|
1678
1677
|
|
|
@@ -1698,7 +1697,7 @@ Storefront contract (DTOs, schema and path from the Contracts package,
|
|
|
1698
1697
|
- Serve `/<cc>/verify-account`, read `token` from `location.hash`, confirm it
|
|
1699
1698
|
and then send the customer to the login.
|
|
1700
1699
|
|
|
1701
|
-
|
|
1700
|
+
#### Ownership
|
|
1702
1701
|
|
|
1703
1702
|
- Documents owns document issuance and publishes immutable document facts.
|
|
1704
1703
|
- Affiliate owns reminder eligibility and publishes reminder and application
|
|
@@ -1714,7 +1713,7 @@ surfaces for the backend. The Documents and Affiliate event contracts live in
|
|
|
1714
1713
|
the Contracts package (`events/documents`, `events/affiliate`). The base exports no DML models, services,
|
|
1715
1714
|
workflows, migrations, or generated output as a consumer contract.
|
|
1716
1715
|
|
|
1717
|
-
|
|
1716
|
+
#### Event Bus
|
|
1718
1717
|
|
|
1719
1718
|
The backend owns Event Bus selection. Development without `REDIS_URL` leaves
|
|
1720
1719
|
Medusa's local Event Bus active. Configuring `REDIS_URL` registers
|
|
@@ -1728,7 +1727,7 @@ for at most one hour and 1,000 entries. There is no custom Event Bus wrapper,
|
|
|
1728
1727
|
startup capability probe, or purge job. Events are at-least-once facts and can
|
|
1729
1728
|
be replayed, delayed, or delivered out of order.
|
|
1730
1729
|
|
|
1731
|
-
|
|
1730
|
+
#### Event Contracts
|
|
1732
1731
|
|
|
1733
1732
|
All payload objects are strict. Unless noted otherwise, every string shown
|
|
1734
1733
|
below is trimmed and non-empty. Native event names and shapes are fixed to the
|
|
@@ -1854,7 +1853,7 @@ concurrent request never sends a second mail) addressed to
|
|
|
1854
1853
|
`affiliate-consignment:<affiliate_id>:<period_start>`. `locale`, when present,
|
|
1855
1854
|
has at most 35 characters. Email runtime-validates every contract before use.
|
|
1856
1855
|
|
|
1857
|
-
|
|
1856
|
+
#### Invariants
|
|
1858
1857
|
|
|
1859
1858
|
- The accepted kinds are exactly `order.received`, `order.received.admin`, `order.shipped`,
|
|
1860
1859
|
`order.canceled`, `order.return_requested`, `order.return_received`,
|
|
@@ -1908,7 +1907,7 @@ has at most 35 characters. Email runtime-validates every contract before use.
|
|
|
1908
1907
|
|
|
1909
1908
|
Read this section and the central design before changing Email domain behavior.
|
|
1910
1909
|
|
|
1911
|
-
|
|
1910
|
+
#### Shop configuration and default templates
|
|
1912
1911
|
|
|
1913
1912
|
The E-Mail module ships a neutral German layout in grey tones (table-based,
|
|
1914
1913
|
inline styles) and one renderer per accepted kind; the colors of the
|
|
@@ -2094,7 +2093,7 @@ configuration (blank `displayName`, a non-function `layout` or renderer, an
|
|
|
2094
2093
|
unknown renderer kind) fails at startup and not at the first send. Custom
|
|
2095
2094
|
renderers must escape dynamic values in `bodyHtml` themselves.
|
|
2096
2095
|
|
|
2097
|
-
|
|
2096
|
+
#### Deprecated SMTP helpers
|
|
2098
2097
|
|
|
2099
2098
|
`createSmtpNotificationModuleConfig(smtp)`, `resolveSmtp`,
|
|
2100
2099
|
`SMTP_ENVIRONMENT_VARIABLES` and `SMTP_BUILD_PLACEHOLDERS` remain exported
|
|
@@ -2103,7 +2102,7 @@ given to `createSmtpNotificationModuleConfig` only fill SMTP settings that are
|
|
|
2103
2102
|
not stored in the Admin (the Admin status does not count them as configured).
|
|
2104
2103
|
Use `createEmailNotificationModuleConfig` and the "Versand" page instead.
|
|
2105
2104
|
|
|
2106
|
-
|
|
2105
|
+
#### Delivery Semantics
|
|
2107
2106
|
|
|
2108
2107
|
For ordinary durable messages, only characterized connection failures,
|
|
2109
2108
|
temporary SMTP rejections, and temporary attachment access failures are
|
|
@@ -2129,7 +2128,7 @@ data remain ephemeral; both kinds are excluded from the generic retry job and
|
|
|
2129
2128
|
Admin resend, and retryable handling stays bounded by the original Event Bus
|
|
2130
2129
|
event.
|
|
2131
2130
|
|
|
2132
|
-
|
|
2131
|
+
#### Admin API
|
|
2133
2132
|
|
|
2134
2133
|
Email exposes authenticated Admin operations at:
|
|
2135
2134
|
|
|
@@ -2200,7 +2199,7 @@ a different UUID represents a separate, conscious Admin decision. The Admin UI
|
|
|
2200
2199
|
therefore retains a UUID while retrying an ambiguous failed request and creates
|
|
2201
2200
|
a new UUID only after a new confirmation.
|
|
2202
2201
|
|
|
2203
|
-
|
|
2202
|
+
#### Admin UI
|
|
2204
2203
|
|
|
2205
2204
|
The E-Mail module has an Admin area under `/settings/hartl-services/email`
|
|
2206
2205
|
(**Settings → Hartl Services Plugins → E-Mail → Öffnen**; the descriptor's
|
|
@@ -2249,7 +2248,7 @@ request UUID; downloads use the configured authenticated Admin SDK and never
|
|
|
2249
2248
|
expose storage file IDs or URLs in the UI. There is no delete: removing every
|
|
2250
2249
|
assigned mail lets a document rest.
|
|
2251
2250
|
|
|
2252
|
-
|
|
2251
|
+
#### Mail templates
|
|
2253
2252
|
|
|
2254
2253
|
All 13 mails (`EMAIL_EDITABLE_KINDS`) are block templates an Admin edits
|
|
2255
2254
|
from the group pages of the E-Mail area (Bestell-, Konto- and Affiliate-Mails
|
|
@@ -2350,7 +2349,7 @@ and then each changed mail kind on its own.
|
|
|
2350
2349
|
bzw. ist beschädigt.“), any other read failure „Anhang „X“ konnte
|
|
2351
2350
|
gerade nicht geladen werden.“
|
|
2352
2351
|
|
|
2353
|
-
|
|
2352
|
+
#### Mail attachments
|
|
2354
2353
|
|
|
2355
2354
|
The global Terms PDFs (`AGB`), the EU legal-guarantee notice or a flyer are
|
|
2356
2355
|
documents of the attachment library. A document has a name, the customer
|
|
@@ -2378,13 +2377,9 @@ duplicate runtime schemas in an Admin consumer. The base's own exports remain
|
|
|
2378
2377
|
internal server and generated Admin integration surfaces; Email HTTP contracts
|
|
2379
2378
|
are published only by `@hartl-services/medusa-contracts`.
|
|
2380
2379
|
|
|
2381
|
-
|
|
2380
|
+
#### Rollout
|
|
2382
2381
|
|
|
2383
|
-
|
|
2384
|
-
use Medusa's local Event Bus. Set `REDIS_URL` only when intentionally exercising
|
|
2385
|
-
the direct Redis provider. The backend HTTP runner ignores ambient Redis and
|
|
2386
|
-
SMTP settings and injects an isolated Redis container, unique queue, and
|
|
2387
|
-
token-protected SMTP fixture for every child.
|
|
2382
|
+
Local development notes: see "Development".
|
|
2388
2383
|
|
|
2389
2384
|
This architecture replaces the old Documents and Affiliate delivery baselines
|
|
2390
2385
|
without a backfill or compatibility layer. Existing local databases from those
|
|
@@ -2400,7 +2395,7 @@ production business data exists; a populated database requires a separately
|
|
|
2400
2395
|
reviewed migration. No old delivery rows or queued jobs are migrated or purged
|
|
2401
2396
|
by Email.
|
|
2402
2397
|
|
|
2403
|
-
|
|
2398
|
+
### Dokumente
|
|
2404
2399
|
|
|
2405
2400
|
The base issues order documents: one total invoice per order after the final
|
|
2406
2401
|
shipment and one negative cancellation document for each eligible payment
|
|
@@ -2408,10 +2403,9 @@ refund. An Admin user can regenerate issued documents in place for exceptional
|
|
|
2408
2403
|
corrections. The base ships default German document markup, so a new shop needs
|
|
2409
2404
|
no template code; a project can optionally replace the HTML/CSS factory. PDF
|
|
2410
2405
|
generation, private file storage, customer authorization and durable
|
|
2411
|
-
document-readiness facts stay inside the `documents` module.
|
|
2412
|
-
separate documents package since 1.0.0.
|
|
2406
|
+
document-readiness facts stay inside the `documents` module.
|
|
2413
2407
|
|
|
2414
|
-
|
|
2408
|
+
#### What it does
|
|
2415
2409
|
|
|
2416
2410
|
- Listens to Medusa's `shipment.created` event.
|
|
2417
2411
|
- Creates an invoice only if the order is fully shipped and not cancelled.
|
|
@@ -2478,7 +2472,7 @@ separate documents package since 1.0.0.
|
|
|
2478
2472
|
- Reconciles non-canceled physical fulfillments periodically so a lost local
|
|
2479
2473
|
event can be repaired idempotently, including after shipment.
|
|
2480
2474
|
|
|
2481
|
-
|
|
2475
|
+
#### Configuration
|
|
2482
2476
|
|
|
2483
2477
|
The invoice workflow reads the order's frozen B2B evidence from the base's own
|
|
2484
2478
|
`tax_compliance` module (see "Steuer-Compliance"); register the Tax provider
|
|
@@ -2537,7 +2531,7 @@ The `documents` module is always registered with the base (no `documentsEnabled`
|
|
|
2537
2531
|
switch); the Admin switches it off at run time through the feature toggles
|
|
2538
2532
|
below. `medusa db:migrate` creates its tables.
|
|
2539
2533
|
|
|
2540
|
-
|
|
2534
|
+
#### The `/config` subpath
|
|
2541
2535
|
|
|
2542
2536
|
`@hartl-services/medusa-base/config` exports, next to the environment helpers,
|
|
2543
2537
|
build-time values for `medusa-config.ts` and tests:
|
|
@@ -2552,7 +2546,7 @@ build-time values for `medusa-config.ts` and tests:
|
|
|
2552
2546
|
The document types (`DocumentMerchantOption`, `DocumentHtmlFactory`,
|
|
2553
2547
|
`DocumentRenderInput`, ...) come from `@hartl-services/medusa-base/types`.
|
|
2554
2548
|
|
|
2555
|
-
|
|
2549
|
+
#### Layout, logo, payment terms and shop theme
|
|
2556
2550
|
|
|
2557
2551
|
The default markup prints an A4 letterhead: logo top left, document title and
|
|
2558
2552
|
key data (number, order, date, customer UID for exempt B2B supplies) on the
|
|
@@ -2595,7 +2589,7 @@ fonts as `data:` URLs; the container's Chromium has only its system fonts. A
|
|
|
2595
2589
|
shop passes its factory through the preset option `documentHtmlFactory` (see
|
|
2596
2590
|
"Shop-Config-Preset").
|
|
2597
2591
|
|
|
2598
|
-
|
|
2592
|
+
#### Merchant data
|
|
2599
2593
|
|
|
2600
2594
|
The legal sender printed on every document comes from the base plugin's shop
|
|
2601
2595
|
master data (Admin → Settings → Hartl Services Plugins → **Shop-Stammdaten**).
|
|
@@ -2627,7 +2621,7 @@ Requirements/couplings:
|
|
|
2627
2621
|
- The Tax provider `tp_tax_compliance` of the base's Steuer-Compliance on the
|
|
2628
2622
|
regions, so orders carry UID-based exemptions.
|
|
2629
2623
|
- A Chromium/Chrome executable and a Medusa File provider that supports
|
|
2630
|
-
private files (see "
|
|
2624
|
+
private files (see "Shop integration" below for the sandbox/non-root note).
|
|
2631
2625
|
Documents stores PDFs through `Modules.FILE` and streams them only through
|
|
2632
2626
|
its own authenticated routes, so it works with any File provider and does
|
|
2633
2627
|
not need the S3 file storage or its `/files` routes.
|
|
@@ -2643,8 +2637,7 @@ Requirements/couplings:
|
|
|
2643
2637
|
batch/lot data on invoices and packing lists (see "Optional Batch
|
|
2644
2638
|
Management integration" below).
|
|
2645
2639
|
|
|
2646
|
-
|
|
2647
|
-
### Feature toggles
|
|
2640
|
+
#### Feature toggles
|
|
2648
2641
|
|
|
2649
2642
|
The Documents module declares its toggles to the base under the plugin key
|
|
2650
2643
|
`documents`. An Admin switches them under Settings → "Hartl
|
|
@@ -2682,15 +2675,14 @@ using `PluginFeatureGate` from
|
|
|
2682
2675
|
No workflow hook and nothing on the cart, checkout or fulfillment path reads
|
|
2683
2676
|
a toggle.
|
|
2684
2677
|
|
|
2685
|
-
|
|
2678
|
+
#### Shop integration
|
|
2686
2679
|
|
|
2687
2680
|
The demo shop (`apps/backend`) gets everything from the preset (see
|
|
2688
2681
|
"Shop-Config-Preset"): `documents.numbering.timeZone` is its `timeZone`
|
|
2689
2682
|
option, `documents.htmlFactory` its optional `documentHtmlFactory` and
|
|
2690
2683
|
`documents.pdf.executablePath` the `DOCUMENT_PDF_EXECUTABLE` variable. The
|
|
2691
|
-
development UID `ATU00000000` is a placeholder
|
|
2692
|
-
|
|
2693
|
-
merchant UID in the Shop-Stammdaten before issuing production invoices.
|
|
2684
|
+
development UID `ATU00000000` is a placeholder that Documents never prints
|
|
2685
|
+
(see "UID check in the checkout").
|
|
2694
2686
|
|
|
2695
2687
|
`htmlFactory` is intentionally the only presentation extension point. It gets
|
|
2696
2688
|
a fully typed value snapshot (`DocumentTemplateInput` or
|
|
@@ -2708,7 +2700,7 @@ directory and removes it afterward. The Chromium child is also disconnected
|
|
|
2708
2700
|
from the user's session bus and legacy keyring socket, so it cannot access the
|
|
2709
2701
|
operator's normal browser profile or desktop keyring, including under WSLg.
|
|
2710
2702
|
|
|
2711
|
-
|
|
2703
|
+
#### Restore check for private files
|
|
2712
2704
|
|
|
2713
2705
|
After restoring the object store (or the local file directory), check that
|
|
2714
2706
|
every issued document's PDF can be read through the active Medusa File
|
|
@@ -2722,7 +2714,7 @@ pnpm exec medusa exec ./node_modules/@hartl-services/medusa-base/.medusa/server/
|
|
|
2722
2714
|
It fails on the first issued document without a stored file and otherwise
|
|
2723
2715
|
logs the number of verified files and bytes.
|
|
2724
2716
|
|
|
2725
|
-
|
|
2717
|
+
#### Clean baseline and first deployment
|
|
2726
2718
|
|
|
2727
2719
|
The module intentionally contains one generated eight-table baseline,
|
|
2728
2720
|
`Migration20260827061749`. It removes the former `documents_delivery` table
|
|
@@ -2768,7 +2760,7 @@ Apply normal pending module migrations after later changes or upgrades with
|
|
|
2768
2760
|
does not require link synchronization by itself; `db:sync-links` above is the
|
|
2769
2761
|
safe shop-wide first-deployment step for links owned elsewhere.
|
|
2770
2762
|
|
|
2771
|
-
|
|
2763
|
+
#### Storefront contract
|
|
2772
2764
|
|
|
2773
2765
|
All three endpoints require a customer session or bearer token. There is no
|
|
2774
2766
|
guest lookup endpoint and no direct File-provider URL.
|
|
@@ -2807,7 +2799,7 @@ Storefront's configured Medusa SDK. A consumer that needs a runtime guard may
|
|
|
2807
2799
|
opt in to `@hartl-services/medusa-contracts/schemas/<feature>`; it must
|
|
2808
2800
|
not import either the plugin implementation or generated `.medusa` output.
|
|
2809
2801
|
|
|
2810
|
-
|
|
2802
|
+
#### Admin order detail
|
|
2811
2803
|
|
|
2812
2804
|
When Documents is enabled, the Medusa Admin order detail shows a
|
|
2813
2805
|
**Dokumente** widget. It lists the issued documents for that order (kind and
|
|
@@ -2827,7 +2819,7 @@ The Admin API is deliberately scoped by both order and document ID:
|
|
|
2827
2819
|
All authenticated Admin users can inspect order documents; a document from a
|
|
2828
2820
|
different order returns `404` from the download endpoint.
|
|
2829
2821
|
|
|
2830
|
-
|
|
2822
|
+
##### Internal packing lists
|
|
2831
2823
|
|
|
2832
2824
|
The separate **Packlisten** order widget lists every physical fulfillment in
|
|
2833
2825
|
creation order, each with a status badge (Nicht erstellt, Ausstehend, Wird
|
|
@@ -2851,7 +2843,7 @@ Packing lists never pass through the Documents customer-fact pipeline. They are
|
|
|
2851
2843
|
also stored outside `documents_document`, so the authenticated Store document
|
|
2852
2844
|
queries cannot expose them accidentally.
|
|
2853
2845
|
|
|
2854
|
-
|
|
2846
|
+
##### Regenerating an invoice
|
|
2855
2847
|
|
|
2856
2848
|
The widget offers **Rechnung ausstellen** only while the order has no
|
|
2857
2849
|
invoice; once one exists, every document row has a regenerate icon button
|
|
@@ -2872,7 +2864,7 @@ above. A cancellation or future credit note is instead rendered from its
|
|
|
2872
2864
|
persisted snapshot, retaining its original amount, source invoice reference,
|
|
2873
2865
|
number and issue date. It also emits no customer communication event.
|
|
2874
2866
|
|
|
2875
|
-
|
|
2867
|
+
##### Financial document numbers
|
|
2876
2868
|
|
|
2877
2869
|
Documents assigns numbers in these annual ranges:
|
|
2878
2870
|
|
|
@@ -2895,7 +2887,7 @@ increments a range. A permanently failed document retains its number and
|
|
|
2895
2887
|
failure reason as the explanation for that range's gap; reserved numbers are
|
|
2896
2888
|
never recycled or reassigned.
|
|
2897
2889
|
|
|
2898
|
-
|
|
2890
|
+
##### Cancellation documents for payment refunds
|
|
2899
2891
|
|
|
2900
2892
|
Medusa's refund event identifies the payment, so the plugin enumerates its
|
|
2901
2893
|
persisted refund records and claims one cancellation document using each
|
|
@@ -2917,7 +2909,7 @@ not create a second customer fact. For a canceled Order, individual Refund
|
|
|
2917
2909
|
facts are suppressed in favor of the authoritative Order-cancellation
|
|
2918
2910
|
resolution and its sorted artifact list.
|
|
2919
2911
|
|
|
2920
|
-
|
|
2912
|
+
#### Optional Batch Management integration
|
|
2921
2913
|
|
|
2922
2914
|
If `@hartl-services/medusa-batch-management` version `0.0.3` or later is registered, Documents detects
|
|
2923
2915
|
its `getOrderDocumentBatchSnapshot(orderId)` capability structurally. For every
|
|
@@ -2948,7 +2940,7 @@ list revision and serves the newest ready PDF. Returns do not rewrite the
|
|
|
2948
2940
|
historical pick instruction. Fulfillment cancellation marks the packing list as
|
|
2949
2941
|
canceled and prevents a concurrent render from being published as active.
|
|
2950
2942
|
|
|
2951
|
-
|
|
2943
|
+
#### Document facts and artifacts
|
|
2952
2944
|
|
|
2953
2945
|
Documents owns no Notification or delivery state. Its integration boundary is
|
|
2954
2946
|
the strict versioned event contract exported from
|
|
@@ -2969,7 +2961,7 @@ worker has lost its lease, the unreferenced private File is deleted best-effort;
|
|
|
2969
2961
|
a hard process exit in that external File/database gap can leave a storage
|
|
2970
2962
|
orphan but cannot commit another Artifact, carrier or resolution.
|
|
2971
2963
|
|
|
2972
|
-
|
|
2964
|
+
##### Exact outgoing event contracts
|
|
2973
2965
|
|
|
2974
2966
|
The server-only Contracts subpath
|
|
2975
2967
|
`@hartl-services/medusa-contracts/events/documents` exports the
|
|
@@ -3053,8 +3045,6 @@ before the process can persist success, so an ambiguous attempt becomes
|
|
|
3053
3045
|
`unknown` and is not automatically sent again. Any later resend is an explicit
|
|
3054
3046
|
Email Admin action and a new logical message.
|
|
3055
3047
|
|
|
3056
|
-
### Development
|
|
3057
|
-
|
|
3058
3048
|
#### Public Storefront and Admin contracts
|
|
3059
3049
|
|
|
3060
3050
|
The central contracts package exports the Documents Store and Admin DTOs, Zod
|
|
@@ -3095,16 +3085,15 @@ const response = await sdk.client.fetch<ListAdminOrderPackingListsResponse>(
|
|
|
3095
3085
|
);
|
|
3096
3086
|
```
|
|
3097
3087
|
|
|
3098
|
-
|
|
3088
|
+
### Steuer-Compliance
|
|
3099
3089
|
|
|
3100
|
-
|
|
3101
|
-
3.0.0: the EU tax compliance (module `tax_compliance`, the Tax Module provider
|
|
3090
|
+
The EU tax compliance (module `tax_compliance`, the Tax Module provider
|
|
3102
3091
|
`tp_tax_compliance`, the UID check routes, the order-evidence freeze and the
|
|
3103
|
-
apportioned shipping tax) is part of the base
|
|
3104
|
-
validation evidence; a Storefront never imports
|
|
3105
|
-
`@hartl-services/medusa-contracts`.
|
|
3092
|
+
apportioned shipping tax) is part of the base since 3.0.0 (see "Upgrading").
|
|
3093
|
+
The module owns the durable validation evidence; a Storefront never imports
|
|
3094
|
+
its implementation, only `@hartl-services/medusa-contracts`.
|
|
3106
3095
|
|
|
3107
|
-
|
|
3096
|
+
#### Configuration
|
|
3108
3097
|
|
|
3109
3098
|
Register the Tax Module provider in `medusa-config.ts` (the Shop-Config-Preset
|
|
3110
3099
|
does this); the module itself boots with the base and has no options:
|
|
@@ -3134,7 +3123,7 @@ The merchant's own country and VAT number come only from the Shop-Stammdaten
|
|
|
3134
3123
|
select the qualified VAT validator (currently only the Austrian FinanzOnline
|
|
3135
3124
|
adapter). While either is missing, carts get ordinary B2C tax.
|
|
3136
3125
|
|
|
3137
|
-
|
|
3126
|
+
#### FinanzOnline settings
|
|
3138
3127
|
|
|
3139
3128
|
Declared on the Steuer-Compliance descriptor and edited on its Admin page
|
|
3140
3129
|
(Settings → Hartl Services Plugins → Steuer-Compliance):
|
|
@@ -3185,7 +3174,7 @@ qualifizierte UID-Abfrage.").
|
|
|
3185
3174
|
answers ordinary B2C rates. The order tax path needs no merchant: it checks
|
|
3186
3175
|
the evidence frozen under the order ID.
|
|
3187
3176
|
|
|
3188
|
-
|
|
3177
|
+
#### Feature toggles
|
|
3189
3178
|
|
|
3190
3179
|
Steuer-Compliance declares its switch and three features under the plugin key
|
|
3191
3180
|
`tax_compliance` (`TAX_COMPLIANCE_PLUGIN_KEY` / `TAX_COMPLIANCE_FEATURES` from
|
|
@@ -3224,7 +3213,7 @@ values; the `plugin-features.updated.v1` subscriber and the
|
|
|
3224
3213
|
sync (no row) both count as on; when the row cannot be read both count as
|
|
3225
3214
|
off (ordinary B2C rates, Medusa's shipping tax).
|
|
3226
3215
|
|
|
3227
|
-
|
|
3216
|
+
#### UID check in the checkout
|
|
3228
3217
|
|
|
3229
3218
|
Public Store request, response, schema, and path imports belong to
|
|
3230
3219
|
`@hartl-services/medusa-contracts`:
|
|
@@ -3334,7 +3323,7 @@ gets 3 s, so the request stays below a storefront's 15 s checkout wait and the
|
|
|
3334
3323
|
database transaction holding the UID lock never waits on a hanging
|
|
3335
3324
|
FinanzOnline call; a timeout answers `unavailable` (`technical_error`).
|
|
3336
3325
|
|
|
3337
|
-
|
|
3326
|
+
#### Saved customer addresses
|
|
3338
3327
|
|
|
3339
3328
|
A saved customer address carries its UID in `metadata.vat_number`
|
|
3340
3329
|
(`TAX_IDENTIFICATION_METADATA_KEY` in the Contracts). A logged-in customer
|
|
@@ -3382,7 +3371,7 @@ facts likewise receive ordinary B2C tax, without a warning. The registry is
|
|
|
3382
3371
|
country-based: it currently provides only the Austrian FinanzOnline adapter;
|
|
3383
3372
|
an unsupported merchant country deliberately resolves to `unavailable`.
|
|
3384
3373
|
|
|
3385
|
-
|
|
3374
|
+
#### Frozen order evidence
|
|
3386
3375
|
|
|
3387
3376
|
Cart tax workflows pass only the opaque current evidence ID and the merchant
|
|
3388
3377
|
profile (country and UID) to the provider. Order tax workflows use evidence
|
|
@@ -3411,19 +3400,104 @@ wording from the completed order (see "Dokumente"). A later cart change or
|
|
|
3411
3400
|
validation cannot alter an already issued document; B2C, invalid, ineligible
|
|
3412
3401
|
and unavailable evidence adds no tax-proof wording.
|
|
3413
3402
|
|
|
3414
|
-
|
|
3403
|
+
### Versandkostenfrei
|
|
3404
|
+
|
|
3405
|
+
The shop-wide free-shipping threshold with a separate threshold for
|
|
3406
|
+
affiliates is part of the base since 5.0.0 (module `free_shipping`, see
|
|
3407
|
+
"Upgrading"). It has no Admin page and no HTTP route of its own: its switch
|
|
3408
|
+
and its settings are edited on the generic plugin page **Settings → Hartl
|
|
3409
|
+
Services Plugins → Versandkostenfrei**
|
|
3410
|
+
(`/settings/hartl-services/plugin/free_shipping`).
|
|
3411
|
+
|
|
3412
|
+
#### What it does
|
|
3413
|
+
|
|
3414
|
+
- Keeps two automatic Medusa promotions (`AUTO-FREE-SHIPPING-STANDARD`,
|
|
3415
|
+
`AUTO-FREE-SHIPPING-AFFILIATE`, 100 % off shipping) in sync with the
|
|
3416
|
+
settings and the plugin switch:
|
|
3417
|
+
- right after the settings are saved in the Admin
|
|
3418
|
+
(`plugin-settings.updated.v1` subscriber),
|
|
3419
|
+
- right after the switch is changed in the Admin
|
|
3420
|
+
(`plugin-features.updated.v1` subscriber),
|
|
3421
|
+
- every 15 minutes through the `free-shipping-promotion-sync` scheduled job
|
|
3422
|
+
(worker instances only), which also repairs drift and covers a lost event.
|
|
3423
|
+
|
|
3424
|
+
Every run holds one Locking-module lock (`free-shipping-promotion-sync`,
|
|
3425
|
+
60 s timeout) around the whole sync, so the job and the two subscribers never
|
|
3426
|
+
interleave their rule changes, and the last run always writes the latest
|
|
3427
|
+
values. The shop's configured Locking provider applies (Medusa's default
|
|
3428
|
+
in-memory provider serializes within one process only; use a shared
|
|
3429
|
+
provider such as `@medusajs/locking-redis` when server and worker are
|
|
3430
|
+
separate processes). The sync writes only differences, so a run against
|
|
3431
|
+
in-sync promotions is read-only. Changed rules are created before outdated
|
|
3432
|
+
ones are deleted, so an automatic promotion never exists without its rules
|
|
3433
|
+
(which would grant free shipping to every cart).
|
|
3434
|
+
- Adds the cart's affiliate qualification to the promotion context (hook on
|
|
3435
|
+
`updateCartPromotionsWorkflow`). This uses the `affiliate` module only if it
|
|
3436
|
+
is registered (`tryResolveOptional`); without the Affiliate plugin, only the
|
|
3437
|
+
default threshold applies.
|
|
3438
|
+
|
|
3439
|
+
#### Settings
|
|
3440
|
+
|
|
3441
|
+
Stored in `plugin_setting_state` under the plugin key `free_shipping`; every
|
|
3442
|
+
setting has a default, so the feature works without ever saving the page.
|
|
3443
|
+
|
|
3444
|
+
| Key | Type | Default | Label | Effect |
|
|
3445
|
+
| ----------------------------------------- | ------- | ------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
3446
|
+
| `default_threshold` | number ≥ 0 | 50 | Kostenlose Lieferung ab (€) | `item_total` threshold of `AUTO-FREE-SHIPPING-STANDARD` |
|
|
3447
|
+
| `affiliate_threshold` | number ≥ 0 | 100 | Für Affiliates ab (€) | `item_total` threshold of `AUTO-FREE-SHIPPING-AFFILIATE` |
|
|
3448
|
+
| `affiliate_applies_to_account_holders` | boolean | on | Gilt, wenn der Kunde selbst Affiliate ist | the cart's customer is an affiliate → affiliate threshold |
|
|
3449
|
+
| `affiliate_applies_to_referred_customers` | boolean | off | Gilt, wenn der Warenkorb über einen Affiliate-Link oder -Code zugeordnet wurde | the cart is assigned to an affiliate referral → affiliate threshold |
|
|
3450
|
+
|
|
3451
|
+
The base validates the values (a negative threshold is rejected with 400).
|
|
3452
|
+
Server code reads them with `resolveFreeShippingSettings(container)`
|
|
3453
|
+
(`src/free-shipping/free-shipping-settings.ts`, a typed wrapper around
|
|
3454
|
+
`getPluginSettings`). The two boolean rules are evaluated in the cart hook on
|
|
3455
|
+
every promotion computation, so they take effect without a promotion resync;
|
|
3456
|
+
the thresholds reach the promotions through the sync described above.
|
|
3457
|
+
|
|
3458
|
+
The cart hook is fail-safe: if the settings cannot be read, the error is
|
|
3459
|
+
reported with `logger.error` (forwarded to Sentry by the logger wrapper) and
|
|
3460
|
+
the cart is computed with the two rules this process last read successfully,
|
|
3461
|
+
or with their declared defaults (account holders on, referred customers off)
|
|
3462
|
+
when it has not read them yet. The cart request never fails because of it.
|
|
3463
|
+
The job and the subscribers let a read failure propagate (the job runs again,
|
|
3464
|
+
the event is retried).
|
|
3465
|
+
|
|
3466
|
+
#### Feature toggle
|
|
3467
|
+
|
|
3468
|
+
The switch lives under the plugin key `free_shipping`; Storefronts read it
|
|
3469
|
+
from `GET /store/plugin-features/free_shipping`.
|
|
3470
|
+
|
|
3471
|
+
| Key | Label | Default | Gates | Keeps running while off |
|
|
3472
|
+
| --------------- | ----------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
3473
|
+
| (plugin switch) | Versandkostenfrei | on | status of the promotions `AUTO-FREE-SHIPPING-STANDARD` and `AUTO-FREE-SHIPPING-AFFILIATE` (active while on, inactive while off) | the settings (thresholds stay editable and are synced into the inactive promotions), the `free-shipping-promotion-sync` job (it deactivates the promotions), the promotion-context hook on the cart path |
|
|
3474
|
+
|
|
3475
|
+
There are no sub-features. No route answers 404 because of the switch; the
|
|
3476
|
+
store-visible effect of "off" is that the two automatic promotions are
|
|
3477
|
+
inactive. The switch takes effect immediately on an Admin change and at the
|
|
3478
|
+
latest with the next 15-minute job run otherwise.
|
|
3479
|
+
|
|
3480
|
+
#### Migrations
|
|
3481
|
+
|
|
3482
|
+
The module ships the migrations of the former package unchanged: the 0.2.0
|
|
3483
|
+
migration dropped the old `free_shipping_settings` table and carried stored
|
|
3484
|
+
thresholds and affiliate rules over into `plugin_setting_state` /
|
|
3485
|
+
`plugin_feature_state`. A shop that used the former package keeps its data;
|
|
3486
|
+
`medusa db:migrate` after upgrading is enough.
|
|
3487
|
+
|
|
3488
|
+
### Dateispeicher (S3)
|
|
3415
3489
|
|
|
3416
3490
|
The base provides a Medusa File Module provider for durable S3-compatible
|
|
3417
3491
|
asset storage with a local, TTL-based read cache (`providers/s3-cache`), and
|
|
3418
3492
|
the `/files` gateway routes every provider URL points at. It supports AWS S3,
|
|
3419
|
-
MinIO and compatible endpoints through AWS SDK v3.
|
|
3420
|
-
|
|
3421
|
-
|
|
3493
|
+
MinIO and compatible endpoints through AWS SDK v3. The provider identifier is
|
|
3494
|
+
`s3-cache` and its resolve path `@hartl-services/medusa-base/providers/s3-cache`
|
|
3495
|
+
(part of the base since 1.0.0, see "Upgrading").
|
|
3422
3496
|
|
|
3423
3497
|
S3 is always the source of truth. The local cache is a per-replica performance
|
|
3424
3498
|
optimization and may be discarded at any time without data loss.
|
|
3425
3499
|
|
|
3426
|
-
|
|
3500
|
+
#### Coverage
|
|
3427
3501
|
|
|
3428
3502
|
The provider covers every asset that uses Medusa's native `Modules.FILE`
|
|
3429
3503
|
service:
|
|
@@ -3437,7 +3511,7 @@ It cannot transparently back up a plugin that writes directly to its own local
|
|
|
3437
3511
|
directory, own S3 client, or a third-party URL. Such integrations must be
|
|
3438
3512
|
changed to use `Modules.FILE` or migrated explicitly.
|
|
3439
3513
|
|
|
3440
|
-
|
|
3514
|
+
#### Configuration
|
|
3441
3515
|
|
|
3442
3516
|
```ts
|
|
3443
3517
|
// medusa-config.ts
|
|
@@ -3513,7 +3587,7 @@ options) is optional; see
|
|
|
3513
3587
|
[Active S3 options and defaults](#active-s3-options-and-defaults) below. The
|
|
3514
3588
|
provider has no database migrations to run.
|
|
3515
3589
|
|
|
3516
|
-
|
|
3590
|
+
#### Environment helpers
|
|
3517
3591
|
|
|
3518
3592
|
`@hartl-services/medusa-base/config` also exports small helpers for reading
|
|
3519
3593
|
the deployment variables in `medusa-config.ts`:
|
|
@@ -3529,7 +3603,7 @@ the deployment variables in `medusa-config.ts`:
|
|
|
3529
3603
|
- `resolveExpirationStrategy(process.env, defaultValue)` — reads
|
|
3530
3604
|
`S3_CACHE_STORAGE_EXPIRATION_STRATEGY` (`"absolute"` or `"sliding"`).
|
|
3531
3605
|
|
|
3532
|
-
|
|
3606
|
+
#### Runtime configuration
|
|
3533
3607
|
|
|
3534
3608
|
The demo shop (`apps/backend`) enables S3 Cache Storage as the sole Medusa
|
|
3535
3609
|
File Module provider through the preset; there is no per-deployment feature
|
|
@@ -3549,7 +3623,7 @@ S3 keys unless the matching database and storage rollback is being performed.
|
|
|
3549
3623
|
The local provider cannot read S3-backed IDs. Treat a storage-provider change
|
|
3550
3624
|
as a controlled deployment cutover, not as a runtime storage router.
|
|
3551
3625
|
|
|
3552
|
-
|
|
3626
|
+
##### Active S3 options and defaults
|
|
3553
3627
|
|
|
3554
3628
|
In this demo host, `bucket`, `region`, and the File Gateway base URL are
|
|
3555
3629
|
required.
|
|
@@ -3571,7 +3645,7 @@ URLs may use only HTTP(S), with no credentials, query string, or fragment.
|
|
|
3571
3645
|
Prefixes and provider keys reject absolute paths, traversal, control
|
|
3572
3646
|
characters, and backslashes.
|
|
3573
3647
|
|
|
3574
|
-
|
|
3648
|
+
#### Object and access model
|
|
3575
3649
|
|
|
3576
3650
|
New uploads have opaque keys. The original filename is stored only as encoded
|
|
3577
3651
|
object metadata:
|
|
@@ -3606,7 +3680,7 @@ successful remote write/delete evicts the affected entry only after S3 has
|
|
|
3606
3680
|
accepted it. Do not share the cache directory between replicas; use a bounded
|
|
3607
3681
|
`emptyDir` or per-pod volume and monitor free disk space.
|
|
3608
3682
|
|
|
3609
|
-
|
|
3683
|
+
#### Backup and restore
|
|
3610
3684
|
|
|
3611
3685
|
A complete restore requires both a PostgreSQL backup and the versioned object
|
|
3612
3686
|
store. The cache is not a backup and is intentionally excluded.
|
|
@@ -3629,7 +3703,7 @@ count and total bytes without modifying data:
|
|
|
3629
3703
|
medusa exec ./node_modules/@hartl-services/medusa-base/.medusa/server/src/documents/scripts/verify-documents-file-storage.js
|
|
3630
3704
|
```
|
|
3631
3705
|
|
|
3632
|
-
|
|
3706
|
+
#### HTTP contract
|
|
3633
3707
|
|
|
3634
3708
|
The gateway routes expose `GET /files/:asset` for public immutable files and
|
|
3635
3709
|
`GET /files/private/:token` for signed private capability URLs. Both call
|
|
@@ -3640,126 +3714,234 @@ Paths, query types, and the runtime query schema are exported from the separate
|
|
|
3640
3714
|
normally consume the complete URL returned by Medusa and never construct a
|
|
3641
3715
|
private URL themselves.
|
|
3642
3716
|
|
|
3643
|
-
## Shop
|
|
3717
|
+
## Shop behaviour and operations
|
|
3644
3718
|
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
bus, SMTP notifications, S3 file storage, the tax and Stripe payment providers,
|
|
3648
|
-
the Admin path, the Sentry logger bridge and the environment validation are
|
|
3649
|
-
assembled there; the shop supplies only its own facts. It reuses the `/config`
|
|
3650
|
-
helpers above for environment parsing. It is a separate subpath because it
|
|
3651
|
-
loads the Sentry logger wrapper and with it `@sentry/node`; `/config` stays
|
|
3652
|
-
free of it.
|
|
3719
|
+
Behaviour the base adds to every shop without a toggle, and the
|
|
3720
|
+
`medusa-config.ts` helpers for operating it.
|
|
3653
3721
|
|
|
3654
|
-
|
|
3655
|
-
options, and its Tax provider) plus four feature plugins, in this order:
|
|
3656
|
-
`medusa-base`, `medusa-free-shipping`, `medusa-batch-management`,
|
|
3657
|
-
`medusa-affiliate`, `medusa-payload-sync`.
|
|
3722
|
+
### Root redirect
|
|
3658
3723
|
|
|
3659
|
-
|
|
3724
|
+
Medusa serves nothing at `/`, so a deployed domain answers it with a 404. The
|
|
3725
|
+
base registers `GET /` and answers with a `302` to the shop's `admin.path`
|
|
3726
|
+
(read from the config module, fallback `/app`), so opening the bare domain
|
|
3727
|
+
lands on the admin dashboard in every shop and in local development without a
|
|
3728
|
+
reverse-proxy rule. The redirect is temporary on purpose: browsers cache a
|
|
3729
|
+
`301` on the bare domain essentially forever, which would be painful to undo
|
|
3730
|
+
should `/` ever get real content. A shop that needs its own `/` defines
|
|
3731
|
+
`src/api/route.ts` itself.
|
|
3660
3732
|
|
|
3661
|
-
|
|
3662
|
-
pnpm add @hartl-services/medusa-base \
|
|
3663
|
-
@hartl-services/medusa-free-shipping \
|
|
3664
|
-
@hartl-services/medusa-batch-management \
|
|
3665
|
-
@hartl-services/medusa-affiliate \
|
|
3666
|
-
@hartl-services/medusa-payload-sync \
|
|
3667
|
-
@medusajs/event-bus-redis @medusajs/payment-stripe \
|
|
3668
|
-
@sentry/node
|
|
3669
|
-
```
|
|
3733
|
+
### Cart-promotion link repair
|
|
3670
3734
|
|
|
3671
|
-
|
|
3735
|
+
Medusa's `deletePromotionsWorkflow` only soft-deletes a promotion and leaves
|
|
3736
|
+
its `cart_promotion` link rows behind, so every cart that had it applied
|
|
3737
|
+
reads back a `null` entry inside `cart.promotions`. The base removes those
|
|
3738
|
+
links itself:
|
|
3739
|
+
|
|
3740
|
+
- The `deletePromotionsWorkflow.hooks.promotionsDeleted` hook
|
|
3741
|
+
(`workflows/hooks/dismiss-deleted-promotion-cart-links`) dismisses the
|
|
3742
|
+
links of the deleted promotions and restores them if a later step fails.
|
|
3743
|
+
- The migration script `dismiss-orphaned-cart-promotion-links` runs once in
|
|
3744
|
+
`medusa db:migrate` and removes the links that already dangle from earlier
|
|
3745
|
+
deletions. Medusa tracks migration scripts by file name in
|
|
3746
|
+
`script_migrations`, so the name must not change.
|
|
3747
|
+
|
|
3748
|
+
Nothing to configure; `medusa db:migrate` after installing or upgrading to
|
|
3749
|
+
0.4.1 is enough.
|
|
3750
|
+
|
|
3751
|
+
### Draft orders and 0 € orders
|
|
3752
|
+
|
|
3753
|
+
Draft orders (Medusa's `@medusajs/draft-order` plugin, loaded by default) are
|
|
3754
|
+
the shop's way to enter an order in the Admin: sample shipments, orders taken
|
|
3755
|
+
by phone, replacements. Item prices and the shipping amount are set directly
|
|
3756
|
+
in the draft; converting it never involves a payment provider.
|
|
3757
|
+
|
|
3758
|
+
- **Customer order confirmation off by default.** The
|
|
3759
|
+
`createOrderWorkflow.hooks.orderCreated` hook
|
|
3760
|
+
(`workflows/hooks/draft-order-confirmation-default`) sets Medusa's
|
|
3761
|
+
`no_notification` on every new draft, also when an API caller sent
|
|
3762
|
+
`no_notification_order: false`. The Admin switches it per draft in the
|
|
3763
|
+
**Bestellbestätigung** widget on the draft's detail page
|
|
3764
|
+
(`GET`/`POST /admin/draft-orders/:id/order-confirmation`, see the E-Mail
|
|
3765
|
+
"Admin API"). On conversion the `order-placed` subscriber records the
|
|
3766
|
+
customer confirmation as `suppressed` (`notifications_disabled`); the copy
|
|
3767
|
+
to the shop's order address is still sent. The shipment mail follows the
|
|
3768
|
+
Admin's "notify customer" choice per shipment, not this switch.
|
|
3769
|
+
- **0 € orders count as paid.** Medusa derives an order's payment status from
|
|
3770
|
+
its payment collections only, so a converted 0 € draft would stay
|
|
3771
|
+
"Nicht bezahlt". The subscriber `order-zero-total-paid` runs
|
|
3772
|
+
`order-mark-zero-total-paid` on `order.placed`: an order with a total of 0
|
|
3773
|
+
and no payment collection gets a completed 0 € payment collection and reads
|
|
3774
|
+
"Bezahlt" (`payment_status: captured`). Orders that already carry a
|
|
3775
|
+
collection, i.e. every checkout order, are left alone. In SQL, a paid
|
|
3776
|
+
order is one whose linked `payment_collection` (via
|
|
3777
|
+
`order_payment_collection`) has `status = 'completed'`.
|
|
3778
|
+
|
|
3779
|
+
Orders placed before installing this version keep their state.
|
|
3780
|
+
|
|
3781
|
+
### Password policy
|
|
3782
|
+
|
|
3783
|
+
Medusa's `emailpass` provider accepts any non-empty password. The base runs a
|
|
3784
|
+
middleware on `POST /auth/:actor_type/emailpass/register` (customer sign-up,
|
|
3785
|
+
Admin invite) and `POST /auth/:actor_type/emailpass/update` (password reset).
|
|
3786
|
+
It is registered on `/auth/:actor_type/:auth_provider/:action` without
|
|
3787
|
+
`methods` and decides on the decoded, lowercased params, because Express
|
|
3788
|
+
matches literal path segments against the raw path: `/auth/customer/email%70ass/register`
|
|
3789
|
+
and `/auth/Customer/emailpass/register` reach Medusa's handler too, and
|
|
3790
|
+
`Customer` counts as the customer actor here.
|
|
3791
|
+
A password that breaks the rules of the actor is answered with
|
|
3792
|
+
`400 { type: "invalid_data", message }`; the one German message lists every
|
|
3793
|
+
unmet rule, e.g. `Das Passwort erfüllt nicht alle Regeln: mindestens 12
|
|
3794
|
+
Zeichen, eine Ziffer.` The check runs before Medusa's reset-token check, so a
|
|
3795
|
+
rejected password leaves the single-use reset link valid for a second attempt.
|
|
3796
|
+
|
|
3797
|
+
**Admin users** (every actor type except `customer`) always need at least 15
|
|
3798
|
+
characters, counted as Unicode code points, and no composition rules. This
|
|
3799
|
+
follows NIST SP 800-63B Rev. 4 (the US guideline for digital identity) for a
|
|
3800
|
+
password that is the only factor.
|
|
3801
|
+
|
|
3802
|
+
**Customers** follow the settings of the "Kunden-Login" plugin (group
|
|
3803
|
+
"Passwort-Regeln", Settings > "Hartl Services Plugins"). They apply only while
|
|
3804
|
+
`email_password` is on and take effect at once, without a cache:
|
|
3805
|
+
|
|
3806
|
+
| Key | Label | Default | Meaning |
|
|
3807
|
+
| ----------------------------- | ------------------------------------ | ------- | ------- |
|
|
3808
|
+
| `password_min_length` | Mindestlänge | 15 | 8 to 128 characters, whole numbers only (`integer`); NIST SP 800-63B Rev. 4 recommends at least 15 when the password is the only factor |
|
|
3809
|
+
| `password_require_mixed_case` | Groß- und Kleinbuchstaben verlangen | off | at least one uppercase and one lowercase letter |
|
|
3810
|
+
| `password_require_digit` | Ziffer verlangen | off | at least one digit |
|
|
3811
|
+
| `password_require_symbol` | Sonderzeichen verlangen | off | at least one punctuation mark or symbol (Unicode category P or S; whitespace, combining marks and invisible characters do not count) |
|
|
3812
|
+
|
|
3813
|
+
NIST SP 800-63B Rev. 4 advises against composition rules, because they lead to
|
|
3814
|
+
predictable passwords such as "Passwort1!"; a longer password protects better.
|
|
3815
|
+
The Admin texts say so. A missing or invalid stored value falls back to the
|
|
3816
|
+
default of that rule, and if the settings cannot be read at all the default
|
|
3817
|
+
policy (15 characters, no composition rules) applies and a warning is logged,
|
|
3818
|
+
so a registration is never blocked by a settings outage.
|
|
3819
|
+
|
|
3820
|
+
**Route.** `GET /store/customer-auth/password-policy` (publishable key only)
|
|
3821
|
+
answers `{ password_policy: { min_length, require_mixed_case, require_digit,
|
|
3822
|
+
require_symbol } }`, the effective customer rules, and 404 while
|
|
3823
|
+
`email_password` is off. The storefront shows them in its forms and checks
|
|
3824
|
+
them with the same functions as the backend. The types, the path
|
|
3825
|
+
(`customerAuthStorePaths.get.passwordPolicy`) and the rules
|
|
3826
|
+
(`evaluatePasswordPolicy`, `isPasswordAllowed`, `DEFAULT_CUSTOMER_PASSWORD_POLICY`,
|
|
3827
|
+
`ADMIN_PASSWORD_POLICY` in `schemas/password-policy`) come from the Contracts
|
|
3828
|
+
package.
|
|
3829
|
+
|
|
3830
|
+
**Admin passwords and the shared identity.** An `emailpass` identity is shared
|
|
3831
|
+
by all actor types: someone invited as an Admin could register through
|
|
3832
|
+
`/auth/customer/emailpass/register` with the (weaker) customer rules, log in at
|
|
3833
|
+
`/auth/user/emailpass` and accept the invite with that token, which would bind
|
|
3834
|
+
the weak password to an Admin. So the Admin login (`POST` and `GET`
|
|
3835
|
+
`/auth/user/emailpass`, decoded and lowercased like above) checks the body
|
|
3836
|
+
password against the Admin rules and answers the same `400` when the identity
|
|
3837
|
+
has no Admin yet (`app_metadata.user_id` unset). The login of an identity that
|
|
3838
|
+
already belongs to an Admin is never checked. Remaining limit, deliberately not
|
|
3839
|
+
closed: a customer could register through the Admin path with 15 characters and
|
|
3840
|
+
no composition rules although the shop asks for more; that only weakens their
|
|
3841
|
+
own password.
|
|
3842
|
+
|
|
3843
|
+
Login of a customer is not checked, so existing passwords keep working until their next
|
|
3844
|
+
reset, whatever the rules are. Accounts created without HTTP (`medusa user`,
|
|
3845
|
+
seed scripts calling the Auth module) are not checked either.
|
|
3846
|
+
|
|
3847
|
+
### Config helpers
|
|
3848
|
+
|
|
3849
|
+
`@hartl-services/medusa-base/config` holds the generic environment helpers a
|
|
3850
|
+
shop's `medusa-config.ts` needs, so every shop fails at startup with the name
|
|
3851
|
+
of the missing variable instead of a runtime crash. The environment helpers
|
|
3852
|
+
have no dependency on Medusa; the subpath does not load `@sentry/node`.
|
|
3853
|
+
|
|
3854
|
+
| Export | Purpose |
|
|
3855
|
+
|---|---|
|
|
3856
|
+
| `ConfigurationPhase` | `"build" \| "runtime"` |
|
|
3857
|
+
| `MEDUSA_BUILD_PLACEHOLDERS` | Placeholders for `ADMIN_CORS`, `AUTH_CORS`, `COOKIE_SECRET`, `DATABASE_URL`, `JWT_SECRET`, `MEDUSA_BACKEND_URL`, `STOREFRONT_URL`, `STORE_CORS`, `STRIPE_API_KEY`, `STRIPE_WEBHOOK_SECRET` |
|
|
3858
|
+
| `requireEnvironment(environment, names, phase, buildPlaceholders)` | Returns every named value (trimmed) or throws one error listing all missing names; in the build phase `buildPlaceholders` fill in for deployment values |
|
|
3859
|
+
| `optionalEnvironment`, `integerEnvironment`, `booleanEnvironment` | Trimmed optional string, range-checked integer, strict `"true"`/`"false"` boolean |
|
|
3860
|
+
| `allowsInsecureLocalServices(environment, phase)` | `true` for build, development and test |
|
|
3861
|
+
| `resolvePublicOrigin(value, name, allowHttp)`, `shopUrl(origin, path)` | Validate a bare HTTP(S) origin; build an absolute URL that cannot leave the origin |
|
|
3862
|
+
| `resolveStripe(values)` | Checks the `sk_` key and the `whsec_` webhook secret |
|
|
3863
|
+
| `resolveRedisEventBus(environment, phase)` | `{ url } \| undefined`: optional in development, required at runtime elsewhere, never used in a build, authenticated `rediss://` in production |
|
|
3864
|
+
| `createEmailNotificationModuleConfig`, `defineShopEmailConfiguration` (deprecated: `createSmtpNotificationModuleConfig`, `resolveSmtp`, `SMTP_ENVIRONMENT_VARIABLES`, `SMTP_BUILD_PLACEHOLDERS`) | Mail notification provider and E-Mail configuration (see "E-Mail") |
|
|
3865
|
+
| `createS3CacheStorageConfig`, `createS3CacheFileModuleConfig`, `resolveS3Credentials`, `resolveExpirationStrategy`, `S3_BUILD_PLACEHOLDERS` | S3 file storage (see "Dateispeicher (S3)") |
|
|
3866
|
+
| `createDefaultDocumentHtmlFactory`, `DOCUMENTS_BUILD_PLACEHOLDERS` | Documents markup and build placeholder (see "Dokumente") |
|
|
3867
|
+
|
|
3868
|
+
`medusa build` loads the configuration without deployment secrets. Each
|
|
3869
|
+
plugin's own placeholders (SMTP, S3, PDF executable, ...) live with that
|
|
3870
|
+
plugin; the shop merges them:
|
|
3672
3871
|
|
|
3673
3872
|
```ts
|
|
3674
3873
|
// medusa-config.ts
|
|
3675
|
-
import {
|
|
3874
|
+
import {
|
|
3875
|
+
MEDUSA_BUILD_PLACEHOLDERS,
|
|
3876
|
+
requireEnvironment,
|
|
3877
|
+
resolveRedisEventBus,
|
|
3878
|
+
type ConfigurationPhase,
|
|
3879
|
+
} from "@hartl-services/medusa-base/config"
|
|
3676
3880
|
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3881
|
+
const phase: ConfigurationPhase =
|
|
3882
|
+
process.env.MEDUSA_BUILD_PHASE === "true" ? "build" : "runtime"
|
|
3883
|
+
const env = requireEnvironment(
|
|
3884
|
+
process.env,
|
|
3885
|
+
["DATABASE_URL", "JWT_SECRET", "COOKIE_SECRET"],
|
|
3886
|
+
phase,
|
|
3887
|
+
{ ...MEDUSA_BUILD_PLACEHOLDERS /* , ...other plugins' placeholders */ },
|
|
3888
|
+
)
|
|
3889
|
+
const redis = resolveRedisEventBus(process.env, phase)
|
|
3683
3890
|
```
|
|
3684
3891
|
|
|
3685
|
-
|
|
3686
|
-
|
|
3892
|
+
### Sentry logger wrapper
|
|
3893
|
+
|
|
3894
|
+
`@hartl-services/medusa-base/sentry-logger` exports `withSentryErrorReporting(logger)`,
|
|
3895
|
+
formerly published as `@hartl-services/medusa-sentry-logger`. Medusa's HTTP
|
|
3896
|
+
error handler catches thrown route and workflow errors and only logs them, so
|
|
3897
|
+
Sentry's default Node integrations never see them; wrapping the logger reports
|
|
3898
|
+
every `logger.error(...)` and every `Error` passed to `logger.warn(...)` (at
|
|
3899
|
+
level `warning`). It is a plain library import for `medusa-config.ts`, not a
|
|
3900
|
+
Medusa plugin feature, and needs `@sentry/node` (an optional peer dependency):
|
|
3687
3901
|
|
|
3688
3902
|
```ts
|
|
3689
|
-
import {
|
|
3690
|
-
import {
|
|
3903
|
+
import { logger } from "@medusajs/framework/logger";
|
|
3904
|
+
import { withSentryErrorReporting } from "@hartl-services/medusa-base/sentry-logger";
|
|
3691
3905
|
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
)
|
|
3696
|
-
module.exports = defineConfig(config)
|
|
3906
|
+
module.exports = defineConfig({
|
|
3907
|
+
logger: withSentryErrorReporting(logger),
|
|
3908
|
+
// ...
|
|
3909
|
+
});
|
|
3697
3910
|
```
|
|
3698
3911
|
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
- `defineShopConfig(options, environment = process.env)` returns
|
|
3702
|
-
`defineConfig(buildShopConfig(options, environment))`.
|
|
3703
|
-
- `buildShopConfig(options, environment = process.env)` returns the plain
|
|
3704
|
-
config input (`admin`, `logger`, `projectConfig`, `modules`, `plugins`)
|
|
3705
|
-
before `defineConfig`. `modules` and `plugins` are ordinary arrays.
|
|
3706
|
-
- Types: `ShopConfigOptions`, `ShopConfigInput`, `ShopPlugin`,
|
|
3707
|
-
`ShopModule`.
|
|
3912
|
+
Initialize Sentry in `instrumentation.ts` as usual; without a `SENTRY_DSN` the
|
|
3913
|
+
wrapper is inert.
|
|
3708
3914
|
|
|
3709
|
-
|
|
3710
|
-
option (`Shop option "timeZone" must be ...`), so a misconfigured shop
|
|
3711
|
-
fails at boot. They then load the `.env` file (unless disabled) and read and
|
|
3712
|
-
validate the environment; a missing variable fails with the variable's name.
|
|
3915
|
+
### Sentry instrumentation
|
|
3713
3916
|
|
|
3714
|
-
|
|
3917
|
+
`@hartl-services/medusa-base/instrumentation` exports `register()`, the hook
|
|
3918
|
+
Medusa's start command calls from the shop's `instrumentation.ts`. It runs
|
|
3919
|
+
`Sentry.init` with `SENTRY_DSN`, `SENTRY_ENVIRONMENT` (fallback `NODE_ENV`)
|
|
3920
|
+
and `tracesSampleRate: 0`; without a `SENTRY_DSN` Sentry stays inert. The
|
|
3921
|
+
shop's `instrumentation.ts` is one line:
|
|
3715
3922
|
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
| `orderNotificationEmail` | Receives order notification e-mails; must look like an e-mail address. |
|
|
3720
|
-
| `timeZone` | IANA time zone (validated with `Intl`). Chooses the annual document-number period, the printed dates on documents and the affiliate consignment reminder time zone. |
|
|
3721
|
-
| `storefrontCountryCode` | 2-letter lowercase country code of the storefront route. Links: `<STOREFRONT_URL>/<cc>/partnerportal` (affiliate portal), `<STOREFRONT_URL>/<cc>/reset-password` and `<STOREFRONT_URL>/<cc>/verify-account` (the e-mail plugin appends `#token=`), referral base `<STOREFRONT_URL>/<cc>`. The Admin invite link is `<MEDUSA_BACKEND_URL>/app/invite?token=`. |
|
|
3722
|
-
| `documentHtmlFactory` | Optional `DocumentHtmlFactory` that replaces the default document markup, typically `createDefaultDocumentHtmlFactory({ timeZone, theme })` from `/config` with the shop's CSS and watermark (see "Layout, logo, payment terms and shop theme"). Must be a function. |
|
|
3723
|
-
| `loadEnvFile` | Default `true`: calls `loadEnv(NODE_ENV \|\| "development", process.cwd())`. Pass `false` when the environment is already complete (tests, isolated child processes). |
|
|
3923
|
+
```ts
|
|
3924
|
+
export { register } from "@hartl-services/medusa-base/instrumentation"
|
|
3925
|
+
```
|
|
3724
3926
|
|
|
3725
|
-
|
|
3927
|
+
`@sentry/node` stays an optional peer dependency of the base: a shop that uses
|
|
3928
|
+
this export (or `sentry-logger`) lists `@sentry/node` in its own
|
|
3929
|
+
`dependencies`.
|
|
3726
3930
|
|
|
3727
|
-
|
|
3728
|
-
plugins' build placeholders stand in for them):
|
|
3931
|
+
## Development
|
|
3729
3932
|
|
|
3730
|
-
|
|
3731
|
-
`JWT_SECRET`, `COOKIE_SECRET`, `MEDUSA_BACKEND_URL`, `STOREFRONT_URL` (plain
|
|
3732
|
-
HTTP origins are accepted only in development and test).
|
|
3733
|
-
- Redis: `REDIS_URL` (optional in development, where Medusa's local event bus
|
|
3734
|
-
is used; required elsewhere, `rediss://` with authentication in production
|
|
3735
|
-
unless `REDIS_ALLOW_INSECURE_PRIVATE_NETWORK=true`). Never used during a
|
|
3736
|
-
build.
|
|
3737
|
-
- SMTP: `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE`, `SMTP_USER`, `SMTP_PASSWORD`,
|
|
3738
|
-
`SMTP_FROM`, `SMTP_REQUIRE_TLS` (outside development and test either
|
|
3739
|
-
`SMTP_SECURE` or `SMTP_REQUIRE_TLS` must be `true`).
|
|
3740
|
-
- Stripe: `STRIPE_API_KEY` (`sk_...`), `STRIPE_WEBHOOK_SECRET` (`whsec_...`).
|
|
3741
|
-
- S3: `S3_CACHE_STORAGE_BUCKET`, `S3_CACHE_STORAGE_REGION`.
|
|
3742
|
-
- Documents: `DOCUMENT_PDF_EXECUTABLE`.
|
|
3933
|
+
### Local e-mail development
|
|
3743
3934
|
|
|
3744
|
-
|
|
3745
|
-
`
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
`S3_CACHE_STORAGE_CLEANUP_INTERVAL_MS`,
|
|
3750
|
-
`S3_CACHE_STORAGE_EXPIRATION_STRATEGY`,
|
|
3751
|
-
`FILE_GATEWAY_PRIVATE_DOWNLOAD_DURATION_SECONDS`, and `SENTRY_DSN` /
|
|
3752
|
-
`SENTRY_ENVIRONMENT` for error tracking (see the base's
|
|
3753
|
-
`/instrumentation`).
|
|
3935
|
+
For local development, provide the seven SMTP variables and omit `REDIS_URL` to
|
|
3936
|
+
use Medusa's local Event Bus. Set `REDIS_URL` only when intentionally exercising
|
|
3937
|
+
the direct Redis provider. The backend HTTP runner ignores ambient Redis and
|
|
3938
|
+
SMTP settings and injects an isolated Redis container, unique queue, and
|
|
3939
|
+
token-protected SMTP fixture for every child.
|
|
3754
3940
|
|
|
3755
|
-
###
|
|
3941
|
+
### Build output of `/admin-components`
|
|
3756
3942
|
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
`http.authVerificationsPerActor.customer` (a getter that follows the E-Mail `customer_verification` toggle, see
|
|
3763
|
-
"Kontobestätigung per E-Mail"), `http.authMethodsPerActor` (customers: a getter that follows the `customer_auth` switches; Admin users: `["emailpass"]`) and the auth module entry
|
|
3764
|
-
(providers `emailpass` and `email-otp`, the latter with `hmacSecret` = `JWT_SECRET`, the same value as `http.jwtSecret`; see
|
|
3765
|
-
"Kunden-Login").
|
|
3943
|
+
`/admin-components` is built (after `medusa plugin:build`) into
|
|
3944
|
+
`.medusa/server/src/admin/admin-components`, not into `dist`: the host Admin's
|
|
3945
|
+
Tailwind scans only `.medusa/server/src/admin/**` of a plugin, so the classes of
|
|
3946
|
+
the shared components are generated there. Do not move the outDir of
|
|
3947
|
+
`tsconfig.admin-components.json`.
|