create-nextblock 0.13.9 → 0.13.11

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 (260) hide show
  1. package/docker-template/.dockerignore +23 -23
  2. package/docker-template/.env.docker.example +69 -69
  3. package/docker-template/docker/db/init/99-jwt.sql +6 -6
  4. package/docker-template/docker/db/init/99-roles.sql +25 -25
  5. package/docker-template/docker/kong/kong.yml +112 -112
  6. package/docker-template/scripts/docker-setup.mjs +310 -310
  7. package/libs/db/tsconfig.lib.json +3 -3
  8. package/libs/editor/tsconfig.lib.json +3 -3
  9. package/libs/ui/tsconfig.lib.json +3 -3
  10. package/libs/utils/tsconfig.json +3 -3
  11. package/package.json +1 -1
  12. package/project.json +19 -19
  13. package/templates/nextblock-template/.browserslistrc +11 -11
  14. package/templates/nextblock-template/.dockerignore +23 -23
  15. package/templates/nextblock-template/README.md +34 -34
  16. package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -9
  17. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -27
  18. package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
  19. package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
  20. package/templates/nextblock-template/app/ToasterProvider.tsx +17 -17
  21. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
  22. package/templates/nextblock-template/app/actions/consent.ts +57 -57
  23. package/templates/nextblock-template/app/actions/postActions.ts +146 -146
  24. package/templates/nextblock-template/app/actions/productGridActions.ts +40 -40
  25. package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
  26. package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
  27. package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
  28. package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
  29. package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
  30. package/templates/nextblock-template/app/api/cms/ecommerce/product-picker/route.ts +151 -151
  31. package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
  32. package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
  33. package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +3490 -3490
  34. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +5749 -5609
  35. package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
  36. package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
  37. package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
  38. package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
  39. package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
  40. package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
  41. package/templates/nextblock-template/app/api/search/route.ts +43 -43
  42. package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +441 -441
  43. package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
  44. package/templates/nextblock-template/app/cart/page.tsx +7 -7
  45. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +10 -1
  46. package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +5 -1
  47. package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
  48. package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +55 -24
  49. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
  50. package/templates/nextblock-template/app/cms/blocks/components/MultiEntityPicker.tsx +263 -251
  51. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
  52. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +90 -90
  53. package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
  54. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
  55. package/templates/nextblock-template/app/cms/components/EcommerceActiveContext.tsx +27 -27
  56. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
  57. package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
  58. package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
  59. package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
  60. package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
  61. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
  62. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
  63. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
  64. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
  65. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
  66. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
  67. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
  68. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
  69. package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
  70. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
  71. package/templates/nextblock-template/app/cms/interactions/EmailRecipientsInput.tsx +189 -189
  72. package/templates/nextblock-template/app/cms/layout.tsx +73 -73
  73. package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
  74. package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
  75. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  76. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
  77. package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
  78. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
  79. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
  80. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
  81. package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
  82. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
  83. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
  84. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
  85. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
  86. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
  87. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -19
  88. package/templates/nextblock-template/app/cms/revisions/utils.ts +132 -132
  89. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
  90. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
  91. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
  92. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +497 -497
  93. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
  94. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
  95. package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
  96. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
  97. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
  98. package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
  99. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageDetectionPanel.tsx +188 -188
  100. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  101. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
  102. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
  103. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -20
  104. package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
  105. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
  106. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  107. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -122
  108. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
  109. package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
  110. package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
  111. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
  112. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
  113. package/templates/nextblock-template/app/cms/users/components/CreateUserForm.tsx +217 -217
  114. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
  115. package/templates/nextblock-template/app/cms/users/new/page.tsx +44 -44
  116. package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
  117. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
  118. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
  119. package/templates/nextblock-template/app/profile/account-links.ts +22 -22
  120. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
  121. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
  122. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
  123. package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
  124. package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
  125. package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
  126. package/templates/nextblock-template/app/setup/layout.tsx +13 -13
  127. package/templates/nextblock-template/app/setup/page.tsx +111 -111
  128. package/templates/nextblock-template/app/sitemap.ts +130 -130
  129. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
  130. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
  131. package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
  132. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
  133. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
  134. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
  135. package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
  136. package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
  137. package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
  138. package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
  139. package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
  140. package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
  141. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
  142. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  143. package/templates/nextblock-template/components/blocks/ProductGridClient.tsx +114 -114
  144. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
  145. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +8 -1
  146. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +249 -249
  147. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +29 -23
  148. package/templates/nextblock-template/components/blocks/types.ts +7 -7
  149. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  150. package/templates/nextblock-template/components/form-message.tsx +32 -32
  151. package/templates/nextblock-template/components/media/YouTubeFacade.tsx +69 -0
  152. package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -0
  153. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
  154. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
  155. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
  156. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
  157. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -464
  158. package/templates/nextblock-template/components/submit-button.tsx +23 -23
  159. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  160. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
  161. package/templates/nextblock-template/context/AuthContext.tsx +23 -23
  162. package/templates/nextblock-template/context/language-rest-client.ts +32 -32
  163. package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
  164. package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
  165. package/templates/nextblock-template/docker/kong/kong.yml +112 -112
  166. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
  167. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
  168. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -202
  169. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +246 -246
  170. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +176 -176
  171. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
  172. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
  173. package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -173
  174. package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +170 -170
  175. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +151 -151
  176. package/templates/nextblock-template/docs/README.md +39 -39
  177. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
  178. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
  179. package/templates/nextblock-template/index.d.ts +7 -7
  180. package/templates/nextblock-template/lib/app-secrets.ts +39 -39
  181. package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
  182. package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
  183. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
  184. package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
  185. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +78 -78
  186. package/templates/nextblock-template/lib/blocks/README.md +13 -13
  187. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +21 -1
  188. package/templates/nextblock-template/lib/blocks/product-grid-data.ts +210 -210
  189. package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
  190. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -2243
  191. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
  192. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
  193. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
  194. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
  195. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
  196. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
  197. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
  198. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
  199. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
  200. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
  201. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
  202. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
  203. package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
  204. package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
  205. package/templates/nextblock-template/lib/email/branding.ts +76 -76
  206. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
  207. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
  208. package/templates/nextblock-template/lib/full-backup/server.ts +743 -743
  209. package/templates/nextblock-template/lib/i18n/country-languages.ts +247 -247
  210. package/templates/nextblock-template/lib/i18n/detection.test.ts +197 -197
  211. package/templates/nextblock-template/lib/i18n/detection.ts +192 -192
  212. package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
  213. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +54 -54
  214. package/templates/nextblock-template/lib/media/youtube.ts +81 -0
  215. package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
  216. package/templates/nextblock-template/lib/onboarding/status.ts +222 -222
  217. package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
  218. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
  219. package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
  220. package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
  221. package/templates/nextblock-template/lib/privacy/types.ts +69 -69
  222. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
  223. package/templates/nextblock-template/lib/promotions/server.ts +741 -741
  224. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
  225. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
  226. package/templates/nextblock-template/lib/search/types.ts +27 -27
  227. package/templates/nextblock-template/lib/setup/actions.ts +460 -460
  228. package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
  229. package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
  230. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +87 -82
  231. package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
  232. package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
  233. package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
  234. package/templates/nextblock-template/lib/setup/types.ts +18 -18
  235. package/templates/nextblock-template/lib/site-url.ts +48 -48
  236. package/templates/nextblock-template/lib/storage/provider.ts +66 -66
  237. package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
  238. package/templates/nextblock-template/lib/updates/check-upstream.ts +441 -441
  239. package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
  240. package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
  241. package/templates/nextblock-template/next-env.d.ts +1 -1
  242. package/templates/nextblock-template/package.json +1 -1
  243. package/templates/nextblock-template/postcss.config.js +6 -6
  244. package/templates/nextblock-template/scripts/backup.js +115 -115
  245. package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
  246. package/templates/nextblock-template/scripts/restore.js +385 -385
  247. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
  248. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
  249. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
  250. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
  251. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
  252. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
  253. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
  254. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
  255. package/templates/nextblock-template/tailwind.config.js +25 -25
  256. package/templates/nextblock-template/tools/build-migrate.mjs +209 -209
  257. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
  258. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  259. package/templates/nextblock-template/types/jsdom.d.ts +6 -6
  260. package/tsconfig.base.json +3 -3
@@ -1,364 +1,364 @@
1
- # 02 Ecommerce Capabilities
2
-
3
- ## Scope and Source of Truth
4
-
5
- The commerce feature set is implemented across:
6
-
7
- - `libs/ecommerce/src/lib/*`
8
- - `libs/db/src/supabase/migrations/00000000000003` through `00000000000006`
9
- - `apps/nextblock/app/api/checkout/route.ts`
10
- - `apps/nextblock/app/api/webhooks/*`
11
- - `apps/nextblock/app/cms/products`, `orders`, `shipping`, `payments`, and
12
- `settings/taxes`
13
-
14
- In workspace code, the developer-facing import paths are:
15
-
16
- - `@nextblock-cms/ecommerce`
17
- - `@nextblock-cms/ecommerce/server`
18
- - `@nextblock-cms/ecommerce/actions`
19
-
20
- One packaging discrepancy exists today: `libs/ecommerce/package.json` is still
21
- named `@nextblock-cms/ecom`, while the workspace and CLI activation flow expose
22
- the package through the `@nextblock-cms/ecommerce` alias.
23
-
24
- ## Commerce Data Model
25
-
26
- The commerce schema spans:
27
-
28
- - Catalog: `products`, `product_media`, `product_attributes`,
29
- `product_attribute_terms`, `product_variants`,
30
- `variant_attribute_mapping`, `categories`, `product_categories`
31
- (categories and their translations were added by migrations
32
- `00000000000019` and `00000000000020`)
33
- - Inventory and licensing: `inventory_items`, `package_activations`,
34
- `freemius_plans`, `freemius_pricing`
35
- - Checkout and fulfillment: `orders`, `order_items`, `shipping_zones`,
36
- `shipping_zone_locations`, `shipping_zone_methods`, `tax_rates`,
37
- `currencies`
38
-
39
- `products` can be physical or digital. The current provider selection logic
40
- resolves:
41
-
42
- - physical -> Stripe
43
- - digital -> Freemius
44
-
45
- Mixed-provider carts are rejected by `app/api/checkout/route.ts`.
46
-
47
- ## Multi-Currency
48
-
49
- The multi-currency implementation is real and fairly deep.
50
-
51
- ### Store currencies
52
-
53
- `currencies` stores:
54
-
55
- - ISO code and symbol
56
- - exchange rate relative to the current store default
57
- - default and active flags
58
- - rounding mode, rounding increment, and optional charm ending
59
- - automatic FX refresh flag
60
- - automatic product price sync flag
61
- - last exchange-rate source and refresh timestamp
62
-
63
- Supported rounding modes in code are:
64
-
65
- - `none`
66
- - `nearest`
67
- - `up`
68
- - `down`
69
- - `charm`
70
-
71
- ### Product and variant pricing
72
-
73
- Products and variants support:
74
-
75
- - legacy single-currency `price` and `sale_price`
76
- - multi-currency `prices` and `sale_prices`
77
-
78
- The pricing helpers resolve amounts by:
79
-
80
- 1. looking for an explicit amount in the selected currency
81
- 2. falling back to the base price
82
- 3. converting from the default currency when store-managed auto-sync pricing is
83
- enabled
84
-
85
- The CMS product editor respects that distinction. Store-managed currencies can
86
- be displayed in forms, but their saved overrides are stripped before
87
- persisting.
88
-
89
- ### Scheduled sales, price changes, and promotions
90
-
91
- Products and variants carry a scheduled-pricing layer (migration
92
- `00000000000025_add_sale_schedule_columns.sql`):
93
-
94
- - `sale_start_at` / `sale_end_at` — the time window during which `sale_price` /
95
- `sale_prices` apply. Both null = always-on (back-compat with static sales).
96
- - `scheduled_price` / `scheduled_prices` / `scheduled_price_at` — a pending,
97
- permanent regular-price change applied once `scheduled_price_at` passes
98
- (bulk/Stripe-oriented; Freemius regular prices are owned by Freemius).
99
- - `product_freemius_sale_coupons` — maps a product to an auto-generated,
100
- time-bounded Freemius coupon so a scheduled Freemius sale is actually enforced
101
- at Freemius-hosted checkout (Freemius enforces the coupon's start/end dates).
102
-
103
- **Enforcement is read-time, not cron-driven.** The helpers in
104
- `libs/ecommerce/src/lib/currency.ts` decide what applies *now*:
105
-
106
- - `isSaleWindowActive({ saleStartAt, saleEndAt, now })`
107
- - `resolveEffectivePriceForCurrency({ ..., saleStartAt, saleEndAt, scheduledPrice*, now })`
108
- — wraps `resolvePriceForCurrency`, gating the sale by its window and swapping in
109
- a due scheduled price. A sale outside its window resolves to `sale_price: null`.
110
-
111
- Every place that computes a payable or displayed amount goes through the
112
- window-aware helper: checkout providers (`providers/stripe.ts`,
113
- `providers/freemius.ts`), cart/tax/coupon math, and storefront components
114
- (`ProductCard`, `FeaturedProduct`, `ProductDetailsLayout`, UCP). The CMS edit
115
- form exposes the window per-product and per-variant (`SaleScheduleFields`); the
116
- bulk **Promotions** admin (`/cms/promotions`, `apps/nextblock/lib/promotions/`)
117
- imports/exports sales and price changes via CSV.
118
-
119
- > **Gotchas for future agents (these caused real revert/display bugs):**
120
- >
121
- > 1. **`generateVariantDrafts` (`variation-utils.ts`) must carry over every
122
- > per-variant field**, including `sale_start_at`/`sale_end_at`. It re-runs on
123
- > editor mount/attribute change; an omitted field resets to null and is then
124
- > autosaved away ("dates revert after publish").
125
- > 2. **Storefront block mappers must pass the window through** on *both* the
126
- > product and each variant — `ProductGridBlock`, `FeaturedProductBlock`,
127
- > `app/product/[slug]/page.tsx`. If the window is dropped, `isSaleWindowActive`
128
- > sees both bounds null and treats the sale as always-on, so an inactive sale
129
- > price shows (e.g. a "$25 – $32" range before the sale starts).
130
- > 3. **`getVariantEffectivePriceRange` is window-aware** — pass
131
- > `sale_start_at`/`sale_end_at` per variant or it ignores the schedule.
132
- > 4. **Persisting on save/publish does not rely on the RPC.** Products are
133
- > written via `upsert_product_with_variants`, but some databases run a stale
134
- > copy of that function. `persistProductSaleSchedule` (`product-actions.ts`)
135
- > writes the window columns with a direct `update` right after the RPC (same
136
- > pattern as `persistProductTaxability`), matching variants by SKU.
137
- > 5. **Autosave must not `reset()` the form or revalidate the edit route.** The
138
- > edit-form autosave (`ProductForm.tsx`) uses a serialized-snapshot guard to
139
- > avoid a render loop; `updateProductAction` writes only the draft (no
140
- > `revalidatePath`). Re-rendering mid-edit resets native datetime inputs.
141
-
142
- ### FX sync and rebasing
143
-
144
- `libs/ecommerce/src/lib/currency-sync.ts` implements two separate operations:
145
-
146
- - `syncStoreCurrencyRates()`: pulls fresh FX rates from `https://api.frankfurter.dev`
147
- unless `FX_API_BASE_URL` overrides the provider.
148
- - `rebaseStoreCurrencyExchangeRates()`: when an admin changes the default
149
- currency, every stored rate is rebased so the new default becomes `1`.
150
-
151
- The app exposes both through:
152
-
153
- - CMS currency settings actions in
154
- `apps/nextblock/app/cms/settings/currencies/actions.ts`
155
- - `GET /api/cron/sync-currencies`, guarded by `CRON_SECRET`
156
-
157
- ## Tax Calculation
158
-
159
- Tax behavior is driven by the `ecommerce_inventory_settings` site setting,
160
- loaded through `getEcommerceInventorySettings()`.
161
-
162
- The current settings shape is:
163
-
164
- - `trackQuantities`
165
- - `enableTaxes`
166
- - `taxCalculationMode`
167
-
168
- Supported tax modes are:
169
-
170
- - `manual`
171
- - `automatic`
172
-
173
- ### Manual mode
174
-
175
- Manual mode uses `tax_rates` rows keyed by country and optional state/province.
176
- Multiple rows can exist for the same jurisdiction, so stacked taxes such as GST
177
- plus PST are supported.
178
-
179
- During checkout:
180
-
181
- - only taxable products are included
182
- - the destination is normalized from shipping or billing data
183
- - matching `tax_rates` are loaded
184
- - tax lines are calculated and stored in `orders.tax_details`
185
-
186
- ### Automatic mode
187
-
188
- Automatic mode defers final tax calculation to Stripe Tax.
189
-
190
- In this mode:
191
-
192
- - checkout still records a tax intent in `orders.tax_details`
193
- - the Stripe provider marks product and shipping tax codes on line items
194
- - the webhook resync step replaces provisional tax data with finalized Stripe
195
- checkout data
196
-
197
- ## Shipping Zones and Rate Resolution
198
-
199
- Shipping is backed by:
200
-
201
- - `shipping_zones`
202
- - `shipping_zone_locations`
203
- - `shipping_zone_methods`
204
-
205
- Each method stores:
206
-
207
- - base amount and currency
208
- - localized names
209
- - per-currency amount maps and threshold maps
210
- - `currency_pricing_mode` of `auto` or `manual`
211
-
212
- ### Current resolver behavior
213
-
214
- `libs/ecommerce/src/lib/shipping/resolver.ts` currently:
215
-
216
- 1. loads active currencies
217
- 2. queries zone locations by destination country
218
- 3. prefers a state match when one exists
219
- 4. otherwise prefers a country-wide match
220
- 5. otherwise falls back to the first zone by `priority_order`
221
- 6. filters methods by the cart total and minimum threshold
222
- 7. converts method prices into the shopper currency
223
- 8. returns only the cheapest valid method
224
-
225
- Important implementation detail: `shipping_zone_locations.postal_code` exists in
226
- schema, but the current resolver does not yet use postal code matching during
227
- runtime resolution.
228
-
229
- The storefront calls this through
230
- `libs/ecommerce/src/lib/server-actions/shipping-actions.ts`.
231
-
232
- ## Stripe Integration
233
-
234
- Stripe is the current payment flow for physical products.
235
-
236
- ### Checkout flow
237
-
238
- `app/api/checkout/route.ts`:
239
-
240
- - verifies the ecommerce package is active
241
- - rejects carts without provider-aware items
242
- - rejects mixed-provider carts
243
- - requires a billing address
244
- - resolves the provider through `getPaymentProvider()`
245
-
246
- `StripeProvider.createCheckoutSession()` then:
247
-
248
- - loads currencies and store settings
249
- - validates products and variants against the database
250
- - validates inventory before session creation when quantity tracking is enabled
251
- - resolves shipping cost from the selected shipping method
252
- - calculates tax in manual or automatic mode
253
- - upserts a Stripe customer when an email is available
254
- - inserts a pending `orders` row plus `order_items`
255
- - stores currency, subtotal, shipping, tax, and exchange-rate data
256
- - creates the Stripe Checkout Session and stores `stripe_session_id`
257
-
258
- ### Webhook flow
259
-
260
- `app/api/webhooks/stripe/route.ts` passes the raw body to
261
- `handleStripeWebhook()`.
262
-
263
- On `checkout.session.completed`, the sync layer:
264
-
265
- - reloads the session with tax breakdown details
266
- - finds the existing order
267
- - stores payment intent, customer details, and finalized totals
268
- - normalizes tax details from Stripe
269
- - updates saved customer addresses
270
- - assigns invoice metadata
271
- - applies inventory deduction
272
-
273
- ## Freemius Licensing and Product Sync
274
-
275
- Freemius currently handles digital-product checkout and product synchronization.
276
-
277
- ### Checkout behavior
278
-
279
- `FreemiusProvider.createCheckoutSession()`:
280
-
281
- - only allows one item per checkout
282
- - loads the product from Supabase
283
- - requires `freemius_product_id` and `freemius_plan_id`
284
- - resolves pricing in the chosen currency
285
- - inserts a pending order and order item
286
- - optionally syncs default addresses and profile fields for the current user
287
- - builds a Freemius checkout URL, including sandbox parameters when enabled
288
-
289
- Supported credential sources include:
290
-
291
- - product-scoped JSON map
292
- - single-product sandbox overrides
293
- - single-product env vars
294
- - legacy shared env vars
295
-
296
- ### Product sync
297
-
298
- `syncFreemiusProductsToSupabase()` and `syncSingleFreemiusProduct()`:
299
-
300
- - call the Freemius API with signed requests
301
- - fetch plugins, plans, and pricing
302
- - upsert digital products into `products`
303
- - upsert related `freemius_plans` and `freemius_pricing`
304
-
305
- These flows are surfaced in the CMS product actions and the sandbox reset route.
306
-
307
- ### Current webhook limitation
308
-
309
- `app/api/webhooks/freemius/route.ts` currently verifies the webhook signature
310
- and acknowledges selected event types, but it does not yet reconcile license or
311
- order state back into the local database.
312
-
313
- ## Inventory Management and Fulfillment
314
-
315
- Inventory behavior is controlled by `trackQuantities`.
316
-
317
- When tracking is enabled:
318
-
319
- - checkout validates requested quantity against `inventory_items`
320
- - if a SKU is not yet cached there, product or variant stock fields are used as
321
- fallback
322
- - a paid order triggers `apply_order_inventory_deduction()`
323
-
324
- The deduction flow is resilient:
325
-
326
- - first it calls the database RPC `apply_order_inventory_deduction`
327
- - if that fails, it falls back to a direct SQL reconciliation path using
328
- `POSTGRES_URL` or `DATABASE_URL`
329
-
330
- Order statuses currently supported in code are:
331
-
332
- - `pending`
333
- - `paid`
334
- - `shipped`
335
- - `cancelled`
336
- - `refunded`
337
-
338
- Manual CMS order status changes also trigger invoice assignment and inventory
339
- deduction when an order is moved to `paid`.
340
-
341
- ## Invoice and Order Presentation
342
-
343
- The order/invoice layer includes:
344
-
345
- - stable invoice numbering through database functions
346
- - `invoice_settings` in `site_settings`
347
- - printable invoice presentation data via `invoice-server.ts`
348
- - UI components such as `InvoiceDocument` and `InvoiceViewerShell`
349
- - customer order history and invoice access through `customer-orders.ts`
350
-
351
- ## CMS Commerce Surfaces
352
-
353
- The active ecommerce CMS surface includes:
354
-
355
- - product list, create, edit, media, attribute, and variation management
356
- - inventory management
357
- - orders list and detail management
358
- - shipping zones and shipping rate management
359
- - payment-provider enablement
360
- - tax settings and manual tax-rate management
361
- - currency settings under `/cms/settings/currencies`
362
-
363
- The CMS shell only exposes these store sections when the ecommerce package is
364
- reported as active.
1
+ # 02 Ecommerce Capabilities
2
+
3
+ ## Scope and Source of Truth
4
+
5
+ The commerce feature set is implemented across:
6
+
7
+ - `libs/ecommerce/src/lib/*`
8
+ - `libs/db/src/supabase/migrations/00000000000003` through `00000000000006`
9
+ - `apps/nextblock/app/api/checkout/route.ts`
10
+ - `apps/nextblock/app/api/webhooks/*`
11
+ - `apps/nextblock/app/cms/products`, `orders`, `shipping`, `payments`, and
12
+ `settings/taxes`
13
+
14
+ In workspace code, the developer-facing import paths are:
15
+
16
+ - `@nextblock-cms/ecommerce`
17
+ - `@nextblock-cms/ecommerce/server`
18
+ - `@nextblock-cms/ecommerce/actions`
19
+
20
+ One packaging discrepancy exists today: `libs/ecommerce/package.json` is still
21
+ named `@nextblock-cms/ecom`, while the workspace and CLI activation flow expose
22
+ the package through the `@nextblock-cms/ecommerce` alias.
23
+
24
+ ## Commerce Data Model
25
+
26
+ The commerce schema spans:
27
+
28
+ - Catalog: `products`, `product_media`, `product_attributes`,
29
+ `product_attribute_terms`, `product_variants`,
30
+ `variant_attribute_mapping`, `categories`, `product_categories`
31
+ (categories and their translations were added by migrations
32
+ `00000000000019` and `00000000000020`)
33
+ - Inventory and licensing: `inventory_items`, `package_activations`,
34
+ `freemius_plans`, `freemius_pricing`
35
+ - Checkout and fulfillment: `orders`, `order_items`, `shipping_zones`,
36
+ `shipping_zone_locations`, `shipping_zone_methods`, `tax_rates`,
37
+ `currencies`
38
+
39
+ `products` can be physical or digital. The current provider selection logic
40
+ resolves:
41
+
42
+ - physical -> Stripe
43
+ - digital -> Freemius
44
+
45
+ Mixed-provider carts are rejected by `app/api/checkout/route.ts`.
46
+
47
+ ## Multi-Currency
48
+
49
+ The multi-currency implementation is real and fairly deep.
50
+
51
+ ### Store currencies
52
+
53
+ `currencies` stores:
54
+
55
+ - ISO code and symbol
56
+ - exchange rate relative to the current store default
57
+ - default and active flags
58
+ - rounding mode, rounding increment, and optional charm ending
59
+ - automatic FX refresh flag
60
+ - automatic product price sync flag
61
+ - last exchange-rate source and refresh timestamp
62
+
63
+ Supported rounding modes in code are:
64
+
65
+ - `none`
66
+ - `nearest`
67
+ - `up`
68
+ - `down`
69
+ - `charm`
70
+
71
+ ### Product and variant pricing
72
+
73
+ Products and variants support:
74
+
75
+ - legacy single-currency `price` and `sale_price`
76
+ - multi-currency `prices` and `sale_prices`
77
+
78
+ The pricing helpers resolve amounts by:
79
+
80
+ 1. looking for an explicit amount in the selected currency
81
+ 2. falling back to the base price
82
+ 3. converting from the default currency when store-managed auto-sync pricing is
83
+ enabled
84
+
85
+ The CMS product editor respects that distinction. Store-managed currencies can
86
+ be displayed in forms, but their saved overrides are stripped before
87
+ persisting.
88
+
89
+ ### Scheduled sales, price changes, and promotions
90
+
91
+ Products and variants carry a scheduled-pricing layer (migration
92
+ `00000000000025_add_sale_schedule_columns.sql`):
93
+
94
+ - `sale_start_at` / `sale_end_at` — the time window during which `sale_price` /
95
+ `sale_prices` apply. Both null = always-on (back-compat with static sales).
96
+ - `scheduled_price` / `scheduled_prices` / `scheduled_price_at` — a pending,
97
+ permanent regular-price change applied once `scheduled_price_at` passes
98
+ (bulk/Stripe-oriented; Freemius regular prices are owned by Freemius).
99
+ - `product_freemius_sale_coupons` — maps a product to an auto-generated,
100
+ time-bounded Freemius coupon so a scheduled Freemius sale is actually enforced
101
+ at Freemius-hosted checkout (Freemius enforces the coupon's start/end dates).
102
+
103
+ **Enforcement is read-time, not cron-driven.** The helpers in
104
+ `libs/ecommerce/src/lib/currency.ts` decide what applies *now*:
105
+
106
+ - `isSaleWindowActive({ saleStartAt, saleEndAt, now })`
107
+ - `resolveEffectivePriceForCurrency({ ..., saleStartAt, saleEndAt, scheduledPrice*, now })`
108
+ — wraps `resolvePriceForCurrency`, gating the sale by its window and swapping in
109
+ a due scheduled price. A sale outside its window resolves to `sale_price: null`.
110
+
111
+ Every place that computes a payable or displayed amount goes through the
112
+ window-aware helper: checkout providers (`providers/stripe.ts`,
113
+ `providers/freemius.ts`), cart/tax/coupon math, and storefront components
114
+ (`ProductCard`, `FeaturedProduct`, `ProductDetailsLayout`, UCP). The CMS edit
115
+ form exposes the window per-product and per-variant (`SaleScheduleFields`); the
116
+ bulk **Promotions** admin (`/cms/promotions`, `apps/nextblock/lib/promotions/`)
117
+ imports/exports sales and price changes via CSV.
118
+
119
+ > **Gotchas for future agents (these caused real revert/display bugs):**
120
+ >
121
+ > 1. **`generateVariantDrafts` (`variation-utils.ts`) must carry over every
122
+ > per-variant field**, including `sale_start_at`/`sale_end_at`. It re-runs on
123
+ > editor mount/attribute change; an omitted field resets to null and is then
124
+ > autosaved away ("dates revert after publish").
125
+ > 2. **Storefront block mappers must pass the window through** on *both* the
126
+ > product and each variant — `ProductGridBlock`, `FeaturedProductBlock`,
127
+ > `app/product/[slug]/page.tsx`. If the window is dropped, `isSaleWindowActive`
128
+ > sees both bounds null and treats the sale as always-on, so an inactive sale
129
+ > price shows (e.g. a "$25 – $32" range before the sale starts).
130
+ > 3. **`getVariantEffectivePriceRange` is window-aware** — pass
131
+ > `sale_start_at`/`sale_end_at` per variant or it ignores the schedule.
132
+ > 4. **Persisting on save/publish does not rely on the RPC.** Products are
133
+ > written via `upsert_product_with_variants`, but some databases run a stale
134
+ > copy of that function. `persistProductSaleSchedule` (`product-actions.ts`)
135
+ > writes the window columns with a direct `update` right after the RPC (same
136
+ > pattern as `persistProductTaxability`), matching variants by SKU.
137
+ > 5. **Autosave must not `reset()` the form or revalidate the edit route.** The
138
+ > edit-form autosave (`ProductForm.tsx`) uses a serialized-snapshot guard to
139
+ > avoid a render loop; `updateProductAction` writes only the draft (no
140
+ > `revalidatePath`). Re-rendering mid-edit resets native datetime inputs.
141
+
142
+ ### FX sync and rebasing
143
+
144
+ `libs/ecommerce/src/lib/currency-sync.ts` implements two separate operations:
145
+
146
+ - `syncStoreCurrencyRates()`: pulls fresh FX rates from `https://api.frankfurter.dev`
147
+ unless `FX_API_BASE_URL` overrides the provider.
148
+ - `rebaseStoreCurrencyExchangeRates()`: when an admin changes the default
149
+ currency, every stored rate is rebased so the new default becomes `1`.
150
+
151
+ The app exposes both through:
152
+
153
+ - CMS currency settings actions in
154
+ `apps/nextblock/app/cms/settings/currencies/actions.ts`
155
+ - `GET /api/cron/sync-currencies`, guarded by `CRON_SECRET`
156
+
157
+ ## Tax Calculation
158
+
159
+ Tax behavior is driven by the `ecommerce_inventory_settings` site setting,
160
+ loaded through `getEcommerceInventorySettings()`.
161
+
162
+ The current settings shape is:
163
+
164
+ - `trackQuantities`
165
+ - `enableTaxes`
166
+ - `taxCalculationMode`
167
+
168
+ Supported tax modes are:
169
+
170
+ - `manual`
171
+ - `automatic`
172
+
173
+ ### Manual mode
174
+
175
+ Manual mode uses `tax_rates` rows keyed by country and optional state/province.
176
+ Multiple rows can exist for the same jurisdiction, so stacked taxes such as GST
177
+ plus PST are supported.
178
+
179
+ During checkout:
180
+
181
+ - only taxable products are included
182
+ - the destination is normalized from shipping or billing data
183
+ - matching `tax_rates` are loaded
184
+ - tax lines are calculated and stored in `orders.tax_details`
185
+
186
+ ### Automatic mode
187
+
188
+ Automatic mode defers final tax calculation to Stripe Tax.
189
+
190
+ In this mode:
191
+
192
+ - checkout still records a tax intent in `orders.tax_details`
193
+ - the Stripe provider marks product and shipping tax codes on line items
194
+ - the webhook resync step replaces provisional tax data with finalized Stripe
195
+ checkout data
196
+
197
+ ## Shipping Zones and Rate Resolution
198
+
199
+ Shipping is backed by:
200
+
201
+ - `shipping_zones`
202
+ - `shipping_zone_locations`
203
+ - `shipping_zone_methods`
204
+
205
+ Each method stores:
206
+
207
+ - base amount and currency
208
+ - localized names
209
+ - per-currency amount maps and threshold maps
210
+ - `currency_pricing_mode` of `auto` or `manual`
211
+
212
+ ### Current resolver behavior
213
+
214
+ `libs/ecommerce/src/lib/shipping/resolver.ts` currently:
215
+
216
+ 1. loads active currencies
217
+ 2. queries zone locations by destination country
218
+ 3. prefers a state match when one exists
219
+ 4. otherwise prefers a country-wide match
220
+ 5. otherwise falls back to the first zone by `priority_order`
221
+ 6. filters methods by the cart total and minimum threshold
222
+ 7. converts method prices into the shopper currency
223
+ 8. returns only the cheapest valid method
224
+
225
+ Important implementation detail: `shipping_zone_locations.postal_code` exists in
226
+ schema, but the current resolver does not yet use postal code matching during
227
+ runtime resolution.
228
+
229
+ The storefront calls this through
230
+ `libs/ecommerce/src/lib/server-actions/shipping-actions.ts`.
231
+
232
+ ## Stripe Integration
233
+
234
+ Stripe is the current payment flow for physical products.
235
+
236
+ ### Checkout flow
237
+
238
+ `app/api/checkout/route.ts`:
239
+
240
+ - verifies the ecommerce package is active
241
+ - rejects carts without provider-aware items
242
+ - rejects mixed-provider carts
243
+ - requires a billing address
244
+ - resolves the provider through `getPaymentProvider()`
245
+
246
+ `StripeProvider.createCheckoutSession()` then:
247
+
248
+ - loads currencies and store settings
249
+ - validates products and variants against the database
250
+ - validates inventory before session creation when quantity tracking is enabled
251
+ - resolves shipping cost from the selected shipping method
252
+ - calculates tax in manual or automatic mode
253
+ - upserts a Stripe customer when an email is available
254
+ - inserts a pending `orders` row plus `order_items`
255
+ - stores currency, subtotal, shipping, tax, and exchange-rate data
256
+ - creates the Stripe Checkout Session and stores `stripe_session_id`
257
+
258
+ ### Webhook flow
259
+
260
+ `app/api/webhooks/stripe/route.ts` passes the raw body to
261
+ `handleStripeWebhook()`.
262
+
263
+ On `checkout.session.completed`, the sync layer:
264
+
265
+ - reloads the session with tax breakdown details
266
+ - finds the existing order
267
+ - stores payment intent, customer details, and finalized totals
268
+ - normalizes tax details from Stripe
269
+ - updates saved customer addresses
270
+ - assigns invoice metadata
271
+ - applies inventory deduction
272
+
273
+ ## Freemius Licensing and Product Sync
274
+
275
+ Freemius currently handles digital-product checkout and product synchronization.
276
+
277
+ ### Checkout behavior
278
+
279
+ `FreemiusProvider.createCheckoutSession()`:
280
+
281
+ - only allows one item per checkout
282
+ - loads the product from Supabase
283
+ - requires `freemius_product_id` and `freemius_plan_id`
284
+ - resolves pricing in the chosen currency
285
+ - inserts a pending order and order item
286
+ - optionally syncs default addresses and profile fields for the current user
287
+ - builds a Freemius checkout URL, including sandbox parameters when enabled
288
+
289
+ Supported credential sources include:
290
+
291
+ - product-scoped JSON map
292
+ - single-product sandbox overrides
293
+ - single-product env vars
294
+ - legacy shared env vars
295
+
296
+ ### Product sync
297
+
298
+ `syncFreemiusProductsToSupabase()` and `syncSingleFreemiusProduct()`:
299
+
300
+ - call the Freemius API with signed requests
301
+ - fetch plugins, plans, and pricing
302
+ - upsert digital products into `products`
303
+ - upsert related `freemius_plans` and `freemius_pricing`
304
+
305
+ These flows are surfaced in the CMS product actions and the sandbox reset route.
306
+
307
+ ### Current webhook limitation
308
+
309
+ `app/api/webhooks/freemius/route.ts` currently verifies the webhook signature
310
+ and acknowledges selected event types, but it does not yet reconcile license or
311
+ order state back into the local database.
312
+
313
+ ## Inventory Management and Fulfillment
314
+
315
+ Inventory behavior is controlled by `trackQuantities`.
316
+
317
+ When tracking is enabled:
318
+
319
+ - checkout validates requested quantity against `inventory_items`
320
+ - if a SKU is not yet cached there, product or variant stock fields are used as
321
+ fallback
322
+ - a paid order triggers `apply_order_inventory_deduction()`
323
+
324
+ The deduction flow is resilient:
325
+
326
+ - first it calls the database RPC `apply_order_inventory_deduction`
327
+ - if that fails, it falls back to a direct SQL reconciliation path using
328
+ `POSTGRES_URL` or `DATABASE_URL`
329
+
330
+ Order statuses currently supported in code are:
331
+
332
+ - `pending`
333
+ - `paid`
334
+ - `shipped`
335
+ - `cancelled`
336
+ - `refunded`
337
+
338
+ Manual CMS order status changes also trigger invoice assignment and inventory
339
+ deduction when an order is moved to `paid`.
340
+
341
+ ## Invoice and Order Presentation
342
+
343
+ The order/invoice layer includes:
344
+
345
+ - stable invoice numbering through database functions
346
+ - `invoice_settings` in `site_settings`
347
+ - printable invoice presentation data via `invoice-server.ts`
348
+ - UI components such as `InvoiceDocument` and `InvoiceViewerShell`
349
+ - customer order history and invoice access through `customer-orders.ts`
350
+
351
+ ## CMS Commerce Surfaces
352
+
353
+ The active ecommerce CMS surface includes:
354
+
355
+ - product list, create, edit, media, attribute, and variation management
356
+ - inventory management
357
+ - orders list and detail management
358
+ - shipping zones and shipping rate management
359
+ - payment-provider enablement
360
+ - tax settings and manual tax-rate management
361
+ - currency settings under `/cms/settings/currencies`
362
+
363
+ The CMS shell only exposes these store sections when the ecommerce package is
364
+ reported as active.