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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +335 -0
- data/LICENSE +21 -0
- data/README.md +104 -0
- data/lib/generators/seams/accounts/accounts_generator.rb +272 -0
- data/lib/generators/seams/accounts/templates/README.md.tt +219 -0
- data/lib/generators/seams/accounts/templates/app/models/account.rb.tt +124 -0
- data/lib/generators/seams/accounts/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/accounts/templates/app/models/current.rb.tt +38 -0
- data/lib/generators/seams/accounts/templates/app/models/membership.rb.tt +114 -0
- data/lib/generators/seams/accounts/templates/config/routes.rb.tt +8 -0
- data/lib/generators/seams/accounts/templates/db/migrate/create_accounts.rb.tt +29 -0
- data/lib/generators/seams/accounts/templates/db/migrate/create_accounts_memberships.rb.tt +49 -0
- data/lib/generators/seams/accounts/templates/lib/accounts.rb.tt +21 -0
- data/lib/generators/seams/accounts/templates/lib/concerns/account_scoped.rb.tt +97 -0
- data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +105 -0
- data/lib/generators/seams/accounts/templates/lib/configuration.rb.tt +26 -0
- data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +55 -0
- data/lib/generators/seams/accounts/templates/spec/factories/accounts.rb.tt +49 -0
- data/lib/generators/seams/accounts/templates/spec/models/accounts/account_spec.rb.tt +64 -0
- data/lib/generators/seams/accounts/templates/spec/models/accounts/membership_spec.rb.tt +99 -0
- data/lib/generators/seams/accounts/templates/spec/runtime/accounts_boot_spec.rb.tt +181 -0
- data/lib/generators/seams/admin/admin_generator.rb +852 -0
- data/lib/generators/seams/admin/templates/README.md.tt +266 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_memberships_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +282 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/identities_controller.rb.tt +26 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/invitations_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/invoices_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/notification_preferences_controller.rb.tt +15 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/notifications_controller.rb.tt +18 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/plans_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/subscriptions_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/teams_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/teams_memberships_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +50 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +58 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +48 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +51 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +67 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +65 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +58 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +43 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +72 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +59 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +39 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +43 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/account_policy.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/accounts_membership_policy.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/application_policy.rb.tt +85 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/identity_policy.rb.tt +18 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/invitation_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/invoice_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/lifetime_pass_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_preference_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/plan_policy.rb.tt +11 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/subscription_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/team_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/teams_membership_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +33 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +169 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +67 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +21 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +21 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +25 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +23 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +47 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +22 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +28 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/config/routes.rb.tt +38 -0
- data/lib/generators/seams/admin/templates/lib/admin.rb.tt +36 -0
- data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +66 -0
- data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +90 -0
- data/lib/generators/seams/admin/templates/lib/context.rb.tt +44 -0
- data/lib/generators/seams/admin/templates/lib/engine.rb.tt +68 -0
- data/lib/generators/seams/admin/templates/spec/factories/admin.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +604 -0
- data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +157 -0
- data/lib/generators/seams/auth/add_oauth_provider/templates/adapter.rb.tt +95 -0
- data/lib/generators/seams/auth/add_oauth_provider/templates/adapter_spec.rb.tt +58 -0
- data/lib/generators/seams/auth/auth_generator.rb +311 -0
- data/lib/generators/seams/auth/templates/README.md.tt +289 -0
- data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +80 -0
- data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +44 -0
- data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +34 -0
- data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +49 -0
- data/lib/generators/seams/auth/templates/app/jobs/application_job.rb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/jobs/cleanup_expired_sessions_job.rb.tt +30 -0
- data/lib/generators/seams/auth/templates/app/mailers/passwords_mailer.rb.tt +15 -0
- data/lib/generators/seams/auth/templates/app/models/api_token.rb.tt +62 -0
- data/lib/generators/seams/auth/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/models/current.rb.tt +15 -0
- data/lib/generators/seams/auth/templates/app/models/identity.rb.tt +74 -0
- data/lib/generators/seams/auth/templates/app/models/oauth/provider.rb.tt +48 -0
- data/lib/generators/seams/auth/templates/app/models/session.rb.tt +28 -0
- data/lib/generators/seams/auth/templates/app/services/authenticate_identity.rb.tt +31 -0
- data/lib/generators/seams/auth/templates/app/services/generate_api_token.rb.tt +35 -0
- data/lib/generators/seams/auth/templates/app/services/oauth/authenticator.rb.tt +94 -0
- data/lib/generators/seams/auth/templates/app/services/register_identity.rb.tt +57 -0
- data/lib/generators/seams/auth/templates/app/services/reset_password.rb.tt +41 -0
- data/lib/generators/seams/auth/templates/app/services/revoke_api_token.rb.tt +38 -0
- data/lib/generators/seams/auth/templates/app/views/password_resets/edit.html.erb.tt +12 -0
- data/lib/generators/seams/auth/templates/app/views/password_resets/new.html.erb.tt +11 -0
- data/lib/generators/seams/auth/templates/app/views/passwords_mailer/reset_email.html.erb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/views/registrations/new.html.erb.tt +26 -0
- data/lib/generators/seams/auth/templates/app/views/sessions/_oauth_buttons.html.erb.tt +18 -0
- data/lib/generators/seams/auth/templates/app/views/sessions/new.html.erb.tt +17 -0
- data/lib/generators/seams/auth/templates/config/routes.rb.tt +21 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_api_tokens.rb.tt +26 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_identities.rb.tt +29 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +35 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_sessions.rb.tt +19 -0
- data/lib/generators/seams/auth/templates/lib/auth.rb.tt +39 -0
- data/lib/generators/seams/auth/templates/lib/concerns/api_authenticatable.rb.tt +58 -0
- data/lib/generators/seams/auth/templates/lib/concerns/authenticatable.rb.tt +32 -0
- data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +60 -0
- data/lib/generators/seams/auth/templates/lib/configuration.rb.tt +45 -0
- data/lib/generators/seams/auth/templates/lib/engine.rb.tt +46 -0
- data/lib/generators/seams/auth/templates/lib/oauth/abstract.rb.tt +87 -0
- data/lib/generators/seams/auth/templates/lib/oauth/github.rb.tt +112 -0
- data/lib/generators/seams/auth/templates/lib/oauth/google.rb.tt +78 -0
- data/lib/generators/seams/auth/templates/lib/tasks/auth_pii.rake.tt +68 -0
- data/lib/generators/seams/auth/templates/spec/factories/auth.rb.tt +38 -0
- data/lib/generators/seams/auth/templates/spec/mailers/passwords_mailer_spec.rb.tt +37 -0
- data/lib/generators/seams/auth/templates/spec/models/api_token_spec.rb.tt +84 -0
- data/lib/generators/seams/auth/templates/spec/models/identity_spec.rb.tt +56 -0
- data/lib/generators/seams/auth/templates/spec/models/oauth/provider_spec.rb.tt +64 -0
- data/lib/generators/seams/auth/templates/spec/models/session_spec.rb.tt +34 -0
- data/lib/generators/seams/auth/templates/spec/runtime/boot_spec.rb.tt +30 -0
- data/lib/generators/seams/auth/templates/spec/runtime/event_payload_spec.rb.tt +29 -0
- data/lib/generators/seams/auth/templates/spec/runtime/login_flow_spec.rb.tt +45 -0
- data/lib/generators/seams/billing/billing_generator.rb +476 -0
- data/lib/generators/seams/billing/templates/README.md.tt +355 -0
- data/lib/generators/seams/billing/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +84 -0
- data/lib/generators/seams/billing/templates/app/controllers/checkout_controller.rb.tt +92 -0
- data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +63 -0
- data/lib/generators/seams/billing/templates/app/controllers/plans_controller.rb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/controllers/portal_controller.rb.tt +45 -0
- data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +119 -0
- data/lib/generators/seams/billing/templates/app/controllers/webhooks_controller.rb.tt +98 -0
- data/lib/generators/seams/billing/templates/app/helpers/currency_helper.rb.tt +44 -0
- data/lib/generators/seams/billing/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/billing/templates/app/jobs/cancel_subscription_job.rb.tt +39 -0
- data/lib/generators/seams/billing/templates/app/jobs/start_subscription_job.rb.tt +32 -0
- data/lib/generators/seams/billing/templates/app/jobs/webhooks/process_event_job.rb.tt +37 -0
- data/lib/generators/seams/billing/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/billing/templates/app/models/invoice.rb.tt +35 -0
- data/lib/generators/seams/billing/templates/app/models/lifetime_pass.rb.tt +60 -0
- data/lib/generators/seams/billing/templates/app/models/plan.rb.tt +95 -0
- data/lib/generators/seams/billing/templates/app/models/subscription.rb.tt +31 -0
- data/lib/generators/seams/billing/templates/app/models/webhook_event.rb.tt +13 -0
- data/lib/generators/seams/billing/templates/app/services/checkout_session_service.rb.tt +25 -0
- data/lib/generators/seams/billing/templates/app/services/customers/find_or_create_service.rb.tt +73 -0
- data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +50 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/create_lifetime_session_service.rb.tt +82 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/create_pass_from_checkout_service.rb.tt +88 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/grant_pass_service.rb.tt +80 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/revoke_pass_service.rb.tt +59 -0
- data/lib/generators/seams/billing/templates/app/services/portal_session_service.rb.tt +23 -0
- data/lib/generators/seams/billing/templates/app/services/service_result.rb.tt +38 -0
- data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +67 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/cancel_service.rb.tt +42 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/change_plan_service.rb.tt +48 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/reactivate_service.rb.tt +28 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/event_router.rb.tt +54 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handler.rb.tt +93 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/charge_refunded_handler.rb.tt +18 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/checkout_session_completed_handler.rb.tt +58 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_created_handler.rb.tt +16 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_finalized_handler.rb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +80 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_paid_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_payment_failed_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_voided_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_failed_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_succeeded_handler.rb.tt +19 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_created_handler.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_deleted_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +92 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_trial_will_end_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_updated_handler.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/index.html.erb.tt +36 -0
- data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/new.html.erb.tt +37 -0
- data/lib/generators/seams/billing/templates/app/views/checkout/success.html.erb.tt +5 -0
- data/lib/generators/seams/billing/templates/app/views/invoices/index.html.erb.tt +22 -0
- data/lib/generators/seams/billing/templates/app/views/invoices/show.html.erb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/views/plans/index.html.erb.tt +51 -0
- data/lib/generators/seams/billing/templates/app/views/subscriptions/index.html.erb.tt +16 -0
- data/lib/generators/seams/billing/templates/app/views/subscriptions/show.html.erb.tt +25 -0
- data/lib/generators/seams/billing/templates/config/routes.rb.tt +39 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_invoices.rb.tt +32 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_lifetime_passes.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_plans.rb.tt +31 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_subscriptions.rb.tt +33 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_webhook_events.rb.tt +24 -0
- data/lib/generators/seams/billing/templates/lib/billing.rb.tt +34 -0
- data/lib/generators/seams/billing/templates/lib/concerns/billable.rb.tt +100 -0
- data/lib/generators/seams/billing/templates/lib/configuration.rb.tt +52 -0
- data/lib/generators/seams/billing/templates/lib/engine.rb.tt +72 -0
- data/lib/generators/seams/billing/templates/lib/gateways/abstract.rb.tt +65 -0
- data/lib/generators/seams/billing/templates/lib/gateways/adyen.rb.tt +16 -0
- data/lib/generators/seams/billing/templates/lib/gateways/paddle.rb.tt +22 -0
- data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +155 -0
- data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +101 -0
- data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/lib/tasks/billing_check.rake.tt +34 -0
- data/lib/generators/seams/billing/templates/spec/factories/billing.rb.tt +65 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +19 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +25 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +28 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +19 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +20 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +21 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/gateways/contract_spec.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +53 -0
- data/lib/generators/seams/billing/templates/spec/models/plan_spec.rb.tt +81 -0
- data/lib/generators/seams/billing/templates/spec/models/subscription_spec.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/spec/runtime/boot_spec.rb.tt +38 -0
- data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +382 -0
- data/lib/generators/seams/billing/templates/spec/support/shared_examples/a_billing_gateway.rb.tt +100 -0
- data/lib/generators/seams/billing/templates/spec/support/stripe_helpers.rb.tt +59 -0
- data/lib/generators/seams/core/core_generator.rb +191 -0
- data/lib/generators/seams/core/templates/README.md.tt +45 -0
- data/lib/generators/seams/core/templates/app/controllers/concerns/has_current_attributes.rb.tt +71 -0
- data/lib/generators/seams/core/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/core/templates/app/models/audit_log.rb.tt +19 -0
- data/lib/generators/seams/core/templates/app/models/concerns/auditable.rb.tt +64 -0
- data/lib/generators/seams/core/templates/app/models/concerns/sluggable.rb.tt +53 -0
- data/lib/generators/seams/core/templates/app/models/concerns/soft_deletable.rb.tt +37 -0
- data/lib/generators/seams/core/templates/app/models/concerns/tenant_scoped.rb.tt +39 -0
- data/lib/generators/seams/core/templates/app/models/current.rb.tt +16 -0
- data/lib/generators/seams/core/templates/app/services/event_publisher.rb.tt +23 -0
- data/lib/generators/seams/core/templates/app/validators/email_format_validator.rb.tt +21 -0
- data/lib/generators/seams/core/templates/db/migrate/create_core_audit_logs.rb.tt +29 -0
- data/lib/generators/seams/core/templates/lib/core.rb.tt +8 -0
- data/lib/generators/seams/core/templates/lib/engine.rb.tt +28 -0
- data/lib/generators/seams/core/templates/spec/concerns/auditable_spec.rb.tt +39 -0
- data/lib/generators/seams/core/templates/spec/concerns/sluggable_spec.rb.tt +29 -0
- data/lib/generators/seams/core/templates/spec/models/audit_log_spec.rb.tt +22 -0
- data/lib/generators/seams/core/templates/spec/runtime/boot_spec.rb.tt +25 -0
- data/lib/generators/seams/core/templates/spec/validators/email_format_validator_spec.rb.tt +29 -0
- data/lib/generators/seams/engine/engine_generator.rb +165 -0
- data/lib/generators/seams/engine/templates/Gemfile.tt +19 -0
- data/lib/generators/seams/engine/templates/LICENSE.tt +21 -0
- data/lib/generators/seams/engine/templates/README.md.tt +40 -0
- data/lib/generators/seams/engine/templates/Rakefile.tt +14 -0
- data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
- data/lib/generators/seams/engine/templates/app/application_record.rb.tt +16 -0
- data/lib/generators/seams/engine/templates/config/locales/en.yml.tt +14 -0
- data/lib/generators/seams/engine/templates/config/routes.rb.tt +4 -0
- data/lib/generators/seams/engine/templates/gemspec.tt +20 -0
- data/lib/generators/seams/engine/templates/host_initializer.rb.tt +13 -0
- data/lib/generators/seams/engine/templates/lib/engine.rb.tt +27 -0
- data/lib/generators/seams/engine/templates/lib/root.rb.tt +7 -0
- data/lib/generators/seams/engine/templates/lib/version.rb.tt +5 -0
- data/lib/generators/seams/engine/templates/rubocop.yml.tt +55 -0
- data/lib/generators/seams/engine/templates/spec/example_spec.rb.tt +16 -0
- data/lib/generators/seams/engine/templates/spec/spec_helper.rb.tt +23 -0
- data/lib/generators/seams/install/install_generator.rb +211 -0
- data/lib/generators/seams/install/templates/Dockerfile.tt +52 -0
- data/lib/generators/seams/install/templates/Procfile.tt +14 -0
- data/lib/generators/seams/install/templates/bin_seams.tt +107 -0
- data/lib/generators/seams/install/templates/ci.yml.tt +123 -0
- data/lib/generators/seams/install/templates/deploy.yml.tt +63 -0
- data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +86 -0
- data/lib/generators/seams/install/templates/docker-entrypoint.tt +27 -0
- data/lib/generators/seams/install/templates/rubocop.yml.tt +33 -0
- data/lib/generators/seams/install/templates/ruby-version.tt +1 -0
- data/lib/generators/seams/install/templates/script/collate_coverage.rb.tt +33 -0
- data/lib/generators/seams/install/templates/script/run_affected_tests.sh.tt +64 -0
- data/lib/generators/seams/install/templates/seams.rake.tt +65 -0
- data/lib/generators/seams/install/templates/seams.rb.tt +9 -0
- data/lib/generators/seams/install/templates/seams_engines.rb.tt +15 -0
- data/lib/generators/seams/notifications/notifications_generator.rb +395 -0
- data/lib/generators/seams/notifications/templates/README.md.tt +269 -0
- data/lib/generators/seams/notifications/templates/app/channels/notification_channel.rb.tt +36 -0
- data/lib/generators/seams/notifications/templates/app/controllers/notifications_controller.rb.tt +58 -0
- data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +54 -0
- data/lib/generators/seams/notifications/templates/app/javascript/controllers/notification_bell_controller.js.tt +34 -0
- data/lib/generators/seams/notifications/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/notifications/templates/app/jobs/create_notification_job.rb.tt +31 -0
- data/lib/generators/seams/notifications/templates/app/jobs/send_due_notifications_job.rb.tt +22 -0
- data/lib/generators/seams/notifications/templates/app/jobs/send_notification_job.rb.tt +13 -0
- data/lib/generators/seams/notifications/templates/app/mailers/application_mailer.rb.tt +12 -0
- data/lib/generators/seams/notifications/templates/app/mailers/notification_mailer.rb.tt +23 -0
- data/lib/generators/seams/notifications/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/notifications/templates/app/models/delivery.rb.tt +13 -0
- data/lib/generators/seams/notifications/templates/app/models/notification.rb.tt +218 -0
- data/lib/generators/seams/notifications/templates/app/models/notification_preference.rb.tt +29 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/email.rb.tt +38 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/in_app.rb.tt +26 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/sms.rb.tt +33 -0
- data/lib/generators/seams/notifications/templates/app/subscribers/auth_subscriber.rb.tt +71 -0
- data/lib/generators/seams/notifications/templates/app/subscribers/billing_subscriber.rb.tt +127 -0
- data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.html.erb.tt +22 -0
- data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/notifications/_bell.html.erb.tt +15 -0
- data/lib/generators/seams/notifications/templates/app/views/notifications/index.html.erb.tt +15 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.html.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.text.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.html.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.html.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.html.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.text.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/default.html.erb.tt +10 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/default.text.erb.tt +11 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/welcome.html.erb.tt +6 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/welcome.text.erb.tt +6 -0
- data/lib/generators/seams/notifications/templates/config/initializers/notifications.rb.tt +58 -0
- data/lib/generators/seams/notifications/templates/config/routes.rb.tt +17 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notification_deliveries.rb.tt +16 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notification_preferences.rb.tt +25 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notifications.rb.tt +35 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/abstract.rb.tt +20 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/action_mailer.rb.tt +17 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/null_sms.rb.tt +23 -0
- data/lib/generators/seams/notifications/templates/lib/concerns/notifiable.rb.tt +135 -0
- data/lib/generators/seams/notifications/templates/lib/configuration.rb.tt +24 -0
- data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +35 -0
- data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +75 -0
- data/lib/generators/seams/notifications/templates/lib/type_registry.rb.tt +74 -0
- data/lib/generators/seams/notifications/templates/spec/factories/notifications.rb.tt +53 -0
- data/lib/generators/seams/notifications/templates/spec/models/delivery_spec.rb.tt +28 -0
- data/lib/generators/seams/notifications/templates/spec/models/notification_preference_spec.rb.tt +46 -0
- data/lib/generators/seams/notifications/templates/spec/models/notification_spec.rb.tt +60 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/bell_broadcast_spec.rb.tt +59 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/billing_subscriber_skip_spec.rb.tt +87 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/boot_spec.rb.tt +39 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/schedule_round_trip_spec.rb.tt +55 -0
- data/lib/generators/seams/remove/remove_generator.rb +259 -0
- data/lib/generators/seams/teams/teams_generator.rb +298 -0
- data/lib/generators/seams/teams/templates/README.md.tt +88 -0
- data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +102 -0
- data/lib/generators/seams/teams/templates/app/controllers/memberships_controller.rb.tt +54 -0
- data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +68 -0
- data/lib/generators/seams/teams/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/teams/templates/app/mailers/invitation_mailer.rb.tt +34 -0
- data/lib/generators/seams/teams/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/teams/templates/app/models/current.rb.tt +30 -0
- data/lib/generators/seams/teams/templates/app/models/invitation.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/models/membership.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/models/team.rb.tt +32 -0
- data/lib/generators/seams/teams/templates/app/subscribers/invitation_subscriber.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/views/invitation_mailer/invite.text.erb.tt +8 -0
- data/lib/generators/seams/teams/templates/app/views/invitations/index.html.erb.tt +44 -0
- data/lib/generators/seams/teams/templates/app/views/memberships/index.html.erb.tt +32 -0
- data/lib/generators/seams/teams/templates/app/views/teams/edit.html.erb.tt +28 -0
- data/lib/generators/seams/teams/templates/app/views/teams/index.html.erb.tt +15 -0
- data/lib/generators/seams/teams/templates/app/views/teams/new.html.erb.tt +24 -0
- data/lib/generators/seams/teams/templates/app/views/teams/show.html.erb.tt +17 -0
- data/lib/generators/seams/teams/templates/config/routes.rb.tt +19 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_team_invitations.rb.tt +24 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_team_memberships.rb.tt +25 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_teams.rb.tt +18 -0
- data/lib/generators/seams/teams/templates/lib/concerns/account_scoped.rb.tt +79 -0
- data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +55 -0
- data/lib/generators/seams/teams/templates/lib/configuration.rb.tt +45 -0
- data/lib/generators/seams/teams/templates/lib/engine.rb.tt +51 -0
- data/lib/generators/seams/teams/templates/lib/teams.rb.tt +22 -0
- data/lib/generators/seams/teams/templates/spec/factories/teams.rb.tt +47 -0
- data/lib/generators/seams/teams/templates/spec/models/invitation_spec.rb.tt +25 -0
- data/lib/generators/seams/teams/templates/spec/models/membership_spec.rb.tt +29 -0
- data/lib/generators/seams/teams/templates/spec/models/team_spec.rb.tt +23 -0
- data/lib/generators/seams/teams/templates/spec/runtime/boot_spec.rb.tt +32 -0
- data/lib/seams/cli/list.rb +111 -0
- data/lib/seams/cli/quality.rb +99 -0
- data/lib/seams/cli/resolve.rb +276 -0
- data/lib/seams/cli/test_changed.rb +116 -0
- data/lib/seams/cli.rb +48 -0
- data/lib/seams/configuration.rb +19 -0
- data/lib/seams/cops/known_queue_names.rb +42 -0
- data/lib/seams/cops/migration_comments.rb +68 -0
- data/lib/seams/cops/no_cross_engine_dependency.rb +58 -0
- data/lib/seams/cops/no_cross_engine_model_access.rb +153 -0
- data/lib/seams/cops.rb +18 -0
- data/lib/seams/event_registry.rb +49 -0
- data/lib/seams/events/adapter.rb +24 -0
- data/lib/seams/events/adapters/active_support.rb +31 -0
- data/lib/seams/events/publisher.rb +178 -0
- data/lib/seams/events.rb +39 -0
- data/lib/seams/generators/dummy_app_writer.rb +424 -0
- data/lib/seams/generators/eject_aware.rb +102 -0
- data/lib/seams/generators/follow_up_generator.rb +148 -0
- data/lib/seams/generators/host_injector.rb +124 -0
- data/lib/seams/generators/sibling_rubocop_writer.rb +77 -0
- data/lib/seams/generators/splicer.rb +217 -0
- data/lib/seams/observability/adapter.rb +33 -0
- data/lib/seams/observability/adapters/rails_logger.rb +59 -0
- data/lib/seams/observability.rb +34 -0
- data/lib/seams/runtime.rb +23 -0
- data/lib/seams/version.rb +5 -0
- data/lib/seams.rb +23 -0
- metadata +493 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../rails_helper"
|
|
4
|
+
require_relative "../support/stripe_helpers"
|
|
5
|
+
|
|
6
|
+
# Runtime coverage for the 13 webhook handlers shipped under
|
|
7
|
+
# app/services/billing/webhooks/handlers/. Generator specs already
|
|
8
|
+
# assert the FILES exist + contain the right SEAMS_EVENT constants;
|
|
9
|
+
# this spec instantiates each handler with the matching Stripe event
|
|
10
|
+
# fixture, calls #call, and asserts:
|
|
11
|
+
#
|
|
12
|
+
# 1. The local Billing::Subscription / Billing::Invoice row is
|
|
13
|
+
# upserted with the expected columns (or NOT upserted, for the
|
|
14
|
+
# stateless payment_intent / charge handlers).
|
|
15
|
+
# 2. The canonical seams event is published with the expected
|
|
16
|
+
# payload shape (including `account_id` post-Wave-9).
|
|
17
|
+
#
|
|
18
|
+
# Why this exists: a regression in SubscriptionHandlerBase#upsert_subscription
|
|
19
|
+
# or InvoiceHandlerBase#upsert_invoice would not be caught by the
|
|
20
|
+
# string-matching generator specs alone — they ship green even if the
|
|
21
|
+
# upsert silently no-ops. This spec exercises the real ActiveRecord
|
|
22
|
+
# round-trip + the Seams::Events::Publisher contract.
|
|
23
|
+
#
|
|
24
|
+
# Account resolution: post-Wave-9 the upsert requires an `account_id`
|
|
25
|
+
# (the Accounts::Account UUID). The handler resolves it from any
|
|
26
|
+
# pre-existing Billing::Subscription / Billing::Invoice row sharing
|
|
27
|
+
# the same `customer_ref`. Each test seeds a sentinel row with the
|
|
28
|
+
# right customer_ref + a known account_id, then asserts the handler
|
|
29
|
+
# preserved or wrote that account_id on the row it cares about.
|
|
30
|
+
#
|
|
31
|
+
# CheckoutSessionCompletedHandler is covered for both the subscription
|
|
32
|
+
# branch (publishes checkout.session_completed.billing) and the
|
|
33
|
+
# Lifetime Deal branch (forks to Billing::Lifetime::CreatePassFromCheckoutService).
|
|
34
|
+
RSpec.describe "Billing webhook handlers (runtime)", type: :integration do
|
|
35
|
+
include StripeHelpers
|
|
36
|
+
|
|
37
|
+
# The known Account that every test references — kept stable so
|
|
38
|
+
# cross-test assertions (account_id is preserved through upsert,
|
|
39
|
+
# for instance) stay obvious.
|
|
40
|
+
TEST_ACCOUNT_ID = "11111111-1111-1111-1111-111111111111"
|
|
41
|
+
|
|
42
|
+
# Helper: load a fixture and reshape it into the event hash that
|
|
43
|
+
# Billing::Gateways::Stripe#verify_webhook hands to the handler.
|
|
44
|
+
# Fixtures are full Stripe event envelopes (top-level id/type +
|
|
45
|
+
# nested data.object); handlers expect a flat hash with :object
|
|
46
|
+
# holding what was data.object.
|
|
47
|
+
def event_for(fixture_name)
|
|
48
|
+
full = stripe_event_fixture(fixture_name)
|
|
49
|
+
{
|
|
50
|
+
id: full[:id],
|
|
51
|
+
type: full[:type],
|
|
52
|
+
livemode: full[:livemode],
|
|
53
|
+
object: full.dig(:data, :object),
|
|
54
|
+
raw: full.to_json
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Capture every payload published under +event_name+ during the
|
|
59
|
+
# block. Returns the Array of payload hashes. Uses the Publisher's
|
|
60
|
+
# public subscribe API so we exercise the real adapter dispatch
|
|
61
|
+
# (ActiveSupport::Notifications by default) rather than mocking the
|
|
62
|
+
# Publisher away.
|
|
63
|
+
def capture_published(event_name)
|
|
64
|
+
captured = []
|
|
65
|
+
subscriber = Seams::Events::Publisher.subscribe(event_name) { |payload| captured << payload }
|
|
66
|
+
yield
|
|
67
|
+
captured
|
|
68
|
+
ensure
|
|
69
|
+
Seams::Events::Publisher.unsubscribe(subscriber) if subscriber
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Seed a sibling Billing::Subscription on `customer_ref` so the
|
|
73
|
+
# handler base's resolve_account_id_from_sibling lookup can find
|
|
74
|
+
# it. Returns the seeded row.
|
|
75
|
+
def seed_account_on(customer_ref)
|
|
76
|
+
create(:billing_subscription,
|
|
77
|
+
account_id: TEST_ACCOUNT_ID,
|
|
78
|
+
customer_ref: customer_ref,
|
|
79
|
+
gateway_ref: "sub_seed_#{customer_ref}",
|
|
80
|
+
plan_ref: "price_seed",
|
|
81
|
+
status: "active")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
let(:gateway) { "stripe" }
|
|
85
|
+
|
|
86
|
+
describe Billing::Webhooks::Handlers::SubscriptionCreatedHandler do
|
|
87
|
+
it "upserts a Billing::Subscription row + publishes subscription.created.billing" do
|
|
88
|
+
seed_account_on("cus_test_123")
|
|
89
|
+
|
|
90
|
+
event = event_for("customer_subscription_created")
|
|
91
|
+
captured = capture_published("subscription.created.billing") do
|
|
92
|
+
described_class.new(event: event, gateway: gateway).call
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
subscription = Billing::Subscription.find_by(gateway_ref: "sub_test_123")
|
|
96
|
+
expect(subscription).not_to be_nil
|
|
97
|
+
expect(subscription.account_id).to eq(TEST_ACCOUNT_ID)
|
|
98
|
+
expect(subscription.customer_ref).to eq("cus_test_123")
|
|
99
|
+
expect(subscription.plan_ref).to eq("price_test_pro")
|
|
100
|
+
expect(subscription.status).to eq("active")
|
|
101
|
+
expect(subscription.current_period_end).to be_within(1.second).of(Time.at(1_732_678_400))
|
|
102
|
+
|
|
103
|
+
expect(captured.size).to eq(1)
|
|
104
|
+
expect(captured.first[:gateway]).to eq("stripe")
|
|
105
|
+
expect(captured.first[:account_id]).to eq(TEST_ACCOUNT_ID)
|
|
106
|
+
expect(captured.first[:customer_ref]).to eq("cus_test_123")
|
|
107
|
+
expect(captured.first[:object_id]).to eq("sub_test_123")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe Billing::Webhooks::Handlers::SubscriptionUpdatedHandler do
|
|
112
|
+
it "updates the existing Billing::Subscription row + publishes subscription.updated.billing" do
|
|
113
|
+
# Seed the row first so the handler exercises the find branch
|
|
114
|
+
# of find_or_initialize_by — guards against a regression that
|
|
115
|
+
# accidentally creates duplicates instead of updating.
|
|
116
|
+
create(:billing_subscription,
|
|
117
|
+
account_id: TEST_ACCOUNT_ID,
|
|
118
|
+
gateway_ref: "sub_test_123",
|
|
119
|
+
customer_ref: "cus_test_123",
|
|
120
|
+
plan_ref: "price_test_pro",
|
|
121
|
+
status: "active")
|
|
122
|
+
|
|
123
|
+
event = event_for("customer_subscription_updated")
|
|
124
|
+
captured = capture_published("subscription.updated.billing") do
|
|
125
|
+
described_class.new(event: event, gateway: gateway).call
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
subscription = Billing::Subscription.find_by(gateway_ref: "sub_test_123")
|
|
129
|
+
expect(Billing::Subscription.where(gateway_ref: "sub_test_123").count).to eq(1)
|
|
130
|
+
expect(subscription.account_id).to eq(TEST_ACCOUNT_ID)
|
|
131
|
+
expect(subscription.plan_ref).to eq("price_test_pro_annual")
|
|
132
|
+
expect(subscription.current_period_end).to be_within(1.second).of(Time.at(1_735_270_400))
|
|
133
|
+
expect(captured.size).to eq(1)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
describe Billing::Webhooks::Handlers::SubscriptionDeletedHandler do
|
|
138
|
+
it "flips the row status to canceled + publishes subscription.canceled.billing" do
|
|
139
|
+
create(:billing_subscription,
|
|
140
|
+
account_id: TEST_ACCOUNT_ID,
|
|
141
|
+
gateway_ref: "sub_test_123",
|
|
142
|
+
customer_ref: "cus_test_123",
|
|
143
|
+
plan_ref: "price_test_pro",
|
|
144
|
+
status: "active")
|
|
145
|
+
|
|
146
|
+
# Stripe sends an empty items.data on the deletion event, but
|
|
147
|
+
# the handler still overwrites plan_ref from the payload — the
|
|
148
|
+
# fixture would fail validation. We patch in a minimal items
|
|
149
|
+
# block here so the upsert succeeds and we can assert on the
|
|
150
|
+
# status flip the handler is responsible for.
|
|
151
|
+
full = stripe_event_fixture("customer_subscription_deleted").deep_dup
|
|
152
|
+
full[:data][:object][:items] = {
|
|
153
|
+
data: [{ price: { id: "price_test_pro" } }]
|
|
154
|
+
}
|
|
155
|
+
event = {
|
|
156
|
+
id: full[:id],
|
|
157
|
+
type: full[:type],
|
|
158
|
+
livemode: full[:livemode],
|
|
159
|
+
object: full[:data][:object],
|
|
160
|
+
raw: full.to_json
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
captured = capture_published("subscription.canceled.billing") do
|
|
164
|
+
described_class.new(event: event, gateway: gateway).call
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
subscription = Billing::Subscription.find_by(gateway_ref: "sub_test_123")
|
|
168
|
+
expect(subscription.status).to eq("canceled")
|
|
169
|
+
expect(subscription.account_id).to eq(TEST_ACCOUNT_ID)
|
|
170
|
+
expect(captured.size).to eq(1)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
describe Billing::Webhooks::Handlers::SubscriptionTrialWillEndHandler do
|
|
175
|
+
it "upserts the row + publishes subscription.trial_will_end.billing" do
|
|
176
|
+
seed_account_on("cus_test_123")
|
|
177
|
+
|
|
178
|
+
# The shipped fixture's items.data[0].price intentionally omits
|
|
179
|
+
# `id` (lookup_key only). We add `id` here so plan_ref_from_object_hash
|
|
180
|
+
# returns a value the model's presence validation accepts.
|
|
181
|
+
full = stripe_event_fixture("customer_subscription_trial_will_end").deep_dup
|
|
182
|
+
full[:data][:object][:items][:data][0][:price][:id] = "price_test_pro"
|
|
183
|
+
event = {
|
|
184
|
+
id: full[:id],
|
|
185
|
+
type: full[:type],
|
|
186
|
+
livemode: full[:livemode],
|
|
187
|
+
object: full[:data][:object],
|
|
188
|
+
raw: full.to_json
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
captured = capture_published("subscription.trial_will_end.billing") do
|
|
192
|
+
described_class.new(event: event, gateway: gateway).call
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
subscription = Billing::Subscription.find_by(gateway_ref: "sub_test_123")
|
|
196
|
+
expect(subscription).not_to be_nil
|
|
197
|
+
expect(subscription.status).to eq("trialing")
|
|
198
|
+
expect(subscription.account_id).to eq(TEST_ACCOUNT_ID)
|
|
199
|
+
expect(captured.size).to eq(1)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe Billing::Webhooks::Handlers::InvoiceCreatedHandler do
|
|
204
|
+
it "upserts a draft Billing::Invoice row + publishes invoice.created.billing" do
|
|
205
|
+
seed_account_on("cus_test_123")
|
|
206
|
+
|
|
207
|
+
event = event_for("invoice_created")
|
|
208
|
+
captured = capture_published("invoice.created.billing") do
|
|
209
|
+
described_class.new(event: event, gateway: gateway).call
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
invoice = Billing::Invoice.find_by(gateway_ref: "in_test_123")
|
|
213
|
+
expect(invoice).not_to be_nil
|
|
214
|
+
expect(invoice.account_id).to eq(TEST_ACCOUNT_ID)
|
|
215
|
+
expect(invoice.customer_ref).to eq("cus_test_123")
|
|
216
|
+
expect(invoice.subscription_ref).to eq("sub_test_123")
|
|
217
|
+
expect(invoice.status).to eq("draft")
|
|
218
|
+
expect(invoice.amount_cents).to eq(1299)
|
|
219
|
+
expect(invoice.currency).to eq("GBP")
|
|
220
|
+
expect(invoice.paid_at).to be_nil
|
|
221
|
+
expect(captured.size).to eq(1)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
describe Billing::Webhooks::Handlers::InvoicePaidHandler do
|
|
226
|
+
it "marks the invoice paid (status + paid_at) + publishes invoice.paid.billing" do
|
|
227
|
+
seed_account_on("cus_test_123")
|
|
228
|
+
|
|
229
|
+
event = event_for("invoice_paid")
|
|
230
|
+
captured = capture_published("invoice.paid.billing") do
|
|
231
|
+
described_class.new(event: event, gateway: gateway).call
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
invoice = Billing::Invoice.find_by(gateway_ref: "in_test_123")
|
|
235
|
+
expect(invoice.status).to eq("paid")
|
|
236
|
+
expect(invoice.paid_at).not_to be_nil
|
|
237
|
+
expect(invoice.amount_cents).to eq(1299)
|
|
238
|
+
expect(invoice.account_id).to eq(TEST_ACCOUNT_ID)
|
|
239
|
+
expect(captured.size).to eq(1)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
describe Billing::Webhooks::Handlers::InvoicePaymentFailedHandler do
|
|
244
|
+
it "leaves the invoice in 'open' + publishes invoice.failed.billing" do
|
|
245
|
+
seed_account_on("cus_test_123")
|
|
246
|
+
|
|
247
|
+
event = event_for("invoice_payment_failed")
|
|
248
|
+
captured = capture_published("invoice.failed.billing") do
|
|
249
|
+
described_class.new(event: event, gateway: gateway).call
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
invoice = Billing::Invoice.find_by(gateway_ref: "in_test_123")
|
|
253
|
+
expect(invoice.status).to eq("open")
|
|
254
|
+
expect(invoice.paid_at).to be_nil
|
|
255
|
+
expect(captured.size).to eq(1)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
describe Billing::Webhooks::Handlers::InvoiceFinalizedHandler do
|
|
260
|
+
it "promotes draft → open + publishes invoice.finalized.billing" do
|
|
261
|
+
create(:billing_invoice,
|
|
262
|
+
account_id: TEST_ACCOUNT_ID,
|
|
263
|
+
gateway_ref: "in_test_123",
|
|
264
|
+
status: "draft",
|
|
265
|
+
paid_at: nil)
|
|
266
|
+
|
|
267
|
+
event = event_for("invoice_finalized")
|
|
268
|
+
captured = capture_published("invoice.finalized.billing") do
|
|
269
|
+
described_class.new(event: event, gateway: gateway).call
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
invoice = Billing::Invoice.find_by(gateway_ref: "in_test_123")
|
|
273
|
+
expect(invoice.status).to eq("open")
|
|
274
|
+
expect(captured.size).to eq(1)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
describe Billing::Webhooks::Handlers::InvoiceVoidedHandler do
|
|
279
|
+
it "flips status to void + publishes invoice.voided.billing" do
|
|
280
|
+
seed_account_on("cus_test_123")
|
|
281
|
+
|
|
282
|
+
event = event_for("invoice_voided")
|
|
283
|
+
captured = capture_published("invoice.voided.billing") do
|
|
284
|
+
described_class.new(event: event, gateway: gateway).call
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
invoice = Billing::Invoice.find_by(gateway_ref: "in_test_123")
|
|
288
|
+
expect(invoice.status).to eq("void")
|
|
289
|
+
expect(captured.size).to eq(1)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
describe Billing::Webhooks::Handlers::PaymentSucceededHandler do
|
|
294
|
+
it "publishes payment.succeeded.billing without writing any local row" do
|
|
295
|
+
event = event_for("payment_intent_succeeded")
|
|
296
|
+
captured = capture_published("payment.succeeded.billing") do
|
|
297
|
+
described_class.new(event: event, gateway: gateway).call
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# PaymentIntents are not modelled locally — see the handler comment.
|
|
301
|
+
expect(Billing::Subscription.count).to eq(0)
|
|
302
|
+
expect(Billing::Invoice.count).to eq(0)
|
|
303
|
+
expect(captured.size).to eq(1)
|
|
304
|
+
expect(captured.first[:object_id]).to eq("pi_test_123")
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
describe Billing::Webhooks::Handlers::PaymentFailedHandler do
|
|
309
|
+
it "publishes payment.failed.billing without writing any local row" do
|
|
310
|
+
event = event_for("payment_intent_payment_failed")
|
|
311
|
+
captured = capture_published("payment.failed.billing") do
|
|
312
|
+
described_class.new(event: event, gateway: gateway).call
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
expect(Billing::Subscription.count).to eq(0)
|
|
316
|
+
expect(Billing::Invoice.count).to eq(0)
|
|
317
|
+
expect(captured.size).to eq(1)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
describe Billing::Webhooks::Handlers::ChargeRefundedHandler do
|
|
322
|
+
it "publishes charge.refunded.billing without writing any local row" do
|
|
323
|
+
event = event_for("charge_refunded")
|
|
324
|
+
captured = capture_published("charge.refunded.billing") do
|
|
325
|
+
described_class.new(event: event, gateway: gateway).call
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
expect(captured.size).to eq(1)
|
|
329
|
+
expect(captured.first[:object_id]).to eq("ch_test_123")
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
describe Billing::Webhooks::Handlers::CheckoutSessionCompletedHandler do
|
|
334
|
+
context "subscription mode (default fixture)" do
|
|
335
|
+
it "publishes checkout.session_completed.billing and does NOT touch LifetimePass" do
|
|
336
|
+
event = event_for("checkout_session_completed")
|
|
337
|
+
captured = capture_published("checkout.session_completed.billing") do
|
|
338
|
+
described_class.new(event: event, gateway: gateway).call
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
expect(captured.size).to eq(1)
|
|
342
|
+
expect(Billing::LifetimePass.count).to eq(0)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
context "lifetime mode (mode=payment + metadata.access_type=lifetime)" do
|
|
347
|
+
it "forks to CreatePassFromCheckoutService which creates a LifetimePass + emits lifetime.purchased.billing" do
|
|
348
|
+
full = stripe_event_fixture("checkout_session_completed").deep_dup
|
|
349
|
+
full[:data][:object][:mode] = "payment"
|
|
350
|
+
full[:data][:object][:metadata] = {
|
|
351
|
+
access_type: "lifetime",
|
|
352
|
+
plan_ref: "price_test_lifetime_1",
|
|
353
|
+
account_id: TEST_ACCOUNT_ID
|
|
354
|
+
}
|
|
355
|
+
event = {
|
|
356
|
+
id: full[:id],
|
|
357
|
+
type: full[:type],
|
|
358
|
+
livemode: full[:livemode],
|
|
359
|
+
object: full[:data][:object],
|
|
360
|
+
raw: full.to_json
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
captured_lifetime = capture_published("lifetime.purchased.billing") do
|
|
364
|
+
captured_session = capture_published("checkout.session_completed.billing") do
|
|
365
|
+
described_class.new(event: event, gateway: gateway).call
|
|
366
|
+
end
|
|
367
|
+
# Lifetime path forks BEFORE publish — checkout.session_completed
|
|
368
|
+
# is intentionally not emitted on the LTD branch.
|
|
369
|
+
expect(captured_session).to be_empty
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
pass = Billing::LifetimePass.find_by(gateway_ref: "cs_test_123")
|
|
373
|
+
expect(pass).not_to be_nil
|
|
374
|
+
expect(pass.account_id).to eq(TEST_ACCOUNT_ID)
|
|
375
|
+
expect(pass.customer_ref).to eq("cus_test_123")
|
|
376
|
+
expect(pass.plan_ref).to eq("price_test_lifetime_1")
|
|
377
|
+
expect(captured_lifetime.size).to eq(1)
|
|
378
|
+
expect(captured_lifetime.first[:account_id]).to eq(TEST_ACCOUNT_ID)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
data/lib/generators/seams/billing/templates/spec/support/shared_examples/a_billing_gateway.rb.tt
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shared contract every Billing gateway adapter must satisfy. Use
|
|
4
|
+
# this when you write a new gateway (Paddle, Adyen, etc) so you know
|
|
5
|
+
# you have implemented the full Abstract interface that the rest of
|
|
6
|
+
# the engine depends on.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
#
|
|
10
|
+
# require "rails_helper"
|
|
11
|
+
# require "support/shared_examples/a_billing_gateway"
|
|
12
|
+
#
|
|
13
|
+
# RSpec.describe Billing::Gateways::Paddle do
|
|
14
|
+
# it_behaves_like "a billing gateway"
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# The shared example only checks the *contract* (the methods exist
|
|
18
|
+
# and accept the documented arguments). Wiring-level behaviour —
|
|
19
|
+
# does Paddle actually charge people? — needs an integration test
|
|
20
|
+
# that hits Paddle's test mode.
|
|
21
|
+
RSpec.shared_examples "a billing gateway" do
|
|
22
|
+
let(:gateway) { described_class.new }
|
|
23
|
+
|
|
24
|
+
describe "#create_subscription" do
|
|
25
|
+
it "accepts customer_ref + plan_ref + arbitrary keyword extras" do
|
|
26
|
+
expect(gateway).to respond_to(:create_subscription)
|
|
27
|
+
method = gateway.method(:create_subscription)
|
|
28
|
+
keyword_names = method.parameters.select { |type, _| %i[key keyreq].include?(type) }.map(&:last)
|
|
29
|
+
expect(keyword_names).to include(:customer_ref, :plan_ref)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe "#cancel_subscription" do
|
|
34
|
+
it "accepts subscription_ref + arbitrary keyword extras" do
|
|
35
|
+
expect(gateway).to respond_to(:cancel_subscription)
|
|
36
|
+
keyword_names = gateway.method(:cancel_subscription).parameters
|
|
37
|
+
.select { |type, _| %i[key keyreq].include?(type) }.map(&:last)
|
|
38
|
+
expect(keyword_names).to include(:subscription_ref)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "#fetch_subscription" do
|
|
43
|
+
it "accepts subscription_ref" do
|
|
44
|
+
expect(gateway).to respond_to(:fetch_subscription)
|
|
45
|
+
keyword_names = gateway.method(:fetch_subscription).parameters
|
|
46
|
+
.select { |type, _| %i[key keyreq].include?(type) }.map(&:last)
|
|
47
|
+
expect(keyword_names).to include(:subscription_ref)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe "#create_checkout_session" do
|
|
52
|
+
it "accepts customer_ref + plan_ref + success_url + cancel_url" do
|
|
53
|
+
expect(gateway).to respond_to(:create_checkout_session)
|
|
54
|
+
keyword_names = gateway.method(:create_checkout_session).parameters
|
|
55
|
+
.select { |type, _| %i[key keyreq].include?(type) }.map(&:last)
|
|
56
|
+
%i[customer_ref plan_ref success_url cancel_url].each do |required|
|
|
57
|
+
expect(keyword_names).to include(required), "missing #{required.inspect}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe "#create_billing_portal_session" do
|
|
63
|
+
it "accepts customer_ref + return_url" do
|
|
64
|
+
expect(gateway).to respond_to(:create_billing_portal_session)
|
|
65
|
+
keyword_names = gateway.method(:create_billing_portal_session).parameters
|
|
66
|
+
.select { |type, _| %i[key keyreq].include?(type) }.map(&:last)
|
|
67
|
+
expect(keyword_names).to include(:customer_ref)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe "#create_lifetime_checkout_session" do
|
|
72
|
+
it "accepts the same shape as #create_checkout_session (mode: payment under the hood)" do
|
|
73
|
+
expect(gateway).to respond_to(:create_lifetime_checkout_session)
|
|
74
|
+
keyword_names = gateway.method(:create_lifetime_checkout_session).parameters
|
|
75
|
+
.select { |type, _| %i[key keyreq].include?(type) }.map(&:last)
|
|
76
|
+
%i[customer_ref plan_ref success_url cancel_url].each do |required|
|
|
77
|
+
expect(keyword_names).to include(required), "missing #{required.inspect}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe "#verify_webhook" do
|
|
83
|
+
it "accepts payload + signature + secret and returns a normalised event hash" do
|
|
84
|
+
expect(gateway).to respond_to(:verify_webhook)
|
|
85
|
+
keyword_names = gateway.method(:verify_webhook).parameters
|
|
86
|
+
.select { |type, _| %i[key keyreq].include?(type) }.map(&:last)
|
|
87
|
+
%i[payload signature secret].each do |required|
|
|
88
|
+
expect(keyword_names).to include(required), "missing #{required.inspect}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "raises Billing::WebhookError on signature mismatch" do
|
|
93
|
+
skip "subclass must seed a verifier that rejects bad signatures" unless gateway.respond_to?(:verify_webhook)
|
|
94
|
+
|
|
95
|
+
expect {
|
|
96
|
+
gateway.verify_webhook(payload: "{}", signature: "bad", secret: "x")
|
|
97
|
+
}.to raise_error(Billing::WebhookError)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Stub helpers for specs that talk to the Stripe REST API via
|
|
4
|
+
# Billing::Stripe::Client (the official `stripe` gem, which uses
|
|
5
|
+
# Net::HTTP under the hood). WebMock intercepts the request before
|
|
6
|
+
# it leaves the process — no live network. The dummy app's
|
|
7
|
+
# rails_helper.rb requires "webmock/rspec" automatically when the
|
|
8
|
+
# webmock gem is bundled, so individual specs only need:
|
|
9
|
+
#
|
|
10
|
+
# require_relative "support/stripe_helpers"
|
|
11
|
+
# RSpec.configure { |c| c.include StripeHelpers }
|
|
12
|
+
#
|
|
13
|
+
# Usage in a spec:
|
|
14
|
+
#
|
|
15
|
+
# stub_stripe(:post, "/v1/checkout/sessions", returns: {
|
|
16
|
+
# id: "cs_test_123", url: "https://stripe.test/cs_test_123"
|
|
17
|
+
# })
|
|
18
|
+
# result = Billing::Checkout::CreateSessionService.call(...)
|
|
19
|
+
# expect(result.value[:id]).to eq("cs_test_123")
|
|
20
|
+
#
|
|
21
|
+
# Or load a saved fixture:
|
|
22
|
+
#
|
|
23
|
+
# stub_stripe_fixture(:post, "/v1/customers", fixture: "customer_created")
|
|
24
|
+
module StripeHelpers
|
|
25
|
+
STRIPE_BASE = "https://api.stripe.com"
|
|
26
|
+
|
|
27
|
+
def stub_stripe(verb, path, returns:, status: 200)
|
|
28
|
+
WebMock.stub_request(verb, "#{STRIPE_BASE}#{path}")
|
|
29
|
+
.to_return(status: status,
|
|
30
|
+
headers: { "Content-Type" => "application/json" },
|
|
31
|
+
body: returns.to_json)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def stub_stripe_fixture(verb, path, fixture:, status: 200)
|
|
35
|
+
body = File.read(stripe_fixture_path(fixture))
|
|
36
|
+
WebMock.stub_request(verb, "#{STRIPE_BASE}#{path}")
|
|
37
|
+
.to_return(status: status,
|
|
38
|
+
headers: { "Content-Type" => "application/json" },
|
|
39
|
+
body: body)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def stub_stripe_failure(verb, path, status:, error_message: "Bad request")
|
|
43
|
+
body = { error: { message: error_message } }.to_json
|
|
44
|
+
WebMock.stub_request(verb, "#{STRIPE_BASE}#{path}")
|
|
45
|
+
.to_return(status: status,
|
|
46
|
+
headers: { "Content-Type" => "application/json" },
|
|
47
|
+
body: body)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def stripe_event_fixture(name)
|
|
51
|
+
JSON.parse(File.read(stripe_fixture_path(name)), symbolize_names: true)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def stripe_fixture_path(name)
|
|
57
|
+
File.expand_path("../fixtures/stripe/#{name}.json", __dir__)
|
|
58
|
+
end
|
|
59
|
+
end
|