@hartl-services/medusa-storefront 0.0.0-stage → 0.2.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 (194) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -2
  3. package/dist/browser/account-verification-channel.d.ts +11 -0
  4. package/dist/browser/account-verification-channel.js +32 -0
  5. package/dist/browser/actions.d.ts +53 -0
  6. package/dist/browser/actions.js +1 -0
  7. package/dist/browser/affiliate-referral.d.ts +152 -0
  8. package/dist/browser/affiliate-referral.js +85 -0
  9. package/dist/browser/browser-session.d.ts +3 -0
  10. package/dist/browser/browser-session.js +27 -0
  11. package/dist/browser/cart-mutations.d.ts +36 -0
  12. package/dist/browser/cart-mutations.js +137 -0
  13. package/dist/browser/cart-refresh.d.ts +11 -0
  14. package/dist/browser/cart-refresh.js +33 -0
  15. package/dist/browser/client.d.ts +15 -0
  16. package/dist/browser/client.js +20 -0
  17. package/dist/browser/fresh-order-marker.d.ts +11 -0
  18. package/dist/browser/fresh-order-marker.js +30 -0
  19. package/dist/browser/index.d.ts +946 -0
  20. package/dist/browser/index.js +70 -0
  21. package/dist/browser/query-invalidation.d.ts +4 -0
  22. package/dist/browser/query-invalidation.js +39 -0
  23. package/dist/browser/query-keys.d.ts +43 -0
  24. package/dist/browser/query-keys.js +28 -0
  25. package/dist/browser/query-options.d.ts +781 -0
  26. package/dist/browser/query-options.js +165 -0
  27. package/dist/browser/region.d.ts +5 -0
  28. package/dist/browser/region.js +15 -0
  29. package/dist/browser/unwrap-medusa-action.d.ts +7 -0
  30. package/dist/browser/unwrap-medusa-action.js +25 -0
  31. package/dist/core/action-result.d.ts +17 -0
  32. package/dist/core/action-result.js +12 -0
  33. package/dist/core/addresses.d.ts +46 -0
  34. package/dist/core/addresses.js +137 -0
  35. package/dist/core/affiliate/affiliate-application-errors.d.ts +2 -0
  36. package/dist/core/affiliate/affiliate-application-errors.js +14 -0
  37. package/dist/core/affiliate/affiliate-commission-presentation.d.ts +15 -0
  38. package/dist/core/affiliate/affiliate-commission-presentation.js +28 -0
  39. package/dist/core/affiliate/affiliate-coupon-code-presentation.d.ts +10 -0
  40. package/dist/core/affiliate/affiliate-coupon-code-presentation.js +22 -0
  41. package/dist/core/affiliate/affiliate-coupon-label.d.ts +3 -0
  42. package/dist/core/affiliate/affiliate-coupon-label.js +4 -0
  43. package/dist/core/affiliate/affiliate-date-range.d.ts +6 -0
  44. package/dist/core/affiliate/affiliate-date-range.js +16 -0
  45. package/dist/core/affiliate/affiliate-rate-presentation.d.ts +1 -0
  46. package/dist/core/affiliate/affiliate-rate-presentation.js +8 -0
  47. package/dist/core/affiliate/partner-company-address.d.ts +24 -0
  48. package/dist/core/affiliate/partner-company-address.js +31 -0
  49. package/dist/core/auth/customer-sign-in.d.ts +36 -0
  50. package/dist/core/auth/customer-sign-in.js +55 -0
  51. package/dist/core/auth/link-token.d.ts +8 -0
  52. package/dist/core/auth/link-token.js +11 -0
  53. package/dist/core/auth/login-code-auto-send.d.ts +7 -0
  54. package/dist/core/auth/login-code-auto-send.js +11 -0
  55. package/dist/core/auth/password-rule-label.d.ts +4 -0
  56. package/dist/core/auth/password-rule-label.js +13 -0
  57. package/dist/core/cart/cart-line-item-image-url.d.ts +9 -0
  58. package/dist/core/cart/cart-line-item-image-url.js +14 -0
  59. package/dist/core/cart/cart-normalization.d.ts +2 -0
  60. package/dist/core/cart/cart-normalization.js +11 -0
  61. package/dist/core/cart/cart-price-breakdown.d.ts +30 -0
  62. package/dist/core/cart/cart-price-breakdown.js +45 -0
  63. package/dist/core/cart/cart-tax-breakdown.d.ts +17 -0
  64. package/dist/core/cart/cart-tax-breakdown.js +18 -0
  65. package/dist/core/cart/coupon-promotions.d.ts +8 -0
  66. package/dist/core/cart/coupon-promotions.js +9 -0
  67. package/dist/core/cart/payment-session.d.ts +3 -0
  68. package/dist/core/cart/payment-session.js +12 -0
  69. package/dist/core/cart/refunded-cart.d.ts +27 -0
  70. package/dist/core/cart/refunded-cart.js +46 -0
  71. package/dist/core/catalog/category-filter.d.ts +16 -0
  72. package/dist/core/catalog/category-filter.js +33 -0
  73. package/dist/core/catalog/inventory.d.ts +10 -0
  74. package/dist/core/catalog/inventory.js +9 -0
  75. package/dist/core/catalog/medusa-product-image.d.ts +8 -0
  76. package/dist/core/catalog/medusa-product-image.js +13 -0
  77. package/dist/core/catalog/product-price.d.ts +8 -0
  78. package/dist/core/catalog/product-price.js +21 -0
  79. package/dist/core/catalog/selected-variant.d.ts +7 -0
  80. package/dist/core/catalog/selected-variant.js +9 -0
  81. package/dist/core/checkout/checkout-account-offer-state.d.ts +41 -0
  82. package/dist/core/checkout/checkout-account-offer-state.js +32 -0
  83. package/dist/core/checkout/checkout-address-draft.d.ts +39 -0
  84. package/dist/core/checkout/checkout-address-draft.js +78 -0
  85. package/dist/core/checkout/checkout-address-form.d.ts +24 -0
  86. package/dist/core/checkout/checkout-address-form.js +99 -0
  87. package/dist/core/checkout/checkout-coupon.d.ts +9 -0
  88. package/dist/core/checkout/checkout-coupon.js +13 -0
  89. package/dist/core/checkout/checkout-steps.d.ts +20 -0
  90. package/dist/core/checkout/checkout-steps.js +44 -0
  91. package/dist/core/checkout/customer-login-required.d.ts +12 -0
  92. package/dist/core/checkout/customer-login-required.js +14 -0
  93. package/dist/core/checkout/payment-authorization.d.ts +8 -0
  94. package/dist/core/checkout/payment-authorization.js +9 -0
  95. package/dist/core/checkout/stripe-redirect-return.d.ts +13 -0
  96. package/dist/core/checkout/stripe-redirect-return.js +29 -0
  97. package/dist/core/errors.d.ts +12 -0
  98. package/dist/core/errors.js +18 -0
  99. package/dist/core/fields.d.ts +18 -0
  100. package/dist/core/fields.js +20 -0
  101. package/dist/core/form-values.d.ts +162 -0
  102. package/dist/core/form-values.js +234 -0
  103. package/dist/core/format/money.d.ts +7 -0
  104. package/dist/core/format/money.js +6 -0
  105. package/dist/core/i18n.d.ts +24 -0
  106. package/dist/core/i18n.js +15 -0
  107. package/dist/core/image-url.d.ts +18 -0
  108. package/dist/core/image-url.js +34 -0
  109. package/dist/core/index.d.ts +48 -0
  110. package/dist/core/index.js +49 -0
  111. package/dist/core/order/order-payment-summary.d.ts +15 -0
  112. package/dist/core/order/order-payment-summary.js +48 -0
  113. package/dist/core/plugin-features.d.ts +21 -0
  114. package/dist/core/plugin-features.js +30 -0
  115. package/dist/core/query-string.d.ts +6 -0
  116. package/dist/core/query-string.js +16 -0
  117. package/dist/core/region.d.ts +11 -0
  118. package/dist/core/region.js +13 -0
  119. package/dist/core/report-error.d.ts +12 -0
  120. package/dist/core/report-error.js +1 -0
  121. package/dist/core/routing-locale.d.ts +1 -0
  122. package/dist/core/routing-locale.js +25 -0
  123. package/dist/core/settle-within.d.ts +8 -0
  124. package/dist/core/settle-within.js +14 -0
  125. package/dist/core/tax-compliance/address-tax-identification.d.ts +37 -0
  126. package/dist/core/tax-compliance/address-tax-identification.js +51 -0
  127. package/dist/core/tax-compliance/tax-identification.d.ts +18 -0
  128. package/dist/core/tax-compliance/tax-identification.js +58 -0
  129. package/dist/core/texts-de.d.ts +136 -0
  130. package/dist/core/texts-de.js +152 -0
  131. package/dist/react/cart-error-boundary.d.ts +24 -0
  132. package/dist/react/cart-error-boundary.js +57 -0
  133. package/dist/react/context.d.ts +17 -0
  134. package/dist/react/context.js +46 -0
  135. package/dist/react/index.d.ts +12 -0
  136. package/dist/react/index.js +14 -0
  137. package/dist/react/use-account.d.ts +214 -0
  138. package/dist/react/use-account.js +294 -0
  139. package/dist/react/use-affiliate.d.ts +566 -0
  140. package/dist/react/use-affiliate.js +573 -0
  141. package/dist/react/use-auth.d.ts +136 -0
  142. package/dist/react/use-auth.js +402 -0
  143. package/dist/react/use-cart.d.ts +57 -0
  144. package/dist/react/use-cart.js +137 -0
  145. package/dist/react/use-checkout-address-form.d.ts +156 -0
  146. package/dist/react/use-checkout-address-form.js +95 -0
  147. package/dist/react/use-checkout.d.ts +268 -0
  148. package/dist/react/use-checkout.js +873 -0
  149. package/dist/react/use-countdown.d.ts +5 -0
  150. package/dist/react/use-countdown.js +23 -0
  151. package/dist/react/use-link-token.d.ts +12 -0
  152. package/dist/react/use-link-token.js +17 -0
  153. package/dist/react/use-plugin-feature.d.ts +16 -0
  154. package/dist/react/use-plugin-feature.js +31 -0
  155. package/dist/react/use-verification-watch.d.ts +7 -0
  156. package/dist/react/use-verification-watch.js +49 -0
  157. package/dist/registry/account.json +248 -0
  158. package/dist/registry/affiliate.json +302 -0
  159. package/dist/registry/cart.json +127 -0
  160. package/dist/registry/checkout.json +169 -0
  161. package/dist/registry/form.json +145 -0
  162. package/dist/registry/registry.json +917 -0
  163. package/dist/registry/setup.json +76 -0
  164. package/dist/server/affiliate.d.ts +124 -0
  165. package/dist/server/affiliate.js +145 -0
  166. package/dist/server/cart.d.ts +10 -0
  167. package/dist/server/cart.js +113 -0
  168. package/dist/server/catalog.d.ts +28 -0
  169. package/dist/server/catalog.js +82 -0
  170. package/dist/server/checkout.d.ts +6 -0
  171. package/dist/server/checkout.js +12 -0
  172. package/dist/server/context.d.ts +38 -0
  173. package/dist/server/context.js +34 -0
  174. package/dist/server/customer.d.ts +84 -0
  175. package/dist/server/customer.js +362 -0
  176. package/dist/server/documents.d.ts +29 -0
  177. package/dist/server/documents.js +55 -0
  178. package/dist/server/index.d.ts +258 -0
  179. package/dist/server/index.js +45 -0
  180. package/dist/server/orders.d.ts +9 -0
  181. package/dist/server/orders.js +31 -0
  182. package/dist/server/plugin-features.d.ts +13 -0
  183. package/dist/server/plugin-features.js +52 -0
  184. package/dist/server/region.d.ts +6 -0
  185. package/dist/server/region.js +17 -0
  186. package/dist/server/route-response.d.ts +2 -0
  187. package/dist/server/route-response.js +17 -0
  188. package/dist/server/run-medusa-action.d.ts +7 -0
  189. package/dist/server/run-medusa-action.js +40 -0
  190. package/dist/server/session.d.ts +29 -0
  191. package/dist/server/session.js +106 -0
  192. package/dist/server/tax-compliance.d.ts +26 -0
  193. package/dist/server/tax-compliance.js +85 -0
  194. package/package.json +86 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hartl Services GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,104 @@
1
- # Temporary Holding Version
1
+ # @hartl-services/medusa-storefront
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Headless storefront for Next.js shops on a Medusa backend with the
4
+ `@hartl-services/medusa-*` plugins: cart, checkout (Stripe), customer account,
5
+ UID check and partner portal. The package holds the commerce logic, the Next
6
+ server/browser adapters and the React hooks; the shop keeps only its markup,
7
+ CMS content and tracking. Bug fixes reach every shop through a version bump.
8
+
9
+ Next.js App Router (16.2+) and React 19.2+ only. ESM only.
10
+
11
+ ```bash
12
+ pnpm add @hartl-services/medusa-storefront @hartl-services/medusa-contracts \
13
+ @medusajs/framework@^2.19.0 @medusajs/js-sdk @medusajs/types \
14
+ @tanstack/react-query @tanstack/react-form \
15
+ @stripe/stripe-js @stripe/react-stripe-js zod
16
+ ```
17
+
18
+ `@medusajs/framework` is the peer of `@hartl-services/medusa-contracts`, whose
19
+ schemas the package validates with. Install exactly one copy of Contracts
20
+ (`pnpm why @hartl-services/medusa-contracts`).
21
+
22
+ ## Subpaths
23
+
24
+ | Import | Runs | Contents |
25
+ |---|---|---|
26
+ | `/core` | anywhere | Pure logic: cart and price breakdown, coupon and payment rules, address drafts and schema factories, UID feedback, plugin-feature evaluation, `fields` constants, text codes (`Translate`, `createTranslator`, `StorefrontError`). |
27
+ | `/core/texts-de` | anywhere | German wording of every text code (`textsDe`, `translateDe`). |
28
+ | `/server` | Next server | `createStorefrontServer(config)`: session cookies, every server function behind the shop's Server Actions, cached loaders (region, plugin features, merchant country), Route Handlers (document download, partner portal reads). |
29
+ | `/browser` | browser | `createStorefrontBrowser(config)`: credentialed Medusa client, cart mutations, referral capture, react-query options, Stripe loader. |
30
+ | `/react` | browser | `StorefrontProvider` and the hooks for cart, sign-in, account, checkout, UID check and partner portal. |
31
+
32
+ ## A new shop
33
+
34
+ The package ships a [shadcn](https://ui.shadcn.com) registry of unstyled
35
+ copy-in components that wire the hooks together. Add it to the shop's
36
+ `components.json`, pinned to the installed package version:
37
+
38
+ ```json
39
+ {
40
+ "registries": {
41
+ "@hartl-storefront": "https://cdn.jsdelivr.net/npm/@hartl-services/medusa-storefront@0.1.0/dist/registry/{name}.json"
42
+ }
43
+ }
44
+ ```
45
+
46
+ ```bash
47
+ pnpm dlx shadcn@latest add @hartl-storefront/setup @hartl-storefront/form @hartl-storefront/cart @hartl-storefront/checkout @hartl-storefront/account @hartl-storefront/affiliate
48
+ ```
49
+
50
+ | Item | Adds |
51
+ |---|---|
52
+ | `setup` | `lib/storefront/{server,client,actions,texts,report-error,public-env,image-url}.ts`, `components/storefront/{storefront-providers,storefront-root}.tsx` |
53
+ | `form` | `hooks/use-app-form.ts` and the field components it registers |
54
+ | `cart` | cart widget, line items, totals, add-to-cart, cart page |
55
+ | `checkout` | checkout page, address, shipping/payment (Stripe), coupon, order confirmation |
56
+ | `account` | sign-in (password, code), password reset, verification, addresses, profile, orders, document download route |
57
+ | `affiliate` | partner portal: registration with UID check, links, coupons, commissions, consignment, its Route Handlers |
58
+
59
+ Then wrap the content of `app/(frontend)/[countryCode]/layout.tsx` in
60
+ `<StorefrontRoot>` and set the environment:
61
+
62
+ | Variable | |
63
+ |---|---|
64
+ | `NEXT_PUBLIC_MEDUSA_BACKEND_URL` | public Medusa URL (browser and images) |
65
+ | `MEDUSA_BACKEND_URL` | optional internal URL for server calls |
66
+ | `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` | Medusa publishable key |
67
+ | `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Stripe key; without it the payment step shows "not configured" |
68
+
69
+ The Medusa backend's `STORE_CORS` and `AUTH_CORS` must contain the shop's
70
+ origin (the browser calls Medusa with credentials).
71
+
72
+ The copied files belong to the shop: style them, rename them, change the
73
+ wording. Keep the commerce logic in the package — a fix there reaches the shop
74
+ with the next version; a fix only in a copied file does not.
75
+
76
+ ## Optional actions
77
+
78
+ Some server functions are optional in `createStorefrontBrowser`'s `actions`,
79
+ so a shop wired before they existed keeps working. Add the `"use server"`
80
+ wrapper (as in the `setup` item's `lib/storefront/actions.ts`) to use them:
81
+
82
+ | Action | Since | Used by |
83
+ |---|---|---|
84
+ | `registerCustomerWithProfile` | 0.2.0 | `usePasswordAuth().registerWithProfile` (name, phone, default address) |
85
+ | `getOrder` | 0.2.0 | `useAccountOrderDetail` (order lines and totals in the account) |
86
+
87
+ ## Wiring rules
88
+
89
+ - Create `createStorefrontServer` and `createStorefrontBrowser` once each, at
90
+ module level (`lib/storefront/server.ts`, `lib/storefront/client.ts`). The
91
+ server's per-request cache and the browser's shared state (refunded-cart
92
+ replacements, in-flight referral captures) rely on a single instance.
93
+ - Every server function the browser calls goes through a `"use server"`
94
+ wrapper defined in the shop (`lib/storefront/actions.ts`); Next accepts no
95
+ Server Action from a package. Never re-export from a `"use server"` file.
96
+ - Texts: the package returns text codes; `t` (`translateDe`, or
97
+ `createTranslator(textsDe, "de-DE", overrides)`) turns them into wording.
98
+ - Tracking goes into the browser config's `callbacks` (`onAddedToCart`,
99
+ `onCheckoutStarted`, `onPaymentMethodSelected`, `onLoggedOut`); each fires
100
+ once from the package's hooks.
101
+ - Errors: `reportError(error, { area, context, tags })` receives everything
102
+ the package reports; hand it to the shop's error tracking.
103
+ - Forms: the shop's TanStack `useAppForm` (its field components are the
104
+ design) takes the `formOptions` a hook returns.
@@ -0,0 +1,11 @@
1
+ export type AccountVerificationMessage = {
2
+ type: "email-verified";
3
+ } | {
4
+ type: "signed-in";
5
+ path: string;
6
+ };
7
+ export declare function parseAccountVerificationMessage(data: unknown): AccountVerificationMessage | null;
8
+ export declare function canReachOtherTabs(): boolean;
9
+ /** Null where the browser has no BroadcastChannel; callers then simply skip the shortcut. */
10
+ export declare function openAccountVerificationChannel(): BroadcastChannel | null;
11
+ export declare function postAccountVerificationMessage(message: AccountVerificationMessage): void;
@@ -0,0 +1,32 @@
1
+ // Tabs of one browser talk about a pending e-mail confirmation: the tab that
2
+ // opened the confirmation link announces it, the tab that waits with the login
3
+ // signs in and names the page the visitor came from.
4
+ const CHANNEL_NAME = "account-verification";
5
+ // Only pages that show the login form; never an arbitrary path another tab sends.
6
+ const RETURN_PATH = /^\/[a-z]{2}\/(checkout|account|partnerportal)(\/[a-z0-9-]+)*$/;
7
+ export function parseAccountVerificationMessage(data) {
8
+ if (typeof data !== "object" || data === null) {
9
+ return null;
10
+ }
11
+ const type = Reflect.get(data, "type");
12
+ if (type === "email-verified") {
13
+ return { type };
14
+ }
15
+ const path = Reflect.get(data, "path");
16
+ if (type === "signed-in" && typeof path === "string" && RETURN_PATH.test(path)) {
17
+ return { type, path };
18
+ }
19
+ return null;
20
+ }
21
+ export function canReachOtherTabs() {
22
+ return typeof BroadcastChannel !== "undefined";
23
+ }
24
+ /** Null where the browser has no BroadcastChannel; callers then simply skip the shortcut. */
25
+ export function openAccountVerificationChannel() {
26
+ return canReachOtherTabs() ? new BroadcastChannel(CHANNEL_NAME) : null;
27
+ }
28
+ export function postAccountVerificationMessage(message) {
29
+ const channel = openAccountVerificationChannel();
30
+ channel?.postMessage(message);
31
+ channel?.close();
32
+ }
@@ -0,0 +1,53 @@
1
+ import type { StorefrontServer } from "../server/index.js";
2
+ type Server = StorefrontServer;
3
+ /**
4
+ * The shop's Server Actions, one per server function: Next.js only accepts
5
+ * `"use server"` functions defined in the app, so the shop wraps each server
6
+ * function and hands the wrappers to the browser side here.
7
+ */
8
+ export interface StorefrontActions {
9
+ getActiveCart: Server["cart"]["getActiveCart"];
10
+ applyCustomerDefaultsToActiveCart: Server["cart"]["applyCustomerDefaultsToActiveCart"];
11
+ syncCartId: Server["cart"]["syncCartId"];
12
+ clearCartId: Server["cart"]["clearCartId"];
13
+ discardActiveCart: Server["cart"]["discardActiveCart"];
14
+ retrieveCustomer: Server["customer"]["retrieveCustomer"];
15
+ loginCustomer: Server["customer"]["loginCustomer"];
16
+ checkCustomerVerification: Server["customer"]["checkCustomerVerification"];
17
+ registerCustomer: Server["customer"]["registerCustomer"];
18
+ /** Optional (since 0.2.0): registration with name, phone and address. */
19
+ registerCustomerWithProfile?: Server["customer"]["registerCustomerWithProfile"];
20
+ requestLoginCode: Server["customer"]["requestLoginCode"];
21
+ loginWithCode: Server["customer"]["loginWithCode"];
22
+ requestCustomerPasswordReset: Server["customer"]["requestCustomerPasswordReset"];
23
+ resetCustomerPassword: Server["customer"]["resetCustomerPassword"];
24
+ confirmCustomerEmail: Server["customer"]["confirmCustomerEmail"];
25
+ resendCustomerVerification: Server["customer"]["resendCustomerVerification"];
26
+ fetchCustomerPasswordPolicy: Server["customer"]["fetchCustomerPasswordPolicy"];
27
+ updateCustomer: Server["customer"]["updateCustomer"];
28
+ createCustomerAddress: Server["customer"]["createCustomerAddress"];
29
+ saveFirstCheckoutAddresses: Server["customer"]["saveFirstCheckoutAddresses"];
30
+ updateCustomerAddress: Server["customer"]["updateCustomerAddress"];
31
+ deleteCustomerAddress: Server["customer"]["deleteCustomerAddress"];
32
+ logoutCustomer: Server["customer"]["logoutCustomer"];
33
+ listShippingOptions: Server["checkout"]["listShippingOptions"];
34
+ listPaymentProviders: Server["checkout"]["listPaymentProviders"];
35
+ listRegions: Server["region"]["listRegions"];
36
+ fetchPluginFeatures: Server["pluginFeatures"]["fetchPluginFeatures"];
37
+ checkCartTaxIdentification: Server["taxCompliance"]["checkCartTaxIdentification"];
38
+ listCustomerTaxIdentifications: Server["taxCompliance"]["listCustomerTaxIdentifications"];
39
+ verifyCustomerAddressTaxIdentification: Server["taxCompliance"]["verifyCustomerAddressTaxIdentification"];
40
+ submitAffiliateApplication: Server["affiliate"]["submitAffiliateApplication"];
41
+ applyAffiliateOwnOrderPromotion: Server["affiliate"]["applyAffiliateOwnOrderPromotion"];
42
+ createAffiliateSlug: Server["affiliate"]["createAffiliateSlug"];
43
+ updateAffiliateSlug: Server["affiliate"]["updateAffiliateSlug"];
44
+ createAffiliateCouponCode: Server["affiliate"]["createAffiliateCouponCode"];
45
+ rotateAffiliateCouponCode: Server["affiliate"]["rotateAffiliateCouponCode"];
46
+ revokeAffiliateCouponCode: Server["affiliate"]["revokeAffiliateCouponCode"];
47
+ createConsignmentReport: Server["affiliate"]["createConsignmentReport"];
48
+ listOrders: Server["orders"]["listOrders"];
49
+ /** Optional (since 0.2.0): one order with lines and totals (account order detail). */
50
+ getOrder?: Server["orders"]["getOrder"];
51
+ listDocuments: Server["documents"]["listDocuments"];
52
+ }
53
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,152 @@
1
+ import type { HttpTypes } from "@medusajs/types";
2
+ import type { CredentialedMedusaClient } from "./client.js";
3
+ export interface CaptureAffiliateReferralInput {
4
+ cartId?: string;
5
+ slug: string;
6
+ }
7
+ type RefreshActiveCart = () => Promise<Pick<HttpTypes.StoreCart, "id"> | null | undefined>;
8
+ export declare function runAffiliateReferralCapture<T>(capture: () => Promise<T>): Promise<T>;
9
+ export declare function waitForAffiliateReferralCapture(): Promise<void>;
10
+ export declare function removeCapturedAffiliateReferral(searchParams: URLSearchParams, capturedSlug: string): URLSearchParams;
11
+ export declare function captureAffiliateReferralWithClient(sdk: CredentialedMedusaClient, input: CaptureAffiliateReferralInput): Promise<{
12
+ status: "captured" | "replaced" | "ignored_existing_assignment" | "kept_existing";
13
+ referral: {
14
+ id: string;
15
+ source: "link" | "coupon";
16
+ status: "active" | "replaced" | "ignored_existing_assignment" | "pending_email_validation" | "consumed" | "expired" | "invalidated";
17
+ affiliate: {
18
+ id: string;
19
+ status: "active";
20
+ };
21
+ captured_at: string;
22
+ expires_at: string;
23
+ } | null;
24
+ }>;
25
+ export declare function captureAffiliateReferralAfterCartRefreshWithClient(sdk: CredentialedMedusaClient, slug: string, refreshActiveCart: RefreshActiveCart): Promise<{
26
+ status: "captured" | "replaced" | "ignored_existing_assignment" | "kept_existing";
27
+ referral: {
28
+ id: string;
29
+ source: "link" | "coupon";
30
+ status: "active" | "replaced" | "ignored_existing_assignment" | "pending_email_validation" | "consumed" | "expired" | "invalidated";
31
+ affiliate: {
32
+ id: string;
33
+ status: "active";
34
+ };
35
+ captured_at: string;
36
+ expires_at: string;
37
+ } | null;
38
+ }>;
39
+ export interface ApplyCheckoutCouponOptions {
40
+ useAffiliatePath: boolean;
41
+ }
42
+ export declare function applyCheckoutCouponWithClient(sdk: CredentialedMedusaClient, cartId: string, code: string, { useAffiliatePath }: ApplyCheckoutCouponOptions): Promise<{
43
+ cart: HttpTypes.StoreCart;
44
+ kind: "promotion";
45
+ response?: undefined;
46
+ } | {
47
+ kind: "affiliate";
48
+ response: {
49
+ status: "captured" | "replaced" | "ignored_existing_assignment" | "kept_existing" | "pending_email_validation" | "expired" | "coupon_already_redeemed";
50
+ referral: {
51
+ id: string;
52
+ source: "link" | "coupon";
53
+ status: "active" | "replaced" | "ignored_existing_assignment" | "pending_email_validation" | "consumed" | "expired" | "invalidated";
54
+ affiliate: {
55
+ id: string;
56
+ status: "active";
57
+ };
58
+ captured_at: string;
59
+ expires_at: string;
60
+ } | null;
61
+ discount_applied: boolean;
62
+ message?: string | undefined;
63
+ };
64
+ cart?: undefined;
65
+ }>;
66
+ export declare function resolveCouponLabelsWithClient(sdk: CredentialedMedusaClient, codes: string[]): Promise<{
67
+ labels: {
68
+ code: string;
69
+ label: string;
70
+ discount_percentage: number;
71
+ }[];
72
+ }>;
73
+ /** The referral and coupon calls bound to the shop's credentialed client. */
74
+ export declare function createAffiliateReferral(createClient: () => CredentialedMedusaClient): {
75
+ runAffiliateReferralCapture: typeof runAffiliateReferralCapture;
76
+ waitForAffiliateReferralCapture: typeof waitForAffiliateReferralCapture;
77
+ removeCapturedAffiliateReferral: typeof removeCapturedAffiliateReferral;
78
+ captureAffiliateReferral: (input: CaptureAffiliateReferralInput) => Promise<{
79
+ status: "captured" | "replaced" | "ignored_existing_assignment" | "kept_existing";
80
+ referral: {
81
+ id: string;
82
+ source: "link" | "coupon";
83
+ status: "active" | "replaced" | "ignored_existing_assignment" | "pending_email_validation" | "consumed" | "expired" | "invalidated";
84
+ affiliate: {
85
+ id: string;
86
+ status: "active";
87
+ };
88
+ captured_at: string;
89
+ expires_at: string;
90
+ } | null;
91
+ }>;
92
+ captureAffiliateReferralAfterCartRefresh: (slug: string, refreshActiveCart: RefreshActiveCart) => Promise<{
93
+ status: "captured" | "replaced" | "ignored_existing_assignment" | "kept_existing";
94
+ referral: {
95
+ id: string;
96
+ source: "link" | "coupon";
97
+ status: "active" | "replaced" | "ignored_existing_assignment" | "pending_email_validation" | "consumed" | "expired" | "invalidated";
98
+ affiliate: {
99
+ id: string;
100
+ status: "active";
101
+ };
102
+ captured_at: string;
103
+ expires_at: string;
104
+ } | null;
105
+ }>;
106
+ applyCheckoutCoupon: (cartId: string, code: string, options: ApplyCheckoutCouponOptions) => Promise<{
107
+ cart: HttpTypes.StoreCart;
108
+ kind: "promotion";
109
+ response?: undefined;
110
+ } | {
111
+ kind: "affiliate";
112
+ response: {
113
+ status: "captured" | "replaced" | "ignored_existing_assignment" | "kept_existing" | "pending_email_validation" | "expired" | "coupon_already_redeemed";
114
+ referral: {
115
+ id: string;
116
+ source: "link" | "coupon";
117
+ status: "active" | "replaced" | "ignored_existing_assignment" | "pending_email_validation" | "consumed" | "expired" | "invalidated";
118
+ affiliate: {
119
+ id: string;
120
+ status: "active";
121
+ };
122
+ captured_at: string;
123
+ expires_at: string;
124
+ } | null;
125
+ discount_applied: boolean;
126
+ message?: string | undefined;
127
+ };
128
+ cart?: undefined;
129
+ }>;
130
+ getAffiliateCartReferral: (cartId: string) => Promise<{
131
+ referral: {
132
+ id: string;
133
+ source: "link" | "coupon";
134
+ status: "active" | "replaced" | "ignored_existing_assignment" | "pending_email_validation" | "consumed" | "expired" | "invalidated";
135
+ affiliate: {
136
+ id: string;
137
+ status: "active";
138
+ };
139
+ captured_at: string;
140
+ expires_at: string;
141
+ } | null;
142
+ }>;
143
+ resolveCouponLabels: (codes: string[]) => Promise<{
144
+ labels: {
145
+ code: string;
146
+ label: string;
147
+ discount_percentage: number;
148
+ }[];
149
+ }>;
150
+ };
151
+ export type BrowserAffiliateReferral = ReturnType<typeof createAffiliateReferral>;
152
+ export {};
@@ -0,0 +1,85 @@
1
+ import { FetchError } from "@medusajs/js-sdk";
2
+ import { affiliateStorePaths } from "@hartl-services/medusa-contracts/paths/affiliate";
3
+ // One per browser page: cart creation waits for every "?ref=" capture still in
4
+ // flight rather than losing it. Module state on purpose, so every caller in
5
+ // the page shares the same set.
6
+ const inFlightReferralCaptures = new Set();
7
+ export function runAffiliateReferralCapture(capture) {
8
+ const capturePromise = Promise.resolve().then(capture);
9
+ inFlightReferralCaptures.add(capturePromise);
10
+ void capturePromise.then(() => inFlightReferralCaptures.delete(capturePromise), () => inFlightReferralCaptures.delete(capturePromise));
11
+ return capturePromise;
12
+ }
13
+ export async function waitForAffiliateReferralCapture() {
14
+ while (inFlightReferralCaptures.size > 0) {
15
+ await Promise.allSettled(inFlightReferralCaptures);
16
+ }
17
+ }
18
+ export function removeCapturedAffiliateReferral(searchParams, capturedSlug) {
19
+ if (searchParams.get("ref") !== capturedSlug) {
20
+ return searchParams;
21
+ }
22
+ const nextParams = new URLSearchParams(searchParams);
23
+ nextParams.delete("ref");
24
+ return nextParams;
25
+ }
26
+ export async function captureAffiliateReferralWithClient(sdk, input) {
27
+ return await sdk.client.fetch(affiliateStorePaths.post.captureReferral, {
28
+ body: { slug: input.slug, ...(input.cartId ? { cart_id: input.cartId } : {}) },
29
+ credentials: "include",
30
+ method: "POST",
31
+ });
32
+ }
33
+ export async function captureAffiliateReferralAfterCartRefreshWithClient(sdk, slug, refreshActiveCart) {
34
+ return await runAffiliateReferralCapture(async () => {
35
+ const activeCart = await refreshActiveCart();
36
+ return await captureAffiliateReferralWithClient(sdk, { cartId: activeCart?.id, slug });
37
+ });
38
+ }
39
+ export async function applyCheckoutCouponWithClient(sdk, cartId, code, { useAffiliatePath }) {
40
+ if (!useAffiliatePath) {
41
+ const { cart } = await sdk.store.cart.addPromotions(cartId, { promo_codes: [code] });
42
+ return { cart, kind: "promotion" };
43
+ }
44
+ try {
45
+ const response = await sdk.client.fetch(affiliateStorePaths.post.applyAffiliateCoupon, {
46
+ body: { cart_id: cartId, code },
47
+ credentials: "include",
48
+ method: "POST",
49
+ });
50
+ return { kind: "affiliate", response };
51
+ }
52
+ catch (error) {
53
+ if (!(error instanceof FetchError) || error.status !== 404) {
54
+ throw error;
55
+ }
56
+ const { cart } = await sdk.store.cart.addPromotions(cartId, { promo_codes: [code] });
57
+ return { cart, kind: "promotion" };
58
+ }
59
+ }
60
+ export async function resolveCouponLabelsWithClient(sdk, codes) {
61
+ if (codes.length === 0) {
62
+ return { labels: [] };
63
+ }
64
+ return await sdk.client.fetch(affiliateStorePaths.post.resolveCouponLabels, {
65
+ body: { codes },
66
+ credentials: "include",
67
+ method: "POST",
68
+ });
69
+ }
70
+ /** The referral and coupon calls bound to the shop's credentialed client. */
71
+ export function createAffiliateReferral(createClient) {
72
+ return {
73
+ runAffiliateReferralCapture,
74
+ waitForAffiliateReferralCapture,
75
+ removeCapturedAffiliateReferral,
76
+ captureAffiliateReferral: (input) => captureAffiliateReferralWithClient(createClient(), input),
77
+ captureAffiliateReferralAfterCartRefresh: (slug, refreshActiveCart) => captureAffiliateReferralAfterCartRefreshWithClient(createClient(), slug, refreshActiveCart),
78
+ applyCheckoutCoupon: (cartId, code, options) => applyCheckoutCouponWithClient(createClient(), cartId, code, options),
79
+ getAffiliateCartReferral: async (cartId) => await createClient().client.fetch(affiliateStorePaths.get.cartReferral(cartId), {
80
+ credentials: "include",
81
+ method: "GET",
82
+ }),
83
+ resolveCouponLabels: (codes) => resolveCouponLabelsWithClient(createClient(), codes),
84
+ };
85
+ }
@@ -0,0 +1,3 @@
1
+ import type { CredentialedMedusaClient } from "./client.js";
2
+ export declare function startMedusaBrowserSessionWithClient(sdk: CredentialedMedusaClient, token: string): Promise<void>;
3
+ export declare function endMedusaBrowserSessionWithClient(sdk: CredentialedMedusaClient): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import { FetchError } from "@medusajs/js-sdk";
2
+ // Cart, payment-session and completion calls go to Medusa straight from the
3
+ // browser, where the storefront's HttpOnly JWT cookie is invisible - so
4
+ // without this, Medusa sees every such call as a guest and cannot tell a
5
+ // logged-in customer from a stranger using their email. A Medusa session
6
+ // cookie on the backend host gives those calls the customer's identity. The
7
+ // JWT is handed over exactly once here and never stored in the browser.
8
+ export async function startMedusaBrowserSessionWithClient(sdk, token) {
9
+ await sdk.client.fetch("/auth/session", {
10
+ method: "POST",
11
+ headers: { authorization: `Bearer ${token}` },
12
+ });
13
+ }
14
+ // A 401 means Medusa no longer has the session (expired, or the backend
15
+ // restarted with its in-memory session store) - the goal of ending it is
16
+ // already reached, so logout must go on instead of failing for good.
17
+ export async function endMedusaBrowserSessionWithClient(sdk) {
18
+ try {
19
+ await sdk.auth.logout();
20
+ }
21
+ catch (error) {
22
+ if (error instanceof FetchError && error.status === 401) {
23
+ return;
24
+ }
25
+ throw error;
26
+ }
27
+ }
@@ -0,0 +1,36 @@
1
+ import type { HttpTypes } from "@medusajs/types";
2
+ import type { StorefrontActions } from "./actions.js";
3
+ import type { BrowserAffiliateReferral } from "./affiliate-referral.js";
4
+ import type { CredentialedMedusaClient } from "./client.js";
5
+ interface CartMutationDependencies {
6
+ actions: Pick<StorefrontActions, "applyCustomerDefaultsToActiveCart" | "clearCartId" | "fetchPluginFeatures" | "getActiveCart" | "syncCartId">;
7
+ createClient: () => CredentialedMedusaClient;
8
+ referral: Pick<BrowserAffiliateReferral, "applyCheckoutCoupon" | "waitForAffiliateReferralCapture">;
9
+ reportCheckoutError: (error: unknown, context?: Record<string, unknown>) => void;
10
+ }
11
+ export declare function createCartMutations({ actions, createClient, referral, reportCheckoutError }: CartMutationDependencies): {
12
+ createCart: (data: HttpTypes.StoreCreateCart) => Promise<HttpTypes.StoreCart>;
13
+ replaceRefundedCart(cart: HttpTypes.StoreCart): Promise<HttpTypes.StoreCart | null>;
14
+ addCartLineItem: (cartId: string, data: HttpTypes.StoreAddCartLineItem) => Promise<HttpTypes.StoreCart>;
15
+ updateCartLineItem(cartId: string, lineItemId: string, data: HttpTypes.StoreUpdateCartLineItem): Promise<HttpTypes.StoreCart>;
16
+ removeCartLineItem(cartId: string, lineItemId: string): Promise<HttpTypes.StoreLineItemDeleteResponse>;
17
+ updateCart(cartId: string, data: HttpTypes.StoreUpdateCart): Promise<HttpTypes.StoreCart>;
18
+ addCartShippingMethod(cartId: string, data: HttpTypes.StoreAddCartShippingMethods): Promise<HttpTypes.StoreCart>;
19
+ addCartPromotion(cartId: string, data: HttpTypes.StoreCartAddPromotion): Promise<HttpTypes.StoreCart>;
20
+ removeCartPromotion(cartId: string, data: HttpTypes.StoreCartRemovePromotion): Promise<HttpTypes.StoreCart>;
21
+ initiateCartPayment(cartId: string, data: HttpTypes.StoreInitializePaymentSession): Promise<HttpTypes.StorePaymentCollection>;
22
+ checkCartCompletion(cartId: string): Promise<{
23
+ cart_id: string;
24
+ ready: true;
25
+ }>;
26
+ completeCart(cartId: string): Promise<HttpTypes.StoreCompleteCartResponse>;
27
+ /** Adds one unit of a variant, creating the cart first when there is none. */
28
+ addProductToCart({ activeCart, quantity, regionId, variantId }: {
29
+ activeCart: Pick<HttpTypes.StoreCart, "id"> | null;
30
+ quantity?: number;
31
+ regionId: string;
32
+ variantId: string;
33
+ }): Promise<Pick<HttpTypes.StoreCart, "id">>;
34
+ };
35
+ export type BrowserCartMutations = ReturnType<typeof createCartMutations>;
36
+ export {};