From c6c5ed2ed3a77558b6cbe32ed33a8ad748ca6d56 Mon Sep 17 00:00:00 2001 From: Blake Erickson Date: Mon, 22 Nov 2021 12:46:40 -0700 Subject: [PATCH] FIX: Include one-time purchases in campaign total (#95) * FIX: Include one-time purchases in campaign total On the subscription banner that shows on the top of the forum it shows the total amount raised toward a goal. But the amount shown was not including one-time purchases. It was only showing subscriptions. This change updates the sync with stripe logic so that it also includes one-time purchases. See: https://meta.discourse.org/t/-/209591 * rubocop: remove extra blank line --- .../discourse_subscriptions/campaign.rb | 50 +++++++++++++++++++ spec/services/campaign_spec.rb | 29 ++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/app/services/discourse_subscriptions/campaign.rb b/app/services/discourse_subscriptions/campaign.rb index d85dd6a..280c463 100644 --- a/app/services/discourse_subscriptions/campaign.rb +++ b/app/services/discourse_subscriptions/campaign.rb @@ -21,6 +21,12 @@ module DiscourseSubscriptions subscriptions = get_subscription_data subscriptions = filter_to_subscriptions_products(subscriptions, product_ids) + # Fetch product purchases + one_time_payments = get_one_time_payments(product_ids) + one_time_payments.each do |c| + amount += c[:price].to_f / 100.00 + end + # get number of subscribers SiteSetting.discourse_subscriptions_campaign_subscribers = subscriptions&.length.to_i @@ -144,6 +150,50 @@ module DiscourseSubscriptions plan = ::Stripe::Price.create(price_object) end + def get_one_time_payments(product_ids) + one_time_payments = [] + current_set = { + has_more: true, + last_record: nil + } + + if product_ids.present? + # lots of matching because the Stripe API doesn't make it easy to match products => payments except from invoices + until current_set[:has_more] == false + all_invoices = ::Stripe::Invoice.list( + limit: 100, + starting_after: current_set[:last_record] + ) + + current_set[:last_record] = all_invoices[:data].last[:id] if all_invoices[:data].present? + current_set[:has_more] = all_invoices[:has_more] + + all_invoices[:data].each do |invoice| + customer_id = invoice[:customer] + line_item = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data] # Discourse only makes single-line item charges + # check if non-subscription and that the plan is active + if line_item[:plan] == nil && + line_item[:price] && + line_item[:price][:recurring] == nil && + line_item[:price][:active] == true + + product_id = line_item[:price][:product] + if product_ids.include? product_id + line_data = { + customer_id: customer_id, + product_id: product_id, + price: line_item[:price][:unit_amount], + } + one_time_payments << line_data + end + end + end + end + end + + one_time_payments + end + def get_subscription_data subscriptions = [] current_set = { diff --git a/spec/services/campaign_spec.rb b/spec/services/campaign_spec.rb index 9b1968a..90f56fe 100644 --- a/spec/services/campaign_spec.rb +++ b/spec/services/campaign_spec.rb @@ -5,6 +5,7 @@ require 'rails_helper' describe DiscourseSubscriptions::Campaign do describe 'campaign data is refreshed' do let (:user) { Fabricate(:user) } + let (:user2) { Fabricate(:user) } let(:subscription) do { id: "sub_1234", @@ -23,10 +24,30 @@ describe DiscourseSubscriptions::Campaign do } } end + let(:invoice) do + { + id: "in_1234", + lines: { + data: [ + { + plan: nil, + price: { + product: "prodct_65432", + active: true, + unit_amount: 1000, + recurring: nil, + } + } + ] + } + } + end before do Fabricate(:product, external_id: "prodct_23456") Fabricate(:customer, product_id: "prodct_23456", user_id: user.id, customer_id: 'x') + Fabricate(:product, external_id: "prodct_65432") + Fabricate(:customer, product_id: "prodct_65432", user_id: user2.id, customer_id: 'y') SiteSetting.discourse_subscriptions_public_key = "public-key" SiteSetting.discourse_subscriptions_secret_key = "secret-key" end @@ -35,25 +56,28 @@ describe DiscourseSubscriptions::Campaign do context "for all subscription purchases" do it "refreshes the campaign data properly" do ::Stripe::Subscription.expects(:list).returns(data: [subscription], has_more: false) + ::Stripe::Invoice.expects(:list).returns(data: [invoice], has_more: false) DiscourseSubscriptions::Campaign.new.refresh_data expect(SiteSetting.discourse_subscriptions_campaign_subscribers).to eq 1 - expect(SiteSetting.discourse_subscriptions_campaign_amount_raised).to eq 10.00 + expect(SiteSetting.discourse_subscriptions_campaign_amount_raised).to eq 20.00 end it "checks if the goal is completed or not" do SiteSetting.discourse_subscriptions_campaign_goal = 5 ::Stripe::Subscription.expects(:list).returns(data: [subscription], has_more: false) + ::Stripe::Invoice.expects(:list).returns(data: [invoice], has_more: false) DiscourseSubscriptions::Campaign.new.refresh_data expect(Discourse.redis.get('subscriptions_goal_met_date')).to be_present end it "checks if goal is < 90% met after being met" do - SiteSetting.discourse_subscriptions_campaign_goal = 15 + SiteSetting.discourse_subscriptions_campaign_goal = 25 Discourse.redis.set('subscriptions_goal_met_date', 10.days.ago) ::Stripe::Subscription.expects(:list).returns(data: [subscription], has_more: false) + ::Stripe::Invoice.expects(:list).returns(data: [invoice], has_more: false) DiscourseSubscriptions::Campaign.new.refresh_data expect(Discourse.redis.get('subscriptions_goal_met_date')).to be_blank @@ -89,6 +113,7 @@ describe DiscourseSubscriptions::Campaign do it "refreshes campaign data with only the campaign product/subscriptions" do ::Stripe::Subscription.expects(:list).returns(data: [subscription, campaign_subscription], has_more: false) + ::Stripe::Invoice.expects(:list).returns(data: [invoice], has_more: false) DiscourseSubscriptions::Campaign.new.refresh_data