@hartl-services/medusa-base 2.11.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginActionsSection.d.ts +1 -1
  2. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginFeaturesCard.d.ts +1 -1
  3. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginHubCard.d.ts +1 -1
  4. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginSettingsForm.d.ts +1 -1
  5. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginStatusChips.d.ts +1 -1
  6. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/ShopMasterDataBanner.d.ts +1 -1
  7. package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.d.ts +1 -1
  8. package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginSettingsForm.d.ts +1 -1
  9. package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginSwitch.d.ts +1 -1
  10. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/action-runs.d.ts +1 -1
  11. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts +1 -1
  12. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-features-admin-client.js +1 -1
  13. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-view.d.ts +1 -1
  14. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/settings-draft.d.ts +1 -1
  15. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/settings-layout.d.ts +1 -1
  16. package/.medusa/server/src/admin/index.js +4 -4
  17. package/.medusa/server/src/admin/index.mjs +4 -4
  18. package/.medusa/server/src/api/admin/draft-orders/[id]/order-confirmation/route.js +2 -2
  19. package/.medusa/server/src/api/admin/email/attachments/[id]/mail-kinds/route.js +2 -2
  20. package/.medusa/server/src/api/admin/email/attachments/[id]/route.js +2 -2
  21. package/.medusa/server/src/api/admin/email/attachments/[id]/versions/route.js +2 -2
  22. package/.medusa/server/src/api/admin/email/attachments/route.js +2 -2
  23. package/.medusa/server/src/api/admin/email/messages/[id]/resends/route.js +2 -2
  24. package/.medusa/server/src/api/admin/email/messages/[id]/route.js +2 -2
  25. package/.medusa/server/src/api/admin/email/messages/route.js +2 -2
  26. package/.medusa/server/src/api/admin/email/templates/[kind]/preview/route.js +2 -2
  27. package/.medusa/server/src/api/admin/email/templates/[kind]/route.js +2 -2
  28. package/.medusa/server/src/api/admin/email/templates/[kind]/send-test/route.js +2 -2
  29. package/.medusa/server/src/api/admin/email/templates/[kind]/test-sources/route.js +2 -2
  30. package/.medusa/server/src/api/admin/email/templates/route.js +2 -2
  31. package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/actions/[action_key]/route.js +2 -2
  32. package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/actions/[action_key]/runs/route.js +2 -2
  33. package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/route.js +2 -2
  34. package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/settings/route.js +2 -2
  35. package/.medusa/server/src/api/admin/plugin-features/route.js +2 -2
  36. package/.medusa/server/src/api/admin/tax-compliance/customer-addresses/[address_id]/tax-identification/route.js +15 -0
  37. package/.medusa/server/src/api/admin-first-login-password-policy.js +2 -2
  38. package/.medusa/server/src/api/answer-code-provider-route-not-found.js +2 -2
  39. package/.medusa/server/src/api/check-customer-address-tax-identification.js +93 -0
  40. package/.medusa/server/src/api/code-account-password-routes.js +2 -2
  41. package/.medusa/server/src/api/customer-address-identification-response.js +14 -0
  42. package/.medusa/server/src/api/middlewares.js +61 -7
  43. package/.medusa/server/src/api/password-policy.js +2 -2
  44. package/.medusa/server/src/api/plugin-actions.js +2 -2
  45. package/.medusa/server/src/api/plugin-features-http.js +2 -2
  46. package/.medusa/server/src/api/require-login-code-customer-email.js +2 -2
  47. package/.medusa/server/src/api/store/carts/[id]/completion-check/route.js +2 -2
  48. package/.medusa/server/src/api/store/carts/[id]/tax-identification/route.js +179 -0
  49. package/.medusa/server/src/api/store/customer-auth/password-policy/route.js +2 -2
  50. package/.medusa/server/src/api/store/customers/me/addresses/[address_id]/tax-identification/route.js +12 -0
  51. package/.medusa/server/src/api/store/customers/me/tax-identifications/route.js +14 -0
  52. package/.medusa/server/src/api/store/plugin-features/[plugin_key]/route.js +2 -2
  53. package/.medusa/server/src/api/store/plugin-features/route.js +2 -2
  54. package/.medusa/server/src/api/store/tax-identification-policy/route.js +21 -0
  55. package/.medusa/server/src/api/tax-identification-message.js +9 -0
  56. package/.medusa/server/src/api/to-response-amount.js +19 -0
  57. package/.medusa/server/src/customer-auth/application/prepare-code-account-password-reset.js +2 -2
  58. package/.medusa/server/src/customer-auth/application/request-login-code.js +2 -2
  59. package/.medusa/server/src/customer-auth/customer-password-policy.js +2 -2
  60. package/.medusa/server/src/customer-auth/domain/auth-methods.js +2 -2
  61. package/.medusa/server/src/customer-auth/domain/login-code.js +2 -2
  62. package/.medusa/server/src/customer-auth/login-code-state.js +2 -2
  63. package/.medusa/server/src/customer-auth/plugin-features.js +2 -2
  64. package/.medusa/server/src/documents/paths/documents.js +2 -2
  65. package/.medusa/server/src/documents/schemas/documents.js +2 -2
  66. package/.medusa/server/src/email/application/deliver-login-code.js +2 -2
  67. package/.medusa/server/src/email/domain/business-keys.js +2 -2
  68. package/.medusa/server/src/email/templates/blocks/render-document.js +2 -2
  69. package/.medusa/server/src/email/templates/render.js +2 -2
  70. package/.medusa/server/src/email/templates/validate.js +2 -2
  71. package/.medusa/server/src/email/types/template-data.js +2 -2
  72. package/.medusa/server/src/jobs/tax-compliance-state-sync.js +26 -0
  73. package/.medusa/server/src/modules/documents/service.js +2 -2
  74. package/.medusa/server/src/modules/email/domain/attachment.js +2 -2
  75. package/.medusa/server/src/modules/email/service.js +3 -3
  76. package/.medusa/server/src/modules/tax-compliance/domain/validation-policy.js +48 -0
  77. package/.medusa/server/src/modules/tax-compliance/index.js +15 -0
  78. package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260814120459.js +18 -0
  79. package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260814121724.js +14 -0
  80. package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260814153410.js +14 -0
  81. package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260929091620.js +27 -0
  82. package/.medusa/server/src/modules/tax-compliance/migrations/Migration20261011090147.js +16 -0
  83. package/.medusa/server/src/modules/tax-compliance/models/tax-compliance-state.js +29 -0
  84. package/.medusa/server/src/modules/tax-compliance/models/tax-compliance-validation.js +74 -0
  85. package/.medusa/server/src/modules/tax-compliance/runtime-settings.js +43 -0
  86. package/.medusa/server/src/modules/tax-compliance/service.js +693 -0
  87. package/.medusa/server/src/providers/email-otp/service.js +2 -2
  88. package/.medusa/server/src/providers/tax-compliance/index.js +11 -0
  89. package/.medusa/server/src/providers/tax-compliance/service.js +179 -0
  90. package/.medusa/server/src/public/constants.js +4 -2
  91. package/.medusa/server/src/public/index.js +6 -2
  92. package/.medusa/server/src/public/shop-config.js +6 -6
  93. package/.medusa/server/src/public/tax-compliance.js +22 -0
  94. package/.medusa/server/src/shared/lib/index.js +2 -1
  95. package/.medusa/server/src/shared/lib/optional-registration.js +34 -0
  96. package/.medusa/server/src/shop-config/{build-food-shop-config.js → build-shop-config.js} +15 -18
  97. package/.medusa/server/src/shop-config/options.js +4 -4
  98. package/.medusa/server/src/subscribers/affiliate-application-submitted.js +2 -2
  99. package/.medusa/server/src/subscribers/affiliate-consignment-reminder-due.js +2 -2
  100. package/.medusa/server/src/subscribers/documents-order-cancellation-ready.js +2 -2
  101. package/.medusa/server/src/subscribers/documents-refund-ready.js +2 -2
  102. package/.medusa/server/src/subscribers/documents-shipment-invoice-assignment-decided.js +2 -2
  103. package/.medusa/server/src/subscribers/packing-list-allocation-revised.js +2 -2
  104. package/.medusa/server/src/subscribers/tax-compliance-plugin-features-updated.js +25 -0
  105. package/.medusa/server/src/tax-compliance/complete-cart-hooks.js +26 -0
  106. package/.medusa/server/src/tax-compliance/integrations/finanzonline/client.js +222 -0
  107. package/.medusa/server/src/tax-compliance/integrations/finanzonline/validator.js +50 -0
  108. package/.medusa/server/src/tax-compliance/integrations/registry.js +26 -0
  109. package/.medusa/server/src/tax-compliance/integrations/validator.js +19 -0
  110. package/.medusa/server/src/tax-compliance/lib/order-tax-context.js +100 -0
  111. package/.medusa/server/src/tax-compliance/lib/tax-compliance-state.js +59 -0
  112. package/.medusa/server/src/tax-compliance/lib/tax-runtime-settings.js +79 -0
  113. package/.medusa/server/src/tax-compliance/plugin-features.js +73 -0
  114. package/.medusa/server/src/tax-compliance/resolve-services.js +7 -0
  115. package/.medusa/server/src/tax-compliance/types/tax-compliance.js +6 -0
  116. package/.medusa/server/src/workflows/documents/issue-invoice-after-shipment.js +25 -30
  117. package/.medusa/server/src/workflows/documents/issue-packing-list.js +3 -14
  118. package/.medusa/server/src/workflows/hooks/cart-tax-context.js +78 -0
  119. package/.medusa/server/src/workflows/hooks/order-tax-context.js +7 -0
  120. package/.medusa/server/src/workflows/hooks/shipping-tax-apportionment.js +145 -0
  121. package/.medusa/server/src/workflows/tax-compliance/apportion-order-shipping-tax.js +72 -0
  122. package/.medusa/server/src/workflows/tax-compliance/domain/requires-frozen-tax-evidence.js +27 -0
  123. package/.medusa/server/src/workflows/tax-compliance/freeze-order-evidence.js +125 -0
  124. package/.medusa/server/src/workflows/tax-compliance/submit-customer-address-tax-identification.js +18 -0
  125. package/.medusa/server/src/workflows/tax-compliance/submit-tax-identification.js +18 -0
  126. package/README.md +393 -61
  127. package/dist/customer-auth/domain/auth-methods.js +1 -1
  128. package/dist/customer-auth/plugin-features.js +1 -1
  129. package/dist/documents/types/admin-api.d.ts +1 -1
  130. package/dist/documents/types/document-summary.d.ts +1 -1
  131. package/dist/documents/types/store-api.d.ts +1 -1
  132. package/dist/email/types/template-data.js +1 -1
  133. package/dist/public/constants.d.ts +2 -0
  134. package/dist/public/constants.js +3 -1
  135. package/dist/public/index.d.ts +2 -1
  136. package/dist/public/index.js +5 -1
  137. package/dist/public/shop-config.d.ts +4 -4
  138. package/dist/public/shop-config.js +5 -5
  139. package/dist/public/tax-compliance.d.ts +30 -0
  140. package/dist/public/tax-compliance.js +21 -0
  141. package/dist/shared/lib/index.d.ts +1 -0
  142. package/dist/shared/lib/index.js +1 -0
  143. package/dist/shared/lib/optional-registration.d.ts +18 -0
  144. package/dist/shared/lib/optional-registration.js +33 -0
  145. package/dist/shop-config/{build-food-shop-config.d.ts → build-shop-config.d.ts} +12 -12
  146. package/dist/shop-config/{build-food-shop-config.js → build-shop-config.js} +14 -17
  147. package/dist/shop-config/options.d.ts +3 -3
  148. package/dist/shop-config/options.js +3 -3
  149. package/package.json +7 -6
package/README.md CHANGED
@@ -6,8 +6,10 @@ Admin hub under **Settings → Hartl Services Plugins** with one page per
6
6
  plugin, a Store endpoint for storefronts and helpers that plugins use to
7
7
  enforce their toggles and read their settings. Since 1.0.0 it also contains
8
8
  the shop's transactional E-Mail, order Documents (invoices, cancellations,
9
- packing lists) and S3 file storage, and the turnkey `medusa-config.ts` preset
10
- (see "E-Mail", "Dokumente", "Dateispeicher (S3)" and "Shop-Config-Preset").
9
+ packing lists) and S3 file storage, and the turnkey `medusa-config.ts` preset,
10
+ and since 3.0.0 the EU tax compliance (UID check, Tax provider) (see "E-Mail",
11
+ "Dokumente", "Steuer-Compliance", "Dateispeicher (S3)" and
12
+ "Shop-Config-Preset").
11
13
 
12
14
  ## What it does
13
15
 
@@ -54,16 +56,20 @@ packing lists) and S3 file storage, and the turnkey `medusa-config.ts` preset
54
56
  "E-Mail"), issues invoices, cancellation documents and packing lists as
55
57
  private PDFs (module `documents`, see "Dokumente") and stores files in
56
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").
57
63
  - Declares which login methods customers may use (module `customer_auth`:
58
64
  e-mail + password and a one-time code by e-mail), provides the `email-otp`
59
65
  auth provider and the code request route (see "Kunden-Login").
60
- - Builds a food shop's complete `medusa-config.ts` from a few shop facts
66
+ - Builds a shop's complete `medusa-config.ts` from a few shop facts
61
67
  (`@hartl-services/medusa-base/shop-config`, see "Shop-Config-Preset").
62
68
 
63
69
  ## Installation in a shop
64
70
 
65
71
  ```bash
66
- pnpm add @hartl-services/medusa-base @hartl-services/medusa-food-supplements-contracts
72
+ pnpm add @hartl-services/medusa-base @hartl-services/medusa-contracts
67
73
  ```
68
74
 
69
75
  ```ts
@@ -116,6 +122,27 @@ exports from the matching `@hartl-services/medusa-base` subpath. The
116
122
  `documentsEnabled` option is gone; Documents is always installed, and the
117
123
  e-mail module follows the Documents `invoices` toggle.
118
124
 
125
+ Upgrading to 3.0.0 (Steuer-Compliance merged): remove
126
+ `@hartl-services/medusa-tax-compliance` from `package.json` and from the
127
+ `plugins` list and change the Tax provider resolve path to
128
+ `@hartl-services/medusa-base/providers/tax-compliance` (the provider id
129
+ `tp_tax_compliance`, the module key `tax_compliance`, its tables and
130
+ migrations are unchanged, so no data migration is needed). Plugins that
131
+ reached `tax_compliance` by container key keep working; the typed helper is
132
+ `listCustomerAddressTaxIdentifications` (see "Steuer-Compliance"). The other
133
+ Hartl Services plugins need the versions released with 3.0.0 (their peer
134
+ range is `^3`).
135
+
136
+ Upgrading to 4.0.0 (renames): the Contracts package is
137
+ `@hartl-services/medusa-contracts` (1.0.0, formerly
138
+ `@hartl-services/medusa-food-supplements-contracts`; same subpaths), and the
139
+ Shop-Config-Preset exports are `buildShopConfig`, `defineShopConfig`,
140
+ `ShopConfigOptions`, `ShopConfigInput`, `ShopPlugin` and `ShopModule`
141
+ (formerly `buildFoodShopConfig` etc.). Replace the import names in
142
+ `medusa-config.ts` and the Contracts package name in the shop and its
143
+ Storefront; nothing else changes. The other Hartl Services plugins need the
144
+ versions released with 4.0.0 (peer range `^4`).
145
+
119
146
  Peer dependencies are the host's Medusa 2.19 packages (`@medusajs/framework`,
120
147
  `@medusajs/medusa`, `@medusajs/admin-sdk`, `@medusajs/js-sdk`, `@medusajs/ui`, `@medusajs/icons`)
121
148
  plus React, React Router and TanStack Query in the versions the Medusa 2.19
@@ -157,8 +184,8 @@ Store values are effective values only; a storefront uses them to hide
157
184
  features. DTOs, schemas and paths come from the Contracts package:
158
185
 
159
186
  ```ts
160
- import type { StorePluginFeaturesListResponse } from "@hartl-services/medusa-food-supplements-contracts/types/plugin-features-store"
161
- import { pluginFeaturesStorePaths } from "@hartl-services/medusa-food-supplements-contracts/paths/plugin-features"
187
+ import type { StorePluginFeaturesListResponse } from "@hartl-services/medusa-contracts/types/plugin-features-store"
188
+ import { pluginFeaturesStorePaths } from "@hartl-services/medusa-contracts/paths/plugin-features"
162
189
 
163
190
  const { plugins } = await sdk.client.fetch<StorePluginFeaturesListResponse>(
164
191
  pluginFeaturesStorePaths.get.list,
@@ -315,7 +342,7 @@ subscribes to it and filters on its own `plugin_key`:
315
342
  import {
316
343
  PLUGIN_FEATURES_UPDATED_EVENT,
317
344
  PluginFeaturesUpdatedEventSchema,
318
- } from "@hartl-services/medusa-food-supplements-contracts/events/plugin-features"
345
+ } from "@hartl-services/medusa-contracts/events/plugin-features"
319
346
 
320
347
  export default async function onToggleChange({ event, container }: SubscriberArgs<unknown>) {
321
348
  const payload = PluginFeaturesUpdatedEventSchema.parse(event.data)
@@ -927,7 +954,7 @@ class MyModuleService extends MedusaService({ /* … */ }) {
927
954
 
928
955
  **Ordering.** Ascending `order`, then module key, then workflow id. In use:
929
956
  Affiliate 100 (`affiliate-validate-complete-cart`,
930
- `finalize-affiliate-order-attribution`), tax compliance 200
957
+ `finalize-affiliate-order-attribution`), the base's Steuer-Compliance 200
931
958
  (`freeze-tax-compliance-order-evidence`, `orderCreated` only).
932
959
 
933
960
  **Rollback semantics.**
@@ -1438,7 +1465,7 @@ Options of the `email` namespace:
1438
1465
  that confirms an account, needed while `customer_verification` is on.
1439
1466
  `adminPasswordResetUrl` (optional builder, receives `{ token }`) is the
1440
1467
  Medusa Admin's "choose a new password" page; without it an Admin user's
1441
- password reset fails with `render_failed`. `buildFoodShopConfig` sets
1468
+ password reset fails with `render_failed`. `buildShopConfig` sets
1442
1469
  `<MEDUSA_BACKEND_URL>/app/reset-password?token=<token>` (the dashboard reads
1443
1470
  the token from the query).
1444
1471
  `adminAffiliateApplicationsUrl` (optional builder, receives
@@ -1460,7 +1487,7 @@ always part of the base.
1460
1487
 
1461
1488
  The event contracts the module consumes for shipment, cancellation, refund and
1462
1489
  affiliate-reminder and partner-application e-mails come from
1463
- `@hartl-services/medusa-food-supplements-contracts/events/documents` and
1490
+ `@hartl-services/medusa-contracts/events/documents` and
1464
1491
  `/events/affiliate`, so installing the base does not pull in Affiliate.
1465
1492
 
1466
1493
  The provider reads no environment variables (since 2.9.0; the former
@@ -1641,7 +1668,7 @@ the toggle itself and follow at once. A failed read keeps the last value and
1641
1668
  logs a warning; before the first successful read the copy is off. The copy
1642
1669
  lives on `globalThis` because the preset (`dist`) and the plugin code Medusa
1643
1670
  loads (`.medusa/server`) are compiled separately. Only the preset
1644
- (`buildFoodShopConfig` / `defineFoodShopConfig`) sets the getter; a shop with a
1671
+ (`buildShopConfig` / `defineShopConfig`) sets the getter; a shop with a
1645
1672
  hand-written config must set `authVerificationsPerActor` itself (a fixed list,
1646
1673
  changed by a restart) for the toggle to have any effect on logins.
1647
1674
 
@@ -1680,7 +1707,7 @@ Storefront contract (DTOs, schema and path from the Contracts package,
1680
1707
  status plus the attachment library (documents with append-only PDF
1681
1708
  versions).
1682
1709
  - Public HTTP DTOs, schemas, and paths remain in
1683
- `@hartl-services/medusa-food-supplements-contracts`.
1710
+ `@hartl-services/medusa-contracts`.
1684
1711
 
1685
1712
  The base's `/types` and `/config` exports are dedicated server integration
1686
1713
  surfaces for the backend. The Documents and Affiliate event contracts live in
@@ -1918,7 +1945,7 @@ an info box lists number, customer, e-mail and order time, followed by an
1918
1945
  "Im Admin öffnen" button and the items, totals and addresses. Two optional
1919
1946
  `ShopEmailConfiguration` fields drive it: `adminOrderUrl({ orderId })` (the
1920
1947
  button is left out without it) and `timeZone` (IANA; the order time is left
1921
- out without it). `buildFoodShopConfig` sets both (`<MEDUSA_BACKEND_URL>/app/
1948
+ out without it). `buildShopConfig` sets both (`<MEDUSA_BACKEND_URL>/app/
1922
1949
  orders/<id>` and its `timeZone` option). The layout receives `audience`
1923
1950
  (`"shop"` for this kind, `affiliate.application_submitted.admin`, `system.credential_expiry.admin`, `user.invite` and `auth.admin_password_reset`, `"customer"` for all others) and
1924
1951
  shows the contact line only to customers.
@@ -2030,7 +2057,7 @@ Partner-Bewerbung von <Firma>" (without a company the applicant's name, then
2030
2057
  the e-mail address; just "Neue Partner-Bewerbung" if none is known). An info
2031
2058
  box lists company, name and e-mail (only the present ones), the applicant's
2032
2059
  message follows as an escaped paragraph, and an "Im Admin prüfen" button links
2033
- `adminAffiliateApplicationsUrl` (left out without it; `buildFoodShopConfig`
2060
+ `adminAffiliateApplicationsUrl` (left out without it; `buildShopConfig`
2034
2061
  sets `<MEDUSA_BACKEND_URL>/app/affiliates/applications`). Its layout audience
2035
2062
  is `"shop"`. All applicant text is HTML-escaped.
2036
2063
 
@@ -2338,8 +2365,8 @@ Admin consumers import wire DTOs and paths only from the Contracts package and
2338
2365
  reuse their configured Medusa SDK:
2339
2366
 
2340
2367
  ```ts
2341
- import type { ListAdminEmailMessagesResponse } from "@hartl-services/medusa-food-supplements-contracts/types/email-admin";
2342
- import { emailAdminPaths } from "@hartl-services/medusa-food-supplements-contracts/paths/email";
2368
+ import type { ListAdminEmailMessagesResponse } from "@hartl-services/medusa-contracts/types/email-admin";
2369
+ import { emailAdminPaths } from "@hartl-services/medusa-contracts/paths/email";
2343
2370
 
2344
2371
  const response = await sdk.client.fetch<ListAdminEmailMessagesResponse>(
2345
2372
  emailAdminPaths.get.list,
@@ -2349,7 +2376,7 @@ const response = await sdk.client.fetch<ListAdminEmailMessagesResponse>(
2349
2376
  Do not import E-Mail module implementations, generated `.medusa` files, or
2350
2377
  duplicate runtime schemas in an Admin consumer. The base's own exports remain
2351
2378
  internal server and generated Admin integration surfaces; Email HTTP contracts
2352
- are published only by `@hartl-services/medusa-food-supplements-contracts`.
2379
+ are published only by `@hartl-services/medusa-contracts`.
2353
2380
 
2354
2381
  ### Development and Rollout
2355
2382
 
@@ -2453,13 +2480,9 @@ separate documents package since 1.0.0.
2453
2480
 
2454
2481
  ### Configuration
2455
2482
 
2456
- `@hartl-services/medusa-tax-compliance` is a required *registered plugin*,
2457
- intentionally not a package dependency of the base: install it and list it in
2458
- `medusa-config.ts` yourself. The invoice workflow resolves the
2459
- `tax_compliance` module with no fallback; a shop that does not register Tax
2460
- Compliance fails at invoice issuance time (the first `shipment.created`
2461
- event) with an `UNEXPECTED_STATE` error naming the missing plugin, not at
2462
- startup.
2483
+ The invoice workflow reads the order's frozen B2B evidence from the base's own
2484
+ `tax_compliance` module (see "Steuer-Compliance"); register the Tax provider
2485
+ `@hartl-services/medusa-base/providers/tax-compliance` as shown there.
2463
2486
 
2464
2487
  ```ts
2465
2488
  export default defineConfig({
@@ -2468,10 +2491,7 @@ export default defineConfig({
2468
2491
  resolve: "@medusajs/medusa/tax",
2469
2492
  options: {
2470
2493
  providers: [
2471
- {
2472
- resolve:
2473
- "@hartl-services/medusa-tax-compliance/providers/tax-compliance",
2474
- },
2494
+ { resolve: "@hartl-services/medusa-base/providers/tax-compliance" },
2475
2495
  ],
2476
2496
  },
2477
2497
  },
@@ -2490,10 +2510,6 @@ export default defineConfig({
2490
2510
  },
2491
2511
  },
2492
2512
  },
2493
- {
2494
- resolve: "@hartl-services/medusa-tax-compliance",
2495
- options: {},
2496
- },
2497
2513
  ],
2498
2514
  });
2499
2515
  ```
@@ -2608,7 +2624,8 @@ medusa-config fallback: what is stored in the Admin is what is printed.
2608
2624
 
2609
2625
  Requirements/couplings:
2610
2626
 
2611
- - A registered Tax Compliance module (`tax_compliance`) as shown above.
2627
+ - The Tax provider `tp_tax_compliance` of the base's Steuer-Compliance on the
2628
+ regions, so orders carry UID-based exemptions.
2612
2629
  - A Chromium/Chrome executable and a Medusa File provider that supports
2613
2630
  private files (see "Development" below for the sandbox/non-root note).
2614
2631
  Documents stores PDFs through `Modules.FILE` and streams them only through
@@ -2767,8 +2784,8 @@ address/email, file and artifact IDs, leases and resolution state.
2767
2784
  For a typed Storefront integration:
2768
2785
 
2769
2786
  ```ts
2770
- import type { ListStoreDocumentsResponse } from "@hartl-services/medusa-food-supplements-contracts/types/documents-store";
2771
- import { documentsPaths } from "@hartl-services/medusa-food-supplements-contracts/paths/documents";
2787
+ import type { ListStoreDocumentsResponse } from "@hartl-services/medusa-contracts/types/documents-store";
2788
+ import { documentsPaths } from "@hartl-services/medusa-contracts/paths/documents";
2772
2789
 
2773
2790
  const { documents: issuedDocuments } =
2774
2791
  await sdk.client.fetch<ListStoreDocumentsResponse>(
@@ -2784,10 +2801,10 @@ storage URL.
2784
2801
 
2785
2802
  The same Contracts boundary applies to all Documents and Tax Compliance public
2786
2803
  routes: import transport DTOs only from
2787
- `@hartl-services/medusa-food-supplements-contracts/types/<feature>`, paths only from
2788
- `@hartl-services/medusa-food-supplements-contracts/paths/<feature>`, and call the
2804
+ `@hartl-services/medusa-contracts/types/<feature>`, paths only from
2805
+ `@hartl-services/medusa-contracts/paths/<feature>`, and call the
2789
2806
  Storefront's configured Medusa SDK. A consumer that needs a runtime guard may
2790
- opt in to `@hartl-services/medusa-food-supplements-contracts/schemas/<feature>`; it must
2807
+ opt in to `@hartl-services/medusa-contracts/schemas/<feature>`; it must
2791
2808
  not import either the plugin implementation or generated `.medusa` output.
2792
2809
 
2793
2810
  ### Admin order detail
@@ -2935,7 +2952,7 @@ canceled and prevents a concurrent render from being published as active.
2935
2952
 
2936
2953
  Documents owns no Notification or delivery state. Its integration boundary is
2937
2954
  the strict versioned event contract exported from
2938
- `@hartl-services/medusa-food-supplements-contracts/events/documents`. Shipment assignments, standalone Refund resolutions and Order-
2955
+ `@hartl-services/medusa-contracts/events/documents`. Shipment assignments, standalone Refund resolutions and Order-
2939
2956
  cancellation resolutions each carry an `event_emitted_at` marker that is set
2940
2957
  only after successful enqueue.
2941
2958
 
@@ -2955,7 +2972,7 @@ orphan but cannot commit another Artifact, carrier or resolution.
2955
2972
  #### Exact outgoing event contracts
2956
2973
 
2957
2974
  The server-only Contracts subpath
2958
- `@hartl-services/medusa-food-supplements-contracts/events/documents` exports the
2975
+ `@hartl-services/medusa-contracts/events/documents` exports the
2959
2976
  event-name constants,
2960
2977
  strict Zod schemas and inferred payload types for exactly these three events:
2961
2978
 
@@ -3045,8 +3062,8 @@ schemas and route paths, but no second Storefront client. Consumers use their
3045
3062
  own Medusa SDK instance:
3046
3063
 
3047
3064
  ```ts
3048
- import type { GetStoreDocumentResponse } from "@hartl-services/medusa-food-supplements-contracts/types/documents-store";
3049
- import { documentsPaths } from "@hartl-services/medusa-food-supplements-contracts/paths/documents";
3065
+ import type { GetStoreDocumentResponse } from "@hartl-services/medusa-contracts/types/documents-store";
3066
+ import { documentsPaths } from "@hartl-services/medusa-contracts/paths/documents";
3050
3067
 
3051
3068
  const response = await sdk.client.fetch<GetStoreDocumentResponse>(
3052
3069
  documentsPaths.store.get.retrieve(documentId),
@@ -3054,10 +3071,10 @@ const response = await sdk.client.fetch<GetStoreDocumentResponse>(
3054
3071
  );
3055
3072
  ```
3056
3073
 
3057
- `@hartl-services/medusa-food-supplements-contracts/types/documents-store` and
3058
- `@hartl-services/medusa-food-supplements-contracts/types/documents-admin` contain the
3074
+ `@hartl-services/medusa-contracts/types/documents-store` and
3075
+ `@hartl-services/medusa-contracts/types/documents-admin` contain the
3059
3076
  respective type-only DTOs. Import
3060
- `@hartl-services/medusa-food-supplements-contracts/schemas/documents` only for explicit
3077
+ `@hartl-services/medusa-contracts/schemas/documents` only for explicit
3061
3078
  runtime validation. The common Storefront case imports only types, which are
3062
3079
  erased by TypeScript, and the path module; it never imports the plugin's Medusa
3063
3080
  server code. Store and Admin responses use the same safe `DocumentSummary`
@@ -3069,8 +3086,8 @@ and README note together when introducing another Documents route.
3069
3086
  Packing-list consumers stay Admin-only:
3070
3087
 
3071
3088
  ```ts
3072
- import type { ListAdminOrderPackingListsResponse } from "@hartl-services/medusa-food-supplements-contracts/types/documents-admin";
3073
- import { packingListsPaths } from "@hartl-services/medusa-food-supplements-contracts/paths/documents";
3089
+ import type { ListAdminOrderPackingListsResponse } from "@hartl-services/medusa-contracts/types/documents-admin";
3090
+ import { packingListsPaths } from "@hartl-services/medusa-contracts/paths/documents";
3074
3091
 
3075
3092
  const response = await sdk.client.fetch<ListAdminOrderPackingListsResponse>(
3076
3093
  packingListsPaths.admin.get.listForOrder(orderId),
@@ -3078,6 +3095,322 @@ const response = await sdk.client.fetch<ListAdminOrderPackingListsResponse>(
3078
3095
  );
3079
3096
  ```
3080
3097
 
3098
+ ## Steuer-Compliance
3099
+
3100
+ There is no separate `@hartl-services/medusa-tax-compliance` package since
3101
+ 3.0.0: the EU tax compliance (module `tax_compliance`, the Tax Module provider
3102
+ `tp_tax_compliance`, the UID check routes, the order-evidence freeze and the
3103
+ apportioned shipping tax) is part of the base. The module owns the durable
3104
+ validation evidence; a Storefront never imports its implementation, only
3105
+ `@hartl-services/medusa-contracts`.
3106
+
3107
+ ### Configuration
3108
+
3109
+ Register the Tax Module provider in `medusa-config.ts` (the Shop-Config-Preset
3110
+ does this); the module itself boots with the base and has no options:
3111
+
3112
+ ```ts
3113
+ modules: [
3114
+ {
3115
+ resolve: "@medusajs/medusa/tax",
3116
+ options: {
3117
+ providers: [
3118
+ { resolve: "@hartl-services/medusa-base/providers/tax-compliance" },
3119
+ ],
3120
+ },
3121
+ },
3122
+ ],
3123
+ ```
3124
+
3125
+ The provider is configured without a secondary `id`, so Medusa registers its
3126
+ class identifier `tax_compliance` as exactly `tp_tax_compliance`; regions
3127
+ select it as their tax provider. `medusa db:migrate` creates the tables
3128
+ `tax_compliance_validation` and `tax_compliance_state` (unchanged names and
3129
+ migrations of the former package, so an upgraded shop needs no data
3130
+ migration).
3131
+
3132
+ The merchant's own country and VAT number come only from the Shop-Stammdaten
3133
+ (fields `country_code` and `vat_number`); there is no config fallback. They
3134
+ select the qualified VAT validator (currently only the Austrian FinanzOnline
3135
+ adapter). While either is missing, carts get ordinary B2C tax.
3136
+
3137
+ ### Runtime settings
3138
+
3139
+ Declared on the Steuer-Compliance descriptor and edited on its Admin page
3140
+ (Settings → Hartl Services Plugins → Steuer-Compliance):
3141
+
3142
+ | Key | Label | Type | Required |
3143
+ | ----------------------------- | -------------- | ------ | -------- |
3144
+ | `finanzonline_participant_id` | Teilnehmer-ID | string | yes |
3145
+ | `finanzonline_user_id` | Benutzer-ID | string | yes |
3146
+ | `finanzonline_password` | PIN | secret | yes |
3147
+
3148
+ All three sit in the group "FinanzOnline" ("Webservice-Benutzer für die
3149
+ qualifizierte UID-Abfrage.").
3150
+
3151
+ - Read per validation, only for an Austrian merchant with a real UID. There
3152
+ is no environment or medusa-config fallback; `FINANZONLINE_*` variables are
3153
+ ignored.
3154
+ - All three are needed as a set. While any is missing the plugin page shows
3155
+ "nicht konfiguriert", one warning is logged per process and not-configured
3156
+ state, and the qualified check answers `unavailable`
3157
+ (`missing_credentials`) without calling FinanzOnline.
3158
+ - A failing settings read is logged (`logger.error`, reported to Sentry by
3159
+ the base logger wrapper) and handled like missing credentials.
3160
+ - Merchant country and UID are read from the shop master data. On the cart
3161
+ tax path (tax-context hook) and in the Store route a failing read is logged
3162
+ and treated as "no merchant": ordinary B2C tax, and the route answers 500
3163
+ as before for a missing merchant profile. The cart tax path and the
3164
+ order-evidence freeze read the master data only for a cart that has a
3165
+ current UID validation record, so B2C carts never touch the settings and
3166
+ an outage does not log an error per tax calculation. The order-evidence
3167
+ freeze at checkout lets a read failure propagate, so a zero-rated order
3168
+ fails and an ordinarily taxed one continues.
3169
+ - Both are read at app level, never by the module service (a module does not
3170
+ reach into other modules): `src/tax-compliance/lib/tax-runtime-settings.ts`
3171
+ provides `resolveMerchantProfile(container, { failSafe })` and
3172
+ `resolveFinanzOnlineCredentials(container)`, called with the application
3173
+ container of the Store route, the validation and freeze workflow steps and
3174
+ the cart tax-context hook. The service takes the results as parameters:
3175
+ `validateCart(input, credentials)` (the step reads the credentials only
3176
+ when `requiresFinanzOnlineCredentials(input)` says so, and never passes
3177
+ them as workflow input), `getCurrentValidationId(cart, loadMerchant)` and
3178
+ `freezeOrderEvidence(orderId, cart, loadMerchant)` (the loader runs only
3179
+ after a current validation record was found) and
3180
+ `isValidationEligibleForTax(validationId, countryCode, merchant)`.
3181
+ - The cart tax-context hook puts the merchant profile it checked the
3182
+ evidence against into `additional_context.tax_compliance_merchant`, next to
3183
+ the validation ID. The tax provider runs in the Tax Module's isolated
3184
+ container and reads the merchant only from that context; without one it
3185
+ answers ordinary B2C rates. The order tax path needs no merchant: it checks
3186
+ the evidence frozen under the order ID.
3187
+
3188
+ ### Feature toggles
3189
+
3190
+ Steuer-Compliance declares its switch and three features under the plugin key
3191
+ `tax_compliance` (`TAX_COMPLIANCE_PLUGIN_KEY` / `TAX_COMPLIANCE_FEATURES` from
3192
+ the Contracts `/paths/tax-compliance` subpath). An Admin switches them under
3193
+ Settings → "Hartl Services Plugins"; Storefronts read the effective values
3194
+ from `GET /store/plugin-features/tax_compliance`. All features default to on.
3195
+ The plugin switch off counts as all three features off.
3196
+
3197
+ | Key | Label | Default | Off means |
3198
+ | ---------------- | ------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3199
+ | `vat_validation` | UID-Prüfung erlauben | on | `POST /store/customers/me/addresses/:address_id/tax-identification`, `GET /store/customers/me/tax-identifications` and the Admin re-check answer 404; Affiliate partner registration takes VAT IDs unchecked. Also switches `b2b` off. The FinanzOnline settings are dimmed. |
3200
+ | `b2b` | B2B-Verkauf im Checkout | on | `POST /store/carts/:id/tax-identification` answers 404 (before body validation); carts get no reverse charge, even with evidence checked earlier today; completing an ordinarily taxed order freezes no evidence. Child of `vat_validation`. Storefronts keep the company name and hide the VAT id. |
3201
+ | `shipping_tax` | Versandsteuer-Compliance | on | Shipping is taxed at the shipping option's own rate (Medusa's default) instead of the net-value-weighted item rate, for carts and order recalculations. |
3202
+
3203
+ With `shipping_tax` on, a cart with items at several rates gets one shipping
3204
+ tax line "Anteilige Versandsteuer" (code `APPORTIONED_SHIPPING_TAX_CODE` from
3205
+ `/lib`) at the items' rates weighted by their net amount after discounts
3206
+ (computed like Medusa's line totals), which yields the same tax as splitting
3207
+ the shipping by that amount onto each rate. Medusa taxes a cart before it
3208
+ applies promotions and a code redeemed through `POST /store/carts/:id/promotions`
3209
+ recalculates no tax, so the cart's rate can lag behind its discounts; the
3210
+ `orderCreated` participant therefore sets the order's rate again from its
3211
+ final discounts (tax-inclusive shipping only, whose paid gross amount stays
3212
+ the same; on error the order keeps the cart's rate and a warning is logged).
3213
+ Medusa stores the rate with six decimals. The invoice (see "Dokumente")
3214
+ splits that line back onto the item rates by the same key.
3215
+
3216
+ `GET /store/tax-identification-policy` follows only the plugin switch.
3217
+ Evidence frozen under an order that was zero-rated keeps counting for that
3218
+ order whatever the toggles say now.
3219
+
3220
+ The tax hooks and the order-evidence freeze never read a toggle. The
3221
+ `tax_compliance_state` row mirrors the effective `b2b` and `shipping_tax`
3222
+ values; the `plugin-features.updated.v1` subscriber and the
3223
+ `tax-compliance-state-sync` job (every 5 minutes) write it. Before the first
3224
+ sync (no row) both count as on; when the row cannot be read both count as
3225
+ off (ordinary B2C rates, Medusa's shipping tax).
3226
+
3227
+ ### UID check in the checkout
3228
+
3229
+ Public Store request, response, schema, and path imports belong to
3230
+ `@hartl-services/medusa-contracts`:
3231
+
3232
+ ```ts
3233
+ import type { SubmitTaxIdentificationResponse } from "@hartl-services/medusa-contracts/types/tax-compliance-store";
3234
+ import { taxCompliancePaths } from "@hartl-services/medusa-contracts/paths/tax-compliance";
3235
+
3236
+ const result = await sdk.client.fetch<SubmitTaxIdentificationResponse>(
3237
+ taxCompliancePaths.store.carts.taxIdentification(cartId),
3238
+ { method: "POST", body: {} },
3239
+ );
3240
+ ```
3241
+
3242
+ The module persists normalized cart validation evidence and selects qualified
3243
+ VAT validators by merchant country. Austrian merchants use the isolated
3244
+ FinanzOnline stage-2 adapter. Missing credentials, the development UID
3245
+ placeholder `ATU00000000`, technical/rate-limit responses, and unsupported
3246
+ merchant countries produce local `unavailable` evidence. Tax calculation
3247
+ interprets missing, stale, invalid, ineligible, and unavailable evidence as the
3248
+ normal B2C outcome. Cart tax calculation only accepts same-day `eligible`
3249
+ evidence whose current cart and merchant fingerprints still match, whose UID
3250
+ prefix matches the delivery country, and whose delivery country is
3251
+ cross-border. Once evidence is frozen, order tax calculation instead verifies
3252
+ the exact evidence-ID/order-ID binding and its frozen facts; it deliberately
3253
+ does not reopen current-cart or current-day eligibility. Eligible calculations
3254
+ receive explicit zero-percent item and shipping tax lines.
3255
+
3256
+ `ATU00000000` is a development-only Austrian UID placeholder: the registry
3257
+ treats it as missing (`merchant_uid_sentinel`, never sent to FinanzOnline)
3258
+ and Documents never prints it. Enter the real UID in the Shop-Stammdaten
3259
+ before issuing production invoices.
3260
+
3261
+ The Store route is:
3262
+
3263
+ ```text
3264
+ POST /store/carts/:id/tax-identification
3265
+ ```
3266
+
3267
+ The UID belongs to the cart's billing address: company plus
3268
+ `metadata.vat_number` (`TAX_IDENTIFICATION_METADATA_KEY`), set by the
3269
+ storefront like any other address field, for guests too. The cart evidence
3270
+ fingerprint reads the UID from the current billing address, so changing or
3271
+ removing it ends the exemption. Medusa itself recomputes taxes only when the
3272
+ shipping address changes, so a storefront calls this route (empty body) after
3273
+ the addresses changed and before it initiates payment.
3274
+
3275
+ It requires a publishable key scoped to the cart's sales channel. With a UID
3276
+ on a billing address outside the merchant country (Shop-Stammdaten) it
3277
+ performs one qualified validation; without one, or for a company in the
3278
+ merchant country, it answers `status: null` without any query and only
3279
+ recomputes when the cart still has a current check record (an earlier
3280
+ exemption falls away). It then forces tax-line recomputation, reconciles an
3281
+ existing payment collection with the new cart total, and returns only the
3282
+ public status, optional user message, and safe cart totals. The deprecated
3283
+ body (`company_name` and `vat_number` together) is still accepted: the route
3284
+ writes both into the billing address first. Provider facts, evidence IDs,
3285
+ credentials, sessions, and SOAP data are never part of the response. When the
3286
+ total changed, the reconciliation deletes the cart's payment sessions, so a
3287
+ storefront re-initiates payment after the call.
3288
+
3289
+ The adapter requires a dedicated FinanzOnline web-service user whose
3290
+ credentials are entered as runtime settings (see above). The PIN is stored
3291
+ encrypted and never sent to the Admin or a Storefront. SOAP clients are
3292
+ validation-scoped, scrub their node-soap transport state after every
3293
+ operation, and always attempt logout after a successful login. Every
3294
+ `unavailable` answer from the adapter logs one warning with the failing step
3295
+ (login or UID query) and FinanzOnline's return code and message, or the
3296
+ exception message (PIN masked), so a rejected web-service user shows up in
3297
+ the server log while the shopper only sees the generic message. Same-day
3298
+ provider facts are serialized and reused by normalized merchant UID, customer
3299
+ UID, and UTC date across carts and saved addresses. Every subject still
3300
+ receives its own evidence row and fingerprint; changed company or address
3301
+ submissions are re-evaluated locally against the structured provider facts
3302
+ without another remote request. Only records FinanzOnline actually answered
3303
+ for (provider facts, valid or not) are reused: an `unavailable` without an
3304
+ answer (missing credentials, the placeholder UID, an outage or login failure)
3305
+ is checked again on the next submission instead of blocking the UID for the
3306
+ rest of the day. Transaction-scoped PostgreSQL advisory locks cover both the
3307
+ normalized subject and the UID/day reuse key. Partial unique indexes permit
3308
+ only one current evidence row per cart and per saved address, while a
3309
+ separate lookup index supports cross-subject reuse.
3310
+
3311
+ A domestic company never qualifies for a tax-free intra-Community supply, so
3312
+ its UID is neither checked nor stored as evidence. Storefronts ask for the UID
3313
+ only outside the merchant country and read that country from:
3314
+
3315
+ ```text
3316
+ GET /store/tax-identification-policy → { merchant_country_code: "at" | null }
3317
+ ```
3318
+
3319
+ `null` while the master data name no country (or cannot be read): the
3320
+ storefront then asks for every country.
3321
+
3322
+ ```ts
3323
+ import type { GetTaxIdentificationPolicyResponse } from "@hartl-services/medusa-contracts/types/tax-compliance-store";
3324
+ import { taxCompliancePaths } from "@hartl-services/medusa-contracts/paths/tax-compliance";
3325
+
3326
+ const { merchant_country_code } = await sdk.client.fetch<GetTaxIdentificationPolicyResponse>(
3327
+ taxCompliancePaths.store.taxIdentificationPolicy,
3328
+ { method: "GET" },
3329
+ );
3330
+ ```
3331
+
3332
+ Login and UID query share a 10 s budget (WSDL downloads included) and logout
3333
+ gets 3 s, so the request stays below a storefront's 15 s checkout wait and the
3334
+ database transaction holding the UID lock never waits on a hanging
3335
+ FinanzOnline call; a timeout answers `unavailable` (`technical_error`).
3336
+
3337
+ ### Saved customer addresses
3338
+
3339
+ A saved customer address carries its UID in `metadata.vat_number`
3340
+ (`TAX_IDENTIFICATION_METADATA_KEY` in the Contracts). A logged-in customer
3341
+ checks it with
3342
+
3343
+ ```text
3344
+ POST /store/customers/me/addresses/:address_id/tax-identification
3345
+ GET /store/customers/me/tax-identifications
3346
+ ```
3347
+
3348
+ Company, UID and address are read from the stored address, never from the
3349
+ request; the address's own country stands in for the delivery country.
3350
+ `verified` (status `eligible` or `ineligible`) means the UID is real and
3351
+ matches company and address. The GET route only reports evidence whose
3352
+ fingerprint still matches the address as it is now: editing company, UID or
3353
+ address drops the status until the address is checked again. Metadata alone
3354
+ never proves a check. Both routes follow the `vat_validation` feature (404
3355
+ while off) and require a customer login.
3356
+
3357
+ Other plugins read the same current evidence, whatever its status (also
3358
+ `unavailable` and `invalid`), through
3359
+ `listCustomerAddressTaxIdentifications(container, customerId)` from
3360
+ `@hartl-services/medusa-base` (typed `CustomerAddressTaxIdentification` with
3361
+ the checked `billing_address`); the caller decides with
3362
+ `isPluginFeatureEnabledByKey(container, "tax_compliance", "vat_validation")`
3363
+ whether the check is on. The Affiliate partner registration uses it.
3364
+
3365
+ An Admin checks any customer's saved address again with
3366
+
3367
+ ```text
3368
+ POST /admin/tax-compliance/customer-addresses/:address_id/tax-identification
3369
+ ```
3370
+
3371
+ (admin login, `vat_validation` feature; answers like the Store route,
3372
+ `SubmitCustomerAddressTaxIdentificationResponse`, path
3373
+ `taxCompliancePaths.admin.customerAddressTaxIdentification(addressId)`). The
3374
+ Affiliate plugin uses it to re-check a partner application whose check was
3375
+ unavailable.
3376
+
3377
+ All three FinanzOnline settings are required as a set. Otherwise the service
3378
+ stores `unavailable` evidence and checkout continues with B2C tax; the Store
3379
+ response returns the checkout warning "Die UID-Prüfung ist derzeit nicht
3380
+ verfügbar. Bitte versuchen Sie es später erneut." Invalid or ineligible B2B
3381
+ facts likewise receive ordinary B2C tax, without a warning. The registry is
3382
+ country-based: it currently provides only the Austrian FinanzOnline adapter;
3383
+ an unsupported merchant country deliberately resolves to `unavailable`.
3384
+
3385
+ ### Frozen order evidence
3386
+
3387
+ Cart tax workflows pass only the opaque current evidence ID and the merchant
3388
+ profile (country and UID) to the provider. Order tax workflows use evidence
3389
+ frozen under the order ID by the workflow
3390
+ `freeze-tax-compliance-order-evidence`, which the base runs as an
3391
+ `orderCreated` participant with `order` 200 (see "Complete-cart hook
3392
+ composition"), after Affiliate's attribution (100) when Affiliate is
3393
+ installed. After the freeze the same workflow sets the order's apportioned
3394
+ shipping rate from its final discounts (see "Feature toggles" above).
3395
+ Compensation runs in reverse (tax, then Affiliate); if the freeze fails, the
3396
+ base cancels Affiliate's already finished attribution. Tax-only module lookup
3397
+ and context failures remain fail-closed to ordinary B2C. The freeze step
3398
+ decides itself whether a failure is fatal: an auxiliary freeze failure is
3399
+ logged and contained for an ordinarily taxed B2C cart, but a zero-rated cart
3400
+ cannot complete unless its evidence was durably frozen under the order ID.
3401
+ Zero-rated means the provider set a line to 0 % although its configured tax
3402
+ rate is not 0 % (UID-based exemption); a line that is 0 % because its
3403
+ configured tax rate is 0 % (e.g. the small-business exemption) is ordinary
3404
+ taxation and needs no evidence. Freeze-compensation persistence failures are
3405
+ logged and propagated so workflow cancellation stays failed instead of
3406
+ silently leaving a stale order binding.
3407
+
3408
+ Documents reads that frozen eligible B2B evidence when an invoice is issued,
3409
+ so the document snapshot contains the company, customer UID and exemption
3410
+ wording from the completed order (see "Dokumente"). A later cart change or
3411
+ validation cannot alter an already issued document; B2C, invalid, ineligible
3412
+ and unavailable evidence adds no tax-proof wording.
3413
+
3081
3414
  ## Dateispeicher (S3)
3082
3415
 
3083
3416
  The base provides a Medusa File Module provider for durable S3-compatible
@@ -3309,7 +3642,7 @@ private URL themselves.
3309
3642
 
3310
3643
  ## Shop-Config-Preset
3311
3644
 
3312
- `@hartl-services/medusa-base/shop-config` turns a food shop's configuration
3645
+ `@hartl-services/medusa-base/shop-config` turns a shop's configuration
3313
3646
  from about 290 lines into about 30: the plugin list and order, the Redis event
3314
3647
  bus, SMTP notifications, S3 file storage, the tax and Stripe payment providers,
3315
3648
  the Admin path, the Sentry logger bridge and the environment validation are
@@ -3319,15 +3652,14 @@ loads the Sentry logger wrapper and with it `@sentry/node`; `/config` stays
3319
3652
  free of it.
3320
3653
 
3321
3654
  The preset registers the base (with its E-Mail, Documents and `/files`
3322
- options) plus five feature plugins, in this order: `medusa-base`,
3323
- `medusa-tax-compliance`, `medusa-free-shipping`, `medusa-batch-management`,
3655
+ options, and its Tax provider) plus four feature plugins, in this order:
3656
+ `medusa-base`, `medusa-free-shipping`, `medusa-batch-management`,
3324
3657
  `medusa-affiliate`, `medusa-payload-sync`.
3325
3658
 
3326
3659
  ### Installation in a shop
3327
3660
 
3328
3661
  ```bash
3329
3662
  pnpm add @hartl-services/medusa-base \
3330
- @hartl-services/medusa-tax-compliance \
3331
3663
  @hartl-services/medusa-free-shipping \
3332
3664
  @hartl-services/medusa-batch-management \
3333
3665
  @hartl-services/medusa-affiliate \
@@ -3340,9 +3672,9 @@ pnpm add @hartl-services/medusa-base \
3340
3672
 
3341
3673
  ```ts
3342
3674
  // medusa-config.ts
3343
- import { defineFoodShopConfig } from "@hartl-services/medusa-base/shop-config"
3675
+ import { defineShopConfig } from "@hartl-services/medusa-base/shop-config"
3344
3676
 
3345
- module.exports = defineFoodShopConfig({
3677
+ module.exports = defineShopConfig({
3346
3678
  displayName: "EXAMPLE SHOP",
3347
3679
  orderNotificationEmail: "orders@example.com",
3348
3680
  timeZone: "Europe/Vienna",
@@ -3355,9 +3687,9 @@ plain input, changes it and calls `defineConfig` itself:
3355
3687
 
3356
3688
  ```ts
3357
3689
  import { defineConfig } from "@medusajs/framework/utils"
3358
- import { buildFoodShopConfig } from "@hartl-services/medusa-base/shop-config"
3690
+ import { buildShopConfig } from "@hartl-services/medusa-base/shop-config"
3359
3691
 
3360
- const config = buildFoodShopConfig(options)
3692
+ const config = buildShopConfig(options)
3361
3693
  config.plugins = config.plugins.filter(
3362
3694
  (plugin) => plugin.resolve !== "@hartl-services/medusa-batch-management",
3363
3695
  )
@@ -3366,16 +3698,16 @@ module.exports = defineConfig(config)
3366
3698
 
3367
3699
  ### API
3368
3700
 
3369
- - `defineFoodShopConfig(options, environment = process.env)` returns
3370
- `defineConfig(buildFoodShopConfig(options, environment))`.
3371
- - `buildFoodShopConfig(options, environment = process.env)` returns the plain
3701
+ - `defineShopConfig(options, environment = process.env)` returns
3702
+ `defineConfig(buildShopConfig(options, environment))`.
3703
+ - `buildShopConfig(options, environment = process.env)` returns the plain
3372
3704
  config input (`admin`, `logger`, `projectConfig`, `modules`, `plugins`)
3373
3705
  before `defineConfig`. `modules` and `plugins` are ordinary arrays.
3374
- - Types: `FoodShopConfigOptions`, `FoodShopConfigInput`, `FoodShopPlugin`,
3375
- `FoodShopModule`.
3706
+ - Types: `ShopConfigOptions`, `ShopConfigInput`, `ShopPlugin`,
3707
+ `ShopModule`.
3376
3708
 
3377
3709
  Both functions validate the options first and throw a `TypeError` naming the
3378
- option (`Food shop option "timeZone" must be ...`), so a misconfigured shop
3710
+ option (`Shop option "timeZone" must be ...`), so a misconfigured shop
3379
3711
  fails at boot. They then load the `.env` file (unless disabled) and read and
3380
3712
  validate the environment; a missing variable fails with the variable's name.
3381
3713