diff --git a/.github/workflows/plugin-linting.yml b/.github/workflows/plugin-linting.yml index c807794..6d2bb97 100644 --- a/.github/workflows/plugin-linting.yml +++ b/.github/workflows/plugin-linting.yml @@ -55,3 +55,12 @@ jobs: - name: Rubocop if: ${{ !cancelled() }} run: bundle exec rubocop . + + - name: Syntax Tree + if: ${{ !cancelled() }} + run: | + if test -f .streerc; then + bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake') + else + echo "Stree config not detected for this repository. Skipping." + fi diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 9d390bc..f30a5be 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -80,7 +80,7 @@ jobs: - name: Get yarn cache directory id: yarn-cache-dir - run: echo "::set-output name=dir::$(yarn cache dir)" + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - name: Yarn cache uses: actions/cache@v3 @@ -130,7 +130,7 @@ jobs: shell: bash run: | if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/spec -type f -name "*.rb" 2> /dev/null | wc -l) ]; then - echo "::set-output name=files_exist::true" + echo "files_exist=true" >> $GITHUB_OUTPUT fi - name: Plugin RSpec @@ -142,7 +142,7 @@ jobs: shell: bash run: | if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/test/javascripts -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then - echo "::set-output name=files_exist::true" + echo "files_exist=true" >> $GITHUB_OUTPUT fi - name: Plugin QUnit diff --git a/.rubocop.yml b/.rubocop.yml index 8cdf035..4be6fc5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,5 @@ inherit_gem: - rubocop-discourse: default.yml + rubocop-discourse: stree-compat.yml AllCops: Exclude: - "gems/**/*" diff --git a/.streerc b/.streerc new file mode 100644 index 0000000..0bc4379 --- /dev/null +++ b/.streerc @@ -0,0 +1,2 @@ +--print-width=100 +--plugins=plugin/trailing_comma diff --git a/Gemfile b/Gemfile index 85c1173..8bb1dfc 100644 --- a/Gemfile +++ b/Gemfile @@ -1,8 +1,9 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" group :development do - gem 'translations-manager', git: 'https://github.com/discourse/translations-manager.git' - gem 'rubocop-discourse' + gem "translations-manager", git: "https://github.com/discourse/translations-manager.git" + gem "rubocop-discourse" + gem "syntax_tree" end diff --git a/Gemfile.lock b/Gemfile.lock index 281f2a5..8e05f5a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,6 +12,7 @@ GEM parallel (1.22.1) parser (3.1.2.1) ast (~> 2.4.1) + prettier_print (1.2.0) rainbow (3.1.1) regexp_parser (2.6.0) rexml (3.2.5) @@ -33,6 +34,8 @@ GEM rubocop-rspec (2.13.2) rubocop (~> 1.33) ruby-progressbar (1.11.0) + syntax_tree (5.1.0) + prettier_print (>= 1.2.0) unicode-display_width (2.3.0) PLATFORMS @@ -40,6 +43,7 @@ PLATFORMS DEPENDENCIES rubocop-discourse + syntax_tree translations-manager! BUNDLED WITH diff --git a/app/controllers/concerns/stripe.rb b/app/controllers/concerns/stripe.rb index 04023f7..bbe8bbe 100644 --- a/app/controllers/concerns/stripe.rb +++ b/app/controllers/concerns/stripe.rb @@ -9,7 +9,8 @@ module DiscourseSubscriptions end def is_stripe_configured? - SiteSetting.discourse_subscriptions_public_key.present? && SiteSetting.discourse_subscriptions_secret_key.present? + SiteSetting.discourse_subscriptions_public_key.present? && + SiteSetting.discourse_subscriptions_secret_key.present? end end end diff --git a/app/controllers/discourse_subscriptions/admin/coupons_controller.rb b/app/controllers/discourse_subscriptions/admin/coupons_controller.rb index 072a5be..80fc551 100644 --- a/app/controllers/discourse_subscriptions/admin/coupons_controller.rb +++ b/app/controllers/discourse_subscriptions/admin/coupons_controller.rb @@ -18,23 +18,24 @@ module DiscourseSubscriptions end def create - params.require([:promo, :discount_type, :discount, :active]) + params.require(%i[promo discount_type discount active]) begin - coupon_params = { - duration: 'forever', - } + coupon_params = { duration: "forever" } case params[:discount_type] - when 'amount' + when "amount" coupon_params[:amount_off] = params[:discount].to_i * 100 coupon_params[:currency] = SiteSetting.discourse_subscriptions_currency - when 'percent' + when "percent" coupon_params[:percent_off] = params[:discount] end coupon = ::Stripe::Coupon.create(coupon_params) - promo_code = ::Stripe::PromotionCode.create({ coupon: coupon[:id], code: params[:promo] }) if coupon.present? + promo_code = + ::Stripe::PromotionCode.create( + { coupon: coupon[:id], code: params[:promo] }, + ) if coupon.present? render_json_dump promo_code rescue ::Stripe::InvalidRequestError => e @@ -43,14 +44,9 @@ module DiscourseSubscriptions end def update - params.require([:id, :active]) + params.require(%i[id active]) begin - promo_code = ::Stripe::PromotionCode.update( - params[:id], - { - active: params[:active] - } - ) + promo_code = ::Stripe::PromotionCode.update(params[:id], { active: params[:active] }) render_json_dump promo_code rescue ::Stripe::InvalidRequestError => e diff --git a/app/controllers/discourse_subscriptions/admin/plans_controller.rb b/app/controllers/discourse_subscriptions/admin/plans_controller.rb index e0024ff..354de83 100644 --- a/app/controllers/discourse_subscriptions/admin/plans_controller.rb +++ b/app/controllers/discourse_subscriptions/admin/plans_controller.rb @@ -27,15 +27,11 @@ module DiscourseSubscriptions active: params[:active], metadata: { group_name: params[:metadata][:group_name], - trial_period_days: params[:trial_period_days] - } + trial_period_days: params[:trial_period_days], + }, } - if params[:type] == 'recurring' - price_object[:recurring] = { - interval: params[:interval] - } - end + price_object[:recurring] = { interval: params[:interval] } if params[:type] == "recurring" plan = ::Stripe::Price.create(price_object) @@ -56,14 +52,16 @@ module DiscourseSubscriptions end interval = nil - if plan[:recurring] && plan[:recurring][:interval] - interval = plan[:recurring][:interval] - end + interval = plan[:recurring][:interval] if plan[:recurring] && plan[:recurring][:interval] - serialized = plan.to_h.merge(trial_period_days: trial_days, currency: plan[:currency].upcase, interval: interval) + serialized = + plan.to_h.merge( + trial_period_days: trial_days, + currency: plan[:currency].upcase, + interval: interval, + ) render_json_dump serialized - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -71,15 +69,16 @@ module DiscourseSubscriptions def update begin - plan = ::Stripe::Price.update( - params[:id], - nickname: params[:nickname], - active: params[:active], - metadata: { - group_name: params[:metadata][:group_name], - trial_period_days: params[:trial_period_days] - } - ) + plan = + ::Stripe::Price.update( + params[:id], + nickname: params[:nickname], + active: params[:active], + metadata: { + group_name: params[:metadata][:group_name], + trial_period_days: params[:trial_period_days], + }, + ) render_json_dump plan rescue ::Stripe::InvalidRequestError => e diff --git a/app/controllers/discourse_subscriptions/admin/products_controller.rb b/app/controllers/discourse_subscriptions/admin/products_controller.rb index 4fede95..9a3e026 100644 --- a/app/controllers/discourse_subscriptions/admin/products_controller.rb +++ b/app/controllers/discourse_subscriptions/admin/products_controller.rb @@ -27,20 +27,15 @@ module DiscourseSubscriptions def create begin - create_params = product_params.merge!(type: 'service') + create_params = product_params.merge!(type: "service") - if params[:statement_descriptor].blank? - create_params.except!(:statement_descriptor) - end + create_params.except!(:statement_descriptor) if params[:statement_descriptor].blank? product = ::Stripe::Product.create(create_params) - Product.create( - external_id: product[:id] - ) + Product.create(external_id: product[:id]) render_json_dump product - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -51,7 +46,6 @@ module DiscourseSubscriptions product = ::Stripe::Product.retrieve(params[:id]) render_json_dump product - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -59,13 +53,9 @@ module DiscourseSubscriptions def update begin - product = ::Stripe::Product.update( - params[:id], - product_params - ) + product = ::Stripe::Product.update(params[:id], product_params) render_json_dump product - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -78,7 +68,6 @@ module DiscourseSubscriptions Product.delete_by(external_id: params[:id]) render_json_dump product - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -95,8 +84,8 @@ module DiscourseSubscriptions statement_descriptor: params[:statement_descriptor], metadata: { description: params.dig(:metadata, :description), - repurchaseable: params.dig(:metadata, :repurchaseable) - } + repurchaseable: params.dig(:metadata, :repurchaseable), + }, } end end diff --git a/app/controllers/discourse_subscriptions/admin/subscriptions_controller.rb b/app/controllers/discourse_subscriptions/admin/subscriptions_controller.rb index 8498c37..d4c529d 100644 --- a/app/controllers/discourse_subscriptions/admin/subscriptions_controller.rb +++ b/app/controllers/discourse_subscriptions/admin/subscriptions_controller.rb @@ -16,20 +16,23 @@ module DiscourseSubscriptions has_more: false, data: [], length: 0, - last_record: params[:last_record] + last_record: params[:last_record], } if subscription_ids.present? && is_stripe_configured? while subscriptions[:length] < PAGE_LIMIT current_set = get_subscriptions(subscriptions[:last_record]) - until valid_subscriptions = find_valid_subscriptions(current_set[:data], subscription_ids) do + until valid_subscriptions = + find_valid_subscriptions(current_set[:data], subscription_ids) current_set = get_subscriptions(current_set[:data].last) break if current_set[:has_more] == false end subscriptions[:data] = subscriptions[:data].concat(valid_subscriptions.to_a) - subscriptions[:last_record] = current_set[:data].last[:id] if current_set[:data].present? + subscriptions[:last_record] = current_set[:data].last[:id] if current_set[ + :data + ].present? subscriptions[:length] = subscriptions[:data].length subscriptions[:has_more] = current_set[:has_more] break if subscriptions[:has_more] == false @@ -50,10 +53,11 @@ module DiscourseSubscriptions refund_subscription(params[:id]) if params[:refund] subscription = ::Stripe::Subscription.delete(params[:id]) - customer = Customer.find_by( - product_id: subscription[:plan][:product], - customer_id: subscription[:customer] - ) + customer = + Customer.find_by( + product_id: subscription[:plan][:product], + customer_id: subscription[:customer], + ) Subscription.delete_by(external_id: params[:id]) @@ -65,7 +69,6 @@ module DiscourseSubscriptions end render_json_dump subscription - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -74,7 +77,11 @@ module DiscourseSubscriptions private def get_subscriptions(start) - ::Stripe::Subscription.list(expand: ['data.plan.product'], limit: PAGE_LIMIT, starting_after: start) + ::Stripe::Subscription.list( + expand: ["data.plan.product"], + limit: PAGE_LIMIT, + starting_after: start, + ) end def find_valid_subscriptions(data, ids) @@ -85,11 +92,11 @@ module DiscourseSubscriptions # this will only refund the most recent subscription payment def refund_subscription(subscription_id) subscription = ::Stripe::Subscription.retrieve(subscription_id) - invoice = ::Stripe::Invoice.retrieve(subscription[:latest_invoice]) if subscription[:latest_invoice] + invoice = ::Stripe::Invoice.retrieve(subscription[:latest_invoice]) if subscription[ + :latest_invoice + ] payment_intent = invoice[:payment_intent] if invoice[:payment_intent] - refund = ::Stripe::Refund.create({ - payment_intent: payment_intent, - }) + refund = ::Stripe::Refund.create({ payment_intent: payment_intent }) end end end diff --git a/app/controllers/discourse_subscriptions/hooks_controller.rb b/app/controllers/discourse_subscriptions/hooks_controller.rb index e0ae0e3..f020c01 100644 --- a/app/controllers/discourse_subscriptions/hooks_controller.rb +++ b/app/controllers/discourse_subscriptions/hooks_controller.rb @@ -14,7 +14,7 @@ module DiscourseSubscriptions def create begin payload = request.body.read - sig_header = request.env['HTTP_STRIPE_SIGNATURE'] + sig_header = request.env["HTTP_STRIPE_SIGNATURE"] webhook_secret = SiteSetting.discourse_subscriptions_webhook_secret event = ::Stripe::Webhook.construct_event(payload, sig_header, webhook_secret) @@ -25,37 +25,39 @@ module DiscourseSubscriptions end case event[:type] - when 'customer.subscription.created' - when 'customer.subscription.updated' - customer = Customer.find_by( - customer_id: event[:data][:object][:customer], - product_id: event[:data][:object][:plan][:product], - ) + when "customer.subscription.created" + when "customer.subscription.updated" + customer = + Customer.find_by( + customer_id: event[:data][:object][:customer], + product_id: event[:data][:object][:plan][:product], + ) - return render_json_error 'customer not found' if !customer - return head 200 if event[:data][:object][:status] != 'complete' + return render_json_error "customer not found" if !customer + return head 200 if event[:data][:object][:status] != "complete" user = ::User.find_by(id: customer.user_id) - return render_json_error 'user not found' if !user + return render_json_error "user not found" if !user if group = plan_group(event[:data][:object][:plan]) group.add(user) end - when 'customer.subscription.deleted' - customer = Customer.find_by( - customer_id: event[:data][:object][:customer], - product_id: event[:data][:object][:plan][:product], - ) + when "customer.subscription.deleted" + customer = + Customer.find_by( + customer_id: event[:data][:object][:customer], + product_id: event[:data][:object][:plan][:product], + ) - return render_json_error 'customer not found' if !customer + return render_json_error "customer not found" if !customer Subscription.find_by( customer_id: customer.id, - external_id: event[:data][:object][:id] + external_id: event[:data][:object][:id], )&.destroy! user = ::User.find(customer.user_id) - return render_json_error 'user not found' if !user + return render_json_error "user not found" if !user if group = plan_group(event[:data][:object][:plan]) group.remove(user) diff --git a/app/controllers/discourse_subscriptions/subscribe_controller.rb b/app/controllers/discourse_subscriptions/subscribe_controller.rb index c38805e..1dcaf28 100644 --- a/app/controllers/discourse_subscriptions/subscribe_controller.rb +++ b/app/controllers/discourse_subscriptions/subscribe_controller.rb @@ -5,7 +5,7 @@ module DiscourseSubscriptions include DiscourseSubscriptions::Stripe include DiscourseSubscriptions::Group before_action :set_api_key - requires_login except: [:index, :contributors, :show] + requires_login except: %i[index contributors show] def index begin @@ -13,19 +13,12 @@ module DiscourseSubscriptions products = [] if product_ids.present? && is_stripe_configured? - response = ::Stripe::Product.list({ - ids: product_ids, - active: true - }) - - products = response[:data].map do |p| - serialize_product(p) - end + response = ::Stripe::Product.list({ ids: product_ids, active: true }) + products = response[:data].map { |p| serialize_product(p) } end render_json_dump products - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -36,7 +29,11 @@ module DiscourseSubscriptions contributor_ids = Set.new campaign_product = SiteSetting.discourse_subscriptions_campaign_product - campaign_product.present? ? contributor_ids.merge(Customer.where(product_id: campaign_product).last(5).pluck(:user_id)) : contributor_ids.merge(Customer.last(5).pluck(:user_id)) + if campaign_product.present? + contributor_ids.merge(Customer.where(product_id: campaign_product).last(5).pluck(:user_id)) + else + contributor_ids.merge(Customer.last(5).pluck(:user_id)) + end contributors = ::User.where(id: contributor_ids) @@ -49,10 +46,7 @@ module DiscourseSubscriptions product = ::Stripe::Product.retrieve(params[:id]) plans = ::Stripe::Price.list(active: true, product: params[:id]) - response = { - product: serialize_product(product), - plans: serialize_plans(plans) - } + response = { product: serialize_product(product), plans: serialize_plans(plans) } render_json_dump response rescue ::Stripe::InvalidRequestError => e @@ -61,7 +55,7 @@ module DiscourseSubscriptions end def create - params.require([:source, :plan]) + params.require(%i[source plan]) begin customer = find_or_create_customer(params[:source]) plan = ::Stripe::Price.retrieve(params[:plan]) @@ -70,38 +64,46 @@ module DiscourseSubscriptions promo_code = ::Stripe::PromotionCode.list({ code: params[:promo] }) promo_code = promo_code[:data][0] # we assume promo codes have a unique name - return render_json_error I18n.t("js.discourse_subscriptions.subscribe.invalid_coupon") if promo_code.blank? + if promo_code.blank? + return render_json_error I18n.t("js.discourse_subscriptions.subscribe.invalid_coupon") + end end - recurring_plan = plan[:type] == 'recurring' + recurring_plan = plan[:type] == "recurring" if recurring_plan - trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] && plan[:metadata][:trial_period_days] + trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] && + plan[:metadata][:trial_period_days] promo_code_id = promo_code[:id] if promo_code - transaction = ::Stripe::Subscription.create( - customer: customer[:id], - items: [{ price: params[:plan] }], - metadata: metadata_user, - trial_period_days: trial_days, - promotion_code: promo_code_id - ) + transaction = + ::Stripe::Subscription.create( + customer: customer[:id], + items: [{ price: params[:plan] }], + metadata: metadata_user, + trial_period_days: trial_days, + promotion_code: promo_code_id, + ) - payment_intent = retrieve_payment_intent(transaction[:latest_invoice]) if transaction[:status] == 'incomplete' + payment_intent = retrieve_payment_intent(transaction[:latest_invoice]) if transaction[ + :status + ] == "incomplete" else - coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] && promo_code[:coupon][:id] - invoice_item = ::Stripe::InvoiceItem.create( - customer: customer[:id], - price: params[:plan], - discounts: [{ coupon: coupon_id }] - ) - invoice = ::Stripe::Invoice.create( - customer: customer[:id] - ) + coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] && + promo_code[:coupon][:id] + invoice_item = + ::Stripe::InvoiceItem.create( + customer: customer[:id], + price: params[:plan], + discounts: [{ coupon: coupon_id }], + ) + invoice = ::Stripe::Invoice.create(customer: customer[:id]) transaction = ::Stripe::Invoice.finalize_invoice(invoice[:id]) - payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] == 'open' - transaction = ::Stripe::Invoice.pay(invoice[:id]) if payment_intent[:status] == 'successful' + payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] == + "open" + transaction = ::Stripe::Invoice.pay(invoice[:id]) if payment_intent[:status] == + "successful" end finalize_transaction(transaction, plan) if transaction_ok(transaction) @@ -115,7 +117,7 @@ module DiscourseSubscriptions end def finalize - params.require([:plan, :transaction]) + params.require(%i[plan transaction]) begin price = ::Stripe::Price.retrieve(params[:plan]) transaction = retrieve_transaction(params[:transaction]) @@ -132,17 +134,15 @@ module DiscourseSubscriptions group.add(current_user) if group - customer = Customer.create( - user_id: current_user.id, - customer_id: transaction[:customer], - product_id: plan[:product] - ) - - if transaction[:object] == 'subscription' - Subscription.create( - customer_id: customer.id, - external_id: transaction[:id] + customer = + Customer.create( + user_id: current_user.id, + customer_id: transaction[:customer], + product_id: plan[:product], ) + + if transaction[:object] == "subscription" + Subscription.create(customer_id: customer.id, external_id: transaction[:id]) end end @@ -154,23 +154,20 @@ module DiscourseSubscriptions name: product[:name], description: PrettyText.cook(product[:metadata][:description]), subscribed: current_user_products.include?(product[:id]), - repurchaseable: product[:metadata][:repurchaseable] + repurchaseable: product[:metadata][:repurchaseable], } end def current_user_products return [] if current_user.nil? - Customer - .select(:product_id) - .where(user_id: current_user.id) - .map { |c| c.product_id }.compact + Customer.select(:product_id).where(user_id: current_user.id).map { |c| c.product_id }.compact end def serialize_plans(plans) - plans[:data].map do |plan| - plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring) - end.sort_by { |plan| plan[:amount] } + plans[:data] + .map { |plan| plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring) } + .sort_by { |plan| plan[:amount] } end def find_or_create_customer(source) @@ -179,10 +176,7 @@ module DiscourseSubscriptions if customer.present? ::Stripe::Customer.retrieve(customer.customer_id) else - ::Stripe::Customer.create( - email: current_user.email, - source: source - ) + ::Stripe::Customer.create(email: current_user.email, source: source) end end diff --git a/app/controllers/discourse_subscriptions/user/payments_controller.rb b/app/controllers/discourse_subscriptions/user/payments_controller.rb index b84668d..136933a 100644 --- a/app/controllers/discourse_subscriptions/user/payments_controller.rb +++ b/app/controllers/discourse_subscriptions/user/payments_controller.rb @@ -22,7 +22,8 @@ module DiscourseSubscriptions invoices_with_products = parse_invoices(all_invoices, product_ids) invoice_ids = invoices_with_products.map { |invoice| invoice[:id] } payments = ::Stripe::PaymentIntent.list(customer: customer_id) - payments_from_invoices = payments[:data].select { |payment| invoice_ids.include?(payment[:invoice]) } + payments_from_invoices = + payments[:data].select { |payment| invoice_ids.include?(payment[:invoice]) } data = data | payments_from_invoices end end @@ -30,7 +31,6 @@ module DiscourseSubscriptions data = data.sort_by { |pmt| pmt[:created] }.reverse render_json_dump data - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -39,16 +39,19 @@ 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] - invoice_product_id = parse_invoice_lines(invoice_lines) - product_ids.include?(invoice_product_id) - end + invoices_with_products = + all_invoices[:data].select do |invoice| + invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data] + invoice_product_id = parse_invoice_lines(invoice_lines) + product_ids.include?(invoice_product_id) + end end def parse_invoice_lines(invoice_lines) - invoice_product_id = invoice_lines[:price][:product] if invoice_lines[:price] && invoice_lines[:price][:product] - invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] && invoice_lines[:plan][:product] + invoice_product_id = invoice_lines[:price][:product] if invoice_lines[:price] && + invoice_lines[:price][:product] + invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] && + invoice_lines[:plan][:product] invoice_product_id end end diff --git a/app/controllers/discourse_subscriptions/user/subscriptions_controller.rb b/app/controllers/discourse_subscriptions/user/subscriptions_controller.rb index 0e86e6a..c8b5721 100644 --- a/app/controllers/discourse_subscriptions/user/subscriptions_controller.rb +++ b/app/controllers/discourse_subscriptions/user/subscriptions_controller.rb @@ -12,24 +12,21 @@ module DiscourseSubscriptions begin customer = Customer.where(user_id: current_user.id) customer_ids = customer.map { |c| c.id } if customer - subscription_ids = Subscription.where("customer_id in (?)", customer_ids).pluck(:external_id) if customer_ids + subscription_ids = + Subscription.where("customer_id in (?)", customer_ids).pluck( + :external_id, + ) if customer_ids subscriptions = [] if subscription_ids - plans = ::Stripe::Price.list( - expand: ['data.product'], - limit: 100 - ) + plans = ::Stripe::Price.list(expand: ["data.product"], limit: 100) - customers = ::Stripe::Customer.list( - email: current_user.email, - expand: ['data.subscriptions'] - ) + customers = + ::Stripe::Customer.list(email: current_user.email, expand: ["data.subscriptions"]) - subscriptions = customers[:data].map do |sub_customer| - sub_customer[:subscriptions][:data] - end.flatten(1) + subscriptions = + customers[:data].map { |sub_customer| sub_customer[:subscriptions][:data] }.flatten(1) subscriptions = subscriptions.select { |sub| subscription_ids.include?(sub[:id]) } @@ -41,7 +38,6 @@ module DiscourseSubscriptions end render_json_dump subscriptions - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -51,14 +47,13 @@ module DiscourseSubscriptions # we cancel but don't remove until the end of the period # full removal is done via webhooks begin - subscription = ::Stripe::Subscription.update(params[:id], { cancel_at_period_end: true, }) + subscription = ::Stripe::Subscription.update(params[:id], { cancel_at_period_end: true }) if subscription render_json_dump subscription else - render_json_error I18n.t('discourse_subscriptions.customer_not_found') + render_json_error I18n.t("discourse_subscriptions.customer_not_found") end - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -70,7 +65,11 @@ module DiscourseSubscriptions subscription = Subscription.where(external_id: params[:id]).first begin attach_method_to_customer(subscription.customer_id, params[:payment_method]) - subscription = ::Stripe::Subscription.update(params[:id], { default_payment_method: params[:payment_method] }) + subscription = + ::Stripe::Subscription.update( + params[:id], + { default_payment_method: params[:payment_method] }, + ) render json: success_json rescue ::Stripe::InvalidRequestError render_json_error I18n.t("discourse_subscriptions.card.invalid") @@ -81,12 +80,7 @@ module DiscourseSubscriptions def attach_method_to_customer(customer_id, method) customer = Customer.find(customer_id) - ::Stripe::PaymentMethod.attach( - method, - { - customer: customer.customer_id - } - ) + ::Stripe::PaymentMethod.attach(method, { customer: customer.customer_id }) end end end diff --git a/app/jobs/regular/manually_update_campaign_data.rb b/app/jobs/regular/manually_update_campaign_data.rb index 954b90a..7a3cc17 100644 --- a/app/jobs/regular/manually_update_campaign_data.rb +++ b/app/jobs/regular/manually_update_campaign_data.rb @@ -2,7 +2,6 @@ module ::Jobs class ManuallyUpdateCampaignData < ::Jobs::Base - def execute(args) return unless SiteSetting.discourse_subscriptions_campaign_enabled DiscourseSubscriptions::Campaign.new.refresh_data diff --git a/app/serializers/discourse_subscriptions/payment_serializer.rb b/app/serializers/discourse_subscriptions/payment_serializer.rb index 8f6ee15..7a102a8 100644 --- a/app/serializers/discourse_subscriptions/payment_serializer.rb +++ b/app/serializers/discourse_subscriptions/payment_serializer.rb @@ -2,14 +2,14 @@ module DiscourseSubscriptions class PaymentSerializer < ApplicationSerializer - attributes :payment_intent_id, - :receipt_email, - :url, - :created_at_age, - :amount, - :amount_currency, - :username, - :user_id + attributes :payment_intent_id, + :receipt_email, + :url, + :created_at_age, + :amount, + :amount_currency, + :username, + :user_id def created_at_age Time.now - object.created_at @@ -19,7 +19,7 @@ module DiscourseSubscriptions ActiveSupport::NumberHelper.number_to_currency( object.amount / 100, precision: 2, - unit: currency_unit + unit: currency_unit, ) end @@ -32,7 +32,7 @@ module DiscourseSubscriptions def user begin User.find(object.user_id) - rescue + rescue StandardError nil end end diff --git a/app/services/discourse_subscriptions/campaign.rb b/app/services/discourse_subscriptions/campaign.rb index 3209066..16aec62 100644 --- a/app/services/discourse_subscriptions/campaign.rb +++ b/app/services/discourse_subscriptions/campaign.rb @@ -23,9 +23,7 @@ module DiscourseSubscriptions # 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 + 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 @@ -57,7 +55,7 @@ module DiscourseSubscriptions protected def goal_met_date_key - 'subscriptions_goal_met_date' + "subscriptions_goal_met_date" end def check_goal_status @@ -91,9 +89,9 @@ module DiscourseSubscriptions 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" + full_name: I18n.t("js.discourse_subscriptions.campaign.supporters"), + title: I18n.t("js.discourse_subscriptions.campaign.supporter"), + flair_icon: "donate", } group.update(params) @@ -104,11 +102,11 @@ module DiscourseSubscriptions def create_campaign_product product_params = { - name: I18n.t('js.discourse_subscriptions.campaign.title'), + name: I18n.t("js.discourse_subscriptions.campaign.title"), active: true, metadata: { - description: I18n.t('js.discourse_subscriptions.campaign.body'), - } + description: I18n.t("js.discourse_subscriptions.campaign.body"), + }, } product = ::Stripe::Product.create(product_params) @@ -123,13 +121,9 @@ module DiscourseSubscriptions monthly_prices = [3, 5, 10, 25] yearly_prices = [50, 100] - monthly_prices.each do |price| - create_price(product[:id], group, price, "month") - end + monthly_prices.each { |price| create_price(product[:id], group, price, "month") } - yearly_prices.each do |price| - create_price(product[:id], group, price, "year") - end + yearly_prices.each { |price| create_price(product[:id], group, price, "year") } end def create_price(product_id, group_name, amount, recurrence) @@ -140,11 +134,11 @@ module DiscourseSubscriptions currency: SiteSetting.discourse_subscriptions_currency, active: true, recurring: { - interval: recurrence + interval: recurrence, }, metadata: { - group_name: group_name - } + group_name: group_name, + }, } plan = ::Stripe::Price.create(price_object) @@ -152,18 +146,13 @@ module DiscourseSubscriptions def get_one_time_payments(product_ids) one_time_payments = [] - current_set = { - has_more: true, - last_record: nil - } + 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] - ) + 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] @@ -173,11 +162,8 @@ module DiscourseSubscriptions 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 - + 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 = { @@ -197,17 +183,15 @@ module DiscourseSubscriptions def get_subscription_data subscriptions = [] - current_set = { - has_more: true, - last_record: nil - } + 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 = + ::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) @@ -217,13 +201,14 @@ module DiscourseSubscriptions 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] + 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 + ids.include?(product) + end valid.empty? ? nil : valid end diff --git a/config/routes.rb b/config/routes.rb index dcb0674..3d2da42 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,31 +2,31 @@ require_dependency "subscriptions_user_constraint" DiscourseSubscriptions::Engine.routes.draw do - scope 'admin' do - get '/' => 'admin#index' - post '/refresh' => 'admin#refresh_campaign' - post '/create-campaign' => 'admin#create_campaign' + scope "admin" do + get "/" => "admin#index" + post "/refresh" => "admin#refresh_campaign" + post "/create-campaign" => "admin#create_campaign" end namespace :admin, constraints: AdminConstraint.new do resources :plans - resources :subscriptions, only: [:index, :destroy] + resources :subscriptions, only: %i[index destroy] resources :products - resources :coupons, only: [:index, :create] - resource :coupons, only: [:destroy, :update] + resources :coupons, only: %i[index create] + resource :coupons, only: %i[destroy update] end namespace :user do resources :payments, only: [:index] - resources :subscriptions, only: [:index, :update, :destroy] + resources :subscriptions, only: %i[index update destroy] end - get '/' => 'subscribe#index' - get '.json' => 'subscribe#index' - get '/contributors' => 'subscribe#contributors' - get '/:id' => 'subscribe#show' - post '/create' => 'subscribe#create' - post '/finalize' => 'subscribe#finalize' + get "/" => "subscribe#index" + get ".json" => "subscribe#index" + get "/contributors" => "subscribe#contributors" + get "/:id" => "subscribe#show" + post "/create" => "subscribe#create" + post "/finalize" => "subscribe#finalize" - post '/hooks' => 'hooks#create' + post "/hooks" => "hooks#create" end diff --git a/lib/discourse_subscriptions/engine.rb b/lib/discourse_subscriptions/engine.rb index d1d3baf..73a2521 100644 --- a/lib/discourse_subscriptions/engine.rb +++ b/lib/discourse_subscriptions/engine.rb @@ -2,7 +2,7 @@ module ::DiscourseSubscriptions class Engine < ::Rails::Engine - engine_name 'discourse-subscriptions' + engine_name "discourse-subscriptions" isolate_namespace DiscourseSubscriptions end end diff --git a/lib/tasks/subscriptions_import.rake b/lib/tasks/subscriptions_import.rake index cc50b64..9c78ab7 100644 --- a/lib/tasks/subscriptions_import.rake +++ b/lib/tasks/subscriptions_import.rake @@ -1,23 +1,23 @@ # frozen_string_literal: true -require 'stripe' -require 'highline/import' +require "stripe" +require "highline/import" -desc 'Import subscriptions from Stripe' -task 'subscriptions:subscriptions_import' => :environment do +desc "Import subscriptions from Stripe" +task "subscriptions:subscriptions_import" => :environment do setup_api products = get_stripe_products strip_products_to_import = [] procourse_import = false - procourse_import_response = ask("Were the subscriptions you are importing created in Procourse Memberships?: (y/N)") - if procourse_import_response.downcase == 'y' - procourse_import = true - end + procourse_import_response = + ask("Were the subscriptions you are importing created in Procourse Memberships?: (y/N)") + procourse_import = true if procourse_import_response.downcase == "y" products.each do |product| - confirm_import = ask("Do you wish to import product #{product[:name]} (id: #{product[:id]}): (y/N)") - next if confirm_import.downcase != 'y' + confirm_import = + ask("Do you wish to import product #{product[:name]} (id: #{product[:id]}): (y/N)") + next if confirm_import.downcase != "y" strip_products_to_import << product end @@ -26,28 +26,28 @@ task 'subscriptions:subscriptions_import' => :environment do end def get_stripe_products(starting_after: nil) - puts 'Getting products from Stripe API' + puts "Getting products from Stripe API" all_products = [] loop do - products = Stripe::Product.list({ type: 'service', starting_after: starting_after, active: true }) + products = + Stripe::Product.list({ type: "service", starting_after: starting_after, active: true }) all_products += products[:data] break if products[:has_more] == false starting_after = products[:data].last["id"] end all_products - end def get_stripe_subscriptions(starting_after: nil) - puts 'Getting Subscriptions from Stripe API' + puts "Getting Subscriptions from Stripe API" all_subscriptions = [] loop do - subscriptions = Stripe::Subscription.list({ starting_after: starting_after, status: 'active' }) + subscriptions = Stripe::Subscription.list({ starting_after: starting_after, status: "active" }) all_subscriptions += subscriptions[:data] break if subscriptions[:has_more] == false starting_after = subscriptions[:data].last["id"] @@ -57,7 +57,7 @@ def get_stripe_subscriptions(starting_after: nil) end def get_stripe_customers(starting_after: nil) - puts 'Getting Customers from Stripe API' + puts "Getting Customers from Stripe API" all_customers = [] @@ -72,7 +72,7 @@ def get_stripe_customers(starting_after: nil) end def import_products(products) - puts 'Importing products:' + puts "Importing products:" products.each do |product| puts "Looking for external_id #{product[:id]} ..." @@ -86,7 +86,7 @@ def import_products(products) end def import_subscriptions(procourse_import) - puts 'Importing subscriptions' + puts "Importing subscriptions" product_ids = DiscourseSubscriptions::Product.all.pluck(:external_id) all_customers = get_stripe_customers @@ -95,7 +95,8 @@ def import_subscriptions(procourse_import) subscriptions = get_stripe_subscriptions puts "Total Active Subscriptions available: #{subscriptions.length.to_s}" - subscriptions_for_products = subscriptions.select { |sub| product_ids.include?(sub[:items][:data][0][:price][:product]) } + subscriptions_for_products = + subscriptions.select { |sub| product_ids.include?(sub[:items][:data][0][:price][:product]) } puts "Total Subscriptions matching Products to Import: #{subscriptions_for_products.length.to_s}" subscriptions_for_products.each do |subscription| @@ -113,29 +114,38 @@ def import_subscriptions(procourse_import) end if product_id && customer_id && subscription_id - subscriptions_customer = DiscourseSubscriptions::Customer.find_by(user_id: user_id, customer_id: customer_id, product_id: product_id) + subscriptions_customer = + DiscourseSubscriptions::Customer.find_by( + user_id: user_id, + customer_id: customer_id, + product_id: product_id, + ) if subscriptions_customer.nil? && user_id && user_id > 0 # create the customer record if doesn't exist only if the user_id and username match, which # prevents issues if multiple sites use the same Stripe account. Does not apply to a Procourse import. user = User.find(user_id) if procourse_import || (user && (user.username == username)) - subscriptions_customer = DiscourseSubscriptions::Customer.create( - user_id: user_id, - customer_id: customer_id, - product_id: product_id - ) - puts "Subscriptions Customer user_id: #{user_id}, customer_id: #{customer_id}, product_id: #{product_id}) CREATED" + subscriptions_customer = + DiscourseSubscriptions::Customer.create( + user_id: user_id, + customer_id: customer_id, + product_id: product_id, + ) + puts "Subscriptions Customer user_id: #{user_id}, customer_id: #{customer_id}, product_id: #{product_id}) CREATED" end else puts "Subscriptions Customer user_id: #{user_id}, customer_id: #{customer_id}, product_id: #{product_id}) already exists" end if subscriptions_customer - if DiscourseSubscriptions::Subscription.find_by(customer_id: subscriptions_customer.id, external_id: subscription_id).blank? + if DiscourseSubscriptions::Subscription.find_by( + customer_id: subscriptions_customer.id, + external_id: subscription_id, + ).blank? DiscourseSubscriptions::Subscription.create( customer_id: subscriptions_customer.id, - external_id: subscription_id + external_id: subscription_id, ) puts "Discourse Subscription customer_id: #{subscriptions_customer.id}, external_id: #{subscription_id}) CREATED" else @@ -147,9 +157,11 @@ def import_subscriptions(procourse_import) discourse_user = User.find(user_id) puts "Discourse User: #{discourse_user.username_lower} found for Strip metadata update ..." - updated_subscription = Stripe::Subscription.update(subscription_id, - { metadata: { user_id: user_id, - username: discourse_user.username_lower } }) + updated_subscription = + Stripe::Subscription.update( + subscription_id, + { metadata: { user_id: user_id, username: discourse_user.username_lower } }, + ) puts "Stripe Subscription: #{updated_subscription[:id]}, metadata: #{updated_subscription[:metadata]} UPDATED" updated_customer = Stripe::Customer.update(customer_id, { email: discourse_user.email }) @@ -163,6 +175,6 @@ end private def setup_api - api_key = SiteSetting.discourse_subscriptions_secret_key || ask('Input Stripe secret key') + api_key = SiteSetting.discourse_subscriptions_secret_key || ask("Input Stripe secret key") Stripe.api_key = api_key end diff --git a/plugin.rb b/plugin.rb index 2647f6e..ff9a226 100644 --- a/plugin.rb +++ b/plugin.rb @@ -9,7 +9,7 @@ enabled_site_setting :discourse_subscriptions_enabled -gem 'stripe', '5.29.0' +gem "stripe", "5.29.0" register_asset "stylesheets/common/main.scss" register_asset "stylesheets/common/layout.scss" @@ -18,47 +18,56 @@ register_asset "stylesheets/common/campaign.scss" register_asset "stylesheets/mobile/main.scss" register_svg_icon "far-credit-card" if respond_to?(:register_svg_icon) -register_html_builder('server:before-head-close') do +register_html_builder("server:before-head-close") do "" end -extend_content_security_policy( - script_src: ['https://js.stripe.com/v3/', 'https://hooks.stripe.com'] -) +extend_content_security_policy(script_src: %w[https://js.stripe.com/v3/ https://hooks.stripe.com]) -add_admin_route 'discourse_subscriptions.admin_navigation', 'discourse-subscriptions.products' +add_admin_route "discourse_subscriptions.admin_navigation", "discourse-subscriptions.products" Discourse::Application.routes.append do - get '/admin/plugins/discourse-subscriptions' => 'admin/plugins#index', constraints: AdminConstraint.new - get '/admin/plugins/discourse-subscriptions/products' => 'admin/plugins#index', constraints: AdminConstraint.new - get '/admin/plugins/discourse-subscriptions/products/:product_id' => 'admin/plugins#index', constraints: AdminConstraint.new - get '/admin/plugins/discourse-subscriptions/products/:product_id/plans' => 'admin/plugins#index', constraints: AdminConstraint.new - get '/admin/plugins/discourse-subscriptions/products/:product_id/plans/:plan_id' => 'admin/plugins#index', constraints: AdminConstraint.new - get '/admin/plugins/discourse-subscriptions/subscriptions' => 'admin/plugins#index', constraints: AdminConstraint.new - get '/admin/plugins/discourse-subscriptions/plans' => 'admin/plugins#index', constraints: AdminConstraint.new - get '/admin/plugins/discourse-subscriptions/plans/:plan_id' => 'admin/plugins#index', constraints: AdminConstraint.new - get '/admin/plugins/discourse-subscriptions/coupons' => 'admin/plugins#index', constraints: AdminConstraint.new - get 'u/:username/billing' => 'users#show', constraints: { username: USERNAME_ROUTE_FORMAT } - get 'u/:username/billing/:id' => 'users#show', constraints: { username: USERNAME_ROUTE_FORMAT } - get 'u/:username/billing/subscriptions/card/:subscription_id' => 'users#show', constraints: { username: USERNAME_ROUTE_FORMAT } + get "/admin/plugins/discourse-subscriptions" => "admin/plugins#index", + :constraints => AdminConstraint.new + get "/admin/plugins/discourse-subscriptions/products" => "admin/plugins#index", + :constraints => AdminConstraint.new + get "/admin/plugins/discourse-subscriptions/products/:product_id" => "admin/plugins#index", + :constraints => AdminConstraint.new + get "/admin/plugins/discourse-subscriptions/products/:product_id/plans" => "admin/plugins#index", + :constraints => AdminConstraint.new + get "/admin/plugins/discourse-subscriptions/products/:product_id/plans/:plan_id" => + "admin/plugins#index", + :constraints => AdminConstraint.new + get "/admin/plugins/discourse-subscriptions/subscriptions" => "admin/plugins#index", + :constraints => AdminConstraint.new + get "/admin/plugins/discourse-subscriptions/plans" => "admin/plugins#index", + :constraints => AdminConstraint.new + get "/admin/plugins/discourse-subscriptions/plans/:plan_id" => "admin/plugins#index", + :constraints => AdminConstraint.new + get "/admin/plugins/discourse-subscriptions/coupons" => "admin/plugins#index", + :constraints => AdminConstraint.new + get "u/:username/billing" => "users#show", :constraints => { username: USERNAME_ROUTE_FORMAT } + get "u/:username/billing/:id" => "users#show", :constraints => { username: USERNAME_ROUTE_FORMAT } + get "u/:username/billing/subscriptions/card/:subscription_id" => "users#show", + :constraints => { + username: USERNAME_ROUTE_FORMAT, + } end -load File.expand_path('lib/discourse_subscriptions/engine.rb', __dir__) -load File.expand_path('app/controllers/concerns/stripe.rb', __dir__) -load File.expand_path('app/controllers/concerns/group.rb', __dir__) +load File.expand_path("lib/discourse_subscriptions/engine.rb", __dir__) +load File.expand_path("app/controllers/concerns/stripe.rb", __dir__) +load File.expand_path("app/controllers/concerns/group.rb", __dir__) after_initialize do ::Stripe.api_version = "2020-08-27" ::Stripe.set_app_info( - 'Discourse Subscriptions', - version: '2.8.1', - url: 'https://github.com/discourse/discourse-subscriptions' + "Discourse Subscriptions", + version: "2.8.1", + url: "https://github.com/discourse/discourse-subscriptions", ) - Discourse::Application.routes.append do - mount ::DiscourseSubscriptions::Engine, at: 's' - end + Discourse::Application.routes.append { mount ::DiscourseSubscriptions::Engine, at: "s" } add_to_serializer(:site, :show_campaign_banner) do begin @@ -67,7 +76,7 @@ after_initialize do goal_met = Discourse.redis.get("subscriptions_goal_met_date") enabled && campaign_enabled && (!goal_met || 7.days.ago <= Date.parse(goal_met)) - rescue + rescue StandardError false end end diff --git a/spec/jobs/refresh_subscriptions_campaign_data_spec.rb b/spec/jobs/refresh_subscriptions_campaign_data_spec.rb index fd83234..f692bfa 100644 --- a/spec/jobs/refresh_subscriptions_campaign_data_spec.rb +++ b/spec/jobs/refresh_subscriptions_campaign_data_spec.rb @@ -1,14 +1,11 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe Jobs::RefreshSubscriptionsCampaignData do + before { SiteSetting.discourse_subscriptions_campaign_enabled = true } - before do - SiteSetting.discourse_subscriptions_campaign_enabled = true - end - - it 'should execute the job only if stripe is configured' do + it "should execute the job only if stripe is configured" do DiscourseSubscriptions::Campaign.any_instance.expects(:refresh_data).once described_class.new.execute({}) @@ -16,5 +13,4 @@ RSpec.describe Jobs::RefreshSubscriptionsCampaignData do SiteSetting.discourse_subscriptions_secret_key = "SECRET_KEY" described_class.new.execute({}) end - end diff --git a/spec/models/customer_spec.rb b/spec/models/customer_spec.rb index f5afc40..7f4e826 100644 --- a/spec/models/customer_spec.rb +++ b/spec/models/customer_spec.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" module DiscourseSubscriptions RSpec.describe Customer do let(:user) { Fabricate(:user) } - let(:stripe_customer) { { id: 'cus_id4567' } } + let(:stripe_customer) { { id: "cus_id4567" } } it "has a table name" do expect(described_class.table_name).to eq "discourse_subscriptions_customers" @@ -13,14 +13,14 @@ module DiscourseSubscriptions it "creates" do customer = described_class.create_customer(user, stripe_customer) - expect(customer.customer_id).to eq 'cus_id4567' + expect(customer.customer_id).to eq "cus_id4567" expect(customer.user_id).to eq user.id end it "has a user scope" do described_class.create_customer(user, stripe_customer) customer = described_class.find_user(user) - expect(customer.customer_id).to eq 'cus_id4567' + expect(customer.customer_id).to eq "cus_id4567" end end end diff --git a/spec/requests/admin/coupons_controller_spec.rb b/spec/requests/admin/coupons_controller_spec.rb index 0f0ed82..078726c 100644 --- a/spec/requests/admin/coupons_controller_spec.rb +++ b/spec/requests/admin/coupons_controller_spec.rb @@ -1,14 +1,16 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" module DiscourseSubscriptions RSpec.describe Admin::CouponsController do - it 'is a subclass of AdminController' do - expect(DiscourseSubscriptions::Admin::CouponsController < ::Admin::AdminController).to eq(true) + it "is a subclass of AdminController" do + expect(DiscourseSubscriptions::Admin::CouponsController < ::Admin::AdminController).to eq( + true, + ) end - context 'when unauthenticated' do + context "when unauthenticated" do it "does nothing" do ::Stripe::PromotionCode.expects(:list).never get "/s/admin/coupons.json" @@ -16,36 +18,28 @@ module DiscourseSubscriptions end end - context 'when authenticated' do + context "when authenticated" do let(:admin) { Fabricate(:admin) } before { sign_in(admin) } describe "#index" do it "returns a list of promo codes" do - ::Stripe::PromotionCode.expects(:list).with({ limit: 100 }).returns({ - data: [{ - id: 'promo_123', - coupon: { - valid: true - } - }] - }) + ::Stripe::PromotionCode + .expects(:list) + .with({ limit: 100 }) + .returns({ data: [{ id: "promo_123", coupon: { valid: true } }] }) get "/s/admin/coupons.json" expect(response.status).to eq(200) - expect(response.parsed_body[0]['id']).to eq('promo_123') + expect(response.parsed_body[0]["id"]).to eq("promo_123") end it "only returns valid promo codes" do - ::Stripe::PromotionCode.expects(:list).with({ limit: 100 }).returns({ - data: [{ - id: 'promo_123', - coupon: { - valid: false - } - }] - }) + ::Stripe::PromotionCode + .expects(:list) + .with({ limit: 100 }) + .returns({ data: [{ id: "promo_123", coupon: { valid: false } }] }) get "/s/admin/coupons.json" expect(response.status).to eq(200) @@ -55,43 +49,39 @@ module DiscourseSubscriptions describe "#create" do it "creates a coupon with an amount off" do - ::Stripe::Coupon.expects(:create).returns(id: 'coup_123') - ::Stripe::PromotionCode.expects(:create).returns({ - code: 'p123', - coupon: { - amount_off: 2000 - } - }) + ::Stripe::Coupon.expects(:create).returns(id: "coup_123") + ::Stripe::PromotionCode.expects(:create).returns( + { code: "p123", coupon: { amount_off: 2000 } }, + ) - post "/s/admin/coupons.json", params: { - promo: 'p123', - discount_type: 'amount', - discount: '2000', - active: true, - } + post "/s/admin/coupons.json", + params: { + promo: "p123", + discount_type: "amount", + discount: "2000", + active: true, + } expect(response.status).to eq(200) - expect(response.parsed_body['code']).to eq('p123') - expect(response.parsed_body['coupon']['amount_off']).to eq(2000) + expect(response.parsed_body["code"]).to eq("p123") + expect(response.parsed_body["coupon"]["amount_off"]).to eq(2000) end it "creates a coupon with a percent off" do - ::Stripe::Coupon.expects(:create).returns(id: 'coup_123') - ::Stripe::PromotionCode.expects(:create).returns({ - code: 'p123', - coupon: { - percent_off: 20 - } - }) + ::Stripe::Coupon.expects(:create).returns(id: "coup_123") + ::Stripe::PromotionCode.expects(:create).returns( + { code: "p123", coupon: { percent_off: 20 } }, + ) - post "/s/admin/coupons.json", params: { - promo: 'p123', - discount_type: 'percent', - discount: '20', - active: true, - } + post "/s/admin/coupons.json", + params: { + promo: "p123", + discount_type: "percent", + discount: "20", + active: true, + } expect(response.status).to eq(200) - expect(response.parsed_body['code']).to eq('p123') - expect(response.parsed_body['coupon']['percent_off']).to eq(20) + expect(response.parsed_body["code"]).to eq("p123") + expect(response.parsed_body["coupon"]["percent_off"]).to eq(20) end end end diff --git a/spec/requests/admin/plans_controller_spec.rb b/spec/requests/admin/plans_controller_spec.rb index 029f05d..c4b546a 100644 --- a/spec/requests/admin/plans_controller_spec.rb +++ b/spec/requests/admin/plans_controller_spec.rb @@ -1,15 +1,17 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" module DiscourseSubscriptions module Admin RSpec.describe PlansController do - it 'is a subclass of AdminController' do - expect(DiscourseSubscriptions::Admin::PlansController < ::Admin::AdminController).to eq(true) + it "is a subclass of AdminController" do + expect(DiscourseSubscriptions::Admin::PlansController < ::Admin::AdminController).to eq( + true, + ) end - context 'when not authenticated' do + context "when not authenticated" do describe "index" do it "does not get the plans" do ::Stripe::Price.expects(:list).never @@ -25,11 +27,11 @@ module DiscourseSubscriptions describe "create" do it "does not create a plan" do ::Stripe::Price.expects(:create).never - post "/s/admin/plans.json", params: { name: 'Rick Astley', amount: 1, interval: 'week' } + post "/s/admin/plans.json", params: { name: "Rick Astley", amount: 1, interval: "week" } end it "is not ok" do - post "/s/admin/plans.json", params: { name: 'Rick Astley', amount: 1, interval: 'week' } + post "/s/admin/plans.json", params: { name: "Rick Astley", amount: 1, interval: "week" } expect(response.status).to eq 404 end end @@ -54,7 +56,7 @@ module DiscourseSubscriptions end end - context 'when authenticated' do + context "when authenticated" do let(:admin) { Fabricate(:admin) } before { sign_in(admin) } @@ -66,62 +68,78 @@ module DiscourseSubscriptions end it "lists the plans for the product" do - ::Stripe::Price.expects(:list).with({ product: 'prod_id123' }) - get "/s/admin/plans.json", params: { product_id: 'prod_id123' } + ::Stripe::Price.expects(:list).with({ product: "prod_id123" }) + get "/s/admin/plans.json", params: { product_id: "prod_id123" } end end describe "show" do it "shows a plan" do - ::Stripe::Price.expects(:retrieve).with('plan_12345').returns(currency: 'aud') + ::Stripe::Price.expects(:retrieve).with("plan_12345").returns(currency: "aud") get "/s/admin/plans/plan_12345.json" expect(response.status).to eq 200 end it "upcases the currency" do - ::Stripe::Price.expects(:retrieve).with('plan_12345').returns(currency: 'aud', recurring: { interval: 'year' }) + ::Stripe::Price + .expects(:retrieve) + .with("plan_12345") + .returns(currency: "aud", recurring: { interval: "year" }) get "/s/admin/plans/plan_12345.json" plan = response.parsed_body - expect(plan["currency"]).to eq 'AUD' - expect(plan["interval"]).to eq 'year' + expect(plan["currency"]).to eq "AUD" + expect(plan["interval"]).to eq "year" end end describe "create" do it "creates a plan with a nickname" do - ::Stripe::Price.expects(:create).with(has_entry(:nickname, 'Veg')) - post "/s/admin/plans.json", params: { nickname: 'Veg', metadata: { group_name: '' } } + ::Stripe::Price.expects(:create).with(has_entry(:nickname, "Veg")) + post "/s/admin/plans.json", params: { nickname: "Veg", metadata: { group_name: "" } } end it "creates a plan with a currency" do - ::Stripe::Price.expects(:create).with(has_entry(:currency, 'AUD')) - post "/s/admin/plans.json", params: { currency: 'AUD', metadata: { group_name: '' } } + ::Stripe::Price.expects(:create).with(has_entry(:currency, "AUD")) + post "/s/admin/plans.json", params: { currency: "AUD", metadata: { group_name: "" } } end it "creates a plan with an interval" do - ::Stripe::Price.expects(:create).with(has_entry(recurring: { interval: 'week' })) - post "/s/admin/plans.json", params: { type: 'recurring', interval: 'week', metadata: { group_name: '' } } + ::Stripe::Price.expects(:create).with(has_entry(recurring: { interval: "week" })) + post "/s/admin/plans.json", + params: { + type: "recurring", + interval: "week", + metadata: { + group_name: "", + }, + } end it "creates a plan as a one-time purchase" do ::Stripe::Price.expects(:create).with(Not(has_key(:recurring))) - post "/s/admin/plans.json", params: { metadata: { group_name: '' } } + post "/s/admin/plans.json", params: { metadata: { group_name: "" } } end it "creates a plan with an amount" do - ::Stripe::Price.expects(:create).with(has_entry(:unit_amount, '102')) - post "/s/admin/plans.json", params: { amount: '102', metadata: { group_name: '' } } + ::Stripe::Price.expects(:create).with(has_entry(:unit_amount, "102")) + post "/s/admin/plans.json", params: { amount: "102", metadata: { group_name: "" } } end it "creates a plan with a product" do - ::Stripe::Price.expects(:create).with(has_entry(product: 'prod_walterwhite')) - post "/s/admin/plans.json", params: { product: 'prod_walterwhite', metadata: { group_name: '' } } + ::Stripe::Price.expects(:create).with(has_entry(product: "prod_walterwhite")) + post "/s/admin/plans.json", + params: { + product: "prod_walterwhite", + metadata: { + group_name: "", + }, + } end it "creates a plan with an active status" do - ::Stripe::Price.expects(:create).with(has_entry(:active, 'false')) - post "/s/admin/plans.json", params: { active: 'false', metadata: { group_name: '' } } + ::Stripe::Price.expects(:create).with(has_entry(:active, "false")) + post "/s/admin/plans.json", params: { active: "false", metadata: { group_name: "" } } end # TODO: Need to fix the metadata tests @@ -141,7 +159,13 @@ module DiscourseSubscriptions describe "update" do it "updates a plan" do ::Stripe::Price.expects(:update) - patch "/s/admin/plans/plan_12345.json", params: { trial_period_days: '14', metadata: { group_name: 'discourse-user-group-name' } } + patch "/s/admin/plans/plan_12345.json", + params: { + trial_period_days: "14", + metadata: { + group_name: "discourse-user-group-name", + }, + } end end end diff --git a/spec/requests/admin/products_controller_spec.rb b/spec/requests/admin/products_controller_spec.rb index f0c6edc..541e821 100644 --- a/spec/requests/admin/products_controller_spec.rb +++ b/spec/requests/admin/products_controller_spec.rb @@ -1,15 +1,17 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" module DiscourseSubscriptions module Admin RSpec.describe ProductsController do - it 'is a subclass of AdminController' do - expect(DiscourseSubscriptions::Admin::ProductsController < ::Admin::AdminController).to eq(true) + it "is a subclass of AdminController" do + expect(DiscourseSubscriptions::Admin::ProductsController < ::Admin::AdminController).to eq( + true, + ) end - context 'when unauthenticated' do + context "when unauthenticated" do it "does not list the products" do ::Stripe::Product.expects(:list).never get "/s/admin/products.json" @@ -41,12 +43,12 @@ module DiscourseSubscriptions end end - context 'when authenticated' do + context "when authenticated" do let(:admin) { Fabricate(:admin) } before { sign_in(admin) } - describe 'index' do + describe "index" do it "gets the empty products" do SiteSetting.discourse_subscriptions_public_key = "public-key" SiteSetting.discourse_subscriptions_secret_key = "secret-key" @@ -55,61 +57,74 @@ module DiscourseSubscriptions end end - describe 'create' do - it 'is of product type service' do - ::Stripe::Product.expects(:create).with(has_entry(:type, 'service')) + describe "create" do + it "is of product type service" do + ::Stripe::Product.expects(:create).with(has_entry(:type, "service")) post "/s/admin/products.json", params: {} end - it 'has a name' do - ::Stripe::Product.expects(:create).with(has_entry(:name, 'Jesse Pinkman')) - post "/s/admin/products.json", params: { name: 'Jesse Pinkman' } + it "has a name" do + ::Stripe::Product.expects(:create).with(has_entry(:name, "Jesse Pinkman")) + post "/s/admin/products.json", params: { name: "Jesse Pinkman" } end - it 'has an active attribute' do - ::Stripe::Product.expects(:create).with(has_entry(active: 'false')) - post "/s/admin/products.json", params: { active: 'false' } + it "has an active attribute" do + ::Stripe::Product.expects(:create).with(has_entry(active: "false")) + post "/s/admin/products.json", params: { active: "false" } end - it 'has a statement descriptor' do - ::Stripe::Product.expects(:create).with(has_entry(statement_descriptor: 'Blessed are the cheesemakers')) - post "/s/admin/products.json", params: { statement_descriptor: 'Blessed are the cheesemakers' } + it "has a statement descriptor" do + ::Stripe::Product.expects(:create).with( + has_entry(statement_descriptor: "Blessed are the cheesemakers"), + ) + post "/s/admin/products.json", + params: { + statement_descriptor: "Blessed are the cheesemakers", + } end - it 'has no statement descriptor if empty' do + it "has no statement descriptor if empty" do ::Stripe::Product.expects(:create).with(has_key(:statement_descriptor)).never - post "/s/admin/products.json", params: { statement_descriptor: '' } + post "/s/admin/products.json", params: { statement_descriptor: "" } end - it 'has metadata' do - ::Stripe::Product.expects(:create).with(has_entry(metadata: { description: 'Oi, I think he just said bless be all the bignoses!', repurchaseable: 'false' })) + it "has metadata" do + ::Stripe::Product.expects(:create).with( + has_entry( + metadata: { + description: "Oi, I think he just said bless be all the bignoses!", + repurchaseable: "false", + }, + ), + ) - post "/s/admin/products.json", params: { - metadata: { - description: 'Oi, I think he just said bless be all the bignoses!', - repurchaseable: 'false' - } - } + post "/s/admin/products.json", + params: { + metadata: { + description: "Oi, I think he just said bless be all the bignoses!", + repurchaseable: "false", + }, + } end end - describe 'show' do - it 'retrieves the product' do - ::Stripe::Product.expects(:retrieve).with('prod_walterwhite') + describe "show" do + it "retrieves the product" do + ::Stripe::Product.expects(:retrieve).with("prod_walterwhite") get "/s/admin/products/prod_walterwhite.json" end end - describe 'update' do - it 'updates the product' do + describe "update" do + it "updates the product" do ::Stripe::Product.expects(:update) patch "/s/admin/products/prod_walterwhite.json", params: {} end end - describe 'delete' do - it 'deletes the product' do - ::Stripe::Product.expects(:delete).with('prod_walterwhite') + describe "delete" do + it "deletes the product" do + ::Stripe::Product.expects(:delete).with("prod_walterwhite") delete "/s/admin/products/prod_walterwhite.json" end end diff --git a/spec/requests/admin/subscriptions_controller_spec.rb b/spec/requests/admin/subscriptions_controller_spec.rb index fb157cf..82ec198 100644 --- a/spec/requests/admin/subscriptions_controller_spec.rb +++ b/spec/requests/admin/subscriptions_controller_spec.rb @@ -1,22 +1,26 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" module DiscourseSubscriptions RSpec.describe Admin::SubscriptionsController do - it 'is a subclass of AdminController' do - expect(DiscourseSubscriptions::Admin::SubscriptionsController < ::Admin::AdminController).to eq(true) + it "is a subclass of AdminController" do + expect( + DiscourseSubscriptions::Admin::SubscriptionsController < ::Admin::AdminController, + ).to eq(true) end let(:user) { Fabricate(:user) } - let(:customer) { Fabricate(:customer, user_id: user.id, customer_id: 'c_123', product_id: 'pr_34578') } + let(:customer) do + Fabricate(:customer, user_id: user.id, customer_id: "c_123", product_id: "pr_34578") + end before do Fabricate(:subscription, external_id: "sub_12345", customer_id: customer.id) Fabricate(:subscription, external_id: "sub_77777", customer_id: customer.id) end - context 'when unauthenticated' do + context "when unauthenticated" do it "does nothing" do ::Stripe::Subscription.expects(:list).never get "/s/admin/subscriptions.json" @@ -29,7 +33,7 @@ module DiscourseSubscriptions end end - context 'when authenticated' do + context "when authenticated" do let(:admin) { Fabricate(:admin) } before { sign_in(admin) } @@ -41,15 +45,10 @@ module DiscourseSubscriptions end it "gets the subscriptions and products" do - ::Stripe::Subscription.expects(:list) - .with(expand: ['data.plan.product'], limit: 10, starting_after: nil) - .returns( - has_more: false, - data: [ - { id: "sub_12345" }, - { id: "sub_nope" } - ] - ) + ::Stripe::Subscription + .expects(:list) + .with(expand: ["data.plan.product"], limit: 10, starting_after: nil) + .returns(has_more: false, data: [{ id: "sub_12345" }, { id: "sub_nope" }]) get "/s/admin/subscriptions.json" subscriptions = response.parsed_body["data"][0]["id"] @@ -58,16 +57,11 @@ module DiscourseSubscriptions end it "handles starting at a different point in the set" do - ::Stripe::Subscription.expects(:list) - .with(expand: ['data.plan.product'], limit: 10, starting_after: 'sub_nope') - .returns( - has_more: false, - data: [ - { id: "sub_77777" }, - { id: "sub_yepnoep" } - ] - ) - get "/s/admin/subscriptions.json", params: { last_record: 'sub_nope' } + ::Stripe::Subscription + .expects(:list) + .with(expand: ["data.plan.product"], limit: 10, starting_after: "sub_nope") + .returns(has_more: false, data: [{ id: "sub_77777" }, { id: "sub_yepnoep" }]) + get "/s/admin/subscriptions.json", params: { last_record: "sub_nope" } subscriptions = response.parsed_body["data"][0]["id"] expect(response.status).to eq(200) @@ -76,65 +70,70 @@ module DiscourseSubscriptions end describe "destroy" do - let(:group) { Fabricate(:group, name: 'subscribers') } + let(:group) { Fabricate(:group, name: "subscribers") } - before do - group.add(user) - end + before { group.add(user) } it "deletes a customer" do ::Stripe::Subscription .expects(:delete) - .with('sub_12345') - .returns( - plan: { product: 'pr_34578' }, - customer: 'c_123' - ) + .with("sub_12345") + .returns(plan: { product: "pr_34578" }, customer: "c_123") - expect { - delete "/s/admin/subscriptions/sub_12345.json" - }.to change { DiscourseSubscriptions::Customer.count }.by(-1) + expect { delete "/s/admin/subscriptions/sub_12345.json" }.to change { + DiscourseSubscriptions::Customer.count + }.by(-1) end it "removes the user from the group" do ::Stripe::Subscription .expects(:delete) - .with('sub_12345') + .with("sub_12345") .returns( - plan: { product: 'pr_34578', metadata: { group_name: 'subscribers' } }, - customer: 'c_123' + plan: { + product: "pr_34578", + metadata: { + group_name: "subscribers", + }, + }, + customer: "c_123", ) - expect { - delete "/s/admin/subscriptions/sub_12345.json" - }.to change { user.groups.count }.by(-1) + expect { delete "/s/admin/subscriptions/sub_12345.json" }.to change { + user.groups.count + }.by(-1) end it "does not remove the user from the group" do ::Stripe::Subscription .expects(:delete) - .with('sub_12345') + .with("sub_12345") .returns( - plan: { product: 'pr_34578', metadata: { group_name: 'group_does_not_exist' } }, - customer: 'c_123' + plan: { + product: "pr_34578", + metadata: { + group_name: "group_does_not_exist", + }, + }, + customer: "c_123", ) - expect { - delete "/s/admin/subscriptions/sub_12345.json" - }.not_to change { user.groups.count } + expect { delete "/s/admin/subscriptions/sub_12345.json" }.not_to change { + user.groups.count + } end it "refunds if params[:refund] present" do ::Stripe::Subscription .expects(:delete) - .with('sub_12345') - .returns( - plan: { product: 'pr_34578' }, - customer: 'c_123' - ) - ::Stripe::Subscription.expects(:retrieve).with('sub_12345').returns(latest_invoice: 'in_123') - ::Stripe::Invoice.expects(:retrieve).with('in_123').returns(payment_intent: 'pi_123') - ::Stripe::Refund.expects(:create).with({ payment_intent: 'pi_123' }) + .with("sub_12345") + .returns(plan: { product: "pr_34578" }, customer: "c_123") + ::Stripe::Subscription + .expects(:retrieve) + .with("sub_12345") + .returns(latest_invoice: "in_123") + ::Stripe::Invoice.expects(:retrieve).with("in_123").returns(payment_intent: "pi_123") + ::Stripe::Refund.expects(:create).with({ payment_intent: "pi_123" }) delete "/s/admin/subscriptions/sub_12345.json", params: { refund: true } end diff --git a/spec/requests/admin_controller_spec.rb b/spec/requests/admin_controller_spec.rb index 647b60e..cebf5db 100644 --- a/spec/requests/admin_controller_spec.rb +++ b/spec/requests/admin_controller_spec.rb @@ -1,15 +1,14 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" module DiscourseSubscriptions RSpec.describe AdminController do - let(:admin) { Fabricate(:admin) } before { sign_in(admin) } - it 'is a subclass of AdminController' do + it "is a subclass of AdminController" do expect(DiscourseSubscriptions::AdminController < ::Admin::AdminController).to eq(true) end diff --git a/spec/requests/hooks_controller_spec.rb b/spec/requests/hooks_controller_spec.rb index 6398464..b016607 100644 --- a/spec/requests/hooks_controller_spec.rb +++ b/spec/requests/hooks_controller_spec.rb @@ -1,21 +1,19 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" module DiscourseSubscriptions RSpec.describe HooksController do - before do - SiteSetting.discourse_subscriptions_webhook_secret = 'zascharoo' - end + before { SiteSetting.discourse_subscriptions_webhook_secret = "zascharoo" } it "contructs a webhook event" do - payload = 'we-want-a-shrubbery' - headers = { HTTP_STRIPE_SIGNATURE: 'stripe-webhook-signature' } + payload = "we-want-a-shrubbery" + headers = { HTTP_STRIPE_SIGNATURE: "stripe-webhook-signature" } ::Stripe::Webhook .expects(:construct_event) - .with('we-want-a-shrubbery', 'stripe-webhook-signature', 'zascharoo') - .returns(type: 'something') + .with("we-want-a-shrubbery", "stripe-webhook-signature", "zascharoo") + .returns(type: "something") post "/s/hooks.json", params: payload, headers: headers @@ -24,65 +22,61 @@ module DiscourseSubscriptions describe "event types" do let(:user) { Fabricate(:user) } - let(:customer) { Fabricate(:customer, customer_id: 'c_575768', product_id: 'p_8654', user_id: user.id) } - let(:group) { Fabricate(:group, name: 'subscribers-group') } + let(:customer) do + Fabricate(:customer, customer_id: "c_575768", product_id: "p_8654", user_id: user.id) + end + let(:group) { Fabricate(:group, name: "subscribers-group") } let(:event_data) do { object: { customer: customer.customer_id, - plan: { product: customer.product_id, metadata: { group_name: group.name } } - } + plan: { + product: customer.product_id, + metadata: { + group_name: group.name, + }, + }, + }, } end describe "customer.subscription.updated" do before do - event = { - type: 'customer.subscription.updated', - data: event_data - } + event = { type: "customer.subscription.updated", data: event_data } - ::Stripe::Webhook - .stubs(:construct_event) - .returns(event) + ::Stripe::Webhook.stubs(:construct_event).returns(event) end - it 'is successfull' do + it "is successfull" do post "/s/hooks.json" expect(response.status).to eq 200 end - describe 'completing the subscription' do - it 'does not add the user to the group' do - event_data[:object][:status] = 'incomplete' - event_data[:previous_attributes] = { status: 'incomplete' } + describe "completing the subscription" do + it "does not add the user to the group" do + event_data[:object][:status] = "incomplete" + event_data[:previous_attributes] = { status: "incomplete" } - expect { - post "/s/hooks.json" - }.not_to change { user.groups.count } + expect { post "/s/hooks.json" }.not_to change { user.groups.count } expect(response.status).to eq 200 end - it 'does not add the user to the group' do - event_data[:object][:status] = 'incomplete' - event_data[:previous_attributes] = { status: 'something-else' } + it "does not add the user to the group" do + event_data[:object][:status] = "incomplete" + event_data[:previous_attributes] = { status: "something-else" } - expect { - post "/s/hooks.json" - }.not_to change { user.groups.count } + expect { post "/s/hooks.json" }.not_to change { user.groups.count } expect(response.status).to eq 200 end - it 'adds the user to the group when completing the transaction' do - event_data[:object][:status] = 'complete' - event_data[:previous_attributes] = { status: 'incomplete' } + it "adds the user to the group when completing the transaction" do + event_data[:object][:status] = "complete" + event_data[:previous_attributes] = { status: "incomplete" } - expect { - post "/s/hooks.json" - }.to change { user.groups.count }.by(1) + expect { post "/s/hooks.json" }.to change { user.groups.count }.by(1) expect(response.status).to eq 200 end @@ -91,30 +85,23 @@ module DiscourseSubscriptions describe "customer.subscription.deleted" do before do - event = { - type: 'customer.subscription.deleted', - data: event_data - } + event = { type: "customer.subscription.deleted", data: event_data } - ::Stripe::Webhook - .stubs(:construct_event) - .returns(event) + ::Stripe::Webhook.stubs(:construct_event).returns(event) group.add(user) end it "deletes the customer" do - expect { - post "/s/hooks.json" - }.to change { DiscourseSubscriptions::Customer.count }.by(-1) + expect { post "/s/hooks.json" }.to change { DiscourseSubscriptions::Customer.count }.by( + -1, + ) expect(response.status).to eq 200 end it "removes the user from the group" do - expect { - post "/s/hooks.json" - }.to change { user.groups.count }.by(-1) + expect { post "/s/hooks.json" }.to change { user.groups.count }.by(-1) expect(response.status).to eq 200 end diff --git a/spec/requests/subscribe_controller_spec.rb b/spec/requests/subscribe_controller_spec.rb index 47f61fd..eb1c595 100644 --- a/spec/requests/subscribe_controller_spec.rb +++ b/spec/requests/subscribe_controller_spec.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" module DiscourseSubscriptions RSpec.describe SubscribeController do - let (:user) { Fabricate(:user) } - let (:campaign_user) { Fabricate(:user) } + let (:user) { + Fabricate(:user) + } + let (:campaign_user) { + Fabricate(:user) + } context "when showing products" do let(:product) do @@ -13,8 +17,9 @@ module DiscourseSubscriptions id: "prodct_23456", name: "Very Special Product", metadata: { - description: "Many people listened to my phone call with the Ukrainian President while it was being made", - repurchaseable: false + description: + "Many people listened to my phone call with the Ukrainian President while it was being made", + repurchaseable: false, }, otherstuff: true, } @@ -23,10 +28,37 @@ module DiscourseSubscriptions let(:prices) do { data: [ - { id: 'plan_id123', unit_amount: 1220, currency: 'aud', recurring: { interval: 'year' }, metadata: {} }, - { id: 'plan_id234', unit_amount: 1399, currency: 'usd', recurring: { interval: 'year' }, metadata: {} }, - { id: 'plan_id678', unit_amount: 1000, currency: 'aud', recurring: { interval: 'week' }, metadata: {} } - ] + { + id: "plan_id123", + unit_amount: 1220, + currency: "aud", + recurring: { + interval: "year", + }, + metadata: { + }, + }, + { + id: "plan_id234", + unit_amount: 1399, + currency: "usd", + recurring: { + interval: "year", + }, + metadata: { + }, + }, + { + id: "plan_id678", + unit_amount: 1000, + currency: "aud", + recurring: { + interval: "week", + }, + metadata: { + }, + }, + ], } end @@ -40,24 +72,36 @@ module DiscourseSubscriptions end describe "#index" do - it "gets products" do - ::Stripe::Product.expects(:list).with({ ids: product_ids, active: true }).returns(data: [product]) + ::Stripe::Product + .expects(:list) + .with({ ids: product_ids, active: true }) + .returns(data: [product]) get "/s.json" - expect(response.parsed_body).to eq([{ - "id" => "prodct_23456", - "name" => "Very Special Product", - "description" => PrettyText.cook("Many people listened to my phone call with the Ukrainian President while it was being made"), - "subscribed" => false, - "repurchaseable" => false, - }]) + expect(response.parsed_body).to eq( + [ + { + "id" => "prodct_23456", + "name" => "Very Special Product", + "description" => + PrettyText.cook( + "Many people listened to my phone call with the Ukrainian President while it was being made", + ), + "subscribed" => false, + "repurchaseable" => false, + }, + ], + ) end it "is subscribed" do - Fabricate(:customer, product_id: product[:id], user_id: user.id, customer_id: 'x') - ::Stripe::Product.expects(:list).with({ ids: product_ids, active: true }).returns(data: [product]) + Fabricate(:customer, product_id: product[:id], user_id: user.id, customer_id: "x") + ::Stripe::Product + .expects(:list) + .with({ ids: product_ids, active: true }) + .returns(data: [product]) get "/s.json" data = response.parsed_body @@ -66,7 +110,10 @@ module DiscourseSubscriptions it "is not subscribed" do ::DiscourseSubscriptions::Customer.delete_all - ::Stripe::Product.expects(:list).with({ ids: product_ids, active: true }).returns(data: [product]) + ::Stripe::Product + .expects(:list) + .with({ ids: product_ids, active: true }) + .returns(data: [product]) get "/s.json" data = response.parsed_body @@ -77,11 +124,16 @@ module DiscourseSubscriptions describe "#get_contributors" do before do Fabricate(:product, external_id: "prod_campaign") - Fabricate(:customer, product_id: "prodct_23456", user_id: user.id, customer_id: 'x') - Fabricate(:customer, product_id: "prod_campaign", user_id: campaign_user.id, customer_id: 'y') + Fabricate(:customer, product_id: "prodct_23456", user_id: user.id, customer_id: "x") + Fabricate( + :customer, + product_id: "prod_campaign", + user_id: campaign_user.id, + customer_id: "y", + ) end - context 'when not showing contributors' do - it 'returns nothing if not set to show contributors' do + context "when not showing contributors" do + it "returns nothing if not set to show contributors" do SiteSetting.discourse_subscriptions_campaign_show_contributors = false get "/s/contributors.json" @@ -90,12 +142,10 @@ module DiscourseSubscriptions end end - context 'when showing contributors' do - before do - SiteSetting.discourse_subscriptions_campaign_show_contributors = true - end + context "when showing contributors" do + before { SiteSetting.discourse_subscriptions_campaign_show_contributors = true } - it 'filters users by campaign product if set' do + it "filters users by campaign product if set" do SiteSetting.discourse_subscriptions_campaign_product = "prod_campaign" get "/s/contributors.json" @@ -105,7 +155,7 @@ module DiscourseSubscriptions expect(data.length).to eq 1 end - it 'shows all purchases if campaign product not set' do + it "shows all purchases if campaign product not set" do SiteSetting.discourse_subscriptions_campaign_product = nil get "/s/contributors.json" @@ -117,25 +167,54 @@ module DiscourseSubscriptions end describe "#show" do - it 'retrieves the product' do - ::Stripe::Product.expects(:retrieve).with('prod_walterwhite').returns(product) - ::Stripe::Price.expects(:list).with(active: true, product: 'prod_walterwhite').returns(prices) + it "retrieves the product" do + ::Stripe::Product.expects(:retrieve).with("prod_walterwhite").returns(product) + ::Stripe::Price + .expects(:list) + .with(active: true, product: "prod_walterwhite") + .returns(prices) get "/s/prod_walterwhite.json" - expect(response.parsed_body).to eq({ - "product" => { - "id" => "prodct_23456", - "name" => "Very Special Product", - "description" => PrettyText.cook("Many people listened to my phone call with the Ukrainian President while it was being made"), - "subscribed" => false, - "repurchaseable" => false - }, - "plans" => [ - { "currency" => "aud", "id" => "plan_id123", "recurring" => { "interval" => "year" }, "unit_amount" => 1220 }, - { "currency" => "usd", "id" => "plan_id234", "recurring" => { "interval" => "year" }, "unit_amount" => 1399 }, - { "currency" => "aud", "id" => "plan_id678", "recurring" => { "interval" => "week" }, "unit_amount" => 1000 } - ] - }) + expect(response.parsed_body).to eq( + { + "product" => { + "id" => "prodct_23456", + "name" => "Very Special Product", + "description" => + PrettyText.cook( + "Many people listened to my phone call with the Ukrainian President while it was being made", + ), + "subscribed" => false, + "repurchaseable" => false, + }, + "plans" => [ + { + "currency" => "aud", + "id" => "plan_id123", + "recurring" => { + "interval" => "year", + }, + "unit_amount" => 1220, + }, + { + "currency" => "usd", + "id" => "plan_id234", + "recurring" => { + "interval" => "year", + }, + "unit_amount" => 1399, + }, + { + "currency" => "aud", + "id" => "plan_id678", + "recurring" => { + "interval" => "week", + }, + "unit_amount" => 1000, + }, + ], + }, + ) end end end @@ -146,81 +225,91 @@ module DiscourseSubscriptions ::Stripe::Customer.expects(:create).never ::Stripe::Price.expects(:retrieve).never ::Stripe::Subscription.expects(:create).never - post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' } + post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } end end context "when authenticated" do - before do - sign_in(user) - end + before { sign_in(user) } describe "#create" do - before do - ::Stripe::Customer.expects(:create).returns(id: 'cus_1234') - end + before { ::Stripe::Customer.expects(:create).returns(id: "cus_1234") } it "creates a subscription" do ::Stripe::Price.expects(:retrieve).returns( - type: 'recurring', - product: 'product_12345', + type: "recurring", + product: "product_12345", metadata: { - group_name: 'awesome', - trial_period_days: 0 - } + group_name: "awesome", + trial_period_days: 0, + }, ) - ::Stripe::Subscription.expects(:create).with( - customer: 'cus_1234', - items: [ price: 'plan_1234' ], - metadata: { user_id: user.id, username: user.username_lower }, - trial_period_days: 0, - promotion_code: nil - ).returns(status: 'active', customer: 'cus_1234') + ::Stripe::Subscription + .expects(:create) + .with( + customer: "cus_1234", + items: [price: "plan_1234"], + metadata: { + user_id: user.id, + username: user.username_lower, + }, + trial_period_days: 0, + promotion_code: nil, + ) + .returns(status: "active", customer: "cus_1234") expect { - post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' } + post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.to change { DiscourseSubscriptions::Customer.count } end it "creates a one time payment subscription" do ::Stripe::Price.expects(:retrieve).returns( - type: 'one_time', - product: 'product_12345', + type: "one_time", + product: "product_12345", metadata: { - group_name: 'awesome' - } + group_name: "awesome", + }, ) ::Stripe::InvoiceItem.expects(:create) - ::Stripe::Invoice.expects(:create).returns(status: 'open', id: 'in_123') + ::Stripe::Invoice.expects(:create).returns(status: "open", id: "in_123") - ::Stripe::Invoice.expects(:finalize_invoice).returns(id: 'in_123', status: 'open', payment_intent: 'pi_123') + ::Stripe::Invoice.expects(:finalize_invoice).returns( + id: "in_123", + status: "open", + payment_intent: "pi_123", + ) - ::Stripe::Invoice.expects(:retrieve).returns(id: 'in_123', status: 'open', payment_intent: 'pi_123') + ::Stripe::Invoice.expects(:retrieve).returns( + id: "in_123", + status: "open", + payment_intent: "pi_123", + ) - ::Stripe::PaymentIntent.expects(:retrieve).returns(status: 'successful') + ::Stripe::PaymentIntent.expects(:retrieve).returns(status: "successful") - ::Stripe::Invoice.expects(:pay).returns(status: 'paid', customer: 'cus_1234') + ::Stripe::Invoice.expects(:pay).returns(status: "paid", customer: "cus_1234") expect { - post '/s/create.json', params: { plan: 'plan_1234', source: 'tok_1234' } + post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.to change { DiscourseSubscriptions::Customer.count } end it "creates a customer model" do - ::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: {}).twice - ::Stripe::Subscription.expects(:create).returns(status: 'active', customer: 'cus_1234') + ::Stripe::Price.expects(:retrieve).returns(type: "recurring", metadata: {}).twice + ::Stripe::Subscription.expects(:create).returns(status: "active", customer: "cus_1234") expect { - post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' } + post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.to change { DiscourseSubscriptions::Customer.count } - ::Stripe::Customer.expects(:retrieve).with('cus_1234') + ::Stripe::Customer.expects(:retrieve).with("cus_1234") expect { - post "/s/create.json", params: { plan: 'plan_5678', source: 'tok_5678' } + post "/s/create.json", params: { plan: "plan_5678", source: "tok_5678" } }.not_to change { DiscourseSubscriptions::Customer.count } end @@ -228,19 +317,22 @@ module DiscourseSubscriptions context "with invalid code" do it "prevents use of invalid coupon codes" do ::Stripe::Price.expects(:retrieve).returns( - type: 'recurring', - product: 'product_12345', + type: "recurring", + product: "product_12345", metadata: { - group_name: 'awesome', - trial_period_days: 0 - } + group_name: "awesome", + trial_period_days: 0, + }, ) - ::Stripe::PromotionCode.expects(:list).with({ code: 'invalid' }).returns( - data: [] - ) + ::Stripe::PromotionCode.expects(:list).with({ code: "invalid" }).returns(data: []) - post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234', promo: 'invalid' } + post "/s/create.json", + params: { + plan: "plan_1234", + source: "tok_1234", + promo: "invalid", + } data = response.parsed_body expect(data["errors"]).not_to be_blank @@ -249,61 +341,86 @@ module DiscourseSubscriptions context "with valid code" do before do - ::Stripe::PromotionCode.expects(:list).with({ code: '123' }).returns( - data: [{ - id: 'promo123', - coupon: { id: 'c123' } - }] - ) + ::Stripe::PromotionCode + .expects(:list) + .with({ code: "123" }) + .returns(data: [{ id: "promo123", coupon: { id: "c123" } }]) end it "applies promo code to recurring subscription" do ::Stripe::Price.expects(:retrieve).returns( - type: 'recurring', - product: 'product_12345', + type: "recurring", + product: "product_12345", metadata: { - group_name: 'awesome', - trial_period_days: 0 - } + group_name: "awesome", + trial_period_days: 0, + }, ) - ::Stripe::Subscription.expects(:create).with( - customer: 'cus_1234', - items: [ price: 'plan_1234' ], - metadata: { user_id: user.id, username: user.username_lower }, - trial_period_days: 0, - promotion_code: 'promo123' - ).returns(status: 'active', customer: 'cus_1234') + ::Stripe::Subscription + .expects(:create) + .with( + customer: "cus_1234", + items: [price: "plan_1234"], + metadata: { + user_id: user.id, + username: user.username_lower, + }, + trial_period_days: 0, + promotion_code: "promo123", + ) + .returns(status: "active", customer: "cus_1234") expect { - post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234', promo: '123' } + post "/s/create.json", + params: { + plan: "plan_1234", + source: "tok_1234", + promo: "123", + } }.to change { DiscourseSubscriptions::Customer.count } - end it "applies promo code to one time purchase" do ::Stripe::Price.expects(:retrieve).returns( - type: 'one_time', - product: 'product_12345', + type: "one_time", + product: "product_12345", metadata: { - group_name: 'awesome' - } + group_name: "awesome", + }, ) - ::Stripe::InvoiceItem.expects(:create).with(customer: 'cus_1234', price: 'plan_1234', discounts: [{ coupon: 'c123' }]) + ::Stripe::InvoiceItem.expects(:create).with( + customer: "cus_1234", + price: "plan_1234", + discounts: [{ coupon: "c123" }], + ) - ::Stripe::Invoice.expects(:create).returns(status: 'open', id: 'in_123') + ::Stripe::Invoice.expects(:create).returns(status: "open", id: "in_123") - ::Stripe::Invoice.expects(:finalize_invoice).returns(id: 'in_123', status: 'open', payment_intent: 'pi_123') + ::Stripe::Invoice.expects(:finalize_invoice).returns( + id: "in_123", + status: "open", + payment_intent: "pi_123", + ) - ::Stripe::Invoice.expects(:retrieve).returns(id: 'in_123', status: 'open', payment_intent: 'pi_123') + ::Stripe::Invoice.expects(:retrieve).returns( + id: "in_123", + status: "open", + payment_intent: "pi_123", + ) - ::Stripe::PaymentIntent.expects(:retrieve).returns(status: 'successful') + ::Stripe::PaymentIntent.expects(:retrieve).returns(status: "successful") - ::Stripe::Invoice.expects(:pay).returns(status: 'paid', customer: 'cus_1234') + ::Stripe::Invoice.expects(:pay).returns(status: "paid", customer: "cus_1234") expect { - post '/s/create.json', params: { plan: 'plan_1234', source: 'tok_1234', promo: '123' } + post "/s/create.json", + params: { + plan: "plan_1234", + source: "tok_1234", + promo: "123", + } }.to change { DiscourseSubscriptions::Customer.count } end end @@ -313,81 +430,114 @@ module DiscourseSubscriptions describe "#finalize strong customer authenticated transaction" do context "with subscription" do it "finalizes the subscription" do - ::Stripe::Price.expects(:retrieve).returns(id: "plan_1234", product: "prod_1234", metadata: {}) - ::Stripe::Subscription.expects(:retrieve).returns(id: "sub_123", customer: 'cus_1234', status: "active") + ::Stripe::Price.expects(:retrieve).returns( + id: "plan_1234", + product: "prod_1234", + metadata: { + }, + ) + ::Stripe::Subscription.expects(:retrieve).returns( + id: "sub_123", + customer: "cus_1234", + status: "active", + ) expect { - post "/s/finalize.json", params: { plan: 'plan_1234', transaction: 'sub_1234' } + post "/s/finalize.json", params: { plan: "plan_1234", transaction: "sub_1234" } }.to change { DiscourseSubscriptions::Customer.count } end end context "with one-time payment" do it "finalizes the one-time payment" do - ::Stripe::Price.expects(:retrieve).returns(id: "plan_1234", product: "prod_1234", metadata: {}) - ::Stripe::Invoice.expects(:retrieve).returns(id: "in_123", customer: 'cus_1234', status: "paid") + ::Stripe::Price.expects(:retrieve).returns( + id: "plan_1234", + product: "prod_1234", + metadata: { + }, + ) + ::Stripe::Invoice.expects(:retrieve).returns( + id: "in_123", + customer: "cus_1234", + status: "paid", + ) expect { - post "/s/finalize.json", params: { plan: 'plan_1234', transaction: 'in_1234' } + post "/s/finalize.json", params: { plan: "plan_1234", transaction: "in_1234" } }.to change { DiscourseSubscriptions::Customer.count } end end end describe "user groups" do - let(:group_name) { 'group-123' } + let(:group_name) { "group-123" } let(:group) { Fabricate(:group, name: group_name) } context "with unauthorized group" do before do - ::Stripe::Customer.expects(:create).returns(id: 'cus_1234') - ::Stripe::Subscription.expects(:create).returns(status: 'active') + ::Stripe::Customer.expects(:create).returns(id: "cus_1234") + ::Stripe::Subscription.expects(:create).returns(status: "active") end it "does not add the user to the admins group" do - ::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: 'admins' }) - post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' } + ::Stripe::Price.expects(:retrieve).returns( + type: "recurring", + metadata: { + group_name: "admins", + }, + ) + post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } expect(user.admin).to eq false end it "does not add the user to other group" do - ::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: 'other' }) - post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' } + ::Stripe::Price.expects(:retrieve).returns( + type: "recurring", + metadata: { + group_name: "other", + }, + ) + post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } expect(user.groups).to be_empty end end context "when plan has group in metadata" do before do - ::Stripe::Customer.expects(:create).returns(id: 'cus_1234') - ::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: group_name }) + ::Stripe::Customer.expects(:create).returns(id: "cus_1234") + ::Stripe::Price.expects(:retrieve).returns( + type: "recurring", + metadata: { + group_name: group_name, + }, + ) end it "does not add the user to the group when subscription fails" do - ::Stripe::Subscription.expects(:create).returns(status: 'failed') + ::Stripe::Subscription.expects(:create).returns(status: "failed") expect { - post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' } + post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.not_to change { group.users.count } expect(user.groups).to be_empty end it "adds the user to the group when the subscription is active" do - ::Stripe::Subscription.expects(:create).returns(status: 'active') + ::Stripe::Subscription.expects(:create).returns(status: "active") expect { - post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' } + post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.to change { group.users.count } expect(user.groups).not_to be_empty end it "adds the user to the group when the subscription is trialing" do - ::Stripe::Subscription.expects(:create).returns(status: 'trialing') + ::Stripe::Subscription.expects(:create).returns(status: "trialing") expect { - post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' } + post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.to change { group.users.count } expect(user.groups).not_to be_empty diff --git a/spec/requests/user/payments_controller_spec.rb b/spec/requests/user/payments_controller_spec.rb index 2398fb4..07196ac 100644 --- a/spec/requests/user/payments_controller_spec.rb +++ b/spec/requests/user/payments_controller_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" module DiscourseSubscriptions RSpec.describe User::PaymentsController do - it 'is a subclass of ApplicationController' do + it "is a subclass of ApplicationController" do expect(DiscourseSubscriptions::User::PaymentsController < ::ApplicationController).to eq(true) end @@ -17,80 +17,39 @@ module DiscourseSubscriptions end context "when authenticated" do - let(:user) { Fabricate(:user, email: 'zasch@example.com') } + let(:user) { Fabricate(:user, email: "zasch@example.com") } before do sign_in(user) - Fabricate(:customer, customer_id: 'c_345678', user_id: user.id) - Fabricate(:product, external_id: 'prod_8675309') - Fabricate(:product, external_id: 'prod_8675310') + Fabricate(:customer, customer_id: "c_345678", user_id: user.id) + Fabricate(:product, external_id: "prod_8675309") + Fabricate(:product, external_id: "prod_8675310") end it "gets payment intents" do created_time = Time.now - ::Stripe::Invoice.expects(:list).with( - customer: 'c_345678' - ).returns( - data: [ - { - id: "inv_900007", - lines: { - data: [ - plan: { - product: "prod_8675309" - } - ] - } - }, - { - id: "inv_900008", - lines: { - data: [ - plan: { - product: "prod_8675310" - } - ] - } - }, - { - id: "inv_900008", - lines: { - data: [ - plan: { - product: "prod_8675310" - } - ] - } - }, - ] - ) + ::Stripe::Invoice + .expects(:list) + .with(customer: "c_345678") + .returns( + data: [ + { id: "inv_900007", lines: { data: [plan: { product: "prod_8675309" }] } }, + { id: "inv_900008", lines: { data: [plan: { product: "prod_8675310" }] } }, + { id: "inv_900008", lines: { data: [plan: { product: "prod_8675310" }] } }, + ], + ) - ::Stripe::PaymentIntent.expects(:list).with( - customer: 'c_345678', - ).returns( - data: [ - { - id: "pi_900008", - invoice: "inv_900008", - created: created_time - }, - { - id: "pi_900008", - invoice: "inv_900008", - created: created_time - }, - { - id: "pi_900007", - invoice: "inv_900007", - created: Time.now - }, - { - id: "pi_007", - invoice: "inv_007", - created: Time.now - } - ] - ) + ::Stripe::PaymentIntent + .expects(:list) + .with(customer: "c_345678") + .returns( + data: [ + { id: "pi_900008", invoice: "inv_900008", created: created_time }, + { id: "pi_900008", invoice: "inv_900008", created: created_time }, + { id: "pi_900007", invoice: "inv_900007", created: Time.now }, + { id: "pi_007", invoice: "inv_007", created: Time.now }, + ], + ) get "/s/user/payments.json" @@ -99,9 +58,7 @@ module DiscourseSubscriptions expect(invoice).to eq("inv_900007") expect(parsed_body.count).to eq(2) - end - end end end diff --git a/spec/requests/user/subscriptions_controller_spec.rb b/spec/requests/user/subscriptions_controller_spec.rb index 151babf..d7399c2 100644 --- a/spec/requests/user/subscriptions_controller_spec.rb +++ b/spec/requests/user/subscriptions_controller_spec.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" module DiscourseSubscriptions RSpec.describe User::SubscriptionsController do - it 'is a subclass of ApplicationController' do - expect(DiscourseSubscriptions::User::SubscriptionsController < ::ApplicationController).to eq(true) + it "is a subclass of ApplicationController" do + expect(DiscourseSubscriptions::User::SubscriptionsController < ::ApplicationController).to eq( + true, + ) end context "when not authenticated" do @@ -27,8 +29,10 @@ module DiscourseSubscriptions end context "when authenticated" do - let(:user) { Fabricate(:user, email: 'beanie@example.com') } - let(:customer) { Fabricate(:customer, user_id: user.id, customer_id: "cus_23456", product_id: "prod_123") } + let(:user) { Fabricate(:user, email: "beanie@example.com") } + let(:customer) do + Fabricate(:customer, user_id: user.id, customer_id: "cus_23456", product_id: "prod_123") + end before do sign_in(user) @@ -39,42 +43,35 @@ module DiscourseSubscriptions let(:plans) do { data: [ - { - id: "plan_1", - product: { name: 'ACME Subscriptions' }, - }, - { - id: "plan_2", - product: { name: 'ACME Other Subscriptions' }, - } - ] + { id: "plan_1", product: { name: "ACME Subscriptions" } }, + { id: "plan_2", product: { name: "ACME Other Subscriptions" } }, + ], } end let(:customers) do { - data: [{ - id: "cus_23456", - subscriptions: { - data: [ - { id: "sub_1234", items: { data: [price: { id: "plan_1" }] } }, - { id: "sub_4567", items: { data: [price: { id: "plan_2" }] } } - ] + data: [ + { + id: "cus_23456", + subscriptions: { + data: [ + { id: "sub_1234", items: { data: [price: { id: "plan_1" }] } }, + { id: "sub_4567", items: { data: [price: { id: "plan_2" }] } }, + ], + }, }, - }] + ], } end it "gets subscriptions" do - ::Stripe::Price.expects(:list).with( - expand: ['data.product'], - limit: 100 - ).returns(plans) + ::Stripe::Price.expects(:list).with(expand: ["data.product"], limit: 100).returns(plans) - ::Stripe::Customer.expects(:list).with( - email: user.email, - expand: ['data.subscriptions'] - ).returns(customers) + ::Stripe::Customer + .expects(:list) + .with(email: user.email, expand: ["data.subscriptions"]) + .returns(customers) get "/s/user/subscriptions.json" @@ -82,9 +79,18 @@ module DiscourseSubscriptions expect(subscription).to eq( "id" => "sub_1234", - "items" => { "data" => [{ "price" => { "id" => "plan_1" } }] }, - "plan" => { "id" => "plan_1", "product" => { "name" => "ACME Subscriptions" } }, - "product" => { "name" => "ACME Subscriptions" } + "items" => { + "data" => [{ "price" => { "id" => "plan_1" } }], + }, + "plan" => { + "id" => "plan_1", + "product" => { + "name" => "ACME Subscriptions", + }, + }, + "product" => { + "name" => "ACME Subscriptions", + }, ) end end diff --git a/spec/serializers/site_serializer_spec.rb b/spec/serializers/site_serializer_spec.rb index 96b53d2..6ea61e6 100644 --- a/spec/serializers/site_serializer_spec.rb +++ b/spec/serializers/site_serializer_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" describe SiteSerializer do - fab!(:user) { Fabricate(:user) } + fab!(:user) { Fabricate(:user) } let(:guardian) { Guardian.new(user) } before do @@ -11,22 +11,22 @@ describe SiteSerializer do SiteSetting.discourse_subscriptions_enabled = true SiteSetting.discourse_subscriptions_campaign_enabled = true end - it 'is false if the goal_met date is < 7 days old' do - Discourse.redis.set('subscriptions_goal_met_date', 10.days.ago) + it "is false if the goal_met date is < 7 days old" do + Discourse.redis.set("subscriptions_goal_met_date", 10.days.ago) data = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json expect(data[:show_campaign_banner]).to be false end - it 'is true if the goal_met date is > 7 days old' do - Discourse.redis.set('subscriptions_goal_met_date', 1.days.ago) + it "is true if the goal_met date is > 7 days old" do + Discourse.redis.set("subscriptions_goal_met_date", 1.days.ago) data = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json expect(data[:show_campaign_banner]).to be true end - it 'fails gracefully if the goal_met date is invalid' do - Discourse.redis.set('subscriptions_goal_met_date', 'bananas') + it "fails gracefully if the goal_met date is invalid" do + Discourse.redis.set("subscriptions_goal_met_date", "bananas") data = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json expect(data[:show_campaign_banner]).to be false end diff --git a/spec/services/campaign_spec.rb b/spec/services/campaign_spec.rb index 3ae8eba..583420b 100644 --- a/spec/services/campaign_spec.rb +++ b/spec/services/campaign_spec.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" describe DiscourseSubscriptions::Campaign do - describe 'campaign data is refreshed' do - let (:user) { Fabricate(:user) } - let (:user2) { Fabricate(:user) } + describe "campaign data is refreshed" do + let (:user) { + Fabricate(:user) + } + let (:user2) { + Fabricate(:user) + } let(:subscription) do { id: "sub_1234", @@ -16,12 +20,12 @@ describe DiscourseSubscriptions::Campaign do product: "prodct_23456", unit_amount: 1000, recurring: { - interval: "month" - } - } - } - ] - } + interval: "month", + }, + }, + }, + ], + }, } end let(:invoice) do @@ -37,10 +41,10 @@ describe DiscourseSubscriptions::Campaign do active: true, unit_amount: 1000, recurring: nil, - } - } - ] - } + }, + }, + ], + }, } end let(:invoice2) do @@ -56,20 +60,20 @@ describe DiscourseSubscriptions::Campaign do active: true, unit_amount: 600, 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(: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') + Fabricate(:customer, product_id: "prodct_65432", user_id: user2.id, customer_id: "y") Fabricate(:product, external_id: "prodct_65433") - Fabricate(:customer, product_id: "prodct_65433", user_id: user2.id, customer_id: 'y') + Fabricate(:customer, product_id: "prodct_65433", user_id: user2.id, customer_id: "y") SiteSetting.discourse_subscriptions_public_key = "public-key" SiteSetting.discourse_subscriptions_secret_key = "secret-key" end @@ -92,17 +96,17 @@ describe DiscourseSubscriptions::Campaign do ::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 + 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 = 25 - Discourse.redis.set('subscriptions_goal_met_date', 10.days.ago) + 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 + expect(Discourse.redis.get("subscriptions_goal_met_date")).to be_blank end end @@ -116,25 +120,28 @@ describe DiscourseSubscriptions::Campaign do { price: { product: "prod_use", - unit_amount: 10000, + unit_amount: 10_000, recurring: { - interval: "year" - } - } - } - ] - } + interval: "year", + }, + }, + }, + ], + }, } end before do Fabricate(:product, external_id: "prod_use") - Fabricate(:customer, product_id: "prod_use", user_id: user2.id, customer_id: 'y') + Fabricate(:customer, product_id: "prod_use", user_id: user2.id, customer_id: "y") SiteSetting.discourse_subscriptions_campaign_product = "prod_use" end it "refreshes campaign data with only the campaign product/subscriptions" do - ::Stripe::Subscription.expects(:list).returns(data: [subscription, campaign_subscription], has_more: false) + ::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