@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.
- 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 +4 -4
- package/.medusa/server/src/admin/index.mjs +4 -4
- 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/tax-compliance/customer-addresses/[address_id]/tax-identification/route.js +15 -0
- 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 +93 -0
- package/.medusa/server/src/api/code-account-password-routes.js +2 -2
- package/.medusa/server/src/api/customer-address-identification-response.js +14 -0
- package/.medusa/server/src/api/middlewares.js +61 -7
- 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 +179 -0
- package/.medusa/server/src/api/store/customer-auth/password-policy/route.js +2 -2
- package/.medusa/server/src/api/store/customers/me/addresses/[address_id]/tax-identification/route.js +12 -0
- package/.medusa/server/src/api/store/customers/me/tax-identifications/route.js +14 -0
- 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/api/store/tax-identification-policy/route.js +21 -0
- package/.medusa/server/src/api/tax-identification-message.js +9 -0
- package/.medusa/server/src/api/to-response-amount.js +19 -0
- 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/jobs/tax-compliance-state-sync.js +26 -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/tax-compliance/domain/validation-policy.js +48 -0
- package/.medusa/server/src/modules/tax-compliance/index.js +15 -0
- package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260814120459.js +18 -0
- package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260814121724.js +14 -0
- package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260814153410.js +14 -0
- package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260929091620.js +27 -0
- package/.medusa/server/src/modules/tax-compliance/migrations/Migration20261011090147.js +16 -0
- package/.medusa/server/src/modules/tax-compliance/models/tax-compliance-state.js +29 -0
- package/.medusa/server/src/modules/tax-compliance/models/tax-compliance-validation.js +74 -0
- package/.medusa/server/src/modules/tax-compliance/runtime-settings.js +43 -0
- package/.medusa/server/src/modules/tax-compliance/service.js +693 -0
- package/.medusa/server/src/providers/email-otp/service.js +2 -2
- package/.medusa/server/src/providers/tax-compliance/index.js +11 -0
- package/.medusa/server/src/providers/tax-compliance/service.js +179 -0
- package/.medusa/server/src/public/constants.js +4 -2
- package/.medusa/server/src/public/index.js +6 -2
- package/.medusa/server/src/public/shop-config.js +6 -6
- package/.medusa/server/src/public/tax-compliance.js +22 -0
- package/.medusa/server/src/shared/lib/index.js +2 -1
- package/.medusa/server/src/shared/lib/optional-registration.js +34 -0
- package/.medusa/server/src/shop-config/{build-food-shop-config.js → build-shop-config.js} +15 -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/packing-list-allocation-revised.js +2 -2
- package/.medusa/server/src/subscribers/tax-compliance-plugin-features-updated.js +25 -0
- package/.medusa/server/src/tax-compliance/complete-cart-hooks.js +26 -0
- package/.medusa/server/src/tax-compliance/integrations/finanzonline/client.js +222 -0
- package/.medusa/server/src/tax-compliance/integrations/finanzonline/validator.js +50 -0
- package/.medusa/server/src/tax-compliance/integrations/registry.js +26 -0
- package/.medusa/server/src/tax-compliance/integrations/validator.js +19 -0
- package/.medusa/server/src/tax-compliance/lib/order-tax-context.js +100 -0
- package/.medusa/server/src/tax-compliance/lib/tax-compliance-state.js +59 -0
- package/.medusa/server/src/tax-compliance/lib/tax-runtime-settings.js +79 -0
- package/.medusa/server/src/tax-compliance/plugin-features.js +73 -0
- package/.medusa/server/src/tax-compliance/resolve-services.js +7 -0
- package/.medusa/server/src/tax-compliance/types/tax-compliance.js +6 -0
- package/.medusa/server/src/workflows/documents/issue-invoice-after-shipment.js +25 -30
- package/.medusa/server/src/workflows/documents/issue-packing-list.js +3 -14
- package/.medusa/server/src/workflows/hooks/cart-tax-context.js +78 -0
- package/.medusa/server/src/workflows/hooks/order-tax-context.js +7 -0
- package/.medusa/server/src/workflows/hooks/shipping-tax-apportionment.js +145 -0
- package/.medusa/server/src/workflows/tax-compliance/apportion-order-shipping-tax.js +72 -0
- package/.medusa/server/src/workflows/tax-compliance/domain/requires-frozen-tax-evidence.js +27 -0
- package/.medusa/server/src/workflows/tax-compliance/freeze-order-evidence.js +125 -0
- package/.medusa/server/src/workflows/tax-compliance/submit-customer-address-tax-identification.js +18 -0
- package/.medusa/server/src/workflows/tax-compliance/submit-tax-identification.js +18 -0
- package/README.md +393 -61
- 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/constants.d.ts +2 -0
- package/dist/public/constants.js +3 -1
- package/dist/public/index.d.ts +2 -1
- package/dist/public/index.js +5 -1
- package/dist/public/shop-config.d.ts +4 -4
- package/dist/public/shop-config.js +5 -5
- package/dist/public/tax-compliance.d.ts +30 -0
- package/dist/public/tax-compliance.js +21 -0
- package/dist/shared/lib/index.d.ts +1 -0
- package/dist/shared/lib/index.js +1 -0
- package/dist/shared/lib/optional-registration.d.ts +18 -0
- package/dist/shared/lib/optional-registration.js +33 -0
- 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} +14 -17
- package/dist/shop-config/options.d.ts +3 -3
- package/dist/shop-config/options.js +3 -3
- 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
|
-
(
|
|
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
|
|
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-
|
|
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-
|
|
161
|
-
import { pluginFeaturesStorePaths } from "@hartl-services/medusa-
|
|
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-
|
|
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`),
|
|
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`. `
|
|
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-
|
|
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
|
-
(`
|
|
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-
|
|
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). `
|
|
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; `
|
|
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-
|
|
2342
|
-
import { emailAdminPaths } from "@hartl-services/medusa-
|
|
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-
|
|
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
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
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
|
-
-
|
|
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-
|
|
2771
|
-
import { documentsPaths } from "@hartl-services/medusa-
|
|
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-
|
|
2788
|
-
`@hartl-services/medusa-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
3049
|
-
import { documentsPaths } from "@hartl-services/medusa-
|
|
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-
|
|
3058
|
-
`@hartl-services/medusa-
|
|
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-
|
|
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-
|
|
3073
|
-
import { packingListsPaths } from "@hartl-services/medusa-
|
|
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
|
|
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
|
|
3323
|
-
`medusa-
|
|
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 {
|
|
3675
|
+
import { defineShopConfig } from "@hartl-services/medusa-base/shop-config"
|
|
3344
3676
|
|
|
3345
|
-
module.exports =
|
|
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 {
|
|
3690
|
+
import { buildShopConfig } from "@hartl-services/medusa-base/shop-config"
|
|
3359
3691
|
|
|
3360
|
-
const config =
|
|
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
|
-
- `
|
|
3370
|
-
`defineConfig(
|
|
3371
|
-
- `
|
|
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: `
|
|
3375
|
-
`
|
|
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 (`
|
|
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
|
|