diff --git a/app/models/graphql/transactions_query.rb b/app/models/graphql/transactions_query.rb index e4fa5d6..5b02e1c 100644 --- a/app/models/graphql/transactions_query.rb +++ b/app/models/graphql/transactions_query.rb @@ -10,6 +10,7 @@ createdAt, # Apps ... on AppSubscriptionSale { + billingInterval, netAmount { amount }, diff --git a/app/models/import.rb b/app/models/import.rb index b03fe3a..d607666 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -69,7 +69,7 @@ def source_adaptor def import_payments_after_date max_allowed_ago = source_adaptor.const_get(:MAX_HISTORY).ago - if user.newest_metric_date.to_i < max_allowed_ago.to_i + if (user.newest_metric_date&.to_time).to_i < max_allowed_ago.to_i max_allowed_ago else user.newest_metric_date diff --git a/app/models/import/adaptor/csv_file.rb b/app/models/import/adaptor/csv_file.rb index b005b88..158078f 100644 --- a/app/models/import/adaptor/csv_file.rb +++ b/app/models/import/adaptor/csv_file.rb @@ -10,6 +10,8 @@ class Import::Adaptor::CsvFile encoding: "UTF-8" }.freeze + CSV_YEARLY_IDENTIFIER = "yearly".freeze + CSV_REVENUE_TYPES = { "recurring_revenue" => [ "RecurringApplicationFee", @@ -91,6 +93,7 @@ def parse(csv_row) charge_type: charge_type(csv_row), payment_date: payment_date(csv_row), revenue: revenue(csv_row), + is_yearly_revenue: is_yearly_revenue(csv_row), app_title: app_title(csv_row), shop: shop(csv_row), shop_country: shop_country(csv_row) @@ -109,6 +112,10 @@ def revenue(csv_row) csv_row[:partner_share]&.to_f || 0.0 end + def is_yearly_revenue(csv_row) + csv_row[:charge_type].to_s.include?(CSV_YEARLY_IDENTIFIER) + end + def app_title(csv_row) csv_row[:app_title].presence || Payment::UNKNOWN_APP_TITLE end diff --git a/app/models/import/adaptor/shopify_payments_api.rb b/app/models/import/adaptor/shopify_payments_api.rb index e802273..cf42eaf 100644 --- a/app/models/import/adaptor/shopify_payments_api.rb +++ b/app/models/import/adaptor/shopify_payments_api.rb @@ -90,6 +90,7 @@ def parse(node) charge_type: charge_type(node), payment_date: payment_date(node), revenue: revenue(node), + is_yearly_revenue: is_yearly_revenue(node), app_title: app_title(node), shop: shop(node), # ShopifyPartnerApi does not return shop country @@ -114,6 +115,15 @@ def revenue(node) end || 0.0 end + def is_yearly_revenue(node) + case node.__typename + when "AppSubscriptionSale" + node.billing_interval&.to_s == "ANNUAL" + else + false + end + end + def app_title(node) case node.__typename when "ReferralAdjustment", "ReferralTransaction", "ServiceSale", "ServiceSaleAdjustment" diff --git a/app/models/import/metrics_processor.rb b/app/models/import/metrics_processor.rb index 0cfd536..05bbc46 100644 --- a/app/models/import/metrics_processor.rb +++ b/app/models/import/metrics_processor.rb @@ -20,17 +20,20 @@ def calculate_new_metrics @import_from.upto(@import_to) do |date| metrics = [] Metric::CHARGE_TYPES.each do |charge_type| - app_titles = app_titles_for(date: date, charge_type: charge_type) - next if app_titles.empty? + is_yearly_revenue_intervals_for(charge_type).each do |is_yearly_revenue| + app_titles = app_titles_for(date: date, charge_type: charge_type, is_yearly_revenue: is_yearly_revenue) + next if app_titles.empty? - app_titles.each do |app_title| - calculator = Metric::Calculator.new( - user: @user, - date: date, - charge_type: charge_type, - app_title: app_title - ) - metrics << new_metric_from(calculator: calculator) if calculator.has_metrics? + app_titles.each do |app_title| + calculator = Metric::Calculator.new( + user: @user, + date: date, + charge_type: charge_type, + app_title: app_title, + is_yearly_revenue: is_yearly_revenue + ) + metrics << new_metric_from(calculator: calculator) if calculator.has_metrics? + end end end Metric.import!(metrics, validate: false, no_returning: true) if metrics.present? @@ -38,8 +41,20 @@ def calculate_new_metrics end end - def app_titles_for(date:, charge_type:) - @user.payments.where(payment_date: date, charge_type: charge_type).pluck(:app_title).uniq + def is_yearly_revenue_intervals_for(charge_type) + if Metric::CHARGE_TYPE_CAN_HAVE_YEARLY_INTERVAL[charge_type] + [true, false] + else + [false] + end + end + + def app_titles_for(date:, charge_type:, is_yearly_revenue:) + @user.payments.where( + payment_date: date, + charge_type: charge_type, + is_yearly_revenue: is_yearly_revenue + ).pluck(:app_title).uniq end def new_metric_from(calculator:) @@ -50,6 +65,7 @@ def new_metric_from(calculator:) charge_type: calculator.charge_type, app_title: calculator.app_title, revenue: calculator.revenue, + is_yearly_revenue: calculator.is_yearly_revenue, number_of_charges: calculator.number_of_charges, number_of_shops: calculator.number_of_shops, average_revenue_per_shop: calculator.average_revenue_per_shop, diff --git a/app/models/import/payments_processor.rb b/app/models/import/payments_processor.rb index 4b8996f..6a2e8c2 100644 --- a/app/models/import/payments_processor.rb +++ b/app/models/import/payments_processor.rb @@ -47,6 +47,7 @@ def new_payment(payment) payment_date: payment[:payment_date], charge_type: payment[:charge_type], revenue: payment[:revenue], + is_yearly_revenue: payment[:is_yearly_revenue], app_title: payment[:app_title], shop: payment[:shop], shop_country: payment[:shop_country] diff --git a/app/models/metric.rb b/app/models/metric.rb index a60b30e..e27eb3c 100644 --- a/app/models/metric.rb +++ b/app/models/metric.rb @@ -7,6 +7,15 @@ class Metric < ApplicationRecord CHARGE_TYPES = ["recurring_revenue", "onetime_revenue", "affiliate_revenue", "refund"].freeze + # Used to calculate metrics in groups of not applicable, yearly & monthly + # nil = not applicable, true = yearly, false = monthly + CHARGE_TYPE_CAN_HAVE_YEARLY_INTERVAL = { + "recurring_revenue" => true, + "onetime_revenue" => false, + "affiliate_revenue" => false, + "refund" => false + }.freeze + DISPLAYABLE_TYPES = [ :recurring_revenue, :onetime_revenue, @@ -22,6 +31,10 @@ def by_optional_charge_type(charge_type) charge_type.blank? ? all : where(charge_type: charge_type) end + def by_optional_is_yearly_revenue(is_yearly_revenue) + is_yearly_revenue.nil? ? all : where(is_yearly_revenue: is_yearly_revenue) + end + def by_date_and_period(date:, period:) previous_period = date - period.days + 1 where(metric_date: previous_period.beginning_of_day..date.end_of_day) diff --git a/app/models/metric/calculator.rb b/app/models/metric/calculator.rb index e803db0..48b761f 100644 --- a/app/models/metric/calculator.rb +++ b/app/models/metric/calculator.rb @@ -1,16 +1,20 @@ class Metric::Calculator - MONTHLY_RECURRING_BILLING_FREQUENCY = 30.days - MONTHLY_RECURRING_BILLING_CHURN_WINDOW = 15.days + MONTHLY_BILLING_FREQUENCY = 30.days + MONTHLY_BILLING_CHURN_WINDOW = 15.days - def initialize(user:, date:, charge_type:, app_title:) + YEARLY_BILLING_FREQUENCY = 1.year + YEARLY_BILLING_CHURN_WINDOW = 30.days + + def initialize(user:, date:, charge_type:, app_title:, is_yearly_revenue:) @user = user @date = date @charge_type = charge_type @app_title = app_title - @payments = user.payments.where(payment_date: date, charge_type: charge_type, app_title: app_title) + @is_yearly_revenue = is_yearly_revenue + @payments = payments_by_options_and_date(date) end - attr_reader :user, :date, :charge_type, :app_title, :payments + attr_reader :user, :date, :charge_type, :app_title, :is_yearly_revenue, :payments def has_metrics? payments.any? @@ -82,18 +86,29 @@ def unique_shops end def current_shops - churn_calulation_date_lower_bound = date - (MONTHLY_RECURRING_BILLING_CHURN_WINDOW * 2) - @current_shops ||= user.payments.where(payment_date: churn_calulation_date_lower_bound..date, charge_type: charge_type, app_title: app_title).group_by(&:shop) + @current_shops ||= payments_by_options_and_date(churn_calculations_date_lower_bound..date).group_by(&:shop) end def previous_shops - @previous_shops ||= user.payments.where(payment_date: churn_calculation_date, charge_type: charge_type, app_title: app_title).group_by(&:shop) + @previous_shops ||= payments_by_options_and_date(churn_calculation_date).group_by(&:shop) end def churn_calculation_date # To calculate churn, we need to look at the previous set of payments but also # allow for a lookahead window (due to shifted payment dates). - date - MONTHLY_RECURRING_BILLING_FREQUENCY - MONTHLY_RECURRING_BILLING_CHURN_WINDOW + if is_yearly_revenue == true + date - YEARLY_BILLING_FREQUENCY - YEARLY_BILLING_CHURN_WINDOW + else + date - MONTHLY_BILLING_FREQUENCY - MONTHLY_BILLING_CHURN_WINDOW + end + end + + def churn_calculations_date_lower_bound + if is_yearly_revenue == true + date - (YEARLY_BILLING_CHURN_WINDOW * 2) + else + date - (MONTHLY_BILLING_CHURN_WINDOW * 2) + end end def churned_shops @@ -115,4 +130,13 @@ def not_repeatable_charge_type? def not_churnable_charge_type? charge_type != "recurring_revenue" && charge_type != "affiliate_revenue" end + + def payments_by_options_and_date(date) + user.payments.where( + payment_date: date, + charge_type: charge_type, + app_title: app_title, + is_yearly_revenue: is_yearly_revenue + ) + end end diff --git a/app/models/metric/tile_presenter.rb b/app/models/metric/tile_presenter.rb index dc4c5a6..eb99ff9 100644 --- a/app/models/metric/tile_presenter.rb +++ b/app/models/metric/tile_presenter.rb @@ -8,17 +8,24 @@ def initialize(filter:, tile_config:) @column = tile_config[:column] @display = tile_config[:display] @positive_change_is_good = tile_config[:positive_change_is_good] + @is_yearly_revenue = tile_config[:is_yearly_revenue] end - attr_reader :handle, :display, :calculation, :positive_change_is_good + attr_reader :handle, :display, :calculation, :positive_change_is_good, :is_yearly_revenue def current_value - metrics = @filter.current_period_metrics.by_optional_charge_type(@charge_type) - metrics.calculate_value(@calculation, @column) + metrics = @filter.current_period_metrics + .by_optional_charge_type(@charge_type) + .by_optional_is_yearly_revenue(@is_yearly_revenue) + .calculate_value(@calculation, @column) + metrics.blank? ? 0 : metrics end def previous_value - metrics = @filter.previous_period_metrics.by_optional_charge_type(@charge_type) - metrics.calculate_value(@calculation, @column) + metrics = @filter.previous_period_metrics + .by_optional_charge_type(@charge_type) + .by_optional_is_yearly_revenue(@is_yearly_revenue) + .calculate_value(@calculation, @column) + metrics.blank? ? 0 : metrics end def change @@ -27,13 +34,17 @@ def change end def average_value + return 0 if current_value.blank? current_value / @filter.period end def period_ago_value(period_ago) period_ago_date = @filter.date - (period_ago * @filter.period).days - metrics = @filter.user_metrics_by_app.by_optional_charge_type(@charge_type) - metrics.by_date_and_period(date: period_ago_date, period: @filter.period).calculate_value(@calculation, @column) + @filter.user_metrics_by_app + .by_date_and_period(date: period_ago_date, period: @filter.period) + .by_optional_charge_type(@charge_type) + .by_optional_is_yearly_revenue(@is_yearly_revenue) + .calculate_value(@calculation, @column) end def period_ago_change(period_ago) @@ -66,8 +77,11 @@ def forecast_chart_data(chart_data) def metrics_chart_data @metrics_chart_data ||= begin - metrics = @filter.user_metrics_by_app.by_optional_charge_type(@charge_type) - metrics.chart_data(@filter.date, @filter.period, @calculation, @column).to_h + @filter.user_metrics_by_app + .by_optional_charge_type(@charge_type) + .by_optional_is_yearly_revenue(@is_yearly_revenue) + .chart_data(@filter.date, @filter.period, @calculation, @column) + .to_h end end end diff --git a/app/models/metric/tiles_config.rb b/app/models/metric/tiles_config.rb index 8da1d4c..ba118c3 100644 --- a/app/models/metric/tiles_config.rb +++ b/app/models/metric/tiles_config.rb @@ -6,7 +6,8 @@ module Metric::TilesConfig charge_type: nil, column: :revenue, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil }, { handle: :recurring_revenue, @@ -14,7 +15,8 @@ module Metric::TilesConfig charge_type: :recurring_revenue, column: :revenue, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil }, { handle: :onetime_revenue, @@ -22,7 +24,8 @@ module Metric::TilesConfig charge_type: :onetime_revenue, column: :revenue, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil }, { handle: :affiliate_revenue, @@ -30,7 +33,8 @@ module Metric::TilesConfig charge_type: :affiliate_revenue, column: :revenue, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil }, { handle: :refunds, @@ -38,7 +42,8 @@ module Metric::TilesConfig calculation: :sum, charge_type: :refund, display: :currency, - positive_change_is_good: false + positive_change_is_good: false, + is_yearly_revenue: nil }, { handle: :avg_revenue_per_shop, @@ -46,7 +51,8 @@ module Metric::TilesConfig charge_type: nil, column: :average_revenue_per_shop, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil } ].freeze @@ -57,7 +63,26 @@ module Metric::TilesConfig charge_type: :recurring_revenue, column: :revenue, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil + }, + { + handle: :recurring_revenue_monthly, + calculation: :sum, + charge_type: :recurring_revenue, + column: :revenue, + display: :currency, + positive_change_is_good: true, + is_yearly_revenue: false + }, + { + handle: :recurring_revenue_yearly, + calculation: :sum, + charge_type: :recurring_revenue, + column: :revenue, + display: :currency, + positive_change_is_good: true, + is_yearly_revenue: true }, { handle: :paying_shops, @@ -65,7 +90,26 @@ module Metric::TilesConfig charge_type: :recurring_revenue, column: :number_of_shops, display: :number, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil + }, + { + handle: :paying_shops_monthly, + calculation: :sum, + charge_type: :recurring_revenue, + column: :number_of_shops, + display: :number, + positive_change_is_good: true, + is_yearly_revenue: false + }, + { + handle: :paying_shops_yearly, + calculation: :sum, + charge_type: :recurring_revenue, + column: :number_of_shops, + display: :number, + positive_change_is_good: true, + is_yearly_revenue: true }, { handle: :recurring_avg_revenue_per_shop, @@ -73,7 +117,26 @@ module Metric::TilesConfig charge_type: :recurring_revenue, column: :average_revenue_per_shop, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil + }, + { + handle: :recurring_avg_revenue_per_shop_monthly, + calculation: :average, + charge_type: :recurring_revenue, + column: :average_revenue_per_shop, + display: :currency, + positive_change_is_good: true, + is_yearly_revenue: false + }, + { + handle: :recurring_avg_revenue_per_shop_yearly, + calculation: :average, + charge_type: :recurring_revenue, + column: :average_revenue_per_shop, + display: :currency, + positive_change_is_good: true, + is_yearly_revenue: true }, { handle: :shop_churn, @@ -81,7 +144,26 @@ module Metric::TilesConfig charge_type: :recurring_revenue, column: :shop_churn, display: :percentage, - positive_change_is_good: false + positive_change_is_good: false, + is_yearly_revenue: nil + }, + { + handle: :shop_churn_monthly, + calculation: :average, + charge_type: :recurring_revenue, + column: :shop_churn, + display: :percentage, + positive_change_is_good: false, + is_yearly_revenue: false + }, + { + handle: :shop_churn_yearly, + calculation: :average, + charge_type: :recurring_revenue, + column: :shop_churn, + display: :percentage, + positive_change_is_good: false, + is_yearly_revenue: true }, { handle: :revenue_churn, @@ -89,7 +171,26 @@ module Metric::TilesConfig charge_type: :recurring_revenue, column: :revenue_churn, display: :percentage, - positive_change_is_good: false + positive_change_is_good: false, + is_yearly_revenue: nil + }, + { + handle: :revenue_churn_monthly, + calculation: :average, + charge_type: :recurring_revenue, + column: :revenue_churn, + display: :percentage, + positive_change_is_good: false, + is_yearly_revenue: false + }, + { + handle: :revenue_churn_yearly, + calculation: :average, + charge_type: :recurring_revenue, + column: :revenue_churn, + display: :percentage, + positive_change_is_good: false, + is_yearly_revenue: true }, { handle: :lifetime_value, @@ -97,7 +198,26 @@ module Metric::TilesConfig charge_type: :recurring_revenue, column: :lifetime_value, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil + }, + { + handle: :lifetime_value_monthly, + calculation: :average, + charge_type: :recurring_revenue, + column: :lifetime_value, + display: :currency, + positive_change_is_good: true, + is_yearly_revenue: false + }, + { + handle: :lifetime_value_yearly, + calculation: :average, + charge_type: :recurring_revenue, + column: :lifetime_value, + display: :currency, + positive_change_is_good: true, + is_yearly_revenue: true } ].freeze @@ -108,7 +228,8 @@ module Metric::TilesConfig charge_type: :onetime_revenue, column: :revenue, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil }, { handle: :onetime_avg_revenue_per_charge, @@ -116,7 +237,8 @@ module Metric::TilesConfig charge_type: :onetime_revenue, column: :average_revenue_per_charge, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil }, { handle: :onetime_avg_revenue_per_shop, @@ -124,7 +246,8 @@ module Metric::TilesConfig charge_type: :onetime_revenue, column: :average_revenue_per_shop, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil }, { handle: :number_of_sales, @@ -132,7 +255,8 @@ module Metric::TilesConfig charge_type: :onetime_revenue, column: :number_of_charges, display: :number, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil }, { handle: :repeat_customers, @@ -140,7 +264,8 @@ module Metric::TilesConfig charge_type: :onetime_revenue, column: :repeat_customers, display: :number, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil }, { handle: :repeat_vs_new_customers, @@ -148,7 +273,8 @@ module Metric::TilesConfig charge_type: :onetime_revenue, column: :repeat_vs_new_customers, display: :percentage, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil } ].freeze @@ -159,7 +285,8 @@ module Metric::TilesConfig charge_type: :affiliate_revenue, column: :revenue, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil }, { handle: :affiliate_number_of_charges, @@ -167,7 +294,8 @@ module Metric::TilesConfig charge_type: :affiliate_revenue, column: :number_of_charges, display: :number, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil }, { handle: :affiliate_avg_revenue_per_shop, @@ -175,7 +303,8 @@ module Metric::TilesConfig charge_type: :affiliate_revenue, column: :average_revenue_per_shop, display: :currency, - positive_change_is_good: true + positive_change_is_good: true, + is_yearly_revenue: nil } ].freeze end diff --git a/app/views/metrics/show.html.erb b/app/views/metrics/show.html.erb index ce26fef..97b71c7 100644 --- a/app/views/metrics/show.html.erb +++ b/app/views/metrics/show.html.erb @@ -5,15 +5,10 @@ <%= render "shared/page_actions", page: page %> - <%= turbo_frame_tag :metrics, data: { - #controller: "chartkick", - #action: "turbo:frame-load->chartkick#createChart", - } do %> + <%= turbo_frame_tag :metrics do %> <%= polaris_vertical_stack(gap: "5") do %> - <%= @filter.charge_type %> - <%= polaris_stack(alignment: :center) do |stack| %> <% stack.with_item(fill:true) do %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 5732b39..c435c21 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -62,8 +62,25 @@ en: title: "Affiliate" subtitle: "Revenue from the partner affiliate program." tiles: + recurring_revenue: "Total subscription revenue" + recurring_revenue_monthly: "Monthly subscription revenue" + recurring_revenue_yearly: "Annual subscription revenue" + paying_shops: "Total subscriptions" + paying_shops_monthly: "Monthly subscriptions" + paying_shops_yearly: "Annual subscriptions" recurring_avg_revenue_per_charge: "Avg. revenue per charge" - recurring_avg_revenue_per_shop: "Avg. revenue per shop" + recurring_avg_revenue_per_shop: "Avg. subscription payment" + recurring_avg_revenue_per_shop_monthly: "Avg. monthly payment" + recurring_avg_revenue_per_shop_yearly: "Avg. annual payment" + shop_churn: "Subscription churn" + shop_churn_monthly: "Monthly subscription churn" + shop_churn_yearly: "Annual subscription churn" + revenue_churn: "Revenue churn" + revenue_churn_monthly: "Monthly revenue churn" + revenue_churn_yearly: "Annual revenue churn" + lifetime_value: "Total LTV" + lifetime_value_monthly: "Monthly subscription LTV" + lifetime_value_yearly: "Annual subscription LTV" onetime_avg_revenue_per_charge: "Avg. revenue per charge" onetime_avg_revenue_per_shop: "Avg. revenue per shop" affiliate_avg_revenue_per_charge: "Avg. revenue per charge" diff --git a/db/migrate/20230919173404_add_is_yearly_revenue.rb b/db/migrate/20230919173404_add_is_yearly_revenue.rb new file mode 100644 index 0000000..e515bb8 --- /dev/null +++ b/db/migrate/20230919173404_add_is_yearly_revenue.rb @@ -0,0 +1,6 @@ +class AddIsYearlyRevenue < ActiveRecord::Migration[7.0] + def change + add_column :payments, :is_yearly_revenue, :boolean, default: false + add_column :metrics, :is_yearly_revenue, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 6a253ad..d52853d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_09_14_091554) do +ActiveRecord::Schema[7.0].define(version: 2023_09_19_173404) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -69,6 +69,7 @@ t.datetime "updated_at", precision: nil t.integer "user_id" t.bigint "import_id" + t.boolean "is_yearly_revenue", default: false t.index ["import_id"], name: "index_metrics_on_import_id" t.index ["metric_date"], name: "index_metrics_on_metric_date" t.index ["user_id", "app_title"], name: "index_metrics_on_user_id_and_app_title" @@ -98,6 +99,7 @@ t.integer "user_id" t.string "shop_country" t.bigint "import_id" + t.boolean "is_yearly_revenue", default: false t.index ["import_id"], name: "index_payments_on_import_id" t.index ["payment_date"], name: "index_payments_on_payment_date" t.index ["user_id", "app_title"], name: "index_payments_on_user_id_and_app_title"