@mmailaender/convex-creem 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1176 -0
  3. package/dist/client/helpers.d.ts +17 -0
  4. package/dist/client/helpers.d.ts.map +1 -0
  5. package/dist/client/helpers.js +43 -0
  6. package/dist/client/helpers.js.map +1 -0
  7. package/dist/client/index.d.ts +1041 -0
  8. package/dist/client/index.d.ts.map +1 -0
  9. package/dist/client/index.js +1068 -0
  10. package/dist/client/index.js.map +1 -0
  11. package/dist/client/parsers.d.ts +45 -0
  12. package/dist/client/parsers.d.ts.map +1 -0
  13. package/dist/client/parsers.js +138 -0
  14. package/dist/client/parsers.js.map +1 -0
  15. package/dist/client/polyfill.d.ts +2 -0
  16. package/dist/client/polyfill.d.ts.map +1 -0
  17. package/dist/client/polyfill.js +3 -0
  18. package/dist/client/polyfill.js.map +1 -0
  19. package/dist/component/_generated/api.d.ts +36 -0
  20. package/dist/component/_generated/api.d.ts.map +1 -0
  21. package/dist/component/_generated/api.js +31 -0
  22. package/dist/component/_generated/api.js.map +1 -0
  23. package/dist/component/_generated/component.d.ts +542 -0
  24. package/dist/component/_generated/component.d.ts.map +1 -0
  25. package/dist/component/_generated/component.js +11 -0
  26. package/dist/component/_generated/component.js.map +1 -0
  27. package/dist/component/_generated/dataModel.d.ts +46 -0
  28. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  29. package/dist/component/_generated/dataModel.js +11 -0
  30. package/dist/component/_generated/dataModel.js.map +1 -0
  31. package/dist/component/_generated/server.d.ts +121 -0
  32. package/dist/component/_generated/server.d.ts.map +1 -0
  33. package/dist/component/_generated/server.js +78 -0
  34. package/dist/component/_generated/server.js.map +1 -0
  35. package/dist/component/convex.config.d.ts +3 -0
  36. package/dist/component/convex.config.d.ts.map +1 -0
  37. package/dist/component/convex.config.js +3 -0
  38. package/dist/component/convex.config.js.map +1 -0
  39. package/dist/component/lib.d.ts +1005 -0
  40. package/dist/component/lib.d.ts.map +1 -0
  41. package/dist/component/lib.js +647 -0
  42. package/dist/component/lib.js.map +1 -0
  43. package/dist/component/schema.d.ts +191 -0
  44. package/dist/component/schema.d.ts.map +1 -0
  45. package/dist/component/schema.js +104 -0
  46. package/dist/component/schema.js.map +1 -0
  47. package/dist/component/util.d.ts +61 -0
  48. package/dist/component/util.d.ts.map +1 -0
  49. package/dist/component/util.js +142 -0
  50. package/dist/component/util.js.map +1 -0
  51. package/dist/core/catalog.d.ts +18 -0
  52. package/dist/core/catalog.d.ts.map +1 -0
  53. package/dist/core/catalog.js +82 -0
  54. package/dist/core/catalog.js.map +1 -0
  55. package/dist/core/index.d.ts +9 -0
  56. package/dist/core/index.d.ts.map +1 -0
  57. package/dist/core/index.js +9 -0
  58. package/dist/core/index.js.map +1 -0
  59. package/dist/core/markdown.d.ts +12 -0
  60. package/dist/core/markdown.d.ts.map +1 -0
  61. package/dist/core/markdown.js +26 -0
  62. package/dist/core/markdown.js.map +1 -0
  63. package/dist/core/payments.d.ts +11 -0
  64. package/dist/core/payments.d.ts.map +1 -0
  65. package/dist/core/payments.js +27 -0
  66. package/dist/core/payments.js.map +1 -0
  67. package/dist/core/pendingCheckout.d.ts +15 -0
  68. package/dist/core/pendingCheckout.d.ts.map +1 -0
  69. package/dist/core/pendingCheckout.js +40 -0
  70. package/dist/core/pendingCheckout.js.map +1 -0
  71. package/dist/core/resolver.d.ts +11 -0
  72. package/dist/core/resolver.d.ts.map +1 -0
  73. package/dist/core/resolver.js +106 -0
  74. package/dist/core/resolver.js.map +1 -0
  75. package/dist/core/selectors.d.ts +12 -0
  76. package/dist/core/selectors.d.ts.map +1 -0
  77. package/dist/core/selectors.js +18 -0
  78. package/dist/core/selectors.js.map +1 -0
  79. package/dist/core/subscriptionUpdate.d.ts +20 -0
  80. package/dist/core/subscriptionUpdate.d.ts.map +1 -0
  81. package/dist/core/subscriptionUpdate.js +64 -0
  82. package/dist/core/subscriptionUpdate.js.map +1 -0
  83. package/dist/core/types.d.ts +170 -0
  84. package/dist/core/types.d.ts.map +1 -0
  85. package/dist/core/types.js +15 -0
  86. package/dist/core/types.js.map +1 -0
  87. package/dist/design-system/colors/color-utils.d.ts +10 -0
  88. package/dist/design-system/colors/color-utils.d.ts.map +1 -0
  89. package/dist/design-system/colors/color-utils.js +91 -0
  90. package/dist/design-system/colors/color-utils.js.map +1 -0
  91. package/dist/design-system/colors/config.d.ts +33 -0
  92. package/dist/design-system/colors/config.d.ts.map +1 -0
  93. package/dist/design-system/colors/config.js +224 -0
  94. package/dist/design-system/colors/config.js.map +1 -0
  95. package/dist/design-system/colors/index.d.ts +3 -0
  96. package/dist/design-system/colors/index.d.ts.map +1 -0
  97. package/dist/design-system/colors/index.js +3 -0
  98. package/dist/design-system/colors/index.js.map +1 -0
  99. package/dist/design-system/rounded/config.d.ts +31 -0
  100. package/dist/design-system/rounded/config.d.ts.map +1 -0
  101. package/dist/design-system/rounded/config.js +76 -0
  102. package/dist/design-system/rounded/config.js.map +1 -0
  103. package/dist/design-system/rounded/index.d.ts +2 -0
  104. package/dist/design-system/rounded/index.d.ts.map +1 -0
  105. package/dist/design-system/rounded/index.js +2 -0
  106. package/dist/design-system/rounded/index.js.map +1 -0
  107. package/dist/design-system/typography/config.d.ts +55 -0
  108. package/dist/design-system/typography/config.d.ts.map +1 -0
  109. package/dist/design-system/typography/config.js +308 -0
  110. package/dist/design-system/typography/config.js.map +1 -0
  111. package/dist/design-system/typography/index.d.ts +3 -0
  112. package/dist/design-system/typography/index.d.ts.map +1 -0
  113. package/dist/design-system/typography/index.js +3 -0
  114. package/dist/design-system/typography/index.js.map +1 -0
  115. package/dist/design-system/typography/tokens.d.ts +23 -0
  116. package/dist/design-system/typography/tokens.d.ts.map +1 -0
  117. package/dist/design-system/typography/tokens.js +99 -0
  118. package/dist/design-system/typography/tokens.js.map +1 -0
  119. package/dist/react/hooks/useCheckoutSuccessParams.d.ts +2 -0
  120. package/dist/react/hooks/useCheckoutSuccessParams.d.ts.map +1 -0
  121. package/dist/react/hooks/useCheckoutSuccessParams.js +5 -0
  122. package/dist/react/hooks/useCheckoutSuccessParams.js.map +1 -0
  123. package/dist/react/index.d.ts +25 -0
  124. package/dist/react/index.d.ts.map +1 -0
  125. package/dist/react/index.js +22 -0
  126. package/dist/react/index.js.map +1 -0
  127. package/dist/react/primitives/BillingGate.d.ts +8 -0
  128. package/dist/react/primitives/BillingGate.d.ts.map +1 -0
  129. package/dist/react/primitives/BillingGate.js +13 -0
  130. package/dist/react/primitives/BillingGate.js.map +1 -0
  131. package/dist/react/primitives/BillingToggle.d.ts +8 -0
  132. package/dist/react/primitives/BillingToggle.d.ts.map +1 -0
  133. package/dist/react/primitives/BillingToggle.js +12 -0
  134. package/dist/react/primitives/BillingToggle.js.map +1 -0
  135. package/dist/react/primitives/CheckoutButton.d.ts +11 -0
  136. package/dist/react/primitives/CheckoutButton.d.ts.map +1 -0
  137. package/dist/react/primitives/CheckoutButton.js +21 -0
  138. package/dist/react/primitives/CheckoutButton.js.map +1 -0
  139. package/dist/react/primitives/CheckoutSuccessSummary.d.ts +7 -0
  140. package/dist/react/primitives/CheckoutSuccessSummary.d.ts.map +1 -0
  141. package/dist/react/primitives/CheckoutSuccessSummary.js +11 -0
  142. package/dist/react/primitives/CheckoutSuccessSummary.js.map +1 -0
  143. package/dist/react/primitives/CustomerPortalButton.d.ts +8 -0
  144. package/dist/react/primitives/CustomerPortalButton.d.ts.map +1 -0
  145. package/dist/react/primitives/CustomerPortalButton.js +21 -0
  146. package/dist/react/primitives/CustomerPortalButton.js.map +1 -0
  147. package/dist/react/primitives/NumberInput.d.ts +11 -0
  148. package/dist/react/primitives/NumberInput.d.ts.map +1 -0
  149. package/dist/react/primitives/NumberInput.js +18 -0
  150. package/dist/react/primitives/NumberInput.js.map +1 -0
  151. package/dist/react/primitives/OneTimeCheckoutButton.d.ts +11 -0
  152. package/dist/react/primitives/OneTimeCheckoutButton.d.ts.map +1 -0
  153. package/dist/react/primitives/OneTimeCheckoutButton.js +4 -0
  154. package/dist/react/primitives/OneTimeCheckoutButton.js.map +1 -0
  155. package/dist/react/primitives/OneTimePaymentStatusBadge.d.ts +6 -0
  156. package/dist/react/primitives/OneTimePaymentStatusBadge.d.ts.map +1 -0
  157. package/dist/react/primitives/OneTimePaymentStatusBadge.js +11 -0
  158. package/dist/react/primitives/OneTimePaymentStatusBadge.js.map +1 -0
  159. package/dist/react/primitives/PaymentWarningBanner.d.ts +7 -0
  160. package/dist/react/primitives/PaymentWarningBanner.d.ts.map +1 -0
  161. package/dist/react/primitives/PaymentWarningBanner.js +18 -0
  162. package/dist/react/primitives/PaymentWarningBanner.js.map +1 -0
  163. package/dist/react/primitives/PricingCard.d.ts +37 -0
  164. package/dist/react/primitives/PricingCard.d.ts.map +1 -0
  165. package/dist/react/primitives/PricingCard.js +125 -0
  166. package/dist/react/primitives/PricingCard.js.map +1 -0
  167. package/dist/react/primitives/PricingSection.d.ts +39 -0
  168. package/dist/react/primitives/PricingSection.d.ts.map +1 -0
  169. package/dist/react/primitives/PricingSection.js +24 -0
  170. package/dist/react/primitives/PricingSection.js.map +1 -0
  171. package/dist/react/primitives/ScheduledChangeBanner.d.ts +8 -0
  172. package/dist/react/primitives/ScheduledChangeBanner.d.ts.map +1 -0
  173. package/dist/react/primitives/ScheduledChangeBanner.js +13 -0
  174. package/dist/react/primitives/ScheduledChangeBanner.js.map +1 -0
  175. package/dist/react/primitives/SegmentControl.d.ts +11 -0
  176. package/dist/react/primitives/SegmentControl.d.ts.map +1 -0
  177. package/dist/react/primitives/SegmentControl.js +8 -0
  178. package/dist/react/primitives/SegmentControl.js.map +1 -0
  179. package/dist/react/primitives/SegmentGroup.d.ts +14 -0
  180. package/dist/react/primitives/SegmentGroup.d.ts.map +1 -0
  181. package/dist/react/primitives/SegmentGroup.js +11 -0
  182. package/dist/react/primitives/SegmentGroup.js.map +1 -0
  183. package/dist/react/primitives/TrialLimitBanner.d.ts +7 -0
  184. package/dist/react/primitives/TrialLimitBanner.d.ts.map +1 -0
  185. package/dist/react/primitives/TrialLimitBanner.js +14 -0
  186. package/dist/react/primitives/TrialLimitBanner.js.map +1 -0
  187. package/dist/react/shared.d.ts +28 -0
  188. package/dist/react/shared.d.ts.map +1 -0
  189. package/dist/react/shared.js +109 -0
  190. package/dist/react/shared.js.map +1 -0
  191. package/dist/react/widgets/BillingPortal.d.ts +9 -0
  192. package/dist/react/widgets/BillingPortal.d.ts.map +1 -0
  193. package/dist/react/widgets/BillingPortal.js +30 -0
  194. package/dist/react/widgets/BillingPortal.js.map +1 -0
  195. package/dist/react/widgets/ProductItem.d.ts +8 -0
  196. package/dist/react/widgets/ProductItem.d.ts.map +1 -0
  197. package/dist/react/widgets/ProductItem.js +14 -0
  198. package/dist/react/widgets/ProductItem.js.map +1 -0
  199. package/dist/react/widgets/ProductRoot.d.ts +16 -0
  200. package/dist/react/widgets/ProductRoot.d.ts.map +1 -0
  201. package/dist/react/widgets/ProductRoot.js +171 -0
  202. package/dist/react/widgets/ProductRoot.js.map +1 -0
  203. package/dist/react/widgets/SubscriptionItem.d.ts +27 -0
  204. package/dist/react/widgets/SubscriptionItem.d.ts.map +1 -0
  205. package/dist/react/widgets/SubscriptionItem.js +32 -0
  206. package/dist/react/widgets/SubscriptionItem.js.map +1 -0
  207. package/dist/react/widgets/SubscriptionRoot.d.ts +16 -0
  208. package/dist/react/widgets/SubscriptionRoot.d.ts.map +1 -0
  209. package/dist/react/widgets/SubscriptionRoot.js +405 -0
  210. package/dist/react/widgets/SubscriptionRoot.js.map +1 -0
  211. package/dist/react/widgets/index.d.ts +19 -0
  212. package/dist/react/widgets/index.d.ts.map +1 -0
  213. package/dist/react/widgets/index.js +16 -0
  214. package/dist/react/widgets/index.js.map +1 -0
  215. package/dist/react/widgets/productGroupContext.d.ts +6 -0
  216. package/dist/react/widgets/productGroupContext.d.ts.map +1 -0
  217. package/dist/react/widgets/productGroupContext.js +3 -0
  218. package/dist/react/widgets/productGroupContext.js.map +1 -0
  219. package/dist/react/widgets/subscriptionContext.d.ts +6 -0
  220. package/dist/react/widgets/subscriptionContext.d.ts.map +1 -0
  221. package/dist/react/widgets/subscriptionContext.js +3 -0
  222. package/dist/react/widgets/subscriptionContext.js.map +1 -0
  223. package/dist/react/widgets/types.d.ts +171 -0
  224. package/dist/react/widgets/types.d.ts.map +1 -0
  225. package/dist/react/widgets/types.js +2 -0
  226. package/dist/react/widgets/types.js.map +1 -0
  227. package/dist/svelte/index.d.ts +22 -0
  228. package/dist/svelte/index.d.ts.map +1 -0
  229. package/dist/svelte/index.js +20 -0
  230. package/dist/svelte/index.js.map +1 -0
  231. package/dist/svelte/primitives/BillingGate.svelte +28 -0
  232. package/dist/svelte/primitives/BillingToggle.svelte +27 -0
  233. package/dist/svelte/primitives/CheckoutButton.svelte +60 -0
  234. package/dist/svelte/primitives/CheckoutSuccessSummary.svelte +34 -0
  235. package/dist/svelte/primitives/CustomerPortalButton.svelte +60 -0
  236. package/dist/svelte/primitives/NumberInput.svelte +71 -0
  237. package/dist/svelte/primitives/OneTimeCheckoutButton.svelte +37 -0
  238. package/dist/svelte/primitives/OneTimePaymentStatusBadge.svelte +20 -0
  239. package/dist/svelte/primitives/PaymentWarningBanner.svelte +30 -0
  240. package/dist/svelte/primitives/PricingCard.svelte +356 -0
  241. package/dist/svelte/primitives/PricingSection.svelte +121 -0
  242. package/dist/svelte/primitives/ScheduledChangeBanner.svelte +46 -0
  243. package/dist/svelte/primitives/SegmentControl.svelte +38 -0
  244. package/dist/svelte/primitives/SegmentGroup.svelte +52 -0
  245. package/dist/svelte/primitives/TrialLimitBanner.svelte +32 -0
  246. package/dist/svelte/primitives/shared.d.ts +13 -0
  247. package/dist/svelte/primitives/shared.d.ts.map +1 -0
  248. package/dist/svelte/primitives/shared.js +87 -0
  249. package/dist/svelte/primitives/shared.js.map +1 -0
  250. package/dist/svelte/widgets/BillingPortal.svelte +55 -0
  251. package/dist/svelte/widgets/Product.svelte +35 -0
  252. package/dist/svelte/widgets/ProductRoot.svelte +428 -0
  253. package/dist/svelte/widgets/Subscription.svelte +52 -0
  254. package/dist/svelte/widgets/SubscriptionRoot.svelte +690 -0
  255. package/dist/svelte/widgets/index.d.ts +19 -0
  256. package/dist/svelte/widgets/index.d.ts.map +1 -0
  257. package/dist/svelte/widgets/index.js +16 -0
  258. package/dist/svelte/widgets/index.js.map +1 -0
  259. package/dist/svelte/widgets/productGroupContext.d.ts +6 -0
  260. package/dist/svelte/widgets/productGroupContext.d.ts.map +1 -0
  261. package/dist/svelte/widgets/productGroupContext.js +2 -0
  262. package/dist/svelte/widgets/productGroupContext.js.map +1 -0
  263. package/dist/svelte/widgets/subscriptionContext.d.ts +6 -0
  264. package/dist/svelte/widgets/subscriptionContext.d.ts.map +1 -0
  265. package/dist/svelte/widgets/subscriptionContext.js +2 -0
  266. package/dist/svelte/widgets/subscriptionContext.js.map +1 -0
  267. package/dist/svelte/widgets/types.d.ts +171 -0
  268. package/dist/svelte/widgets/types.d.ts.map +1 -0
  269. package/dist/svelte/widgets/types.js +2 -0
  270. package/dist/svelte/widgets/types.js.map +1 -0
  271. package/package.json +182 -0
  272. package/src/client/helpers.test.ts +139 -0
  273. package/src/client/helpers.ts +51 -0
  274. package/src/client/index.test.ts +1554 -0
  275. package/src/client/index.ts +1504 -0
  276. package/src/client/parsers.test.ts +1017 -0
  277. package/src/client/parsers.ts +182 -0
  278. package/src/client/polyfill.ts +2 -0
  279. package/src/component/_generated/api.ts +52 -0
  280. package/src/component/_generated/component.ts +619 -0
  281. package/src/component/_generated/dataModel.ts +60 -0
  282. package/src/component/_generated/server.ts +156 -0
  283. package/src/component/convex.config.ts +3 -0
  284. package/src/component/lib.test.ts +1359 -0
  285. package/src/component/lib.ts +726 -0
  286. package/src/component/schema.ts +112 -0
  287. package/src/component/util.test.ts +281 -0
  288. package/src/component/util.ts +228 -0
  289. package/src/core/catalog.test.ts +212 -0
  290. package/src/core/catalog.ts +119 -0
  291. package/src/core/index.ts +8 -0
  292. package/src/core/markdown.test.ts +43 -0
  293. package/src/core/markdown.ts +26 -0
  294. package/src/core/payments.test.ts +69 -0
  295. package/src/core/payments.ts +33 -0
  296. package/src/core/pendingCheckout.test.ts +44 -0
  297. package/src/core/pendingCheckout.ts +40 -0
  298. package/src/core/resolver.test.ts +283 -0
  299. package/src/core/resolver.ts +160 -0
  300. package/src/core/selectors.test.ts +119 -0
  301. package/src/core/selectors.ts +35 -0
  302. package/src/core/subscriptionUpdate.test.ts +164 -0
  303. package/src/core/subscriptionUpdate.ts +102 -0
  304. package/src/core/types.ts +220 -0
  305. package/src/design-system/README.md +40 -0
  306. package/src/design-system/base.css +27 -0
  307. package/src/design-system/colors/color-utils.ts +110 -0
  308. package/src/design-system/colors/config.ts +282 -0
  309. package/src/design-system/colors/index.ts +2 -0
  310. package/src/design-system/colors/utilities.css +2328 -0
  311. package/src/design-system/components/badges.css +65 -0
  312. package/src/design-system/components/buttons.css +256 -0
  313. package/src/design-system/components/dialog.css +218 -0
  314. package/src/design-system/components/icon-buttons.css +115 -0
  315. package/src/design-system/components/inputs.css +94 -0
  316. package/src/design-system/components/links.css +53 -0
  317. package/src/design-system/components/prose.css +67 -0
  318. package/src/design-system/components/segment-control.css +303 -0
  319. package/src/design-system/index.css +21 -0
  320. package/src/design-system/rounded/config.ts +91 -0
  321. package/src/design-system/rounded/index.ts +1 -0
  322. package/src/design-system/rounded/utilities.css +37 -0
  323. package/src/design-system/typography/config.ts +340 -0
  324. package/src/design-system/typography/index.ts +2 -0
  325. package/src/design-system/typography/tokens.ts +148 -0
  326. package/src/design-system/typography/utilities.css +728 -0
  327. package/src/library.css +20 -0
  328. package/src/react/hooks/useCheckoutSuccessParams.ts +7 -0
  329. package/src/react/index.tsx +47 -0
  330. package/src/react/primitives/BillingGate.tsx +26 -0
  331. package/src/react/primitives/BillingToggle.tsx +29 -0
  332. package/src/react/primitives/CheckoutButton.tsx +47 -0
  333. package/src/react/primitives/CheckoutSuccessSummary.tsx +36 -0
  334. package/src/react/primitives/CustomerPortalButton.tsx +50 -0
  335. package/src/react/primitives/NumberInput.tsx +83 -0
  336. package/src/react/primitives/OneTimeCheckoutButton.tsx +27 -0
  337. package/src/react/primitives/OneTimePaymentStatusBadge.tsx +18 -0
  338. package/src/react/primitives/PaymentWarningBanner.tsx +33 -0
  339. package/src/react/primitives/PricingCard.tsx +421 -0
  340. package/src/react/primitives/PricingSection.tsx +129 -0
  341. package/src/react/primitives/ScheduledChangeBanner.tsx +52 -0
  342. package/src/react/primitives/SegmentControl.tsx +32 -0
  343. package/src/react/primitives/SegmentGroup.tsx +53 -0
  344. package/src/react/primitives/TrialLimitBanner.tsx +32 -0
  345. package/src/react/shared.ts +138 -0
  346. package/src/react/widgets/BillingPortal.tsx +56 -0
  347. package/src/react/widgets/ProductItem.tsx +26 -0
  348. package/src/react/widgets/ProductRoot.tsx +441 -0
  349. package/src/react/widgets/SubscriptionItem.tsx +71 -0
  350. package/src/react/widgets/SubscriptionRoot.tsx +759 -0
  351. package/src/react/widgets/index.ts +36 -0
  352. package/src/react/widgets/productGroupContext.ts +10 -0
  353. package/src/react/widgets/subscriptionContext.ts +10 -0
  354. package/src/react/widgets/types.ts +179 -0
  355. package/src/svelte/index.ts +43 -0
  356. package/src/svelte/primitives/BillingGate.svelte +28 -0
  357. package/src/svelte/primitives/BillingToggle.svelte +27 -0
  358. package/src/svelte/primitives/CheckoutButton.svelte +60 -0
  359. package/src/svelte/primitives/CheckoutSuccessSummary.svelte +34 -0
  360. package/src/svelte/primitives/CustomerPortalButton.svelte +60 -0
  361. package/src/svelte/primitives/NumberInput.svelte +71 -0
  362. package/src/svelte/primitives/OneTimeCheckoutButton.svelte +37 -0
  363. package/src/svelte/primitives/OneTimePaymentStatusBadge.svelte +20 -0
  364. package/src/svelte/primitives/PaymentWarningBanner.svelte +30 -0
  365. package/src/svelte/primitives/PricingCard.svelte +356 -0
  366. package/src/svelte/primitives/PricingSection.svelte +121 -0
  367. package/src/svelte/primitives/ScheduledChangeBanner.svelte +46 -0
  368. package/src/svelte/primitives/SegmentControl.svelte +38 -0
  369. package/src/svelte/primitives/SegmentGroup.svelte +52 -0
  370. package/src/svelte/primitives/TrialLimitBanner.svelte +32 -0
  371. package/src/svelte/primitives/shared.ts +113 -0
  372. package/src/svelte/svelte.d.ts +6 -0
  373. package/src/svelte/widgets/BillingPortal.svelte +55 -0
  374. package/src/svelte/widgets/Product.svelte +35 -0
  375. package/src/svelte/widgets/ProductRoot.svelte +428 -0
  376. package/src/svelte/widgets/Subscription.svelte +52 -0
  377. package/src/svelte/widgets/SubscriptionRoot.svelte +690 -0
  378. package/src/svelte/widgets/index.ts +36 -0
  379. package/src/svelte/widgets/productGroupContext.ts +7 -0
  380. package/src/svelte/widgets/subscriptionContext.ts +7 -0
  381. package/src/svelte/widgets/types.ts +179 -0
  382. package/src/tailwind.css +6 -0
  383. package/src/test.ts +18 -0
package/README.md ADDED
@@ -0,0 +1,1176 @@
1
+ # Convex Creem Component
2
+
3
+ Add subscriptions, one-time purchases, and billing to your Convex app with
4
+ [Creem](https://www.creem.io).
5
+
6
+ **Check out the [Svelte example](example-svelte) and
7
+ [React example](example-react) for complete integrations.**
8
+
9
+ ## Table of Contents
10
+
11
+ - [Quick Start — Backend](#quick-start--backend)
12
+ - [1. Install](#1-install)
13
+ - [2. Register component](#2-register-component)
14
+ - [3. Set environment variables](#3-set-environment-variables)
15
+ - [4. Configure billing](#4-configure-billing)
16
+ - [5. Register webhooks](#5-register-webhooks)
17
+ - [6. Sync products](#6-sync-products)
18
+ - [Quick Start — Frontend (UI Widgets)](#quick-start--frontend-ui-widgets)
19
+ - [7. Install Tailwind CSS](#7-Install-UI-primitives)
20
+ - [7. Install Tailwind CSS](#8-install-tailwind-css)
21
+ - [8. Import styles](#9-import-styles)
22
+ - [Entity Model](#entity-model)
23
+ - [Scenarios](#scenarios)
24
+ - [Wire the billing API](#wire-the-billing-api)
25
+ - [1. Subscriptions](#1-subscriptions)
26
+ - [2. Products](#2-products)
27
+ - [3. Billing Portal](#3-billing-portal)
28
+ - [4. Feature Gating](#4-feature-gating)
29
+ - [5. Checkout Success](#5-checkout-success)
30
+ - [Advanced](#advanced)
31
+ - [Webhook event middleware](#webhook-event-middleware)
32
+ - [Security & Access Control](#security--access-control)
33
+ - [Custom billing UI model](#custom-billing-ui-model)
34
+ - [Server endpoint overrides](#server-endpoint-overrides)
35
+ - [API Reference](#api-reference)
36
+ - [Resource namespaces](#resource-namespaces--creemnamespace)
37
+ - [`creem.api({ resolve })` — convenience exports](#creemapi-resolve---convenience-exports)
38
+ - [Infrastructure](#infrastructure)
39
+ - [Direct API access — `creem.sdk.*`](#direct-api-access--creemsdk)
40
+ - [Component Reference](#component-reference)
41
+ - [Widgets](#widgets)
42
+ - [Presentational components](#presentational-components)
43
+ - [Troubleshooting](#troubleshooting)
44
+
45
+ ---
46
+
47
+ ## Quick Start — Backend
48
+
49
+ Complete these steps to use the billing API from your Convex functions. No
50
+ frontend framework required.
51
+
52
+ ### 1. Install
53
+
54
+ ```bash
55
+ npm install @mmailaender/convex-creem
56
+ ```
57
+
58
+ ### 2. Register component
59
+
60
+ ```ts
61
+ // convex/convex.config.ts
62
+ import { defineApp } from "convex/server";
63
+ import creem from "@mmailaender/convex-creem/convex.config";
64
+
65
+ const app = defineApp();
66
+ app.use(creem);
67
+
68
+ export default app;
69
+ ```
70
+
71
+ ### 3. Set environment variables
72
+
73
+ ```bash
74
+ npx convex env set CREEM_API_KEY <your_creem_api_key>
75
+ npx convex env set CREEM_WEBHOOK_SECRET <your_creem_webhook_signing_secret>
76
+ ```
77
+
78
+ ### 4. Configure billing
79
+
80
+ ```ts
81
+ // convex/billing.ts
82
+ import { Creem, type ApiResolver } from "@mmailaender/convex-creem";
83
+ import { api, components } from "./_generated/api";
84
+ import { query, internalAction } from "./_generated/server";
85
+
86
+ export const creem = new Creem(components.creem);
87
+
88
+ // Auth resolver — replace with your own auth logic
89
+ const resolve: ApiResolver = async (ctx) => {
90
+ const user = await ctx.runQuery(api.users.currentUser);
91
+ return {
92
+ userId: user._id,
93
+ email: user.email,
94
+ entityId: user._id, // For org billing: user.activeOrgId ?? user._id
95
+ };
96
+ };
97
+
98
+ // Generate Convex function exports — each calls resolve(), then delegates
99
+ const {
100
+ uiModel,
101
+ snapshot,
102
+ checkouts,
103
+ subscriptions,
104
+ products,
105
+ customers,
106
+ orders,
107
+ } = creem.api({ resolve });
108
+
109
+ export { uiModel, snapshot };
110
+ export const checkoutsCreate = checkouts.create;
111
+ export const subscriptionsUpdate = subscriptions.update;
112
+ export const subscriptionsCancel = subscriptions.cancel;
113
+ export const subscriptionsResume = subscriptions.resume;
114
+ export const subscriptionsPause = subscriptions.pause;
115
+ export const subscriptionsList = subscriptions.list;
116
+ export const subscriptionsListAll = subscriptions.listAll;
117
+ export const productsList = products.list;
118
+ export const productsGet = products.get;
119
+ export const customersRetrieve = customers.retrieve;
120
+ export const customersPortalUrl = customers.portalUrl;
121
+ export const ordersList = orders.list;
122
+
123
+ // Sync products from Creem (CLI / dashboard only)
124
+ export const syncBillingProducts = internalAction({
125
+ args: {},
126
+ handler: async (ctx) => {
127
+ await creem.syncProducts(ctx);
128
+ },
129
+ });
130
+ ```
131
+
132
+ ### 5. Register webhooks
133
+
134
+ ```ts
135
+ // convex/http.ts
136
+ import { httpRouter } from "convex/server";
137
+ import { creem } from "./billing";
138
+
139
+ const http = httpRouter();
140
+
141
+ creem.registerRoutes(http);
142
+
143
+ export default http;
144
+ ```
145
+
146
+ Use your **Convex site URL** + `/creem/events` as the webhook endpoint in your
147
+ Creem dashboard. The component automatically handles `checkout.completed`,
148
+ `subscription.*`, and `product.*` events.
149
+
150
+ > For custom event handlers (e.g. sending emails on checkout), see
151
+ > [Webhook event middleware](#webhook-event-middleware).
152
+
153
+ ### 6. Sync products
154
+
155
+ After configuring webhooks, pull your Creem products into the Convex database:
156
+
157
+ ```bash
158
+ npx convex run billing:syncBillingProducts
159
+ ```
160
+
161
+ > This is an `internalAction` — it can only be triggered from the CLI or the
162
+ > Convex dashboard.
163
+
164
+ **You're done with the backend.** You can now call `api.billing.*` from your
165
+ frontend or other Convex functions. If you only need the API (no UI widgets),
166
+ skip ahead to the [API Reference](#api-reference).
167
+
168
+ ---
169
+
170
+ ## Quick Start — Frontend (UI Widgets)
171
+
172
+ The component ships pre-built Svelte and React widgets that handle checkout,
173
+ plan switching, cancellation, seat management, and billing state — all connected
174
+ to Convex. Complete these three extra steps to use them.
175
+
176
+ ### 7. Install UI primitives
177
+
178
+ The widgets are built on [Ark UI](https://ark-ui.com) headless primitives.
179
+ Install the adapter for your framework:
180
+
181
+ **React**
182
+
183
+ ```sh
184
+ npm install @ark-ui/react
185
+ ```
186
+
187
+ **Svelte**
188
+
189
+ ```sh
190
+ npm install @ark-ui/svelte
191
+ ```
192
+
193
+ ### 8. Install Tailwind CSS
194
+
195
+ The widgets use [Tailwind CSS v4](https://tailwindcss.com/docs/installation). If
196
+ your project doesn't have Tailwind yet, install it following the
197
+ [official guide](https://tailwindcss.com/docs/installation).
198
+
199
+ ### 9. Import styles
200
+
201
+ Add the component's design system import to your CSS entry point, **after** the
202
+ Tailwind import:
203
+
204
+ ```css
205
+ @import "tailwindcss";
206
+ @import "@mmailaender/convex-creem/styles";
207
+ ```
208
+
209
+ This registers the component's design tokens, base styles, and `@source`
210
+ directives so Tailwind scans the library's component files automatically.
211
+
212
+ **You're ready to use the UI widgets.** Continue with the scenarios below.
213
+
214
+ ---
215
+
216
+ ## Entity Model
217
+
218
+ By default, billing is scoped to the **authenticated user** — the `entityId`
219
+ returned from your resolver is used as the billing entity. All
220
+ `creem.api({ resolve })` functions, checkout metadata, and webhook resolution
221
+ automatically use this entity.
222
+
223
+ For **organization or team billing**, return the org ID as `entityId`:
224
+
225
+ ```ts
226
+ const resolve: ApiResolver = async (ctx) => {
227
+ const user = await ctx.runQuery(api.users.currentUser);
228
+ const org = await ctx.runQuery(api.orgs.getActiveOrg);
229
+ return {
230
+ userId: user._id,
231
+ email: user.email,
232
+ entityId: org?._id ?? user._id, // org billing or personal billing
233
+ };
234
+ };
235
+ ```
236
+
237
+ All billing operations scope to the `entityId`. Webhooks resolve
238
+ `convexBillingEntityId` from checkout metadata (falls back to `convexUserId`).
239
+ No other code changes needed.
240
+
241
+ For access control details, see
242
+ [Security & Access Control](#security--access-control).
243
+
244
+ ---
245
+
246
+ ## Scenarios
247
+
248
+ Both Svelte and React widgets share **identical props and APIs** — only the
249
+ import path and framework boilerplate differ.
250
+
251
+ > **Convention:** Where the markup is identical in both frameworks, examples are
252
+ > shown once. The only recurring difference is `class=` (Svelte) vs `className=`
253
+ > (React) when passing CSS classes. Where Svelte and React syntax diverges (e.g.
254
+ > children rendering), both versions are shown.
255
+
256
+ ### Wire the billing API
257
+
258
+ Every widget needs a `ConnectedBillingApi` object. Create it once in your layout
259
+ or page component:
260
+
261
+ **Svelte**
262
+
263
+ ```svelte
264
+ <script lang="ts">
265
+ import { setupConvex } from "convex-svelte";
266
+ import {
267
+ Subscription, Product, BillingPortal,
268
+ type ConnectedBillingApi,
269
+ } from "@mmailaender/convex-creem/svelte";
270
+ import { api } from "../convex/_generated/api.js";
271
+
272
+ setupConvex(import.meta.env.VITE_CONVEX_URL);
273
+
274
+ const billingApi: ConnectedBillingApi = {
275
+ uiModel: api.billing.uiModel,
276
+ checkouts: { create: api.billing.checkoutsCreate },
277
+ subscriptions: {
278
+ update: api.billing.subscriptionsUpdate,
279
+ cancel: api.billing.subscriptionsCancel,
280
+ resume: api.billing.subscriptionsResume,
281
+ },
282
+ customers: { portalUrl: api.billing.customersPortalUrl },
283
+ };
284
+ </script>
285
+ ```
286
+
287
+ **React**
288
+
289
+ ```tsx
290
+ import {
291
+ Subscription,
292
+ Product,
293
+ BillingPortal,
294
+ type ConnectedBillingApi,
295
+ } from "@mmailaender/convex-creem/react";
296
+ import { api } from "../convex/_generated/api";
297
+
298
+ const billingApi: ConnectedBillingApi = {
299
+ uiModel: api.billing.uiModel,
300
+ checkouts: { create: api.billing.checkoutsCreate },
301
+ subscriptions: {
302
+ update: api.billing.subscriptionsUpdate,
303
+ cancel: api.billing.subscriptionsCancel,
304
+ resume: api.billing.subscriptionsResume,
305
+ },
306
+ customers: { portalUrl: api.billing.customersPortalUrl },
307
+ };
308
+ ```
309
+
310
+ > The `ConnectedBillingApi` object is the same shape in both frameworks. Only
311
+ > the Convex client setup differs: `setupConvex()` in Svelte vs
312
+ > `<ConvexProvider>` in React (see
313
+ > [convex-svelte](https://github.com/get-convex/convex-svelte) and
314
+ > [convex/react](https://docs.convex.dev/client/react) docs).
315
+
316
+ ### 1. Subscriptions
317
+
318
+ #### 1.1 Standard subscription plans
319
+
320
+ A typical pricing page with Free / Basic / Premium / Enterprise tiers. The
321
+ billing toggle auto-derives from the cycles present in registered plans.
322
+
323
+ ```svelte
324
+ <Subscription.Root api={billingApi}>
325
+ <Subscription.Item type="free" title="Free" description="Up to 3 users" />
326
+ <Subscription.Item
327
+ planId="basic"
328
+ type="single"
329
+ productIds={{
330
+ "every-month": "prod_basic_monthly",
331
+ "every-year": "prod_basic_yearly",
332
+ }}
333
+ />
334
+ <Subscription.Item
335
+ planId="premium"
336
+ type="single"
337
+ recommended
338
+ productIds={{
339
+ "every-month": "prod_premium_monthly",
340
+ "every-year": "prod_premium_yearly",
341
+ }}
342
+ />
343
+ <Subscription.Item
344
+ type="enterprise"
345
+ title="Enterprise"
346
+ contactUrl="https://example.com/sales"
347
+ />
348
+ </Subscription.Root>
349
+ <BillingPortal api={billingApi} />
350
+ ```
351
+
352
+ **What you get:**
353
+
354
+ - Pricing cards with auto-resolved titles, descriptions (rendered as Markdown),
355
+ and prices from Creem product data
356
+ - Billing cycle toggle (monthly/yearly) — hidden when all plans share a single
357
+ cycle
358
+ - "Current plan" badge on the active subscription
359
+ - Plan switching with confirmation dialog
360
+ - Trial countdown badge
361
+ - Cancel / resume subscription (with confirmation dialog)
362
+ - Scheduled cancellation banner with "Undo" button
363
+
364
+ #### 1.2 Seat-based subscriptions
365
+
366
+ Two workflows for seat-based pricing:
367
+
368
+ **User-selectable seats** — the customer picks a quantity before checkout:
369
+
370
+ ```svelte
371
+ <Subscription.Root api={billingApi} showSeatPicker>
372
+ <Subscription.Item
373
+ type="seat-based"
374
+ productIds={{ "every-month": "prod_team_monthly" }}
375
+ />
376
+ <Subscription.Item
377
+ type="seat-based"
378
+ productIds={{ "every-month": "prod_business_monthly" }}
379
+ />
380
+ </Subscription.Root>
381
+ ```
382
+
383
+ **Auto-derived seats** — pass a fixed count (e.g. org member count) to checkout:
384
+
385
+ ```svelte
386
+ <Subscription.Root api={billingApi} units={orgMemberCount}>
387
+ <Subscription.Item
388
+ type="seat-based"
389
+ productIds={{ "every-month": "prod_team_monthly" }}
390
+ />
391
+ </Subscription.Root>
392
+ ```
393
+
394
+ When `subscriptions.update` is provided in the API, active seat-based plans show
395
+ a "Change seats" control.
396
+
397
+ > **Tip:** For auto-derived seats, keep the subscription in sync with your data.
398
+ > When your member count changes, call `subscriptions.update` with the new
399
+ > `units` so the billing reflects the current seat count.
400
+
401
+ ### 2. Products
402
+
403
+ #### 2.1 Single one-time product
404
+
405
+ A standalone product purchased once. Shows "Owned" after purchase:
406
+
407
+ ```svelte
408
+ <Product.Root api={billingApi}>
409
+ <Product.Item type="one-time" productId="prod_license" />
410
+ </Product.Root>
411
+ ```
412
+
413
+ #### 2.2 Repeating product (consumable)
414
+
415
+ Can be purchased multiple times — no "Owned" badge:
416
+
417
+ ```svelte
418
+ <Product.Root api={billingApi}>
419
+ <Product.Item type="recurring" productId="prod_credits" title="100 Credits" />
420
+ </Product.Root>
421
+ ```
422
+
423
+ #### 2.3 Mutually exclusive product group
424
+
425
+ Use the `transition` prop to define upgrade paths between products. When the
426
+ user owns a lower-tier product, only valid upgrade paths are shown:
427
+
428
+ ```svelte
429
+ <Product.Root
430
+ api={billingApi}
431
+ transition={[
432
+ {
433
+ from: "prod_basic_license",
434
+ to: "prod_premium_license",
435
+ kind: "via_product",
436
+ viaProductId: "prod_basic_to_premium_upgrade",
437
+ },
438
+ ]}
439
+ >
440
+ <Product.Item type="one-time" productId="prod_basic_license" />
441
+ <Product.Item type="one-time" productId="prod_premium_license" />
442
+ </Product.Root>
443
+ ```
444
+
445
+ **Transition kinds:**
446
+
447
+ - **`via_product`** — checkout uses a dedicated upgrade product (delta pricing)
448
+ - **`direct`** — checkout uses the target product directly
449
+
450
+ ### 3. Billing Portal
451
+
452
+ `<BillingPortal>` opens the Creem customer billing portal. It auto-hides when
453
+ the billing entity has no Creem customer record.
454
+
455
+ Pass `permissions` to control who can access the portal (e.g. only admins):
456
+
457
+ ```svelte
458
+ <BillingPortal api={billingApi} permissions={{ canAccessPortal: isAdmin }} />
459
+ ```
460
+
461
+ ```svelte
462
+ <!-- After a subscription group -->
463
+ <BillingPortal api={billingApi} />
464
+
465
+ <!-- Standalone with custom label -->
466
+ <BillingPortal api={billingApi}>Manage billing & invoices</BillingPortal>
467
+ ```
468
+
469
+ ### 4. Feature Gating
470
+
471
+ Use `BillingGate` to conditionally render UI based on available billing actions:
472
+
473
+ **Svelte**
474
+
475
+ ```svelte
476
+ <BillingGate snapshot={billingSnapshot} requiredActions="portal">
477
+ {#snippet children()}
478
+ <p>You have portal access.</p>
479
+ {/snippet}
480
+ {#snippet fallback()}
481
+ <p>Upgrade to access the billing portal.</p>
482
+ {/snippet}
483
+ </BillingGate>
484
+ ```
485
+
486
+ **React**
487
+
488
+ ```tsx
489
+ <BillingGate
490
+ snapshot={billingSnapshot}
491
+ requiredActions="portal"
492
+ fallback={<p>Upgrade to access the billing portal.</p>}
493
+ >
494
+ <p>You have portal access.</p>
495
+ </BillingGate>
496
+ ```
497
+
498
+ Available actions: `checkout`, `portal`, `cancel`, `reactivate`,
499
+ `switch_interval`, `update_seats`, `contact_sales`.
500
+
501
+ ### 5. Checkout Success
502
+
503
+ Show a confirmation banner when the user returns from checkout. The component
504
+ parses Creem's query parameters automatically:
505
+
506
+ ```svelte
507
+ <CheckoutSuccessSummary />
508
+ ```
509
+
510
+ ---
511
+
512
+ ## Advanced
513
+
514
+ ### Webhook event middleware
515
+
516
+ `registerRoutes` accepts an optional `events` object to run app-specific logic
517
+ alongside the component's automatic handling:
518
+
519
+ ```ts
520
+ creem.registerRoutes(http, {
521
+ path: "/creem/events", // default
522
+ events: {
523
+ "checkout.completed": async (ctx, event) => {
524
+ // ctx is a Convex mutation context
525
+ // event has { type, data } from Creem
526
+ // Example: send confirmation email, grant entitlements, log analytics
527
+ },
528
+ "subscription.updated": async (ctx, event) => {
529
+ const data = event.data as { customerCancellationReason?: string };
530
+ if (data?.customerCancellationReason) {
531
+ console.log("Cancellation reason:", data.customerCancellationReason);
532
+ }
533
+ },
534
+ },
535
+ });
536
+ ```
537
+
538
+ Your handlers run **after** the component's built-in processing
539
+ (customer/subscription/order upserts). The `ctx` is a Convex mutation context —
540
+ you can read/write to your own tables.
541
+
542
+ **Supported events:** `checkout.completed`, `subscription.active`,
543
+ `subscription.updated`, `subscription.canceled`, `subscription.paused`,
544
+ `subscription.resumed`, `product.created`, `product.updated`.
545
+
546
+ ### Security & Access Control
547
+
548
+ **Auth is the app's responsibility.** The component is a sync engine — it reads
549
+ from Convex DB and writes to the Creem API. Every class method takes explicit
550
+ args; there is no hidden auth layer.
551
+
552
+ **Choose your approach:**
553
+
554
+ - **Quick start** — use [`creem.api({ resolve })`](#4-configure-billing) to
555
+ generate ready-to-export Convex functions. Each one calls your `resolve`
556
+ callback to authenticate and determine the `entityId`. The billing entity is
557
+ derived from the authenticated session, never from client input. See
558
+ [Step 4: Configure billing](#4-configure-billing) and the
559
+ [`creem.api({ resolve })` reference](#creemapi-resolve---convenience-exports).
560
+
561
+ - **Full control** — use the
562
+ [resource namespaces](#resource-namespaces--creemnamespace)
563
+ (`creem.subscriptions.*`, `creem.checkouts.*`, etc.) directly in your own
564
+ Convex functions. You handle auth, entity resolution, and permission checks
565
+ yourself.
566
+
567
+ The library exports **shared arg validators** that match exactly what the
568
+ connected widgets send. Use them to keep your custom functions in sync:
569
+
570
+ | Export | Used by |
571
+ | ------------------------ | ------------------------------------------------ |
572
+ | `checkoutCreateArgs` | `<Subscription.Root>`, `<Product.Root>` |
573
+ | `subscriptionUpdateArgs` | `<Subscription.Root>` (plan switch, seat update) |
574
+ | `subscriptionCancelArgs` | `<Subscription.Root>` (cancel button) |
575
+ | `subscriptionResumeArgs` | `<Subscription.Root>` (resume button) |
576
+ | `subscriptionPauseArgs` | `<Subscription.Root>` (pause button) |
577
+
578
+ Example:
579
+
580
+ ```ts
581
+ // convex/billing.ts
582
+ import {
583
+ Creem,
584
+ checkoutCreateArgs,
585
+ subscriptionCancelArgs,
586
+ } from "@mmailaender/convex-creem";
587
+ import { ConvexError } from "convex/values";
588
+ import { action, mutation } from "./_generated/server";
589
+ import { api, components } from "./_generated/api";
590
+
591
+ const creem = new Creem(components.creem);
592
+
593
+ async function resolveAuth(ctx) {
594
+ const session = await ctx.runQuery(api.auth.getSession);
595
+ if (!session) throw new ConvexError("Not authenticated");
596
+ const org = await ctx.runQuery(api.orgs.getActiveOrg);
597
+ return {
598
+ userId: session.userId,
599
+ email: session.user.email,
600
+ entityId: org?._id ?? session.userId,
601
+ role: session.user.role,
602
+ };
603
+ }
604
+
605
+ // Admin-only: create checkout
606
+ export const checkoutsCreate = action({
607
+ args: checkoutCreateArgs,
608
+ handler: async (ctx, args) => {
609
+ const auth = await resolveAuth(ctx);
610
+ if (auth.role !== "admin") throw new ConvexError("Forbidden");
611
+ return await creem.checkouts.create(ctx, {
612
+ entityId: auth.entityId,
613
+ userId: auth.userId,
614
+ email: auth.email,
615
+ ...args,
616
+ });
617
+ },
618
+ });
619
+
620
+ // Admin-only: cancel subscription
621
+ export const subscriptionsCancel = mutation({
622
+ args: subscriptionCancelArgs,
623
+ handler: async (ctx, args) => {
624
+ const auth = await resolveAuth(ctx);
625
+ if (auth.role !== "admin") throw new ConvexError("Forbidden");
626
+ await creem.subscriptions.cancel(ctx, { entityId: auth.entityId, ...args });
627
+ },
628
+ });
629
+ ```
630
+
631
+ **UI-side permissions** — `BillingPermissions` controls which buttons are
632
+ enabled in the widgets. This is cosmetic gating only; enforce real permissions
633
+ server-side.
634
+
635
+ ```ts
636
+ type BillingPermissions = {
637
+ canCheckout?: boolean;
638
+ canChangeSubscription?: boolean;
639
+ canCancelSubscription?: boolean;
640
+ canResumeSubscription?: boolean;
641
+ canUpdateSeats?: boolean;
642
+ canAccessPortal?: boolean;
643
+ };
644
+ ```
645
+
646
+ ```svelte
647
+ <script lang="ts">
648
+ const isAdmin = $derived(currentUser?.role === "admin" || currentUser?.role === "owner");
649
+ const permissions = $derived({
650
+ canCheckout: isAdmin,
651
+ canChangeSubscription: isAdmin,
652
+ canCancelSubscription: isAdmin,
653
+ canResumeSubscription: isAdmin,
654
+ canUpdateSeats: isAdmin,
655
+ });
656
+ </script>
657
+
658
+ <Subscription.Root api={billingApi} {permissions}>
659
+ ...
660
+ </Subscription.Root>
661
+ ```
662
+
663
+ When a permission is `false`, the button renders as disabled (greyed out). When
664
+ omitted or `undefined`, all actions default to enabled.
665
+
666
+ ### Pre-checkout gate — `onBeforeCheckout`
667
+
668
+ Both `<Subscription.Root>` and `<Product.Root>` accept an `onBeforeCheckout`
669
+ callback that fires **before** the widget calls `checkouts.create`. Return
670
+ `true` to proceed, `false` to abort silently.
671
+
672
+ This is a generic hook — use it for authentication gates, terms acceptance,
673
+ confirmation dialogs, analytics, or any logic that must run before checkout.
674
+
675
+ ```svelte
676
+ <Subscription.Root
677
+ api={billingApi}
678
+ onBeforeCheckout={(intent) => {
679
+ if (!currentUser) {
680
+ pendingCheckout.save(intent);
681
+ openSignInDialog();
682
+ return false;
683
+ }
684
+ return true;
685
+ }}
686
+ >
687
+ ...
688
+ </Subscription.Root>
689
+ ```
690
+
691
+ **`CheckoutIntent`** — the object passed to the callback:
692
+
693
+ ```ts
694
+ type CheckoutIntent = {
695
+ productId: string;
696
+ units?: number;
697
+ };
698
+ ```
699
+
700
+ #### Auto-resume after sign-in
701
+
702
+ The widget automatically resumes a pending checkout when the user becomes
703
+ authenticated. The full flow:
704
+
705
+ 1. Unauthenticated user clicks "Subscribe" → `onBeforeCheckout` fires
706
+ 2. Your callback saves the intent via `pendingCheckout.save(intent)`, opens your
707
+ sign-in dialog/redirect, and returns `false`
708
+ 3. After sign-in, the Convex query re-fires → `model.user` becomes non-null
709
+ 4. The widget detects the pending checkout and auto-triggers `checkouts.create`
710
+
711
+ This works for both **modal auth** (Clerk, Auth0 popup) and **redirect auth**
712
+ (OAuth) — no manual resume code needed.
713
+
714
+ **Safety:** The widget skips auto-resume if the user already has an active
715
+ subscription (`<Subscription.Root>`) or already owns the product
716
+ (`<Product.Root>`), preventing duplicate purchases after sign-in.
717
+
718
+ `pendingCheckout` is a tiny sessionStorage-based helper exported from the
719
+ library:
720
+
721
+ ```ts
722
+ import { pendingCheckout } from "@mmailaender/convex-creem/svelte";
723
+
724
+ pendingCheckout.save(intent); // store before sign-in
725
+ pendingCheckout.load(); // read + auto-clear (used internally by widgets)
726
+ pendingCheckout.clear(); // manual clear if needed
727
+ ```
728
+
729
+ ### Custom billing UI model
730
+
731
+ `uiModel` (from `creem.api({ resolve })`) returns everything the connected
732
+ widgets need. If you need app-specific fields, write your own query using
733
+ `creem.getBillingModel()`:
734
+
735
+ ```ts
736
+ import { query } from "./_generated/server";
737
+
738
+ export const getCustomBillingModel = query({
739
+ args: {},
740
+ handler: async (ctx) => {
741
+ const user = await currentUser(ctx);
742
+ const billingData = await creem.getBillingModel(ctx, {
743
+ entityId: user?._id ?? null,
744
+ user: user ? { _id: user._id, email: user.email } : null,
745
+ });
746
+ return {
747
+ ...billingData,
748
+ teamSize: user?.teamSize,
749
+ featureFlags: user?.featureFlags,
750
+ };
751
+ },
752
+ });
753
+ ```
754
+
755
+ ### Server endpoint overrides
756
+
757
+ Only needed for non-default API endpoints (e.g. test/staging):
758
+
759
+ ```bash
760
+ npx convex env set CREEM_SERVER_IDX <index>
761
+ # or
762
+ npx convex env set CREEM_SERVER_URL <url>
763
+ ```
764
+
765
+ Leave both unset to use the default Creem production endpoint.
766
+
767
+ ---
768
+
769
+ ## API Reference
770
+
771
+ ### Resource namespaces — `creem.<namespace>.*`
772
+
773
+ All methods take explicit arguments. Use them directly in your own Convex
774
+ functions, or let `creem.api({ resolve })` generate ready-to-export wrappers.
775
+
776
+ **`creem.subscriptions.*`**
777
+
778
+ | Method | Data source | Description |
779
+ | ---------------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
780
+ | `.getCurrent(ctx, { entityId })` | Convex DB | Current active subscription with product join |
781
+ | `.list(ctx, { entityId })` | Convex DB | Active subscriptions (excludes ended + expired trials) |
782
+ | `.listAll(ctx, { entityId })` | Convex DB | All subscriptions including ended |
783
+ | `.update(ctx, { entityId, subscriptionId?, productId?, units?, updateBehavior? })` | Creem API | Unified plan switch (`productId`) or seat update (`units`). Pass `subscriptionId` when the entity has multiple active subscriptions. Optional proration override. |
784
+ | `.cancel(ctx, { entityId, revokeImmediately? })` | Creem API | Cancel subscription |
785
+ | `.pause(ctx, { entityId })` | Creem API | Pause an active subscription |
786
+ | `.resume(ctx, { entityId })` | Creem API | Resume a paused or scheduled-cancel subscription |
787
+
788
+ **`creem.checkouts.*`**
789
+
790
+ | Method | Data source | Description |
791
+ | ------------------------------------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------- |
792
+ | `.create(ctx, { entityId, userId, email, productId, successUrl?, fallbackSuccessUrl?, units?, metadata?, theme? })` | Creem API | Create checkout URL with 3-tier `successUrl` resolution and optional `theme` |
793
+
794
+ **`creem.products.*`**
795
+
796
+ | Method | Data source | Description |
797
+ | -------------------------- | ----------- | --------------------------------------------------- |
798
+ | `.list(ctx, options?)` | Convex DB | All synced products (public — no `entityId` needed) |
799
+ | `.get(ctx, { productId })` | Convex DB | Single product by ID (public) |
800
+
801
+ **`creem.customers.*`**
802
+
803
+ | Method | Data source | Description |
804
+ | ------------------------------- | ----------- | --------------------------- |
805
+ | `.retrieve(ctx, { entityId })` | Convex DB | Customer record by entity |
806
+ | `.portalUrl(ctx, { entityId })` | Creem API | Customer billing portal URL |
807
+
808
+ **`creem.orders.*`**
809
+
810
+ | Method | Data source | Description |
811
+ | -------------------------- | ----------- | -------------------- |
812
+ | `.list(ctx, { entityId })` | Convex DB | Paid one-time orders |
813
+
814
+ **Composite helpers (top-level methods)**
815
+
816
+ | Method | Description |
817
+ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
818
+ | `creem.getBillingModel(ctx, { entityId, user? })` | Aggregates snapshot + products + subscriptions + orders into a single object for widgets. Graceful when `entityId` is null (returns public catalog only). |
819
+ | `creem.getBillingSnapshot(ctx, { entityId })` | Resolved billing state (plan, status, available actions). Uses `resolvePlan` override if configured, otherwise built-in resolver. |
820
+
821
+ ### `creem.api({ resolve })` — convenience exports
822
+
823
+ Generates ready-to-export Convex function definitions. Each function calls your
824
+ `resolve` callback, then delegates to the corresponding namespace method.
825
+
826
+ | Export | Wraps | Type | Description |
827
+ | ----------------------- | ----------------------- | ------ | ---------------------------------------------------------------------------------- |
828
+ | `uiModel` | `getBillingModel` | query | Calls `resolve()`, then `getBillingModel`. Graceful when unauthenticated. |
829
+ | `snapshot` | `getBillingSnapshot` | query | Calls `resolve()`, then `getBillingSnapshot`. Returns `null` when unauthenticated. |
830
+ | `checkouts.create` | `checkouts.create` | action | Auto-resolves auth |
831
+ | `subscriptions.update` | `subscriptions.update` | action | Auto-resolves auth |
832
+ | `subscriptions.cancel` | `subscriptions.cancel` | action | Auto-resolves auth |
833
+ | `subscriptions.resume` | `subscriptions.resume` | action | Auto-resolves auth |
834
+ | `subscriptions.pause` | `subscriptions.pause` | action | Auto-resolves auth |
835
+ | `subscriptions.list` | `subscriptions.list` | query | Auto-resolves auth |
836
+ | `subscriptions.listAll` | `subscriptions.listAll` | query | Auto-resolves auth |
837
+ | `products.list` | `products.list` | query | Public, no auth needed |
838
+ | `products.get` | `products.get` | query | Public, no auth needed |
839
+ | `customers.retrieve` | `customers.retrieve` | query | Auto-resolves auth |
840
+ | `customers.portalUrl` | `customers.portalUrl` | action | Auto-resolves auth |
841
+ | `orders.list` | `orders.list` | query | Auto-resolves auth |
842
+
843
+ ### Infrastructure
844
+
845
+ | Method | Description |
846
+ | ------------------------------------------------ | ------------------------------------------- |
847
+ | `creem.syncProducts(ctx)` | Pull products from Creem API into Convex DB |
848
+ | `creem.registerRoutes(http, { path?, events? })` | Register webhook HTTP routes |
849
+
850
+ ### Direct API access — `creem.sdk.*`
851
+
852
+ The resource namespaces above cover all **billing features that stay in sync**
853
+ with Convex via webhooks. Some Creem API resources have no webhook support, so
854
+ the component cannot mirror them in Convex DB. For these, use `creem.sdk.*`
855
+ directly inside your own Convex actions — it's the same Creem SDK client,
856
+ already configured with your API key:
857
+
858
+ | Resource | Synced to Convex? | Access |
859
+ | ---------------- | -------------------- | -------------------------- |
860
+ | Subscriptions | Yes (webhook) | `creem.subscriptions.*` |
861
+ | Checkouts | Yes (webhook) | `creem.checkouts.*` |
862
+ | Products | Yes (webhook + sync) | `creem.products.*` |
863
+ | Customers | Yes (webhook) | `creem.customers.*` |
864
+ | Orders | Yes (webhook) | `creem.orders.*` |
865
+ | **Licenses** | No webhook | `creem.sdk.licenses.*` |
866
+ | **Discounts** | No webhook | `creem.sdk.discounts.*` |
867
+ | **Transactions** | No webhook | `creem.sdk.transactions.*` |
868
+
869
+ ```ts
870
+ import { action } from "./_generated/server";
871
+ import { v } from "convex/values";
872
+
873
+ // Example: create a discount (not synced — Creem has no webhook for discounts)
874
+ export const createDiscount = action({
875
+ args: { code: v.string(), percentage: v.number() },
876
+ handler: async (ctx, args) => {
877
+ return await creem.sdk.discounts.create({
878
+ name: args.code,
879
+ code: args.code,
880
+ type: "percentage",
881
+ percentage: args.percentage,
882
+ duration: "forever",
883
+ appliesTo: [],
884
+ });
885
+ },
886
+ });
887
+ ```
888
+
889
+ ---
890
+
891
+ ## Component Reference
892
+
893
+ All components share **identical props** across Svelte and React.
894
+
895
+ - **Import:** `@mmailaender/convex-creem/svelte` or
896
+ `@mmailaender/convex-creem/react`
897
+ - **CSS class prop:** `class` in Svelte, `className` in React
898
+ - **Children:** Svelte `Snippet` / React `ReactNode`
899
+ - **Svelte** components use Svelte 5 runes and snippet rendering
900
+ (`{@render ...}`)
901
+
902
+ See the [Svelte example](example-svelte) and [React example](example-react) for
903
+ complete integrations.
904
+
905
+ ### Widgets
906
+
907
+ These query Convex directly and manage billing state end-to-end.
908
+
909
+ #### `<Subscription.Root>`
910
+
911
+ Container for subscription plan cards. Handles billing cycle toggle, checkout,
912
+ plan switching, cancellation, and seat management.
913
+
914
+ | Prop | Type | Default | Description |
915
+ | ------------------- | --------------------------------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
916
+ | `api` | `ConnectedBillingApi` | — | **Required.** Backend function references |
917
+ | `permissions` | `BillingPermissions` | all enabled | Disable actions based on user role |
918
+ | `class`/`className` | `string` | `""` | Wrapper CSS class |
919
+ | `successUrl` | `string` | product's `defaultSuccessUrl` → current page | Override redirect after checkout. When omitted, uses the product's `defaultSuccessUrl` from Creem; if that is also unset, falls back to the current page. |
920
+ | `units` | `number` | — | Auto-derived seat count for seat-based plans |
921
+ | `showSeatPicker` | `boolean` | `false` | Show quantity picker on seat-based cards |
922
+ | `twoColumnLayout` | `boolean` | `false` | Use two-column card layout |
923
+ | `updateBehavior` | `UpdateBehavior` | `"proration-charge-immediately"` | How plan switches and seat updates are billed. See below. |
924
+ | `onBeforeCheckout` | `(intent: CheckoutIntent) => Promise<boolean> \| boolean` | — | Gate checkout (auth, terms, etc.). Return `false` to abort. |
925
+ | `children` | `Snippet` / `ReactNode` | — | `<Subscription.Item>` children |
926
+
927
+ **`UpdateBehavior`** controls how the Creem API handles plan switches and seat
928
+ changes:
929
+
930
+ - `"proration-charge-immediately"` — prorate and charge the difference now
931
+ (default)
932
+ - `"proration-charge"` — prorate, charge on next invoice
933
+ - `"proration-none"` — no proration, change takes effect on next billing cycle
934
+
935
+ #### `<Subscription.Item>`
936
+
937
+ Registers a plan inside `<Subscription.Root>`. Renders nothing on its own — the
938
+ root component renders the pricing cards.
939
+
940
+ | Prop | Type | Default | Description |
941
+ | ------------- | ---------------------------------------------------- | -------------------------- | --------------------------------------------------------------------------------------------------- |
942
+ | `type` | `"free" \| "single" \| "seat-based" \| "enterprise"` | — | **Required.** Plan type |
943
+ | `planId` | `string` | first product ID or `type` | Unique plan identifier |
944
+ | `title` | `string` | from Creem product data | Plan display title |
945
+ | `description` | `string` | from Creem product data | Plan subtitle (rendered as Markdown) |
946
+ | `contactUrl` | `string` | — | "Contact sales" link. **Required when `type="enterprise"`**. |
947
+ | `recommended` | `boolean` | `false` | Highlight as recommended plan |
948
+ | `productIds` | `Partial<Record<RecurringCycle, string>>` | — | Creem product IDs keyed by billing cycle. **Required when `type="single"` or `type="seat-based"`**. |
949
+
950
+ **Supported billing cycles:** `every-month`, `every-three-months`,
951
+ `every-six-months`, `every-year`.
952
+
953
+ `Subscription` and `Subscription.Item` are aliases — use whichever reads better
954
+ in your markup.
955
+
956
+ #### `<Product.Root>`
957
+
958
+ Container for one-time or repeating product cards. Handles ownership tracking,
959
+ upgrade transitions, and checkout.
960
+
961
+ | Prop | Type | Default | Description |
962
+ | ------------------- | --------------------------------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
963
+ | `api` | `ConnectedBillingApi` | — | **Required.** Backend function references |
964
+ | `permissions` | `BillingPermissions` | all enabled | Disable actions based on user role |
965
+ | `transition` | `Transition[]` | `[]` | Upgrade path rules between products |
966
+ | `class`/`className` | `string` | `""` | Wrapper CSS class |
967
+ | `layout` | `"default" \| "single"` | `"default"` | Card layout mode |
968
+ | `styleVariant` | `"legacy" \| "pricing"` | `"legacy"` | Visual style variant |
969
+ | `showImages` | `boolean` | `false` | Show product images on cards |
970
+ | `pricingCtaVariant` | `"filled" \| "faded"` | `"faded"` | Call-to-action button style |
971
+ | `successUrl` | `string` | product's `defaultSuccessUrl` → current page | Override redirect after checkout. When omitted, uses the product's `defaultSuccessUrl` from Creem; if that is also unset, falls back to the current page. |
972
+ | `onBeforeCheckout` | `(intent: CheckoutIntent) => Promise<boolean> \| boolean` | — | Gate checkout (auth, terms, etc.). Return `false` to abort. |
973
+ | `children` | `Snippet` / `ReactNode` | — | `<Product.Item>` children |
974
+
975
+ **Transition types:**
976
+
977
+ ```ts
978
+ type Transition =
979
+ | { from: string; to: string; kind: "direct" }
980
+ | { from: string; to: string; kind: "via_product"; viaProductId: string };
981
+ ```
982
+
983
+ #### `<Product.Item>`
984
+
985
+ Registers a product inside `<Product.Root>`.
986
+
987
+ | Prop | Type | Default | Description |
988
+ | ------------- | --------------------------- | ----------------------- | --------------------------------------------------------- |
989
+ | `productId` | `string` | — | **Required.** Creem product ID |
990
+ | `type` | `"one-time" \| "recurring"` | — | **Required.** One-time shows "Owned" badge after purchase |
991
+ | `title` | `string` | from Creem product data | Card display title |
992
+ | `description` | `string` | from Creem product data | Card subtitle (rendered as Markdown) |
993
+
994
+ `Product` and `Product.Item` are aliases.
995
+
996
+ #### `<BillingPortal>`
997
+
998
+ Button that opens the Creem customer billing portal. Auto-hides when the billing
999
+ entity has no Creem customer record, or when `canAccessPortal` is `false`.
1000
+
1001
+ | Prop | Type | Default | Description |
1002
+ | ------------------- | ----------------------- | ------------------ | --------------------------------------------------------- |
1003
+ | `api` | `ConnectedBillingApi` | — | **Required.** Backend function references |
1004
+ | `permissions` | `BillingPermissions` | all enabled | Control portal access (e.g. `{ canAccessPortal: false }`) |
1005
+ | `class`/`className` | `string` | `""` | Button CSS class |
1006
+ | `children` | `Snippet` / `ReactNode` | `"Manage billing"` | Custom button label |
1007
+
1008
+ ### Presentational components
1009
+
1010
+ Lower-level building blocks for custom layouts. These do **not** call Convex
1011
+ directly — pass data and callbacks as props.
1012
+
1013
+ #### `<PricingSection>`
1014
+
1015
+ Renders a grid of pricing cards with an optional billing cycle toggle.
1016
+
1017
+ | Prop | Type | Description |
1018
+ | ----------------------- | ------------------------- | ------------------------------------- |
1019
+ | `plans` | `UIPlanEntry[]` | Plan definitions |
1020
+ | `snapshot` | `BillingSnapshot \| null` | Current billing state |
1021
+ | `selectedCycle` | `RecurringCycle` | Active billing cycle |
1022
+ | `products` | `ConnectedProduct[]` | Product data for price resolution |
1023
+ | `subscriptionProductId` | `string \| null` | Currently subscribed product |
1024
+ | `subscriptionStatus` | `string \| null` | Subscription status |
1025
+ | `units` | `number` | Seat count |
1026
+ | `showSeatPicker` | `boolean` | Show quantity picker |
1027
+ | `subscribedSeats` | `number \| null` | Current seat count |
1028
+ | `isGroupSubscribed` | `boolean` | Whether group has active subscription |
1029
+ | `disableCheckout` | `boolean` | Disable checkout buttons |
1030
+ | `disableSwitch` | `boolean` | Disable plan switch buttons |
1031
+ | `disableSeats` | `boolean` | Disable seat controls |
1032
+ | `onCycleChange` | `(cycle) => void` | Billing cycle change handler |
1033
+ | `onCheckout` | `(payload) => void` | Checkout handler |
1034
+ | `onSwitchPlan` | `(payload) => void` | Plan switch handler |
1035
+ | `onUpdateSeats` | `(payload) => void` | Seat update handler |
1036
+
1037
+ #### `<PricingCard>`
1038
+
1039
+ A single plan card with price, description, and action button. Same props as
1040
+ `<PricingSection>` for a single plan (see source for full list).
1041
+
1042
+ #### `<BillingToggle>`
1043
+
1044
+ Billing cycle segment control (e.g. Monthly / Yearly).
1045
+
1046
+ | Prop | Type | Description |
1047
+ | --------------- | ------------------ | ---------------- |
1048
+ | `cycles` | `RecurringCycle[]` | Available cycles |
1049
+ | `value` | `RecurringCycle` | Selected cycle |
1050
+ | `onValueChange` | `(cycle) => void` | Change handler |
1051
+ | `className` | `string` | CSS class |
1052
+
1053
+ #### `<CheckoutButton>`
1054
+
1055
+ Styled checkout button. Supports both `onCheckout` callback and `href` link
1056
+ modes.
1057
+
1058
+ | Prop | Type | Description |
1059
+ | ------------ | ----------------------- | ---------------------------------- |
1060
+ | `productId` | `string` | Product ID |
1061
+ | `href` | `string` | Link mode: direct URL |
1062
+ | `disabled` | `boolean` | Disable button |
1063
+ | `className` | `string` | CSS class |
1064
+ | `onCheckout` | `(payload) => void` | Callback mode: `{ productId }` |
1065
+ | `children` | `Snippet` / `ReactNode` | Button label (default: "Checkout") |
1066
+
1067
+ #### `<OneTimeCheckoutButton>`
1068
+
1069
+ Same as `<CheckoutButton>` with default label "Buy now".
1070
+
1071
+ #### `<CustomerPortalButton>`
1072
+
1073
+ Styled button for opening the customer billing portal.
1074
+
1075
+ | Prop | Type | Description |
1076
+ | -------------- | ----------------------- | ---------------------------------------- |
1077
+ | `href` | `string` | Link mode: direct URL |
1078
+ | `disabled` | `boolean` | Disable button |
1079
+ | `className` | `string` | CSS class |
1080
+ | `onOpenPortal` | `() => void` | Callback mode |
1081
+ | `children` | `Snippet` / `ReactNode` | Button label (default: "Manage billing") |
1082
+
1083
+ #### `<BillingGate>`
1084
+
1085
+ Conditionally renders children based on available billing actions.
1086
+
1087
+ | Prop | Type | Description |
1088
+ | ----------------- | -------------------------------------- | --------------------------------------- |
1089
+ | `snapshot` | `BillingSnapshot \| null` | Current billing state |
1090
+ | `requiredActions` | `AvailableAction \| AvailableAction[]` | Actions that must be available |
1091
+ | `children` | `Snippet` / `ReactNode` | Rendered when all actions are available |
1092
+ | `fallback` | `Snippet` / `ReactNode` | Rendered otherwise |
1093
+
1094
+ #### `<CheckoutSuccessSummary>`
1095
+
1096
+ Displays a success banner after checkout. Parses Creem query params
1097
+ automatically.
1098
+
1099
+ | Prop | Type | Description |
1100
+ | ----------- | ----------------------- | ------------------------------------------------------------ |
1101
+ | `params` | `CheckoutSuccessParams` | Manual params (overrides URL parsing) |
1102
+ | `search` | `string` | Query string to parse (defaults to `window.location.search`) |
1103
+ | `className` | `string` | CSS class |
1104
+
1105
+ React also exports a `useCheckoutSuccessParams()` hook that returns the parsed
1106
+ params directly.
1107
+
1108
+ #### `<TrialLimitBanner>`
1109
+
1110
+ Shows a trial expiration notice.
1111
+
1112
+ | Prop | Type | Description |
1113
+ | ------------- | ------------------------- | ----------------------- |
1114
+ | `snapshot` | `BillingSnapshot \| null` | Current billing state |
1115
+ | `trialEndsAt` | `string \| null` | Override trial end date |
1116
+ | `className` | `string` | CSS class |
1117
+
1118
+ #### `<ScheduledChangeBanner>`
1119
+
1120
+ Shows a cancellation-scheduled notice with optional "Undo" button.
1121
+
1122
+ | Prop | Type | Description |
1123
+ | ----------- | ------------------------- | ------------------------------------------------- |
1124
+ | `snapshot` | `BillingSnapshot \| null` | Current billing state |
1125
+ | `isLoading` | `boolean` | Loading state for resume button |
1126
+ | `onResume` | `() => void` | Resume handler (shows "Undo cancellation" button) |
1127
+ | `className` | `string` | CSS class |
1128
+
1129
+ #### `<PaymentWarningBanner>`
1130
+
1131
+ Shows a warning for pending, refunded, or partially refunded payments.
1132
+
1133
+ | Prop | Type | Description |
1134
+ | ----------- | ------------------------- | --------------------- |
1135
+ | `snapshot` | `BillingSnapshot \| null` | Current billing state |
1136
+ | `payment` | `PaymentSnapshot \| null` | Override payment data |
1137
+ | `className` | `string` | CSS class |
1138
+
1139
+ #### `<OneTimePaymentStatusBadge>`
1140
+
1141
+ Inline status badge for one-time payments.
1142
+
1143
+ | Prop | Type | Description |
1144
+ | ----------- | ----------------------------------------------------------- | -------------- |
1145
+ | `status` | `"pending" \| "paid" \| "refunded" \| "partially_refunded"` | Payment status |
1146
+ | `className` | `string` | CSS class |
1147
+
1148
+ ---
1149
+
1150
+ ## Troubleshooting
1151
+
1152
+ **Webhooks not receiving events** Verify your Creem dashboard webhook URL
1153
+ matches `<CONVEX_SITE_URL>/creem/events`. Check that `CREEM_WEBHOOK_SECRET`
1154
+ matches the signing secret in Creem. Check the Convex dashboard logs for
1155
+ verification errors.
1156
+
1157
+ **Products not syncing** Run `npx convex run billing:syncBillingProducts` after
1158
+ setting up webhooks. Ensure `CREEM_API_KEY` is set and the key has read access
1159
+ to products.
1160
+
1161
+ **Widgets rendering unstyled** Ensure both Tailwind CSS v4 and
1162
+ `@import "@mmailaender/convex-creem/styles"` are in your CSS entry point. The
1163
+ styles import must come after the Tailwind import.
1164
+
1165
+ **Checkout URL missing from response** The Creem API returned no checkout URL.
1166
+ Verify the product ID exists and is active in your Creem dashboard. Check the
1167
+ Convex dashboard logs for the full error.
1168
+
1169
+ **Entity/org billing not scoping correctly** Ensure `billingEntityId` is
1170
+ returned from `getUserInfo`. If omitted, `userId` is used as the billing entity.
1171
+ Verify that checkout metadata includes `convexBillingEntityId` by checking
1172
+ webhook logs.
1173
+
1174
+ **"Customer not found" when opening billing portal** The customer record is
1175
+ created on first checkout. If the user hasn't completed a checkout yet, there's
1176
+ no customer to link to the portal.