create-nextblock 0.13.10 → 0.13.12

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 (259) 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/CustomBlockEditorPreview.tsx +160 -160
  46. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
  47. package/templates/nextblock-template/app/cms/blocks/components/MultiEntityPicker.tsx +263 -263
  48. package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
  49. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +90 -90
  50. package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
  51. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
  52. package/templates/nextblock-template/app/cms/components/EcommerceActiveContext.tsx +27 -27
  53. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
  54. package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
  55. package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
  56. package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
  57. package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
  58. package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
  59. package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
  60. package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
  61. package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
  62. package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
  63. package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
  64. package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
  65. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
  66. package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
  67. package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
  68. package/templates/nextblock-template/app/cms/interactions/EmailRecipientsInput.tsx +189 -189
  69. package/templates/nextblock-template/app/cms/layout.tsx +73 -73
  70. package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
  71. package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
  72. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +26 -26
  73. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
  74. package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
  75. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
  76. package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
  77. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
  78. package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
  79. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
  80. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
  81. package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
  82. package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
  83. package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
  84. package/templates/nextblock-template/app/cms/revisions/service.ts +19 -19
  85. package/templates/nextblock-template/app/cms/revisions/utils.ts +132 -132
  86. package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
  87. package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
  88. package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
  89. package/templates/nextblock-template/app/cms/settings/cortex-ai/SandboxCortexAiSettingsClient.tsx +497 -497
  90. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
  91. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
  92. package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
  93. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
  94. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
  95. package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
  96. package/templates/nextblock-template/app/cms/settings/languages/components/LanguageDetectionPanel.tsx +188 -188
  97. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  98. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
  99. package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
  100. package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +20 -20
  101. package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
  102. package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
  103. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  104. package/templates/nextblock-template/app/cms/settings/packages/package-card.tsx +122 -122
  105. package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
  106. package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
  107. package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
  108. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
  109. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
  110. package/templates/nextblock-template/app/cms/users/components/CreateUserForm.tsx +217 -217
  111. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
  112. package/templates/nextblock-template/app/cms/users/new/page.tsx +44 -44
  113. package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
  114. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
  115. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
  116. package/templates/nextblock-template/app/profile/account-links.ts +22 -22
  117. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
  118. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
  119. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
  120. package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
  121. package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
  122. package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
  123. package/templates/nextblock-template/app/setup/layout.tsx +13 -13
  124. package/templates/nextblock-template/app/setup/page.tsx +111 -111
  125. package/templates/nextblock-template/app/sitemap.ts +130 -130
  126. package/templates/nextblock-template/components/BlockRenderer.tsx +4 -0
  127. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
  128. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
  129. package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
  130. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
  131. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
  132. package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
  133. package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
  134. package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
  135. package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
  136. package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
  137. package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
  138. package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
  139. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
  140. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  141. package/templates/nextblock-template/components/blocks/ProductGridClient.tsx +114 -114
  142. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
  143. package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +8 -1
  144. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +249 -249
  145. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +3 -0
  146. package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +33 -23
  147. package/templates/nextblock-template/components/blocks/types.ts +7 -7
  148. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  149. package/templates/nextblock-template/components/form-message.tsx +32 -32
  150. package/templates/nextblock-template/components/media/YouTubeFacade.tsx +105 -0
  151. package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -0
  152. package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
  153. package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
  154. package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
  155. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
  156. package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -464
  157. package/templates/nextblock-template/components/submit-button.tsx +23 -23
  158. package/templates/nextblock-template/components/theme-switcher.tsx +8 -8
  159. package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
  160. package/templates/nextblock-template/context/AuthContext.tsx +23 -23
  161. package/templates/nextblock-template/context/language-rest-client.ts +32 -32
  162. package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
  163. package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
  164. package/templates/nextblock-template/docker/kong/kong.yml +112 -112
  165. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
  166. package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
  167. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +202 -202
  168. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +246 -246
  169. package/templates/nextblock-template/docs/06-CLI-AND-SCAFFOLDING.md +176 -176
  170. package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
  171. package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
  172. package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +173 -173
  173. package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +170 -170
  174. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +151 -151
  175. package/templates/nextblock-template/docs/README.md +39 -39
  176. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
  177. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
  178. package/templates/nextblock-template/index.d.ts +7 -7
  179. package/templates/nextblock-template/lib/app-secrets.ts +39 -39
  180. package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
  181. package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
  182. package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
  183. package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
  184. package/templates/nextblock-template/lib/blocks/ProductGridBlock.tsx +78 -78
  185. package/templates/nextblock-template/lib/blocks/README.md +13 -13
  186. package/templates/nextblock-template/lib/blocks/product-grid-data.ts +210 -210
  187. package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
  188. package/templates/nextblock-template/lib/cms-transfer/server.ts +2243 -2243
  189. package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
  190. package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
  191. package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
  192. package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
  193. package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
  194. package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
  195. package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
  196. package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
  197. package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
  198. package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
  199. package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
  200. package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
  201. package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
  202. package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
  203. package/templates/nextblock-template/lib/email/branding.ts +76 -76
  204. package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
  205. package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
  206. package/templates/nextblock-template/lib/full-backup/server.ts +743 -743
  207. package/templates/nextblock-template/lib/i18n/country-languages.ts +247 -247
  208. package/templates/nextblock-template/lib/i18n/detection.test.ts +197 -197
  209. package/templates/nextblock-template/lib/i18n/detection.ts +192 -192
  210. package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
  211. package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +54 -54
  212. package/templates/nextblock-template/lib/media/youtube.ts +99 -0
  213. package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
  214. package/templates/nextblock-template/lib/onboarding/status.ts +222 -222
  215. package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
  216. package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
  217. package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
  218. package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
  219. package/templates/nextblock-template/lib/privacy/types.ts +69 -69
  220. package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
  221. package/templates/nextblock-template/lib/promotions/server.ts +741 -741
  222. package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
  223. package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
  224. package/templates/nextblock-template/lib/search/types.ts +27 -27
  225. package/templates/nextblock-template/lib/setup/actions.ts +460 -460
  226. package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
  227. package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
  228. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +87 -82
  229. package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
  230. package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
  231. package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
  232. package/templates/nextblock-template/lib/setup/types.ts +18 -18
  233. package/templates/nextblock-template/lib/site-url.ts +48 -48
  234. package/templates/nextblock-template/lib/storage/provider.ts +66 -66
  235. package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
  236. package/templates/nextblock-template/lib/updates/check-upstream.ts +441 -441
  237. package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
  238. package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
  239. package/templates/nextblock-template/next-env.d.ts +1 -1
  240. package/templates/nextblock-template/next.config.js +6 -0
  241. package/templates/nextblock-template/package.json +1 -1
  242. package/templates/nextblock-template/postcss.config.js +6 -6
  243. package/templates/nextblock-template/scripts/backup.js +115 -115
  244. package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
  245. package/templates/nextblock-template/scripts/restore.js +385 -385
  246. package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
  247. package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
  248. package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
  249. package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
  250. package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
  251. package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
  252. package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
  253. package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
  254. package/templates/nextblock-template/tailwind.config.js +25 -25
  255. package/templates/nextblock-template/tools/build-migrate.mjs +209 -209
  256. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
  257. package/templates/nextblock-template/tsconfig.tsbuildinfo +1 -1
  258. package/templates/nextblock-template/types/jsdom.d.ts +6 -6
  259. 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.