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,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Billing
|
|
4
|
+
module Gateways
|
|
5
|
+
# Contract every billing gateway must implement. Subclass this
|
|
6
|
+
# in the host application to wire Paddle, Adyen, Lemon Squeezy,
|
|
7
|
+
# then point Billing.configuration.gateway at the subclass name.
|
|
8
|
+
class Abstract
|
|
9
|
+
# Create or fetch a recurring subscription for the host's
|
|
10
|
+
# customer. `customer_ref` is whatever opaque identifier the
|
|
11
|
+
# host stores on its user (typically a Stripe customer id).
|
|
12
|
+
def create_subscription(customer_ref:, plan_ref:, **)
|
|
13
|
+
raise NotImplementedError, "#{self.class} must implement #create_subscription"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def cancel_subscription(subscription_ref:, **)
|
|
17
|
+
raise NotImplementedError, "#{self.class} must implement #cancel_subscription"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Retrieve a subscription as a normalised hash:
|
|
21
|
+
# { id:, status:, current_period_end:, plan_ref: }
|
|
22
|
+
def fetch_subscription(subscription_ref:)
|
|
23
|
+
raise NotImplementedError, "#{self.class} must implement #fetch_subscription"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Create a hosted checkout session. Returns a normalised hash:
|
|
27
|
+
# { id:, url: }
|
|
28
|
+
# The host redirects the user to `url`, the gateway handles the
|
|
29
|
+
# payment UI, and webhooks tell us the result.
|
|
30
|
+
def create_checkout_session(customer_ref:, plan_ref:, success_url:, cancel_url:, **)
|
|
31
|
+
raise NotImplementedError, "#{self.class} must implement #create_checkout_session"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Create a customer-portal session so the user can self-serve
|
|
35
|
+
# their subscription (cancel, change plan, update payment
|
|
36
|
+
# method). Returns: { id:, url: }.
|
|
37
|
+
def create_billing_portal_session(customer_ref:, return_url:, **)
|
|
38
|
+
raise NotImplementedError, "#{self.class} must implement #create_billing_portal_session"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Create a one-time-payment hosted-checkout session for an LTD
|
|
42
|
+
# plan. Same return shape as #create_checkout_session, but uses
|
|
43
|
+
# the gateway's `mode: payment` flow (Stripe) — no recurring
|
|
44
|
+
# billing, no customer.subscription.* webhooks. Idempotency
|
|
45
|
+
# carries through metadata so the webhook handler can resolve
|
|
46
|
+
# the LTD plan_ref without a separate API call.
|
|
47
|
+
def create_lifetime_checkout_session(customer_ref:, plan_ref:, success_url:, cancel_url:, **)
|
|
48
|
+
raise NotImplementedError, "#{self.class} must implement #create_lifetime_checkout_session"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Verify a webhook payload + signature and return a normalised
|
|
52
|
+
# event hash:
|
|
53
|
+
# { id:, type:, livemode:, object: <provider's raw object>, raw: <payload> }
|
|
54
|
+
#
|
|
55
|
+
# `id:` is the gateway's own event id (e.g. evt_* from Stripe).
|
|
56
|
+
# The Billing webhook controller uses it to dedupe retries via
|
|
57
|
+
# Billing::WebhookEvent.unique(gateway, gateway_event_id).
|
|
58
|
+
# Implementations MUST raise Billing::WebhookError on any
|
|
59
|
+
# signature mismatch.
|
|
60
|
+
def verify_webhook(payload:, signature:, secret:)
|
|
61
|
+
raise NotImplementedError, "#{self.class} must implement #verify_webhook"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Billing
|
|
4
|
+
module Gateways
|
|
5
|
+
# Adyen gateway stub — generated when the host runs
|
|
6
|
+
# `bin/rails generate seams:billing --gateway=adyen`. Each method
|
|
7
|
+
# raises NotImplementedError. Verify against
|
|
8
|
+
# https://docs.adyen.com/api-explorer/ and ship via Faraday
|
|
9
|
+
# (no Net::HTTP — see feedback_external_apis.md).
|
|
10
|
+
class Adyen < Abstract
|
|
11
|
+
# Override every Abstract method here. Adyen uses a recurring
|
|
12
|
+
# payments / shopper reference flow — design your customer_ref
|
|
13
|
+
# mapping accordingly.
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Billing
|
|
4
|
+
module Gateways
|
|
5
|
+
# Paddle gateway stub — generated when the host runs
|
|
6
|
+
# `bin/rails generate seams:billing --gateway=paddle`. Each method
|
|
7
|
+
# raises NotImplementedError so the host knows exactly where to
|
|
8
|
+
# plug in Paddle's REST API. Verify against
|
|
9
|
+
# https://developer.paddle.com/api-reference and ship via Faraday
|
|
10
|
+
# (no Net::HTTP — see feedback_external_apis.md).
|
|
11
|
+
class Paddle < Abstract
|
|
12
|
+
# Override every Abstract method here. As a starting point:
|
|
13
|
+
#
|
|
14
|
+
# def create_checkout_session(customer_ref:, plan_ref:, success_url:, cancel_url:, **)
|
|
15
|
+
# # POST /transactions to https://api.paddle.com via Faraday.
|
|
16
|
+
# # Return: { id:, url: }
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# See lib/billing/stripe/client.rb for the Faraday wrapper pattern.
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "billing/gateways/abstract"
|
|
4
|
+
require "billing/stripe/client"
|
|
5
|
+
require "billing/stripe/webhook_signature"
|
|
6
|
+
|
|
7
|
+
module Billing
|
|
8
|
+
module Gateways
|
|
9
|
+
# Stripe billing gateway. Implements the Billing::Gateways::Abstract
|
|
10
|
+
# contract on top of the official `stripe` Ruby gem
|
|
11
|
+
# (https://github.com/stripe/stripe-ruby), wrapped in a thin
|
|
12
|
+
# facade at Billing::Stripe::Client so the rest of the engine
|
|
13
|
+
# has one place to call. Webhook signature verification uses
|
|
14
|
+
# Stripe::Webhook.construct_event from the same gem rather than
|
|
15
|
+
# a hand-rolled HMAC. Wave 8's earlier decision to roll a Faraday
|
|
16
|
+
# client was reversed in 2026-05 — the gem is actively maintained
|
|
17
|
+
# by Stripe and tracking their API ourselves wasn't worth the
|
|
18
|
+
# centralisation benefit Faraday alone provided.
|
|
19
|
+
#
|
|
20
|
+
# API surface used (verified against current Stripe docs):
|
|
21
|
+
# POST /v1/customers https://docs.stripe.com/api/customers/create
|
|
22
|
+
# GET /v1/customers/search https://docs.stripe.com/api/customers/search
|
|
23
|
+
# POST /v1/subscriptions https://docs.stripe.com/api/subscriptions/create
|
|
24
|
+
# POST /v1/subscriptions/{id} https://docs.stripe.com/api/subscriptions/update
|
|
25
|
+
# DELETE /v1/subscriptions/{id} https://docs.stripe.com/api/subscriptions/cancel
|
|
26
|
+
# GET /v1/subscriptions/{id} https://docs.stripe.com/api/subscriptions/retrieve
|
|
27
|
+
# GET /v1/invoices/{id} https://docs.stripe.com/api/invoices/retrieve
|
|
28
|
+
# POST /v1/checkout/sessions https://docs.stripe.com/api/checkout/sessions/create
|
|
29
|
+
# POST /v1/billing_portal/sessions https://docs.stripe.com/api/customer_portal/sessions/create
|
|
30
|
+
# Webhook signature https://docs.stripe.com/webhooks/signatures
|
|
31
|
+
class Stripe < Abstract
|
|
32
|
+
def initialize(client: nil)
|
|
33
|
+
super()
|
|
34
|
+
@injected_client = client
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Lazy so `Billing::Gateways::Stripe.new` works without an API
|
|
38
|
+
# key configured — important for contract specs that only check
|
|
39
|
+
# the method shape via reflection. Real callers exercise actual
|
|
40
|
+
# endpoints, which forces the client to be built (and the
|
|
41
|
+
# missing-key error to fire) at the call site, not at boot.
|
|
42
|
+
def client
|
|
43
|
+
@injected_client || @client ||= Billing::Stripe::Client.new(api_key: Billing.configuration.api_key)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# POST /v1/subscriptions
|
|
47
|
+
def create_subscription(customer_ref:, plan_ref:, **opts)
|
|
48
|
+
sub = client.create_subscription(
|
|
49
|
+
customer: customer_ref,
|
|
50
|
+
items: [{ price: plan_ref }],
|
|
51
|
+
**opts
|
|
52
|
+
)
|
|
53
|
+
normalise_subscription(sub)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# DELETE /v1/subscriptions/{id}
|
|
57
|
+
def cancel_subscription(subscription_ref:, **opts)
|
|
58
|
+
sub = client.cancel_subscription(subscription_ref, **opts)
|
|
59
|
+
normalise_subscription(sub)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# GET /v1/subscriptions/{id}
|
|
63
|
+
def fetch_subscription(subscription_ref:)
|
|
64
|
+
sub = client.retrieve_subscription(subscription_ref)
|
|
65
|
+
normalise_subscription(sub)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# POST /v1/checkout/sessions (mode: "subscription")
|
|
69
|
+
def create_checkout_session(customer_ref:, plan_ref:, success_url:, cancel_url:, **opts)
|
|
70
|
+
session = client.create_checkout_session({
|
|
71
|
+
mode: "subscription",
|
|
72
|
+
line_items: [{ price: plan_ref, quantity: 1 }],
|
|
73
|
+
customer: customer_ref,
|
|
74
|
+
success_url: success_url,
|
|
75
|
+
cancel_url: cancel_url
|
|
76
|
+
}.merge(opts))
|
|
77
|
+
{ id: session["id"], url: session["url"] }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# POST /v1/billing_portal/sessions
|
|
81
|
+
def create_billing_portal_session(customer_ref:, return_url:, **)
|
|
82
|
+
session = client.create_billing_portal_session(
|
|
83
|
+
customer: customer_ref,
|
|
84
|
+
return_url: return_url
|
|
85
|
+
)
|
|
86
|
+
{ id: session["id"], url: session["url"] }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# POST /v1/checkout/sessions (mode: "payment", LTD path).
|
|
90
|
+
# metadata.access_type=lifetime + metadata.plan_ref=<plan_ref>
|
|
91
|
+
# are duplicated on the session AND payment_intent_data so the
|
|
92
|
+
# webhook handler can read either one. Caller-supplied metadata
|
|
93
|
+
# (typically `account_id` so the webhook handler knows which
|
|
94
|
+
# tenant the resulting LifetimePass belongs to) is merged in;
|
|
95
|
+
# `access_type` and `plan_ref` always win to avoid an LTD
|
|
96
|
+
# session being misclassified.
|
|
97
|
+
def create_lifetime_checkout_session(customer_ref:, plan_ref:, success_url:, cancel_url:, metadata: {}, **opts)
|
|
98
|
+
merged_metadata = metadata.merge(access_type: "lifetime", plan_ref: plan_ref)
|
|
99
|
+
session = client.create_checkout_session({
|
|
100
|
+
mode: "payment",
|
|
101
|
+
line_items: [{ price: plan_ref, quantity: 1 }],
|
|
102
|
+
customer: customer_ref,
|
|
103
|
+
success_url: success_url,
|
|
104
|
+
cancel_url: cancel_url,
|
|
105
|
+
payment_intent_data: { metadata: merged_metadata },
|
|
106
|
+
metadata: merged_metadata
|
|
107
|
+
}.merge(opts))
|
|
108
|
+
{ id: session["id"], url: session["url"] }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# https://docs.stripe.com/webhooks/signatures
|
|
112
|
+
# Verifies the Stripe-Signature header against `secret` via the
|
|
113
|
+
# official Stripe::Webhook helper. Raises Billing::WebhookError
|
|
114
|
+
# on any failure (no header / signature mismatch / timestamp
|
|
115
|
+
# outside the 5-minute tolerance window / non-JSON payload).
|
|
116
|
+
# Returns the normalised event hash on success.
|
|
117
|
+
def verify_webhook(payload:, signature:, secret:)
|
|
118
|
+
event = Billing::Stripe::WebhookSignature.verify!(
|
|
119
|
+
payload: payload,
|
|
120
|
+
signature_header: signature,
|
|
121
|
+
secret: secret
|
|
122
|
+
)
|
|
123
|
+
# event.data["object"] is a Stripe::StripeObject. Convert to
|
|
124
|
+
# a plain Hash so subscribers downstream don't need to know
|
|
125
|
+
# they're dealing with a Stripe SDK type — the engine's
|
|
126
|
+
# contract is "events are hashes".
|
|
127
|
+
object_hash = event.data["object"].respond_to?(:to_hash) ? event.data["object"].to_hash.transform_keys(&:to_s) : event.data["object"]
|
|
128
|
+
{
|
|
129
|
+
id: event["id"], # evt_* — controller dedupes via Billing::WebhookEvent
|
|
130
|
+
type: event["type"],
|
|
131
|
+
livemode: event["livemode"],
|
|
132
|
+
object: object_hash,
|
|
133
|
+
raw: payload
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
# Stripe's response shape changed: current_period_end now lives
|
|
140
|
+
# on items.data[].current_period_end. We try the subscription
|
|
141
|
+
# root first for backwards compatibility, then fall back to the
|
|
142
|
+
# first item.
|
|
143
|
+
def normalise_subscription(sub)
|
|
144
|
+
items_data = sub.dig("items", "data") || []
|
|
145
|
+
first_item = items_data.first || {}
|
|
146
|
+
{
|
|
147
|
+
id: sub["id"],
|
|
148
|
+
status: sub["status"],
|
|
149
|
+
current_period_end: sub["current_period_end"] || first_item["current_period_end"],
|
|
150
|
+
plan_ref: first_item.dig("price", "id")
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stripe"
|
|
4
|
+
|
|
5
|
+
module Billing
|
|
6
|
+
module Stripe
|
|
7
|
+
# Thin facade over the official `stripe` Ruby gem so the engine's
|
|
8
|
+
# service objects + gateway have one shape to call. The previous
|
|
9
|
+
# version of this file was a hand-rolled Faraday REST client we
|
|
10
|
+
# owned; that decision was reversed in 2026-05 (Wave 8 → reversed
|
|
11
|
+
# Wave 6) because Stripe maintains the gem as a first-class
|
|
12
|
+
# deliverable and keeping our own client current with their API
|
|
13
|
+
# was strictly more work than the centralisation benefit Faraday
|
|
14
|
+
# alone gave us.
|
|
15
|
+
#
|
|
16
|
+
# The public method surface is unchanged — every service object
|
|
17
|
+
# and the gateway adapter call this Client without knowing
|
|
18
|
+
# whether the implementation is Faraday or the gem.
|
|
19
|
+
#
|
|
20
|
+
# Stripe gem version pinned to ~> 13 in the host Gemfile by the
|
|
21
|
+
# Billing generator. The StripeClient API
|
|
22
|
+
# (`client.v1.<resource>.<action>`) is what Stripe documents at
|
|
23
|
+
# https://docs.stripe.com/api?lang=ruby for every endpoint we use.
|
|
24
|
+
#
|
|
25
|
+
# Verified against:
|
|
26
|
+
# https://docs.stripe.com/api/customers/create
|
|
27
|
+
# https://docs.stripe.com/api/customers/search
|
|
28
|
+
# https://docs.stripe.com/api/subscriptions/create
|
|
29
|
+
# https://docs.stripe.com/api/subscriptions/update
|
|
30
|
+
# https://docs.stripe.com/api/subscriptions/cancel
|
|
31
|
+
# https://docs.stripe.com/api/subscriptions/retrieve
|
|
32
|
+
# https://docs.stripe.com/api/invoices/retrieve
|
|
33
|
+
# https://docs.stripe.com/api/checkout/sessions/create
|
|
34
|
+
# https://docs.stripe.com/api/customer_portal/sessions/create
|
|
35
|
+
# https://docs.stripe.com/api/idempotent_requests
|
|
36
|
+
class Client
|
|
37
|
+
def initialize(api_key:)
|
|
38
|
+
@sdk = ::Stripe::StripeClient.new(api_key)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# POST /v1/subscriptions
|
|
42
|
+
def create_subscription(customer:, items:, **extra)
|
|
43
|
+
sdk.v1.subscriptions.create({ customer: customer, items: items }.merge(extra))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# DELETE /v1/subscriptions/{id}
|
|
47
|
+
def cancel_subscription(subscription_id, **extra)
|
|
48
|
+
sdk.v1.subscriptions.cancel(subscription_id, extra)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# GET /v1/subscriptions/{id}
|
|
52
|
+
def retrieve_subscription(subscription_id)
|
|
53
|
+
sdk.v1.subscriptions.retrieve(subscription_id)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# POST /v1/subscriptions/{id} — used by ChangePlanService (items[])
|
|
57
|
+
# and ReactivateService (cancel_at_period_end: false).
|
|
58
|
+
def update_subscription(subscription_id, **fields)
|
|
59
|
+
sdk.v1.subscriptions.update(subscription_id, fields)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# POST /v1/checkout/sessions — params is a Hash (subscription
|
|
63
|
+
# mode for recurring; payment mode for LTDs).
|
|
64
|
+
def create_checkout_session(params)
|
|
65
|
+
sdk.v1.checkout.sessions.create(params)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# POST /v1/billing_portal/sessions
|
|
69
|
+
def create_billing_portal_session(customer:, return_url: nil)
|
|
70
|
+
body = { customer: customer }
|
|
71
|
+
body[:return_url] = return_url if return_url
|
|
72
|
+
sdk.v1.billing_portal.sessions.create(body)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# POST /v1/customers. The optional `idempotency_key:` is the gem's
|
|
76
|
+
# standard per-request opts hash (Stripe holds the response for
|
|
77
|
+
# ~24h and replays it for the same key, so concurrent
|
|
78
|
+
# FindOrCreate calls during a signup burst produce one customer
|
|
79
|
+
# instead of two — see https://docs.stripe.com/api/idempotent_requests).
|
|
80
|
+
def create_customer(email:, idempotency_key: nil, **extra)
|
|
81
|
+
body = { email: email }.merge(extra)
|
|
82
|
+
opts = idempotency_key ? { idempotency_key: idempotency_key } : {}
|
|
83
|
+
sdk.v1.customers.create(body, opts)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# GET /v1/customers/search
|
|
87
|
+
def search_customers(query:, limit: 1)
|
|
88
|
+
sdk.v1.customers.search(query: query, limit: limit)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# GET /v1/invoices/{id}
|
|
92
|
+
def retrieve_invoice(invoice_id)
|
|
93
|
+
sdk.v1.invoices.retrieve(invoice_id)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
attr_reader :sdk
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stripe"
|
|
4
|
+
|
|
5
|
+
module Billing
|
|
6
|
+
module Stripe
|
|
7
|
+
# Verifies the Stripe-Signature header on incoming webhooks via
|
|
8
|
+
# the official `stripe` gem's first-party helper.
|
|
9
|
+
#
|
|
10
|
+
# Stripe maintains the verification logic — HMAC-SHA256 over
|
|
11
|
+
# `<timestamp>.<payload>`, 5-minute tolerance window, multiple
|
|
12
|
+
# v1= entries for key rotation, constant-time comparison. We used
|
|
13
|
+
# to reimplement this against https://docs.stripe.com/webhooks/signatures
|
|
14
|
+
# in a hand-rolled HMAC module; that decision was reversed in
|
|
15
|
+
# 2026-05 because Stripe ships breaking changes to the signing
|
|
16
|
+
# scheme over time and the gem tracks them as a first-class
|
|
17
|
+
# commitment.
|
|
18
|
+
#
|
|
19
|
+
# Public surface preserved (verify!) so callers (Billing::Gateways::Stripe)
|
|
20
|
+
# don't need to change. Stripe::SignatureVerificationError is
|
|
21
|
+
# rethrown as Billing::WebhookError to keep the engine's error
|
|
22
|
+
# boundary intact.
|
|
23
|
+
module WebhookSignature
|
|
24
|
+
DEFAULT_TOLERANCE = 300 # seconds — matches Stripe's default
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# Returns the parsed Stripe::Event on success; raises
|
|
29
|
+
# Billing::WebhookError on any failure mode (no header,
|
|
30
|
+
# malformed header, no v1, no matching v1, timestamp outside
|
|
31
|
+
# tolerance, malformed JSON).
|
|
32
|
+
def verify!(payload:, signature_header:, secret:, tolerance: DEFAULT_TOLERANCE)
|
|
33
|
+
raise Billing::WebhookError, "Stripe-Signature header missing" if signature_header.nil? || signature_header.empty?
|
|
34
|
+
|
|
35
|
+
::Stripe::Webhook.construct_event(payload, signature_header, secret, tolerance: tolerance)
|
|
36
|
+
rescue ::Stripe::SignatureVerificationError => e
|
|
37
|
+
raise Billing::WebhookError, "Stripe signature invalid: #{e.message}"
|
|
38
|
+
rescue JSON::ParserError => e
|
|
39
|
+
raise Billing::WebhookError, "Stripe payload not JSON: #{e.message}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Smoke-check Billing configuration without hitting Stripe.
|
|
4
|
+
#
|
|
5
|
+
# Run from a host or CI before promoting a deploy:
|
|
6
|
+
#
|
|
7
|
+
# bin/rails seams:billing:check_config
|
|
8
|
+
#
|
|
9
|
+
# Exit non-zero if anything required is missing — wire it into your
|
|
10
|
+
# pre-deploy gate so a missing STRIPE_SECRET_KEY fails the deploy
|
|
11
|
+
# instead of silently surviving boot and 500ing on the first webhook.
|
|
12
|
+
namespace :seams do
|
|
13
|
+
namespace :billing do
|
|
14
|
+
desc "Verify Billing configuration is complete (api_key + webhook_secret) — exits non-zero if not"
|
|
15
|
+
task check_config: :environment do
|
|
16
|
+
missing = []
|
|
17
|
+
missing << "Billing.configuration.api_key (set STRIPE_SECRET_KEY or assign in initializer)" \
|
|
18
|
+
if Billing.configuration.api_key.to_s.strip.empty?
|
|
19
|
+
missing << "Billing.configuration.webhook_secret (set STRIPE_WEBHOOK_SECRET or assign in initializer)" \
|
|
20
|
+
if Billing.configuration.webhook_secret.to_s.strip.empty?
|
|
21
|
+
|
|
22
|
+
if missing.any?
|
|
23
|
+
warn "Billing config incomplete:"
|
|
24
|
+
missing.each { |m| warn " - #{m}" }
|
|
25
|
+
warn ""
|
|
26
|
+
warn "Without these, Billing::Gateways::Stripe will raise at the first " \
|
|
27
|
+
"outbound API call or webhook, not at boot. Set them before deploying."
|
|
28
|
+
abort("Billing config check failed.")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
puts "Billing config OK: api_key + webhook_secret are present."
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Factories for Billing engine specs. Sequences keep ref columns
|
|
4
|
+
# (gateway_ref, customer_ref, plan_ref) unique across the spec run
|
|
5
|
+
# so uniqueness validations don't trip.
|
|
6
|
+
#
|
|
7
|
+
# Post-Wave-9: every billing-row factory carries an `account_id`
|
|
8
|
+
# (UUID) — the Accounts::Account the row belongs to. We don't
|
|
9
|
+
# `association :account` because cross-engine model access is
|
|
10
|
+
# forbidden by the Seams cops; the spec generates a fresh
|
|
11
|
+
# SecureRandom UUID per row, which is safe in a test DB without a
|
|
12
|
+
# DB-level FK to accounts. Hosts that have an Accounts::Account
|
|
13
|
+
# factory available can override the `account_id` value with one
|
|
14
|
+
# from a real `create(:account)`.
|
|
15
|
+
FactoryBot.define do
|
|
16
|
+
factory :billing_plan, class: "Billing::Plan" do
|
|
17
|
+
sequence(:gateway_ref) { |n| "price_test_#{n}" }
|
|
18
|
+
sequence(:name) { |n| "Plan #{n}" }
|
|
19
|
+
amount_cents { 1299 }
|
|
20
|
+
currency { "GBP" }
|
|
21
|
+
interval { "month" }
|
|
22
|
+
trial_period_days { 0 }
|
|
23
|
+
|
|
24
|
+
factory :billing_lifetime_plan do
|
|
25
|
+
interval { "lifetime" }
|
|
26
|
+
max_lifetime_units { nil }
|
|
27
|
+
sequence(:gateway_ref) { |n| "price_test_lifetime_#{n}" }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
factory :billing_subscription, class: "Billing::Subscription" do
|
|
32
|
+
account_id { SecureRandom.uuid }
|
|
33
|
+
sequence(:gateway_ref) { |n| "sub_test_#{n}" }
|
|
34
|
+
sequence(:customer_ref) { |n| "cus_test_#{n}" }
|
|
35
|
+
sequence(:plan_ref) { |n| "price_test_#{n}" }
|
|
36
|
+
status { "active" }
|
|
37
|
+
current_period_end { 30.days.from_now }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
factory :billing_invoice, class: "Billing::Invoice" do
|
|
41
|
+
account_id { SecureRandom.uuid }
|
|
42
|
+
sequence(:gateway_ref) { |n| "in_test_#{n}" }
|
|
43
|
+
sequence(:customer_ref) { |n| "cus_test_#{n}" }
|
|
44
|
+
sequence(:subscription_ref) { |n| "sub_test_#{n}" }
|
|
45
|
+
status { "paid" }
|
|
46
|
+
amount_cents { 1299 }
|
|
47
|
+
currency { "GBP" }
|
|
48
|
+
paid_at { Time.current }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
factory :billing_lifetime_pass, class: "Billing::LifetimePass" do
|
|
52
|
+
account_id { SecureRandom.uuid }
|
|
53
|
+
sequence(:customer_ref) { |n| "cus_test_lifetime_#{n}" }
|
|
54
|
+
sequence(:plan_ref) { |n| "price_test_lifetime_#{n}" }
|
|
55
|
+
sequence(:gateway_ref) { |n| "cs_test_#{n}" }
|
|
56
|
+
granted_at { Time.current }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
factory :billing_webhook_event, class: "Billing::WebhookEvent" do
|
|
60
|
+
gateway { "stripe" }
|
|
61
|
+
sequence(:gateway_event_id) { |n| "evt_test_#{n}" }
|
|
62
|
+
event_type { "customer.subscription.created" }
|
|
63
|
+
livemode { false }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "evt_test_charge_refunded",
|
|
3
|
+
"object": "event",
|
|
4
|
+
"type": "charge.refunded",
|
|
5
|
+
"livemode": false,
|
|
6
|
+
"created": 1731100000,
|
|
7
|
+
"data": {
|
|
8
|
+
"object": {
|
|
9
|
+
"id": "ch_test_123",
|
|
10
|
+
"object": "charge",
|
|
11
|
+
"customer": "cus_test_123",
|
|
12
|
+
"payment_intent": "pi_test_123",
|
|
13
|
+
"amount": 1299,
|
|
14
|
+
"amount_refunded": 1299,
|
|
15
|
+
"currency": "gbp",
|
|
16
|
+
"refunded": true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "evt_test_checkout_session_completed",
|
|
3
|
+
"object": "event",
|
|
4
|
+
"type": "checkout.session.completed",
|
|
5
|
+
"livemode": false,
|
|
6
|
+
"created": 1731200000,
|
|
7
|
+
"data": {
|
|
8
|
+
"object": {
|
|
9
|
+
"id": "cs_test_123",
|
|
10
|
+
"object": "checkout.session",
|
|
11
|
+
"mode": "subscription",
|
|
12
|
+
"customer": "cus_test_123",
|
|
13
|
+
"subscription": "sub_test_123",
|
|
14
|
+
"payment_status": "paid"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "evt_test_subscription_created",
|
|
3
|
+
"object": "event",
|
|
4
|
+
"type": "customer.subscription.created",
|
|
5
|
+
"livemode": false,
|
|
6
|
+
"created": 1730000000,
|
|
7
|
+
"data": {
|
|
8
|
+
"object": {
|
|
9
|
+
"id": "sub_test_123",
|
|
10
|
+
"object": "subscription",
|
|
11
|
+
"customer": "cus_test_123",
|
|
12
|
+
"status": "active",
|
|
13
|
+
"current_period_end": 1732678400,
|
|
14
|
+
"items": {
|
|
15
|
+
"data": [
|
|
16
|
+
{
|
|
17
|
+
"id": "si_test_123",
|
|
18
|
+
"price": { "id": "price_test_pro", "lookup_key": "pro" },
|
|
19
|
+
"current_period_end": 1732678400
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "evt_test_subscription_deleted",
|
|
3
|
+
"object": "event",
|
|
4
|
+
"type": "customer.subscription.deleted",
|
|
5
|
+
"livemode": false,
|
|
6
|
+
"created": 1730200000,
|
|
7
|
+
"data": {
|
|
8
|
+
"object": {
|
|
9
|
+
"id": "sub_test_123",
|
|
10
|
+
"object": "subscription",
|
|
11
|
+
"customer": "cus_test_123",
|
|
12
|
+
"status": "canceled",
|
|
13
|
+
"canceled_at": 1730200000,
|
|
14
|
+
"items": { "data": [] }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "evt_test_trial_will_end",
|
|
3
|
+
"object": "event",
|
|
4
|
+
"type": "customer.subscription.trial_will_end",
|
|
5
|
+
"livemode": false,
|
|
6
|
+
"created": 1730300000,
|
|
7
|
+
"data": {
|
|
8
|
+
"object": {
|
|
9
|
+
"id": "sub_test_123",
|
|
10
|
+
"object": "subscription",
|
|
11
|
+
"customer": "cus_test_123",
|
|
12
|
+
"status": "trialing",
|
|
13
|
+
"trial_end": 1730560000,
|
|
14
|
+
"items": { "data": [{ "price": { "lookup_key": "pro" } }] }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "evt_test_subscription_updated",
|
|
3
|
+
"object": "event",
|
|
4
|
+
"type": "customer.subscription.updated",
|
|
5
|
+
"livemode": false,
|
|
6
|
+
"created": 1730100000,
|
|
7
|
+
"data": {
|
|
8
|
+
"object": {
|
|
9
|
+
"id": "sub_test_123",
|
|
10
|
+
"object": "subscription",
|
|
11
|
+
"customer": "cus_test_123",
|
|
12
|
+
"status": "active",
|
|
13
|
+
"current_period_end": 1735270400,
|
|
14
|
+
"items": {
|
|
15
|
+
"data": [
|
|
16
|
+
{
|
|
17
|
+
"id": "si_test_123",
|
|
18
|
+
"price": { "id": "price_test_pro_annual", "lookup_key": "pro_annual" },
|
|
19
|
+
"current_period_end": 1735270400
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"previous_attributes": {
|
|
25
|
+
"items": { "data": [{ "price": { "lookup_key": "pro" } }] }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "evt_test_invoice_created",
|
|
3
|
+
"object": "event",
|
|
4
|
+
"type": "invoice.created",
|
|
5
|
+
"livemode": false,
|
|
6
|
+
"created": 1730400000,
|
|
7
|
+
"data": {
|
|
8
|
+
"object": {
|
|
9
|
+
"id": "in_test_123",
|
|
10
|
+
"object": "invoice",
|
|
11
|
+
"customer": "cus_test_123",
|
|
12
|
+
"subscription": "sub_test_123",
|
|
13
|
+
"status": "draft",
|
|
14
|
+
"amount_due": 1299,
|
|
15
|
+
"currency": "gbp"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|