seams 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 (414) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +335 -0
  3. data/LICENSE +21 -0
  4. data/README.md +104 -0
  5. data/lib/generators/seams/accounts/accounts_generator.rb +272 -0
  6. data/lib/generators/seams/accounts/templates/README.md.tt +219 -0
  7. data/lib/generators/seams/accounts/templates/app/models/account.rb.tt +124 -0
  8. data/lib/generators/seams/accounts/templates/app/models/application_record.rb.tt +7 -0
  9. data/lib/generators/seams/accounts/templates/app/models/current.rb.tt +38 -0
  10. data/lib/generators/seams/accounts/templates/app/models/membership.rb.tt +114 -0
  11. data/lib/generators/seams/accounts/templates/config/routes.rb.tt +8 -0
  12. data/lib/generators/seams/accounts/templates/db/migrate/create_accounts.rb.tt +29 -0
  13. data/lib/generators/seams/accounts/templates/db/migrate/create_accounts_memberships.rb.tt +49 -0
  14. data/lib/generators/seams/accounts/templates/lib/accounts.rb.tt +21 -0
  15. data/lib/generators/seams/accounts/templates/lib/concerns/account_scoped.rb.tt +97 -0
  16. data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +105 -0
  17. data/lib/generators/seams/accounts/templates/lib/configuration.rb.tt +26 -0
  18. data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +55 -0
  19. data/lib/generators/seams/accounts/templates/spec/factories/accounts.rb.tt +49 -0
  20. data/lib/generators/seams/accounts/templates/spec/models/accounts/account_spec.rb.tt +64 -0
  21. data/lib/generators/seams/accounts/templates/spec/models/accounts/membership_spec.rb.tt +99 -0
  22. data/lib/generators/seams/accounts/templates/spec/runtime/accounts_boot_spec.rb.tt +181 -0
  23. data/lib/generators/seams/admin/admin_generator.rb +852 -0
  24. data/lib/generators/seams/admin/templates/README.md.tt +266 -0
  25. data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_controller.rb.tt +16 -0
  26. data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_memberships_controller.rb.tt +16 -0
  27. data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +282 -0
  28. data/lib/generators/seams/admin/templates/app/controllers/admin/identities_controller.rb.tt +26 -0
  29. data/lib/generators/seams/admin/templates/app/controllers/admin/invitations_controller.rb.tt +14 -0
  30. data/lib/generators/seams/admin/templates/app/controllers/admin/invoices_controller.rb.tt +14 -0
  31. data/lib/generators/seams/admin/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +14 -0
  32. data/lib/generators/seams/admin/templates/app/controllers/admin/notification_preferences_controller.rb.tt +15 -0
  33. data/lib/generators/seams/admin/templates/app/controllers/admin/notifications_controller.rb.tt +18 -0
  34. data/lib/generators/seams/admin/templates/app/controllers/admin/plans_controller.rb.tt +14 -0
  35. data/lib/generators/seams/admin/templates/app/controllers/admin/subscriptions_controller.rb.tt +14 -0
  36. data/lib/generators/seams/admin/templates/app/controllers/admin/teams_controller.rb.tt +14 -0
  37. data/lib/generators/seams/admin/templates/app/controllers/admin/teams_memberships_controller.rb.tt +16 -0
  38. data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +50 -0
  39. data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +58 -0
  40. data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +48 -0
  41. data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +51 -0
  42. data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +67 -0
  43. data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +65 -0
  44. data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +58 -0
  45. data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +43 -0
  46. data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +72 -0
  47. data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +59 -0
  48. data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +39 -0
  49. data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +43 -0
  50. data/lib/generators/seams/admin/templates/app/policies/admin/platform/account_policy.rb.tt +10 -0
  51. data/lib/generators/seams/admin/templates/app/policies/admin/platform/accounts_membership_policy.rb.tt +10 -0
  52. data/lib/generators/seams/admin/templates/app/policies/admin/platform/application_policy.rb.tt +85 -0
  53. data/lib/generators/seams/admin/templates/app/policies/admin/platform/identity_policy.rb.tt +18 -0
  54. data/lib/generators/seams/admin/templates/app/policies/admin/platform/invitation_policy.rb.tt +9 -0
  55. data/lib/generators/seams/admin/templates/app/policies/admin/platform/invoice_policy.rb.tt +9 -0
  56. data/lib/generators/seams/admin/templates/app/policies/admin/platform/lifetime_pass_policy.rb.tt +9 -0
  57. data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_policy.rb.tt +9 -0
  58. data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_preference_policy.rb.tt +9 -0
  59. data/lib/generators/seams/admin/templates/app/policies/admin/platform/plan_policy.rb.tt +11 -0
  60. data/lib/generators/seams/admin/templates/app/policies/admin/platform/subscription_policy.rb.tt +9 -0
  61. data/lib/generators/seams/admin/templates/app/policies/admin/platform/team_policy.rb.tt +9 -0
  62. data/lib/generators/seams/admin/templates/app/policies/admin/platform/teams_membership_policy.rb.tt +9 -0
  63. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +33 -0
  64. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +24 -0
  65. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +169 -0
  66. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +67 -0
  67. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +24 -0
  68. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +21 -0
  69. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +21 -0
  70. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +25 -0
  71. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +23 -0
  72. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +47 -0
  73. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +22 -0
  74. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +28 -0
  75. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +24 -0
  76. data/lib/generators/seams/admin/templates/config/routes.rb.tt +38 -0
  77. data/lib/generators/seams/admin/templates/lib/admin.rb.tt +36 -0
  78. data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +66 -0
  79. data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +90 -0
  80. data/lib/generators/seams/admin/templates/lib/context.rb.tt +44 -0
  81. data/lib/generators/seams/admin/templates/lib/engine.rb.tt +68 -0
  82. data/lib/generators/seams/admin/templates/spec/factories/admin.rb.tt +10 -0
  83. data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +604 -0
  84. data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +157 -0
  85. data/lib/generators/seams/auth/add_oauth_provider/templates/adapter.rb.tt +95 -0
  86. data/lib/generators/seams/auth/add_oauth_provider/templates/adapter_spec.rb.tt +58 -0
  87. data/lib/generators/seams/auth/auth_generator.rb +311 -0
  88. data/lib/generators/seams/auth/templates/README.md.tt +289 -0
  89. data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +80 -0
  90. data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +44 -0
  91. data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +34 -0
  92. data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +49 -0
  93. data/lib/generators/seams/auth/templates/app/jobs/application_job.rb.tt +7 -0
  94. data/lib/generators/seams/auth/templates/app/jobs/cleanup_expired_sessions_job.rb.tt +30 -0
  95. data/lib/generators/seams/auth/templates/app/mailers/passwords_mailer.rb.tt +15 -0
  96. data/lib/generators/seams/auth/templates/app/models/api_token.rb.tt +62 -0
  97. data/lib/generators/seams/auth/templates/app/models/application_record.rb.tt +7 -0
  98. data/lib/generators/seams/auth/templates/app/models/current.rb.tt +15 -0
  99. data/lib/generators/seams/auth/templates/app/models/identity.rb.tt +74 -0
  100. data/lib/generators/seams/auth/templates/app/models/oauth/provider.rb.tt +48 -0
  101. data/lib/generators/seams/auth/templates/app/models/session.rb.tt +28 -0
  102. data/lib/generators/seams/auth/templates/app/services/authenticate_identity.rb.tt +31 -0
  103. data/lib/generators/seams/auth/templates/app/services/generate_api_token.rb.tt +35 -0
  104. data/lib/generators/seams/auth/templates/app/services/oauth/authenticator.rb.tt +94 -0
  105. data/lib/generators/seams/auth/templates/app/services/register_identity.rb.tt +57 -0
  106. data/lib/generators/seams/auth/templates/app/services/reset_password.rb.tt +41 -0
  107. data/lib/generators/seams/auth/templates/app/services/revoke_api_token.rb.tt +38 -0
  108. data/lib/generators/seams/auth/templates/app/views/password_resets/edit.html.erb.tt +12 -0
  109. data/lib/generators/seams/auth/templates/app/views/password_resets/new.html.erb.tt +11 -0
  110. data/lib/generators/seams/auth/templates/app/views/passwords_mailer/reset_email.html.erb.tt +7 -0
  111. data/lib/generators/seams/auth/templates/app/views/registrations/new.html.erb.tt +26 -0
  112. data/lib/generators/seams/auth/templates/app/views/sessions/_oauth_buttons.html.erb.tt +18 -0
  113. data/lib/generators/seams/auth/templates/app/views/sessions/new.html.erb.tt +17 -0
  114. data/lib/generators/seams/auth/templates/config/routes.rb.tt +21 -0
  115. data/lib/generators/seams/auth/templates/db/migrate/create_auth_api_tokens.rb.tt +26 -0
  116. data/lib/generators/seams/auth/templates/db/migrate/create_auth_identities.rb.tt +29 -0
  117. data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +35 -0
  118. data/lib/generators/seams/auth/templates/db/migrate/create_auth_sessions.rb.tt +19 -0
  119. data/lib/generators/seams/auth/templates/lib/auth.rb.tt +39 -0
  120. data/lib/generators/seams/auth/templates/lib/concerns/api_authenticatable.rb.tt +58 -0
  121. data/lib/generators/seams/auth/templates/lib/concerns/authenticatable.rb.tt +32 -0
  122. data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +60 -0
  123. data/lib/generators/seams/auth/templates/lib/configuration.rb.tt +45 -0
  124. data/lib/generators/seams/auth/templates/lib/engine.rb.tt +46 -0
  125. data/lib/generators/seams/auth/templates/lib/oauth/abstract.rb.tt +87 -0
  126. data/lib/generators/seams/auth/templates/lib/oauth/github.rb.tt +112 -0
  127. data/lib/generators/seams/auth/templates/lib/oauth/google.rb.tt +78 -0
  128. data/lib/generators/seams/auth/templates/lib/tasks/auth_pii.rake.tt +68 -0
  129. data/lib/generators/seams/auth/templates/spec/factories/auth.rb.tt +38 -0
  130. data/lib/generators/seams/auth/templates/spec/mailers/passwords_mailer_spec.rb.tt +37 -0
  131. data/lib/generators/seams/auth/templates/spec/models/api_token_spec.rb.tt +84 -0
  132. data/lib/generators/seams/auth/templates/spec/models/identity_spec.rb.tt +56 -0
  133. data/lib/generators/seams/auth/templates/spec/models/oauth/provider_spec.rb.tt +64 -0
  134. data/lib/generators/seams/auth/templates/spec/models/session_spec.rb.tt +34 -0
  135. data/lib/generators/seams/auth/templates/spec/runtime/boot_spec.rb.tt +30 -0
  136. data/lib/generators/seams/auth/templates/spec/runtime/event_payload_spec.rb.tt +29 -0
  137. data/lib/generators/seams/auth/templates/spec/runtime/login_flow_spec.rb.tt +45 -0
  138. data/lib/generators/seams/billing/billing_generator.rb +476 -0
  139. data/lib/generators/seams/billing/templates/README.md.tt +355 -0
  140. data/lib/generators/seams/billing/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +84 -0
  141. data/lib/generators/seams/billing/templates/app/controllers/checkout_controller.rb.tt +92 -0
  142. data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +63 -0
  143. data/lib/generators/seams/billing/templates/app/controllers/plans_controller.rb.tt +14 -0
  144. data/lib/generators/seams/billing/templates/app/controllers/portal_controller.rb.tt +45 -0
  145. data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +119 -0
  146. data/lib/generators/seams/billing/templates/app/controllers/webhooks_controller.rb.tt +98 -0
  147. data/lib/generators/seams/billing/templates/app/helpers/currency_helper.rb.tt +44 -0
  148. data/lib/generators/seams/billing/templates/app/jobs/application_job.rb.tt +6 -0
  149. data/lib/generators/seams/billing/templates/app/jobs/cancel_subscription_job.rb.tt +39 -0
  150. data/lib/generators/seams/billing/templates/app/jobs/start_subscription_job.rb.tt +32 -0
  151. data/lib/generators/seams/billing/templates/app/jobs/webhooks/process_event_job.rb.tt +37 -0
  152. data/lib/generators/seams/billing/templates/app/models/application_record.rb.tt +7 -0
  153. data/lib/generators/seams/billing/templates/app/models/invoice.rb.tt +35 -0
  154. data/lib/generators/seams/billing/templates/app/models/lifetime_pass.rb.tt +60 -0
  155. data/lib/generators/seams/billing/templates/app/models/plan.rb.tt +95 -0
  156. data/lib/generators/seams/billing/templates/app/models/subscription.rb.tt +31 -0
  157. data/lib/generators/seams/billing/templates/app/models/webhook_event.rb.tt +13 -0
  158. data/lib/generators/seams/billing/templates/app/services/checkout_session_service.rb.tt +25 -0
  159. data/lib/generators/seams/billing/templates/app/services/customers/find_or_create_service.rb.tt +73 -0
  160. data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +50 -0
  161. data/lib/generators/seams/billing/templates/app/services/lifetime/create_lifetime_session_service.rb.tt +82 -0
  162. data/lib/generators/seams/billing/templates/app/services/lifetime/create_pass_from_checkout_service.rb.tt +88 -0
  163. data/lib/generators/seams/billing/templates/app/services/lifetime/grant_pass_service.rb.tt +80 -0
  164. data/lib/generators/seams/billing/templates/app/services/lifetime/revoke_pass_service.rb.tt +59 -0
  165. data/lib/generators/seams/billing/templates/app/services/portal_session_service.rb.tt +23 -0
  166. data/lib/generators/seams/billing/templates/app/services/service_result.rb.tt +38 -0
  167. data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +67 -0
  168. data/lib/generators/seams/billing/templates/app/services/subscriptions/cancel_service.rb.tt +42 -0
  169. data/lib/generators/seams/billing/templates/app/services/subscriptions/change_plan_service.rb.tt +48 -0
  170. data/lib/generators/seams/billing/templates/app/services/subscriptions/reactivate_service.rb.tt +28 -0
  171. data/lib/generators/seams/billing/templates/app/services/webhooks/event_router.rb.tt +54 -0
  172. data/lib/generators/seams/billing/templates/app/services/webhooks/handler.rb.tt +93 -0
  173. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/charge_refunded_handler.rb.tt +18 -0
  174. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/checkout_session_completed_handler.rb.tt +58 -0
  175. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_created_handler.rb.tt +16 -0
  176. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_finalized_handler.rb.tt +14 -0
  177. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +80 -0
  178. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_paid_handler.rb.tt +12 -0
  179. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_payment_failed_handler.rb.tt +12 -0
  180. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_voided_handler.rb.tt +12 -0
  181. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_failed_handler.rb.tt +15 -0
  182. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_succeeded_handler.rb.tt +19 -0
  183. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_created_handler.rb.tt +11 -0
  184. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_deleted_handler.rb.tt +15 -0
  185. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +92 -0
  186. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_trial_will_end_handler.rb.tt +15 -0
  187. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_updated_handler.rb.tt +11 -0
  188. data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/index.html.erb.tt +36 -0
  189. data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/new.html.erb.tt +37 -0
  190. data/lib/generators/seams/billing/templates/app/views/checkout/success.html.erb.tt +5 -0
  191. data/lib/generators/seams/billing/templates/app/views/invoices/index.html.erb.tt +22 -0
  192. data/lib/generators/seams/billing/templates/app/views/invoices/show.html.erb.tt +14 -0
  193. data/lib/generators/seams/billing/templates/app/views/plans/index.html.erb.tt +51 -0
  194. data/lib/generators/seams/billing/templates/app/views/subscriptions/index.html.erb.tt +16 -0
  195. data/lib/generators/seams/billing/templates/app/views/subscriptions/show.html.erb.tt +25 -0
  196. data/lib/generators/seams/billing/templates/config/routes.rb.tt +39 -0
  197. data/lib/generators/seams/billing/templates/db/migrate/create_billing_invoices.rb.tt +32 -0
  198. data/lib/generators/seams/billing/templates/db/migrate/create_billing_lifetime_passes.rb.tt +43 -0
  199. data/lib/generators/seams/billing/templates/db/migrate/create_billing_plans.rb.tt +31 -0
  200. data/lib/generators/seams/billing/templates/db/migrate/create_billing_subscriptions.rb.tt +33 -0
  201. data/lib/generators/seams/billing/templates/db/migrate/create_billing_webhook_events.rb.tt +24 -0
  202. data/lib/generators/seams/billing/templates/lib/billing.rb.tt +34 -0
  203. data/lib/generators/seams/billing/templates/lib/concerns/billable.rb.tt +100 -0
  204. data/lib/generators/seams/billing/templates/lib/configuration.rb.tt +52 -0
  205. data/lib/generators/seams/billing/templates/lib/engine.rb.tt +72 -0
  206. data/lib/generators/seams/billing/templates/lib/gateways/abstract.rb.tt +65 -0
  207. data/lib/generators/seams/billing/templates/lib/gateways/adyen.rb.tt +16 -0
  208. data/lib/generators/seams/billing/templates/lib/gateways/paddle.rb.tt +22 -0
  209. data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +155 -0
  210. data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +101 -0
  211. data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +43 -0
  212. data/lib/generators/seams/billing/templates/lib/tasks/billing_check.rake.tt +34 -0
  213. data/lib/generators/seams/billing/templates/spec/factories/billing.rb.tt +65 -0
  214. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +19 -0
  215. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +17 -0
  216. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +25 -0
  217. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +17 -0
  218. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +17 -0
  219. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +28 -0
  220. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +18 -0
  221. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +18 -0
  222. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +19 -0
  223. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +20 -0
  224. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +18 -0
  225. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +21 -0
  226. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +17 -0
  227. data/lib/generators/seams/billing/templates/spec/gateways/contract_spec.rb.tt +11 -0
  228. data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +53 -0
  229. data/lib/generators/seams/billing/templates/spec/models/plan_spec.rb.tt +81 -0
  230. data/lib/generators/seams/billing/templates/spec/models/subscription_spec.rb.tt +43 -0
  231. data/lib/generators/seams/billing/templates/spec/runtime/boot_spec.rb.tt +38 -0
  232. data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +382 -0
  233. data/lib/generators/seams/billing/templates/spec/support/shared_examples/a_billing_gateway.rb.tt +100 -0
  234. data/lib/generators/seams/billing/templates/spec/support/stripe_helpers.rb.tt +59 -0
  235. data/lib/generators/seams/core/core_generator.rb +191 -0
  236. data/lib/generators/seams/core/templates/README.md.tt +45 -0
  237. data/lib/generators/seams/core/templates/app/controllers/concerns/has_current_attributes.rb.tt +71 -0
  238. data/lib/generators/seams/core/templates/app/models/application_record.rb.tt +7 -0
  239. data/lib/generators/seams/core/templates/app/models/audit_log.rb.tt +19 -0
  240. data/lib/generators/seams/core/templates/app/models/concerns/auditable.rb.tt +64 -0
  241. data/lib/generators/seams/core/templates/app/models/concerns/sluggable.rb.tt +53 -0
  242. data/lib/generators/seams/core/templates/app/models/concerns/soft_deletable.rb.tt +37 -0
  243. data/lib/generators/seams/core/templates/app/models/concerns/tenant_scoped.rb.tt +39 -0
  244. data/lib/generators/seams/core/templates/app/models/current.rb.tt +16 -0
  245. data/lib/generators/seams/core/templates/app/services/event_publisher.rb.tt +23 -0
  246. data/lib/generators/seams/core/templates/app/validators/email_format_validator.rb.tt +21 -0
  247. data/lib/generators/seams/core/templates/db/migrate/create_core_audit_logs.rb.tt +29 -0
  248. data/lib/generators/seams/core/templates/lib/core.rb.tt +8 -0
  249. data/lib/generators/seams/core/templates/lib/engine.rb.tt +28 -0
  250. data/lib/generators/seams/core/templates/spec/concerns/auditable_spec.rb.tt +39 -0
  251. data/lib/generators/seams/core/templates/spec/concerns/sluggable_spec.rb.tt +29 -0
  252. data/lib/generators/seams/core/templates/spec/models/audit_log_spec.rb.tt +22 -0
  253. data/lib/generators/seams/core/templates/spec/runtime/boot_spec.rb.tt +25 -0
  254. data/lib/generators/seams/core/templates/spec/validators/email_format_validator_spec.rb.tt +29 -0
  255. data/lib/generators/seams/engine/engine_generator.rb +165 -0
  256. data/lib/generators/seams/engine/templates/Gemfile.tt +19 -0
  257. data/lib/generators/seams/engine/templates/LICENSE.tt +21 -0
  258. data/lib/generators/seams/engine/templates/README.md.tt +40 -0
  259. data/lib/generators/seams/engine/templates/Rakefile.tt +14 -0
  260. data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
  261. data/lib/generators/seams/engine/templates/app/application_record.rb.tt +16 -0
  262. data/lib/generators/seams/engine/templates/config/locales/en.yml.tt +14 -0
  263. data/lib/generators/seams/engine/templates/config/routes.rb.tt +4 -0
  264. data/lib/generators/seams/engine/templates/gemspec.tt +20 -0
  265. data/lib/generators/seams/engine/templates/host_initializer.rb.tt +13 -0
  266. data/lib/generators/seams/engine/templates/lib/engine.rb.tt +27 -0
  267. data/lib/generators/seams/engine/templates/lib/root.rb.tt +7 -0
  268. data/lib/generators/seams/engine/templates/lib/version.rb.tt +5 -0
  269. data/lib/generators/seams/engine/templates/rubocop.yml.tt +55 -0
  270. data/lib/generators/seams/engine/templates/spec/example_spec.rb.tt +16 -0
  271. data/lib/generators/seams/engine/templates/spec/spec_helper.rb.tt +23 -0
  272. data/lib/generators/seams/install/install_generator.rb +211 -0
  273. data/lib/generators/seams/install/templates/Dockerfile.tt +52 -0
  274. data/lib/generators/seams/install/templates/Procfile.tt +14 -0
  275. data/lib/generators/seams/install/templates/bin_seams.tt +107 -0
  276. data/lib/generators/seams/install/templates/ci.yml.tt +123 -0
  277. data/lib/generators/seams/install/templates/deploy.yml.tt +63 -0
  278. data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +86 -0
  279. data/lib/generators/seams/install/templates/docker-entrypoint.tt +27 -0
  280. data/lib/generators/seams/install/templates/rubocop.yml.tt +33 -0
  281. data/lib/generators/seams/install/templates/ruby-version.tt +1 -0
  282. data/lib/generators/seams/install/templates/script/collate_coverage.rb.tt +33 -0
  283. data/lib/generators/seams/install/templates/script/run_affected_tests.sh.tt +64 -0
  284. data/lib/generators/seams/install/templates/seams.rake.tt +65 -0
  285. data/lib/generators/seams/install/templates/seams.rb.tt +9 -0
  286. data/lib/generators/seams/install/templates/seams_engines.rb.tt +15 -0
  287. data/lib/generators/seams/notifications/notifications_generator.rb +395 -0
  288. data/lib/generators/seams/notifications/templates/README.md.tt +269 -0
  289. data/lib/generators/seams/notifications/templates/app/channels/notification_channel.rb.tt +36 -0
  290. data/lib/generators/seams/notifications/templates/app/controllers/notifications_controller.rb.tt +58 -0
  291. data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +54 -0
  292. data/lib/generators/seams/notifications/templates/app/javascript/controllers/notification_bell_controller.js.tt +34 -0
  293. data/lib/generators/seams/notifications/templates/app/jobs/application_job.rb.tt +6 -0
  294. data/lib/generators/seams/notifications/templates/app/jobs/create_notification_job.rb.tt +31 -0
  295. data/lib/generators/seams/notifications/templates/app/jobs/send_due_notifications_job.rb.tt +22 -0
  296. data/lib/generators/seams/notifications/templates/app/jobs/send_notification_job.rb.tt +13 -0
  297. data/lib/generators/seams/notifications/templates/app/mailers/application_mailer.rb.tt +12 -0
  298. data/lib/generators/seams/notifications/templates/app/mailers/notification_mailer.rb.tt +23 -0
  299. data/lib/generators/seams/notifications/templates/app/models/application_record.rb.tt +7 -0
  300. data/lib/generators/seams/notifications/templates/app/models/delivery.rb.tt +13 -0
  301. data/lib/generators/seams/notifications/templates/app/models/notification.rb.tt +218 -0
  302. data/lib/generators/seams/notifications/templates/app/models/notification_preference.rb.tt +29 -0
  303. data/lib/generators/seams/notifications/templates/app/models/strategies/email.rb.tt +38 -0
  304. data/lib/generators/seams/notifications/templates/app/models/strategies/in_app.rb.tt +26 -0
  305. data/lib/generators/seams/notifications/templates/app/models/strategies/sms.rb.tt +33 -0
  306. data/lib/generators/seams/notifications/templates/app/subscribers/auth_subscriber.rb.tt +71 -0
  307. data/lib/generators/seams/notifications/templates/app/subscribers/billing_subscriber.rb.tt +127 -0
  308. data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.html.erb.tt +22 -0
  309. data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.text.erb.tt +4 -0
  310. data/lib/generators/seams/notifications/templates/app/views/notifications/_bell.html.erb.tt +15 -0
  311. data/lib/generators/seams/notifications/templates/app/views/notifications/index.html.erb.tt +15 -0
  312. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.html.erb.tt +4 -0
  313. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.text.erb.tt +4 -0
  314. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.html.erb.tt +3 -0
  315. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.text.erb.tt +3 -0
  316. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.html.erb.tt +5 -0
  317. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.text.erb.tt +5 -0
  318. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.html.erb.tt +5 -0
  319. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.text.erb.tt +5 -0
  320. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.html.erb.tt +4 -0
  321. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.text.erb.tt +4 -0
  322. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.html.erb.tt +4 -0
  323. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.text.erb.tt +5 -0
  324. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.html.erb.tt +3 -0
  325. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.text.erb.tt +3 -0
  326. data/lib/generators/seams/notifications/templates/app/views/templates/default.html.erb.tt +10 -0
  327. data/lib/generators/seams/notifications/templates/app/views/templates/default.text.erb.tt +11 -0
  328. data/lib/generators/seams/notifications/templates/app/views/templates/welcome.html.erb.tt +6 -0
  329. data/lib/generators/seams/notifications/templates/app/views/templates/welcome.text.erb.tt +6 -0
  330. data/lib/generators/seams/notifications/templates/config/initializers/notifications.rb.tt +58 -0
  331. data/lib/generators/seams/notifications/templates/config/routes.rb.tt +17 -0
  332. data/lib/generators/seams/notifications/templates/db/migrate/create_notification_deliveries.rb.tt +16 -0
  333. data/lib/generators/seams/notifications/templates/db/migrate/create_notification_preferences.rb.tt +25 -0
  334. data/lib/generators/seams/notifications/templates/db/migrate/create_notifications.rb.tt +35 -0
  335. data/lib/generators/seams/notifications/templates/lib/adapters/abstract.rb.tt +20 -0
  336. data/lib/generators/seams/notifications/templates/lib/adapters/action_mailer.rb.tt +17 -0
  337. data/lib/generators/seams/notifications/templates/lib/adapters/null_sms.rb.tt +23 -0
  338. data/lib/generators/seams/notifications/templates/lib/concerns/notifiable.rb.tt +135 -0
  339. data/lib/generators/seams/notifications/templates/lib/configuration.rb.tt +24 -0
  340. data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +35 -0
  341. data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +75 -0
  342. data/lib/generators/seams/notifications/templates/lib/type_registry.rb.tt +74 -0
  343. data/lib/generators/seams/notifications/templates/spec/factories/notifications.rb.tt +53 -0
  344. data/lib/generators/seams/notifications/templates/spec/models/delivery_spec.rb.tt +28 -0
  345. data/lib/generators/seams/notifications/templates/spec/models/notification_preference_spec.rb.tt +46 -0
  346. data/lib/generators/seams/notifications/templates/spec/models/notification_spec.rb.tt +60 -0
  347. data/lib/generators/seams/notifications/templates/spec/runtime/bell_broadcast_spec.rb.tt +59 -0
  348. data/lib/generators/seams/notifications/templates/spec/runtime/billing_subscriber_skip_spec.rb.tt +87 -0
  349. data/lib/generators/seams/notifications/templates/spec/runtime/boot_spec.rb.tt +39 -0
  350. data/lib/generators/seams/notifications/templates/spec/runtime/schedule_round_trip_spec.rb.tt +55 -0
  351. data/lib/generators/seams/remove/remove_generator.rb +259 -0
  352. data/lib/generators/seams/teams/teams_generator.rb +298 -0
  353. data/lib/generators/seams/teams/templates/README.md.tt +88 -0
  354. data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +102 -0
  355. data/lib/generators/seams/teams/templates/app/controllers/memberships_controller.rb.tt +54 -0
  356. data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +68 -0
  357. data/lib/generators/seams/teams/templates/app/jobs/application_job.rb.tt +6 -0
  358. data/lib/generators/seams/teams/templates/app/mailers/invitation_mailer.rb.tt +34 -0
  359. data/lib/generators/seams/teams/templates/app/models/application_record.rb.tt +7 -0
  360. data/lib/generators/seams/teams/templates/app/models/current.rb.tt +30 -0
  361. data/lib/generators/seams/teams/templates/app/models/invitation.rb.tt +36 -0
  362. data/lib/generators/seams/teams/templates/app/models/membership.rb.tt +36 -0
  363. data/lib/generators/seams/teams/templates/app/models/team.rb.tt +32 -0
  364. data/lib/generators/seams/teams/templates/app/subscribers/invitation_subscriber.rb.tt +36 -0
  365. data/lib/generators/seams/teams/templates/app/views/invitation_mailer/invite.text.erb.tt +8 -0
  366. data/lib/generators/seams/teams/templates/app/views/invitations/index.html.erb.tt +44 -0
  367. data/lib/generators/seams/teams/templates/app/views/memberships/index.html.erb.tt +32 -0
  368. data/lib/generators/seams/teams/templates/app/views/teams/edit.html.erb.tt +28 -0
  369. data/lib/generators/seams/teams/templates/app/views/teams/index.html.erb.tt +15 -0
  370. data/lib/generators/seams/teams/templates/app/views/teams/new.html.erb.tt +24 -0
  371. data/lib/generators/seams/teams/templates/app/views/teams/show.html.erb.tt +17 -0
  372. data/lib/generators/seams/teams/templates/config/routes.rb.tt +19 -0
  373. data/lib/generators/seams/teams/templates/db/migrate/create_team_invitations.rb.tt +24 -0
  374. data/lib/generators/seams/teams/templates/db/migrate/create_team_memberships.rb.tt +25 -0
  375. data/lib/generators/seams/teams/templates/db/migrate/create_teams.rb.tt +18 -0
  376. data/lib/generators/seams/teams/templates/lib/concerns/account_scoped.rb.tt +79 -0
  377. data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +55 -0
  378. data/lib/generators/seams/teams/templates/lib/configuration.rb.tt +45 -0
  379. data/lib/generators/seams/teams/templates/lib/engine.rb.tt +51 -0
  380. data/lib/generators/seams/teams/templates/lib/teams.rb.tt +22 -0
  381. data/lib/generators/seams/teams/templates/spec/factories/teams.rb.tt +47 -0
  382. data/lib/generators/seams/teams/templates/spec/models/invitation_spec.rb.tt +25 -0
  383. data/lib/generators/seams/teams/templates/spec/models/membership_spec.rb.tt +29 -0
  384. data/lib/generators/seams/teams/templates/spec/models/team_spec.rb.tt +23 -0
  385. data/lib/generators/seams/teams/templates/spec/runtime/boot_spec.rb.tt +32 -0
  386. data/lib/seams/cli/list.rb +111 -0
  387. data/lib/seams/cli/quality.rb +99 -0
  388. data/lib/seams/cli/resolve.rb +276 -0
  389. data/lib/seams/cli/test_changed.rb +116 -0
  390. data/lib/seams/cli.rb +48 -0
  391. data/lib/seams/configuration.rb +19 -0
  392. data/lib/seams/cops/known_queue_names.rb +42 -0
  393. data/lib/seams/cops/migration_comments.rb +68 -0
  394. data/lib/seams/cops/no_cross_engine_dependency.rb +58 -0
  395. data/lib/seams/cops/no_cross_engine_model_access.rb +153 -0
  396. data/lib/seams/cops.rb +18 -0
  397. data/lib/seams/event_registry.rb +49 -0
  398. data/lib/seams/events/adapter.rb +24 -0
  399. data/lib/seams/events/adapters/active_support.rb +31 -0
  400. data/lib/seams/events/publisher.rb +178 -0
  401. data/lib/seams/events.rb +39 -0
  402. data/lib/seams/generators/dummy_app_writer.rb +424 -0
  403. data/lib/seams/generators/eject_aware.rb +102 -0
  404. data/lib/seams/generators/follow_up_generator.rb +148 -0
  405. data/lib/seams/generators/host_injector.rb +124 -0
  406. data/lib/seams/generators/sibling_rubocop_writer.rb +77 -0
  407. data/lib/seams/generators/splicer.rb +217 -0
  408. data/lib/seams/observability/adapter.rb +33 -0
  409. data/lib/seams/observability/adapters/rails_logger.rb +59 -0
  410. data/lib/seams/observability.rb +34 -0
  411. data/lib/seams/runtime.rb +23 -0
  412. data/lib/seams/version.rb +5 -0
  413. data/lib/seams.rb +23 -0
  414. metadata +493 -0
@@ -0,0 +1,5 @@
1
+ <h1>Thanks!</h1>
2
+
3
+ <p>Your subscription is being activated. You'll receive a confirmation email shortly.</p>
4
+
5
+ <p><%%= link_to "Back to plans", billing.plans_path %></p>
@@ -0,0 +1,22 @@
1
+ <%%# Read-only invoice history for the signed-in user. %>
2
+ <h1>Billing history</h1>
3
+
4
+ <%% if @invoices.empty? %>
5
+ <p>No invoices yet.</p>
6
+ <%% else %>
7
+ <table>
8
+ <thead>
9
+ <tr><th>Date</th><th>Amount</th><th>Status</th><th></th></tr>
10
+ </thead>
11
+ <tbody>
12
+ <%% @invoices.each do |invoice| %>
13
+ <tr>
14
+ <td><%%= invoice.created_at.to_date %></td>
15
+ <td><%%= Billing::CurrencyHelper.format_money(invoice.amount_cents, invoice.currency) %></td>
16
+ <td><%%= invoice.status.titleize %></td>
17
+ <td><%%= link_to "View", invoice_path(invoice) %></td>
18
+ </tr>
19
+ <%% end %>
20
+ </tbody>
21
+ </table>
22
+ <%% end %>
@@ -0,0 +1,14 @@
1
+ <%%# Single invoice view. The Stripe-hosted PDF lives off the gateway %>
2
+ <%%# (we do not proxy it); add a `hosted_invoice_url` link in your host %>
3
+ <%%# override if you want one. %>
4
+ <h1>Invoice <%%= @invoice.gateway_ref %></h1>
5
+
6
+ <dl>
7
+ <dt>Amount</dt> <dd><%%= Billing::CurrencyHelper.format_money(@invoice.amount_cents, @invoice.currency) %></dd>
8
+ <dt>Status</dt> <dd><%%= @invoice.status.titleize %></dd>
9
+ <dt>Customer</dt> <dd><%%= @invoice.customer_ref %></dd>
10
+ <dt>Subscription</dt><dd><%%= @invoice.subscription_ref %></dd>
11
+ <dt>Paid at</dt> <dd><%%= @invoice.paid_at %></dd>
12
+ </dl>
13
+
14
+ <%%= link_to "Back to billing history", invoices_path %>
@@ -0,0 +1,51 @@
1
+ <h1>Plans</h1>
2
+
3
+ <%% if @recurring_plans.any? %>
4
+ <section class="plans plans-recurring">
5
+ <h2>Subscriptions</h2>
6
+ <%% @recurring_plans.each do |plan| %>
7
+ <article class="plan">
8
+ <h3><%%= plan.name %></h3>
9
+ <p class="plan-price">
10
+ <%%= number_to_currency(plan.amount_cents / 100.0, unit: plan.currency.upcase + " ") %> /
11
+ <%%= plan.interval %>
12
+ </p>
13
+ <%% if plan.has_trial? %>
14
+ <p><%%= plan.trial_period_days %>-day free trial</p>
15
+ <%% end %>
16
+ <%%= button_to "Subscribe", billing.checkout_path(plan: plan.gateway_ref), method: :post %>
17
+ </article>
18
+ <%% end %>
19
+ </section>
20
+ <%% end %>
21
+
22
+ <%% if @lifetime_plans.any? %>
23
+ <section class="plans plans-lifetime">
24
+ <h2>Lifetime — buy once, own forever</h2>
25
+ <p class="plans-lifetime-disclaimer">
26
+ One payment, permanent access. No recurring billing.
27
+ </p>
28
+ <%% @lifetime_plans.each do |plan| %>
29
+ <article class="plan plan-lifetime">
30
+ <h3><%%= plan.name %></h3>
31
+ <p class="plan-price">
32
+ <%%= number_to_currency(plan.amount_cents / 100.0, unit: plan.currency.upcase + " ") %> ·
33
+ one-time
34
+ </p>
35
+ <%% remaining = plan.lifetime_inventory_remaining %>
36
+ <%% if remaining.nil? %>
37
+ <p class="plan-inventory">Unlimited</p>
38
+ <%% elsif remaining.zero? %>
39
+ <p class="plan-inventory plan-sold-out">Sold out</p>
40
+ <%% else %>
41
+ <p class="plan-inventory">Only <%%= remaining %> left at this price</p>
42
+ <%% end %>
43
+ <%% if plan.lifetime_sold_out? %>
44
+ <button disabled>Sold out</button>
45
+ <%% else %>
46
+ <%%= button_to "Buy lifetime", billing.lifetime_checkout_path(plan: plan.gateway_ref), method: :post %>
47
+ <%% end %>
48
+ </article>
49
+ <%% end %>
50
+ </section>
51
+ <%% end %>
@@ -0,0 +1,16 @@
1
+ <%%# Lists the signed-in user's subscriptions. Override at %>
2
+ <%%# app/views/billing/subscriptions/index.html.erb in your host. %>
3
+ <h1>Your subscriptions</h1>
4
+
5
+ <%% if @subscriptions.empty? %>
6
+ <p>You do not have any subscriptions yet. <%%= link_to "View plans", plans_path %>.</p>
7
+ <%% else %>
8
+ <ul>
9
+ <%% @subscriptions.each do |subscription| %>
10
+ <li>
11
+ <%%= link_to subscription.plan_ref, subscription_path(subscription) %>
12
+ — <%%= subscription.status.titleize %>
13
+ </li>
14
+ <%% end %>
15
+ </ul>
16
+ <%% end %>
@@ -0,0 +1,25 @@
1
+ <%%# Single subscription view with cancel / reactivate / change_plan %>
2
+ <%%# controls. Override in your host to brand it. %>
3
+ <h1>Subscription <%%= @subscription.gateway_ref %></h1>
4
+
5
+ <dl>
6
+ <dt>Status</dt> <dd><%%= @subscription.status.titleize %></dd>
7
+ <dt>Plan</dt> <dd><%%= @subscription.plan_ref %></dd>
8
+ <dt>Customer</dt> <dd><%%= @subscription.customer_ref %></dd>
9
+ <dt>Period ends</dt> <dd><%%= @subscription.current_period_end %></dd>
10
+ </dl>
11
+
12
+ <%% if @subscription.active? %>
13
+ <%%= button_to "Cancel at period end", subscription_path(@subscription),
14
+ method: :delete,
15
+ data: { confirm: "Cancel this subscription at the end of the billing period?" } %>
16
+
17
+ <h2>Change plan</h2>
18
+ <%%= form_with url: change_plan_subscription_path(@subscription), method: :post do |form| %>
19
+ <%%= form.label :price_ref, "New plan id" %>
20
+ <%%= form.text_field :price_ref %>
21
+ <%%= form.submit "Change plan" %>
22
+ <%% end %>
23
+ <%% else %>
24
+ <%%= button_to "Reactivate", reactivate_subscription_path(@subscription), method: :post %>
25
+ <%% end %>
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ Billing::Engine.routes.draw do
4
+ resources :plans, only: %i[index]
5
+
6
+ # Self-service subscription management — Phase 3 (4/4). Backed by
7
+ # the Phase 3 (2/4) service objects; member actions map onto
8
+ # CancelService / ReactivateService / ChangePlanService.
9
+ resources :subscriptions, only: %i[index show] do
10
+ member do
11
+ delete :cancel
12
+ post :reactivate
13
+ post :change_plan
14
+ end
15
+ end
16
+
17
+ # Read-only billing history. Stripe hosts the PDF; the show view
18
+ # links to hosted_invoice_url if your host wires it up.
19
+ resources :invoices, only: %i[index show]
20
+
21
+ post "/checkout", to: "checkout#create", as: :checkout
22
+ get "/checkout/success", to: "checkout#success", as: :checkout_success
23
+ # Lifetime Deal purchase — Stripe Checkout `mode: "payment"`.
24
+ # Namespaced under /checkout/ alongside the recurring-checkout flow
25
+ # so the surface stays grouped. See issue #2 section 3A.LTD.
26
+ post "/checkout/lifetime", to: "checkout#lifetime", as: :lifetime_checkout
27
+
28
+ post "/portal", to: "portal#create", as: :portal
29
+
30
+ # Admin-side LTD management. Mount behind your admin gate; see
31
+ # Billing::Admin::LifetimePassesController for the contract.
32
+ namespace :admin do
33
+ resources :lifetime_passes, only: %i[index new create destroy]
34
+ end
35
+
36
+ # Follow-up generators that add new gateway-flow routes (Adyen webhook, Paddle webhook, alternative checkout) splice their routes here, ahead of the existing Stripe webhook.
37
+ # seams:insertion-point billing.routes.before_webhook
38
+ post "/webhooks/stripe", to: "webhooks#stripe", as: :stripe_webhook
39
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # What: creates the billing_invoices table.
4
+ # Why: per-invoice audit trail for support and finance — when a
5
+ # customer asks "did you charge me on the 3rd?" we don't have to
6
+ # round-trip the gateway every time. The invoice belongs to an
7
+ # Accounts::Account (the tenant the subscription was billed on),
8
+ # not to an individual Identity.
9
+ # Risk: append-only writes from webhook handlers; no live-traffic SQL.
10
+ class CreateBillingInvoices < ActiveRecord::Migration[7.1]
11
+ def change
12
+ create_table :billing_invoices do |t|
13
+ # Local tenant FK. UUID to match Accounts::Account#id; no DB-level
14
+ # FK across engines — see create_billing_subscriptions for why.
15
+ t.uuid :account_id, null: false
16
+ t.string :gateway_ref, null: false
17
+ t.string :customer_ref, null: false # Stripe customer id (cus_*)
18
+ t.string :subscription_ref # Stripe subscription id (sub_*); nil for one-off invoices
19
+ t.integer :amount_cents, null: false
20
+ t.string :currency, null: false, default: "USD"
21
+ t.string :status, null: false, default: "open"
22
+ t.datetime :paid_at
23
+ t.timestamps
24
+ end
25
+
26
+ add_index :billing_invoices, :account_id
27
+ add_index :billing_invoices, :gateway_ref, unique: true
28
+ add_index :billing_invoices, :customer_ref
29
+ add_index :billing_invoices, :subscription_ref, where: "subscription_ref IS NOT NULL"
30
+ add_index :billing_invoices, %i[status created_at]
31
+ end
32
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # What: creates billing_lifetime_passes — the one-time-paid OR privately
4
+ # granted permanent-access record. Lives in its own table because
5
+ # Subscription's renewal/period semantics don't apply.
6
+ # Why: Lifetime Deals (LTDs) are a real revenue + early-adopter feedback
7
+ # channel; the engine ships the mechanism so hosts can offer them
8
+ # without rolling their own model. See README LTD section for the
9
+ # trade-off (indefinite support, no recurring revenue). Bound to
10
+ # an Accounts::Account — the entitlement belongs to the tenant.
11
+ # `granted_by_identity_id` is the human (Auth::Identity) who
12
+ # pressed the "grant lifetime" button — Identity, not Account,
13
+ # because that column tracks human action rather than tenant data.
14
+ # Risk: append-mostly (revoke is a soft delete via revoked_at, not a row
15
+ # delete, so audit history survives). Unique index on
16
+ # (account_id, plan_ref) prevents accidentally double-granting.
17
+ class CreateBillingLifetimePasses < ActiveRecord::Migration[7.1]
18
+ def change
19
+ create_table :billing_lifetime_passes do |t|
20
+ # Local tenant FK. UUID to match Accounts::Account#id.
21
+ t.uuid :account_id, null: false
22
+ t.string :customer_ref, null: false # gateway-side customer id (cus_*)
23
+ t.string :plan_ref, null: false # billing_plans.gateway_ref
24
+ t.string :gateway_ref # Stripe Checkout Session id; nil for private grants
25
+ # The Identity (auth_identities row) of the admin who granted
26
+ # the pass; nil for paid LTDs purchased through Stripe Checkout.
27
+ # bigint to match auth_identities' default integer PK.
28
+ t.bigint :granted_by_identity_id
29
+ t.datetime :granted_at, null: false
30
+ t.datetime :revoked_at # soft-revoke (refund / ToS violation)
31
+ t.bigint :revoked_by_identity_id # admin Identity who revoked
32
+ t.text :notes
33
+ t.timestamps
34
+ end
35
+
36
+ add_index :billing_lifetime_passes, :account_id
37
+ add_index :billing_lifetime_passes, %i[account_id plan_ref], unique: true,
38
+ name: "index_billing_ltd_unique"
39
+ add_index :billing_lifetime_passes, :gateway_ref, unique: true,
40
+ where: "gateway_ref IS NOT NULL"
41
+ add_index :billing_lifetime_passes, :revoked_at, where: "revoked_at IS NULL"
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # What: creates billing_plans, the local mirror of Stripe Prices.
4
+ # Why: the pricing page renders from this table without a per-request
5
+ # Stripe round-trip; webhooks keep it in sync if a plan changes
6
+ # upstream.
7
+ # Risk: small table, append-mostly. Unique index on gateway_ref.
8
+ class CreateBillingPlans < ActiveRecord::Migration[7.1]
9
+ def change
10
+ create_table :billing_plans do |t|
11
+ t.string :gateway_ref, null: false # price_* from Stripe
12
+ t.string :name, null: false
13
+ t.text :description
14
+ t.integer :amount_cents, null: false, default: 0
15
+ t.string :currency, null: false, default: "usd"
16
+ t.string :interval, null: false, default: "month"
17
+ t.integer :trial_period_days
18
+ t.boolean :active, null: false, default: true
19
+ t.jsonb :features, null: false, default: {}
20
+ # LTD inventory cap. nil = unlimited. Only meaningful when
21
+ # interval == "lifetime"; ignored otherwise. The pricing page +
22
+ # admin grant flow read Plan#lifetime_inventory_remaining to
23
+ # gate purchases / grants.
24
+ t.integer :max_lifetime_units
25
+ t.timestamps
26
+ end
27
+
28
+ add_index :billing_plans, :gateway_ref, unique: true
29
+ add_index :billing_plans, :active
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # What: creates the billing_subscriptions table.
4
+ # Why: source of truth for "is this customer paying us right now?"
5
+ # Synced from gateway webhooks (status, current_period_end). Bound
6
+ # to an Accounts::Account (the tenant) — not to an Identity. The
7
+ # Stripe customer is the workspace, not the human; a single
8
+ # human (Identity) with memberships in two Accounts has two
9
+ # independent billing relationships.
10
+ # Risk: empty on creation, append-mostly thereafter. Status updates
11
+ # happen in transactions of one row — no locking concerns.
12
+ class CreateBillingSubscriptions < ActiveRecord::Migration[7.1]
13
+ def change
14
+ create_table :billing_subscriptions do |t|
15
+ # Local tenant FK. UUID to match Accounts::Account#id; no DB-level
16
+ # foreign key because billing and accounts may live in different
17
+ # schemas / databases — cross-engine integrity is enforced at the
18
+ # application layer (the AccountScoped concern + service objects).
19
+ t.uuid :account_id, null: false
20
+ t.string :customer_ref, null: false # Stripe customer id (cus_*) for the Account
21
+ t.string :plan_ref, null: false
22
+ t.string :gateway_ref, null: false
23
+ t.string :status, null: false, default: "incomplete"
24
+ t.datetime :current_period_end
25
+ t.timestamps
26
+ end
27
+
28
+ add_index :billing_subscriptions, :account_id
29
+ add_index :billing_subscriptions, :customer_ref
30
+ add_index :billing_subscriptions, :gateway_ref, unique: true
31
+ add_index :billing_subscriptions, :status
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # What: creates billing_webhook_events for idempotent webhook handling.
4
+ # Why: Stripe explicitly recommends deduping by event id because they
5
+ # retry indefinitely until the endpoint returns 2xx
6
+ # (https://docs.stripe.com/webhooks#handle-duplicate-events).
7
+ # Without this table the welcome-email subscriber would re-send
8
+ # on every retry.
9
+ # Risk: empty on creation, append-only thereafter. Unique index on
10
+ # gateway_event_id is the deduper.
11
+ class CreateBillingWebhookEvents < ActiveRecord::Migration[7.1]
12
+ def change
13
+ create_table :billing_webhook_events do |t|
14
+ t.string :gateway, null: false # "stripe" | "paddle" | ...
15
+ t.string :gateway_event_id, null: false # provider's evt_* id
16
+ t.string :event_type, null: false
17
+ t.boolean :livemode, null: false, default: false
18
+ t.timestamps
19
+ end
20
+
21
+ add_index :billing_webhook_events, %i[gateway gateway_event_id], unique: true
22
+ add_index :billing_webhook_events, :event_type
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "billing/version"
4
+ require "billing/configuration"
5
+ require "billing/engine"
6
+ require "billing/concerns/billable"
7
+ require "billing/gateways/abstract"
8
+ require "billing/gateways/stripe"
9
+ # Follow-up generators that ship pre-wired gateway adapters (seams:billing:add_gateway paddle) splice their `require "billing/gateways/paddle"` line here.
10
+ # seams:insertion-point billing.gateways.adapters
11
+
12
+ module Billing
13
+ class Error < StandardError; end
14
+ class GatewayError < Error; end
15
+ class WebhookError < Error; end
16
+
17
+ class << self
18
+ def configuration
19
+ @configuration ||= Configuration.new
20
+ end
21
+
22
+ def configure
23
+ yield configuration
24
+ end
25
+
26
+ def gateway
27
+ configuration.gateway.constantize.new
28
+ rescue NameError => e
29
+ raise Billing::Error,
30
+ "Billing.configuration.gateway is set to #{configuration.gateway.inspect}, " \
31
+ "which could not be resolved: #{e.message}"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Billing
6
+ # Mix into the host's Accounts::Account model — the tenant — to gain
7
+ # billing helpers. The Account is the Stripe customer; subscriptions,
8
+ # invoices, and lifetime passes all belong to the tenant, not to an
9
+ # individual human (Identity). A single Identity who is a member of
10
+ # two Accounts has TWO independent billing relationships.
11
+ #
12
+ # Wiring is config-driven (see Billing::Configuration#billable_class).
13
+ # The default `"Accounts::Account"` matches the canonical accounts
14
+ # engine; hosts on a different tenant model point the config at
15
+ # their own class.
16
+ #
17
+ # Hosts that want to wire it manually instead can do so:
18
+ #
19
+ # class Accounts::Account < ApplicationRecord
20
+ # include Billing::Billable
21
+ # end
22
+ module Billable
23
+ extend ActiveSupport::Concern
24
+
25
+ included do
26
+ # Associations are bare `account_id` keyed — the cross-engine
27
+ # model-access cop forbids `belongs_to :account` from inside
28
+ # billing pointing at Accounts::Account, so we use foreign_key
29
+ # without a class_name on the back-edge: account.billing_*
30
+ # queries the billing tables by the account's id.
31
+ has_many :billing_subscriptions, class_name: "Billing::Subscription",
32
+ foreign_key: :account_id, primary_key: :id
33
+ has_many :billing_lifetime_passes, class_name: "Billing::LifetimePass",
34
+ foreign_key: :account_id, primary_key: :id
35
+ has_many :billing_invoices, class_name: "Billing::Invoice",
36
+ foreign_key: :account_id, primary_key: :id
37
+ end
38
+
39
+ # Enqueues a Stripe subscription. The `email:` arg is the contact
40
+ # address Stripe attaches to the customer record on first creation
41
+ # — typically the Account owner's Identity#email_address, but the
42
+ # host decides (it might be a billing-specific contact instead).
43
+ # Subsequent calls reuse the existing Stripe customer.
44
+ def start_subscription!(plan_ref:, email:)
45
+ Billing::StartSubscriptionJob.perform_later(
46
+ account_id: id,
47
+ customer_ref: stripe_customer_ref!(email: email),
48
+ plan_ref: plan_ref
49
+ )
50
+ end
51
+
52
+ def cancel_subscription!(subscription_ref:)
53
+ Billing::CancelSubscriptionJob.perform_later(subscription_ref: subscription_ref)
54
+ end
55
+
56
+ # Lifetime Deal helpers (issue #2 section 3A.LTD).
57
+ # `lifetime?` is true if this Account holds an active LifetimePass
58
+ # for ANY plan; `has_lifetime_for?(plan_ref:)` narrows to one.
59
+ def lifetime?
60
+ billing_lifetime_passes.where(revoked_at: nil).exists?
61
+ end
62
+
63
+ def has_lifetime_for?(plan_ref:)
64
+ billing_lifetime_passes.where(revoked_at: nil, plan_ref: plan_ref).exists?
65
+ end
66
+
67
+ # The "is this Account a paying / entitled customer right now?"
68
+ # check. True for active or trialing recurring subscriptions OR
69
+ # for any active lifetime pass. Use this in host code instead of
70
+ # querying subscriptions directly so LTD-only accounts are
71
+ # accounted for.
72
+ def has_active_billing?
73
+ billing_subscriptions.where(status: %w[active trialing]).exists? || lifetime?
74
+ end
75
+
76
+ # Lazily resolves (and caches) the Stripe customer id for this
77
+ # Account. First call creates a Stripe customer keyed on the
78
+ # Account#name + the supplied billing email; subsequent calls
79
+ # return the cached value.
80
+ #
81
+ # The `customer_ref` is stored on every billing_subscriptions /
82
+ # billing_invoices / billing_lifetime_passes row, so once an
83
+ # Account has any billing activity we can read the ref off any
84
+ # existing row and skip the Stripe API call.
85
+ def stripe_customer_ref!(email:)
86
+ existing = billing_subscriptions.first&.customer_ref ||
87
+ billing_invoices.first&.customer_ref ||
88
+ billing_lifetime_passes.first&.customer_ref
89
+ return existing if existing
90
+
91
+ result = Billing::Customers::FindOrCreateService.call(
92
+ email: email,
93
+ metadata: { account_id: id, account_name: respond_to?(:name) ? name : nil }
94
+ )
95
+ raise Billing::Error, "Could not resolve Stripe customer: #{result.error}" unless result.ok?
96
+
97
+ result.value
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Billing
4
+ # Engine-scoped configuration. Override in
5
+ # config/initializers/billing.rb of the host application:
6
+ #
7
+ # Billing.configure do |c|
8
+ # c.gateway = "Billing::Gateways::Stripe"
9
+ # c.api_key = ENV.fetch("STRIPE_SECRET_KEY")
10
+ # c.webhook_secret = ENV.fetch("STRIPE_WEBHOOK_SECRET")
11
+ # # The host class that represents the billable tenant. The
12
+ # # engine includes `Billing::Billable` into this class at boot
13
+ # # so `account.start_subscription!(plan_ref:, email:)` etc are
14
+ # # available wherever you handle Account rows. Default is the
15
+ # # canonical `Accounts::Account` from the seams accounts engine;
16
+ # # set to a different constant name if your tenant lives on a
17
+ # # different model.
18
+ # c.billable_class = "Accounts::Account"
19
+ # end
20
+ class Configuration
21
+ attr_accessor :gateway, :api_key, :webhook_secret, :default_currency,
22
+ :process_webhooks_async, :billable_class
23
+ # Follow-up generators that add knobs (proration mode, dunning retries, tax adapters) declare their attr_accessor here.
24
+ # seams:insertion-point billing.configuration.attributes
25
+
26
+ def initialize
27
+ @gateway = "<%= gateway_class_name %>"
28
+ @api_key = ENV.fetch("<%= gateway_env_prefix %>_SECRET_KEY", nil)
29
+ @webhook_secret = ENV.fetch("<%= gateway_env_prefix %>_WEBHOOK_SECRET", nil)
30
+ @default_currency = "usd"
31
+ # Default to synchronous so failed handlers roll back the
32
+ # WebhookEvent insert and Stripe gets to retry. Hosts on Solid
33
+ # Queue (or similar) can flip to async + accept the trade-off.
34
+ @process_webhooks_async = false
35
+ # The Account-as-billable contract — see class comment.
36
+ @billable_class = "Accounts::Account"
37
+ # Follow-up generators that add defaults for new attributes (matching billing.configuration.attributes) splice them here.
38
+ # seams:insertion-point billing.configuration.defaults
39
+ end
40
+
41
+ # Short identifier used as the `gateway:` field in canonical event
42
+ # payloads. Derived from `gateway` (e.g. "Billing::Gateways::Stripe"
43
+ # → "stripe"). Override directly if your class name doesn't follow
44
+ # the convention. NOT memoized — `gateway` is mutable, so
45
+ # gateway_name re-derives on every read.
46
+ def gateway_name
47
+ @gateway_name || gateway.to_s.split("::").last.to_s.downcase
48
+ end
49
+
50
+ attr_writer :gateway_name
51
+ end
52
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Billing
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Billing
6
+
7
+ config.generators do |g|
8
+ g.test_framework :rspec
9
+ end
10
+
11
+ initializer "billing.register_events" do
12
+ # Subscription lifecycle.
13
+ Seams::EventRegistry.register("subscription.created.billing", emitted_by: "Billing")
14
+ Seams::EventRegistry.register("subscription.updated.billing", emitted_by: "Billing")
15
+ Seams::EventRegistry.register("subscription.canceled.billing", emitted_by: "Billing")
16
+ Seams::EventRegistry.register("subscription.trial_will_end.billing", emitted_by: "Billing")
17
+ # Invoice lifecycle.
18
+ Seams::EventRegistry.register("invoice.created.billing", emitted_by: "Billing")
19
+ Seams::EventRegistry.register("invoice.paid.billing", emitted_by: "Billing")
20
+ Seams::EventRegistry.register("invoice.failed.billing", emitted_by: "Billing")
21
+ Seams::EventRegistry.register("invoice.finalized.billing", emitted_by: "Billing")
22
+ Seams::EventRegistry.register("invoice.voided.billing", emitted_by: "Billing")
23
+ # One-off payment + refund signals.
24
+ Seams::EventRegistry.register("payment.succeeded.billing", emitted_by: "Billing")
25
+ Seams::EventRegistry.register("payment.failed.billing", emitted_by: "Billing")
26
+ Seams::EventRegistry.register("charge.refunded.billing", emitted_by: "Billing")
27
+ Seams::EventRegistry.register("checkout.session_completed.billing", emitted_by: "Billing")
28
+ # Lifetime Deal events — see issue #2 section 3A.LTD.
29
+ Seams::EventRegistry.register("lifetime.granted.billing", emitted_by: "Billing")
30
+ Seams::EventRegistry.register("lifetime.purchased.billing", emitted_by: "Billing")
31
+ Seams::EventRegistry.register("lifetime.revoked.billing", emitted_by: "Billing")
32
+ # Follow-up generators that ship new billing events (subscription.discount_applied.billing, dispute.created.billing) register them here.
33
+ # seams:insertion-point billing.engine.events
34
+ end
35
+
36
+ initializer "billing.append_migrations" do |app|
37
+ unless app.root == root
38
+ config.paths["db/migrate"].expanded.each do |expanded_path|
39
+ app.config.paths["db/migrate"] << expanded_path
40
+ end
41
+ end
42
+ end
43
+
44
+ # Follow-up generators that need their own initializer block (custom Billable include, gateway feature-flag wiring) declare it here.
45
+ # seams:insertion-point billing.engine.initializers
46
+
47
+ # Auto-include Billing::Billable into the configured tenant class
48
+ # (default: Accounts::Account). Wired via a to_prepare hook so the
49
+ # host's accounts engine has had a chance to autoload its model
50
+ # first; safe to re-run because Module#include is idempotent. The
51
+ # host can opt out by setting `billable_class = nil`.
52
+ config.to_prepare do
53
+ target = Billing.configuration.billable_class
54
+ next if target.nil? || (target.respond_to?(:empty?) && target.empty?)
55
+
56
+ begin
57
+ # Accept either a String ("Accounts::Account") or a Class
58
+ # (Accounts::Account). String is the documented shape — late
59
+ # constantize sidesteps autoload-order issues — but a host
60
+ # that hardwires the class object (e.g. via `c.billable_class
61
+ # = User`) shouldn't blow up at boot just because Class
62
+ # doesn't respond to `.constantize`.
63
+ klass = target.is_a?(String) ? target.constantize : target
64
+ klass.include(Billing::Billable)
65
+ rescue NameError
66
+ # Tenant class not yet loaded (e.g. accounts engine not
67
+ # installed). Hosts that wire Billable manually will still
68
+ # work; this is a best-effort auto-include.
69
+ end
70
+ end
71
+ end
72
+ end