Compare commits
83
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fe7be7ef8 | ||
|
|
d6c4ec21ae | ||
|
|
c48cbc7071 | ||
|
|
66cf05943c | ||
|
|
e46693b94e | ||
|
|
fcc414dbbe | ||
|
|
411ac6ec80 | ||
|
|
ce988c9512 | ||
|
|
bf7079928e | ||
|
|
ff981f76e8 | ||
|
|
13d23dc3fa | ||
|
|
dcde03d7c4 | ||
|
|
d63f241e0d | ||
|
|
8f1b332f9e | ||
|
|
9190001a3c | ||
|
|
0d28c9a56d | ||
|
|
7361721ec0 | ||
|
|
87659e237b | ||
|
|
7b8a41ad0d | ||
|
|
0dcf17de59 | ||
|
|
2cfded9a77 | ||
|
|
d33cf2921e | ||
|
|
a3883d2a9a | ||
|
|
331c8630c3 | ||
|
|
2ea8d7b6eb | ||
|
|
316303da5d | ||
|
|
2f9353bc33 | ||
|
|
3608944b9c | ||
|
|
3aec1a9336 | ||
|
|
2f87566366 | ||
|
|
ac81a7a830 | ||
|
|
a257b386f9 | ||
|
|
c30fea21ef | ||
|
|
b855ce9bb9 | ||
|
|
1a80f9d52b | ||
|
|
c4f8220a55 | ||
|
|
7140517ec8 | ||
|
|
9173190181 | ||
|
|
77cb62bf1f | ||
|
|
2a75da65b8 | ||
|
|
00e5bc1614 | ||
|
|
e365ced531 | ||
|
|
bf41ea9aa7 | ||
|
|
81205cebaf | ||
|
|
49ddc757c9 | ||
|
|
d6337e559a | ||
|
|
a6a0cd72c8 | ||
|
|
7bc34bbbb4 | ||
|
|
803bba7938 | ||
|
|
2babb43ffb | ||
|
|
9aa4a44b2a | ||
|
|
c06ae37048 | ||
|
|
45b7eb6e6c | ||
|
|
ee9741aefa | ||
|
|
bb99a9a66a | ||
|
|
1834095b23 | ||
|
|
8dfdca567d | ||
|
|
d4f0b9a43c | ||
|
|
3051bceb8f | ||
|
|
7fb0936e5c | ||
|
|
fb072aa851 | ||
|
|
4e1a17c40c | ||
|
|
cbbc23fd5a | ||
|
|
e917c3bca6 | ||
|
|
425fbc0af1 | ||
|
|
c0b7aa65d7 | ||
|
|
d59a283fd2 | ||
|
|
1f9907d9e7 | ||
|
|
550e8347e3 | ||
|
|
c93be7632c | ||
|
|
c00e539304 | ||
|
|
a58743e376 | ||
|
|
aba86e5e25 | ||
|
|
827f9df444 | ||
|
|
39b7253c08 | ||
|
|
a78b92ddcc | ||
|
|
e48b817e8e | ||
|
|
945af4f140 | ||
|
|
d4d92d9653 | ||
|
|
392b88265b | ||
|
|
8762d6742e | ||
|
|
353898a6b4 | ||
|
|
97caad7205 |
@@ -0,0 +1,3 @@
|
|||||||
|
< 3.3.0.beta1-dev: 0dcf17de59bdf82c0233bb630dad0ce407d1ae76
|
||||||
|
3.1.999: c4f8220a5572ef9a42a9a71500e001d0df206c43
|
||||||
|
2.9.0.beta9: 39b7253c082270e33839f4b0821fbf75abfdd826
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"globals": {
|
|
||||||
"Stripe": true
|
|
||||||
},
|
|
||||||
"extends": "eslint-config-discourse"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
module.exports = require("@discourse/lint-configs/eslint");
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
name: Discourse Plugin
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
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 .
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
module.exports = require("@discourse/lint-configs/prettier");
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
inherit_gem:
|
inherit_gem:
|
||||||
rubocop-discourse: default.yml
|
rubocop-discourse: stree-compat.yml
|
||||||
AllCops:
|
AllCops:
|
||||||
Exclude:
|
Exclude:
|
||||||
- "gems/**/*"
|
- "gems/**/*"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
module.exports = require("@discourse/lint-configs/template-lint");
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: ["ember-template-lint-plugin-discourse"],
|
|
||||||
extends: "discourse:recommended",
|
|
||||||
};
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
# 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
|
||||||
|
|||||||
+59
-18
@@ -7,38 +7,79 @@ GIT
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
|
activesupport (7.1.3.2)
|
||||||
|
base64
|
||||||
|
bigdecimal
|
||||||
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
|
connection_pool (>= 2.2.5)
|
||||||
|
drb
|
||||||
|
i18n (>= 1.6, < 2)
|
||||||
|
minitest (>= 5.1)
|
||||||
|
mutex_m
|
||||||
|
tzinfo (~> 2.0)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
parallel (1.22.1)
|
base64 (0.2.0)
|
||||||
parser (3.1.2.0)
|
bigdecimal (3.1.6)
|
||||||
|
concurrent-ruby (1.2.3)
|
||||||
|
connection_pool (2.4.1)
|
||||||
|
drb (2.2.1)
|
||||||
|
i18n (1.14.3)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
racc (~> 1.7)
|
||||||
|
json (2.7.1)
|
||||||
|
language_server-protocol (3.17.0.3)
|
||||||
|
minitest (5.22.2)
|
||||||
|
mutex_m (0.2.0)
|
||||||
|
parallel (1.24.0)
|
||||||
|
parser (3.3.0.5)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
|
racc
|
||||||
|
prettier_print (1.2.1)
|
||||||
|
racc (1.7.3)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
regexp_parser (2.5.0)
|
regexp_parser (2.9.0)
|
||||||
rexml (3.2.5)
|
rexml (3.2.6)
|
||||||
rubocop (1.30.1)
|
rubocop (1.62.0)
|
||||||
|
json (~> 2.3)
|
||||||
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.1.0.0)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
rexml (>= 3.2.5, < 4.0)
|
rexml (>= 3.2.5, < 4.0)
|
||||||
rubocop-ast (>= 1.18.0, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 1.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.18.0)
|
rubocop-ast (1.31.1)
|
||||||
parser (>= 3.1.1.0)
|
parser (>= 3.3.0.4)
|
||||||
rubocop-discourse (2.5.0)
|
rubocop-capybara (2.20.0)
|
||||||
rubocop (>= 1.1.0)
|
rubocop (~> 1.41)
|
||||||
rubocop-rspec (>= 2.0.0)
|
rubocop-discourse (3.7.1)
|
||||||
rubocop-rspec (2.11.1)
|
activesupport (>= 6.1)
|
||||||
rubocop (~> 1.19)
|
rubocop (>= 1.59.0)
|
||||||
ruby-progressbar (1.11.0)
|
rubocop-capybara (>= 2.0.0)
|
||||||
unicode-display_width (2.1.0)
|
rubocop-factory_bot (>= 2.0.0)
|
||||||
|
rubocop-rspec (>= 2.25.0)
|
||||||
|
rubocop-factory_bot (2.25.1)
|
||||||
|
rubocop (~> 1.41)
|
||||||
|
rubocop-rspec (2.27.1)
|
||||||
|
rubocop (~> 1.40)
|
||||||
|
rubocop-capybara (~> 2.17)
|
||||||
|
rubocop-factory_bot (~> 2.22)
|
||||||
|
ruby-progressbar (1.13.0)
|
||||||
|
syntax_tree (6.2.0)
|
||||||
|
prettier_print (>= 1.2.0)
|
||||||
|
tzinfo (2.0.6)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
unicode-display_width (2.5.0)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
rubocop-discourse
|
rubocop-discourse
|
||||||
|
syntax_tree
|
||||||
translations-manager!
|
translations-manager!
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.2.19
|
2.5.4
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ module DiscourseSubscriptions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def is_stripe_configured?
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ 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
|
||||||
@@ -18,23 +21,24 @@ module DiscourseSubscriptions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
params.require([:promo, :discount_type, :discount, :active])
|
params.require(%i[promo discount_type discount active])
|
||||||
begin
|
begin
|
||||||
coupon_params = {
|
coupon_params = { duration: "forever" }
|
||||||
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 = ::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
|
render_json_dump promo_code
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
rescue ::Stripe::InvalidRequestError => e
|
||||||
@@ -43,14 +47,9 @@ module DiscourseSubscriptions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
params.require([:id, :active])
|
params.require(%i[id active])
|
||||||
begin
|
begin
|
||||||
promo_code = ::Stripe::PromotionCode.update(
|
promo_code = ::Stripe::PromotionCode.update(params[:id], { active: params[:active] })
|
||||||
params[:id],
|
|
||||||
{
|
|
||||||
active: params[:active]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
render_json_dump promo_code
|
render_json_dump promo_code
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
rescue ::Stripe::InvalidRequestError => e
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ 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
|
||||||
@@ -27,15 +29,11 @@ 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],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if params[:type] == 'recurring'
|
price_object[:recurring] = { interval: params[:interval] } if params[:type] == "recurring"
|
||||||
price_object[:recurring] = {
|
|
||||||
interval: params[:interval]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
plan = ::Stripe::Price.create(price_object)
|
plan = ::Stripe::Price.create(price_object)
|
||||||
|
|
||||||
@@ -56,14 +54,16 @@ module DiscourseSubscriptions
|
|||||||
end
|
end
|
||||||
|
|
||||||
interval = nil
|
interval = nil
|
||||||
if plan[:recurring] && plan[:recurring][:interval]
|
interval = plan[:recurring][:interval] if plan[:recurring] && plan[:recurring][:interval]
|
||||||
interval = plan[:recurring][:interval]
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
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,15 +71,16 @@ module DiscourseSubscriptions
|
|||||||
|
|
||||||
def update
|
def update
|
||||||
begin
|
begin
|
||||||
plan = ::Stripe::Price.update(
|
plan =
|
||||||
params[:id],
|
::Stripe::Price.update(
|
||||||
nickname: params[:nickname],
|
params[:id],
|
||||||
active: params[:active],
|
nickname: params[:nickname],
|
||||||
metadata: {
|
active: params[:active],
|
||||||
group_name: params[:metadata][:group_name],
|
metadata: {
|
||||||
trial_period_days: params[:trial_period_days]
|
group_name: params[:metadata][:group_name],
|
||||||
}
|
trial_period_days: params[:trial_period_days],
|
||||||
)
|
},
|
||||||
|
)
|
||||||
|
|
||||||
render_json_dump plan
|
render_json_dump plan
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
rescue ::Stripe::InvalidRequestError => e
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ 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
|
||||||
@@ -27,20 +29,15 @@ module DiscourseSubscriptions
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
begin
|
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) 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(
|
Product.create(external_id: product[:id])
|
||||||
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
|
||||||
@@ -51,7 +48,6 @@ 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
|
||||||
@@ -59,13 +55,9 @@ module DiscourseSubscriptions
|
|||||||
|
|
||||||
def update
|
def update
|
||||||
begin
|
begin
|
||||||
product = ::Stripe::Product.update(
|
product = ::Stripe::Product.update(params[:id], product_params)
|
||||||
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
|
||||||
@@ -78,7 +70,6 @@ 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
|
||||||
@@ -95,8 +86,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,6 +3,8 @@
|
|||||||
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
|
||||||
@@ -16,20 +18,23 @@ 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 = 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)
|
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[:data].present?
|
subscriptions[:last_record] = current_set[:data].last[:id] if current_set[
|
||||||
|
: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
|
||||||
@@ -50,10 +55,11 @@ module DiscourseSubscriptions
|
|||||||
refund_subscription(params[:id]) if params[:refund]
|
refund_subscription(params[:id]) if params[:refund]
|
||||||
subscription = ::Stripe::Subscription.delete(params[:id])
|
subscription = ::Stripe::Subscription.delete(params[:id])
|
||||||
|
|
||||||
customer = Customer.find_by(
|
customer =
|
||||||
product_id: subscription[:plan][:product],
|
Customer.find_by(
|
||||||
customer_id: subscription[:customer]
|
product_id: subscription[:plan][:product],
|
||||||
)
|
customer_id: subscription[:customer],
|
||||||
|
)
|
||||||
|
|
||||||
Subscription.delete_by(external_id: params[:id])
|
Subscription.delete_by(external_id: params[:id])
|
||||||
|
|
||||||
@@ -65,7 +71,6 @@ module DiscourseSubscriptions
|
|||||||
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
|
||||||
@@ -74,7 +79,11 @@ module DiscourseSubscriptions
|
|||||||
private
|
private
|
||||||
|
|
||||||
def get_subscriptions(start)
|
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
|
end
|
||||||
|
|
||||||
def find_valid_subscriptions(data, ids)
|
def find_valid_subscriptions(data, ids)
|
||||||
@@ -85,11 +94,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[:latest_invoice]
|
invoice = ::Stripe::Invoice.retrieve(subscription[:latest_invoice]) if subscription[
|
||||||
|
:latest_invoice
|
||||||
|
]
|
||||||
payment_intent = invoice[:payment_intent] if invoice[:payment_intent]
|
payment_intent = invoice[:payment_intent] if invoice[:payment_intent]
|
||||||
refund = ::Stripe::Refund.create({
|
refund = ::Stripe::Refund.create({ payment_intent: payment_intent })
|
||||||
payment_intent: payment_intent,
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
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,7 +4,11 @@ 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
|
||||||
|
|
||||||
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]
|
||||||
@@ -12,71 +16,59 @@ 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
|
||||||
render_json_error e.message
|
return render_json_error e.message
|
||||||
return
|
|
||||||
rescue ::Stripe::SignatureVerificationError => e
|
rescue ::Stripe::SignatureVerificationError => e
|
||||||
render_json_error e.message
|
return render_json_error e.message
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
case event[:type]
|
case event[:type]
|
||||||
when 'customer.subscription.updated'
|
when "customer.subscription.created"
|
||||||
customer = Customer.find_by(
|
when "customer.subscription.updated"
|
||||||
customer_id: event[:data][:object][:customer],
|
customer =
|
||||||
product_id: event[:data][:object][:plan][:product]
|
Customer.find_by(
|
||||||
)
|
customer_id: event[:data][:object][:customer],
|
||||||
|
product_id: event[:data][:object][:plan][:product],
|
||||||
|
)
|
||||||
|
|
||||||
if customer && subscription_completion?(event)
|
return render_json_error "customer not found" if !customer
|
||||||
user = ::User.find(customer.user_id)
|
return head 200 if event[:data][:object][:status] != "complete"
|
||||||
group = plan_group(event[:data][:object][:plan])
|
|
||||||
group.add(user) if group
|
user = ::User.find_by(id: customer.user_id)
|
||||||
|
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],
|
||||||
|
)
|
||||||
|
|
||||||
|
return render_json_error "customer not found" if !customer
|
||||||
|
|
||||||
|
Subscription.find_by(
|
||||||
|
customer_id: customer.id,
|
||||||
|
external_id: event[:data][:object][:id],
|
||||||
|
)&.destroy!
|
||||||
|
|
||||||
|
user = ::User.find(customer.user_id)
|
||||||
|
return render_json_error "user not found" if !user
|
||||||
|
|
||||||
|
if group = plan_group(event[:data][:object][:plan])
|
||||||
|
group.remove(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
when 'customer.subscription.deleted'
|
customer.destroy!
|
||||||
delete_subscription(event)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
head 200
|
head 200
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def subscription_completion?(event)
|
|
||||||
subscription_complete?(event) && previously_incomplete?(event)
|
|
||||||
end
|
|
||||||
|
|
||||||
def subscription_complete?(event)
|
|
||||||
event && event[:data] && event[:data][:object] && event[:data][:object][:status] && event[:data][:object][:status] == 'complete'
|
|
||||||
end
|
|
||||||
|
|
||||||
def previously_incomplete?(event)
|
|
||||||
event && event[:data] && event[:data][:previous_attributes] && event[:data][:previous_attributes][:status] && event[:data][:previous_attributes][:status] == 'incomplete'
|
|
||||||
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]
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -4,31 +4,14 @@ 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: [:index, :contributors, :show]
|
requires_login except: %i[index contributors show]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
begin
|
head 200
|
||||||
product_ids = Product.all.pluck(:external_id)
|
|
||||||
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
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
render_json_dump products
|
|
||||||
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def contributors
|
def contributors
|
||||||
@@ -36,7 +19,11 @@ module DiscourseSubscriptions
|
|||||||
contributor_ids = Set.new
|
contributor_ids = Set.new
|
||||||
|
|
||||||
campaign_product = SiteSetting.discourse_subscriptions_campaign_product
|
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)
|
contributors = ::User.where(id: contributor_ids)
|
||||||
|
|
||||||
@@ -49,10 +36,7 @@ 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 = {
|
response = { product: serialize_product(product), plans: serialize_plans(plans) }
|
||||||
product: serialize_product(product),
|
|
||||||
plans: serialize_plans(plans)
|
|
||||||
}
|
|
||||||
|
|
||||||
render_json_dump response
|
render_json_dump response
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
rescue ::Stripe::InvalidRequestError => e
|
||||||
@@ -61,47 +45,60 @@ module DiscourseSubscriptions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
params.require([:source, :plan])
|
params.require(%i[source plan])
|
||||||
begin
|
begin
|
||||||
customer = find_or_create_customer(params[:source])
|
customer =
|
||||||
|
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
|
||||||
|
|
||||||
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
|
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] && 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
|
promo_code_id = promo_code[:id] if promo_code
|
||||||
|
|
||||||
transaction = ::Stripe::Subscription.create(
|
transaction =
|
||||||
customer: customer[:id],
|
::Stripe::Subscription.create(
|
||||||
items: [{ price: params[:plan] }],
|
customer: customer[:id],
|
||||||
metadata: metadata_user,
|
items: [{ price: params[:plan] }],
|
||||||
trial_period_days: trial_days,
|
metadata: metadata_user,
|
||||||
promotion_code: promo_code_id
|
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
|
else
|
||||||
coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] && promo_code[:coupon][:id]
|
coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] &&
|
||||||
invoice_item = ::Stripe::InvoiceItem.create(
|
promo_code[:coupon][:id]
|
||||||
customer: customer[:id],
|
invoice_item =
|
||||||
price: params[:plan],
|
::Stripe::InvoiceItem.create(
|
||||||
discounts: [{ coupon: coupon_id }]
|
customer: customer[:id],
|
||||||
)
|
price: params[:plan],
|
||||||
invoice = ::Stripe::Invoice.create(
|
discounts: [{ coupon: coupon_id }],
|
||||||
customer: customer[:id]
|
)
|
||||||
)
|
invoice = ::Stripe::Invoice.create(customer: customer[: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] == 'open'
|
payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] ==
|
||||||
transaction = ::Stripe::Invoice.pay(invoice[:id]) if payment_intent[:status] == 'successful'
|
"open"
|
||||||
|
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)
|
||||||
@@ -115,7 +112,7 @@ module DiscourseSubscriptions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def finalize
|
def finalize
|
||||||
params.require([:plan, :transaction])
|
params.require(%i[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])
|
||||||
@@ -132,17 +129,15 @@ module DiscourseSubscriptions
|
|||||||
|
|
||||||
group.add(current_user) if group
|
group.add(current_user) if group
|
||||||
|
|
||||||
customer = Customer.create(
|
customer =
|
||||||
user_id: current_user.id,
|
Customer.create(
|
||||||
customer_id: transaction[:customer],
|
user_id: current_user.id,
|
||||||
product_id: plan[:product]
|
customer_id: transaction[:customer],
|
||||||
)
|
product_id: plan[:product],
|
||||||
|
|
||||||
if transaction[:object] == 'subscription'
|
|
||||||
Subscription.create(
|
|
||||||
customer_id: customer.id,
|
|
||||||
external_id: transaction[:id]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if transaction[:object] == "subscription"
|
||||||
|
Subscription.create(customer_id: customer.id, external_id: transaction[:id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -154,34 +149,47 @@ 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
|
||||||
|
|
||||||
def current_user_products
|
def current_user_products
|
||||||
return [] if current_user.nil?
|
return [] if current_user.nil?
|
||||||
|
|
||||||
Customer
|
Customer.select(:product_id).where(user_id: current_user.id).map { |c| c.product_id }.compact
|
||||||
.select(:product_id)
|
|
||||||
.where(user_id: current_user.id)
|
|
||||||
.map { |c| c.product_id }.compact
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def serialize_plans(plans)
|
def serialize_plans(plans)
|
||||||
plans[:data].map do |plan|
|
plans[:data]
|
||||||
plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring)
|
.map { |plan| plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring) }
|
||||||
end.sort_by { |plan| plan[:amount] }
|
.sort_by { |plan| plan[:amount] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_or_create_customer(source)
|
def find_or_create_customer(source, cardholder_name = nil, cardholder_address = nil)
|
||||||
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,6 +4,9 @@ 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
|
||||||
|
|
||||||
@@ -22,7 +25,8 @@ 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[: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
|
data = data | payments_from_invoices
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -30,7 +34,6 @@ module DiscourseSubscriptions
|
|||||||
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
|
||||||
@@ -39,16 +42,19 @@ module DiscourseSubscriptions
|
|||||||
private
|
private
|
||||||
|
|
||||||
def parse_invoices(all_invoices, product_ids)
|
def parse_invoices(all_invoices, product_ids)
|
||||||
invoices_with_products = all_invoices[:data].select do |invoice|
|
invoices_with_products =
|
||||||
invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data]
|
all_invoices[:data].select do |invoice|
|
||||||
invoice_product_id = parse_invoice_lines(invoice_lines)
|
invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data]
|
||||||
product_ids.include?(invoice_product_id)
|
invoice_product_id = parse_invoice_lines(invoice_lines)
|
||||||
end
|
product_ids.include?(invoice_product_id)
|
||||||
|
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_lines[:price][:product]
|
invoice_product_id = invoice_lines[:price][:product] if invoice_lines[:price] &&
|
||||||
invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] && invoice_lines[:plan][:product]
|
invoice_lines[:price][:product]
|
||||||
|
invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] &&
|
||||||
|
invoice_lines[:plan][:product]
|
||||||
invoice_product_id
|
invoice_product_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ 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
|
||||||
|
|
||||||
@@ -12,24 +15,21 @@ 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
|
||||||
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
|
||||||
plans = ::Stripe::Price.list(
|
plans = ::Stripe::Price.list(expand: ["data.product"], limit: 100)
|
||||||
expand: ['data.product'],
|
|
||||||
limit: 100
|
|
||||||
)
|
|
||||||
|
|
||||||
customers = ::Stripe::Customer.list(
|
customers =
|
||||||
email: current_user.email,
|
::Stripe::Customer.list(email: current_user.email, expand: ["data.subscriptions"])
|
||||||
expand: ['data.subscriptions']
|
|
||||||
)
|
|
||||||
|
|
||||||
subscriptions = customers[:data].map do |sub_customer|
|
subscriptions =
|
||||||
sub_customer[:subscriptions][:data]
|
customers[:data].map { |sub_customer| sub_customer[:subscriptions][:data] }.flatten(1)
|
||||||
end.flatten(1)
|
|
||||||
|
|
||||||
subscriptions = subscriptions.select { |sub| subscription_ids.include?(sub[:id]) }
|
subscriptions = subscriptions.select { |sub| subscription_ids.include?(sub[:id]) }
|
||||||
|
|
||||||
@@ -41,7 +41,6 @@ module DiscourseSubscriptions
|
|||||||
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
|
||||||
@@ -51,18 +50,41 @@ 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,7 +2,6 @@
|
|||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
module DiscourseSubscriptions
|
module DiscourseSubscriptions
|
||||||
class PaymentSerializer < ApplicationSerializer
|
class PaymentSerializer < ApplicationSerializer
|
||||||
attributes :payment_intent_id,
|
attributes :payment_intent_id,
|
||||||
:receipt_email,
|
:receipt_email,
|
||||||
:url,
|
:url,
|
||||||
:created_at_age,
|
:created_at_age,
|
||||||
:amount,
|
:amount,
|
||||||
:amount_currency,
|
:amount_currency,
|
||||||
:username,
|
:username,
|
||||||
:user_id
|
:user_id
|
||||||
|
|
||||||
def created_at_age
|
def created_at_age
|
||||||
Time.now - object.created_at
|
Time.now - object.created_at
|
||||||
@@ -19,7 +19,7 @@ module DiscourseSubscriptions
|
|||||||
ActiveSupport::NumberHelper.number_to_currency(
|
ActiveSupport::NumberHelper.number_to_currency(
|
||||||
object.amount / 100,
|
object.amount / 100,
|
||||||
precision: 2,
|
precision: 2,
|
||||||
unit: currency_unit
|
unit: currency_unit,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ module DiscourseSubscriptions
|
|||||||
def user
|
def user
|
||||||
begin
|
begin
|
||||||
User.find(object.user_id)
|
User.find(object.user_id)
|
||||||
rescue
|
rescue StandardError
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -51,6 +51,8 @@ module DiscourseSubscriptions
|
|||||||
"KR"
|
"KR"
|
||||||
when "sgd"
|
when "sgd"
|
||||||
"S$"
|
"S$"
|
||||||
|
when "zar"
|
||||||
|
"R"
|
||||||
else
|
else
|
||||||
"$"
|
"$"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ module DiscourseSubscriptions
|
|||||||
|
|
||||||
# Fetch product purchases
|
# Fetch product purchases
|
||||||
one_time_payments = get_one_time_payments(product_ids)
|
one_time_payments = get_one_time_payments(product_ids)
|
||||||
one_time_payments.each do |c|
|
one_time_payments.each { |c| amount += c[:price].to_f / 100.00 }
|
||||||
amount += c[:price].to_f / 100.00
|
|
||||||
end
|
|
||||||
|
|
||||||
# get number of subscribers
|
# get number of subscribers
|
||||||
SiteSetting.discourse_subscriptions_campaign_subscribers = subscriptions&.length.to_i
|
SiteSetting.discourse_subscriptions_campaign_subscribers = subscriptions&.length.to_i
|
||||||
@@ -57,7 +55,7 @@ module DiscourseSubscriptions
|
|||||||
protected
|
protected
|
||||||
|
|
||||||
def goal_met_date_key
|
def goal_met_date_key
|
||||||
'subscriptions_goal_met_date'
|
"subscriptions_goal_met_date"
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_goal_status
|
def check_goal_status
|
||||||
@@ -91,9 +89,9 @@ module DiscourseSubscriptions
|
|||||||
SiteSetting.discourse_subscriptions_campaign_group = group[:id]
|
SiteSetting.discourse_subscriptions_campaign_group = group[:id]
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
full_name: I18n.t('js.discourse_subscriptions.campaign.supporters'),
|
full_name: I18n.t("js.discourse_subscriptions.campaign.supporters"),
|
||||||
title: I18n.t('js.discourse_subscriptions.campaign.supporter'),
|
title: I18n.t("js.discourse_subscriptions.campaign.supporter"),
|
||||||
flair_icon: "donate"
|
flair_icon: "donate",
|
||||||
}
|
}
|
||||||
|
|
||||||
group.update(params)
|
group.update(params)
|
||||||
@@ -104,11 +102,11 @@ module DiscourseSubscriptions
|
|||||||
|
|
||||||
def create_campaign_product
|
def create_campaign_product
|
||||||
product_params = {
|
product_params = {
|
||||||
name: I18n.t('js.discourse_subscriptions.campaign.title'),
|
name: I18n.t("js.discourse_subscriptions.campaign.title"),
|
||||||
active: true,
|
active: true,
|
||||||
metadata: {
|
metadata: {
|
||||||
description: I18n.t('js.discourse_subscriptions.campaign.body'),
|
description: I18n.t("js.discourse_subscriptions.campaign.body"),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
product = ::Stripe::Product.create(product_params)
|
product = ::Stripe::Product.create(product_params)
|
||||||
@@ -123,13 +121,9 @@ module DiscourseSubscriptions
|
|||||||
monthly_prices = [3, 5, 10, 25]
|
monthly_prices = [3, 5, 10, 25]
|
||||||
yearly_prices = [50, 100]
|
yearly_prices = [50, 100]
|
||||||
|
|
||||||
monthly_prices.each do |price|
|
monthly_prices.each { |price| create_price(product[:id], group, price, "month") }
|
||||||
create_price(product[:id], group, price, "month")
|
|
||||||
end
|
|
||||||
|
|
||||||
yearly_prices.each do |price|
|
yearly_prices.each { |price| create_price(product[:id], group, price, "year") }
|
||||||
create_price(product[:id], group, price, "year")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_price(product_id, group_name, amount, recurrence)
|
def create_price(product_id, group_name, amount, recurrence)
|
||||||
@@ -140,11 +134,11 @@ module DiscourseSubscriptions
|
|||||||
currency: SiteSetting.discourse_subscriptions_currency,
|
currency: SiteSetting.discourse_subscriptions_currency,
|
||||||
active: true,
|
active: true,
|
||||||
recurring: {
|
recurring: {
|
||||||
interval: recurrence
|
interval: recurrence,
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
group_name: group_name
|
group_name: group_name,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
plan = ::Stripe::Price.create(price_object)
|
plan = ::Stripe::Price.create(price_object)
|
||||||
@@ -152,18 +146,13 @@ module DiscourseSubscriptions
|
|||||||
|
|
||||||
def get_one_time_payments(product_ids)
|
def get_one_time_payments(product_ids)
|
||||||
one_time_payments = []
|
one_time_payments = []
|
||||||
current_set = {
|
current_set = { has_more: true, last_record: nil }
|
||||||
has_more: true,
|
|
||||||
last_record: nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if product_ids.present?
|
if product_ids.present?
|
||||||
# lots of matching because the Stripe API doesn't make it easy to match products => payments except from invoices
|
# 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
|
until current_set[:has_more] == false
|
||||||
all_invoices = ::Stripe::Invoice.list(
|
all_invoices =
|
||||||
limit: 100,
|
::Stripe::Invoice.list(limit: 100, starting_after: current_set[:last_record])
|
||||||
starting_after: current_set[:last_record]
|
|
||||||
)
|
|
||||||
|
|
||||||
current_set[:last_record] = all_invoices[:data].last[:id] if all_invoices[:data].present?
|
current_set[:last_record] = all_invoices[:data].last[:id] if all_invoices[:data].present?
|
||||||
current_set[:has_more] = all_invoices[:has_more]
|
current_set[:has_more] = all_invoices[:has_more]
|
||||||
@@ -173,11 +162,8 @@ module DiscourseSubscriptions
|
|||||||
next if invoice[:paid] != true
|
next if invoice[:paid] != true
|
||||||
line_item = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data] # Discourse only makes single-line item charges
|
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
|
# check if non-subscription and that the plan is active
|
||||||
if line_item[:plan] == nil &&
|
if line_item[:plan] == nil && line_item[:price] &&
|
||||||
line_item[:price] &&
|
line_item[:price][:recurring] == nil && line_item[:price][:active] == true
|
||||||
line_item[:price][:recurring] == nil &&
|
|
||||||
line_item[:price][:active] == true
|
|
||||||
|
|
||||||
product_id = line_item[:price][:product]
|
product_id = line_item[:price][:product]
|
||||||
if product_ids.include? product_id
|
if product_ids.include? product_id
|
||||||
line_data = {
|
line_data = {
|
||||||
@@ -197,17 +183,15 @@ module DiscourseSubscriptions
|
|||||||
|
|
||||||
def get_subscription_data
|
def get_subscription_data
|
||||||
subscriptions = []
|
subscriptions = []
|
||||||
current_set = {
|
current_set = { has_more: true, last_record: nil }
|
||||||
has_more: true,
|
|
||||||
last_record: nil
|
|
||||||
}
|
|
||||||
|
|
||||||
until current_set[:has_more] == false
|
until current_set[:has_more] == false
|
||||||
current_set = ::Stripe::Subscription.list(
|
current_set =
|
||||||
expand: ['data.plan.product'],
|
::Stripe::Subscription.list(
|
||||||
limit: 100,
|
expand: ["data.plan.product"],
|
||||||
starting_after: current_set[:last_record]
|
limit: 100,
|
||||||
)
|
starting_after: current_set[:last_record],
|
||||||
|
)
|
||||||
|
|
||||||
current_set[:last_record] = current_set[:data].last[:id] if current_set[:data].present?
|
current_set[:last_record] = current_set[:data].last[:id] if current_set[:data].present?
|
||||||
subscriptions.concat(current_set[:data].to_a)
|
subscriptions.concat(current_set[:data].to_a)
|
||||||
@@ -217,13 +201,14 @@ module DiscourseSubscriptions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def filter_to_subscriptions_products(data, ids)
|
def filter_to_subscriptions_products(data, ids)
|
||||||
valid = data.select do |sub|
|
valid =
|
||||||
# cannot .dig stripe objects
|
data.select do |sub|
|
||||||
items = sub[:items][:data][0] if sub[:items] && sub[:items][:data]
|
# cannot .dig stripe objects
|
||||||
product = items[:price][:product] if items[:price] && items[:price][:product]
|
items = sub[:items][:data][0] if sub[:items] && sub[:items][:data]
|
||||||
|
product = items[:price][:product] if items[:price] && items[:price][:product]
|
||||||
|
|
||||||
ids.include?(product)
|
ids.include?(product)
|
||||||
end
|
end
|
||||||
valid.empty? ? nil : valid
|
valid.empty? ? nil : valid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+15
-15
@@ -3,7 +3,7 @@
|
|||||||
class="campaign-banner"
|
class="campaign-banner"
|
||||||
style={{html-safe (concat "box-shadow: 5px 5px #" dropShadowColor)}}
|
style={{html-safe (concat "box-shadow: 5px 5px #" dropShadowColor)}}
|
||||||
>
|
>
|
||||||
{{d-button icon="times" action="dismissBanner" class="close"}}
|
<DButton @icon="times" @action={{this.dismissBanner}} class="close" />
|
||||||
|
|
||||||
<div class="campaign-banner-info" style={{html-safe this.bannerInfoStyle}}>
|
<div class="campaign-banner-info" style={{html-safe this.bannerInfoStyle}}>
|
||||||
{{#if isGoalMet}}
|
{{#if isGoalMet}}
|
||||||
@@ -24,25 +24,25 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{{#if product}}
|
{{#if product}}
|
||||||
{{#link-to
|
<LinkTo
|
||||||
"subscribe.show"
|
@route="subscribe.show"
|
||||||
product
|
@model={{product}}
|
||||||
disabled=product.subscribed
|
@disabled={{product.subscribed}}
|
||||||
class="btn btn-primary campaign-banner-info-button"
|
class="btn btn-primary campaign-banner-info-button"
|
||||||
}}
|
>
|
||||||
{{d-icon "far-heart"}}
|
{{d-icon "far-heart"}}
|
||||||
{{d-icon "heart" class="hover-heart"}}
|
{{d-icon "heart" class="hover-heart"}}
|
||||||
{{i18n "discourse_subscriptions.campaign.button"}}
|
{{i18n "discourse_subscriptions.campaign.button"}}
|
||||||
{{/link-to}}
|
</LinkTo>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#link-to
|
<LinkTo
|
||||||
"subscribe"
|
@route="subscribe"
|
||||||
class="btn btn-primary campaign-banner-info-button"
|
class="btn btn-primary campaign-banner-info-button"
|
||||||
}}
|
>
|
||||||
{{d-icon "far-heart"}}
|
{{d-icon "far-heart"}}
|
||||||
{{d-icon "heart" class="hover-heart"}}
|
{{d-icon "heart" class="hover-heart"}}
|
||||||
{{i18n "discourse_subscriptions.campaign.button"}}
|
{{i18n "discourse_subscriptions.campaign.button"}}
|
||||||
{{/link-to}}
|
</LinkTo>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{{#if showContributors}}
|
{{#if showContributors}}
|
||||||
{{#conditional-loading-spinner condition=loading size="small"}}
|
<ConditionalLoadingSpinner @condition={{loading}} @size="small">
|
||||||
<div class="campaign-banner-progress-users">
|
<div class="campaign-banner-progress-users">
|
||||||
<p class="campaign-banner-progress-users-title">
|
<p class="campaign-banner-progress-users-title">
|
||||||
<strong>
|
<strong>
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/conditional-loading-spinner}}
|
</ConditionalLoadingSpinner>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if showContributors}}
|
{{#if showContributors}}
|
||||||
{{#conditional-loading-spinner condition=loading size="small"}}
|
<ConditionalLoadingSpinner @condition={{loading}} @size="small">
|
||||||
<div class="campaign-banner-progress-users">
|
<div class="campaign-banner-progress-users">
|
||||||
<p class="campaign-banner-progress-users-title">
|
<p class="campaign-banner-progress-users-title">
|
||||||
<strong>
|
<strong>
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/conditional-loading-spinner}}
|
</ConditionalLoadingSpinner>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { action } from "@ember/object";
|
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import { equal } from "@ember/object/computed";
|
|
||||||
import { setting } from "discourse/lib/computed";
|
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
import { action } from "@ember/object";
|
||||||
|
import { equal } from "@ember/object/computed";
|
||||||
import { later } from "@ember/runloop";
|
import { later } from "@ember/runloop";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { setting } from "discourse/lib/computed";
|
||||||
|
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
const SIDEBAR_BODY_CLASS = "subscription-campaign-sidebar";
|
const SIDEBAR_BODY_CLASS = "subscription-campaign-sidebar";
|
||||||
|
|
||||||
@@ -58,12 +58,14 @@ export default Component.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.currentUser && this.showContributors) {
|
if (this.currentUser && this.showContributors) {
|
||||||
return ajax("/s/contributors", { method: "get" }).then((result) => {
|
return ajax("/subscriptions/contributors", { method: "get" }).then(
|
||||||
this.setProperties({
|
(result) => {
|
||||||
contributors: result,
|
this.setProperties({
|
||||||
loading: false,
|
contributors: result,
|
||||||
});
|
loading: false,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -96,6 +98,7 @@ export default Component.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
willDestroyElement() {
|
willDestroyElement() {
|
||||||
|
this._super(...arguments);
|
||||||
document.body.classList.remove(SIDEBAR_BODY_CLASS);
|
document.body.classList.remove(SIDEBAR_BODY_CLASS);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<div class="create-coupon-form">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<p>
|
||||||
|
<label for="promo_code">
|
||||||
|
{{i18n "discourse_subscriptions.admin.coupons.promo_code"}}
|
||||||
|
</label>
|
||||||
|
<Input @type="text" name="promo_code" @value={{promoCode}} />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="amount">
|
||||||
|
{{i18n "discourse_subscriptions.admin.coupons.discount"}}
|
||||||
|
</label>
|
||||||
|
<ComboBox
|
||||||
|
@content={{discountTypes}}
|
||||||
|
@value={{discountType}}
|
||||||
|
@onChange={{action (mut discountType)}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
class="discount-amount"
|
||||||
|
@type="text"
|
||||||
|
name="amount"
|
||||||
|
@value={{discount}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="active">
|
||||||
|
{{i18n "discourse_subscriptions.admin.coupons.active"}}
|
||||||
|
</label>
|
||||||
|
<Input @type="checkbox" name="active" @checked={{active}} />
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<DButton
|
||||||
|
@action={{action "createNewCoupon"}}
|
||||||
|
@label="discourse_subscriptions.admin.coupons.create"
|
||||||
|
@title="discourse_subscriptions.admin.coupons.create"
|
||||||
|
@icon="plus"
|
||||||
|
class="btn-primary btn btn-icon"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DButton
|
||||||
|
@action={{action "cancelCreate"}}
|
||||||
|
label="cancel"
|
||||||
|
@title="cancel"
|
||||||
|
@icon="times"
|
||||||
|
class="btn btn-icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user