Files
discourse-subscriptions/app/services/discourse_subscriptions/campaign.rb
T
Blake Erickson 572aa0f4a9 FIX: Exception with campaign refresh data job (#222)
Depending on the invoices in stripe there could be some nil values for
things so we need to account for those in this job or an exception will
be thrown causing the job to fail.
2024-07-16 11:54:56 -06:00

238 lines
7.4 KiB
Ruby

# frozen_string_literal: true
module DiscourseSubscriptions
class Campaign
include DiscourseSubscriptions::Stripe
def initialize
set_api_key # instantiates Stripe API
end
def refresh_data
product_ids = Set.new(Product.all.pluck(:external_id))
# if a product id is set for the campaign, we only want to return those results.
# if it's blank, return them all.
campaign_product = SiteSetting.discourse_subscriptions_campaign_product
if campaign_product.present?
product_ids = product_ids.include?(campaign_product) ? [campaign_product] : []
end
amount = 0.00
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 { |c| amount += c[:price].to_f / 100.00 }
# get number of subscribers
SiteSetting.discourse_subscriptions_campaign_subscribers = subscriptions&.length.to_i
# calculate amount raised
subscriptions&.each do |sub|
sub_amount = calculate_monthly_amount(sub)
amount += sub_amount / 100.00
end
SiteSetting.discourse_subscriptions_campaign_amount_raised = amount.round(2)
check_goal_status
end
def create_campaign
begin
group = create_campaign_group
product = create_campaign_product
create_campaign_prices(product, group)
SiteSetting.discourse_subscriptions_campaign_enabled = true
SiteSetting.discourse_subscriptions_campaign_product = product[:id]
rescue ::Stripe::InvalidRequestError => e
e
end
end
protected
def goal_met_date_key
"subscriptions_goal_met_date"
end
def check_goal_status
goal = SiteSetting.discourse_subscriptions_campaign_goal
goal_type = SiteSetting.discourse_subscriptions_campaign_type
case goal_type
when "Amount"
current_volume = SiteSetting.discourse_subscriptions_campaign_amount_raised
when "Subscribers"
current_volume = SiteSetting.discourse_subscriptions_campaign_subscribers
end
goal_met_date = Discourse.redis.get(goal_met_date_key)
if goal_met_date
# delete the key if we're at or below 90% of the goal
Discourse.redis.del(goal_met_date_key) if current_volume / goal <= 0.90
else
Discourse.redis.set(goal_met_date_key, Time.now) if current_volume > goal
end
end
def create_campaign_group
campaign_group = SiteSetting.discourse_subscriptions_campaign_group
group = ::Group.find_by_id(campaign_group) if campaign_group.present?
unless group
group = ::Group.create(name: "campaign_supporters")
SiteSetting.discourse_subscriptions_campaign_group = group[:id]
params = {
full_name: I18n.t("js.discourse_subscriptions.campaign.supporters"),
title: I18n.t("js.discourse_subscriptions.campaign.supporter"),
flair_icon: "donate",
}
group.update(params)
end
group[:name]
end
def create_campaign_product
product_params = {
name: I18n.t("js.discourse_subscriptions.campaign.title"),
active: true,
metadata: {
description: I18n.t("js.discourse_subscriptions.campaign.body"),
},
}
product = ::Stripe::Product.create(product_params)
Product.create(external_id: product[:id])
product
end
def create_campaign_prices(product, group)
# hard coded defaults to make setting this up as simple as possible
monthly_prices = [3, 5, 10, 25]
yearly_prices = [50, 100]
monthly_prices.each { |price| create_price(product[:id], group, price, "month") }
yearly_prices.each { |price| create_price(product[:id], group, price, "year") }
end
def create_price(product_id, group_name, amount, recurrence)
price_object = {
nickname: "#{amount}/#{recurrence}",
unit_amount: amount * 100,
product: product_id,
currency: SiteSetting.discourse_subscriptions_currency,
active: true,
recurring: {
interval: recurrence,
},
metadata: {
group_name: group_name,
},
}
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])
if all_invoices[:data].present?
current_set[:last_record] = all_invoices[:data].last[:id]
current_set[:has_more] = all_invoices[:has_more]
all_invoices[:data].each do |invoice|
customer_id = invoice[:customer]
next if invoice[:paid] != true
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 && 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
else
current_set[:has_more] = false
end
end
end
one_time_payments
end
def get_subscription_data
subscriptions = []
current_set = { has_more: true, last_record: nil }
until current_set[:has_more] == false
current_set =
::Stripe::Subscription.list(
expand: ["data.plan.product"],
limit: 100,
starting_after: current_set[:last_record],
)
current_set[:last_record] = current_set[:data].last[:id] if current_set[:data].present?
subscriptions.concat(current_set[:data].to_a)
end
subscriptions
end
def filter_to_subscriptions_products(data, ids)
valid =
data.select do |sub|
# cannot .dig stripe objects
items = sub[:items][:data][0] if sub[:items] && sub[:items][:data]
product = items[:price][:product] if items[:price] && items[:price][:product]
ids.include?(product)
end
valid.empty? ? nil : valid
end
def calculate_monthly_amount(sub)
items = sub[:items][:data][0] if sub[:items] && sub[:items][:data]
price = items[:price] if items[:price]
unit_amount = price[:unit_amount] if price[:unit_amount]
recurrence = price[:recurring][:interval] if price[:recurring] && price[:recurring][:interval]
case recurrence
when "day"
unit_amount = unit_amount * 30
when "week"
unit_amount = unit_amount * 4
when "year"
unit_amount = unit_amount / 12.00
end
unit_amount.to_f
end
end
end