From f0b4984ceebe99294379af0cf0dbb46f52256b59 Mon Sep 17 00:00:00 2001 From: Blake Erickson Date: Tue, 20 Aug 2024 13:32:32 -0600 Subject: [PATCH] FIX: Pricing table for one-off purchases (#228) When using the Stripe Pricing table for one-off purchases (non-subscriptions) the webhook was encountering an error because it was expecting subscription information which does not exist for one-off purchases. This fix addresses that issue ensuring that no errors occur, that users are added to the appropriate group, and that uses can see their purchase on the billing page. --- .../hooks_controller.rb | 20 +++++---- .../user/payments_controller.rb | 20 +++++---- spec/requests/hooks_controller_spec.rb | 41 +++++++++++++++++++ .../requests/user/payments_controller_spec.rb | 15 +++++++ 4 files changed, 80 insertions(+), 16 deletions(-) diff --git a/app/controllers/discourse_subscriptions/hooks_controller.rb b/app/controllers/discourse_subscriptions/hooks_controller.rb index 78bea0f..008aefc 100644 --- a/app/controllers/discourse_subscriptions/hooks_controller.rb +++ b/app/controllers/discourse_subscriptions/hooks_controller.rb @@ -44,23 +44,27 @@ module DiscourseSubscriptions discourse_customer = Customer.create(user_id: user.id, customer_id: customer_id) - Subscription.create( - customer_id: discourse_customer.id, - external_id: checkout_session[:subscription], - ) + subscription = checkout_session[:subscription] + + if !subscription.nil? + Subscription.create(customer_id: discourse_customer.id, external_id: subscription) + end line_items = ::Stripe::Checkout::Session.list_line_items(checkout_session[:id], { limit: 1 }) item = line_items[:data].first + group = plan_group(item[:price]) group.add(user) unless group.nil? discourse_customer.product_id = item[:price][:product] discourse_customer.save! - ::Stripe::Subscription.update( - checkout_session[:subscription], - { metadata: { user_id: user.id, username: user.username } }, - ) + if !subscription.nil? + ::Stripe::Subscription.update( + subscription, + { metadata: { user_id: user.id, username: user.username } }, + ) + end when "customer.subscription.created" when "customer.subscription.updated" subscription = event[:data][:object] diff --git a/app/controllers/discourse_subscriptions/user/payments_controller.rb b/app/controllers/discourse_subscriptions/user/payments_controller.rb index 9588d8d..b1acff7 100644 --- a/app/controllers/discourse_subscriptions/user/payments_controller.rb +++ b/app/controllers/discourse_subscriptions/user/payments_controller.rb @@ -27,7 +27,12 @@ module DiscourseSubscriptions payments = ::Stripe::PaymentIntent.list(customer: customer_id) payments_from_invoices = payments[:data].select { |payment| invoice_ids.include?(payment[:invoice]) } - data = data | payments_from_invoices + + # Pricing table one-off purchases do not have invoices + payments_without_invoices = + payments[:data].select { |payment| payment[:invoice].nil? } + + data = data | payments_from_invoices | payments_without_invoices end end @@ -42,14 +47,13 @@ module DiscourseSubscriptions private def parse_invoices(all_invoices, product_ids) - invoices_with_products = - all_invoices[:data].select do |invoice| - invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data] - if invoice_lines - invoice_product_id = parse_invoice_lines(invoice_lines) - product_ids.include?(invoice_product_id) - end + all_invoices[:data].select do |invoice| + invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data] + if invoice_lines + invoice_product_id = parse_invoice_lines(invoice_lines) + product_ids.include?(invoice_product_id) end + end end def parse_invoice_lines(invoice_lines) diff --git a/spec/requests/hooks_controller_spec.rb b/spec/requests/hooks_controller_spec.rb index b9acb62..ccb2ca2 100644 --- a/spec/requests/hooks_controller_spec.rb +++ b/spec/requests/hooks_controller_spec.rb @@ -83,6 +83,27 @@ RSpec.describe DiscourseSubscriptions::HooksController do } end + let(:checkout_session_completed_data_one_off) do + { + object: { + id: "cs_test_a1ENei5A9TGOaEketyV5qweiQR5CyJWHT5j8T3HheQY3uah3RxzKttVUKZ", + object: "checkout.session", + customer: customer.customer_id, + customer_email: user.email, + invoice: nil, + metadata: { + }, + mode: "subscription", + payment_status: "paid", + status: "complete", + submit_type: nil, + subscription: nil, + success_url: "http://localhost:4200/my/billing/subscriptions", + url: nil, + }, + } + end + let(:checkout_session_completed_bad_data) do { object: { @@ -184,6 +205,26 @@ RSpec.describe DiscourseSubscriptions::HooksController do end end + describe "checkout.session.completed for one-off purchase" do + before do + event = { + type: "checkout.session.completed", + data: checkout_session_completed_data_one_off, + } + ::Stripe::Checkout::Session + .stubs(:list_line_items) + .with(checkout_session_completed_data[:object][:id], { limit: 1 }) + .returns(list_line_items_data) + + ::Stripe::Webhook.stubs(:construct_event).returns(event) + end + + it "is returns 200" do + expect { post "/s/hooks.json" }.to change { user.groups.count }.by(1) + expect(response.status).to eq 200 + end + end + describe "checkout.session.completed with anonymous user" do before do checkout_session_completed_bad_data[:object][:customer_email] = "anonymous@example.com" diff --git a/spec/requests/user/payments_controller_spec.rb b/spec/requests/user/payments_controller_spec.rb index 5ad0563..7fe87d4 100644 --- a/spec/requests/user/payments_controller_spec.rb +++ b/spec/requests/user/payments_controller_spec.rb @@ -60,5 +60,20 @@ RSpec.describe DiscourseSubscriptions::User::PaymentsController do expect(invoice).to eq("inv_900007") expect(parsed_body.count).to eq(2) end + + it "gets pricing table one-off purchases" do + ::Stripe::Invoice.expects(:list).with(customer: "c_345678").returns(data: []) + + ::Stripe::PaymentIntent + .expects(:list) + .with(customer: "c_345678") + .returns(data: [{ id: "pi_900010", invoice: nil, created: Time.now }]) + + get "/s/user/payments.json" + + parsed_body = response.parsed_body + + expect(parsed_body.count).to eq(1) + end end end