From 3395b8d6ed2a6637e289af3cf0c6fd945ad161dd Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Mon, 15 Dec 2025 18:26:42 +0100 Subject: [PATCH] Grab and show the next amount to pay from Stripe --- saas/app/controllers/stripe/webhooks_controller.rb | 12 +++++++++++- saas/app/helpers/subscriptions_helper.rb | 2 +- saas/app/models/account/subscription.rb | 4 ++++ ...t_amount_due_in_cents_to_account_subscriptions.rb | 5 +++++ saas/db/saas_schema.rb | 3 ++- .../controllers/stripe/webhooks_controller_test.rb | 9 +++++++-- 6 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 saas/db/migrate/20251215170000_add_next_amount_due_in_cents_to_account_subscriptions.rb diff --git a/saas/app/controllers/stripe/webhooks_controller.rb b/saas/app/controllers/stripe/webhooks_controller.rb index 14c50c74d..22d8d4e0c 100644 --- a/saas/app/controllers/stripe/webhooks_controller.rb +++ b/saas/app/controllers/stripe/webhooks_controller.rb @@ -48,7 +48,8 @@ class Stripe::WebhooksController < ApplicationController stripe_subscription_id: stripe_subscription.id, status: stripe_subscription.status, current_period_end: current_period_end_for(stripe_subscription), - cancel_at: stripe_subscription.cancel_at ? Time.at(stripe_subscription.cancel_at) : nil + cancel_at: stripe_subscription.cancel_at ? Time.at(stripe_subscription.cancel_at) : nil, + next_amount_due_in_cents: next_amount_due_for(stripe_subscription) } yield subscription_properties if block_given? @@ -66,4 +67,13 @@ class Stripe::WebhooksController < ApplicationController timestamp = stripe_subscription.items.data.first&.current_period_end Time.at(timestamp) if timestamp end + + def next_amount_due_for(stripe_subscription) + return nil if stripe_subscription.status == "canceled" + + preview = Stripe::Invoice.create_preview(customer: stripe_subscription.customer, subscription: stripe_subscription.id) + preview.amount_due + rescue Stripe::InvalidRequestError + nil + end end diff --git a/saas/app/helpers/subscriptions_helper.rb b/saas/app/helpers/subscriptions_helper.rb index 32dd45402..2c05b68eb 100644 --- a/saas/app/helpers/subscriptions_helper.rb +++ b/saas/app/helpers/subscriptions_helper.rb @@ -5,7 +5,7 @@ module SubscriptionsHelper elsif subscription.canceled? "Your Fizzy subscription ended on" else - "Your next payment of $#{ subscription.plan.price } will be billed on".html_safe + "Your next payment of #{ number_to_currency(subscription.next_amount_due) } will be billed on".html_safe end end end diff --git a/saas/app/models/account/subscription.rb b/saas/app/models/account/subscription.rb index 937d11bcc..0eac71b02 100644 --- a/saas/app/models/account/subscription.rb +++ b/saas/app/models/account/subscription.rb @@ -14,4 +14,8 @@ class Account::Subscription < SaasRecord def to_be_canceled? active? && cancel_at.present? end + + def next_amount_due + next_amount_due_in_cents ? next_amount_due_in_cents / 100.0 : plan.price + end end diff --git a/saas/db/migrate/20251215170000_add_next_amount_due_in_cents_to_account_subscriptions.rb b/saas/db/migrate/20251215170000_add_next_amount_due_in_cents_to_account_subscriptions.rb new file mode 100644 index 000000000..28838b314 --- /dev/null +++ b/saas/db/migrate/20251215170000_add_next_amount_due_in_cents_to_account_subscriptions.rb @@ -0,0 +1,5 @@ +class AddNextAmountDueInCentsToAccountSubscriptions < ActiveRecord::Migration[8.2] + def change + add_column :account_subscriptions, :next_amount_due_in_cents, :integer + end +end diff --git a/saas/db/saas_schema.rb b/saas/db/saas_schema.rb index 15dea28fc..59c78f4cb 100644 --- a/saas/db/saas_schema.rb +++ b/saas/db/saas_schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.2].define(version: 2025_12_15_160000) do +ActiveRecord::Schema[8.2].define(version: 2025_12_15_170000) do create_table "account_billing_waivers", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.uuid "account_id", null: false t.datetime "created_at", null: false @@ -31,6 +31,7 @@ ActiveRecord::Schema[8.2].define(version: 2025_12_15_160000) do t.datetime "cancel_at" t.datetime "created_at", null: false t.datetime "current_period_end" + t.integer "next_amount_due_in_cents" t.string "plan_key" t.string "status" t.string "stripe_customer_id", null: false diff --git a/saas/test/controllers/stripe/webhooks_controller_test.rb b/saas/test/controllers/stripe/webhooks_controller_test.rb index c1c8ac4d0..92f6b7508 100644 --- a/saas/test/controllers/stripe/webhooks_controller_test.rb +++ b/saas/test/controllers/stripe/webhooks_controller_test.rb @@ -29,6 +29,7 @@ class Stripe::WebhooksControllerTest < ActionDispatch::IntegrationTest Stripe::Webhook.stubs(:construct_event).returns(event) Stripe::Subscription.stubs(:retrieve).returns(stripe_sub) + Stripe::Invoice.stubs(:create_preview).returns(OpenStruct.new(amount_due: 1999)) post stripe_webhooks_path @@ -38,7 +39,7 @@ class Stripe::WebhooksControllerTest < ActionDispatch::IntegrationTest assert_equal "active", @subscription.status end - test "subscription updated changes status" do + test "subscription updated changes status and syncs next amount due" do @subscription.update!(stripe_subscription_id: "sub_123", status: "active") stripe_sub = OpenStruct.new( @@ -53,11 +54,14 @@ class Stripe::WebhooksControllerTest < ActionDispatch::IntegrationTest Stripe::Webhook.stubs(:construct_event).returns(event) Stripe::Subscription.stubs(:retrieve).returns(stripe_sub) + Stripe::Invoice.stubs(:create_preview).returns(OpenStruct.new(amount_due: 1999)) post stripe_webhooks_path assert_response :ok - assert_equal "past_due", @subscription.reload.status + @subscription.reload + assert_equal "past_due", @subscription.status + assert_equal 1999, @subscription.next_amount_due_in_cents end test "subscription deleted cancels subscription" do @@ -82,6 +86,7 @@ class Stripe::WebhooksControllerTest < ActionDispatch::IntegrationTest @subscription.reload assert_equal "canceled", @subscription.status assert_nil @subscription.stripe_subscription_id + assert_nil @subscription.next_amount_due_in_cents end private