2 Commits
275 changed files with 6308 additions and 15577 deletions
-7
View File
@@ -1,7 +0,0 @@
< 3.5.0.beta5-dev: 7309c937dddb1933ac66aa2d3e13d071060d30ff
< 3.5.0.beta1-dev: b4f7be4274ada9400238f8692e705669034c1709
< 3.4.0.beta2-dev: 692f410ab59e329d0aaf537cd772698f83c32a8a
< 3.4.0.beta1-dev: bb96d77056a9ef842e01e46fd980320d427073ad
< 3.3.0.beta1-dev: 0dcf17de59bdf82c0233bb630dad0ce407d1ae76
3.1.999: c4f8220a5572ef9a42a9a71500e001d0df206c43
2.9.0.beta9: 39b7253c082270e33839f4b0821fbf75abfdd826
+6
View File
@@ -0,0 +1,6 @@
{
"globals": {
"Stripe": true
},
"extends": "eslint-config-discourse"
}
-11
View File
@@ -1,11 +0,0 @@
name: Discourse Plugin
on:
push:
branches:
- main
pull_request:
jobs:
ci:
uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1
+57
View File
@@ -0,0 +1,57 @@
name: Linting
on:
push:
branches:
- main
pull_request:
concurrency:
group: plugin-linting-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16
cache: yarn
- name: Yarn install
run: yarn install
- name: Set up ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler-cache: true
- name: ESLint
if: ${{ always() }}
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts
- name: Prettier
if: ${{ always() }}
shell: bash
run: |
yarn prettier -v
if [ 0 -lt $(find assets -type f \( -name "*.scss" -or -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
yarn prettier --list-different "assets/**/*.{scss,js,es6}"
fi
if [ 0 -lt $(find test -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
yarn prettier --list-different "test/**/*.{js,es6}"
fi
- name: Ember template lint
if: ${{ always() }}
run: yarn ember-template-lint --no-error-on-unmatched-pattern assets/javascripts
- name: Rubocop
if: ${{ always() }}
run: bundle exec rubocop .
+156
View File
@@ -0,0 +1,156 @@
name: Plugin Tests
on:
push:
branches:
- main
pull_request:
concurrency:
group: plugin-tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
cancel-in-progress: true
jobs:
build:
name: ${{ matrix.build_type }}
runs-on: ubuntu-latest
container: discourse/discourse_test:slim${{ startsWith(matrix.build_type, 'frontend') && '-browsers' || '' }}
timeout-minutes: 30
env:
DISCOURSE_HOSTNAME: www.example.com
RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072
RAILS_ENV: test
PGUSER: discourse
PGPASSWORD: discourse
strategy:
fail-fast: false
matrix:
build_type: ["backend", "frontend-legacy", "frontend"]
steps:
- uses: actions/checkout@v3
with:
repository: discourse/discourse
fetch-depth: 1
- name: Install plugin
uses: actions/checkout@v3
with:
path: plugins/${{ github.event.repository.name }}
fetch-depth: 1
- name: Setup Git
run: |
git config --global user.email "[email protected]"
git config --global user.name "Discourse CI"
- name: Start redis
run: |
redis-server /etc/redis/redis.conf &
- name: Start Postgres
run: |
chown -R postgres /var/run/postgresql
sudo -E -u postgres script/start_test_db.rb
sudo -u postgres psql -c "CREATE ROLE $PGUSER LOGIN SUPERUSER PASSWORD '$PGPASSWORD';"
- name: Bundler cache
uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
- name: Setup gems
run: |
gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' Gemfile.lock)
bundle config --local path vendor/bundle
bundle config --local deployment true
bundle config --local without development
bundle install --jobs 4
bundle clean
- name: Lint English locale
if: matrix.build_type == 'backend'
run: bundle exec ruby script/i18n_lint.rb "plugins/${{ github.event.repository.name }}/locales/{client,server}.en.yml"
- name: Get yarn cache directory
id: yarn-cache-dir
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Yarn install
run: yarn install
- name: Fetch app state cache
uses: actions/cache@v3
id: app-cache
with:
path: tmp/app-cache
key: >-
${{ hashFiles('.github/workflows/tests.yml') }}-
${{ hashFiles('db/**/*', 'plugins/**/db/**/*') }}-
- name: Restore database from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: psql -f tmp/app-cache/cache.sql postgres
- name: Restore uploads from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: rm -rf public/uploads && cp -r tmp/app-cache/uploads public/uploads
- name: Create and migrate database
if: steps.app-cache.outputs.cache-hit != 'true'
run: |
bin/rake db:create
bin/rake db:migrate
- name: Dump database for cache
if: steps.app-cache.outputs.cache-hit != 'true'
run: mkdir -p tmp/app-cache && pg_dumpall > tmp/app-cache/cache.sql
- name: Dump uploads for cache
if: steps.app-cache.outputs.cache-hit != 'true'
run: rm -rf tmp/app-cache/uploads && cp -r public/uploads tmp/app-cache/uploads
- name: Check spec existence
id: check_spec
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"
fi
- name: Plugin RSpec
if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exist == 'true'
run: bin/rake plugin:spec[${{ github.event.repository.name }}]
- name: Check qunit existence
id: check_qunit
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"
fi
- name: Plugin QUnit
if: matrix.build_type == 'frontend-legacy' && steps.check_qunit.outputs.files_exist == 'true'
run: QUNIT_EMBER_CLI=0 bundle exec rake plugin:qunit['${{ github.event.repository.name }}','1200000']
timeout-minutes: 10
- name: Plugin QUnit (Ember CLI)
if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exist == 'true'
run: QUNIT_EMBER_CLI=1 bundle exec rake plugin:qunit['${{ github.event.repository.name }}','1200000']
timeout-minutes: 10
-2
View File
@@ -1,2 +0,0 @@
engine-strict = true
auto-install-peers = false
+1
View File
@@ -0,0 +1 @@
{}
-1
View File
@@ -1 +0,0 @@
module.exports = require("@discourse/lint-configs/prettier");
+1 -1
View File
@@ -1,5 +1,5 @@
inherit_gem: inherit_gem:
rubocop-discourse: stree-compat.yml rubocop-discourse: default.yml
AllCops: AllCops:
Exclude: Exclude:
- "gems/**/*" - "gems/**/*"
-2
View File
@@ -1,2 +0,0 @@
--print-width=100
--plugins=plugin/trailing_comma
-1
View File
@@ -1 +0,0 @@
module.exports = require("@discourse/lint-configs/template-lint");
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
plugins: ["ember-template-lint-plugin-discourse"],
extends: "discourse:recommended",
};
+3 -4
View File
@@ -1,9 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
source "https://rubygems.org" source 'https://rubygems.org'
group :development do group :development do
gem "translations-manager", git: "https://github.com/discourse/translations-manager.git" gem 'translations-manager', git: 'https://github.com/discourse/translations-manager.git'
gem "rubocop-discourse" gem 'rubocop-discourse'
gem "syntax_tree"
end end
+21 -86
View File
@@ -7,103 +7,38 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
activesupport (8.0.2) ast (2.4.2)
base64 parallel (1.22.1)
benchmark (>= 0.3) parser (3.1.2.0)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
ast (2.4.3)
base64 (0.3.0)
benchmark (0.4.1)
bigdecimal (3.2.0)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
drb (2.2.3)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
json (2.12.2)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.7.0)
minitest (5.25.5)
parallel (1.27.0)
parser (3.3.8.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc
prettier_print (1.2.1)
prism (1.4.0)
racc (1.8.1)
rack (3.1.16)
rainbow (3.1.1) rainbow (3.1.1)
regexp_parser (2.10.0) regexp_parser (2.5.0)
rubocop (1.75.8) rexml (3.2.5)
json (~> 2.3) rubocop (1.30.1)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.3.0.2) parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0) regexp_parser (>= 1.8, < 3.0)
rubocop-ast (>= 1.44.0, < 2.0) rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.18.0, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0) unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.44.1) rubocop-ast (1.18.0)
parser (>= 3.3.7.2) parser (>= 3.1.1.0)
prism (~> 1.4) rubocop-discourse (2.5.0)
rubocop-capybara (2.22.1) rubocop (>= 1.1.0)
lint_roller (~> 1.1) rubocop-rspec (>= 2.0.0)
rubocop (~> 1.72, >= 1.72.1) rubocop-rspec (2.11.1)
rubocop-discourse (3.12.1) rubocop (~> 1.19)
activesupport (>= 6.1) ruby-progressbar (1.11.0)
lint_roller (>= 1.1.0) unicode-display_width (2.1.0)
rubocop (>= 1.73.2)
rubocop-capybara (>= 2.22.0)
rubocop-factory_bot (>= 2.27.0)
rubocop-rails (>= 2.30.3)
rubocop-rspec (>= 3.0.1)
rubocop-rspec_rails (>= 2.31.0)
rubocop-factory_bot (2.27.1)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rails (2.32.0)
activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.44.0, < 2.0)
rubocop-rspec (3.6.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec_rails (2.31.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec (~> 3.5)
ruby-progressbar (1.13.0)
securerandom (0.4.1)
syntax_tree (6.2.0)
prettier_print (>= 1.2.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
uri (1.0.3)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
rubocop-discourse rubocop-discourse
syntax_tree
translations-manager! translations-manager!
BUNDLED WITH BUNDLED WITH
2.6.9 2.2.19
-3
View File
@@ -1,8 +1,5 @@
# Discourse Subscriptions Plugin # Discourse Subscriptions Plugin
> [!IMPORTANT]
> This plugin has now been bundled into Discourse core. See: https://meta.discourse.org/t/bundling-more-popular-plugins-with-discourse-core/373574
Discourse Subscriptions allows site owners to sell recurring and one-time purchase subscriptions that grant access to a group on a Discourse instance. Discourse Subscriptions allows site owners to sell recurring and one-time purchase subscriptions that grant access to a group on a Discourse instance.
For more information, please see: https://meta.discourse.org/t/discourse-subscriptions-plugin/140818 For more information, please see: https://meta.discourse.org/t/discourse-subscriptions-plugin/140818
+1 -2
View File
@@ -9,8 +9,7 @@ module DiscourseSubscriptions
end end
def is_stripe_configured? def is_stripe_configured?
SiteSetting.discourse_subscriptions_public_key.present? && SiteSetting.discourse_subscriptions_public_key.present? && SiteSetting.discourse_subscriptions_secret_key.present?
SiteSetting.discourse_subscriptions_secret_key.present?
end end
end end
end end
@@ -5,9 +5,6 @@ module DiscourseSubscriptions
class CouponsController < ::Admin::AdminController class CouponsController < ::Admin::AdminController
include DiscourseSubscriptions::Stripe include DiscourseSubscriptions::Stripe
include DiscourseSubscriptions::Group include DiscourseSubscriptions::Group
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key before_action :set_api_key
def index def index
@@ -21,24 +18,23 @@ module DiscourseSubscriptions
end end
def create def create
params.require(%i[promo discount_type discount active]) params.require([:promo, :discount_type, :discount, :active])
begin begin
coupon_params = { duration: "forever" } coupon_params = {
duration: 'forever',
}
case params[:discount_type] case params[:discount_type]
when "amount" when 'amount'
coupon_params[:amount_off] = params[:discount].to_i * 100 coupon_params[:amount_off] = params[:discount].to_i * 100
coupon_params[:currency] = SiteSetting.discourse_subscriptions_currency coupon_params[:currency] = SiteSetting.discourse_subscriptions_currency
when "percent" when 'percent'
coupon_params[:percent_off] = params[:discount] coupon_params[:percent_off] = params[:discount]
end end
coupon = ::Stripe::Coupon.create(coupon_params) coupon = ::Stripe::Coupon.create(coupon_params)
promo_code = promo_code = ::Stripe::PromotionCode.create({ coupon: coupon[:id], code: params[:promo] }) if coupon.present?
::Stripe::PromotionCode.create(
{ coupon: coupon[:id], code: params[:promo] },
) if coupon.present?
render_json_dump promo_code render_json_dump promo_code
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
@@ -47,9 +43,14 @@ module DiscourseSubscriptions
end end
def update def update
params.require(%i[id active]) params.require([:id, :active])
begin 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 render_json_dump promo_code
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
@@ -5,8 +5,6 @@ module DiscourseSubscriptions
class PlansController < ::Admin::AdminController class PlansController < ::Admin::AdminController
include DiscourseSubscriptions::Stripe include DiscourseSubscriptions::Stripe
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key before_action :set_api_key
def index def index
@@ -29,11 +27,15 @@ module DiscourseSubscriptions
active: params[:active], active: params[:active],
metadata: { metadata: {
group_name: params[:metadata][:group_name], group_name: params[:metadata][:group_name],
trial_period_days: params[:trial_period_days], trial_period_days: params[:trial_period_days]
}, }
} }
price_object[:recurring] = { interval: params[:interval] } if params[:type] == "recurring" if params[:type] == 'recurring'
price_object[:recurring] = {
interval: params[:interval]
}
end
plan = ::Stripe::Price.create(price_object) plan = ::Stripe::Price.create(price_object)
@@ -54,16 +56,14 @@ module DiscourseSubscriptions
end end
interval = nil interval = nil
interval = plan[:recurring][:interval] if plan[:recurring] && plan[:recurring][:interval] if plan[:recurring] && plan[:recurring][:interval]
interval = plan[:recurring][:interval]
end
serialized = serialized = plan.to_h.merge(trial_period_days: trial_days, currency: plan[:currency].upcase, interval: interval)
plan.to_h.merge(
trial_period_days: trial_days,
currency: plan[:currency].upcase,
interval: interval,
)
render_json_dump serialized render_json_dump serialized
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
render_json_error e.message render_json_error e.message
end end
@@ -71,16 +71,15 @@ module DiscourseSubscriptions
def update def update
begin begin
plan = plan = ::Stripe::Price.update(
::Stripe::Price.update( params[:id],
params[:id], nickname: params[:nickname],
nickname: params[:nickname], active: params[:active],
active: params[:active], metadata: {
metadata: { group_name: params[:metadata][:group_name],
group_name: params[:metadata][:group_name], trial_period_days: params[:trial_period_days]
trial_period_days: params[:trial_period_days], }
}, )
)
render_json_dump plan render_json_dump plan
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
@@ -5,8 +5,6 @@ module DiscourseSubscriptions
class ProductsController < ::Admin::AdminController class ProductsController < ::Admin::AdminController
include DiscourseSubscriptions::Stripe include DiscourseSubscriptions::Stripe
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key before_action :set_api_key
def index def index
@@ -29,15 +27,20 @@ module DiscourseSubscriptions
def create def create
begin begin
create_params = product_params.merge!(type: "service") create_params = product_params.merge!(type: 'service')
create_params.except!(:statement_descriptor) if params[:statement_descriptor].blank? if params[:statement_descriptor].blank?
create_params.except!(:statement_descriptor)
end
product = ::Stripe::Product.create(create_params) product = ::Stripe::Product.create(create_params)
Product.create(external_id: product[:id]) Product.create(
external_id: product[:id]
)
render_json_dump product render_json_dump product
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
render_json_error e.message render_json_error e.message
end end
@@ -48,6 +51,7 @@ module DiscourseSubscriptions
product = ::Stripe::Product.retrieve(params[:id]) product = ::Stripe::Product.retrieve(params[:id])
render_json_dump product render_json_dump product
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
render_json_error e.message render_json_error e.message
end end
@@ -55,9 +59,13 @@ module DiscourseSubscriptions
def update def update
begin begin
product = ::Stripe::Product.update(params[:id], product_params) product = ::Stripe::Product.update(
params[:id],
product_params
)
render_json_dump product render_json_dump product
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
render_json_error e.message render_json_error e.message
end end
@@ -70,6 +78,7 @@ module DiscourseSubscriptions
Product.delete_by(external_id: params[:id]) Product.delete_by(external_id: params[:id])
render_json_dump product render_json_dump product
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
render_json_error e.message render_json_error e.message
end end
@@ -86,8 +95,8 @@ module DiscourseSubscriptions
statement_descriptor: params[:statement_descriptor], statement_descriptor: params[:statement_descriptor],
metadata: { metadata: {
description: params.dig(:metadata, :description), description: params.dig(:metadata, :description),
repurchaseable: params.dig(:metadata, :repurchaseable), repurchaseable: params.dig(:metadata, :repurchaseable)
}, }
} }
end end
end end
@@ -3,8 +3,6 @@
module DiscourseSubscriptions module DiscourseSubscriptions
module Admin module Admin
class SubscriptionsController < ::Admin::AdminController class SubscriptionsController < ::Admin::AdminController
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
include DiscourseSubscriptions::Stripe include DiscourseSubscriptions::Stripe
include DiscourseSubscriptions::Group include DiscourseSubscriptions::Group
before_action :set_api_key before_action :set_api_key
@@ -18,23 +16,20 @@ module DiscourseSubscriptions
has_more: false, has_more: false,
data: [], data: [],
length: 0, length: 0,
last_record: params[:last_record], last_record: params[:last_record]
} }
if subscription_ids.present? && is_stripe_configured? if subscription_ids.present? && is_stripe_configured?
while subscriptions[:length] < PAGE_LIMIT while subscriptions[:length] < PAGE_LIMIT
current_set = get_subscriptions(subscriptions[:last_record]) current_set = get_subscriptions(subscriptions[:last_record])
until valid_subscriptions = until valid_subscriptions = find_valid_subscriptions(current_set[:data], subscription_ids) do
find_valid_subscriptions(current_set[:data], subscription_ids)
current_set = get_subscriptions(current_set[:data].last) current_set = get_subscriptions(current_set[:data].last)
break if current_set[:has_more] == false break if current_set[:has_more] == false
end end
subscriptions[:data] = subscriptions[:data].concat(valid_subscriptions.to_a) subscriptions[:data] = subscriptions[:data].concat(valid_subscriptions.to_a)
subscriptions[:last_record] = current_set[:data].last[:id] if current_set[ subscriptions[:last_record] = current_set[:data].last[:id] if current_set[:data].present?
:data
].present?
subscriptions[:length] = subscriptions[:data].length subscriptions[:length] = subscriptions[:data].length
subscriptions[:has_more] = current_set[:has_more] subscriptions[:has_more] = current_set[:has_more]
break if subscriptions[:has_more] == false break if subscriptions[:has_more] == false
@@ -53,21 +48,24 @@ module DiscourseSubscriptions
params.require(:id) params.require(:id)
begin begin
refund_subscription(params[:id]) if params[:refund] refund_subscription(params[:id]) if params[:refund]
subscription = ::Stripe::Subscription.cancel(params[:id]) subscription = ::Stripe::Subscription.delete(params[:id])
customer = customer = Customer.find_by(
Customer.find_by( product_id: subscription[:plan][:product],
product_id: subscription[:plan][:product], customer_id: subscription[:customer]
customer_id: subscription[:customer], )
)
Subscription.delete_by(external_id: params[:id])
if customer if customer
user = ::User.find(customer.user_id) user = ::User.find(customer.user_id)
customer.delete
group = plan_group(subscription[:plan]) group = plan_group(subscription[:plan])
group.remove(user) if group group.remove(user) if group
end end
render_json_dump subscription render_json_dump subscription
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
render_json_error e.message render_json_error e.message
end end
@@ -76,12 +74,7 @@ module DiscourseSubscriptions
private private
def get_subscriptions(start) def get_subscriptions(start)
::Stripe::Subscription.list( ::Stripe::Subscription.list(expand: ['data.plan.product'], limit: PAGE_LIMIT, starting_after: start)
expand: ["data.plan.product"],
limit: PAGE_LIMIT,
starting_after: start,
status: "all",
)
end end
def find_valid_subscriptions(data, ids) def find_valid_subscriptions(data, ids)
@@ -92,11 +85,11 @@ module DiscourseSubscriptions
# this will only refund the most recent subscription payment # this will only refund the most recent subscription payment
def refund_subscription(subscription_id) def refund_subscription(subscription_id)
subscription = ::Stripe::Subscription.retrieve(subscription_id) subscription = ::Stripe::Subscription.retrieve(subscription_id)
invoice = ::Stripe::Invoice.retrieve(subscription[:latest_invoice]) if subscription[ invoice = ::Stripe::Invoice.retrieve(subscription[:latest_invoice]) if subscription[:latest_invoice]
:latest_invoice
]
payment_intent = invoice[:payment_intent] if invoice[:payment_intent] 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 end
end end
@@ -2,8 +2,6 @@
module DiscourseSubscriptions module DiscourseSubscriptions
class AdminController < ::Admin::AdminController class AdminController < ::Admin::AdminController
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
def index def index
head 200 head 200
end end
@@ -4,12 +4,7 @@ module DiscourseSubscriptions
class HooksController < ::ApplicationController class HooksController < ::ApplicationController
include DiscourseSubscriptions::Group include DiscourseSubscriptions::Group
include DiscourseSubscriptions::Stripe include DiscourseSubscriptions::Stripe
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
layout false layout false
before_action :set_api_key
skip_before_action :check_xhr skip_before_action :check_xhr
skip_before_action :redirect_to_login_if_required skip_before_action :redirect_to_login_if_required
skip_before_action :verify_authenticity_token, only: [:create] skip_before_action :verify_authenticity_token, only: [:create]
@@ -17,109 +12,33 @@ module DiscourseSubscriptions
def create def create
begin begin
payload = request.body.read 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 webhook_secret = SiteSetting.discourse_subscriptions_webhook_secret
event = ::Stripe::Webhook.construct_event(payload, sig_header, webhook_secret) event = ::Stripe::Webhook.construct_event(payload, sig_header, webhook_secret)
rescue JSON::ParserError => e rescue JSON::ParserError => e
return render_json_error e.message render_json_error e.message
return
rescue ::Stripe::SignatureVerificationError => e rescue ::Stripe::SignatureVerificationError => e
return render_json_error e.message render_json_error e.message
return
end end
case event[:type] case event[:type]
when "checkout.session.completed" when 'customer.subscription.updated'
checkout_session = event[:data][:object] customer = Customer.find_by(
customer_id: event[:data][:object][:customer],
product_id: event[:data][:object][:plan][:product]
)
if SiteSetting.discourse_subscriptions_enable_verbose_logging if customer && subscription_completion?(event)
Rails.logger.warn("checkout.session.completed data: #{checkout_session}") user = ::User.find(customer.user_id)
end group = plan_group(event[:data][:object][:plan])
email = checkout_session[:customer_email] group.add(user) if group
return head 200 if checkout_session[:status] != "complete"
return render_json_error "email not found" if !email
if checkout_session[:customer].nil?
customer = ::Stripe::Customer.create({ email: email })
customer_id = customer[:id]
else
customer_id = checkout_session[:customer]
end end
if SiteSetting.discourse_subscriptions_enable_verbose_logging when 'customer.subscription.deleted'
Rails.logger.warn("Looking up user with email: #{email}") delete_subscription(event)
end
user = ::User.find_by_username_or_email(email)
return render_json_error "user not found" if !user
discourse_customer = Customer.create(user_id: user.id, customer_id: customer_id)
subscription = checkout_session[:subscription]
if subscription.present?
Subscription.create(customer_id: discourse_customer.id, external_id: subscription)
end
line_items =
::Stripe::Checkout::Session.list_line_items(checkout_session[:id], { limit: 1 })
item = line_items[:data].first
group = plan_group(item[:price])
group.add(user) unless group.nil?
if SiteSetting.discourse_subscriptions_enable_verbose_logging
Rails.logger.warn("Line item with group name meta data: #{item[:price]}")
if group.nil?
Rails.logger.warn("Group not found or not listed in metadata!")
else
Rails.logger.warn("Group: #{group.name}")
end
end
discourse_customer.product_id = item[:price][:product]
discourse_customer.save!
if !subscription.nil?
::Stripe::Subscription.update(
subscription,
{ metadata: { user_id: user.id, username: user.username } },
)
end
when "customer.subscription.created"
when "customer.subscription.updated"
subscription = event[:data][:object]
status = subscription[:status]
return head 200 if !%w[complete active].include?(status)
customer = find_active_customer(subscription[:customer], subscription[:plan][:product])
return render_json_error "customer not found" if !customer
update_status(customer.id, subscription[:id], status)
user = ::User.find_by(id: customer.user_id)
return render_json_error "user not found" if !user
if group = plan_group(subscription[:plan])
group.add(user)
end
when "customer.subscription.deleted"
subscription = event[:data][:object]
customer = find_active_customer(subscription[:customer], subscription[:plan][:product])
return render_json_error "customer not found" if !customer
update_status(customer.id, subscription[:id], subscription[:status])
user = ::User.find(customer.user_id)
return render_json_error "user not found" if !user
if group = plan_group(subscription[:plan])
group.remove(user)
end
end end
head 200 head 200
@@ -127,22 +46,37 @@ module DiscourseSubscriptions
private private
def update_status(customer_id, subscription_id, status) def subscription_completion?(event)
discourse_subscription = subscription_complete?(event) && previously_incomplete?(event)
Subscription.find_by(customer_id: customer_id, external_id: subscription_id)
discourse_subscription.update(status: status) if discourse_subscription
end end
def find_active_customer(customer_id, product_id) def subscription_complete?(event)
Customer event && event[:data] && event[:data][:object] && event[:data][:object][:status] && event[:data][:object][:status] == 'complete'
.joins(:subscriptions) end
.where(customer_id: customer_id, product_id: product_id)
.where( def previously_incomplete?(event)
Subscription.arel_table[:status].eq(nil).or( event && event[:data] && event[:data][:previous_attributes] && event[:data][:previous_attributes][:status] && event[:data][:previous_attributes][:status] == 'incomplete'
Subscription.arel_table[:status].not_eq("canceled"), end
),
def delete_subscription(event)
customer = Customer.find_by(
customer_id: event[:data][:object][:customer],
product_id: event[:data][:object][:plan][:product]
)
if customer
sub_model = Subscription.find_by(
customer_id: customer.id,
external_id: [:id]
) )
.first
sub_model.delete if sub_model
user = ::User.find(customer.user_id)
customer.delete
group = plan_group(event[:data][:object][:plan])
group.remove(user) if group
end
end end
end end
end end
@@ -1,11 +0,0 @@
# frozen_string_literal: true
module DiscourseSubscriptions
class PricingtableController < ::ApplicationController
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
def index
head 200
end
end
end
@@ -4,11 +4,8 @@ module DiscourseSubscriptions
class SubscribeController < ::ApplicationController class SubscribeController < ::ApplicationController
include DiscourseSubscriptions::Stripe include DiscourseSubscriptions::Stripe
include DiscourseSubscriptions::Group include DiscourseSubscriptions::Group
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key before_action :set_api_key
requires_login except: %i[index contributors show] requires_login except: [:index, :contributors, :show]
def index def index
begin begin
@@ -16,12 +13,19 @@ module DiscourseSubscriptions
products = [] products = []
if product_ids.present? && is_stripe_configured? if product_ids.present? && is_stripe_configured?
response = ::Stripe::Product.list({ ids: product_ids, active: true }) response = ::Stripe::Product.list({
ids: product_ids,
active: true
})
products = response[:data].map do |p|
serialize_product(p)
end
products = response[:data].map { |p| serialize_product(p) }
end end
render_json_dump products render_json_dump products
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
render_json_error e.message render_json_error e.message
end end
@@ -32,11 +36,7 @@ module DiscourseSubscriptions
contributor_ids = Set.new contributor_ids = Set.new
campaign_product = SiteSetting.discourse_subscriptions_campaign_product campaign_product = SiteSetting.discourse_subscriptions_campaign_product
if campaign_product.present? 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))
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) contributors = ::User.where(id: contributor_ids)
@@ -49,7 +49,10 @@ module DiscourseSubscriptions
product = ::Stripe::Product.retrieve(params[:id]) product = ::Stripe::Product.retrieve(params[:id])
plans = ::Stripe::Price.list(active: true, product: 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 render_json_dump response
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
@@ -58,77 +61,47 @@ module DiscourseSubscriptions
end end
def create def create
params.require(%i[source plan]) params.require([:source, :plan])
begin begin
customer = customer = find_or_create_customer(params[:source])
find_or_create_customer(
params[:source],
params[:cardholder_name],
params[:cardholder_address],
)
plan = ::Stripe::Price.retrieve(params[:plan]) plan = ::Stripe::Price.retrieve(params[:plan])
if params[:promo].present? if params[:promo].present?
promo_code = ::Stripe::PromotionCode.list({ code: params[:promo] }) promo_code = ::Stripe::PromotionCode.list({ code: params[:promo] })
promo_code = promo_code[:data][0] # we assume promo codes have a unique name promo_code = promo_code[:data][0] # we assume promo codes have a unique name
if promo_code.blank? return render_json_error I18n.t("js.discourse_subscriptions.subscribe.invalid_coupon") if promo_code.blank?
return render_json_error I18n.t("js.discourse_subscriptions.subscribe.invalid_coupon")
end
end end
recurring_plan = plan[:type] == "recurring" recurring_plan = plan[:type] == 'recurring'
if recurring_plan if recurring_plan
trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] && trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] && plan[:metadata][:trial_period_days]
plan[:metadata][:trial_period_days]
promo_code_id = promo_code[:id] if promo_code promo_code_id = promo_code[:id] if promo_code
subscription_params = { transaction = ::Stripe::Subscription.create(
customer: customer[:id], customer: customer[:id],
items: [{ price: params[:plan] }], items: [{ price: params[:plan] }],
metadata: metadata_user, metadata: metadata_user,
trial_period_days: trial_days, trial_period_days: trial_days,
promotion_code: promo_code_id, promotion_code: promo_code_id
} )
if SiteSetting.discourse_subscriptions_enable_automatic_tax payment_intent = retrieve_payment_intent(transaction[:latest_invoice]) if transaction[:status] == 'incomplete'
subscription_params[:automatic_tax] = { enabled: true }
end
transaction = ::Stripe::Subscription.create(subscription_params)
payment_intent = retrieve_payment_intent(transaction[:latest_invoice]) if transaction[
:status
] == "incomplete"
else else
coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] && coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] && promo_code[:coupon][:id]
promo_code[:coupon][:id] invoice_item = ::Stripe::InvoiceItem.create(
customer: customer[:id],
invoice_params = { customer: customer[:id] } price: params[:plan],
if SiteSetting.discourse_subscriptions_enable_automatic_tax discounts: [{ coupon: coupon_id }]
invoice_params[:automatic_tax] = { enabled: true } )
end invoice = ::Stripe::Invoice.create(
invoice = ::Stripe::Invoice.create(invoice_params) customer: customer[:id]
)
invoice_item =
::Stripe::InvoiceItem.create(
customer: customer[:id],
price: params[:plan],
discounts: [{ coupon: coupon_id }],
invoice: invoice[:id],
)
transaction = ::Stripe::Invoice.finalize_invoice(invoice[:id]) transaction = ::Stripe::Invoice.finalize_invoice(invoice[:id])
payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] == payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] == 'open'
"open" transaction = ::Stripe::Invoice.pay(invoice[:id]) if payment_intent[:status] == 'successful'
if payment_intent.nil?
return(
render_json_error I18n.t("js.discourse_subscriptions.subscribe.transaction_error")
)
end
transaction = ::Stripe::Invoice.pay(invoice[:id]) if payment_intent[:status] ==
"successful"
end end
finalize_transaction(transaction, plan) if transaction_ok(transaction) finalize_transaction(transaction, plan) if transaction_ok(transaction)
@@ -142,7 +115,7 @@ module DiscourseSubscriptions
end end
def finalize def finalize
params.require(%i[plan transaction]) params.require([:plan, :transaction])
begin begin
price = ::Stripe::Price.retrieve(params[:plan]) price = ::Stripe::Price.retrieve(params[:plan])
transaction = retrieve_transaction(params[:transaction]) transaction = retrieve_transaction(params[:transaction])
@@ -159,18 +132,16 @@ module DiscourseSubscriptions
group.add(current_user) if group group.add(current_user) if group
customer = customer = Customer.create(
Customer.create( user_id: current_user.id,
user_id: current_user.id, customer_id: transaction[:customer],
customer_id: transaction[:customer], product_id: plan[:product]
product_id: plan[:product], )
)
if transaction[:object] == "subscription" if transaction[:object] == 'subscription'
Subscription.create( Subscription.create(
customer_id: customer.id, customer_id: customer.id,
external_id: transaction[:id], external_id: transaction[:id]
status: transaction[:status],
) )
end end
end end
@@ -183,7 +154,7 @@ module DiscourseSubscriptions
name: product[:name], name: product[:name],
description: PrettyText.cook(product[:metadata][:description]), description: PrettyText.cook(product[:metadata][:description]),
subscribed: current_user_products.include?(product[:id]), subscribed: current_user_products.include?(product[:id]),
repurchaseable: product[:metadata][:repurchaseable], repurchaseable: product[:metadata][:repurchaseable]
} }
end end
@@ -191,49 +162,26 @@ module DiscourseSubscriptions
return [] if current_user.nil? return [] if current_user.nil?
Customer Customer
.joins(:subscriptions)
.where(user_id: current_user.id)
.where(
Subscription.arel_table[:status].eq(nil).or(
Subscription.arel_table[:status].not_eq("canceled"),
),
)
.select(:product_id) .select(:product_id)
.distinct .where(user_id: current_user.id)
.pluck(:product_id) .map { |c| c.product_id }.compact
end end
def serialize_plans(plans) def serialize_plans(plans)
plans[:data] plans[:data].map do |plan|
.map { |plan| plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring) } plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring)
.sort_by { |plan| plan[:amount] } end.sort_by { |plan| plan[:amount] }
end end
def find_or_create_customer(source, cardholder_name = nil, cardholder_address = nil) def find_or_create_customer(source)
customer = Customer.find_by_user_id(current_user.id) customer = Customer.find_by_user_id(current_user.id)
cardholder_address =
(
if cardholder_address.present?
{
line1: cardholder_address[:line1],
city: cardholder_address[:city],
state: cardholder_address[:state],
country: cardholder_address[:country],
postal_code: cardholder_address[:postalCode],
}
else
nil
end
)
if customer.present? if customer.present?
::Stripe::Customer.retrieve(customer.customer_id) ::Stripe::Customer.retrieve(customer.customer_id)
else else
::Stripe::Customer.create( ::Stripe::Customer.create(
email: current_user.email, email: current_user.email,
source: source, source: source
name: cardholder_name,
address: cardholder_address,
) )
end end
end end
@@ -4,9 +4,6 @@ module DiscourseSubscriptions
module User module User
class PaymentsController < ::ApplicationController class PaymentsController < ::ApplicationController
include DiscourseSubscriptions::Stripe include DiscourseSubscriptions::Stripe
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key before_action :set_api_key
requires_login requires_login
@@ -25,36 +22,15 @@ module DiscourseSubscriptions
invoices_with_products = parse_invoices(all_invoices, product_ids) invoices_with_products = parse_invoices(all_invoices, product_ids)
invoice_ids = invoices_with_products.map { |invoice| invoice[:id] } invoice_ids = invoices_with_products.map { |invoice| invoice[:id] }
payments = ::Stripe::PaymentIntent.list(customer: customer_id) payments = ::Stripe::PaymentIntent.list(customer: customer_id)
payments_from_invoices = payments_from_invoices = payments[:data].select { |payment| invoice_ids.include?(payment[:invoice]) }
payments[:data].select { |payment| invoice_ids.include?(payment[:invoice]) } data = data | payments_from_invoices
if SiteSetting.discourse_subscriptions_enable_verbose_logging
Rails.logger.warn("Payments from invoices: #{payments_from_invoices}")
end
# Pricing table one-off purchases do not have invoices
payments_without_invoices =
payments[:data].select { |payment| payment[:invoice].nil? }
if SiteSetting.discourse_subscriptions_enable_verbose_logging
Rails.logger.warn("Payments without invoices: #{payments_without_invoices}")
end
data = data | payments_from_invoices | payments_without_invoices
end end
end end
if SiteSetting.discourse_subscriptions_pricing_table_enabled && current_user.email
related_guest_payments = fetch_guest_payments(current_user.email)
if SiteSetting.discourse_subscriptions_enable_verbose_logging
Rails.logger.warn("Related guest payments: #{related_guest_payments}")
end
data = data | related_guest_payments
end
data = data.sort_by { |pmt| pmt[:created] }.reverse data = data.sort_by { |pmt| pmt[:created] }.reverse
render_json_dump data render_json_dump data
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
render_json_error e.message render_json_error e.message
end end
@@ -63,56 +39,18 @@ module DiscourseSubscriptions
private private
def parse_invoices(all_invoices, product_ids) def parse_invoices(all_invoices, product_ids)
all_invoices[:data].select do |invoice| invoices_with_products = all_invoices[:data].select do |invoice|
invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data] invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data]
if invoice_lines invoice_product_id = parse_invoice_lines(invoice_lines)
invoice_product_id = parse_invoice_lines(invoice_lines) product_ids.include?(invoice_product_id)
product_ids.include?(invoice_product_id)
end
end end
end end
def parse_invoice_lines(invoice_lines) def parse_invoice_lines(invoice_lines)
invoice_product_id = invoice_lines[:price][:product] if invoice_lines[:price] && invoice_product_id = invoice_lines[:price][:product] if invoice_lines[:price] && invoice_lines[:price][:product]
invoice_lines[:price][:product] invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] && invoice_lines[:plan][:product]
invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] &&
invoice_lines[:plan][:product]
invoice_product_id invoice_product_id
end end
def fetch_guest_payments(email)
guest_payments = []
starting_after = nil
begin
loop do
# Fetch charges in batches of 100, using pagination with starting_after
charges =
::Stripe::Charge.list(
limit: 100,
starting_after: starting_after,
expand: ["data.payment_intent"],
)
charges[:data].each do |charge|
# Check if the charge is associated with the given email and has no customer ID
if charge[:billing_details][:email] == email && charge[:customer].nil?
guest_payments << charge
end
end
# Check if there are more charges to fetch
break if charges[:data].count < 100
# Set starting_after to the last charge's ID for the next batch
starting_after = charges[:data].last[:id]
end
rescue ::Stripe::StripeError => e
render_json_error e.message
end
guest_payments
end
end end
end end
end end
@@ -5,9 +5,6 @@ module DiscourseSubscriptions
class SubscriptionsController < ::ApplicationController class SubscriptionsController < ::ApplicationController
include DiscourseSubscriptions::Stripe include DiscourseSubscriptions::Stripe
include DiscourseSubscriptions::Group include DiscourseSubscriptions::Group
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key before_action :set_api_key
requires_login requires_login
@@ -15,40 +12,36 @@ module DiscourseSubscriptions
begin begin
customer = Customer.where(user_id: current_user.id) customer = Customer.where(user_id: current_user.id)
customer_ids = customer.map { |c| c.id } if customer customer_ids = customer.map { |c| c.id } if customer
stripe_customer_ids = customer.map { |c| c.customer_id }.uniq 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 = [] subscriptions = []
if subscription_ids if subscription_ids
prices = [] plans = ::Stripe::Price.list(
price_params = { limit: 100, expand: ["data.product"] } expand: ['data.product'],
loop do limit: 100
response = ::Stripe::Price.list(price_params) )
prices.concat(response[:data])
break unless response[:has_more]
price_params[:starting_after] = response[:data].last.id
end
all_subscriptions = []
stripe_customer_ids.each do |stripe_customer_id| customers = ::Stripe::Customer.list(
customer_subscriptions = email: current_user.email,
::Stripe::Subscription.list(customer: stripe_customer_id, status: "all") expand: ['data.subscriptions']
all_subscriptions.concat(customer_subscriptions[:data]) )
end
subscriptions = customers[:data].map do |sub_customer|
sub_customer[:subscriptions][:data]
end.flatten(1)
subscriptions = subscriptions.select { |sub| subscription_ids.include?(sub[:id]) }
subscriptions = all_subscriptions.select { |sub| subscription_ids.include?(sub[:id]) }
subscriptions.map! do |subscription| subscriptions.map! do |subscription|
plan = prices.find { |p| p[:id] == subscription[:items][:data][0][:price][:id] } plan = plans[:data].find { |p| p[:id] == subscription[:items][:data][0][:price][:id] }
subscription.to_h.except!(:plan) subscription.to_h.except!(:plan)
subscription.to_h.merge(plan: plan, product: plan[:product].to_h.slice(:id, :name)) subscription.to_h.merge(plan: plan, product: plan[:product].to_h.slice(:id, :name))
end end
end end
render_json_dump subscriptions render_json_dump subscriptions
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
render_json_error e.message render_json_error e.message
end end
@@ -58,41 +51,18 @@ module DiscourseSubscriptions
# we cancel but don't remove until the end of the period # we cancel but don't remove until the end of the period
# full removal is done via webhooks # full removal is done via webhooks
begin 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 if subscription
render_json_dump subscription render_json_dump subscription
else else
render_json_error I18n.t("discourse_subscriptions.customer_not_found") render_json_error I18n.t('discourse_subscriptions.customer_not_found')
end end
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
render_json_error e.message render_json_error e.message
end end
end end
def update
params.require(:payment_method)
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] },
)
render json: success_json
rescue ::Stripe::InvalidRequestError
render_json_error I18n.t("discourse_subscriptions.card.invalid")
end
end
private
def attach_method_to_customer(customer_id, method)
customer = Customer.find(customer_id)
::Stripe::PaymentMethod.attach(method, { customer: customer.customer_id })
end
end end
end end
end end
@@ -2,6 +2,7 @@
module ::Jobs module ::Jobs
class ManuallyUpdateCampaignData < ::Jobs::Base class ManuallyUpdateCampaignData < ::Jobs::Base
def execute(args) def execute(args)
return unless SiteSetting.discourse_subscriptions_campaign_enabled return unless SiteSetting.discourse_subscriptions_campaign_enabled
DiscourseSubscriptions::Campaign.new.refresh_data DiscourseSubscriptions::Campaign.new.refresh_data
@@ -13,20 +13,3 @@ module DiscourseSubscriptions
end end
end end
end end
# == Schema Information
#
# Table name: discourse_subscriptions_customers
#
# id :bigint not null, primary key
# customer_id :string not null
# product_id :string
# user_id :bigint
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_discourse_subscriptions_customers_on_customer_id (customer_id)
# index_discourse_subscriptions_customers_on_user_id (user_id)
#
@@ -4,17 +4,3 @@ module DiscourseSubscriptions
class Product < ActiveRecord::Base class Product < ActiveRecord::Base
end end
end end
# == Schema Information
#
# Table name: discourse_subscriptions_products
#
# id :bigint not null, primary key
# external_id :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_discourse_subscriptions_products_on_external_id (external_id)
#
@@ -5,20 +5,3 @@ module DiscourseSubscriptions
belongs_to :customer belongs_to :customer
end end
end end
# == Schema Information
#
# Table name: discourse_subscriptions_subscriptions
#
# id :bigint not null, primary key
# customer_id :bigint not null
# external_id :string not null
# created_at :datetime not null
# updated_at :datetime not null
# status :string
#
# Indexes
#
# index_discourse_subscriptions_subscriptions_on_customer_id (customer_id)
# index_discourse_subscriptions_subscriptions_on_external_id (external_id)
#

Some files were not shown because too many files have changed in this diff Show More