kasy-cli 1.21.9 → 1.23.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 (269) hide show
  1. package/lib/commands/add.js +93 -80
  2. package/lib/commands/configure.js +100 -32
  3. package/lib/commands/doctor.js +28 -2
  4. package/lib/commands/new.js +80 -37
  5. package/lib/commands/notifications.js +1 -1
  6. package/lib/commands/remove.js +43 -15
  7. package/lib/commands/run.js +2 -2
  8. package/lib/commands/update.js +2 -2
  9. package/lib/scaffold/CHANGELOG.json +14 -0
  10. package/lib/scaffold/backends/api/generator.js +14 -14
  11. package/lib/scaffold/backends/api/patch/README.md +83 -0
  12. package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
  13. package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +5 -0
  14. package/lib/scaffold/backends/api/patch/lib/{environnements.dart → environments.dart} +3 -11
  15. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_api.dart +108 -0
  16. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +51 -0
  17. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +5 -5
  18. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +71 -38
  19. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +2 -2
  20. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +54 -0
  21. package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +16 -16
  22. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  23. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  24. package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +100 -0
  25. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  26. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +60 -0
  27. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  28. package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -5
  29. package/lib/scaffold/backends/firebase/deploy.js +87 -13
  30. package/lib/scaffold/backends/firebase/generator.js +5 -5
  31. package/lib/scaffold/backends/firebase/tokens.js +4 -4
  32. package/lib/scaffold/backends/supabase/deploy.js +63 -11
  33. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
  34. package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
  35. package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
  36. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
  37. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
  38. package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
  39. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
  40. package/lib/scaffold/backends/supabase/generator.js +17 -17
  41. package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
  42. package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
  43. package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
  44. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
  45. package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
  46. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
  47. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
  48. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  49. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
  50. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
  51. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
  52. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
  53. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  54. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  55. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
  56. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  57. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
  58. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  59. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
  60. package/lib/scaffold/backends/supabase/tokens.js +3 -3
  61. package/lib/scaffold/catalog.js +9 -11
  62. package/lib/scaffold/generate.js +45 -31
  63. package/lib/scaffold/shared/generator-utils.js +188 -81
  64. package/lib/scaffold/shared/sort-imports.js +191 -0
  65. package/lib/scaffold/shared/template-strings.js +3 -3
  66. package/lib/utils/checks.js +22 -6
  67. package/lib/utils/env-tools.js +7 -0
  68. package/lib/utils/flutter-install.js +114 -0
  69. package/lib/utils/i18n/messages-en.js +52 -35
  70. package/lib/utils/i18n/messages-es.js +52 -35
  71. package/lib/utils/i18n/messages-pt.js +54 -37
  72. package/lib/utils/updates.js +15 -15
  73. package/package.json +1 -1
  74. package/templates/firebase/.env.example +2 -2
  75. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  76. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  77. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  78. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  79. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  80. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  81. package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
  82. package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
  83. package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
  84. package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
  85. package/templates/firebase/firestore.rules +24 -5
  86. package/templates/firebase/functions/package-lock.json +22 -1
  87. package/templates/firebase/functions/package.json +2 -1
  88. package/templates/firebase/functions/src/admin/functions.ts +113 -0
  89. package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
  90. package/templates/firebase/functions/src/index.ts +8 -2
  91. package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
  92. package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
  93. package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
  94. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
  95. package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
  96. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  97. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  98. package/templates/firebase/lib/components/components.dart +4 -1
  99. package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
  100. package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
  101. package/templates/firebase/lib/components/kasy_button.dart +23 -99
  102. package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
  103. package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
  104. package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +702 -425
  105. package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
  106. package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
  107. package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
  108. package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
  109. package/templates/firebase/lib/components/kasy_toast.dart +2 -2
  110. package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
  111. package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
  112. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +136 -23
  113. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
  114. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
  115. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +54 -0
  116. package/templates/firebase/lib/core/config/app_env.dart +5 -11
  117. package/templates/firebase/lib/core/config/features.dart +5 -4
  118. package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
  119. package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
  120. package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
  121. package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
  122. package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
  123. package/templates/firebase/lib/core/data/models/user.dart +11 -0
  124. package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
  125. package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
  126. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
  127. package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
  128. package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
  129. package/templates/firebase/lib/core/states/logout_action.dart +25 -0
  130. package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
  131. package/templates/firebase/lib/core/theme/colors.dart +488 -188
  132. package/templates/firebase/lib/core/theme/radius.dart +22 -11
  133. package/templates/firebase/lib/core/theme/shadows.dart +66 -0
  134. package/templates/firebase/lib/core/theme/texts.dart +75 -41
  135. package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
  136. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
  137. package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
  138. package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
  139. package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
  140. package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
  141. package/templates/firebase/lib/core/widgets/kasy_hover.dart +53 -14
  142. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
  143. package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
  144. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
  145. package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
  146. package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
  147. package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  148. package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
  149. package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
  150. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
  151. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
  152. package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
  153. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
  154. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
  155. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
  156. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
  157. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
  158. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
  159. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
  160. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
  161. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +128 -0
  162. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
  163. package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
  164. package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
  165. package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
  166. package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
  167. package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
  168. package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
  169. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
  170. package/templates/firebase/lib/features/home/home_feed.dart +289 -0
  171. package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
  172. package/templates/firebase/lib/features/home/home_page.dart +11 -250
  173. package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
  174. package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
  175. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
  176. package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
  177. package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
  178. package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
  179. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  180. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
  181. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
  182. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
  183. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
  184. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
  185. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
  186. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
  187. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
  188. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
  189. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
  190. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  191. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
  192. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
  193. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
  194. package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
  195. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
  196. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
  197. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
  198. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
  199. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
  200. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
  201. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
  202. package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
  203. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
  204. package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
  205. package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
  206. package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
  207. package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
  208. package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
  209. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
  210. package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  211. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
  212. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
  213. package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
  214. package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
  215. package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
  216. package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
  217. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
  218. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
  219. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
  220. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
  221. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
  222. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
  223. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
  224. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
  225. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
  226. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
  227. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
  228. package/templates/firebase/lib/i18n/en.i18n.json +171 -46
  229. package/templates/firebase/lib/i18n/es.i18n.json +175 -50
  230. package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
  231. package/templates/firebase/lib/main.dart +6 -3
  232. package/templates/firebase/lib/router.dart +15 -23
  233. package/templates/firebase/pubspec.yaml +5 -6
  234. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
  235. package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
  236. package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
  237. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
  238. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
  239. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
  240. package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
  241. package/templates/firebase/test/test_utils.dart +6 -6
  242. package/templates/firebase/web/index.html +5 -2
  243. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
  244. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
  245. package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
  246. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
  247. package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
  248. package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
  249. package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
  250. package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
  251. package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
  252. package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
  253. package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
  254. package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
  255. package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
  256. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
  257. package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
  258. /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
  259. /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
  260. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
  261. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
  262. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
  263. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
  264. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
  265. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
  266. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
  267. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
  268. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
  269. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
@@ -0,0 +1,189 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:kasy_kit/core/data/models/subscription.dart';
3
+ import 'package:kasy_kit/i18n/translations.g.dart';
4
+ import 'package:purchases_flutter/purchases_flutter.dart';
5
+
6
+ /// RevenueCat-backed [SubscriptionProduct] (mobile: App Store / Play Store).
7
+ ///
8
+ /// Lives in its own file (not in `subscription.dart`) so the subscription core
9
+ /// stays free of `purchases_flutter`. Only compiled when the RevenueCat module
10
+ /// is enabled.
11
+ class RevenueCatProduct implements SubscriptionProduct {
12
+ final Offering revenueCatOffer;
13
+ final Package revenueCatPackage;
14
+
15
+ RevenueCatProduct({
16
+ required this.revenueCatOffer,
17
+ required this.revenueCatPackage,
18
+ });
19
+
20
+ @override
21
+ String get skuId => revenueCatPackage.identifier;
22
+
23
+ @override
24
+ String get id => revenueCatOffer.identifier;
25
+
26
+ @override
27
+ String get description => revenueCatOffer.serverDescription;
28
+
29
+ @override
30
+ String get label =>
31
+ revenueCatPackage.presentedOfferingContext.offeringIdentifier;
32
+
33
+ @override
34
+ double get price => revenueCatPackage.storeProduct.price;
35
+
36
+ @override
37
+ String get currency => revenueCatPackage.storeProduct.currencyCode;
38
+
39
+ @override
40
+ int? get trialDays {
41
+ final introductory = revenueCatPackage.storeProduct.introductoryPrice;
42
+ if (introductory == null) {
43
+ return null;
44
+ }
45
+ if (introductory.price == 0) {
46
+ final unit = introductory.periodUnit;
47
+ switch (unit) {
48
+ case PeriodUnit.day:
49
+ return introductory.periodNumberOfUnits;
50
+ case PeriodUnit.week:
51
+ return introductory.periodNumberOfUnits * 7;
52
+ case PeriodUnit.month:
53
+ return introductory.periodNumberOfUnits * 30;
54
+ case PeriodUnit.year:
55
+ return introductory.periodNumberOfUnits * 365;
56
+ default:
57
+ return null;
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+
63
+ @override
64
+ String? get promotion {
65
+ if (revenueCatOffer.metadata["promotions"] == null) {
66
+ return null;
67
+ }
68
+ final data =
69
+ revenueCatOffer.metadata["promotions"]! as Map<Object?, Object?>;
70
+ final locale = LocaleSettings.currentLocale.languageCode;
71
+ final id = skuId;
72
+ if (data.containsKey(id)) {
73
+ final promotion = data[id]! as Map<Object?, Object?>;
74
+ return promotion[locale]! as String;
75
+ }
76
+ return null;
77
+ }
78
+
79
+ @override
80
+ String? get title {
81
+ return revenueCatPackage.storeProduct.title;
82
+ }
83
+
84
+ @override
85
+ List<String>? get features {
86
+ final locale = LocaleSettings.currentLocale.languageCode;
87
+ if (revenueCatOffer.metadata[locale] == null) {
88
+ return null;
89
+ }
90
+ final data = revenueCatOffer.metadata[locale]! as Map<Object?, Object?>;
91
+ final featuerObj = data["features"]! as List<Object?>;
92
+ return featuerObj.map((e) => e! as String).toList();
93
+ }
94
+
95
+ @override
96
+ String get priceString {
97
+ return revenueCatPackage.storeProduct.priceString;
98
+ }
99
+
100
+ @override
101
+ Duration get duration =>
102
+ switch (revenueCatPackage.storeProduct.subscriptionPeriod) {
103
+ 'P1W' => const Duration(days: 7),
104
+ 'P1M' => const Duration(days: 30),
105
+ 'P3M' => const Duration(days: 90),
106
+ 'P6M' => const Duration(days: 180),
107
+ 'P1Y' => const Duration(days: 365),
108
+ _ => Duration.zero,
109
+ };
110
+
111
+ @override
112
+ DurationType get durationType {
113
+ final durationCpy = duration;
114
+ if (durationCpy.inDays == 7) {
115
+ return DurationType.week;
116
+ } else if (durationCpy.inDays == 30) {
117
+ return DurationType.month;
118
+ } else if (durationCpy.inDays == 90) {
119
+ return DurationType.threeMonth;
120
+ } else if (durationCpy.inDays == 180) {
121
+ return DurationType.sixMonth;
122
+ } else if (durationCpy.inDays == 365) {
123
+ return DurationType.year;
124
+ }
125
+ return DurationType.lifetime;
126
+ }
127
+
128
+ @override
129
+ String formattedPrice(BuildContext context) {
130
+ final translatedDuration = switch (durationType) {
131
+ DurationType.week => Translations.of(context).premium.duration_weekly,
132
+ DurationType.year => Translations.of(context).premium.duration_annual,
133
+ DurationType.month => Translations.of(context).premium.duration_monthly,
134
+ DurationType.lifetime => Translations.of(
135
+ context,
136
+ ).premium.duration_lifetime,
137
+ _ => "",
138
+ };
139
+ return "${revenueCatPackage.storeProduct.priceString} $translatedDuration";
140
+ }
141
+
142
+ @override
143
+ String pricePerMonth(BuildContext context) {
144
+ final translatedDuration = Translations.of(
145
+ context,
146
+ ).premium.duration_monthly;
147
+ if (durationType == DurationType.lifetime) {
148
+ return revenueCatPackage.storeProduct.priceString;
149
+ }
150
+ final pricePerMonth = switch (durationType) {
151
+ DurationType.year => price / 12,
152
+ DurationType.threeMonth => price / 3,
153
+ DurationType.sixMonth => price / 6,
154
+ DurationType.week => (price * 4) / 12,
155
+ _ => 1,
156
+ };
157
+ final pricePerMonthString = pricePerMonth.toStringAsFixed(2);
158
+ final money = revenueCatPackage.storeProduct.currencyCode;
159
+ if (money == "USD") {
160
+ return "\$$pricePerMonthString/$translatedDuration";
161
+ } else if (money == "EUR") {
162
+ return "$pricePerMonthString/$translatedDuration";
163
+ }
164
+ return "$pricePerMonthString $money/$translatedDuration";
165
+ }
166
+
167
+ @override
168
+ String? pricePerYear(BuildContext context) {
169
+ final translatedDuration = Translations.of(context).premium.duration_annual;
170
+ if (durationType == DurationType.lifetime) {
171
+ return revenueCatPackage.storeProduct.priceString;
172
+ }
173
+ final pricePerMonth = switch (durationType) {
174
+ DurationType.year => price,
175
+ DurationType.threeMonth => price * 4,
176
+ DurationType.sixMonth => price * 2,
177
+ DurationType.week => (price * 4) * 12,
178
+ _ => 1,
179
+ };
180
+ final pricePerMonthString = pricePerMonth.toStringAsFixed(2);
181
+ final money = revenueCatPackage.storeProduct.currencyCode;
182
+ if (money == "USD") {
183
+ return "\$$pricePerMonthString/$translatedDuration";
184
+ } else if (money == "EUR") {
185
+ return "$pricePerMonthString/$translatedDuration";
186
+ }
187
+ return "$pricePerMonthString $money/$translatedDuration";
188
+ }
189
+ }
@@ -0,0 +1,55 @@
1
+ import 'package:cloud_functions/cloud_functions.dart';
2
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
3
+ import 'package:kasy_kit/features/subscriptions/api/stripe_product.dart';
4
+
5
+ final stripeBackendApiProvider = Provider<StripeBackendApi>(
6
+ (ref) => StripeBackendApi(
7
+ functions: FirebaseFunctions.instanceFor(region: 'europe-west1'),
8
+ ),
9
+ );
10
+
11
+ /// Talks to the Stripe backend (Firebase Cloud Functions). The user identity is
12
+ /// taken server-side from the authenticated callable context — never trusted
13
+ /// from the client. This file is replaced per backend (Supabase / API) so the
14
+ /// rest of the Stripe client code stays backend-agnostic.
15
+ class StripeBackendApi {
16
+ final FirebaseFunctions _functions;
17
+
18
+ StripeBackendApi({required FirebaseFunctions functions})
19
+ : _functions = functions;
20
+
21
+ /// Active recurring prices, mapped to paywall offers.
22
+ Future<List<StripeProduct>> listPrices() async {
23
+ final res = await _functions
24
+ .httpsCallable('stripeFunctions-listPrices')
25
+ .call();
26
+ final list = (res.data as List).cast<dynamic>();
27
+ return list
28
+ .map((e) => StripeProduct.fromJson(Map<String, dynamic>.from(e as Map)))
29
+ .toList();
30
+ }
31
+
32
+ /// Create a hosted Checkout session for [priceId] and return its URL.
33
+ Future<String> createCheckoutSession({
34
+ required String priceId,
35
+ String? successUrl,
36
+ String? cancelUrl,
37
+ }) async {
38
+ final res = await _functions
39
+ .httpsCallable('stripeFunctions-createCheckoutSession')
40
+ .call({
41
+ 'priceId': priceId,
42
+ if (successUrl != null) 'successUrl': successUrl,
43
+ if (cancelUrl != null) 'cancelUrl': cancelUrl,
44
+ });
45
+ return (res.data as Map)['url'] as String;
46
+ }
47
+
48
+ /// Create a Customer Portal session (manage / cancel) and return its URL.
49
+ Future<String> createPortalSession({String? returnUrl}) async {
50
+ final res = await _functions
51
+ .httpsCallable('stripeFunctions-createPortalSession')
52
+ .call({if (returnUrl != null) 'returnUrl': returnUrl});
53
+ return (res.data as Map)['url'] as String;
54
+ }
55
+ }
@@ -0,0 +1,82 @@
1
+ import 'package:kasy_kit/core/data/models/entitlement.dart';
2
+ import 'package:kasy_kit/core/data/models/subscription.dart';
3
+ import 'package:kasy_kit/features/subscriptions/api/entities/subscription_entity.dart';
4
+ import 'package:kasy_kit/features/subscriptions/api/stripe_backend_api.dart';
5
+ import 'package:kasy_kit/features/subscriptions/api/subscription_payment_api.dart';
6
+ import 'package:url_launcher/url_launcher.dart';
7
+
8
+ /// Stripe web subscription provider.
9
+ ///
10
+ /// Uses hosted Stripe Checkout + Customer Portal: all card handling / PCI stay
11
+ /// on Stripe and the client only opens the URLs the backend returns. Premium
12
+ /// status lives server-side (the Stripe webhook writes the `subscriptions`
13
+ /// record), so this provider exposes no local entitlements — the repository
14
+ /// reads the status from the backend just like for RevenueCat.
15
+ class StripePaymentApi implements SubscriptionPaymentApi {
16
+ final StripeBackendApi _backend;
17
+
18
+ StripePaymentApi({required StripeBackendApi backend}) : _backend = backend;
19
+
20
+ @override
21
+ Future<void> init() async {}
22
+
23
+ @override
24
+ Future<void> initUser(String userId) async {}
25
+
26
+ @override
27
+ Future<void> disconnectUser() async {}
28
+
29
+ @override
30
+ Future<List<SubscriptionProduct>> getOffers(String? offerId) {
31
+ return _backend.listPrices();
32
+ }
33
+
34
+ @override
35
+ Future<void> purchaseProduct(SubscriptionProduct product) async {
36
+ final returnUrl = Uri.base.toString();
37
+ final url = await _backend.createCheckoutSession(
38
+ priceId: product.skuId,
39
+ successUrl: returnUrl,
40
+ cancelUrl: returnUrl,
41
+ );
42
+ await _open(url);
43
+ }
44
+
45
+ @override
46
+ Future<void> unsubscribe(SubscriptionStore? origin) async {
47
+ final url = await _backend.createPortalSession(
48
+ returnUrl: Uri.base.toString(),
49
+ );
50
+ await _open(url);
51
+ }
52
+
53
+ @override
54
+ Future<void> restorePurchase() async {
55
+ // Stripe status is server-side (webhook -> backend). Nothing to restore on
56
+ // the device; the repository re-reads the subscription from the backend.
57
+ }
58
+
59
+ @override
60
+ Future<List<Entitlement>> getPermissions() async => [];
61
+
62
+ @override
63
+ Future<List<Entitlement>?> getEntitlements() async => [];
64
+
65
+ @override
66
+ Future<SubscriptionProduct?> getFromProductId(String productId) async {
67
+ final prices = await _backend.listPrices();
68
+ for (final price in prices) {
69
+ if (price.skuId == productId) return price;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ @override
75
+ Future<void> presentCodeRedemptionSheet() async {}
76
+
77
+ Future<void> _open(String url) async {
78
+ // On web, redirect the current tab so the user returns to the app after
79
+ // Stripe Checkout / the Customer Portal.
80
+ await launchUrl(Uri.parse(url), webOnlyWindowName: '_self');
81
+ }
82
+ }
@@ -0,0 +1,178 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:kasy_kit/core/data/models/subscription.dart';
3
+ import 'package:kasy_kit/i18n/translations.g.dart';
4
+
5
+ /// Stripe-backed [SubscriptionProduct] (web). Built from a Stripe Price returned
6
+ /// by the `stripe-list-prices` backend function, so the existing paywall UI
7
+ /// renders it exactly like a RevenueCat product — no UI changes needed.
8
+ class StripeProduct implements SubscriptionProduct {
9
+ /// Stripe Price id (`price_...`) — used as the sku and to start checkout.
10
+ final String priceId;
11
+
12
+ /// Stripe Product id (`prod_...`).
13
+ final String productId;
14
+
15
+ /// Stripe Product name.
16
+ final String productName;
17
+
18
+ final String _description;
19
+
20
+ /// Amount in the currency's minor unit (e.g. cents).
21
+ final int unitAmount;
22
+
23
+ final String _currency;
24
+
25
+ /// Recurring interval reported by Stripe: `day` | `week` | `month` | `year`.
26
+ final String interval;
27
+
28
+ /// Recurring interval count (e.g. 3 months).
29
+ final int intervalCount;
30
+
31
+ final int? _trialDays;
32
+
33
+ final List<String>? _features;
34
+
35
+ StripeProduct({
36
+ required this.priceId,
37
+ required this.productId,
38
+ required this.productName,
39
+ required String description,
40
+ required this.unitAmount,
41
+ required String currency,
42
+ required this.interval,
43
+ this.intervalCount = 1,
44
+ int? trialDays,
45
+ List<String>? features,
46
+ }) : _description = description,
47
+ _currency = currency,
48
+ _trialDays = trialDays,
49
+ _features = features;
50
+
51
+ factory StripeProduct.fromJson(Map<String, dynamic> json) => StripeProduct(
52
+ priceId: json['priceId'] as String,
53
+ productId: (json['productId'] ?? '') as String,
54
+ productName: (json['productName'] ?? '') as String,
55
+ description: (json['description'] ?? '') as String,
56
+ unitAmount: (json['unitAmount'] as num?)?.toInt() ?? 0,
57
+ currency: (json['currency'] ?? 'usd') as String,
58
+ interval: (json['interval'] ?? 'month') as String,
59
+ intervalCount: (json['intervalCount'] as num?)?.toInt() ?? 1,
60
+ trialDays: (json['trialDays'] as num?)?.toInt(),
61
+ features: (json['features'] as List?)?.map((e) => e.toString()).toList(),
62
+ );
63
+
64
+ @override
65
+ String get skuId => priceId;
66
+
67
+ @override
68
+ String get id => productId;
69
+
70
+ @override
71
+ String get description => _description;
72
+
73
+ @override
74
+ String get label => productName;
75
+
76
+ @override
77
+ double get price => unitAmount / 100.0;
78
+
79
+ @override
80
+ String get currency => _currency.toUpperCase();
81
+
82
+ @override
83
+ int? get trialDays => _trialDays;
84
+
85
+ @override
86
+ String? get promotion => null;
87
+
88
+ @override
89
+ String? get title => productName;
90
+
91
+ @override
92
+ List<String>? get features => _features;
93
+
94
+ @override
95
+ String get priceString => _formatMoney(price);
96
+
97
+ @override
98
+ Duration get duration => switch (durationType) {
99
+ DurationType.week => const Duration(days: 7),
100
+ DurationType.month => const Duration(days: 30),
101
+ DurationType.threeMonth => const Duration(days: 90),
102
+ DurationType.sixMonth => const Duration(days: 180),
103
+ DurationType.year => const Duration(days: 365),
104
+ DurationType.lifetime => Duration.zero,
105
+ };
106
+
107
+ @override
108
+ DurationType get durationType {
109
+ switch (interval) {
110
+ case 'week':
111
+ return DurationType.week;
112
+ case 'year':
113
+ return DurationType.year;
114
+ case 'month':
115
+ if (intervalCount == 3) return DurationType.threeMonth;
116
+ if (intervalCount == 6) return DurationType.sixMonth;
117
+ if (intervalCount >= 12) return DurationType.year;
118
+ return DurationType.month;
119
+ default:
120
+ return DurationType.month;
121
+ }
122
+ }
123
+
124
+ String _formatMoney(double value) {
125
+ final amount = value.toStringAsFixed(2);
126
+ if (currency == 'USD') return "\$$amount";
127
+ if (currency == 'EUR') return "$amount€";
128
+ return "$amount $currency";
129
+ }
130
+
131
+ @override
132
+ String formattedPrice(BuildContext context) {
133
+ final translatedDuration = switch (durationType) {
134
+ DurationType.week => Translations.of(context).premium.duration_weekly,
135
+ DurationType.year => Translations.of(context).premium.duration_annual,
136
+ DurationType.month => Translations.of(context).premium.duration_monthly,
137
+ DurationType.lifetime => Translations.of(
138
+ context,
139
+ ).premium.duration_lifetime,
140
+ _ => "",
141
+ };
142
+ return "$priceString $translatedDuration";
143
+ }
144
+
145
+ @override
146
+ String pricePerMonth(BuildContext context) {
147
+ final translatedDuration = Translations.of(
148
+ context,
149
+ ).premium.duration_monthly;
150
+ if (durationType == DurationType.lifetime) {
151
+ return priceString;
152
+ }
153
+ final monthly = switch (durationType) {
154
+ DurationType.year => price / 12,
155
+ DurationType.threeMonth => price / 3,
156
+ DurationType.sixMonth => price / 6,
157
+ DurationType.week => (price * 4) / 12,
158
+ _ => price,
159
+ };
160
+ return "${_formatMoney(monthly)}/$translatedDuration";
161
+ }
162
+
163
+ @override
164
+ String? pricePerYear(BuildContext context) {
165
+ final translatedDuration = Translations.of(context).premium.duration_annual;
166
+ if (durationType == DurationType.lifetime) {
167
+ return priceString;
168
+ }
169
+ final yearly = switch (durationType) {
170
+ DurationType.year => price,
171
+ DurationType.threeMonth => price * 4,
172
+ DurationType.sixMonth => price * 2,
173
+ DurationType.week => (price * 4) * 12,
174
+ _ => price * 12,
175
+ };
176
+ return "${_formatMoney(yearly)}/$translatedDuration";
177
+ }
178
+ }
@@ -1,7 +1,7 @@
1
1
  import 'package:cloud_firestore/cloud_firestore.dart';
2
2
  import 'package:flutter_riverpod/flutter_riverpod.dart';
3
3
  import 'package:kasy_kit/core/data/api/firestore_retry.dart';
4
- import 'package:kasy_kit/features/subscription/api/entities/subscription_entity.dart';
4
+ import 'package:kasy_kit/features/subscriptions/api/entities/subscription_entity.dart';
5
5
 
6
6
  final subscriptionApiProvider = Provider(
7
7
  (ref) => SubscriptionApi(client: FirebaseFirestore.instance),
@@ -0,0 +1,53 @@
1
+ import 'package:kasy_kit/core/data/models/entitlement.dart';
2
+ import 'package:kasy_kit/core/data/models/subscription.dart';
3
+ import 'package:kasy_kit/features/subscriptions/api/entities/subscription_entity.dart';
4
+
5
+ /// Provider-agnostic contract for a subscription payment provider.
6
+ ///
7
+ /// Implemented by `RevenueCatPaymentApi` (mobile: App Store / Play Store) and
8
+ /// `StripePaymentApi` (web). The repository depends on this interface only, so
9
+ /// the right provider is injected per platform and a project can ship either,
10
+ /// both, or (web-only) just Stripe — without pulling the other's SDK.
11
+ abstract interface class SubscriptionPaymentApi {
12
+ /// Configure the provider SDK (no-op on platforms it does not support).
13
+ Future<void> init();
14
+
15
+ /// Associate the current app user with the provider.
16
+ Future<void> initUser(String userId);
17
+
18
+ /// Dissociate the current user from the provider.
19
+ Future<void> disconnectUser();
20
+
21
+ /// Available offers shown on the paywall.
22
+ Future<List<SubscriptionProduct>> getOffers(String? offerId);
23
+
24
+ /// Start the purchase flow for [product].
25
+ ///
26
+ /// Throws [UserCancelledPurchaseException] when the user cancels.
27
+ Future<void> purchaseProduct(SubscriptionProduct product);
28
+
29
+ /// Open the management/cancellation flow for a subscription whose origin is
30
+ /// [origin] (where it was purchased). Implementations only open external
31
+ /// management when allowed on the current platform.
32
+ Future<void> unsubscribe(SubscriptionStore? origin);
33
+
34
+ /// Restore / re-sync previous purchases.
35
+ Future<void> restorePurchase();
36
+
37
+ /// Currently active entitlements (access rights).
38
+ Future<List<Entitlement>> getPermissions();
39
+
40
+ /// Currently active entitlements, or null when none can be determined.
41
+ Future<List<Entitlement>?> getEntitlements();
42
+
43
+ /// Resolve a [SubscriptionProduct] from a store product id.
44
+ Future<SubscriptionProduct?> getFromProductId(String productId);
45
+
46
+ /// iOS-only: present the offer-code redemption sheet.
47
+ Future<void> presentCodeRedemptionSheet();
48
+ }
49
+
50
+ /// Thrown when the user cancels a purchase flow.
51
+ class UserCancelledPurchaseException implements Exception {
52
+ UserCancelledPurchaseException();
53
+ }
@@ -0,0 +1,21 @@
1
+ import 'package:flutter/foundation.dart';
2
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
3
+ import 'package:kasy_kit/core/config/features.dart';
4
+ import 'package:kasy_kit/environments.dart';
5
+ import 'package:kasy_kit/features/subscriptions/api/inapp_subscription_api.dart';
6
+ import 'package:kasy_kit/features/subscriptions/api/stripe_backend_api.dart';
7
+ import 'package:kasy_kit/features/subscriptions/api/stripe_payment_api.dart';
8
+ import 'package:kasy_kit/features/subscriptions/api/subscription_payment_api.dart';
9
+
10
+ /// Selects the subscription payment provider per platform × enabled feature:
11
+ /// - web + Stripe module -> [StripePaymentApi]
12
+ /// - mobile + RevenueCat -> [RevenueCatPaymentApi]
13
+ ///
14
+ /// The CLI regenerates this file to match the modules a project enabled, so a
15
+ /// web-only Stripe app never references RevenueCat (and vice-versa).
16
+ final inAppSubscriptionApiProvider = Provider<SubscriptionPaymentApi>((ref) {
17
+ if (kIsWeb && withStripe) {
18
+ return StripePaymentApi(backend: ref.read(stripeBackendApiProvider));
19
+ }
20
+ return RevenueCatPaymentApi(environment: ref.read(environmentProvider));
21
+ });
@@ -7,13 +7,12 @@ import 'package:kasy_kit/core/states/translations.dart';
7
7
  import 'package:kasy_kit/core/states/user_state_notifier.dart';
8
8
  import 'package:kasy_kit/core/toast/toast_service.dart';
9
9
  import 'package:kasy_kit/features/feedbacks/repositories/feature_request_repository.dart';
10
- import 'package:kasy_kit/features/subscription/api/inapp_subscription_api.dart'
10
+ import 'package:kasy_kit/features/subscriptions/api/subscription_payment_api.dart'
11
11
  show UserCancelledPurchaseException;
12
- import 'package:kasy_kit/features/subscription/providers/models/premium_state.dart';
13
- import 'package:kasy_kit/features/subscription/repositories/subscription_repository.dart';
12
+ import 'package:kasy_kit/features/subscriptions/providers/models/premium_state.dart';
13
+ import 'package:kasy_kit/features/subscriptions/repositories/subscription_repository.dart';
14
14
  import 'package:kasy_kit/router.dart';
15
15
  import 'package:logger/logger.dart';
16
- import 'package:purchases_flutter/purchases_flutter.dart';
17
16
  import 'package:riverpod_annotation/riverpod_annotation.dart';
18
17
  import 'package:sentry_flutter/sentry_flutter.dart';
19
18
 
@@ -90,7 +89,7 @@ class PremiumStateNotifier extends _$PremiumStateNotifier {
90
89
  if (TargetPlatform.iOS != defaultTargetPlatform) {
91
90
  return;
92
91
  }
93
- await Purchases.presentCodeRedemptionSheet();
92
+ await _subscriptionRepository.presentCodeRedemptionSheet();
94
93
  }
95
94
 
96
95
  void selectOffer(SubscriptionProduct offer) {
@@ -184,7 +183,11 @@ class PremiumStateNotifier extends _$PremiumStateNotifier {
184
183
 
185
184
  Future<void> unsubscribe() async {
186
185
  // _analytics.logPageOpened("launch_unsubscribe_page");
187
- await _subscriptionRepository.unsubscribe();
186
+ // Route by the subscription's origin (store). The UI only reaches here when
187
+ // the subscription can be managed on the current platform; the cross-platform
188
+ // case is handled with an info dialog before this is called.
189
+ final store = _userState.subscription?.store;
190
+ await _subscriptionRepository.unsubscribe(store);
188
191
  }
189
192
 
190
193
  Future<void> saveUnsubscribeReason(String reason) async {