@hartl-services/medusa-base 2.10.0 → 3.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 (87) hide show
  1. package/.medusa/server/src/admin/index.js +44 -40
  2. package/.medusa/server/src/admin/index.mjs +44 -40
  3. package/.medusa/server/src/api/admin/tax-compliance/customer-addresses/[address_id]/tax-identification/route.js +15 -0
  4. package/.medusa/server/src/api/check-customer-address-tax-identification.js +93 -0
  5. package/.medusa/server/src/api/customer-address-identification-response.js +14 -0
  6. package/.medusa/server/src/api/middlewares.js +55 -1
  7. package/.medusa/server/src/api/store/carts/[id]/tax-identification/route.js +179 -0
  8. package/.medusa/server/src/api/store/customers/me/addresses/[address_id]/tax-identification/route.js +12 -0
  9. package/.medusa/server/src/api/store/customers/me/tax-identifications/route.js +14 -0
  10. package/.medusa/server/src/api/store/tax-identification-policy/route.js +21 -0
  11. package/.medusa/server/src/api/tax-identification-message.js +9 -0
  12. package/.medusa/server/src/api/to-response-amount.js +19 -0
  13. package/.medusa/server/src/email/application/deliver-ephemeral-auth-mail.js +2 -4
  14. package/.medusa/server/src/email/application/handle-password-reset-event.js +75 -27
  15. package/.medusa/server/src/email/rendering/default-templates.js +2 -1
  16. package/.medusa/server/src/email/rendering/render-email.js +2 -1
  17. package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +5 -1
  18. package/.medusa/server/src/email/templates/blocks/dynamic-blocks.js +5 -2
  19. package/.medusa/server/src/email/templates/defaults/documents.js +8 -1
  20. package/.medusa/server/src/email/templates/defaults/subjects.js +3 -1
  21. package/.medusa/server/src/email/templates/defaults/texts.js +4 -2
  22. package/.medusa/server/src/email/templates/render.js +2 -1
  23. package/.medusa/server/src/email/templates/sample-data.js +5 -1
  24. package/.medusa/server/src/email/types/email-kinds.js +3 -1
  25. package/.medusa/server/src/email/types/template-data.js +8 -1
  26. package/.medusa/server/src/jobs/tax-compliance-state-sync.js +26 -0
  27. package/.medusa/server/src/modules/email/migrations/Migration20261013120000.js +20 -0
  28. package/.medusa/server/src/modules/tax-compliance/domain/validation-policy.js +48 -0
  29. package/.medusa/server/src/modules/tax-compliance/index.js +15 -0
  30. package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260814120459.js +18 -0
  31. package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260814121724.js +14 -0
  32. package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260814153410.js +14 -0
  33. package/.medusa/server/src/modules/tax-compliance/migrations/Migration20260929091620.js +27 -0
  34. package/.medusa/server/src/modules/tax-compliance/migrations/Migration20261011090147.js +16 -0
  35. package/.medusa/server/src/modules/tax-compliance/models/tax-compliance-state.js +29 -0
  36. package/.medusa/server/src/modules/tax-compliance/models/tax-compliance-validation.js +74 -0
  37. package/.medusa/server/src/modules/tax-compliance/runtime-settings.js +43 -0
  38. package/.medusa/server/src/modules/tax-compliance/service.js +693 -0
  39. package/.medusa/server/src/providers/tax-compliance/index.js +11 -0
  40. package/.medusa/server/src/providers/tax-compliance/service.js +179 -0
  41. package/.medusa/server/src/public/constants.js +4 -2
  42. package/.medusa/server/src/public/index.js +6 -2
  43. package/.medusa/server/src/public/tax-compliance.js +22 -0
  44. package/.medusa/server/src/shared/lib/index.js +2 -1
  45. package/.medusa/server/src/shared/lib/optional-registration.js +34 -0
  46. package/.medusa/server/src/shop-config/build-food-shop-config.js +12 -9
  47. package/.medusa/server/src/subscribers/tax-compliance-plugin-features-updated.js +25 -0
  48. package/.medusa/server/src/tax-compliance/complete-cart-hooks.js +26 -0
  49. package/.medusa/server/src/tax-compliance/integrations/finanzonline/client.js +222 -0
  50. package/.medusa/server/src/tax-compliance/integrations/finanzonline/validator.js +50 -0
  51. package/.medusa/server/src/tax-compliance/integrations/registry.js +26 -0
  52. package/.medusa/server/src/tax-compliance/integrations/validator.js +19 -0
  53. package/.medusa/server/src/tax-compliance/lib/order-tax-context.js +100 -0
  54. package/.medusa/server/src/tax-compliance/lib/tax-compliance-state.js +59 -0
  55. package/.medusa/server/src/tax-compliance/lib/tax-runtime-settings.js +79 -0
  56. package/.medusa/server/src/tax-compliance/plugin-features.js +73 -0
  57. package/.medusa/server/src/tax-compliance/resolve-services.js +7 -0
  58. package/.medusa/server/src/tax-compliance/types/tax-compliance.js +6 -0
  59. package/.medusa/server/src/workflows/documents/issue-invoice-after-shipment.js +25 -30
  60. package/.medusa/server/src/workflows/documents/issue-packing-list.js +3 -14
  61. package/.medusa/server/src/workflows/hooks/cart-tax-context.js +78 -0
  62. package/.medusa/server/src/workflows/hooks/order-tax-context.js +7 -0
  63. package/.medusa/server/src/workflows/hooks/shipping-tax-apportionment.js +145 -0
  64. package/.medusa/server/src/workflows/tax-compliance/apportion-order-shipping-tax.js +72 -0
  65. package/.medusa/server/src/workflows/tax-compliance/domain/requires-frozen-tax-evidence.js +27 -0
  66. package/.medusa/server/src/workflows/tax-compliance/freeze-order-evidence.js +125 -0
  67. package/.medusa/server/src/workflows/tax-compliance/submit-customer-address-tax-identification.js +18 -0
  68. package/.medusa/server/src/workflows/tax-compliance/submit-tax-identification.js +18 -0
  69. package/README.md +369 -27
  70. package/dist/email/types/email-kinds.d.ts +3 -2
  71. package/dist/email/types/email-kinds.js +2 -0
  72. package/dist/email/types/shop-configuration.d.ts +8 -0
  73. package/dist/email/types/template-data.d.ts +6 -1
  74. package/dist/email/types/template-data.js +7 -0
  75. package/dist/public/constants.d.ts +2 -0
  76. package/dist/public/constants.js +3 -1
  77. package/dist/public/index.d.ts +2 -1
  78. package/dist/public/index.js +5 -1
  79. package/dist/public/tax-compliance.d.ts +30 -0
  80. package/dist/public/tax-compliance.js +21 -0
  81. package/dist/shared/lib/index.d.ts +1 -0
  82. package/dist/shared/lib/index.js +1 -0
  83. package/dist/shared/lib/optional-registration.d.ts +18 -0
  84. package/dist/shared/lib/optional-registration.js +33 -0
  85. package/dist/shop-config/build-food-shop-config.d.ts +2 -2
  86. package/dist/shop-config/build-food-shop-config.js +11 -8
  87. package/package.json +5 -4
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,6 +56,10 @@ 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").
@@ -116,6 +122,17 @@ 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
+
119
136
  Peer dependencies are the host's Medusa 2.19 packages (`@medusajs/framework`,
120
137
  `@medusajs/medusa`, `@medusajs/admin-sdk`, `@medusajs/js-sdk`, `@medusajs/ui`, `@medusajs/icons`)
121
138
  plus React, React Router and TanStack Query in the versions the Medusa 2.19
@@ -927,7 +944,7 @@ class MyModuleService extends MedusaService({ /* … */ }) {
927
944
 
928
945
  **Ordering.** Ascending `order`, then module key, then workflow id. In use:
929
946
  Affiliate 100 (`affiliate-validate-complete-cart`,
930
- `finalize-affiliate-order-attribution`), tax compliance 200
947
+ `finalize-affiliate-order-attribution`), the base's Steuer-Compliance 200
931
948
  (`freeze-tax-compliance-order-evidence`, `orderCreated` only).
932
949
 
933
950
  **Rollback semantics.**
@@ -1436,6 +1453,11 @@ Options of the `email` namespace:
1436
1453
  `orderNotificationEmail` mailbox. `verifyAccountUrl` (optional builder,
1437
1454
  like `passwordResetUrl`; the mail appends `#token=`) is the storefront page
1438
1455
  that confirms an account, needed while `customer_verification` is on.
1456
+ `adminPasswordResetUrl` (optional builder, receives `{ token }`) is the
1457
+ Medusa Admin's "choose a new password" page; without it an Admin user's
1458
+ password reset fails with `render_failed`. `buildFoodShopConfig` sets
1459
+ `<MEDUSA_BACKEND_URL>/app/reset-password?token=<token>` (the dashboard reads
1460
+ the token from the query).
1439
1461
  `adminAffiliateApplicationsUrl` (optional builder, receives
1440
1462
  `{ applicationId }`) is the Admin page of the partner applications; the
1441
1463
  notification about a new application shows its "Im Admin prüfen" button
@@ -1539,7 +1561,7 @@ feature is switched on again.
1539
1561
  | --------------------- | ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
1540
1562
  | (plugin switch) | E-Mail | on | every route under `/admin/email/*` (mail log, resends, attachments; 404); jobs `email-reconcile-attempts`, `email-reconcile-pending-messages`, `email-retry-messages`; every feature below | checkout and orders; already queued messages stay stored |
1541
1563
  | `order_mails` | Bestell-Mails | on | subscribers `order-placed`, `email-shipment-created`, `email-order-canceled`, `return-requested`, `return-received`, `documents-shipment-invoice-assignment-decided`, `documents-order-cancellation-ready`, `documents-refund-ready`; route `/admin/draft-orders/:id/order-confirmation` (404) and its draft-order widget | jobs, Admin routes, already created messages |
1542
- | `account_mails` | Konto-Mails | on | subscribers `password-reset`, `user-invite`, `customer-verification` (the last one only with `customer_verification` on as well); not the login code mail, which depends on neither this feature nor the plugin switch (see "Kunden-Login") | jobs, Admin routes, already created messages |
1564
+ | `account_mails` | Konto-Mails | on | subscribers `password-reset` (customers and Admin users), `user-invite`, `customer-verification` (the last one only with `customer_verification` on as well); not the login code mail, which depends on neither this feature nor the plugin switch (see "Kunden-Login") | jobs, Admin routes, already created messages |
1543
1565
  | `customer_verification` | Kontobestätigung per E-Mail | off | Medusa's e-mail verification of customer logins (see "Kontobestätigung per E-Mail"), route `POST /store/customer-verification/resend` (404 while off), subscriber `customer-verification`; child of `account_mails` | checkout and orders; confirmed accounts stay confirmed, already created messages |
1544
1566
  | `affiliate_reminders` | Affiliate-Mails | on | subscribers `affiliate-consignment-reminder-due` (reminders to affiliates) and `affiliate-application-submitted` (notification to the shop about a new partner application) | jobs, Admin routes, already created messages |
1545
1567
 
@@ -1826,9 +1848,10 @@ has at most 35 characters. Email runtime-validates every contract before use.
1826
1848
 
1827
1849
  - The accepted kinds are exactly `order.received`, `order.received.admin`, `order.shipped`,
1828
1850
  `order.canceled`, `order.return_requested`, `order.return_received`,
1829
- `order.refunded`, `auth.password_reset`, `auth.verification`,
1830
- `auth.login_code`, `affiliate.consignment_report_due`,
1831
- `affiliate.application_submitted.admin` and `user.invite`.
1851
+ `order.refunded`, `auth.password_reset`, `auth.admin_password_reset`,
1852
+ `auth.verification`, `auth.login_code`, `affiliate.consignment_report_due`,
1853
+ `affiliate.application_submitted.admin`, `user.invite` and
1854
+ `system.credential_expiry.admin`.
1832
1855
  - Every automatic message has a deterministic business key.
1833
1856
  - Every placed Order creates independent customer and Shop intents. The customer
1834
1857
  intent honors `no_notification`; the Shop intent always targets the static
@@ -1914,7 +1937,7 @@ an info box lists number, customer, e-mail and order time, followed by an
1914
1937
  button is left out without it) and `timeZone` (IANA; the order time is left
1915
1938
  out without it). `buildFoodShopConfig` sets both (`<MEDUSA_BACKEND_URL>/app/
1916
1939
  orders/<id>` and its `timeZone` option). The layout receives `audience`
1917
- (`"shop"` for this kind, `affiliate.application_submitted.admin` and `user.invite`, `"customer"` for all others) and
1940
+ (`"shop"` for this kind, `affiliate.application_submitted.admin`, `system.credential_expiry.admin`, `user.invite` and `auth.admin_password_reset`, `"customer"` for all others) and
1918
1941
  shows the contact line only to customers.
1919
1942
 
1920
1943
  The shipping confirmation (`order.shipped`, one per fulfillment) receives a
@@ -1978,6 +2001,20 @@ without `iat`) and a note that the mail can be ignored. The lifetime is
1978
2001
  passed as the ephemeral `valid_minutes` next to `reset_url` and is never
1979
2002
  persisted.
1980
2003
 
2004
+ The same Medusa event with `actor_type: "user"` (the Admin's "Passwort
2005
+ zurücksetzen" at `/app/reset-password`) becomes its own kind,
2006
+ `auth.admin_password_reset`, with its own Admin template: subject "Neues
2007
+ Passwort für den Admin-Bereich von <displayName>", the same button, link text,
2008
+ lifetime and ignore note, and the link from `adminPasswordResetUrl` (the token
2009
+ in the query). It is sent only when exactly one Admin user has the address:
2010
+ the `emailpass` identity is shared by all actor types, so a customer could ask
2011
+ for an Admin reset of their own identity, and that request is ignored
2012
+ (`no_admin_user`). A token whose `actor_type` differs from the event's is
2013
+ ignored as `invalid_token`; every other actor type stays `invalid_event`. Like
2014
+ the customer reset it is gated by `account_mails`, ephemeral (never resent or
2015
+ retried by the generic job) and stored with `resource_type` `"user"`; its
2016
+ layout audience is `"shop"`.
2017
+
1981
2018
  The account confirmation (`auth.verification`) has the subject "Bestätige
1982
2019
  deine E-Mail-Adresse", a "E-Mail-Adresse bestätigen" button, the link as
1983
2020
  copyable text, a note that the link is only valid for a limited time (a new
@@ -2433,13 +2470,9 @@ separate documents package since 1.0.0.
2433
2470
 
2434
2471
  ### Configuration
2435
2472
 
2436
- `@hartl-services/medusa-tax-compliance` is a required *registered plugin*,
2437
- intentionally not a package dependency of the base: install it and list it in
2438
- `medusa-config.ts` yourself. The invoice workflow resolves the
2439
- `tax_compliance` module with no fallback; a shop that does not register Tax
2440
- Compliance fails at invoice issuance time (the first `shipment.created`
2441
- event) with an `UNEXPECTED_STATE` error naming the missing plugin, not at
2442
- startup.
2473
+ The invoice workflow reads the order's frozen B2B evidence from the base's own
2474
+ `tax_compliance` module (see "Steuer-Compliance"); register the Tax provider
2475
+ `@hartl-services/medusa-base/providers/tax-compliance` as shown there.
2443
2476
 
2444
2477
  ```ts
2445
2478
  export default defineConfig({
@@ -2448,10 +2481,7 @@ export default defineConfig({
2448
2481
  resolve: "@medusajs/medusa/tax",
2449
2482
  options: {
2450
2483
  providers: [
2451
- {
2452
- resolve:
2453
- "@hartl-services/medusa-tax-compliance/providers/tax-compliance",
2454
- },
2484
+ { resolve: "@hartl-services/medusa-base/providers/tax-compliance" },
2455
2485
  ],
2456
2486
  },
2457
2487
  },
@@ -2470,10 +2500,6 @@ export default defineConfig({
2470
2500
  },
2471
2501
  },
2472
2502
  },
2473
- {
2474
- resolve: "@hartl-services/medusa-tax-compliance",
2475
- options: {},
2476
- },
2477
2503
  ],
2478
2504
  });
2479
2505
  ```
@@ -2588,7 +2614,8 @@ medusa-config fallback: what is stored in the Admin is what is printed.
2588
2614
 
2589
2615
  Requirements/couplings:
2590
2616
 
2591
- - A registered Tax Compliance module (`tax_compliance`) as shown above.
2617
+ - The Tax provider `tp_tax_compliance` of the base's Steuer-Compliance on the
2618
+ regions, so orders carry UID-based exemptions.
2592
2619
  - A Chromium/Chrome executable and a Medusa File provider that supports
2593
2620
  private files (see "Development" below for the sandbox/non-root note).
2594
2621
  Documents stores PDFs through `Modules.FILE` and streams them only through
@@ -3058,6 +3085,322 @@ const response = await sdk.client.fetch<ListAdminOrderPackingListsResponse>(
3058
3085
  );
3059
3086
  ```
3060
3087
 
3088
+ ## Steuer-Compliance
3089
+
3090
+ There is no separate `@hartl-services/medusa-tax-compliance` package since
3091
+ 3.0.0: the EU tax compliance (module `tax_compliance`, the Tax Module provider
3092
+ `tp_tax_compliance`, the UID check routes, the order-evidence freeze and the
3093
+ apportioned shipping tax) is part of the base. The module owns the durable
3094
+ validation evidence; a Storefront never imports its implementation, only
3095
+ `@hartl-services/medusa-food-supplements-contracts`.
3096
+
3097
+ ### Configuration
3098
+
3099
+ Register the Tax Module provider in `medusa-config.ts` (the Shop-Config-Preset
3100
+ does this); the module itself boots with the base and has no options:
3101
+
3102
+ ```ts
3103
+ modules: [
3104
+ {
3105
+ resolve: "@medusajs/medusa/tax",
3106
+ options: {
3107
+ providers: [
3108
+ { resolve: "@hartl-services/medusa-base/providers/tax-compliance" },
3109
+ ],
3110
+ },
3111
+ },
3112
+ ],
3113
+ ```
3114
+
3115
+ The provider is configured without a secondary `id`, so Medusa registers its
3116
+ class identifier `tax_compliance` as exactly `tp_tax_compliance`; regions
3117
+ select it as their tax provider. `medusa db:migrate` creates the tables
3118
+ `tax_compliance_validation` and `tax_compliance_state` (unchanged names and
3119
+ migrations of the former package, so an upgraded shop needs no data
3120
+ migration).
3121
+
3122
+ The merchant's own country and VAT number come only from the Shop-Stammdaten
3123
+ (fields `country_code` and `vat_number`); there is no config fallback. They
3124
+ select the qualified VAT validator (currently only the Austrian FinanzOnline
3125
+ adapter). While either is missing, carts get ordinary B2C tax.
3126
+
3127
+ ### Runtime settings
3128
+
3129
+ Declared on the Steuer-Compliance descriptor and edited on its Admin page
3130
+ (Settings → Hartl Services Plugins → Steuer-Compliance):
3131
+
3132
+ | Key | Label | Type | Required |
3133
+ | ----------------------------- | -------------- | ------ | -------- |
3134
+ | `finanzonline_participant_id` | Teilnehmer-ID | string | yes |
3135
+ | `finanzonline_user_id` | Benutzer-ID | string | yes |
3136
+ | `finanzonline_password` | PIN | secret | yes |
3137
+
3138
+ All three sit in the group "FinanzOnline" ("Webservice-Benutzer für die
3139
+ qualifizierte UID-Abfrage.").
3140
+
3141
+ - Read per validation, only for an Austrian merchant with a real UID. There
3142
+ is no environment or medusa-config fallback; `FINANZONLINE_*` variables are
3143
+ ignored.
3144
+ - All three are needed as a set. While any is missing the plugin page shows
3145
+ "nicht konfiguriert", one warning is logged per process and not-configured
3146
+ state, and the qualified check answers `unavailable`
3147
+ (`missing_credentials`) without calling FinanzOnline.
3148
+ - A failing settings read is logged (`logger.error`, reported to Sentry by
3149
+ the base logger wrapper) and handled like missing credentials.
3150
+ - Merchant country and UID are read from the shop master data. On the cart
3151
+ tax path (tax-context hook) and in the Store route a failing read is logged
3152
+ and treated as "no merchant": ordinary B2C tax, and the route answers 500
3153
+ as before for a missing merchant profile. The cart tax path and the
3154
+ order-evidence freeze read the master data only for a cart that has a
3155
+ current UID validation record, so B2C carts never touch the settings and
3156
+ an outage does not log an error per tax calculation. The order-evidence
3157
+ freeze at checkout lets a read failure propagate, so a zero-rated order
3158
+ fails and an ordinarily taxed one continues.
3159
+ - Both are read at app level, never by the module service (a module does not
3160
+ reach into other modules): `src/tax-compliance/lib/tax-runtime-settings.ts`
3161
+ provides `resolveMerchantProfile(container, { failSafe })` and
3162
+ `resolveFinanzOnlineCredentials(container)`, called with the application
3163
+ container of the Store route, the validation and freeze workflow steps and
3164
+ the cart tax-context hook. The service takes the results as parameters:
3165
+ `validateCart(input, credentials)` (the step reads the credentials only
3166
+ when `requiresFinanzOnlineCredentials(input)` says so, and never passes
3167
+ them as workflow input), `getCurrentValidationId(cart, loadMerchant)` and
3168
+ `freezeOrderEvidence(orderId, cart, loadMerchant)` (the loader runs only
3169
+ after a current validation record was found) and
3170
+ `isValidationEligibleForTax(validationId, countryCode, merchant)`.
3171
+ - The cart tax-context hook puts the merchant profile it checked the
3172
+ evidence against into `additional_context.tax_compliance_merchant`, next to
3173
+ the validation ID. The tax provider runs in the Tax Module's isolated
3174
+ container and reads the merchant only from that context; without one it
3175
+ answers ordinary B2C rates. The order tax path needs no merchant: it checks
3176
+ the evidence frozen under the order ID.
3177
+
3178
+ ### Feature toggles
3179
+
3180
+ Steuer-Compliance declares its switch and three features under the plugin key
3181
+ `tax_compliance` (`TAX_COMPLIANCE_PLUGIN_KEY` / `TAX_COMPLIANCE_FEATURES` from
3182
+ the Contracts `/paths/tax-compliance` subpath). An Admin switches them under
3183
+ Settings → "Hartl Services Plugins"; Storefronts read the effective values
3184
+ from `GET /store/plugin-features/tax_compliance`. All features default to on.
3185
+ The plugin switch off counts as all three features off.
3186
+
3187
+ | Key | Label | Default | Off means |
3188
+ | ---------------- | ------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3189
+ | `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. |
3190
+ | `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. |
3191
+ | `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. |
3192
+
3193
+ With `shipping_tax` on, a cart with items at several rates gets one shipping
3194
+ tax line "Anteilige Versandsteuer" (code `APPORTIONED_SHIPPING_TAX_CODE` from
3195
+ `/lib`) at the items' rates weighted by their net amount after discounts
3196
+ (computed like Medusa's line totals), which yields the same tax as splitting
3197
+ the shipping by that amount onto each rate. Medusa taxes a cart before it
3198
+ applies promotions and a code redeemed through `POST /store/carts/:id/promotions`
3199
+ recalculates no tax, so the cart's rate can lag behind its discounts; the
3200
+ `orderCreated` participant therefore sets the order's rate again from its
3201
+ final discounts (tax-inclusive shipping only, whose paid gross amount stays
3202
+ the same; on error the order keeps the cart's rate and a warning is logged).
3203
+ Medusa stores the rate with six decimals. The invoice (see "Dokumente")
3204
+ splits that line back onto the item rates by the same key.
3205
+
3206
+ `GET /store/tax-identification-policy` follows only the plugin switch.
3207
+ Evidence frozen under an order that was zero-rated keeps counting for that
3208
+ order whatever the toggles say now.
3209
+
3210
+ The tax hooks and the order-evidence freeze never read a toggle. The
3211
+ `tax_compliance_state` row mirrors the effective `b2b` and `shipping_tax`
3212
+ values; the `plugin-features.updated.v1` subscriber and the
3213
+ `tax-compliance-state-sync` job (every 5 minutes) write it. Before the first
3214
+ sync (no row) both count as on; when the row cannot be read both count as
3215
+ off (ordinary B2C rates, Medusa's shipping tax).
3216
+
3217
+ ### UID check in the checkout
3218
+
3219
+ Public Store request, response, schema, and path imports belong to
3220
+ `@hartl-services/medusa-food-supplements-contracts`:
3221
+
3222
+ ```ts
3223
+ import type { SubmitTaxIdentificationResponse } from "@hartl-services/medusa-food-supplements-contracts/types/tax-compliance-store";
3224
+ import { taxCompliancePaths } from "@hartl-services/medusa-food-supplements-contracts/paths/tax-compliance";
3225
+
3226
+ const result = await sdk.client.fetch<SubmitTaxIdentificationResponse>(
3227
+ taxCompliancePaths.store.carts.taxIdentification(cartId),
3228
+ { method: "POST", body: {} },
3229
+ );
3230
+ ```
3231
+
3232
+ The module persists normalized cart validation evidence and selects qualified
3233
+ VAT validators by merchant country. Austrian merchants use the isolated
3234
+ FinanzOnline stage-2 adapter. Missing credentials, the development UID
3235
+ placeholder `ATU00000000`, technical/rate-limit responses, and unsupported
3236
+ merchant countries produce local `unavailable` evidence. Tax calculation
3237
+ interprets missing, stale, invalid, ineligible, and unavailable evidence as the
3238
+ normal B2C outcome. Cart tax calculation only accepts same-day `eligible`
3239
+ evidence whose current cart and merchant fingerprints still match, whose UID
3240
+ prefix matches the delivery country, and whose delivery country is
3241
+ cross-border. Once evidence is frozen, order tax calculation instead verifies
3242
+ the exact evidence-ID/order-ID binding and its frozen facts; it deliberately
3243
+ does not reopen current-cart or current-day eligibility. Eligible calculations
3244
+ receive explicit zero-percent item and shipping tax lines.
3245
+
3246
+ `ATU00000000` is a development-only Austrian UID placeholder: the registry
3247
+ treats it as missing (`merchant_uid_sentinel`, never sent to FinanzOnline)
3248
+ and Documents never prints it. Enter the real UID in the Shop-Stammdaten
3249
+ before issuing production invoices.
3250
+
3251
+ The Store route is:
3252
+
3253
+ ```text
3254
+ POST /store/carts/:id/tax-identification
3255
+ ```
3256
+
3257
+ The UID belongs to the cart's billing address: company plus
3258
+ `metadata.vat_number` (`TAX_IDENTIFICATION_METADATA_KEY`), set by the
3259
+ storefront like any other address field, for guests too. The cart evidence
3260
+ fingerprint reads the UID from the current billing address, so changing or
3261
+ removing it ends the exemption. Medusa itself recomputes taxes only when the
3262
+ shipping address changes, so a storefront calls this route (empty body) after
3263
+ the addresses changed and before it initiates payment.
3264
+
3265
+ It requires a publishable key scoped to the cart's sales channel. With a UID
3266
+ on a billing address outside the merchant country (Shop-Stammdaten) it
3267
+ performs one qualified validation; without one, or for a company in the
3268
+ merchant country, it answers `status: null` without any query and only
3269
+ recomputes when the cart still has a current check record (an earlier
3270
+ exemption falls away). It then forces tax-line recomputation, reconciles an
3271
+ existing payment collection with the new cart total, and returns only the
3272
+ public status, optional user message, and safe cart totals. The deprecated
3273
+ body (`company_name` and `vat_number` together) is still accepted: the route
3274
+ writes both into the billing address first. Provider facts, evidence IDs,
3275
+ credentials, sessions, and SOAP data are never part of the response. When the
3276
+ total changed, the reconciliation deletes the cart's payment sessions, so a
3277
+ storefront re-initiates payment after the call.
3278
+
3279
+ The adapter requires a dedicated FinanzOnline web-service user whose
3280
+ credentials are entered as runtime settings (see above). The PIN is stored
3281
+ encrypted and never sent to the Admin or a Storefront. SOAP clients are
3282
+ validation-scoped, scrub their node-soap transport state after every
3283
+ operation, and always attempt logout after a successful login. Every
3284
+ `unavailable` answer from the adapter logs one warning with the failing step
3285
+ (login or UID query) and FinanzOnline's return code and message, or the
3286
+ exception message (PIN masked), so a rejected web-service user shows up in
3287
+ the server log while the shopper only sees the generic message. Same-day
3288
+ provider facts are serialized and reused by normalized merchant UID, customer
3289
+ UID, and UTC date across carts and saved addresses. Every subject still
3290
+ receives its own evidence row and fingerprint; changed company or address
3291
+ submissions are re-evaluated locally against the structured provider facts
3292
+ without another remote request. Only records FinanzOnline actually answered
3293
+ for (provider facts, valid or not) are reused: an `unavailable` without an
3294
+ answer (missing credentials, the placeholder UID, an outage or login failure)
3295
+ is checked again on the next submission instead of blocking the UID for the
3296
+ rest of the day. Transaction-scoped PostgreSQL advisory locks cover both the
3297
+ normalized subject and the UID/day reuse key. Partial unique indexes permit
3298
+ only one current evidence row per cart and per saved address, while a
3299
+ separate lookup index supports cross-subject reuse.
3300
+
3301
+ A domestic company never qualifies for a tax-free intra-Community supply, so
3302
+ its UID is neither checked nor stored as evidence. Storefronts ask for the UID
3303
+ only outside the merchant country and read that country from:
3304
+
3305
+ ```text
3306
+ GET /store/tax-identification-policy → { merchant_country_code: "at" | null }
3307
+ ```
3308
+
3309
+ `null` while the master data name no country (or cannot be read): the
3310
+ storefront then asks for every country.
3311
+
3312
+ ```ts
3313
+ import type { GetTaxIdentificationPolicyResponse } from "@hartl-services/medusa-food-supplements-contracts/types/tax-compliance-store";
3314
+ import { taxCompliancePaths } from "@hartl-services/medusa-food-supplements-contracts/paths/tax-compliance";
3315
+
3316
+ const { merchant_country_code } = await sdk.client.fetch<GetTaxIdentificationPolicyResponse>(
3317
+ taxCompliancePaths.store.taxIdentificationPolicy,
3318
+ { method: "GET" },
3319
+ );
3320
+ ```
3321
+
3322
+ Login and UID query share a 10 s budget (WSDL downloads included) and logout
3323
+ gets 3 s, so the request stays below a storefront's 15 s checkout wait and the
3324
+ database transaction holding the UID lock never waits on a hanging
3325
+ FinanzOnline call; a timeout answers `unavailable` (`technical_error`).
3326
+
3327
+ ### Saved customer addresses
3328
+
3329
+ A saved customer address carries its UID in `metadata.vat_number`
3330
+ (`TAX_IDENTIFICATION_METADATA_KEY` in the Contracts). A logged-in customer
3331
+ checks it with
3332
+
3333
+ ```text
3334
+ POST /store/customers/me/addresses/:address_id/tax-identification
3335
+ GET /store/customers/me/tax-identifications
3336
+ ```
3337
+
3338
+ Company, UID and address are read from the stored address, never from the
3339
+ request; the address's own country stands in for the delivery country.
3340
+ `verified` (status `eligible` or `ineligible`) means the UID is real and
3341
+ matches company and address. The GET route only reports evidence whose
3342
+ fingerprint still matches the address as it is now: editing company, UID or
3343
+ address drops the status until the address is checked again. Metadata alone
3344
+ never proves a check. Both routes follow the `vat_validation` feature (404
3345
+ while off) and require a customer login.
3346
+
3347
+ Other plugins read the same current evidence, whatever its status (also
3348
+ `unavailable` and `invalid`), through
3349
+ `listCustomerAddressTaxIdentifications(container, customerId)` from
3350
+ `@hartl-services/medusa-base` (typed `CustomerAddressTaxIdentification` with
3351
+ the checked `billing_address`); the caller decides with
3352
+ `isPluginFeatureEnabledByKey(container, "tax_compliance", "vat_validation")`
3353
+ whether the check is on. The Affiliate partner registration uses it.
3354
+
3355
+ An Admin checks any customer's saved address again with
3356
+
3357
+ ```text
3358
+ POST /admin/tax-compliance/customer-addresses/:address_id/tax-identification
3359
+ ```
3360
+
3361
+ (admin login, `vat_validation` feature; answers like the Store route,
3362
+ `SubmitCustomerAddressTaxIdentificationResponse`, path
3363
+ `taxCompliancePaths.admin.customerAddressTaxIdentification(addressId)`). The
3364
+ Affiliate plugin uses it to re-check a partner application whose check was
3365
+ unavailable.
3366
+
3367
+ All three FinanzOnline settings are required as a set. Otherwise the service
3368
+ stores `unavailable` evidence and checkout continues with B2C tax; the Store
3369
+ response returns the checkout warning "Die UID-Prüfung ist derzeit nicht
3370
+ verfügbar. Bitte versuchen Sie es später erneut." Invalid or ineligible B2B
3371
+ facts likewise receive ordinary B2C tax, without a warning. The registry is
3372
+ country-based: it currently provides only the Austrian FinanzOnline adapter;
3373
+ an unsupported merchant country deliberately resolves to `unavailable`.
3374
+
3375
+ ### Frozen order evidence
3376
+
3377
+ Cart tax workflows pass only the opaque current evidence ID and the merchant
3378
+ profile (country and UID) to the provider. Order tax workflows use evidence
3379
+ frozen under the order ID by the workflow
3380
+ `freeze-tax-compliance-order-evidence`, which the base runs as an
3381
+ `orderCreated` participant with `order` 200 (see "Complete-cart hook
3382
+ composition"), after Affiliate's attribution (100) when Affiliate is
3383
+ installed. After the freeze the same workflow sets the order's apportioned
3384
+ shipping rate from its final discounts (see "Feature toggles" above).
3385
+ Compensation runs in reverse (tax, then Affiliate); if the freeze fails, the
3386
+ base cancels Affiliate's already finished attribution. Tax-only module lookup
3387
+ and context failures remain fail-closed to ordinary B2C. The freeze step
3388
+ decides itself whether a failure is fatal: an auxiliary freeze failure is
3389
+ logged and contained for an ordinarily taxed B2C cart, but a zero-rated cart
3390
+ cannot complete unless its evidence was durably frozen under the order ID.
3391
+ Zero-rated means the provider set a line to 0 % although its configured tax
3392
+ rate is not 0 % (UID-based exemption); a line that is 0 % because its
3393
+ configured tax rate is 0 % (e.g. the small-business exemption) is ordinary
3394
+ taxation and needs no evidence. Freeze-compensation persistence failures are
3395
+ logged and propagated so workflow cancellation stays failed instead of
3396
+ silently leaving a stale order binding.
3397
+
3398
+ Documents reads that frozen eligible B2B evidence when an invoice is issued,
3399
+ so the document snapshot contains the company, customer UID and exemption
3400
+ wording from the completed order (see "Dokumente"). A later cart change or
3401
+ validation cannot alter an already issued document; B2C, invalid, ineligible
3402
+ and unavailable evidence adds no tax-proof wording.
3403
+
3061
3404
  ## Dateispeicher (S3)
3062
3405
 
3063
3406
  The base provides a Medusa File Module provider for durable S3-compatible
@@ -3299,15 +3642,14 @@ loads the Sentry logger wrapper and with it `@sentry/node`; `/config` stays
3299
3642
  free of it.
3300
3643
 
3301
3644
  The preset registers the base (with its E-Mail, Documents and `/files`
3302
- options) plus five feature plugins, in this order: `medusa-base`,
3303
- `medusa-tax-compliance`, `medusa-free-shipping`, `medusa-batch-management`,
3645
+ options, and its Tax provider) plus four feature plugins, in this order:
3646
+ `medusa-base`, `medusa-free-shipping`, `medusa-batch-management`,
3304
3647
  `medusa-affiliate`, `medusa-payload-sync`.
3305
3648
 
3306
3649
  ### Installation in a shop
3307
3650
 
3308
3651
  ```bash
3309
3652
  pnpm add @hartl-services/medusa-base \
3310
- @hartl-services/medusa-tax-compliance \
3311
3653
  @hartl-services/medusa-free-shipping \
3312
3654
  @hartl-services/medusa-batch-management \
3313
3655
  @hartl-services/medusa-affiliate \
@@ -1,5 +1,5 @@
1
1
  import { z } from "@medusajs/framework/zod";
2
- export declare const EMAIL_KINDS: readonly ["order.received", "order.received.admin", "order.shipped", "order.canceled", "order.return_requested", "order.return_received", "order.refunded", "auth.password_reset", "affiliate.consignment_report_due", "affiliate.application_submitted.admin", "user.invite", "auth.verification", "auth.login_code", "system.credential_expiry.admin"];
2
+ export declare const EMAIL_KINDS: readonly ["order.received", "order.received.admin", "order.shipped", "order.canceled", "order.return_requested", "order.return_received", "order.refunded", "auth.password_reset", "affiliate.consignment_report_due", "affiliate.application_submitted.admin", "user.invite", "auth.verification", "auth.login_code", "system.credential_expiry.admin", "auth.admin_password_reset"];
3
3
  export declare const EmailKindSchema: z.ZodEnum<{
4
4
  "order.received": "order.received";
5
5
  "order.received.admin": "order.received.admin";
@@ -15,6 +15,7 @@ export declare const EmailKindSchema: z.ZodEnum<{
15
15
  "auth.verification": "auth.verification";
16
16
  "auth.login_code": "auth.login_code";
17
17
  "system.credential_expiry.admin": "system.credential_expiry.admin";
18
+ "auth.admin_password_reset": "auth.admin_password_reset";
18
19
  }>;
19
20
  export type EmailKind = (typeof EMAIL_KINDS)[number];
20
21
  /**
@@ -22,5 +23,5 @@ export type EmailKind = (typeof EMAIL_KINDS)[number];
22
23
  * subscriber builds the mail from the event, so neither the generic retry
23
24
  * job, the automatic dispatch nor an Admin resend may touch them.
24
25
  */
25
- export declare const EPHEMERAL_EMAIL_KINDS: readonly ["auth.password_reset", "auth.verification", "auth.login_code"];
26
+ export declare const EPHEMERAL_EMAIL_KINDS: readonly ["auth.password_reset", "auth.admin_password_reset", "auth.verification", "auth.login_code"];
26
27
  export declare const isEphemeralEmailKind: (kind: EmailKind) => boolean;
@@ -17,6 +17,7 @@ exports.EMAIL_KINDS = [
17
17
  "auth.verification",
18
18
  "auth.login_code",
19
19
  "system.credential_expiry.admin",
20
+ "auth.admin_password_reset",
20
21
  ];
21
22
  exports.EmailKindSchema = zod_1.z.enum(exports.EMAIL_KINDS);
22
23
  /**
@@ -26,6 +27,7 @@ exports.EmailKindSchema = zod_1.z.enum(exports.EMAIL_KINDS);
26
27
  */
27
28
  exports.EPHEMERAL_EMAIL_KINDS = [
28
29
  "auth.password_reset",
30
+ "auth.admin_password_reset",
29
31
  "auth.verification",
30
32
  "auth.login_code",
31
33
  ];
@@ -48,6 +48,14 @@ export type ShopEmailConfiguration = Readonly<{
48
48
  adminAffiliateApplicationsUrl?: (input: Readonly<{
49
49
  applicationId: string;
50
50
  }>) => string;
51
+ /**
52
+ * The Medusa Admin's "choose a new password" page with the reset token
53
+ * (`/app/reset-password?token=<token>`). Without it an Admin user's
54
+ * password reset sends no mail.
55
+ */
56
+ adminPasswordResetUrl?: (input: Readonly<{
57
+ token: string;
58
+ }>) => string;
51
59
  affiliatePortalUrl: string;
52
60
  displayName: string;
53
61
  inviteUrl: (input: Readonly<{
@@ -381,6 +381,7 @@ export declare const EmailPersistedDataSchemas: {
381
381
  client_id: z.ZodString;
382
382
  expires_on: z.ZodString;
383
383
  }, z.core.$strict>;
384
+ readonly "auth.admin_password_reset": z.ZodObject<{}, z.core.$strict>;
384
385
  };
385
386
  export declare const EmailRenderDataSchemas: {
386
387
  readonly "auth.password_reset": z.ZodObject<{
@@ -388,6 +389,10 @@ export declare const EmailRenderDataSchemas: {
388
389
  reset_url: z.ZodString;
389
390
  valid_minutes: z.ZodOptional<z.ZodNumber>;
390
391
  }, z.core.$strict>;
392
+ readonly "auth.admin_password_reset": z.ZodObject<{
393
+ reset_url: z.ZodString;
394
+ valid_minutes: z.ZodOptional<z.ZodNumber>;
395
+ }, z.core.$strict>;
391
396
  readonly "user.invite": z.ZodObject<{
392
397
  invite_url: z.ZodString;
393
398
  expires_at: z.ZodOptional<z.ZodString>;
@@ -635,7 +640,7 @@ export type EmailPersistedDataMap = {
635
640
  };
636
641
  type EmptyRenderContext = Readonly<Record<never, never>>;
637
642
  export type EmailEphemeralRenderContextMap = {
638
- readonly [Kind in EmailKind]: Kind extends "auth.password_reset" ? Readonly<{
643
+ readonly [Kind in EmailKind]: Kind extends "auth.password_reset" | "auth.admin_password_reset" ? Readonly<{
639
644
  reset_url: string;
640
645
  valid_minutes?: number;
641
646
  }> : Kind extends "user.invite" ? Readonly<{
@@ -240,6 +240,8 @@ exports.EmailPersistedDataSchemas = {
240
240
  expires_on: affiliate_1.CalendarDateSchema,
241
241
  })
242
242
  .strict(),
243
+ // The Admin dashboard is German only, so no locale; the link is ephemeral.
244
+ "auth.admin_password_reset": zod_1.z.object({}).strict(),
243
245
  };
244
246
  exports.EmailRenderDataSchemas = {
245
247
  ...exports.EmailPersistedDataSchemas,
@@ -248,6 +250,11 @@ exports.EmailRenderDataSchemas = {
248
250
  /** How long the reset link is valid, from the token; `undefined` if unknown. */
249
251
  valid_minutes: zod_1.z.number().int().positive().optional(),
250
252
  }),
253
+ "auth.admin_password_reset": exports.EmailPersistedDataSchemas["auth.admin_password_reset"].extend({
254
+ reset_url: zod_1.z.string().url(),
255
+ /** How long the reset link is valid, from the token; `undefined` if unknown. */
256
+ valid_minutes: zod_1.z.number().int().positive().optional(),
257
+ }),
251
258
  "user.invite": exports.EmailPersistedDataSchemas["user.invite"].extend({
252
259
  invite_url: zod_1.z.string().url(),
253
260
  /** When the invite expires (ISO 8601), from the current invite. */
@@ -1,2 +1,4 @@
1
1
  /** Container key of the base plugin's module (`Module("plugin_features", …)`). */
2
2
  export declare const PLUGIN_FEATURES_MODULE: "plugin_features";
3
+ /** Container key of the base plugin's Steuer-Compliance module (`Module("tax_compliance", …)`). */
4
+ export declare const TAX_COMPLIANCE_MODULE: "tax_compliance";
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PLUGIN_FEATURES_MODULE = void 0;
3
+ exports.TAX_COMPLIANCE_MODULE = exports.PLUGIN_FEATURES_MODULE = void 0;
4
4
  /** Container key of the base plugin's module (`Module("plugin_features", …)`). */
5
5
  exports.PLUGIN_FEATURES_MODULE = "plugin_features";
6
+ /** Container key of the base plugin's Steuer-Compliance module (`Module("tax_compliance", …)`). */
7
+ exports.TAX_COMPLIANCE_MODULE = "tax_compliance";
@@ -1,4 +1,5 @@
1
- export { PLUGIN_FEATURES_MODULE } from "./constants";
1
+ export { PLUGIN_FEATURES_MODULE, TAX_COMPLIANCE_MODULE } from "./constants";
2
+ export { listCustomerAddressTaxIdentifications, TAX_COMPLIANCE_STATUSES, type AddressSnapshot, type CustomerAddressTaxIdentification, type TaxComplianceStatus, } from "./tax-compliance";
2
3
  export { definePluginFeatures } from "./descriptor";
3
4
  export { resolveEffectivePluginFeatures } from "./effective-features";
4
5
  export { isOwnPluginEvent, isPluginFeatureEnabled, isPluginFeatureEnabledByKey, pluginFeatureGate, requirePluginFeature, } from "./enforcement";
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.reportPluginActionProgress = exports.shopMasterDataDescriptor = exports.getShopMasterData = exports.getShopAppearance = exports.getPluginSettings = exports.PLUGIN_ICON_NAMES = exports.resolveBaseModule = exports.isBasePluginRegistered = exports.requirePluginFeature = exports.pluginFeatureGate = exports.isPluginFeatureEnabledByKey = exports.isPluginFeatureEnabled = exports.isOwnPluginEvent = exports.resolveEffectivePluginFeatures = exports.definePluginFeatures = exports.PLUGIN_FEATURES_MODULE = void 0;
3
+ exports.reportPluginActionProgress = exports.shopMasterDataDescriptor = exports.getShopMasterData = exports.getShopAppearance = exports.getPluginSettings = exports.PLUGIN_ICON_NAMES = exports.resolveBaseModule = exports.isBasePluginRegistered = exports.requirePluginFeature = exports.pluginFeatureGate = exports.isPluginFeatureEnabledByKey = exports.isPluginFeatureEnabled = exports.isOwnPluginEvent = exports.resolveEffectivePluginFeatures = exports.definePluginFeatures = exports.TAX_COMPLIANCE_STATUSES = exports.listCustomerAddressTaxIdentifications = exports.TAX_COMPLIANCE_MODULE = exports.PLUGIN_FEATURES_MODULE = void 0;
4
4
  var constants_1 = require("./constants");
5
5
  Object.defineProperty(exports, "PLUGIN_FEATURES_MODULE", { enumerable: true, get: function () { return constants_1.PLUGIN_FEATURES_MODULE; } });
6
+ Object.defineProperty(exports, "TAX_COMPLIANCE_MODULE", { enumerable: true, get: function () { return constants_1.TAX_COMPLIANCE_MODULE; } });
7
+ var tax_compliance_1 = require("./tax-compliance");
8
+ Object.defineProperty(exports, "listCustomerAddressTaxIdentifications", { enumerable: true, get: function () { return tax_compliance_1.listCustomerAddressTaxIdentifications; } });
9
+ Object.defineProperty(exports, "TAX_COMPLIANCE_STATUSES", { enumerable: true, get: function () { return tax_compliance_1.TAX_COMPLIANCE_STATUSES; } });
6
10
  var descriptor_1 = require("./descriptor");
7
11
  Object.defineProperty(exports, "definePluginFeatures", { enumerable: true, get: function () { return descriptor_1.definePluginFeatures; } });
8
12
  var effective_features_1 = require("./effective-features");