discourse-subscriptions/app/services/discourse_subscriptions/campaign.rb

249 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 do |c|
amount += c[:price].to_f / 100.00
end
# 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 do |price|
create_price(product[:id], group, price, "month")
end
yearly_prices.each do |price|
create_price(product[:id], group, price, "year")
end
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]
)
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]
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[: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 = {
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