@hartl-services/medusa-base 3.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/admin-components/features/plugin-features/components/PluginActionsSection.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginFeaturesCard.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginHubCard.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginSettingsForm.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginStatusChips.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/components/ShopMasterDataBanner.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginSettingsForm.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginSwitch.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/action-runs.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-features-admin-client.js +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-view.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/settings-draft.d.ts +1 -1
- package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/settings-layout.d.ts +1 -1
- package/.medusa/server/src/admin/index.js +51 -51
- package/.medusa/server/src/admin/index.mjs +51 -51
- package/.medusa/server/src/api/admin/draft-orders/[id]/order-confirmation/route.js +2 -2
- package/.medusa/server/src/api/admin/email/attachments/[id]/mail-kinds/route.js +2 -2
- package/.medusa/server/src/api/admin/email/attachments/[id]/route.js +2 -2
- package/.medusa/server/src/api/admin/email/attachments/[id]/versions/route.js +2 -2
- package/.medusa/server/src/api/admin/email/attachments/route.js +2 -2
- package/.medusa/server/src/api/admin/email/messages/[id]/resends/route.js +2 -2
- package/.medusa/server/src/api/admin/email/messages/[id]/route.js +2 -2
- package/.medusa/server/src/api/admin/email/messages/route.js +2 -2
- package/.medusa/server/src/api/admin/email/templates/[kind]/preview/route.js +2 -2
- package/.medusa/server/src/api/admin/email/templates/[kind]/route.js +2 -2
- package/.medusa/server/src/api/admin/email/templates/[kind]/send-test/route.js +2 -2
- package/.medusa/server/src/api/admin/email/templates/[kind]/test-sources/route.js +2 -2
- package/.medusa/server/src/api/admin/email/templates/route.js +2 -2
- package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/actions/[action_key]/route.js +2 -2
- package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/actions/[action_key]/runs/route.js +2 -2
- package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/route.js +2 -2
- package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/settings/route.js +2 -2
- package/.medusa/server/src/api/admin/plugin-features/route.js +2 -2
- package/.medusa/server/src/api/admin-first-login-password-policy.js +2 -2
- package/.medusa/server/src/api/answer-code-provider-route-not-found.js +2 -2
- package/.medusa/server/src/api/check-customer-address-tax-identification.js +3 -3
- package/.medusa/server/src/api/code-account-password-routes.js +2 -2
- package/.medusa/server/src/api/middlewares.js +9 -9
- package/.medusa/server/src/api/password-policy.js +2 -2
- package/.medusa/server/src/api/plugin-actions.js +2 -2
- package/.medusa/server/src/api/plugin-features-http.js +2 -2
- package/.medusa/server/src/api/require-login-code-customer-email.js +2 -2
- package/.medusa/server/src/api/store/carts/[id]/completion-check/route.js +2 -2
- package/.medusa/server/src/api/store/carts/[id]/tax-identification/route.js +2 -2
- package/.medusa/server/src/api/store/customer-auth/password-policy/route.js +2 -2
- package/.medusa/server/src/api/store/plugin-features/[plugin_key]/route.js +2 -2
- package/.medusa/server/src/api/store/plugin-features/route.js +2 -2
- package/.medusa/server/src/customer-auth/application/prepare-code-account-password-reset.js +2 -2
- package/.medusa/server/src/customer-auth/application/request-login-code.js +2 -2
- package/.medusa/server/src/customer-auth/customer-password-policy.js +2 -2
- package/.medusa/server/src/customer-auth/domain/auth-methods.js +2 -2
- package/.medusa/server/src/customer-auth/domain/login-code.js +2 -2
- package/.medusa/server/src/customer-auth/login-code-state.js +2 -2
- package/.medusa/server/src/customer-auth/plugin-features.js +2 -2
- package/.medusa/server/src/documents/paths/documents.js +2 -2
- package/.medusa/server/src/documents/schemas/documents.js +2 -2
- package/.medusa/server/src/email/application/deliver-login-code.js +2 -2
- package/.medusa/server/src/email/domain/business-keys.js +2 -2
- package/.medusa/server/src/email/templates/blocks/render-document.js +2 -2
- package/.medusa/server/src/email/templates/render.js +2 -2
- package/.medusa/server/src/email/templates/validate.js +2 -2
- package/.medusa/server/src/email/types/template-data.js +2 -2
- 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/documents/service.js +2 -2
- package/.medusa/server/src/modules/email/domain/attachment.js +2 -2
- package/.medusa/server/src/modules/email/service.js +3 -3
- 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/modules/tax-compliance/service.js +2 -2
- package/.medusa/server/src/providers/email-otp/service.js +2 -2
- package/.medusa/server/src/public/shop-config.js +6 -6
- package/.medusa/server/src/shop-config/{build-food-shop-config.js → build-shop-config.js} +14 -18
- package/.medusa/server/src/shop-config/options.js +4 -4
- package/.medusa/server/src/subscribers/affiliate-application-submitted.js +2 -2
- package/.medusa/server/src/subscribers/affiliate-consignment-reminder-due.js +2 -2
- package/.medusa/server/src/subscribers/documents-order-cancellation-ready.js +2 -2
- package/.medusa/server/src/subscribers/documents-refund-ready.js +2 -2
- package/.medusa/server/src/subscribers/documents-shipment-invoice-assignment-decided.js +2 -2
- 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/subscribers/packing-list-allocation-revised.js +2 -2
- package/.medusa/server/src/subscribers/tax-compliance-plugin-features-updated.js +2 -2
- package/.medusa/server/src/tax-compliance/lib/tax-compliance-state.js +2 -2
- package/.medusa/server/src/tax-compliance/plugin-features.js +2 -2
- 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 +754 -562
- package/dist/customer-auth/domain/auth-methods.js +1 -1
- package/dist/customer-auth/plugin-features.js +1 -1
- package/dist/documents/types/admin-api.d.ts +1 -1
- package/dist/documents/types/document-summary.d.ts +1 -1
- package/dist/documents/types/store-api.d.ts +1 -1
- package/dist/email/types/template-data.js +1 -1
- package/dist/public/shop-config.d.ts +4 -4
- package/dist/public/shop-config.js +5 -5
- package/dist/shop-config/{build-food-shop-config.d.ts → build-shop-config.d.ts} +12 -12
- package/dist/shop-config/{build-food-shop-config.js → build-shop-config.js} +13 -17
- package/dist/shop-config/options.d.ts +3 -3
- package/dist/shop-config/options.js +3 -3
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,75 +1,128 @@
|
|
|
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 food 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
|
|
|
71
124
|
```bash
|
|
72
|
-
pnpm add @hartl-services/medusa-base @hartl-services/medusa-
|
|
125
|
+
pnpm add @hartl-services/medusa-base @hartl-services/medusa-contracts
|
|
73
126
|
```
|
|
74
127
|
|
|
75
128
|
```ts
|
|
@@ -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,12 +212,158 @@ 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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
215
|
+
### Upgrading to 4.0.0
|
|
216
|
+
|
|
217
|
+
Renames: the Contracts package is
|
|
218
|
+
`@hartl-services/medusa-contracts` (1.0.0, formerly
|
|
219
|
+
`@hartl-services/medusa-food-supplements-contracts`; same subpaths), and the
|
|
220
|
+
Shop-Config-Preset exports are `buildShopConfig`, `defineShopConfig`,
|
|
221
|
+
`ShopConfigOptions`, `ShopConfigInput`, `ShopPlugin` and `ShopModule`
|
|
222
|
+
(formerly `buildFoodShopConfig` etc.). Replace the import names in
|
|
223
|
+
`medusa-config.ts` and the Contracts package name in the shop and its
|
|
224
|
+
Storefront; nothing else changes. The other Hartl Services plugins need the
|
|
225
|
+
versions released with 4.0.0 (peer range `^4`).
|
|
226
|
+
|
|
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
|
|
140
361
|
|
|
141
|
-
|
|
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
|
|
142
367
|
|
|
143
368
|
| Method | Path | Auth | Response |
|
|
144
369
|
|---|---|---|---|
|
|
@@ -174,8 +399,8 @@ Store values are effective values only; a storefront uses them to hide
|
|
|
174
399
|
features. DTOs, schemas and paths come from the Contracts package:
|
|
175
400
|
|
|
176
401
|
```ts
|
|
177
|
-
import type { StorePluginFeaturesListResponse } from "@hartl-services/medusa-
|
|
178
|
-
import { pluginFeaturesStorePaths } from "@hartl-services/medusa-
|
|
402
|
+
import type { StorePluginFeaturesListResponse } from "@hartl-services/medusa-contracts/types/plugin-features-store"
|
|
403
|
+
import { pluginFeaturesStorePaths } from "@hartl-services/medusa-contracts/paths/plugin-features"
|
|
179
404
|
|
|
180
405
|
const { plugins } = await sdk.client.fetch<StorePluginFeaturesListResponse>(
|
|
181
406
|
pluginFeaturesStorePaths.get.list,
|
|
@@ -183,7 +408,7 @@ const { plugins } = await sdk.client.fetch<StorePluginFeaturesListResponse>(
|
|
|
183
408
|
)
|
|
184
409
|
```
|
|
185
410
|
|
|
186
|
-
|
|
411
|
+
### Declaring features in a plugin
|
|
187
412
|
|
|
188
413
|
A plugin depends on `@hartl-services/medusa-base` (peer dependency), declares
|
|
189
414
|
its features once and exposes the declaration as `pluginFeatures` on its
|
|
@@ -223,7 +448,7 @@ base's own shop master data): the Admin shows no plugin switch, `enabled` is
|
|
|
223
448
|
always `true` whatever is stored, and `POST /admin/plugin-features` with
|
|
224
449
|
`enabled: false` is 400. It cannot be combined with `default_enabled: false`.
|
|
225
450
|
|
|
226
|
-
|
|
451
|
+
#### Presentation fields for the Admin page
|
|
227
452
|
|
|
228
453
|
All optional; the Admin response always carries them (`null`, `[]` or
|
|
229
454
|
`"full"` when not declared). Texts are non-empty trimmed strings;
|
|
@@ -266,7 +491,7 @@ All optional; the Admin response always carries them (`null`, `[]` or
|
|
|
266
491
|
share a two-column row; not for `boolean`) and `section` (sub-heading inside
|
|
267
492
|
the group, shared by consecutive settings with the same value).
|
|
268
493
|
|
|
269
|
-
|
|
494
|
+
#### Gating routes
|
|
270
495
|
|
|
271
496
|
Put gate entries at the start of the plugin's `routes` array. An entry without
|
|
272
497
|
`methods` is a prefix match and runs before every method-specific middleware:
|
|
@@ -293,7 +518,7 @@ export default defineMiddlewares({
|
|
|
293
518
|
A disabled plugin or feature answers 404. With several keys, all must be
|
|
294
519
|
enabled. The toggles are read once per request.
|
|
295
520
|
|
|
296
|
-
|
|
521
|
+
#### Gating subscribers and jobs
|
|
297
522
|
|
|
298
523
|
Return early:
|
|
299
524
|
|
|
@@ -314,7 +539,7 @@ To check another plugin without importing it, use its plugin key:
|
|
|
314
539
|
the descriptor among the registered modules; an unknown or uninstalled plugin
|
|
315
540
|
key (and an undeclared feature key) is `false`.
|
|
316
541
|
|
|
317
|
-
|
|
542
|
+
#### Toggle-change event
|
|
318
543
|
|
|
319
544
|
After a successful `POST /admin/plugin-features` the base emits
|
|
320
545
|
`PLUGIN_FEATURES_UPDATED_EVENT` (`plugin-features.updated.v1`) through the
|
|
@@ -332,7 +557,7 @@ subscribes to it and filters on its own `plugin_key`:
|
|
|
332
557
|
import {
|
|
333
558
|
PLUGIN_FEATURES_UPDATED_EVENT,
|
|
334
559
|
PluginFeaturesUpdatedEventSchema,
|
|
335
|
-
} from "@hartl-services/medusa-
|
|
560
|
+
} from "@hartl-services/medusa-contracts/events/plugin-features"
|
|
336
561
|
|
|
337
562
|
export default async function onToggleChange({ event, container }: SubscriberArgs<unknown>) {
|
|
338
563
|
const payload = PluginFeaturesUpdatedEventSchema.parse(event.data)
|
|
@@ -346,7 +571,7 @@ export const config: SubscriberConfig = { event: PLUGIN_FEATURES_UPDATED_EVENT }
|
|
|
346
571
|
Events can arrive late or twice; re-read the current toggles instead of
|
|
347
572
|
trusting the payload for anything that writes.
|
|
348
573
|
|
|
349
|
-
|
|
574
|
+
#### Never on the cart path
|
|
350
575
|
|
|
351
576
|
Code on the cart and checkout hot path (cart middlewares, cart workflow hooks,
|
|
352
577
|
`orderCreated` handling, cart subscribers) must never read toggles. It stays
|
|
@@ -356,7 +581,7 @@ existing data. Login is not the cart path: the E-Mail plugin's
|
|
|
356
581
|
`customer_verification` toggle does switch customer logins, through an
|
|
357
582
|
in-memory copy of the toggle (see "Kontobestätigung per E-Mail").
|
|
358
583
|
|
|
359
|
-
|
|
584
|
+
### Runtime settings
|
|
360
585
|
|
|
361
586
|
Configuration a plugin reads at RUN time (for example an external system's
|
|
362
587
|
URL and API key) is declared in the descriptor and edited on the plugin's
|
|
@@ -450,7 +675,7 @@ class DemoModuleService extends MedusaService({ /* models */ }) {
|
|
|
450
675
|
(`plugin-settings.updated.v1`) with `{ plugin_key, version, changed_keys }`
|
|
451
676
|
(no values). Direct service calls emit nothing.
|
|
452
677
|
|
|
453
|
-
|
|
678
|
+
#### Secrets
|
|
454
679
|
|
|
455
680
|
Secrets are stored AES-256-GCM encrypted (`{ enc: "<base64 iv|tag|ciphertext>" }`
|
|
456
681
|
in `plugin_setting_state.values`) with a key derived by HKDF-SHA256 from the
|
|
@@ -465,7 +690,7 @@ applies in the meantime. Secrets saved by an unpublished development build
|
|
|
465
690
|
before the key binding existed count as "nicht gesetzt" the same way and must
|
|
466
691
|
be entered again.
|
|
467
692
|
|
|
468
|
-
|
|
693
|
+
### Actions
|
|
469
694
|
|
|
470
695
|
A plugin declares actions as registered workflow ids; the base runs them from
|
|
471
696
|
the plugin's Admin page:
|
|
@@ -519,7 +744,7 @@ Admin user id, `run_id` the recorded run).
|
|
|
519
744
|
workflow is not registered is 500 (`UNEXPECTED_STATE` naming the workflow,
|
|
520
745
|
no run is recorded).
|
|
521
746
|
|
|
522
|
-
|
|
747
|
+
#### Reporting progress
|
|
523
748
|
|
|
524
749
|
A long-running action reports progress from its workflow; the Admin shows it
|
|
525
750
|
as a bar ("current / total") with the label:
|
|
@@ -548,7 +773,7 @@ characters. Without a `run_id` (the same workflow run from a script) the
|
|
|
548
773
|
helper does nothing; invalid progress, an unknown or finished run and a
|
|
549
774
|
failed write are logged as a warning and dropped, and never fail the action.
|
|
550
775
|
|
|
551
|
-
|
|
776
|
+
### Shop-Stammdaten
|
|
552
777
|
|
|
553
778
|
The base declares its own descriptor on its module service
|
|
554
779
|
(`plugin_key: "shop"`, label "Shop-Stammdaten", `switchable: false`, no
|
|
@@ -616,7 +841,7 @@ Documents plugin into the appearance: the Documents logo, else the E-Mail
|
|
|
616
841
|
logo; a `logo` stored in the Shop-Stammdaten by an earlier release is kept
|
|
617
842
|
only when neither plugin had one.
|
|
618
843
|
|
|
619
|
-
|
|
844
|
+
### Admin pages
|
|
620
845
|
|
|
621
846
|
- `/settings/hartl-services` (sidebar entry "Hartl Services Plugins") is the
|
|
622
847
|
hub: a banner for the Shop-Stammdaten (icon, summary, "Nicht konfiguriert"
|
|
@@ -645,7 +870,7 @@ only when neither plugin had one.
|
|
|
645
870
|
rejects a path outside `/settings/hartl-services/` or under
|
|
646
871
|
`/settings/hartl-services/plugin/`.
|
|
647
872
|
|
|
648
|
-
|
|
873
|
+
#### Admin components for plugin pages
|
|
649
874
|
|
|
650
875
|
`@hartl-services/medusa-base/admin-components` (ESM for the Admin's vite
|
|
651
876
|
build, with types) exports:
|
|
@@ -744,7 +969,7 @@ The consuming plugin lists `@hartl-services/medusa-base` in its
|
|
|
744
969
|
external and the shop's Admin resolves one shared copy. The components use
|
|
745
970
|
the host Admin's React, React Query, React Router and `@medusajs/ui`.
|
|
746
971
|
|
|
747
|
-
|
|
972
|
+
##### Rich-text editor
|
|
748
973
|
|
|
749
974
|
`useRichTextEditor(config, initialContent, deps)` returns the TipTap
|
|
750
975
|
`Editor | null` (`null` until it exists), `<RichTextEditor editor
|
|
@@ -837,7 +1062,7 @@ policy (`link.isAllowedHref`) are read on every use and always see the latest
|
|
|
837
1062
|
render, so they need no stable reference. Pass `[]` for an editor that lives as
|
|
838
1063
|
long as its component.
|
|
839
1064
|
|
|
840
|
-
|
|
1065
|
+
#### Server helpers
|
|
841
1066
|
|
|
842
1067
|
`@hartl-services/medusa-base/lib` (server and isomorphic code; a separate
|
|
843
1068
|
subpath because unit tests mock `@hartl-services/medusa-base` wholesale)
|
|
@@ -883,13 +1108,7 @@ The main entry (`@hartl-services/medusa-base`) additionally exports, next to
|
|
|
883
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` |
|
|
884
1109
|
| `isBasePluginRegistered(container)` / `resolveBaseModule(container)` | Whether the base module is registered (boot-time check) / its service, or `UNEXPECTED_STATE` |
|
|
885
1110
|
|
|
886
|
-
|
|
887
|
-
`.medusa/server/src/admin/admin-components`, not into `dist`: the host Admin's
|
|
888
|
-
Tailwind scans only `.medusa/server/src/admin/**` of a plugin, so the classes of
|
|
889
|
-
the shared components are generated there. Do not move the outDir of
|
|
890
|
-
`tsconfig.admin-components.json`.
|
|
891
|
-
|
|
892
|
-
## Semantics
|
|
1111
|
+
### Semantics
|
|
893
1112
|
|
|
894
1113
|
- No stored row: every value is its declared default, `version` is 0.
|
|
895
1114
|
- `effective(feature) = plugin enabled && configured(feature) && effective(parent)`.
|
|
@@ -901,7 +1120,7 @@ the shared components are generated there. Do not move the outDir of
|
|
|
901
1120
|
value updates exactly that version. A concurrent change answers 409; the
|
|
902
1121
|
Admin page reloads and shows a notice.
|
|
903
1122
|
|
|
904
|
-
|
|
1123
|
+
### Complete-cart hook composition
|
|
905
1124
|
|
|
906
1125
|
**Why.** Medusa 2.19.0 allows exactly one handler per workflow hook; a second
|
|
907
1126
|
registration throws "Cannot define multiple hook handlers" at boot. Several
|
|
@@ -986,42 +1205,12 @@ Storefront calls it right before confirming a payment. Plugins that guard
|
|
|
986
1205
|
`/store/carts/:id/complete` with a middleware register it for this route too
|
|
987
1206
|
(the affiliate plugin's registered-email gate does).
|
|
988
1207
|
|
|
1208
|
+
## Built-in features
|
|
989
1209
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
`@hartl-services/medusa-base/sentry-logger` exports `withSentryErrorReporting(logger)`,
|
|
993
|
-
formerly published as `@hartl-services/medusa-sentry-logger`. Medusa's HTTP
|
|
994
|
-
error handler catches thrown route and workflow errors and only logs them, so
|
|
995
|
-
Sentry's default Node integrations never see them; wrapping the logger reports
|
|
996
|
-
every `logger.error(...)` and every `Error` passed to `logger.warn(...)` (at
|
|
997
|
-
level `warning`). It is a plain library import for `medusa-config.ts`, not a
|
|
998
|
-
Medusa plugin feature, and needs `@sentry/node` (an optional peer dependency):
|
|
999
|
-
|
|
1000
|
-
```ts
|
|
1001
|
-
import { logger } from "@medusajs/framework/logger";
|
|
1002
|
-
import { withSentryErrorReporting } from "@hartl-services/medusa-base/sentry-logger";
|
|
1003
|
-
|
|
1004
|
-
module.exports = defineConfig({
|
|
1005
|
-
logger: withSentryErrorReporting(logger),
|
|
1006
|
-
// ...
|
|
1007
|
-
});
|
|
1008
|
-
```
|
|
1009
|
-
|
|
1010
|
-
Initialize Sentry in `instrumentation.ts` as usual; without a `SENTRY_DSN` the
|
|
1011
|
-
wrapper is inert.
|
|
1012
|
-
|
|
1013
|
-
## Root redirect
|
|
1014
|
-
|
|
1015
|
-
Medusa serves nothing at `/`, so a deployed domain answers it with a 404. The
|
|
1016
|
-
base registers `GET /` and answers with a `302` to the shop's `admin.path`
|
|
1017
|
-
(read from the config module, fallback `/app`), so opening the bare domain
|
|
1018
|
-
lands on the admin dashboard in every shop and in local development without a
|
|
1019
|
-
reverse-proxy rule. The redirect is temporary on purpose: browsers cache a
|
|
1020
|
-
`301` on the bare domain essentially forever, which would be painful to undo
|
|
1021
|
-
should `/` ever get real content. A shop that needs its own `/` defines
|
|
1022
|
-
`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.
|
|
1023
1212
|
|
|
1024
|
-
|
|
1213
|
+
### Kunden-Login
|
|
1025
1214
|
|
|
1026
1215
|
Module `customer_auth` (plugin key `customer_auth`, label "Kunden-Login")
|
|
1027
1216
|
decides which login methods customers may use. The plugin cannot be switched
|
|
@@ -1218,233 +1407,57 @@ Storefront contract: DTOs, schemas and paths come from the Contracts package
|
|
|
1218
1407
|
read `GET /store/plugin-features/customer_auth` and render only the enabled
|
|
1219
1408
|
forms.
|
|
1220
1409
|
|
|
1221
|
-
|
|
1410
|
+
### E-Mail
|
|
1222
1411
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
and `/auth/Customer/emailpass/register` reach Medusa's handler too, and
|
|
1230
|
-
`Customer` counts as the customer actor here.
|
|
1231
|
-
A password that breaks the rules of the actor is answered with
|
|
1232
|
-
`400 { type: "invalid_data", message }`; the one German message lists every
|
|
1233
|
-
unmet rule, e.g. `Das Passwort erfüllt nicht alle Regeln: mindestens 12
|
|
1234
|
-
Zeichen, eine Ziffer.` The check runs before Medusa's reset-token check, so a
|
|
1235
|
-
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`.
|
|
1236
1418
|
|
|
1237
|
-
|
|
1238
|
-
characters, counted as Unicode code points, and no composition rules. This
|
|
1239
|
-
follows NIST SP 800-63B Rev. 4 (the US guideline for digital identity) for a
|
|
1240
|
-
password that is the only factor.
|
|
1419
|
+
#### Configuration
|
|
1241
1420
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
`
|
|
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:
|
|
1245
1426
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
| `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 |
|
|
1249
|
-
| `password_require_mixed_case` | Groß- und Kleinbuchstaben verlangen | off | at least one uppercase and one lowercase letter |
|
|
1250
|
-
| `password_require_digit` | Ziffer verlangen | off | at least one digit |
|
|
1251
|
-
| `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";
|
|
1252
1429
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
+
```
|
|
1259
1459
|
|
|
1260
|
-
|
|
1261
|
-
answers `{ password_policy: { min_length, require_mixed_case, require_digit,
|
|
1262
|
-
require_symbol } }`, the effective customer rules, and 404 while
|
|
1263
|
-
`email_password` is off. The storefront shows them in its forms and checks
|
|
1264
|
-
them with the same functions as the backend. The types, the path
|
|
1265
|
-
(`customerAuthStorePaths.get.passwordPolicy`) and the rules
|
|
1266
|
-
(`evaluatePasswordPolicy`, `isPasswordAllowed`, `DEFAULT_CUSTOMER_PASSWORD_POLICY`,
|
|
1267
|
-
`ADMIN_PASSWORD_POLICY` in `schemas/password-policy`) come from the Contracts
|
|
1268
|
-
package.
|
|
1269
|
-
|
|
1270
|
-
**Admin passwords and the shared identity.** An `emailpass` identity is shared
|
|
1271
|
-
by all actor types: someone invited as an Admin could register through
|
|
1272
|
-
`/auth/customer/emailpass/register` with the (weaker) customer rules, log in at
|
|
1273
|
-
`/auth/user/emailpass` and accept the invite with that token, which would bind
|
|
1274
|
-
the weak password to an Admin. So the Admin login (`POST` and `GET`
|
|
1275
|
-
`/auth/user/emailpass`, decoded and lowercased like above) checks the body
|
|
1276
|
-
password against the Admin rules and answers the same `400` when the identity
|
|
1277
|
-
has no Admin yet (`app_metadata.user_id` unset). The login of an identity that
|
|
1278
|
-
already belongs to an Admin is never checked. Remaining limit, deliberately not
|
|
1279
|
-
closed: a customer could register through the Admin path with 15 characters and
|
|
1280
|
-
no composition rules although the shop asks for more; that only weakens their
|
|
1281
|
-
own password.
|
|
1282
|
-
|
|
1283
|
-
Login of a customer is not checked, so existing passwords keep working until their next
|
|
1284
|
-
reset, whatever the rules are. Accounts created without HTTP (`medusa user`,
|
|
1285
|
-
seed scripts calling the Auth module) are not checked either.
|
|
1286
|
-
|
|
1287
|
-
## Cart-promotion link repair
|
|
1288
|
-
|
|
1289
|
-
Medusa's `deletePromotionsWorkflow` only soft-deletes a promotion and leaves
|
|
1290
|
-
its `cart_promotion` link rows behind, so every cart that had it applied
|
|
1291
|
-
reads back a `null` entry inside `cart.promotions`. The base removes those
|
|
1292
|
-
links itself:
|
|
1293
|
-
|
|
1294
|
-
- The `deletePromotionsWorkflow.hooks.promotionsDeleted` hook
|
|
1295
|
-
(`workflows/hooks/dismiss-deleted-promotion-cart-links`) dismisses the
|
|
1296
|
-
links of the deleted promotions and restores them if a later step fails.
|
|
1297
|
-
- The migration script `dismiss-orphaned-cart-promotion-links` runs once in
|
|
1298
|
-
`medusa db:migrate` and removes the links that already dangle from earlier
|
|
1299
|
-
deletions. Medusa tracks migration scripts by file name in
|
|
1300
|
-
`script_migrations`, so the name must not change.
|
|
1301
|
-
|
|
1302
|
-
Nothing to configure; `medusa db:migrate` after installing or upgrading to
|
|
1303
|
-
0.4.1 is enough.
|
|
1304
|
-
|
|
1305
|
-
## Draft orders and 0 € orders
|
|
1306
|
-
|
|
1307
|
-
Draft orders (Medusa's `@medusajs/draft-order` plugin, loaded by default) are
|
|
1308
|
-
the shop's way to enter an order in the Admin: sample shipments, orders taken
|
|
1309
|
-
by phone, replacements. Item prices and the shipping amount are set directly
|
|
1310
|
-
in the draft; converting it never involves a payment provider.
|
|
1311
|
-
|
|
1312
|
-
- **Customer order confirmation off by default.** The
|
|
1313
|
-
`createOrderWorkflow.hooks.orderCreated` hook
|
|
1314
|
-
(`workflows/hooks/draft-order-confirmation-default`) sets Medusa's
|
|
1315
|
-
`no_notification` on every new draft, also when an API caller sent
|
|
1316
|
-
`no_notification_order: false`. The Admin switches it per draft in the
|
|
1317
|
-
**Bestellbestätigung** widget on the draft's detail page
|
|
1318
|
-
(`GET`/`POST /admin/draft-orders/:id/order-confirmation`, see the E-Mail
|
|
1319
|
-
"Admin API"). On conversion the `order-placed` subscriber records the
|
|
1320
|
-
customer confirmation as `suppressed` (`notifications_disabled`); the copy
|
|
1321
|
-
to the shop's order address is still sent. The shipment mail follows the
|
|
1322
|
-
Admin's "notify customer" choice per shipment, not this switch.
|
|
1323
|
-
- **0 € orders count as paid.** Medusa derives an order's payment status from
|
|
1324
|
-
its payment collections only, so a converted 0 € draft would stay
|
|
1325
|
-
"Nicht bezahlt". The subscriber `order-zero-total-paid` runs
|
|
1326
|
-
`order-mark-zero-total-paid` on `order.placed`: an order with a total of 0
|
|
1327
|
-
and no payment collection gets a completed 0 € payment collection and reads
|
|
1328
|
-
"Bezahlt" (`payment_status: captured`). Orders that already carry a
|
|
1329
|
-
collection, i.e. every checkout order, are left alone. In SQL, a paid
|
|
1330
|
-
order is one whose linked `payment_collection` (via
|
|
1331
|
-
`order_payment_collection`) has `status = 'completed'`.
|
|
1332
|
-
|
|
1333
|
-
Orders placed before installing this version keep their state.
|
|
1334
|
-
|
|
1335
|
-
## Config helpers
|
|
1336
|
-
|
|
1337
|
-
`@hartl-services/medusa-base/config` holds the generic environment helpers a
|
|
1338
|
-
shop's `medusa-config.ts` needs, so every shop fails at startup with the name
|
|
1339
|
-
of the missing variable instead of a runtime crash. The environment helpers
|
|
1340
|
-
have no dependency on Medusa; the subpath does not load `@sentry/node`.
|
|
1341
|
-
|
|
1342
|
-
| Export | Purpose |
|
|
1343
|
-
|---|---|
|
|
1344
|
-
| `ConfigurationPhase` | `"build" \| "runtime"` |
|
|
1345
|
-
| `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` |
|
|
1346
|
-
| `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 |
|
|
1347
|
-
| `optionalEnvironment`, `integerEnvironment`, `booleanEnvironment` | Trimmed optional string, range-checked integer, strict `"true"`/`"false"` boolean |
|
|
1348
|
-
| `allowsInsecureLocalServices(environment, phase)` | `true` for build, development and test |
|
|
1349
|
-
| `resolvePublicOrigin(value, name, allowHttp)`, `shopUrl(origin, path)` | Validate a bare HTTP(S) origin; build an absolute URL that cannot leave the origin |
|
|
1350
|
-
| `resolveStripe(values)` | Checks the `sk_` key and the `whsec_` webhook secret |
|
|
1351
|
-
| `resolveRedisEventBus(environment, phase)` | `{ url } \| undefined`: optional in development, required at runtime elsewhere, never used in a build, authenticated `rediss://` in production |
|
|
1352
|
-
| `createEmailNotificationModuleConfig`, `defineShopEmailConfiguration` (deprecated: `createSmtpNotificationModuleConfig`, `resolveSmtp`, `SMTP_ENVIRONMENT_VARIABLES`, `SMTP_BUILD_PLACEHOLDERS`) | Mail notification provider and E-Mail configuration (see "E-Mail") |
|
|
1353
|
-
| `createS3CacheStorageConfig`, `createS3CacheFileModuleConfig`, `resolveS3Credentials`, `resolveExpirationStrategy`, `S3_BUILD_PLACEHOLDERS` | S3 file storage (see "Dateispeicher (S3)") |
|
|
1354
|
-
| `createDefaultDocumentHtmlFactory`, `DOCUMENTS_BUILD_PLACEHOLDERS` | Documents markup and build placeholder (see "Dokumente") |
|
|
1355
|
-
|
|
1356
|
-
`medusa build` loads the configuration without deployment secrets. Each
|
|
1357
|
-
plugin's own placeholders (SMTP, S3, PDF executable, ...) live with that
|
|
1358
|
-
plugin; the shop merges them:
|
|
1359
|
-
|
|
1360
|
-
```ts
|
|
1361
|
-
// medusa-config.ts
|
|
1362
|
-
import {
|
|
1363
|
-
MEDUSA_BUILD_PLACEHOLDERS,
|
|
1364
|
-
requireEnvironment,
|
|
1365
|
-
resolveRedisEventBus,
|
|
1366
|
-
type ConfigurationPhase,
|
|
1367
|
-
} from "@hartl-services/medusa-base/config"
|
|
1368
|
-
|
|
1369
|
-
const phase: ConfigurationPhase =
|
|
1370
|
-
process.env.MEDUSA_BUILD_PHASE === "true" ? "build" : "runtime"
|
|
1371
|
-
const env = requireEnvironment(
|
|
1372
|
-
process.env,
|
|
1373
|
-
["DATABASE_URL", "JWT_SECRET", "COOKIE_SECRET"],
|
|
1374
|
-
phase,
|
|
1375
|
-
{ ...MEDUSA_BUILD_PLACEHOLDERS /* , ...other plugins' placeholders */ },
|
|
1376
|
-
)
|
|
1377
|
-
const redis = resolveRedisEventBus(process.env, phase)
|
|
1378
|
-
```
|
|
1379
|
-
|
|
1380
|
-
## Sentry instrumentation
|
|
1381
|
-
|
|
1382
|
-
`@hartl-services/medusa-base/instrumentation` exports `register()`, the hook
|
|
1383
|
-
Medusa's start command calls from the shop's `instrumentation.ts`. It runs
|
|
1384
|
-
`Sentry.init` with `SENTRY_DSN`, `SENTRY_ENVIRONMENT` (fallback `NODE_ENV`)
|
|
1385
|
-
and `tracesSampleRate: 0`; without a `SENTRY_DSN` Sentry stays inert. The
|
|
1386
|
-
shop's `instrumentation.ts` is one line:
|
|
1387
|
-
|
|
1388
|
-
```ts
|
|
1389
|
-
export { register } from "@hartl-services/medusa-base/instrumentation"
|
|
1390
|
-
```
|
|
1391
|
-
|
|
1392
|
-
`@sentry/node` stays an optional peer dependency of the base: a shop that uses
|
|
1393
|
-
this export (or `sentry-logger`) lists `@sentry/node` in its own
|
|
1394
|
-
`dependencies`.
|
|
1395
|
-
|
|
1396
|
-
## E-Mail
|
|
1397
|
-
|
|
1398
|
-
The base owns transactional e-mail for the shop: logical email messages,
|
|
1399
|
-
attempts, rendering, the Medusa Notification integration, the SMTP and
|
|
1400
|
-
Microsoft 365 transports, retries and e-mail-specific Admin operations. It is
|
|
1401
|
-
the `email` module, its notification provider (`providers/smtp`, id `smtp`),
|
|
1402
|
-
its subscribers, jobs and
|
|
1403
|
-
workflows, and the Admin page `/settings/hartl-services/email`. There is no
|
|
1404
|
-
separate e-mail package since 1.0.0.
|
|
1405
|
-
|
|
1406
|
-
### Configuration
|
|
1407
|
-
|
|
1408
|
-
The notification provider is a `modules` entry built by
|
|
1409
|
-
`createEmailNotificationModuleConfig` (from
|
|
1410
|
-
`@hartl-services/medusa-base/config`); which service it sends through and the
|
|
1411
|
-
credentials are set in the Admin (see "Versand"). The branding and rendering
|
|
1412
|
-
configuration goes into the `email` namespace of the base options:
|
|
1413
|
-
|
|
1414
|
-
```ts
|
|
1415
|
-
import { createEmailNotificationModuleConfig } from "@hartl-services/medusa-base/config";
|
|
1416
|
-
|
|
1417
|
-
export default defineConfig({
|
|
1418
|
-
modules: [
|
|
1419
|
-
createEmailNotificationModuleConfig({
|
|
1420
|
-
// Permits "Verschlüsselung: Keine" (a local SMTP server); never in production.
|
|
1421
|
-
allowInsecure:
|
|
1422
|
-
process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test",
|
|
1423
|
-
}),
|
|
1424
|
-
],
|
|
1425
|
-
plugins: [
|
|
1426
|
-
{
|
|
1427
|
-
resolve: "@hartl-services/medusa-base",
|
|
1428
|
-
options: {
|
|
1429
|
-
email: {
|
|
1430
|
-
jwtSecret: process.env.JWT_SECRET!,
|
|
1431
|
-
shop: {
|
|
1432
|
-
displayName: "Mein Shop",
|
|
1433
|
-
affiliatePortalUrl: "https://shop.example.com/de/partner",
|
|
1434
|
-
inviteUrl: ({ token }) =>
|
|
1435
|
-
`https://shop.example.com/app/invite?token=${token}`,
|
|
1436
|
-
orderNotificationEmail: "orders@shop.example.com",
|
|
1437
|
-
passwordResetUrl: ({ locale }) =>
|
|
1438
|
-
`https://shop.example.com/${locale ?? "de"}/passwort-zuruecksetzen`,
|
|
1439
|
-
}, // a ShopEmailConfiguration, see `ShopEmailConfiguration` in `@hartl-services/medusa-base/types`
|
|
1440
|
-
},
|
|
1441
|
-
},
|
|
1442
|
-
},
|
|
1443
|
-
],
|
|
1444
|
-
});
|
|
1445
|
-
```
|
|
1446
|
-
|
|
1447
|
-
Options of the `email` namespace:
|
|
1460
|
+
Options of the `email` namespace:
|
|
1448
1461
|
|
|
1449
1462
|
- `jwtSecret` — used for Email's own tokens; typically the same secret as
|
|
1450
1463
|
`projectConfig.http.jwtSecret`.
|
|
@@ -1455,7 +1468,7 @@ Options of the `email` namespace:
|
|
|
1455
1468
|
that confirms an account, needed while `customer_verification` is on.
|
|
1456
1469
|
`adminPasswordResetUrl` (optional builder, receives `{ token }`) is the
|
|
1457
1470
|
Medusa Admin's "choose a new password" page; without it an Admin user's
|
|
1458
|
-
password reset fails with `render_failed`. `
|
|
1471
|
+
password reset fails with `render_failed`. `buildShopConfig` sets
|
|
1459
1472
|
`<MEDUSA_BACKEND_URL>/app/reset-password?token=<token>` (the dashboard reads
|
|
1460
1473
|
the token from the query).
|
|
1461
1474
|
`adminAffiliateApplicationsUrl` (optional builder, receives
|
|
@@ -1472,22 +1485,17 @@ fact only while the Documents `invoices` feature is on when the message is
|
|
|
1472
1485
|
created (with `invoices` off, or the Documents plugin switched off, Documents
|
|
1473
1486
|
publishes no fact, so the mail is sent without a document). Refund e-mails
|
|
1474
1487
|
come only from the Documents refund fact, so there are none while `invoices`
|
|
1475
|
-
is off.
|
|
1476
|
-
always part of the base.
|
|
1488
|
+
is off.
|
|
1477
1489
|
|
|
1478
1490
|
The event contracts the module consumes for shipment, cancellation, refund and
|
|
1479
1491
|
affiliate-reminder and partner-application e-mails come from
|
|
1480
|
-
`@hartl-services/medusa-
|
|
1492
|
+
`@hartl-services/medusa-contracts/events/documents` and
|
|
1481
1493
|
`/events/affiliate`, so installing the base does not pull in Affiliate.
|
|
1482
1494
|
|
|
1483
1495
|
The provider reads no environment variables (since 2.9.0; the former
|
|
1484
1496
|
`SMTP_*` variables are ignored).
|
|
1485
1497
|
|
|
1486
|
-
|
|
1487
|
-
Event Bus; production requires an authenticated `rediss://` URL. See "Event
|
|
1488
|
-
Bus" below for retry/backoff behavior.
|
|
1489
|
-
|
|
1490
|
-
### Versand
|
|
1498
|
+
#### Versand
|
|
1491
1499
|
|
|
1492
1500
|
The page E-Mail → "Versand" (`/settings/hartl-services/email/delivery`, linked
|
|
1493
1501
|
under "Verwaltung") edits the plugin's settings. It stays reachable while the
|
|
@@ -1547,7 +1555,7 @@ to the e-mail of the Shop-Stammdaten, once per client id and expiry date (a
|
|
|
1547
1555
|
log warning when the Shop-Stammdaten have no e-mail). A new secret with a new
|
|
1548
1556
|
date warns again.
|
|
1549
1557
|
|
|
1550
|
-
|
|
1558
|
+
#### Feature toggles
|
|
1551
1559
|
|
|
1552
1560
|
The E-Mail module declares its toggles to `@hartl-services/medusa-base` under the
|
|
1553
1561
|
plugin key `email`. An Admin switches them under Settings → "Hartl Services
|
|
@@ -1583,7 +1591,7 @@ rows are dimmed and their switches disabled then. All E-Mail sub-pages
|
|
|
1583
1591
|
switch and show `PluginFeatureDisabledNotice` while it is off (the backend
|
|
1584
1592
|
answers `404` for `/admin/email/*` then).
|
|
1585
1593
|
|
|
1586
|
-
|
|
1594
|
+
#### Kontobestätigung per E-Mail
|
|
1587
1595
|
|
|
1588
1596
|
Feature `customer_verification` (child of `account_mails`, default **off**)
|
|
1589
1597
|
makes customers confirm their e-mail address through a link before their
|
|
@@ -1658,11 +1666,12 @@ the toggle itself and follow at once. A failed read keeps the last value and
|
|
|
1658
1666
|
logs a warning; before the first successful read the copy is off. The copy
|
|
1659
1667
|
lives on `globalThis` because the preset (`dist`) and the plugin code Medusa
|
|
1660
1668
|
loads (`.medusa/server`) are compiled separately. Only the preset
|
|
1661
|
-
(`
|
|
1669
|
+
(`buildShopConfig` / `defineShopConfig`) sets the getter; a shop with a
|
|
1662
1670
|
hand-written config must set `authVerificationsPerActor` itself (a fixed list,
|
|
1663
1671
|
changed by a restart) for the toggle to have any effect on logins.
|
|
1664
1672
|
|
|
1665
|
-
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.
|
|
1666
1675
|
|
|
1667
1676
|
Things to know when switching it on:
|
|
1668
1677
|
|
|
@@ -1688,7 +1697,7 @@ Storefront contract (DTOs, schema and path from the Contracts package,
|
|
|
1688
1697
|
- Serve `/<cc>/verify-account`, read `token` from `location.hash`, confirm it
|
|
1689
1698
|
and then send the customer to the login.
|
|
1690
1699
|
|
|
1691
|
-
|
|
1700
|
+
#### Ownership
|
|
1692
1701
|
|
|
1693
1702
|
- Documents owns document issuance and publishes immutable document facts.
|
|
1694
1703
|
- Affiliate owns reminder eligibility and publishes reminder and application
|
|
@@ -1697,14 +1706,14 @@ Storefront contract (DTOs, schema and path from the Contracts package,
|
|
|
1697
1706
|
status plus the attachment library (documents with append-only PDF
|
|
1698
1707
|
versions).
|
|
1699
1708
|
- Public HTTP DTOs, schemas, and paths remain in
|
|
1700
|
-
`@hartl-services/medusa-
|
|
1709
|
+
`@hartl-services/medusa-contracts`.
|
|
1701
1710
|
|
|
1702
1711
|
The base's `/types` and `/config` exports are dedicated server integration
|
|
1703
1712
|
surfaces for the backend. The Documents and Affiliate event contracts live in
|
|
1704
1713
|
the Contracts package (`events/documents`, `events/affiliate`). The base exports no DML models, services,
|
|
1705
1714
|
workflows, migrations, or generated output as a consumer contract.
|
|
1706
1715
|
|
|
1707
|
-
|
|
1716
|
+
#### Event Bus
|
|
1708
1717
|
|
|
1709
1718
|
The backend owns Event Bus selection. Development without `REDIS_URL` leaves
|
|
1710
1719
|
Medusa's local Event Bus active. Configuring `REDIS_URL` registers
|
|
@@ -1718,7 +1727,7 @@ for at most one hour and 1,000 entries. There is no custom Event Bus wrapper,
|
|
|
1718
1727
|
startup capability probe, or purge job. Events are at-least-once facts and can
|
|
1719
1728
|
be replayed, delayed, or delivered out of order.
|
|
1720
1729
|
|
|
1721
|
-
|
|
1730
|
+
#### Event Contracts
|
|
1722
1731
|
|
|
1723
1732
|
All payload objects are strict. Unless noted otherwise, every string shown
|
|
1724
1733
|
below is trimmed and non-empty. Native event names and shapes are fixed to the
|
|
@@ -1844,7 +1853,7 @@ concurrent request never sends a second mail) addressed to
|
|
|
1844
1853
|
`affiliate-consignment:<affiliate_id>:<period_start>`. `locale`, when present,
|
|
1845
1854
|
has at most 35 characters. Email runtime-validates every contract before use.
|
|
1846
1855
|
|
|
1847
|
-
|
|
1856
|
+
#### Invariants
|
|
1848
1857
|
|
|
1849
1858
|
- The accepted kinds are exactly `order.received`, `order.received.admin`, `order.shipped`,
|
|
1850
1859
|
`order.canceled`, `order.return_requested`, `order.return_received`,
|
|
@@ -1898,7 +1907,7 @@ has at most 35 characters. Email runtime-validates every contract before use.
|
|
|
1898
1907
|
|
|
1899
1908
|
Read this section and the central design before changing Email domain behavior.
|
|
1900
1909
|
|
|
1901
|
-
|
|
1910
|
+
#### Shop configuration and default templates
|
|
1902
1911
|
|
|
1903
1912
|
The E-Mail module ships a neutral German layout in grey tones (table-based,
|
|
1904
1913
|
inline styles) and one renderer per accepted kind; the colors of the
|
|
@@ -1935,7 +1944,7 @@ an info box lists number, customer, e-mail and order time, followed by an
|
|
|
1935
1944
|
"Im Admin öffnen" button and the items, totals and addresses. Two optional
|
|
1936
1945
|
`ShopEmailConfiguration` fields drive it: `adminOrderUrl({ orderId })` (the
|
|
1937
1946
|
button is left out without it) and `timeZone` (IANA; the order time is left
|
|
1938
|
-
out without it). `
|
|
1947
|
+
out without it). `buildShopConfig` sets both (`<MEDUSA_BACKEND_URL>/app/
|
|
1939
1948
|
orders/<id>` and its `timeZone` option). The layout receives `audience`
|
|
1940
1949
|
(`"shop"` for this kind, `affiliate.application_submitted.admin`, `system.credential_expiry.admin`, `user.invite` and `auth.admin_password_reset`, `"customer"` for all others) and
|
|
1941
1950
|
shows the contact line only to customers.
|
|
@@ -2047,7 +2056,7 @@ Partner-Bewerbung von <Firma>" (without a company the applicant's name, then
|
|
|
2047
2056
|
the e-mail address; just "Neue Partner-Bewerbung" if none is known). An info
|
|
2048
2057
|
box lists company, name and e-mail (only the present ones), the applicant's
|
|
2049
2058
|
message follows as an escaped paragraph, and an "Im Admin prüfen" button links
|
|
2050
|
-
`adminAffiliateApplicationsUrl` (left out without it; `
|
|
2059
|
+
`adminAffiliateApplicationsUrl` (left out without it; `buildShopConfig`
|
|
2051
2060
|
sets `<MEDUSA_BACKEND_URL>/app/affiliates/applications`). Its layout audience
|
|
2052
2061
|
is `"shop"`. All applicant text is HTML-escaped.
|
|
2053
2062
|
|
|
@@ -2084,7 +2093,7 @@ configuration (blank `displayName`, a non-function `layout` or renderer, an
|
|
|
2084
2093
|
unknown renderer kind) fails at startup and not at the first send. Custom
|
|
2085
2094
|
renderers must escape dynamic values in `bodyHtml` themselves.
|
|
2086
2095
|
|
|
2087
|
-
|
|
2096
|
+
#### Deprecated SMTP helpers
|
|
2088
2097
|
|
|
2089
2098
|
`createSmtpNotificationModuleConfig(smtp)`, `resolveSmtp`,
|
|
2090
2099
|
`SMTP_ENVIRONMENT_VARIABLES` and `SMTP_BUILD_PLACEHOLDERS` remain exported
|
|
@@ -2093,7 +2102,7 @@ given to `createSmtpNotificationModuleConfig` only fill SMTP settings that are
|
|
|
2093
2102
|
not stored in the Admin (the Admin status does not count them as configured).
|
|
2094
2103
|
Use `createEmailNotificationModuleConfig` and the "Versand" page instead.
|
|
2095
2104
|
|
|
2096
|
-
|
|
2105
|
+
#### Delivery Semantics
|
|
2097
2106
|
|
|
2098
2107
|
For ordinary durable messages, only characterized connection failures,
|
|
2099
2108
|
temporary SMTP rejections, and temporary attachment access failures are
|
|
@@ -2119,7 +2128,7 @@ data remain ephemeral; both kinds are excluded from the generic retry job and
|
|
|
2119
2128
|
Admin resend, and retryable handling stays bounded by the original Event Bus
|
|
2120
2129
|
event.
|
|
2121
2130
|
|
|
2122
|
-
|
|
2131
|
+
#### Admin API
|
|
2123
2132
|
|
|
2124
2133
|
Email exposes authenticated Admin operations at:
|
|
2125
2134
|
|
|
@@ -2190,7 +2199,7 @@ a different UUID represents a separate, conscious Admin decision. The Admin UI
|
|
|
2190
2199
|
therefore retains a UUID while retrying an ambiguous failed request and creates
|
|
2191
2200
|
a new UUID only after a new confirmation.
|
|
2192
2201
|
|
|
2193
|
-
|
|
2202
|
+
#### Admin UI
|
|
2194
2203
|
|
|
2195
2204
|
The E-Mail module has an Admin area under `/settings/hartl-services/email`
|
|
2196
2205
|
(**Settings → Hartl Services Plugins → E-Mail → Öffnen**; the descriptor's
|
|
@@ -2239,7 +2248,7 @@ request UUID; downloads use the configured authenticated Admin SDK and never
|
|
|
2239
2248
|
expose storage file IDs or URLs in the UI. There is no delete: removing every
|
|
2240
2249
|
assigned mail lets a document rest.
|
|
2241
2250
|
|
|
2242
|
-
|
|
2251
|
+
#### Mail templates
|
|
2243
2252
|
|
|
2244
2253
|
All 13 mails (`EMAIL_EDITABLE_KINDS`) are block templates an Admin edits
|
|
2245
2254
|
from the group pages of the E-Mail area (Bestell-, Konto- and Affiliate-Mails
|
|
@@ -2340,7 +2349,7 @@ and then each changed mail kind on its own.
|
|
|
2340
2349
|
bzw. ist beschädigt.“), any other read failure „Anhang „X“ konnte
|
|
2341
2350
|
gerade nicht geladen werden.“
|
|
2342
2351
|
|
|
2343
|
-
|
|
2352
|
+
#### Mail attachments
|
|
2344
2353
|
|
|
2345
2354
|
The global Terms PDFs (`AGB`), the EU legal-guarantee notice or a flyer are
|
|
2346
2355
|
documents of the attachment library. A document has a name, the customer
|
|
@@ -2355,8 +2364,8 @@ Admin consumers import wire DTOs and paths only from the Contracts package and
|
|
|
2355
2364
|
reuse their configured Medusa SDK:
|
|
2356
2365
|
|
|
2357
2366
|
```ts
|
|
2358
|
-
import type { ListAdminEmailMessagesResponse } from "@hartl-services/medusa-
|
|
2359
|
-
import { emailAdminPaths } from "@hartl-services/medusa-
|
|
2367
|
+
import type { ListAdminEmailMessagesResponse } from "@hartl-services/medusa-contracts/types/email-admin";
|
|
2368
|
+
import { emailAdminPaths } from "@hartl-services/medusa-contracts/paths/email";
|
|
2360
2369
|
|
|
2361
2370
|
const response = await sdk.client.fetch<ListAdminEmailMessagesResponse>(
|
|
2362
2371
|
emailAdminPaths.get.list,
|
|
@@ -2366,15 +2375,11 @@ const response = await sdk.client.fetch<ListAdminEmailMessagesResponse>(
|
|
|
2366
2375
|
Do not import E-Mail module implementations, generated `.medusa` files, or
|
|
2367
2376
|
duplicate runtime schemas in an Admin consumer. The base's own exports remain
|
|
2368
2377
|
internal server and generated Admin integration surfaces; Email HTTP contracts
|
|
2369
|
-
are published only by `@hartl-services/medusa-
|
|
2378
|
+
are published only by `@hartl-services/medusa-contracts`.
|
|
2370
2379
|
|
|
2371
|
-
|
|
2380
|
+
#### Rollout
|
|
2372
2381
|
|
|
2373
|
-
|
|
2374
|
-
use Medusa's local Event Bus. Set `REDIS_URL` only when intentionally exercising
|
|
2375
|
-
the direct Redis provider. The backend HTTP runner ignores ambient Redis and
|
|
2376
|
-
SMTP settings and injects an isolated Redis container, unique queue, and
|
|
2377
|
-
token-protected SMTP fixture for every child.
|
|
2382
|
+
Local development notes: see "Development".
|
|
2378
2383
|
|
|
2379
2384
|
This architecture replaces the old Documents and Affiliate delivery baselines
|
|
2380
2385
|
without a backfill or compatibility layer. Existing local databases from those
|
|
@@ -2390,7 +2395,7 @@ production business data exists; a populated database requires a separately
|
|
|
2390
2395
|
reviewed migration. No old delivery rows or queued jobs are migrated or purged
|
|
2391
2396
|
by Email.
|
|
2392
2397
|
|
|
2393
|
-
|
|
2398
|
+
### Dokumente
|
|
2394
2399
|
|
|
2395
2400
|
The base issues order documents: one total invoice per order after the final
|
|
2396
2401
|
shipment and one negative cancellation document for each eligible payment
|
|
@@ -2398,10 +2403,9 @@ refund. An Admin user can regenerate issued documents in place for exceptional
|
|
|
2398
2403
|
corrections. The base ships default German document markup, so a new shop needs
|
|
2399
2404
|
no template code; a project can optionally replace the HTML/CSS factory. PDF
|
|
2400
2405
|
generation, private file storage, customer authorization and durable
|
|
2401
|
-
document-readiness facts stay inside the `documents` module.
|
|
2402
|
-
separate documents package since 1.0.0.
|
|
2406
|
+
document-readiness facts stay inside the `documents` module.
|
|
2403
2407
|
|
|
2404
|
-
|
|
2408
|
+
#### What it does
|
|
2405
2409
|
|
|
2406
2410
|
- Listens to Medusa's `shipment.created` event.
|
|
2407
2411
|
- Creates an invoice only if the order is fully shipped and not cancelled.
|
|
@@ -2468,7 +2472,7 @@ separate documents package since 1.0.0.
|
|
|
2468
2472
|
- Reconciles non-canceled physical fulfillments periodically so a lost local
|
|
2469
2473
|
event can be repaired idempotently, including after shipment.
|
|
2470
2474
|
|
|
2471
|
-
|
|
2475
|
+
#### Configuration
|
|
2472
2476
|
|
|
2473
2477
|
The invoice workflow reads the order's frozen B2B evidence from the base's own
|
|
2474
2478
|
`tax_compliance` module (see "Steuer-Compliance"); register the Tax provider
|
|
@@ -2527,7 +2531,7 @@ The `documents` module is always registered with the base (no `documentsEnabled`
|
|
|
2527
2531
|
switch); the Admin switches it off at run time through the feature toggles
|
|
2528
2532
|
below. `medusa db:migrate` creates its tables.
|
|
2529
2533
|
|
|
2530
|
-
|
|
2534
|
+
#### The `/config` subpath
|
|
2531
2535
|
|
|
2532
2536
|
`@hartl-services/medusa-base/config` exports, next to the environment helpers,
|
|
2533
2537
|
build-time values for `medusa-config.ts` and tests:
|
|
@@ -2542,7 +2546,7 @@ build-time values for `medusa-config.ts` and tests:
|
|
|
2542
2546
|
The document types (`DocumentMerchantOption`, `DocumentHtmlFactory`,
|
|
2543
2547
|
`DocumentRenderInput`, ...) come from `@hartl-services/medusa-base/types`.
|
|
2544
2548
|
|
|
2545
|
-
|
|
2549
|
+
#### Layout, logo, payment terms and shop theme
|
|
2546
2550
|
|
|
2547
2551
|
The default markup prints an A4 letterhead: logo top left, document title and
|
|
2548
2552
|
key data (number, order, date, customer UID for exempt B2B supplies) on the
|
|
@@ -2585,7 +2589,7 @@ fonts as `data:` URLs; the container's Chromium has only its system fonts. A
|
|
|
2585
2589
|
shop passes its factory through the preset option `documentHtmlFactory` (see
|
|
2586
2590
|
"Shop-Config-Preset").
|
|
2587
2591
|
|
|
2588
|
-
|
|
2592
|
+
#### Merchant data
|
|
2589
2593
|
|
|
2590
2594
|
The legal sender printed on every document comes from the base plugin's shop
|
|
2591
2595
|
master data (Admin → Settings → Hartl Services Plugins → **Shop-Stammdaten**).
|
|
@@ -2617,7 +2621,7 @@ Requirements/couplings:
|
|
|
2617
2621
|
- The Tax provider `tp_tax_compliance` of the base's Steuer-Compliance on the
|
|
2618
2622
|
regions, so orders carry UID-based exemptions.
|
|
2619
2623
|
- A Chromium/Chrome executable and a Medusa File provider that supports
|
|
2620
|
-
private files (see "
|
|
2624
|
+
private files (see "Shop integration" below for the sandbox/non-root note).
|
|
2621
2625
|
Documents stores PDFs through `Modules.FILE` and streams them only through
|
|
2622
2626
|
its own authenticated routes, so it works with any File provider and does
|
|
2623
2627
|
not need the S3 file storage or its `/files` routes.
|
|
@@ -2633,8 +2637,7 @@ Requirements/couplings:
|
|
|
2633
2637
|
batch/lot data on invoices and packing lists (see "Optional Batch
|
|
2634
2638
|
Management integration" below).
|
|
2635
2639
|
|
|
2636
|
-
|
|
2637
|
-
### Feature toggles
|
|
2640
|
+
#### Feature toggles
|
|
2638
2641
|
|
|
2639
2642
|
The Documents module declares its toggles to the base under the plugin key
|
|
2640
2643
|
`documents`. An Admin switches them under Settings → "Hartl
|
|
@@ -2672,15 +2675,14 @@ using `PluginFeatureGate` from
|
|
|
2672
2675
|
No workflow hook and nothing on the cart, checkout or fulfillment path reads
|
|
2673
2676
|
a toggle.
|
|
2674
2677
|
|
|
2675
|
-
|
|
2678
|
+
#### Shop integration
|
|
2676
2679
|
|
|
2677
2680
|
The demo shop (`apps/backend`) gets everything from the preset (see
|
|
2678
2681
|
"Shop-Config-Preset"): `documents.numbering.timeZone` is its `timeZone`
|
|
2679
2682
|
option, `documents.htmlFactory` its optional `documentHtmlFactory` and
|
|
2680
2683
|
`documents.pdf.executablePath` the `DOCUMENT_PDF_EXECUTABLE` variable. The
|
|
2681
|
-
development UID `ATU00000000` is a placeholder
|
|
2682
|
-
|
|
2683
|
-
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").
|
|
2684
2686
|
|
|
2685
2687
|
`htmlFactory` is intentionally the only presentation extension point. It gets
|
|
2686
2688
|
a fully typed value snapshot (`DocumentTemplateInput` or
|
|
@@ -2698,7 +2700,7 @@ directory and removes it afterward. The Chromium child is also disconnected
|
|
|
2698
2700
|
from the user's session bus and legacy keyring socket, so it cannot access the
|
|
2699
2701
|
operator's normal browser profile or desktop keyring, including under WSLg.
|
|
2700
2702
|
|
|
2701
|
-
|
|
2703
|
+
#### Restore check for private files
|
|
2702
2704
|
|
|
2703
2705
|
After restoring the object store (or the local file directory), check that
|
|
2704
2706
|
every issued document's PDF can be read through the active Medusa File
|
|
@@ -2712,7 +2714,7 @@ pnpm exec medusa exec ./node_modules/@hartl-services/medusa-base/.medusa/server/
|
|
|
2712
2714
|
It fails on the first issued document without a stored file and otherwise
|
|
2713
2715
|
logs the number of verified files and bytes.
|
|
2714
2716
|
|
|
2715
|
-
|
|
2717
|
+
#### Clean baseline and first deployment
|
|
2716
2718
|
|
|
2717
2719
|
The module intentionally contains one generated eight-table baseline,
|
|
2718
2720
|
`Migration20260827061749`. It removes the former `documents_delivery` table
|
|
@@ -2758,7 +2760,7 @@ Apply normal pending module migrations after later changes or upgrades with
|
|
|
2758
2760
|
does not require link synchronization by itself; `db:sync-links` above is the
|
|
2759
2761
|
safe shop-wide first-deployment step for links owned elsewhere.
|
|
2760
2762
|
|
|
2761
|
-
|
|
2763
|
+
#### Storefront contract
|
|
2762
2764
|
|
|
2763
2765
|
All three endpoints require a customer session or bearer token. There is no
|
|
2764
2766
|
guest lookup endpoint and no direct File-provider URL.
|
|
@@ -2774,8 +2776,8 @@ address/email, file and artifact IDs, leases and resolution state.
|
|
|
2774
2776
|
For a typed Storefront integration:
|
|
2775
2777
|
|
|
2776
2778
|
```ts
|
|
2777
|
-
import type { ListStoreDocumentsResponse } from "@hartl-services/medusa-
|
|
2778
|
-
import { documentsPaths } from "@hartl-services/medusa-
|
|
2779
|
+
import type { ListStoreDocumentsResponse } from "@hartl-services/medusa-contracts/types/documents-store";
|
|
2780
|
+
import { documentsPaths } from "@hartl-services/medusa-contracts/paths/documents";
|
|
2779
2781
|
|
|
2780
2782
|
const { documents: issuedDocuments } =
|
|
2781
2783
|
await sdk.client.fetch<ListStoreDocumentsResponse>(
|
|
@@ -2791,13 +2793,13 @@ storage URL.
|
|
|
2791
2793
|
|
|
2792
2794
|
The same Contracts boundary applies to all Documents and Tax Compliance public
|
|
2793
2795
|
routes: import transport DTOs only from
|
|
2794
|
-
`@hartl-services/medusa-
|
|
2795
|
-
`@hartl-services/medusa-
|
|
2796
|
+
`@hartl-services/medusa-contracts/types/<feature>`, paths only from
|
|
2797
|
+
`@hartl-services/medusa-contracts/paths/<feature>`, and call the
|
|
2796
2798
|
Storefront's configured Medusa SDK. A consumer that needs a runtime guard may
|
|
2797
|
-
opt in to `@hartl-services/medusa-
|
|
2799
|
+
opt in to `@hartl-services/medusa-contracts/schemas/<feature>`; it must
|
|
2798
2800
|
not import either the plugin implementation or generated `.medusa` output.
|
|
2799
2801
|
|
|
2800
|
-
|
|
2802
|
+
#### Admin order detail
|
|
2801
2803
|
|
|
2802
2804
|
When Documents is enabled, the Medusa Admin order detail shows a
|
|
2803
2805
|
**Dokumente** widget. It lists the issued documents for that order (kind and
|
|
@@ -2817,7 +2819,7 @@ The Admin API is deliberately scoped by both order and document ID:
|
|
|
2817
2819
|
All authenticated Admin users can inspect order documents; a document from a
|
|
2818
2820
|
different order returns `404` from the download endpoint.
|
|
2819
2821
|
|
|
2820
|
-
|
|
2822
|
+
##### Internal packing lists
|
|
2821
2823
|
|
|
2822
2824
|
The separate **Packlisten** order widget lists every physical fulfillment in
|
|
2823
2825
|
creation order, each with a status badge (Nicht erstellt, Ausstehend, Wird
|
|
@@ -2841,7 +2843,7 @@ Packing lists never pass through the Documents customer-fact pipeline. They are
|
|
|
2841
2843
|
also stored outside `documents_document`, so the authenticated Store document
|
|
2842
2844
|
queries cannot expose them accidentally.
|
|
2843
2845
|
|
|
2844
|
-
|
|
2846
|
+
##### Regenerating an invoice
|
|
2845
2847
|
|
|
2846
2848
|
The widget offers **Rechnung ausstellen** only while the order has no
|
|
2847
2849
|
invoice; once one exists, every document row has a regenerate icon button
|
|
@@ -2862,7 +2864,7 @@ above. A cancellation or future credit note is instead rendered from its
|
|
|
2862
2864
|
persisted snapshot, retaining its original amount, source invoice reference,
|
|
2863
2865
|
number and issue date. It also emits no customer communication event.
|
|
2864
2866
|
|
|
2865
|
-
|
|
2867
|
+
##### Financial document numbers
|
|
2866
2868
|
|
|
2867
2869
|
Documents assigns numbers in these annual ranges:
|
|
2868
2870
|
|
|
@@ -2885,7 +2887,7 @@ increments a range. A permanently failed document retains its number and
|
|
|
2885
2887
|
failure reason as the explanation for that range's gap; reserved numbers are
|
|
2886
2888
|
never recycled or reassigned.
|
|
2887
2889
|
|
|
2888
|
-
|
|
2890
|
+
##### Cancellation documents for payment refunds
|
|
2889
2891
|
|
|
2890
2892
|
Medusa's refund event identifies the payment, so the plugin enumerates its
|
|
2891
2893
|
persisted refund records and claims one cancellation document using each
|
|
@@ -2907,7 +2909,7 @@ not create a second customer fact. For a canceled Order, individual Refund
|
|
|
2907
2909
|
facts are suppressed in favor of the authoritative Order-cancellation
|
|
2908
2910
|
resolution and its sorted artifact list.
|
|
2909
2911
|
|
|
2910
|
-
|
|
2912
|
+
#### Optional Batch Management integration
|
|
2911
2913
|
|
|
2912
2914
|
If `@hartl-services/medusa-batch-management` version `0.0.3` or later is registered, Documents detects
|
|
2913
2915
|
its `getOrderDocumentBatchSnapshot(orderId)` capability structurally. For every
|
|
@@ -2938,11 +2940,11 @@ list revision and serves the newest ready PDF. Returns do not rewrite the
|
|
|
2938
2940
|
historical pick instruction. Fulfillment cancellation marks the packing list as
|
|
2939
2941
|
canceled and prevents a concurrent render from being published as active.
|
|
2940
2942
|
|
|
2941
|
-
|
|
2943
|
+
#### Document facts and artifacts
|
|
2942
2944
|
|
|
2943
2945
|
Documents owns no Notification or delivery state. Its integration boundary is
|
|
2944
2946
|
the strict versioned event contract exported from
|
|
2945
|
-
`@hartl-services/medusa-
|
|
2947
|
+
`@hartl-services/medusa-contracts/events/documents`. Shipment assignments, standalone Refund resolutions and Order-
|
|
2946
2948
|
cancellation resolutions each carry an `event_emitted_at` marker that is set
|
|
2947
2949
|
only after successful enqueue.
|
|
2948
2950
|
|
|
@@ -2959,10 +2961,10 @@ worker has lost its lease, the unreferenced private File is deleted best-effort;
|
|
|
2959
2961
|
a hard process exit in that external File/database gap can leave a storage
|
|
2960
2962
|
orphan but cannot commit another Artifact, carrier or resolution.
|
|
2961
2963
|
|
|
2962
|
-
|
|
2964
|
+
##### Exact outgoing event contracts
|
|
2963
2965
|
|
|
2964
2966
|
The server-only Contracts subpath
|
|
2965
|
-
`@hartl-services/medusa-
|
|
2967
|
+
`@hartl-services/medusa-contracts/events/documents` exports the
|
|
2966
2968
|
event-name constants,
|
|
2967
2969
|
strict Zod schemas and inferred payload types for exactly these three events:
|
|
2968
2970
|
|
|
@@ -3043,8 +3045,6 @@ before the process can persist success, so an ambiguous attempt becomes
|
|
|
3043
3045
|
`unknown` and is not automatically sent again. Any later resend is an explicit
|
|
3044
3046
|
Email Admin action and a new logical message.
|
|
3045
3047
|
|
|
3046
|
-
### Development
|
|
3047
|
-
|
|
3048
3048
|
#### Public Storefront and Admin contracts
|
|
3049
3049
|
|
|
3050
3050
|
The central contracts package exports the Documents Store and Admin DTOs, Zod
|
|
@@ -3052,8 +3052,8 @@ schemas and route paths, but no second Storefront client. Consumers use their
|
|
|
3052
3052
|
own Medusa SDK instance:
|
|
3053
3053
|
|
|
3054
3054
|
```ts
|
|
3055
|
-
import type { GetStoreDocumentResponse } from "@hartl-services/medusa-
|
|
3056
|
-
import { documentsPaths } from "@hartl-services/medusa-
|
|
3055
|
+
import type { GetStoreDocumentResponse } from "@hartl-services/medusa-contracts/types/documents-store";
|
|
3056
|
+
import { documentsPaths } from "@hartl-services/medusa-contracts/paths/documents";
|
|
3057
3057
|
|
|
3058
3058
|
const response = await sdk.client.fetch<GetStoreDocumentResponse>(
|
|
3059
3059
|
documentsPaths.store.get.retrieve(documentId),
|
|
@@ -3061,10 +3061,10 @@ const response = await sdk.client.fetch<GetStoreDocumentResponse>(
|
|
|
3061
3061
|
);
|
|
3062
3062
|
```
|
|
3063
3063
|
|
|
3064
|
-
`@hartl-services/medusa-
|
|
3065
|
-
`@hartl-services/medusa-
|
|
3064
|
+
`@hartl-services/medusa-contracts/types/documents-store` and
|
|
3065
|
+
`@hartl-services/medusa-contracts/types/documents-admin` contain the
|
|
3066
3066
|
respective type-only DTOs. Import
|
|
3067
|
-
`@hartl-services/medusa-
|
|
3067
|
+
`@hartl-services/medusa-contracts/schemas/documents` only for explicit
|
|
3068
3068
|
runtime validation. The common Storefront case imports only types, which are
|
|
3069
3069
|
erased by TypeScript, and the path module; it never imports the plugin's Medusa
|
|
3070
3070
|
server code. Store and Admin responses use the same safe `DocumentSummary`
|
|
@@ -3076,8 +3076,8 @@ and README note together when introducing another Documents route.
|
|
|
3076
3076
|
Packing-list consumers stay Admin-only:
|
|
3077
3077
|
|
|
3078
3078
|
```ts
|
|
3079
|
-
import type { ListAdminOrderPackingListsResponse } from "@hartl-services/medusa-
|
|
3080
|
-
import { packingListsPaths } from "@hartl-services/medusa-
|
|
3079
|
+
import type { ListAdminOrderPackingListsResponse } from "@hartl-services/medusa-contracts/types/documents-admin";
|
|
3080
|
+
import { packingListsPaths } from "@hartl-services/medusa-contracts/paths/documents";
|
|
3081
3081
|
|
|
3082
3082
|
const response = await sdk.client.fetch<ListAdminOrderPackingListsResponse>(
|
|
3083
3083
|
packingListsPaths.admin.get.listForOrder(orderId),
|
|
@@ -3085,16 +3085,15 @@ const response = await sdk.client.fetch<ListAdminOrderPackingListsResponse>(
|
|
|
3085
3085
|
);
|
|
3086
3086
|
```
|
|
3087
3087
|
|
|
3088
|
-
|
|
3088
|
+
### Steuer-Compliance
|
|
3089
3089
|
|
|
3090
|
-
|
|
3091
|
-
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
|
|
3092
3091
|
`tp_tax_compliance`, the UID check routes, the order-evidence freeze and the
|
|
3093
|
-
apportioned shipping tax) is part of the base
|
|
3094
|
-
validation evidence; a Storefront never imports
|
|
3095
|
-
`@hartl-services/medusa-
|
|
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`.
|
|
3096
3095
|
|
|
3097
|
-
|
|
3096
|
+
#### Configuration
|
|
3098
3097
|
|
|
3099
3098
|
Register the Tax Module provider in `medusa-config.ts` (the Shop-Config-Preset
|
|
3100
3099
|
does this); the module itself boots with the base and has no options:
|
|
@@ -3124,7 +3123,7 @@ The merchant's own country and VAT number come only from the Shop-Stammdaten
|
|
|
3124
3123
|
select the qualified VAT validator (currently only the Austrian FinanzOnline
|
|
3125
3124
|
adapter). While either is missing, carts get ordinary B2C tax.
|
|
3126
3125
|
|
|
3127
|
-
|
|
3126
|
+
#### FinanzOnline settings
|
|
3128
3127
|
|
|
3129
3128
|
Declared on the Steuer-Compliance descriptor and edited on its Admin page
|
|
3130
3129
|
(Settings → Hartl Services Plugins → Steuer-Compliance):
|
|
@@ -3175,7 +3174,7 @@ qualifizierte UID-Abfrage.").
|
|
|
3175
3174
|
answers ordinary B2C rates. The order tax path needs no merchant: it checks
|
|
3176
3175
|
the evidence frozen under the order ID.
|
|
3177
3176
|
|
|
3178
|
-
|
|
3177
|
+
#### Feature toggles
|
|
3179
3178
|
|
|
3180
3179
|
Steuer-Compliance declares its switch and three features under the plugin key
|
|
3181
3180
|
`tax_compliance` (`TAX_COMPLIANCE_PLUGIN_KEY` / `TAX_COMPLIANCE_FEATURES` from
|
|
@@ -3214,14 +3213,14 @@ values; the `plugin-features.updated.v1` subscriber and the
|
|
|
3214
3213
|
sync (no row) both count as on; when the row cannot be read both count as
|
|
3215
3214
|
off (ordinary B2C rates, Medusa's shipping tax).
|
|
3216
3215
|
|
|
3217
|
-
|
|
3216
|
+
#### UID check in the checkout
|
|
3218
3217
|
|
|
3219
3218
|
Public Store request, response, schema, and path imports belong to
|
|
3220
|
-
`@hartl-services/medusa-
|
|
3219
|
+
`@hartl-services/medusa-contracts`:
|
|
3221
3220
|
|
|
3222
3221
|
```ts
|
|
3223
|
-
import type { SubmitTaxIdentificationResponse } from "@hartl-services/medusa-
|
|
3224
|
-
import { taxCompliancePaths } from "@hartl-services/medusa-
|
|
3222
|
+
import type { SubmitTaxIdentificationResponse } from "@hartl-services/medusa-contracts/types/tax-compliance-store";
|
|
3223
|
+
import { taxCompliancePaths } from "@hartl-services/medusa-contracts/paths/tax-compliance";
|
|
3225
3224
|
|
|
3226
3225
|
const result = await sdk.client.fetch<SubmitTaxIdentificationResponse>(
|
|
3227
3226
|
taxCompliancePaths.store.carts.taxIdentification(cartId),
|
|
@@ -3310,8 +3309,8 @@ GET /store/tax-identification-policy → { merchant_country_code: "at" | nul
|
|
|
3310
3309
|
storefront then asks for every country.
|
|
3311
3310
|
|
|
3312
3311
|
```ts
|
|
3313
|
-
import type { GetTaxIdentificationPolicyResponse } from "@hartl-services/medusa-
|
|
3314
|
-
import { taxCompliancePaths } from "@hartl-services/medusa-
|
|
3312
|
+
import type { GetTaxIdentificationPolicyResponse } from "@hartl-services/medusa-contracts/types/tax-compliance-store";
|
|
3313
|
+
import { taxCompliancePaths } from "@hartl-services/medusa-contracts/paths/tax-compliance";
|
|
3315
3314
|
|
|
3316
3315
|
const { merchant_country_code } = await sdk.client.fetch<GetTaxIdentificationPolicyResponse>(
|
|
3317
3316
|
taxCompliancePaths.store.taxIdentificationPolicy,
|
|
@@ -3324,7 +3323,7 @@ gets 3 s, so the request stays below a storefront's 15 s checkout wait and the
|
|
|
3324
3323
|
database transaction holding the UID lock never waits on a hanging
|
|
3325
3324
|
FinanzOnline call; a timeout answers `unavailable` (`technical_error`).
|
|
3326
3325
|
|
|
3327
|
-
|
|
3326
|
+
#### Saved customer addresses
|
|
3328
3327
|
|
|
3329
3328
|
A saved customer address carries its UID in `metadata.vat_number`
|
|
3330
3329
|
(`TAX_IDENTIFICATION_METADATA_KEY` in the Contracts). A logged-in customer
|
|
@@ -3372,7 +3371,7 @@ facts likewise receive ordinary B2C tax, without a warning. The registry is
|
|
|
3372
3371
|
country-based: it currently provides only the Austrian FinanzOnline adapter;
|
|
3373
3372
|
an unsupported merchant country deliberately resolves to `unavailable`.
|
|
3374
3373
|
|
|
3375
|
-
|
|
3374
|
+
#### Frozen order evidence
|
|
3376
3375
|
|
|
3377
3376
|
Cart tax workflows pass only the opaque current evidence ID and the merchant
|
|
3378
3377
|
profile (country and UID) to the provider. Order tax workflows use evidence
|
|
@@ -3401,19 +3400,104 @@ wording from the completed order (see "Dokumente"). A later cart change or
|
|
|
3401
3400
|
validation cannot alter an already issued document; B2C, invalid, ineligible
|
|
3402
3401
|
and unavailable evidence adds no tax-proof wording.
|
|
3403
3402
|
|
|
3404
|
-
|
|
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)
|
|
3405
3489
|
|
|
3406
3490
|
The base provides a Medusa File Module provider for durable S3-compatible
|
|
3407
3491
|
asset storage with a local, TTL-based read cache (`providers/s3-cache`), and
|
|
3408
3492
|
the `/files` gateway routes every provider URL points at. It supports AWS S3,
|
|
3409
|
-
MinIO and compatible endpoints through AWS SDK v3.
|
|
3410
|
-
|
|
3411
|
-
|
|
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").
|
|
3412
3496
|
|
|
3413
3497
|
S3 is always the source of truth. The local cache is a per-replica performance
|
|
3414
3498
|
optimization and may be discarded at any time without data loss.
|
|
3415
3499
|
|
|
3416
|
-
|
|
3500
|
+
#### Coverage
|
|
3417
3501
|
|
|
3418
3502
|
The provider covers every asset that uses Medusa's native `Modules.FILE`
|
|
3419
3503
|
service:
|
|
@@ -3427,7 +3511,7 @@ It cannot transparently back up a plugin that writes directly to its own local
|
|
|
3427
3511
|
directory, own S3 client, or a third-party URL. Such integrations must be
|
|
3428
3512
|
changed to use `Modules.FILE` or migrated explicitly.
|
|
3429
3513
|
|
|
3430
|
-
|
|
3514
|
+
#### Configuration
|
|
3431
3515
|
|
|
3432
3516
|
```ts
|
|
3433
3517
|
// medusa-config.ts
|
|
@@ -3503,7 +3587,7 @@ options) is optional; see
|
|
|
3503
3587
|
[Active S3 options and defaults](#active-s3-options-and-defaults) below. The
|
|
3504
3588
|
provider has no database migrations to run.
|
|
3505
3589
|
|
|
3506
|
-
|
|
3590
|
+
#### Environment helpers
|
|
3507
3591
|
|
|
3508
3592
|
`@hartl-services/medusa-base/config` also exports small helpers for reading
|
|
3509
3593
|
the deployment variables in `medusa-config.ts`:
|
|
@@ -3519,7 +3603,7 @@ the deployment variables in `medusa-config.ts`:
|
|
|
3519
3603
|
- `resolveExpirationStrategy(process.env, defaultValue)` — reads
|
|
3520
3604
|
`S3_CACHE_STORAGE_EXPIRATION_STRATEGY` (`"absolute"` or `"sliding"`).
|
|
3521
3605
|
|
|
3522
|
-
|
|
3606
|
+
#### Runtime configuration
|
|
3523
3607
|
|
|
3524
3608
|
The demo shop (`apps/backend`) enables S3 Cache Storage as the sole Medusa
|
|
3525
3609
|
File Module provider through the preset; there is no per-deployment feature
|
|
@@ -3539,7 +3623,7 @@ S3 keys unless the matching database and storage rollback is being performed.
|
|
|
3539
3623
|
The local provider cannot read S3-backed IDs. Treat a storage-provider change
|
|
3540
3624
|
as a controlled deployment cutover, not as a runtime storage router.
|
|
3541
3625
|
|
|
3542
|
-
|
|
3626
|
+
##### Active S3 options and defaults
|
|
3543
3627
|
|
|
3544
3628
|
In this demo host, `bucket`, `region`, and the File Gateway base URL are
|
|
3545
3629
|
required.
|
|
@@ -3561,7 +3645,7 @@ URLs may use only HTTP(S), with no credentials, query string, or fragment.
|
|
|
3561
3645
|
Prefixes and provider keys reject absolute paths, traversal, control
|
|
3562
3646
|
characters, and backslashes.
|
|
3563
3647
|
|
|
3564
|
-
|
|
3648
|
+
#### Object and access model
|
|
3565
3649
|
|
|
3566
3650
|
New uploads have opaque keys. The original filename is stored only as encoded
|
|
3567
3651
|
object metadata:
|
|
@@ -3596,7 +3680,7 @@ successful remote write/delete evicts the affected entry only after S3 has
|
|
|
3596
3680
|
accepted it. Do not share the cache directory between replicas; use a bounded
|
|
3597
3681
|
`emptyDir` or per-pod volume and monitor free disk space.
|
|
3598
3682
|
|
|
3599
|
-
|
|
3683
|
+
#### Backup and restore
|
|
3600
3684
|
|
|
3601
3685
|
A complete restore requires both a PostgreSQL backup and the versioned object
|
|
3602
3686
|
store. The cache is not a backup and is intentionally excluded.
|
|
@@ -3619,7 +3703,7 @@ count and total bytes without modifying data:
|
|
|
3619
3703
|
medusa exec ./node_modules/@hartl-services/medusa-base/.medusa/server/src/documents/scripts/verify-documents-file-storage.js
|
|
3620
3704
|
```
|
|
3621
3705
|
|
|
3622
|
-
|
|
3706
|
+
#### HTTP contract
|
|
3623
3707
|
|
|
3624
3708
|
The gateway routes expose `GET /files/:asset` for public immutable files and
|
|
3625
3709
|
`GET /files/private/:token` for signed private capability URLs. Both call
|
|
@@ -3630,126 +3714,234 @@ Paths, query types, and the runtime query schema are exported from the separate
|
|
|
3630
3714
|
normally consume the complete URL returned by Medusa and never construct a
|
|
3631
3715
|
private URL themselves.
|
|
3632
3716
|
|
|
3633
|
-
## Shop
|
|
3717
|
+
## Shop behaviour and operations
|
|
3634
3718
|
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
bus, SMTP notifications, S3 file storage, the tax and Stripe payment providers,
|
|
3638
|
-
the Admin path, the Sentry logger bridge and the environment validation are
|
|
3639
|
-
assembled there; the shop supplies only its own facts. It reuses the `/config`
|
|
3640
|
-
helpers above for environment parsing. It is a separate subpath because it
|
|
3641
|
-
loads the Sentry logger wrapper and with it `@sentry/node`; `/config` stays
|
|
3642
|
-
free of it.
|
|
3719
|
+
Behaviour the base adds to every shop without a toggle, and the
|
|
3720
|
+
`medusa-config.ts` helpers for operating it.
|
|
3643
3721
|
|
|
3644
|
-
|
|
3645
|
-
options, and its Tax provider) plus four feature plugins, in this order:
|
|
3646
|
-
`medusa-base`, `medusa-free-shipping`, `medusa-batch-management`,
|
|
3647
|
-
`medusa-affiliate`, `medusa-payload-sync`.
|
|
3722
|
+
### Root redirect
|
|
3648
3723
|
|
|
3649
|
-
|
|
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.
|
|
3650
3732
|
|
|
3651
|
-
|
|
3652
|
-
pnpm add @hartl-services/medusa-base \
|
|
3653
|
-
@hartl-services/medusa-free-shipping \
|
|
3654
|
-
@hartl-services/medusa-batch-management \
|
|
3655
|
-
@hartl-services/medusa-affiliate \
|
|
3656
|
-
@hartl-services/medusa-payload-sync \
|
|
3657
|
-
@medusajs/event-bus-redis @medusajs/payment-stripe \
|
|
3658
|
-
@sentry/node
|
|
3659
|
-
```
|
|
3733
|
+
### Cart-promotion link repair
|
|
3660
3734
|
|
|
3661
|
-
|
|
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:
|
|
3662
3871
|
|
|
3663
3872
|
```ts
|
|
3664
3873
|
// medusa-config.ts
|
|
3665
|
-
import {
|
|
3874
|
+
import {
|
|
3875
|
+
MEDUSA_BUILD_PLACEHOLDERS,
|
|
3876
|
+
requireEnvironment,
|
|
3877
|
+
resolveRedisEventBus,
|
|
3878
|
+
type ConfigurationPhase,
|
|
3879
|
+
} from "@hartl-services/medusa-base/config"
|
|
3666
3880
|
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
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)
|
|
3673
3890
|
```
|
|
3674
3891
|
|
|
3675
|
-
|
|
3676
|
-
|
|
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):
|
|
3677
3901
|
|
|
3678
3902
|
```ts
|
|
3679
|
-
import {
|
|
3680
|
-
import {
|
|
3903
|
+
import { logger } from "@medusajs/framework/logger";
|
|
3904
|
+
import { withSentryErrorReporting } from "@hartl-services/medusa-base/sentry-logger";
|
|
3681
3905
|
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
)
|
|
3686
|
-
module.exports = defineConfig(config)
|
|
3906
|
+
module.exports = defineConfig({
|
|
3907
|
+
logger: withSentryErrorReporting(logger),
|
|
3908
|
+
// ...
|
|
3909
|
+
});
|
|
3687
3910
|
```
|
|
3688
3911
|
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
- `defineFoodShopConfig(options, environment = process.env)` returns
|
|
3692
|
-
`defineConfig(buildFoodShopConfig(options, environment))`.
|
|
3693
|
-
- `buildFoodShopConfig(options, environment = process.env)` returns the plain
|
|
3694
|
-
config input (`admin`, `logger`, `projectConfig`, `modules`, `plugins`)
|
|
3695
|
-
before `defineConfig`. `modules` and `plugins` are ordinary arrays.
|
|
3696
|
-
- Types: `FoodShopConfigOptions`, `FoodShopConfigInput`, `FoodShopPlugin`,
|
|
3697
|
-
`FoodShopModule`.
|
|
3912
|
+
Initialize Sentry in `instrumentation.ts` as usual; without a `SENTRY_DSN` the
|
|
3913
|
+
wrapper is inert.
|
|
3698
3914
|
|
|
3699
|
-
|
|
3700
|
-
option (`Food shop option "timeZone" must be ...`), so a misconfigured shop
|
|
3701
|
-
fails at boot. They then load the `.env` file (unless disabled) and read and
|
|
3702
|
-
validate the environment; a missing variable fails with the variable's name.
|
|
3915
|
+
### Sentry instrumentation
|
|
3703
3916
|
|
|
3704
|
-
|
|
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:
|
|
3705
3922
|
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
| `orderNotificationEmail` | Receives order notification e-mails; must look like an e-mail address. |
|
|
3710
|
-
| `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. |
|
|
3711
|
-
| `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=`. |
|
|
3712
|
-
| `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. |
|
|
3713
|
-
| `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
|
+
```
|
|
3714
3926
|
|
|
3715
|
-
|
|
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`.
|
|
3716
3930
|
|
|
3717
|
-
|
|
3718
|
-
plugins' build placeholders stand in for them):
|
|
3931
|
+
## Development
|
|
3719
3932
|
|
|
3720
|
-
|
|
3721
|
-
`JWT_SECRET`, `COOKIE_SECRET`, `MEDUSA_BACKEND_URL`, `STOREFRONT_URL` (plain
|
|
3722
|
-
HTTP origins are accepted only in development and test).
|
|
3723
|
-
- Redis: `REDIS_URL` (optional in development, where Medusa's local event bus
|
|
3724
|
-
is used; required elsewhere, `rediss://` with authentication in production
|
|
3725
|
-
unless `REDIS_ALLOW_INSECURE_PRIVATE_NETWORK=true`). Never used during a
|
|
3726
|
-
build.
|
|
3727
|
-
- SMTP: `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE`, `SMTP_USER`, `SMTP_PASSWORD`,
|
|
3728
|
-
`SMTP_FROM`, `SMTP_REQUIRE_TLS` (outside development and test either
|
|
3729
|
-
`SMTP_SECURE` or `SMTP_REQUIRE_TLS` must be `true`).
|
|
3730
|
-
- Stripe: `STRIPE_API_KEY` (`sk_...`), `STRIPE_WEBHOOK_SECRET` (`whsec_...`).
|
|
3731
|
-
- S3: `S3_CACHE_STORAGE_BUCKET`, `S3_CACHE_STORAGE_REGION`.
|
|
3732
|
-
- Documents: `DOCUMENT_PDF_EXECUTABLE`.
|
|
3933
|
+
### Local e-mail development
|
|
3733
3934
|
|
|
3734
|
-
|
|
3735
|
-
`
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
`S3_CACHE_STORAGE_CLEANUP_INTERVAL_MS`,
|
|
3740
|
-
`S3_CACHE_STORAGE_EXPIRATION_STRATEGY`,
|
|
3741
|
-
`FILE_GATEWAY_PRIVATE_DOWNLOAD_DURATION_SECONDS`, and `SENTRY_DSN` /
|
|
3742
|
-
`SENTRY_ENVIRONMENT` for error tracking (see the base's
|
|
3743
|
-
`/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.
|
|
3744
3940
|
|
|
3745
|
-
###
|
|
3941
|
+
### Build output of `/admin-components`
|
|
3746
3942
|
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
`http.authVerificationsPerActor.customer` (a getter that follows the E-Mail `customer_verification` toggle, see
|
|
3753
|
-
"Kontobestätigung per E-Mail"), `http.authMethodsPerActor` (customers: a getter that follows the `customer_auth` switches; Admin users: `["emailpass"]`) and the auth module entry
|
|
3754
|
-
(providers `emailpass` and `email-otp`, the latter with `hmacSecret` = `JWT_SECRET`, the same value as `http.jwtSecret`; see
|
|
3755
|
-
"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`.
|