seams 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +212 -12
- data/README.md +235 -82
- data/lib/generators/seams/accounts/accounts_generator.rb +31 -3
- data/lib/generators/seams/accounts/templates/app/controllers/memberships_controller.rb.tt +100 -0
- data/lib/generators/seams/accounts/templates/app/views/memberships/index.html.erb.tt +52 -0
- data/lib/generators/seams/accounts/templates/config/routes.rb.tt +10 -4
- data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +35 -2
- data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +13 -0
- data/lib/generators/seams/accounts/templates/spec/runtime/authorization_spec.rb.tt +102 -0
- data/lib/generators/seams/accounts/templates/spec/runtime/memberships_flow_spec.rb.tt +145 -0
- data/lib/generators/seams/admin/admin_generator.rb +51 -4
- data/lib/generators/seams/admin/templates/README.md.tt +33 -3
- data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +156 -8
- data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +9 -1
- data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +9 -1
- data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +7 -1
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +12 -2
- data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +7 -1
- data/lib/generators/seams/admin/templates/app/fields/admin/fields/belongs_to.rb.tt +12 -0
- data/lib/generators/seams/admin/templates/app/fields/admin/fields/dashboard_option.rb.tt +22 -0
- data/lib/generators/seams/admin/templates/app/fields/admin/fields/has_many.rb.tt +12 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +4 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +51 -10
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +4 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +7 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +6 -0
- data/lib/generators/seams/admin/templates/app/views/seams/admin/application/_index_header.html.erb.tt +57 -0
- data/lib/generators/seams/admin/templates/app/views/seams/admin/application/_navigation.html.erb.tt +14 -0
- data/lib/generators/seams/admin/templates/config/routes.rb.tt +24 -14
- data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +3 -0
- data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +3 -1
- data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +4 -2
- data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +1 -2
- data/lib/generators/seams/auth/auth_generator.rb +1 -2
- data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +3 -0
- data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +4 -0
- data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +5 -0
- data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +5 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +5 -1
- data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +12 -1
- data/lib/generators/seams/auth/templates/lib/engine.rb.tt +27 -0
- data/lib/generators/seams/billing/billing_generator.rb +4 -3
- data/lib/generators/seams/billing/templates/README.md.tt +26 -0
- data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +2 -0
- data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +2 -0
- data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +8 -7
- data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +9 -7
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +18 -3
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +7 -21
- data/lib/generators/seams/billing/templates/lib/engine.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +10 -10
- data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +40 -14
- data/lib/generators/seams/billing/templates/lib/stripe/payload.rb.tt +90 -0
- data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +11 -1
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +15 -4
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +7 -1
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +18 -1
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +26 -5
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +17 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +16 -2
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +1 -0
- data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +47 -1
- data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +39 -1
- data/lib/generators/seams/design/design_generator.rb +647 -0
- data/lib/generators/seams/design/templates/README.md.tt +129 -0
- data/lib/generators/seams/design/templates/app/assets/tailwind/_tokens.css +322 -0
- data/lib/generators/seams/design/templates/app/assets/tailwind/themes/_quire.css +42 -0
- data/lib/generators/seams/design/templates/app/controllers/design/dashboard_controller.rb.tt +30 -0
- data/lib/generators/seams/design/templates/app/controllers/design/guide_controller.rb.tt +28 -0
- data/lib/generators/seams/design/templates/app/form_builders/design/form_builder.rb.tt +90 -0
- data/lib/generators/seams/design/templates/app/helpers/design/ui_helper.rb.tt +37 -0
- data/lib/generators/seams/design/templates/app/views/design/dashboard/index.html.erb.tt +38 -0
- data/lib/generators/seams/design/templates/app/views/design/guide/index.html.erb.tt +19 -0
- data/lib/generators/seams/design/templates/app/views/layouts/application.html.erb.tt +61 -0
- data/lib/generators/seams/design/templates/app/views/layouts/design/guide.html.erb.tt +21 -0
- data/lib/generators/seams/design/templates/app/views/ui/_banner.html.erb.tt +10 -0
- data/lib/generators/seams/design/templates/app/views/ui/_breadcrumb.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/_build_row.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/_button.html.erb.tt +4 -0
- data/lib/generators/seams/design/templates/app/views/ui/_card.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_chapter_row.html.erb.tt +11 -0
- data/lib/generators/seams/design/templates/app/views/ui/_checkbox.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_counter.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_data_table.html.erb.tt +25 -0
- data/lib/generators/seams/design/templates/app/views/ui/_dialog.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/_diff.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_drawer.html.erb.tt +7 -0
- data/lib/generators/seams/design/templates/app/views/ui/_empty.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/_field.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/_icon.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_icon_sprite.html.erb.tt +22 -0
- data/lib/generators/seams/design/templates/app/views/ui/_input_group.html.erb.tt +11 -0
- data/lib/generators/seams/design/templates/app/views/ui/_kbd.html.erb.tt +4 -0
- data/lib/generators/seams/design/templates/app/views/ui/_menu.html.erb.tt +18 -0
- data/lib/generators/seams/design/templates/app/views/ui/_meter.html.erb.tt +15 -0
- data/lib/generators/seams/design/templates/app/views/ui/_note.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_outline.html.erb.tt +9 -0
- data/lib/generators/seams/design/templates/app/views/ui/_pagination.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_panel.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_popover.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_radio.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/_savestate.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/_segmented.html.erb.tt +7 -0
- data/lib/generators/seams/design/templates/app/views/ui/_stepper.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_switch.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/_tag.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/_toast.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/_toolbar.html.erb.tt +15 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_banner.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_breadcrumb.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_build_row.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_button.html.erb.tt +9 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_card.html.erb.tt +12 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_chapter_row.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_checkbox.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_counter.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_data_table.html.erb.tt +14 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_dialog.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_diff.html.erb.tt +10 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_drawer.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_empty.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_field.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_input_group.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_kbd.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_menu.html.erb.tt +9 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_meter.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_note.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_outline.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_pagination.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_panel.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_popover.html.erb.tt +2 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_radio.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_savestate.html.erb.tt +4 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_segmented.html.erb.tt +15 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_stepper.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_switch.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_tag.html.erb.tt +8 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_toast.html.erb.tt +6 -0
- data/lib/generators/seams/design/templates/app/views/ui/previews/_toolbar.html.erb.tt +11 -0
- data/lib/generators/seams/design/templates/lib/design/components.rb.tt +22 -0
- data/lib/generators/seams/design/templates/lib/design.rb.tt +18 -0
- data/lib/generators/seams/design/templates/lib/engine.rb.tt +38 -0
- data/lib/generators/seams/design/templates/lib/generators/design/component/component_generator.rb.tt +52 -0
- data/lib/generators/seams/design/templates/lib/generators/design/component/templates/component.html.erb.tt +5 -0
- data/lib/generators/seams/design/templates/lib/generators/design/component/templates/preview.html.erb.tt +3 -0
- data/lib/generators/seams/design/templates/spec/runtime/design_boot_spec.rb.tt +79 -0
- data/lib/generators/seams/design/templates/spec/runtime/form_builder_spec.rb.tt +97 -0
- data/lib/generators/seams/design/templates/spec/runtime/guide_spec.rb.tt +40 -0
- data/lib/generators/seams/design/templates/spec/runtime/ui_components_spec.rb.tt +70 -0
- data/lib/generators/seams/engine/engine_generator.rb +6 -0
- data/lib/generators/seams/engine/templates/Gemfile.tt +1 -1
- data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
- data/lib/generators/seams/engine/templates/rubocop.yml.tt +9 -0
- data/lib/generators/seams/install/install_generator.rb +72 -6
- data/lib/generators/seams/install/templates/Dockerfile.tt +2 -2
- data/lib/generators/seams/install/templates/bin_seams.tt +7 -4
- data/lib/generators/seams/install/templates/ci.yml.tt +18 -8
- data/lib/generators/seams/install/templates/deploy.yml.tt +4 -2
- data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +1 -1
- data/lib/generators/seams/install/templates/docker-entrypoint.tt +4 -2
- data/lib/generators/seams/install/templates/herb.yml.tt +10 -0
- data/lib/generators/seams/install/templates/lefthook.yml.tt +25 -0
- data/lib/generators/seams/install/templates/ruby-version.tt +1 -1
- data/lib/generators/seams/install/templates/seams.rake.tt +13 -1
- data/lib/generators/seams/install/templates/strong_migrations.rb.tt +26 -0
- data/lib/generators/seams/notifications/notifications_generator.rb +3 -2
- data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +5 -13
- data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +12 -0
- data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +1 -0
- data/lib/generators/seams/notifications/templates/lib/preferences.rb.tt +36 -0
- data/lib/generators/seams/permissions/permissions_generator.rb +97 -0
- data/lib/generators/seams/permissions/templates/config/initializers/seams_permissions.rb.tt +35 -0
- data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +27 -11
- data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +5 -13
- data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +2 -4
- data/lib/generators/seams/teams/templates/lib/engine.rb.tt +13 -0
- data/lib/seams/cli/list.rb +15 -0
- data/lib/seams/cli/quality.rb +46 -5
- data/lib/seams/cli/resolve.rb +9 -5
- data/lib/seams/cli/test_changed.rb +5 -1
- data/lib/seams/cli.rb +20 -0
- data/lib/seams/configuration.rb +24 -1
- data/lib/seams/cops/no_cross_engine_dependency.rb +39 -7
- data/lib/seams/cops/no_cross_engine_model_access.rb +79 -1
- data/lib/seams/event_registry.rb +19 -0
- data/lib/seams/events/adapter.rb +9 -0
- data/lib/seams/events/adapters/active_support.rb +4 -0
- data/lib/seams/events/publisher.rb +29 -0
- data/lib/seams/events.rb +1 -0
- data/lib/seams/generators/dummy_app_writer.rb +21 -0
- data/lib/seams/generators/follow_up_generator.rb +1 -2
- data/lib/seams/generators/host_injector.rb +9 -2
- data/lib/seams/generators/splicer.rb +2 -2
- data/lib/seams/observability/adapter.rb +22 -0
- data/lib/seams/observability.rb +6 -0
- data/lib/seams/permission_registry.rb +66 -0
- data/lib/seams/permissions.rb +120 -0
- data/lib/seams/version.rb +1 -1
- data/lib/seams.rb +7 -0
- metadata +107 -3
|
@@ -60,32 +60,18 @@ module Billing
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def plan_ref_from_object_hash
|
|
63
|
-
|
|
64
|
-
return nil unless first
|
|
65
|
-
|
|
66
|
-
price = first[:price] || first["price"]
|
|
67
|
-
price.is_a?(Hash) ? (price[:id] || price["id"]) : nil
|
|
63
|
+
Billing::Stripe::Payload.subscription_price_ref(object_hash)
|
|
68
64
|
end
|
|
69
65
|
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
66
|
+
# Since API 2025-03-31.basil the billing period lives on each
|
|
67
|
+
# subscription item (items.data[].current_period_end); the
|
|
68
|
+
# subscription-level field was removed. Read the first item,
|
|
69
|
+
# fall back to the root for payloads from older API versions.
|
|
70
|
+
# https://docs.stripe.com/changelog/basil/2025-03-31/deprecate-subscription-current-period-start-and-end
|
|
74
71
|
def current_period_end_at
|
|
75
|
-
unix = object_hash
|
|
76
|
-
object_hash["current_period_end"]
|
|
77
|
-
unix ||= (item = first_subscription_item) &&
|
|
78
|
-
(item[:current_period_end] || item["current_period_end"])
|
|
72
|
+
unix = Billing::Stripe::Payload.subscription_current_period_end(object_hash)
|
|
79
73
|
unix && Time.at(unix)
|
|
80
74
|
end
|
|
81
|
-
|
|
82
|
-
def first_subscription_item
|
|
83
|
-
items = object_hash[:items] || object_hash["items"]
|
|
84
|
-
return nil unless items.is_a?(Hash)
|
|
85
|
-
|
|
86
|
-
first = (items[:data] || items["data"] || []).first
|
|
87
|
-
first if first.is_a?(Hash)
|
|
88
|
-
end
|
|
89
75
|
end
|
|
90
76
|
end
|
|
91
77
|
end
|
|
@@ -33,6 +33,21 @@ module Billing
|
|
|
33
33
|
# seams:insertion-point billing.engine.events
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
initializer "billing.register_abilities" do
|
|
37
|
+
# Ability codes this engine owns, registered exactly like events
|
|
38
|
+
# (resource.action.engine). `read` is the member-level view;
|
|
39
|
+
# `manage` is the admin-level mutate. Host grant maps reference
|
|
40
|
+
# these codes; see Seams::Permissions::DEFAULT_GRANTS.
|
|
41
|
+
Seams::PermissionRegistry.register("invoice.read.billing", owned_by: "Billing")
|
|
42
|
+
Seams::PermissionRegistry.register("invoice.manage.billing", owned_by: "Billing")
|
|
43
|
+
Seams::PermissionRegistry.register("subscription.read.billing", owned_by: "Billing")
|
|
44
|
+
Seams::PermissionRegistry.register("subscription.manage.billing", owned_by: "Billing")
|
|
45
|
+
Seams::PermissionRegistry.register("plan.manage.billing", owned_by: "Billing")
|
|
46
|
+
Seams::PermissionRegistry.register("lifetime.manage.billing", owned_by: "Billing")
|
|
47
|
+
# Follow-up generators that ship new billing abilities register them here.
|
|
48
|
+
# seams:insertion-point billing.engine.abilities
|
|
49
|
+
end
|
|
50
|
+
|
|
36
51
|
initializer "billing.append_migrations" do |app|
|
|
37
52
|
unless app.root == root
|
|
38
53
|
config.paths["db/migrate"].expanded.each do |expanded_path|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "billing/gateways/abstract"
|
|
4
4
|
require "billing/stripe/client"
|
|
5
5
|
require "billing/stripe/webhook_signature"
|
|
6
|
+
require "billing/stripe/payload"
|
|
6
7
|
|
|
7
8
|
module Billing
|
|
8
9
|
module Gateways
|
|
@@ -136,18 +137,17 @@ module Billing
|
|
|
136
137
|
|
|
137
138
|
private
|
|
138
139
|
|
|
139
|
-
# Stripe
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
140
|
+
# `sub` is a ::Stripe::Subscription from the gem (no #dig) or a
|
|
141
|
+
# Hash in specs; Billing::Stripe::Payload reads both.
|
|
142
|
+
# Since API 2025-03-31.basil current_period_end lives on
|
|
143
|
+
# items.data[] only; the subscription root is the fallback for
|
|
144
|
+
# older payloads.
|
|
143
145
|
def normalise_subscription(sub)
|
|
144
|
-
items_data = sub.dig("items", "data") || []
|
|
145
|
-
first_item = items_data.first || {}
|
|
146
146
|
{
|
|
147
|
-
id:
|
|
148
|
-
status:
|
|
149
|
-
current_period_end:
|
|
150
|
-
plan_ref:
|
|
147
|
+
id: Billing::Stripe::Payload.dig(sub, :id),
|
|
148
|
+
status: Billing::Stripe::Payload.dig(sub, :status),
|
|
149
|
+
current_period_end: Billing::Stripe::Payload.subscription_current_period_end(sub),
|
|
150
|
+
plan_ref: Billing::Stripe::Payload.subscription_price_ref(sub)
|
|
151
151
|
}
|
|
152
152
|
end
|
|
153
153
|
end
|
|
@@ -17,10 +17,23 @@ module Billing
|
|
|
17
17
|
# and the gateway adapter call this Client without knowing
|
|
18
18
|
# whether the implementation is Faraday or the gem.
|
|
19
19
|
#
|
|
20
|
-
# Stripe gem version pinned to ~>
|
|
21
|
-
# Billing generator.
|
|
22
|
-
#
|
|
23
|
-
#
|
|
20
|
+
# Stripe gem version pinned to ~> 19 in the host Gemfile by the
|
|
21
|
+
# Billing generator. stripe-ruby 19 pins API version
|
|
22
|
+
# 2026-08-26.dahlia (see ::Stripe.api_version). The StripeClient
|
|
23
|
+
# API (`client.v1.<resource>.<action>`) is what Stripe documents
|
|
24
|
+
# at https://docs.stripe.com/api?lang=ruby for every endpoint we
|
|
25
|
+
# use. Responses are ::Stripe::StripeObject instances: read them
|
|
26
|
+
# with `obj[:key]` or Billing::Stripe::Payload.dig — they have no
|
|
27
|
+
# Hash#dig.
|
|
28
|
+
#
|
|
29
|
+
# Errors: every ::Stripe::StripeError raised by the gem is
|
|
30
|
+
# re-raised as Billing::GatewayError (original kept as #cause),
|
|
31
|
+
# because every caller in the engine rescues Billing::GatewayError.
|
|
32
|
+
# The message prefix is what Billing::StripeService#classify_gateway_error
|
|
33
|
+
# matches on:
|
|
34
|
+
# ::Stripe::APIConnectionError -> "Stripe connection error: ..."
|
|
35
|
+
# ::Stripe::AuthenticationError/Permission -> "Stripe authentication error: ..."
|
|
36
|
+
# any other ::Stripe::StripeError -> "Stripe <ErrorClass>: ..."
|
|
24
37
|
#
|
|
25
38
|
# Verified against:
|
|
26
39
|
# https://docs.stripe.com/api/customers/create
|
|
@@ -34,42 +47,45 @@ module Billing
|
|
|
34
47
|
# https://docs.stripe.com/api/customer_portal/sessions/create
|
|
35
48
|
# https://docs.stripe.com/api/idempotent_requests
|
|
36
49
|
class Client
|
|
50
|
+
# StripeClient.new raises ::Stripe::AuthenticationError on a nil
|
|
51
|
+
# key; translated like any other call so callers see
|
|
52
|
+
# Billing::GatewayError (code :gateway_auth).
|
|
37
53
|
def initialize(api_key:)
|
|
38
|
-
@sdk = ::Stripe::StripeClient.new(api_key)
|
|
54
|
+
@sdk = translate_errors { ::Stripe::StripeClient.new(api_key) }
|
|
39
55
|
end
|
|
40
56
|
|
|
41
57
|
# POST /v1/subscriptions
|
|
42
58
|
def create_subscription(customer:, items:, **extra)
|
|
43
|
-
sdk.v1.subscriptions.create({ customer: customer, items: items }.merge(extra))
|
|
59
|
+
translate_errors { sdk.v1.subscriptions.create({ customer: customer, items: items }.merge(extra)) }
|
|
44
60
|
end
|
|
45
61
|
|
|
46
62
|
# DELETE /v1/subscriptions/{id}
|
|
47
63
|
def cancel_subscription(subscription_id, **extra)
|
|
48
|
-
sdk.v1.subscriptions.cancel(subscription_id, extra)
|
|
64
|
+
translate_errors { sdk.v1.subscriptions.cancel(subscription_id, extra) }
|
|
49
65
|
end
|
|
50
66
|
|
|
51
67
|
# GET /v1/subscriptions/{id}
|
|
52
68
|
def retrieve_subscription(subscription_id)
|
|
53
|
-
sdk.v1.subscriptions.retrieve(subscription_id)
|
|
69
|
+
translate_errors { sdk.v1.subscriptions.retrieve(subscription_id) }
|
|
54
70
|
end
|
|
55
71
|
|
|
56
72
|
# POST /v1/subscriptions/{id} — used by ChangePlanService (items[])
|
|
57
73
|
# and ReactivateService (cancel_at_period_end: false).
|
|
58
74
|
def update_subscription(subscription_id, **fields)
|
|
59
|
-
sdk.v1.subscriptions.update(subscription_id, fields)
|
|
75
|
+
translate_errors { sdk.v1.subscriptions.update(subscription_id, fields) }
|
|
60
76
|
end
|
|
61
77
|
|
|
62
78
|
# POST /v1/checkout/sessions — params is a Hash (subscription
|
|
63
79
|
# mode for recurring; payment mode for LTDs).
|
|
64
80
|
def create_checkout_session(params)
|
|
65
|
-
sdk.v1.checkout.sessions.create(params)
|
|
81
|
+
translate_errors { sdk.v1.checkout.sessions.create(params) }
|
|
66
82
|
end
|
|
67
83
|
|
|
68
84
|
# POST /v1/billing_portal/sessions
|
|
69
85
|
def create_billing_portal_session(customer:, return_url: nil)
|
|
70
86
|
body = { customer: customer }
|
|
71
87
|
body[:return_url] = return_url if return_url
|
|
72
|
-
sdk.v1.billing_portal.sessions.create(body)
|
|
88
|
+
translate_errors { sdk.v1.billing_portal.sessions.create(body) }
|
|
73
89
|
end
|
|
74
90
|
|
|
75
91
|
# POST /v1/customers. The optional `idempotency_key:` is the gem's
|
|
@@ -80,22 +96,32 @@ module Billing
|
|
|
80
96
|
def create_customer(email:, idempotency_key: nil, **extra)
|
|
81
97
|
body = { email: email }.merge(extra)
|
|
82
98
|
opts = idempotency_key ? { idempotency_key: idempotency_key } : {}
|
|
83
|
-
sdk.v1.customers.create(body, opts)
|
|
99
|
+
translate_errors { sdk.v1.customers.create(body, opts) }
|
|
84
100
|
end
|
|
85
101
|
|
|
86
102
|
# GET /v1/customers/search
|
|
87
103
|
def search_customers(query:, limit: 1)
|
|
88
|
-
sdk.v1.customers.search(query: query, limit: limit)
|
|
104
|
+
translate_errors { sdk.v1.customers.search({ query: query, limit: limit }) }
|
|
89
105
|
end
|
|
90
106
|
|
|
91
107
|
# GET /v1/invoices/{id}
|
|
92
108
|
def retrieve_invoice(invoice_id)
|
|
93
|
-
sdk.v1.invoices.retrieve(invoice_id)
|
|
109
|
+
translate_errors { sdk.v1.invoices.retrieve(invoice_id) }
|
|
94
110
|
end
|
|
95
111
|
|
|
96
112
|
private
|
|
97
113
|
|
|
98
114
|
attr_reader :sdk
|
|
115
|
+
|
|
116
|
+
def translate_errors
|
|
117
|
+
yield
|
|
118
|
+
rescue ::Stripe::APIConnectionError => e
|
|
119
|
+
raise Billing::GatewayError, "Stripe connection error: #{e.message}"
|
|
120
|
+
rescue ::Stripe::AuthenticationError, ::Stripe::PermissionError => e
|
|
121
|
+
raise Billing::GatewayError, "Stripe authentication error: #{e.message}"
|
|
122
|
+
rescue ::Stripe::StripeError => e
|
|
123
|
+
raise Billing::GatewayError, "Stripe #{e.class.name.split('::').last}: #{e.message}"
|
|
124
|
+
end
|
|
99
125
|
end
|
|
100
126
|
end
|
|
101
127
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Billing
|
|
4
|
+
module Stripe
|
|
5
|
+
# Reads Stripe objects without caring where they came from.
|
|
6
|
+
#
|
|
7
|
+
# The engine sees the same Stripe resource in three shapes:
|
|
8
|
+
#
|
|
9
|
+
# 1. a `::Stripe::StripeObject` returned by the `stripe` gem
|
|
10
|
+
# (Billing::Stripe::Client). It supports `obj[:key]` but has
|
|
11
|
+
# NO `#dig` — calling `dig` on it raises NoMethodError.
|
|
12
|
+
# 2. the Hash built by Billing::Gateways::Stripe#verify_webhook
|
|
13
|
+
# (string keys at the top level, symbol keys below it).
|
|
14
|
+
# 3. a JSON-parsed Hash (string keys, or symbol keys in specs).
|
|
15
|
+
#
|
|
16
|
+
# `dig` walks any mix of those. The field readers below also hide
|
|
17
|
+
# the Stripe API-version differences the engine cares about, so
|
|
18
|
+
# the rest of the engine reads one field instead of two.
|
|
19
|
+
#
|
|
20
|
+
# API-version notes (stripe-ruby 19 pins 2026-08-26.dahlia; the
|
|
21
|
+
# shape changes below arrived in 2025-03-31.basil):
|
|
22
|
+
# https://docs.stripe.com/changelog/basil/2025-03-31/adds-new-parent-field-to-invoicing-objects
|
|
23
|
+
# https://docs.stripe.com/changelog/basil/2025-03-31/deprecate-subscription-current-period-start-and-end
|
|
24
|
+
# https://docs.stripe.com/changelog/basil/2025-03-31/add-support-for-multiple-partial-payments-on-invoices
|
|
25
|
+
module Payload
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# Key-tolerant dig. Integer segments index into Arrays.
|
|
29
|
+
#
|
|
30
|
+
# Payload.dig(invoice, :parent, :subscription_details, :subscription)
|
|
31
|
+
# Payload.dig(subscription, :items, :data, 0, :current_period_end)
|
|
32
|
+
def dig(object, *path)
|
|
33
|
+
path.reduce(object) do |node, key|
|
|
34
|
+
return nil if node.nil?
|
|
35
|
+
|
|
36
|
+
fetch(node, key)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def fetch(node, key)
|
|
41
|
+
if key.is_a?(Integer)
|
|
42
|
+
node.is_a?(Array) ? node[key] : nil
|
|
43
|
+
elsif node.is_a?(Hash)
|
|
44
|
+
node.key?(key.to_s) ? node[key.to_s] : node[key.to_sym]
|
|
45
|
+
elsif node.respond_to?(:keys) && node.respond_to?(:[])
|
|
46
|
+
node[key.to_sym] # ::Stripe::StripeObject
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Stripe id for a field that is either an id String or an
|
|
51
|
+
# expanded object.
|
|
52
|
+
def ref(value)
|
|
53
|
+
value.is_a?(String) ? value : dig(value, :id)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The subscription an invoice belongs to.
|
|
57
|
+
# Current API: invoice.parent.subscription_details.subscription.
|
|
58
|
+
# Before 2025-03-31.basil: invoice.subscription.
|
|
59
|
+
def invoice_subscription_ref(invoice)
|
|
60
|
+
ref(dig(invoice, :parent, :subscription_details, :subscription)) ||
|
|
61
|
+
ref(dig(invoice, :subscription))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Unix time the invoice was paid, or nil. Stripe reports it on
|
|
65
|
+
# status_transitions.paid_at; a top-level paid_at is tolerated
|
|
66
|
+
# for hand-built payloads.
|
|
67
|
+
def invoice_paid_at(invoice)
|
|
68
|
+
dig(invoice, :status_transitions, :paid_at) || dig(invoice, :paid_at)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# First subscription item (items.data[0]) or nil.
|
|
72
|
+
def first_subscription_item(subscription)
|
|
73
|
+
dig(subscription, :items, :data, 0)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Unix end of the current billing period.
|
|
77
|
+
# Current API: items.data[].current_period_end (one per item).
|
|
78
|
+
# Before 2025-03-31.basil: subscription.current_period_end.
|
|
79
|
+
def subscription_current_period_end(subscription)
|
|
80
|
+
dig(first_subscription_item(subscription), :current_period_end) ||
|
|
81
|
+
dig(subscription, :current_period_end)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Price id of the first subscription item.
|
|
85
|
+
def subscription_price_ref(subscription)
|
|
86
|
+
ref(dig(first_subscription_item(subscription), :price))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -28,7 +28,15 @@ module Billing
|
|
|
28
28
|
# Returns the parsed Stripe::Event on success; raises
|
|
29
29
|
# Billing::WebhookError on any failure mode (no header,
|
|
30
30
|
# malformed header, no v1, no matching v1, timestamp outside
|
|
31
|
-
# tolerance, malformed JSON).
|
|
31
|
+
# tolerance, malformed JSON, or a v2 "thin" event).
|
|
32
|
+
#
|
|
33
|
+
# stripe-ruby 19 made construct_event raise ArgumentError when
|
|
34
|
+
# the signed body is a v2 event notification
|
|
35
|
+
# (object: "v2.core.event"). This endpoint handles v1 snapshot
|
|
36
|
+
# events only, so a v2 payload means the event destination is
|
|
37
|
+
# misconfigured: we answer 400 via Billing::WebhookError rather
|
|
38
|
+
# than a 500.
|
|
39
|
+
# https://github.com/stripe/stripe-ruby/wiki/Migration-guide-for-v19
|
|
32
40
|
def verify!(payload:, signature_header:, secret:, tolerance: DEFAULT_TOLERANCE)
|
|
33
41
|
raise Billing::WebhookError, "Stripe-Signature header missing" if signature_header.nil? || signature_header.empty?
|
|
34
42
|
|
|
@@ -37,6 +45,8 @@ module Billing
|
|
|
37
45
|
raise Billing::WebhookError, "Stripe signature invalid: #{e.message}"
|
|
38
46
|
rescue JSON::ParserError => e
|
|
39
47
|
raise Billing::WebhookError, "Stripe payload not JSON: #{e.message}"
|
|
48
|
+
rescue ArgumentError => e
|
|
49
|
+
raise Billing::WebhookError, "Stripe payload is not a v1 event: #{e.message}"
|
|
40
50
|
end
|
|
41
51
|
end
|
|
42
52
|
end
|
|
@@ -4,21 +4,32 @@
|
|
|
4
4
|
"type": "customer.subscription.created",
|
|
5
5
|
"livemode": false,
|
|
6
6
|
"created": 1730000000,
|
|
7
|
+
"api_version": "2026-08-26.dahlia",
|
|
7
8
|
"data": {
|
|
8
9
|
"object": {
|
|
9
10
|
"id": "sub_test_123",
|
|
10
11
|
"object": "subscription",
|
|
11
12
|
"customer": "cus_test_123",
|
|
12
13
|
"status": "active",
|
|
13
|
-
"current_period_end": 1732678400,
|
|
14
14
|
"items": {
|
|
15
|
+
"object": "list",
|
|
15
16
|
"data": [
|
|
16
17
|
{
|
|
17
18
|
"id": "si_test_123",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
19
|
+
"object": "subscription_item",
|
|
20
|
+
"current_period_start": 1730000000,
|
|
21
|
+
"current_period_end": 1732678400,
|
|
22
|
+
"price": {
|
|
23
|
+
"id": "price_test_pro",
|
|
24
|
+
"object": "price",
|
|
25
|
+
"lookup_key": "pro"
|
|
26
|
+
},
|
|
27
|
+
"quantity": 1,
|
|
28
|
+
"subscription": "sub_test_123"
|
|
20
29
|
}
|
|
21
|
-
]
|
|
30
|
+
],
|
|
31
|
+
"has_more": false,
|
|
32
|
+
"url": "/v1/subscription_items?subscription=sub_test_123"
|
|
22
33
|
}
|
|
23
34
|
}
|
|
24
35
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"type": "customer.subscription.deleted",
|
|
5
5
|
"livemode": false,
|
|
6
6
|
"created": 1730200000,
|
|
7
|
+
"api_version": "2026-08-26.dahlia",
|
|
7
8
|
"data": {
|
|
8
9
|
"object": {
|
|
9
10
|
"id": "sub_test_123",
|
|
@@ -11,7 +12,12 @@
|
|
|
11
12
|
"customer": "cus_test_123",
|
|
12
13
|
"status": "canceled",
|
|
13
14
|
"canceled_at": 1730200000,
|
|
14
|
-
"items": {
|
|
15
|
+
"items": {
|
|
16
|
+
"object": "list",
|
|
17
|
+
"data": [],
|
|
18
|
+
"has_more": false,
|
|
19
|
+
"url": "/v1/subscription_items?subscription=sub_test_123"
|
|
20
|
+
}
|
|
15
21
|
}
|
|
16
22
|
}
|
|
17
23
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"type": "customer.subscription.trial_will_end",
|
|
5
5
|
"livemode": false,
|
|
6
6
|
"created": 1730300000,
|
|
7
|
+
"api_version": "2026-08-26.dahlia",
|
|
7
8
|
"data": {
|
|
8
9
|
"object": {
|
|
9
10
|
"id": "sub_test_123",
|
|
@@ -11,7 +12,23 @@
|
|
|
11
12
|
"customer": "cus_test_123",
|
|
12
13
|
"status": "trialing",
|
|
13
14
|
"trial_end": 1730560000,
|
|
14
|
-
"items": {
|
|
15
|
+
"items": {
|
|
16
|
+
"object": "list",
|
|
17
|
+
"data": [
|
|
18
|
+
{
|
|
19
|
+
"id": "si_test_123",
|
|
20
|
+
"object": "subscription_item",
|
|
21
|
+
"current_period_start": 1729955200,
|
|
22
|
+
"current_period_end": 1730560000,
|
|
23
|
+
"price": {
|
|
24
|
+
"object": "price",
|
|
25
|
+
"lookup_key": "pro"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"has_more": false,
|
|
30
|
+
"url": "/v1/subscription_items?subscription=sub_test_123"
|
|
31
|
+
}
|
|
15
32
|
}
|
|
16
33
|
}
|
|
17
34
|
}
|
|
@@ -4,25 +4,46 @@
|
|
|
4
4
|
"type": "customer.subscription.updated",
|
|
5
5
|
"livemode": false,
|
|
6
6
|
"created": 1730100000,
|
|
7
|
+
"api_version": "2026-08-26.dahlia",
|
|
7
8
|
"data": {
|
|
8
9
|
"object": {
|
|
9
10
|
"id": "sub_test_123",
|
|
10
11
|
"object": "subscription",
|
|
11
12
|
"customer": "cus_test_123",
|
|
12
13
|
"status": "active",
|
|
13
|
-
"current_period_end": 1735270400,
|
|
14
14
|
"items": {
|
|
15
|
+
"object": "list",
|
|
15
16
|
"data": [
|
|
16
17
|
{
|
|
17
18
|
"id": "si_test_123",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
19
|
+
"object": "subscription_item",
|
|
20
|
+
"current_period_start": 1732678400,
|
|
21
|
+
"current_period_end": 1735270400,
|
|
22
|
+
"price": {
|
|
23
|
+
"id": "price_test_pro_annual",
|
|
24
|
+
"object": "price",
|
|
25
|
+
"lookup_key": "pro_annual"
|
|
26
|
+
},
|
|
27
|
+
"quantity": 1,
|
|
28
|
+
"subscription": "sub_test_123"
|
|
20
29
|
}
|
|
21
|
-
]
|
|
30
|
+
],
|
|
31
|
+
"has_more": false,
|
|
32
|
+
"url": "/v1/subscription_items?subscription=sub_test_123"
|
|
22
33
|
}
|
|
23
34
|
},
|
|
24
35
|
"previous_attributes": {
|
|
25
|
-
"items": {
|
|
36
|
+
"items": {
|
|
37
|
+
"data": [
|
|
38
|
+
{
|
|
39
|
+
"id": "si_test_123",
|
|
40
|
+
"price": {
|
|
41
|
+
"id": "price_test_pro",
|
|
42
|
+
"lookup_key": "pro"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
26
47
|
}
|
|
27
48
|
}
|
|
28
49
|
}
|
|
@@ -4,15 +4,29 @@
|
|
|
4
4
|
"type": "invoice.created",
|
|
5
5
|
"livemode": false,
|
|
6
6
|
"created": 1730400000,
|
|
7
|
+
"api_version": "2026-08-26.dahlia",
|
|
7
8
|
"data": {
|
|
8
9
|
"object": {
|
|
9
10
|
"id": "in_test_123",
|
|
10
11
|
"object": "invoice",
|
|
11
12
|
"customer": "cus_test_123",
|
|
12
|
-
"
|
|
13
|
+
"parent": {
|
|
14
|
+
"type": "subscription_details",
|
|
15
|
+
"quote_details": null,
|
|
16
|
+
"subscription_details": {
|
|
17
|
+
"metadata": {},
|
|
18
|
+
"subscription": "sub_test_123"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
13
21
|
"status": "draft",
|
|
14
22
|
"amount_due": 1299,
|
|
15
|
-
"currency": "gbp"
|
|
23
|
+
"currency": "gbp",
|
|
24
|
+
"status_transitions": {
|
|
25
|
+
"finalized_at": null,
|
|
26
|
+
"marked_uncollectible_at": null,
|
|
27
|
+
"paid_at": null,
|
|
28
|
+
"voided_at": null
|
|
29
|
+
}
|
|
16
30
|
}
|
|
17
31
|
}
|
|
18
32
|
}
|
|
@@ -4,15 +4,29 @@
|
|
|
4
4
|
"type": "invoice.finalized",
|
|
5
5
|
"livemode": false,
|
|
6
6
|
"created": 1730700000,
|
|
7
|
+
"api_version": "2026-08-26.dahlia",
|
|
7
8
|
"data": {
|
|
8
9
|
"object": {
|
|
9
10
|
"id": "in_test_123",
|
|
10
11
|
"object": "invoice",
|
|
11
12
|
"customer": "cus_test_123",
|
|
12
|
-
"
|
|
13
|
+
"parent": {
|
|
14
|
+
"type": "subscription_details",
|
|
15
|
+
"quote_details": null,
|
|
16
|
+
"subscription_details": {
|
|
17
|
+
"metadata": {},
|
|
18
|
+
"subscription": "sub_test_123"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
13
21
|
"status": "open",
|
|
14
22
|
"amount_due": 1299,
|
|
15
|
-
"currency": "gbp"
|
|
23
|
+
"currency": "gbp",
|
|
24
|
+
"status_transitions": {
|
|
25
|
+
"finalized_at": 1730700000,
|
|
26
|
+
"marked_uncollectible_at": null,
|
|
27
|
+
"paid_at": null,
|
|
28
|
+
"voided_at": null
|
|
29
|
+
}
|
|
16
30
|
}
|
|
17
31
|
}
|
|
18
32
|
}
|
|
@@ -4,16 +4,31 @@
|
|
|
4
4
|
"type": "invoice.paid",
|
|
5
5
|
"livemode": false,
|
|
6
6
|
"created": 1730500000,
|
|
7
|
+
"api_version": "2026-08-26.dahlia",
|
|
7
8
|
"data": {
|
|
8
9
|
"object": {
|
|
9
10
|
"id": "in_test_123",
|
|
10
11
|
"object": "invoice",
|
|
11
12
|
"customer": "cus_test_123",
|
|
12
|
-
"
|
|
13
|
+
"parent": {
|
|
14
|
+
"type": "subscription_details",
|
|
15
|
+
"quote_details": null,
|
|
16
|
+
"subscription_details": {
|
|
17
|
+
"metadata": {},
|
|
18
|
+
"subscription": "sub_test_123"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
13
21
|
"status": "paid",
|
|
14
22
|
"amount_paid": 1299,
|
|
15
23
|
"currency": "gbp",
|
|
16
|
-
"
|
|
24
|
+
"amount_due": 1299,
|
|
25
|
+
"amount_remaining": 0,
|
|
26
|
+
"status_transitions": {
|
|
27
|
+
"finalized_at": 1730450000,
|
|
28
|
+
"marked_uncollectible_at": null,
|
|
29
|
+
"paid_at": 1730500000,
|
|
30
|
+
"voided_at": null
|
|
31
|
+
}
|
|
17
32
|
}
|
|
18
33
|
}
|
|
19
34
|
}
|
data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt
CHANGED
|
@@ -4,17 +4,31 @@
|
|
|
4
4
|
"type": "invoice.payment_failed",
|
|
5
5
|
"livemode": false,
|
|
6
6
|
"created": 1730600000,
|
|
7
|
+
"api_version": "2026-08-26.dahlia",
|
|
7
8
|
"data": {
|
|
8
9
|
"object": {
|
|
9
10
|
"id": "in_test_123",
|
|
10
11
|
"object": "invoice",
|
|
11
12
|
"customer": "cus_test_123",
|
|
12
|
-
"
|
|
13
|
+
"parent": {
|
|
14
|
+
"type": "subscription_details",
|
|
15
|
+
"quote_details": null,
|
|
16
|
+
"subscription_details": {
|
|
17
|
+
"metadata": {},
|
|
18
|
+
"subscription": "sub_test_123"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
13
21
|
"status": "open",
|
|
14
22
|
"amount_due": 1299,
|
|
15
23
|
"currency": "gbp",
|
|
16
24
|
"attempt_count": 1,
|
|
17
|
-
"next_payment_attempt": 1730700000
|
|
25
|
+
"next_payment_attempt": 1730700000,
|
|
26
|
+
"status_transitions": {
|
|
27
|
+
"finalized_at": 1730550000,
|
|
28
|
+
"marked_uncollectible_at": null,
|
|
29
|
+
"paid_at": null,
|
|
30
|
+
"voided_at": null
|
|
31
|
+
}
|
|
18
32
|
}
|
|
19
33
|
}
|
|
20
34
|
}
|
|
@@ -4,15 +4,29 @@
|
|
|
4
4
|
"type": "invoice.voided",
|
|
5
5
|
"livemode": false,
|
|
6
6
|
"created": 1730800000,
|
|
7
|
+
"api_version": "2026-08-26.dahlia",
|
|
7
8
|
"data": {
|
|
8
9
|
"object": {
|
|
9
10
|
"id": "in_test_123",
|
|
10
11
|
"object": "invoice",
|
|
11
12
|
"customer": "cus_test_123",
|
|
12
|
-
"
|
|
13
|
+
"parent": {
|
|
14
|
+
"type": "subscription_details",
|
|
15
|
+
"quote_details": null,
|
|
16
|
+
"subscription_details": {
|
|
17
|
+
"metadata": {},
|
|
18
|
+
"subscription": "sub_test_123"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
13
21
|
"status": "void",
|
|
14
22
|
"amount_due": 1299,
|
|
15
|
-
"currency": "gbp"
|
|
23
|
+
"currency": "gbp",
|
|
24
|
+
"status_transitions": {
|
|
25
|
+
"finalized_at": 1730700000,
|
|
26
|
+
"marked_uncollectible_at": null,
|
|
27
|
+
"paid_at": null,
|
|
28
|
+
"voided_at": 1730800000
|
|
29
|
+
}
|
|
16
30
|
}
|
|
17
31
|
}
|
|
18
32
|
}
|