Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d857cd7fed | ||
|
|
93c0b02a45 | ||
|
|
52468d17b9 | ||
|
|
17f5fd2841 | ||
|
|
255fd7f636 | ||
|
|
824f61aab1 | ||
|
|
aff99cb20b | ||
|
|
ccea023274 | ||
|
|
2e91a8e3a3 | ||
|
|
6ab3de9fca | ||
|
|
3c431b9915 | ||
|
|
1853bdd6f5 | ||
|
|
8e700c4650 | ||
|
|
4933ae2233 | ||
|
|
3bdef7f582 | ||
|
|
518cedab1b | ||
|
|
e981471217 | ||
|
|
a43f79d7a0 | ||
|
|
be122744a3 | ||
|
|
b13cbe663e | ||
|
|
f870634afd | ||
|
|
6a216d81e5 | ||
|
|
2f33bdf76b | ||
|
|
545e962291 | ||
|
|
904cd4d1d9 | ||
|
|
49bcd392d4 | ||
|
|
d531684373 | ||
|
|
377d873f2b | ||
|
|
f942223eac | ||
|
|
83b4e72ff4 | ||
|
|
b80440206b |
@@ -1,2 +0,0 @@
|
|||||||
3.1.999: c4f8220a5572ef9a42a9a71500e001d0df206c43
|
|
||||||
2.9.0.beta9: 39b7253c082270e33839f4b0821fbf75abfdd826
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"globals": {
|
|
||||||
"Stripe": true
|
|
||||||
},
|
|
||||||
"extends": "eslint-config-discourse"
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
name: Discourse Plugin
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ci:
|
|
||||||
uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1
|
|
||||||
+4
-2
@@ -1,2 +1,4 @@
|
|||||||
node_modules
|
.DS_Store
|
||||||
/gems
|
gems
|
||||||
|
log
|
||||||
|
.byebug_history
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
inherit_gem:
|
|
||||||
rubocop-discourse: stree-compat.yml
|
|
||||||
AllCops:
|
|
||||||
Exclude:
|
|
||||||
- "gems/**/*"
|
|
||||||
- "vendor/bundle/**/*"
|
|
||||||
Discourse/NoAddReferenceOrAliasesActiveRecordMigration:
|
|
||||||
Enabled: false
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
2.3.3
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: ["ember-template-lint-plugin-discourse"],
|
|
||||||
extends: "discourse:recommended",
|
|
||||||
};
|
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
language: ruby
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- DISCOURSE_HOSTNAME=www.example.com
|
||||||
|
|
||||||
|
dist: trusty
|
||||||
|
|
||||||
|
cache: bundler --path $TRAVIS_BUILD_DIR/discourse/vendor
|
||||||
|
|
||||||
|
addons:
|
||||||
|
postgresql: 9.5
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- gifsicle
|
||||||
|
- jpegoptim
|
||||||
|
- optipng
|
||||||
|
- jhead
|
||||||
|
|
||||||
|
rvm:
|
||||||
|
- 2.3.3
|
||||||
|
|
||||||
|
services:
|
||||||
|
- redis-server
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- git clone https://github.com/discourse/discourse.git discourse
|
||||||
|
- cd discourse
|
||||||
|
|
||||||
|
install:
|
||||||
|
- touch config/stripe.yml
|
||||||
|
- git clone $TRAVIS_BUILD_DIR plugins/discourse-donations --depth 50 --branch $TRAVIS_BRANCH
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- gem install bundler
|
||||||
|
- bundle install
|
||||||
|
- bundle exec rake db:create db:migrate
|
||||||
|
|
||||||
|
script:
|
||||||
|
- "bundle exec rake plugin:spec[discourse-donations]"
|
||||||
|
- "MODULE='Acceptance: Discourse Donations' bundle exec rake qunit:test[20000]"
|
||||||
|
- "MODULE='component:stripe-card' bundle exec rake qunit:test[20000]"
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
source "https://rubygems.org"
|
|
||||||
|
|
||||||
group :development do
|
|
||||||
gem "translations-manager", git: "https://github.com/discourse/translations-manager.git"
|
|
||||||
gem "rubocop-discourse"
|
|
||||||
gem "syntax_tree"
|
|
||||||
end
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
GIT
|
|
||||||
remote: https://github.com/discourse/translations-manager.git
|
|
||||||
revision: a8b225f7fabd3250ba88a4a2eff797693df51192
|
|
||||||
specs:
|
|
||||||
translations-manager (0.6)
|
|
||||||
|
|
||||||
GEM
|
|
||||||
remote: https://rubygems.org/
|
|
||||||
specs:
|
|
||||||
ast (2.4.2)
|
|
||||||
json (2.6.2)
|
|
||||||
parallel (1.22.1)
|
|
||||||
parser (3.1.2.1)
|
|
||||||
ast (~> 2.4.1)
|
|
||||||
prettier_print (1.2.0)
|
|
||||||
rainbow (3.1.1)
|
|
||||||
regexp_parser (2.6.0)
|
|
||||||
rexml (3.2.5)
|
|
||||||
rubocop (1.36.0)
|
|
||||||
json (~> 2.3)
|
|
||||||
parallel (~> 1.10)
|
|
||||||
parser (>= 3.1.2.1)
|
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
|
||||||
rexml (>= 3.2.5, < 4.0)
|
|
||||||
rubocop-ast (>= 1.20.1, < 2.0)
|
|
||||||
ruby-progressbar (~> 1.7)
|
|
||||||
unicode-display_width (>= 1.4.0, < 3.0)
|
|
||||||
rubocop-ast (1.21.0)
|
|
||||||
parser (>= 3.1.1.0)
|
|
||||||
rubocop-discourse (3.0)
|
|
||||||
rubocop (>= 1.1.0)
|
|
||||||
rubocop-rspec (>= 2.0.0)
|
|
||||||
rubocop-rspec (2.13.2)
|
|
||||||
rubocop (~> 1.33)
|
|
||||||
ruby-progressbar (1.11.0)
|
|
||||||
syntax_tree (5.1.0)
|
|
||||||
prettier_print (>= 1.2.0)
|
|
||||||
unicode-display_width (2.3.0)
|
|
||||||
|
|
||||||
PLATFORMS
|
|
||||||
ruby
|
|
||||||
|
|
||||||
DEPENDENCIES
|
|
||||||
rubocop-discourse
|
|
||||||
syntax_tree
|
|
||||||
translations-manager!
|
|
||||||
|
|
||||||
BUNDLED WITH
|
|
||||||
2.3.4
|
|
||||||
@@ -1,5 +1,60 @@
|
|||||||
# Discourse Subscriptions Plugin
|
# Discourse Donations
|
||||||
|
|
||||||
Discourse Subscriptions allows site owners to sell recurring and one-time purchase subscriptions that grant access to a group on a Discourse instance.
|
[](https://travis-ci.org/choiceaustralia/discourse-donations)
|
||||||
|
|
||||||
For more information, please see: https://meta.discourse.org/t/discourse-subscriptions-plugin/140818
|
Accept donations in Discourse! Integrates with [Stripe](https://stripe.com).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
* Be sure your site is enforcing https.
|
||||||
|
* Follow the install instructions here: https://meta.discourse.org/t/install-a-plugin/19157
|
||||||
|
* Add your Stripe public and private keys in settings and set the currency to your local value.
|
||||||
|
* Add the following script to your page header in a custom theme component: `<script src="https://js.stripe.com/v3/"></script>`
|
||||||
|
* Enable the plugin and wait for people to donate money.
|
||||||
|
|
||||||
|
## Creating new user accounts
|
||||||
|
|
||||||
|
**This is an experimental feature.** A user can create a new account if they makes a successful donation. Enable this in settings. When a user is not logged in, they will be asked to enter details for a new user account. This feature doesn't support mandatory custom user fields yet.
|
||||||
|
|
||||||
|
## Test Suite
|
||||||
|
|
||||||
|
* To run the rails specs, install the plugin and run `bundle exec rake plugin:spec[discourse-donations]` in the discourse root directory.
|
||||||
|
* To run qunit tests: `MODULE='Acceptance: Discourse Donations' bundle exec rake qunit:test[20000]`.
|
||||||
|
* To run Component tests: `MODULE='component:stripe-card' bundle exec rake qunit:test[20000]`.
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
|
||||||
|
* If you're using a zsh shell, then you probably get this error: `zsh: no matches found ...` and you'll need to escape the square brackets with backslashes.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
* Test card fails
|
||||||
|
* Handle custom fields
|
||||||
|
* Acceptance test in RSpec not qunit.
|
||||||
|
|
||||||
|
## Tested Credit Card Numbers
|
||||||
|
|
||||||
|
These numbers can be used in test mode to simulate a transaction. For more information see the [Stripe docs](https://stripe.com/docs/testing).
|
||||||
|
|
||||||
|
Card numbers in **bold** have beed tested.
|
||||||
|
|
||||||
|
* **4000 0000 0000 0077** Charge succeeds and funds will be added directly to your available balance (bypassing your pending balance).
|
||||||
|
* **4000 0000 0000 0093** Charge succeeds and domestic pricing is used (other test cards use international pricing). This card is only significant in countries with split pricing.
|
||||||
|
* **4000 0000 0000 0010** The address_line1_check and address_zip_check verifications fail. If your account is blocking payments that fail postal code validation, the charge is declined.
|
||||||
|
* **4000 0000 0000 0028** Charge succeeds but the address_line1_check verification fails.
|
||||||
|
* **4000 0000 0000 0036** The address_zip_check verification fails. If your account is blocking payments that fail postal code validation, the charge is declined.
|
||||||
|
* **4000 0000 0000 0044** Charge succeeds but the address_zip_check and address_line1_check verifications are both unavailable.
|
||||||
|
* **4000 0000 0000 0101** If a CVC number is provided, the cvc_check fails. If your account is blocking payments that fail CVC code validation, the charge is declined.
|
||||||
|
* **4000 0000 0000 0341** Attaching this card to a Customer object succeeds, but attempts to charge the customer fail.
|
||||||
|
* **4000 0000 0000 9235** Charge succeeds with a risk_level of elevated and placed into review.
|
||||||
|
* **4000 0000 0000 0002** Charge is declined with a card_declined code.
|
||||||
|
* **4100 0000 0000 0019** Charge is declined with a card_declined code and a fraudulent reason.
|
||||||
|
* **4000 0000 0000 0127** Charge is declined with an incorrect_cvc code.
|
||||||
|
* **4000 0000 0000 0069** Charge is declined with an expired_card code.
|
||||||
|
* **4000 0000 0000 0119** Charge is declined with a processing_error code.
|
||||||
|
* 4242 4242 4242 4241 Charge is declined with an incorrect_number code as the card number fails the Luhn check.
|
||||||
|
|
||||||
|
## Warranty
|
||||||
|
|
||||||
|
This software comes with no warranty of any kind.
|
||||||
|
>>>>>>> master
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
module Group
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
def plan_group(plan)
|
|
||||||
::Group.find_by_name(plan[:metadata][:group_name])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
module Stripe
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
AUTH_PROBLEM_IDENTIFIER = "#{DiscourseSubscriptions::PLUGIN_NAME}.stripe_auth_error"
|
|
||||||
|
|
||||||
def set_api_key
|
|
||||||
::Stripe.api_key = SiteSetting.discourse_subscriptions_secret_key
|
|
||||||
end
|
|
||||||
|
|
||||||
def is_stripe_configured?
|
|
||||||
SiteSetting.discourse_subscriptions_public_key.present? &&
|
|
||||||
SiteSetting.discourse_subscriptions_secret_key.present?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
require_dependency 'discourse'
|
||||||
|
|
||||||
|
module DiscourseDonations
|
||||||
|
class ChargesController < ActionController::Base
|
||||||
|
include CurrentUser
|
||||||
|
|
||||||
|
skip_before_filter :verify_authenticity_token, only: [:create]
|
||||||
|
|
||||||
|
def create
|
||||||
|
params.permit(:name, :username, :email, :password, :stripeToken, :amount, :plan, :create_account)
|
||||||
|
|
||||||
|
output = { 'messages' => [], 'rewards' => [] }
|
||||||
|
|
||||||
|
if create_account
|
||||||
|
if !email.present? || !params[:username].present?
|
||||||
|
output['messages'] << I18n.t('login.missing_user_field')
|
||||||
|
end
|
||||||
|
if params[:password] && params[:password].length > User.max_password_length
|
||||||
|
output['messages'] << I18n.t('login.password_too_long')
|
||||||
|
end
|
||||||
|
if params[:username] && ::User.reserved_username?(params[:username])
|
||||||
|
output['messages'] << I18n.t('login.reserved_username')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if output['messages'].present?
|
||||||
|
render(:json => output.merge(success: false)) and return
|
||||||
|
end
|
||||||
|
|
||||||
|
payment = DiscourseDonations::Stripe.new(secret_key, stripe_options)
|
||||||
|
|
||||||
|
begin
|
||||||
|
if params['amount'].present?
|
||||||
|
charge = payment.subscribe(email, params.merge(plan: params[:amount]))
|
||||||
|
else
|
||||||
|
charge = payment.subscribe(email, params)
|
||||||
|
end
|
||||||
|
rescue ::Stripe::CardError => e
|
||||||
|
err = e.json_body[:error]
|
||||||
|
|
||||||
|
output['messages'] << "There was an error (#{err[:type]})."
|
||||||
|
#output['messages'] << "Error code: #{err[:code]}" if err[:code]
|
||||||
|
#output['messages'] << "Decline code: #{err[:decline_code]}" if err[:decline_code]
|
||||||
|
output['messages'] << "Message: #{err[:message]}" if err[:message]
|
||||||
|
|
||||||
|
render(:json => output) and return
|
||||||
|
end
|
||||||
|
|
||||||
|
if charge['paid'] == true || charge['status'] == 'active'
|
||||||
|
output['success'] = true
|
||||||
|
|
||||||
|
output['messages'] << I18n.t('donations.payment.success')
|
||||||
|
|
||||||
|
output['rewards'] << { type: :group, name: group_name } if group_name
|
||||||
|
output['rewards'] << { type: :badge, name: badge_name } if badge_name
|
||||||
|
|
||||||
|
if create_account && email.present?
|
||||||
|
args = params.slice(:email, :username, :password, :name).merge(rewards: output['rewards'])
|
||||||
|
Jobs.enqueue(:donation_user, args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
render :json => output
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_account
|
||||||
|
params[:create_account] == 'true' && SiteSetting.discourse_donations_enable_create_accounts
|
||||||
|
end
|
||||||
|
|
||||||
|
def reward?(payment)
|
||||||
|
payment.present? && payment.successful?
|
||||||
|
end
|
||||||
|
|
||||||
|
def group_name
|
||||||
|
SiteSetting.discourse_donations_reward_group_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def badge_name
|
||||||
|
SiteSetting.discourse_donations_reward_badge_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def secret_key
|
||||||
|
SiteSetting.discourse_donations_secret_key
|
||||||
|
end
|
||||||
|
|
||||||
|
def stripe_options
|
||||||
|
{
|
||||||
|
description: SiteSetting.discourse_donations_description,
|
||||||
|
currency: SiteSetting.discourse_donations_currency
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def email
|
||||||
|
params[:email] || current_user.try(:email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
module DiscourseDonations
|
||||||
|
class PaymentsController < ApplicationController
|
||||||
|
def index
|
||||||
|
render json: {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render json: {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
module Admin
|
|
||||||
class CouponsController < ::Admin::AdminController
|
|
||||||
include DiscourseSubscriptions::Stripe
|
|
||||||
include DiscourseSubscriptions::Group
|
|
||||||
before_action :set_api_key
|
|
||||||
|
|
||||||
def index
|
|
||||||
begin
|
|
||||||
promo_codes = ::Stripe::PromotionCode.list({ limit: 100 })[:data]
|
|
||||||
promo_codes = promo_codes.select { |code| code[:coupon][:valid] == true }
|
|
||||||
render_json_dump promo_codes
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
params.require(%i[promo discount_type discount active])
|
|
||||||
begin
|
|
||||||
coupon_params = { duration: "forever" }
|
|
||||||
|
|
||||||
case params[:discount_type]
|
|
||||||
when "amount"
|
|
||||||
coupon_params[:amount_off] = params[:discount].to_i * 100
|
|
||||||
coupon_params[:currency] = SiteSetting.discourse_subscriptions_currency
|
|
||||||
when "percent"
|
|
||||||
coupon_params[:percent_off] = params[:discount]
|
|
||||||
end
|
|
||||||
|
|
||||||
coupon = ::Stripe::Coupon.create(coupon_params)
|
|
||||||
|
|
||||||
promo_code =
|
|
||||||
::Stripe::PromotionCode.create(
|
|
||||||
{ coupon: coupon[:id], code: params[:promo] },
|
|
||||||
) if coupon.present?
|
|
||||||
|
|
||||||
render_json_dump promo_code
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
params.require(%i[id active])
|
|
||||||
begin
|
|
||||||
promo_code = ::Stripe::PromotionCode.update(params[:id], { active: params[:active] })
|
|
||||||
|
|
||||||
render_json_dump promo_code
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
params.require(:coupon_id)
|
|
||||||
begin
|
|
||||||
coupon = ::Stripe::Coupon.delete(params[:coupon_id])
|
|
||||||
render_json_dump coupon
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
module Admin
|
|
||||||
class PlansController < ::Admin::AdminController
|
|
||||||
include DiscourseSubscriptions::Stripe
|
|
||||||
|
|
||||||
before_action :set_api_key
|
|
||||||
|
|
||||||
def index
|
|
||||||
begin
|
|
||||||
plans = ::Stripe::Price.list(product_params)
|
|
||||||
|
|
||||||
render_json_dump plans.data
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
begin
|
|
||||||
price_object = {
|
|
||||||
nickname: params[:nickname],
|
|
||||||
unit_amount: params[:amount],
|
|
||||||
product: params[:product],
|
|
||||||
currency: params[:currency],
|
|
||||||
active: params[:active],
|
|
||||||
metadata: {
|
|
||||||
group_name: params[:metadata][:group_name],
|
|
||||||
trial_period_days: params[:trial_period_days],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
price_object[:recurring] = { interval: params[:interval] } if params[:type] == "recurring"
|
|
||||||
|
|
||||||
plan = ::Stripe::Price.create(price_object)
|
|
||||||
|
|
||||||
render_json_dump plan
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
|
||||||
begin
|
|
||||||
plan = ::Stripe::Price.retrieve(params[:id])
|
|
||||||
|
|
||||||
if plan[:metadata] && plan[:metadata][:trial_period_days]
|
|
||||||
trial_days = plan[:metadata][:trial_period_days]
|
|
||||||
elsif plan[:recurring] && plan[:recurring][:trial_period_days]
|
|
||||||
trial_days = plan[:recurring][:trial_period_days]
|
|
||||||
end
|
|
||||||
|
|
||||||
interval = nil
|
|
||||||
interval = plan[:recurring][:interval] if plan[:recurring] && plan[:recurring][:interval]
|
|
||||||
|
|
||||||
serialized =
|
|
||||||
plan.to_h.merge(
|
|
||||||
trial_period_days: trial_days,
|
|
||||||
currency: plan[:currency].upcase,
|
|
||||||
interval: interval,
|
|
||||||
)
|
|
||||||
|
|
||||||
render_json_dump serialized
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
begin
|
|
||||||
plan =
|
|
||||||
::Stripe::Price.update(
|
|
||||||
params[:id],
|
|
||||||
nickname: params[:nickname],
|
|
||||||
active: params[:active],
|
|
||||||
metadata: {
|
|
||||||
group_name: params[:metadata][:group_name],
|
|
||||||
trial_period_days: params[:trial_period_days],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
render_json_dump plan
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def product_params
|
|
||||||
{ product: params[:product_id] } if params[:product_id]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
module Admin
|
|
||||||
class ProductsController < ::Admin::AdminController
|
|
||||||
include DiscourseSubscriptions::Stripe
|
|
||||||
|
|
||||||
before_action :set_api_key
|
|
||||||
|
|
||||||
def index
|
|
||||||
begin
|
|
||||||
product_ids = Product.all.pluck(:external_id)
|
|
||||||
products = []
|
|
||||||
|
|
||||||
if product_ids.present? && is_stripe_configured?
|
|
||||||
products = ::Stripe::Product.list({ ids: product_ids, limit: 100 })
|
|
||||||
products = products[:data]
|
|
||||||
elsif !is_stripe_configured?
|
|
||||||
products = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
render_json_dump products
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
begin
|
|
||||||
create_params = product_params.merge!(type: "service")
|
|
||||||
|
|
||||||
create_params.except!(:statement_descriptor) if params[:statement_descriptor].blank?
|
|
||||||
|
|
||||||
product = ::Stripe::Product.create(create_params)
|
|
||||||
|
|
||||||
Product.create(external_id: product[:id])
|
|
||||||
|
|
||||||
render_json_dump product
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
|
||||||
begin
|
|
||||||
product = ::Stripe::Product.retrieve(params[:id])
|
|
||||||
|
|
||||||
render_json_dump product
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
begin
|
|
||||||
product = ::Stripe::Product.update(params[:id], product_params)
|
|
||||||
|
|
||||||
render_json_dump product
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
begin
|
|
||||||
product = ::Stripe::Product.delete(params[:id])
|
|
||||||
|
|
||||||
Product.delete_by(external_id: params[:id])
|
|
||||||
|
|
||||||
render_json_dump product
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def product_params
|
|
||||||
params.permit!
|
|
||||||
|
|
||||||
{
|
|
||||||
name: params[:name],
|
|
||||||
active: params[:active],
|
|
||||||
statement_descriptor: params[:statement_descriptor],
|
|
||||||
metadata: {
|
|
||||||
description: params.dig(:metadata, :description),
|
|
||||||
repurchaseable: params.dig(:metadata, :repurchaseable),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
module Admin
|
|
||||||
class SubscriptionsController < ::Admin::AdminController
|
|
||||||
include DiscourseSubscriptions::Stripe
|
|
||||||
include DiscourseSubscriptions::Group
|
|
||||||
before_action :set_api_key
|
|
||||||
|
|
||||||
PAGE_LIMIT = 10
|
|
||||||
|
|
||||||
def index
|
|
||||||
begin
|
|
||||||
subscription_ids = Subscription.all.pluck(:external_id)
|
|
||||||
subscriptions = {
|
|
||||||
has_more: false,
|
|
||||||
data: [],
|
|
||||||
length: 0,
|
|
||||||
last_record: params[:last_record],
|
|
||||||
}
|
|
||||||
|
|
||||||
if subscription_ids.present? && is_stripe_configured?
|
|
||||||
while subscriptions[:length] < PAGE_LIMIT
|
|
||||||
current_set = get_subscriptions(subscriptions[:last_record])
|
|
||||||
|
|
||||||
until valid_subscriptions =
|
|
||||||
find_valid_subscriptions(current_set[:data], subscription_ids)
|
|
||||||
current_set = get_subscriptions(current_set[:data].last)
|
|
||||||
break if current_set[:has_more] == false
|
|
||||||
end
|
|
||||||
|
|
||||||
subscriptions[:data] = subscriptions[:data].concat(valid_subscriptions.to_a)
|
|
||||||
subscriptions[:last_record] = current_set[:data].last[:id] if current_set[
|
|
||||||
:data
|
|
||||||
].present?
|
|
||||||
subscriptions[:length] = subscriptions[:data].length
|
|
||||||
subscriptions[:has_more] = current_set[:has_more]
|
|
||||||
break if subscriptions[:has_more] == false
|
|
||||||
end
|
|
||||||
elsif !is_stripe_configured?
|
|
||||||
subscriptions = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
render_json_dump subscriptions
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
params.require(:id)
|
|
||||||
begin
|
|
||||||
refund_subscription(params[:id]) if params[:refund]
|
|
||||||
subscription = ::Stripe::Subscription.delete(params[:id])
|
|
||||||
|
|
||||||
customer =
|
|
||||||
Customer.find_by(
|
|
||||||
product_id: subscription[:plan][:product],
|
|
||||||
customer_id: subscription[:customer],
|
|
||||||
)
|
|
||||||
|
|
||||||
Subscription.delete_by(external_id: params[:id])
|
|
||||||
|
|
||||||
if customer
|
|
||||||
user = ::User.find(customer.user_id)
|
|
||||||
customer.delete
|
|
||||||
group = plan_group(subscription[:plan])
|
|
||||||
group.remove(user) if group
|
|
||||||
end
|
|
||||||
|
|
||||||
render_json_dump subscription
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def get_subscriptions(start)
|
|
||||||
::Stripe::Subscription.list(
|
|
||||||
expand: ["data.plan.product"],
|
|
||||||
limit: PAGE_LIMIT,
|
|
||||||
starting_after: start,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_valid_subscriptions(data, ids)
|
|
||||||
valid = data.select { |sub| ids.include?(sub[:id]) }
|
|
||||||
valid.empty? ? nil : valid
|
|
||||||
end
|
|
||||||
|
|
||||||
# this will only refund the most recent subscription payment
|
|
||||||
def refund_subscription(subscription_id)
|
|
||||||
subscription = ::Stripe::Subscription.retrieve(subscription_id)
|
|
||||||
invoice = ::Stripe::Invoice.retrieve(subscription[:latest_invoice]) if subscription[
|
|
||||||
:latest_invoice
|
|
||||||
]
|
|
||||||
payment_intent = invoice[:payment_intent] if invoice[:payment_intent]
|
|
||||||
refund = ::Stripe::Refund.create({ payment_intent: payment_intent })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
class AdminController < ::Admin::AdminController
|
|
||||||
def index
|
|
||||||
head 200
|
|
||||||
end
|
|
||||||
|
|
||||||
def refresh_campaign
|
|
||||||
Jobs.enqueue(:manually_update_campaign_data)
|
|
||||||
render json: success_json
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_campaign
|
|
||||||
begin
|
|
||||||
DiscourseSubscriptions::Campaign.new.create_campaign
|
|
||||||
render json: success_json
|
|
||||||
rescue => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
class HooksController < ::ApplicationController
|
|
||||||
include DiscourseSubscriptions::Group
|
|
||||||
include DiscourseSubscriptions::Stripe
|
|
||||||
|
|
||||||
layout false
|
|
||||||
|
|
||||||
skip_before_action :check_xhr
|
|
||||||
skip_before_action :redirect_to_login_if_required
|
|
||||||
skip_before_action :verify_authenticity_token, only: [:create]
|
|
||||||
|
|
||||||
def create
|
|
||||||
begin
|
|
||||||
payload = request.body.read
|
|
||||||
sig_header = request.env["HTTP_STRIPE_SIGNATURE"]
|
|
||||||
webhook_secret = SiteSetting.discourse_subscriptions_webhook_secret
|
|
||||||
|
|
||||||
event = ::Stripe::Webhook.construct_event(payload, sig_header, webhook_secret)
|
|
||||||
rescue JSON::ParserError => e
|
|
||||||
return render_json_error e.message
|
|
||||||
rescue ::Stripe::SignatureVerificationError => e
|
|
||||||
return render_json_error e.message
|
|
||||||
end
|
|
||||||
|
|
||||||
case event[:type]
|
|
||||||
when "customer.subscription.created"
|
|
||||||
when "customer.subscription.updated"
|
|
||||||
customer =
|
|
||||||
Customer.find_by(
|
|
||||||
customer_id: event[:data][:object][:customer],
|
|
||||||
product_id: event[:data][:object][:plan][:product],
|
|
||||||
)
|
|
||||||
|
|
||||||
return render_json_error "customer not found" if !customer
|
|
||||||
return head 200 if event[:data][:object][:status] != "complete"
|
|
||||||
|
|
||||||
user = ::User.find_by(id: customer.user_id)
|
|
||||||
return render_json_error "user not found" if !user
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
customer.destroy!
|
|
||||||
end
|
|
||||||
|
|
||||||
head 200
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
class SubscribeController < ::ApplicationController
|
|
||||||
include DiscourseSubscriptions::Stripe
|
|
||||||
include DiscourseSubscriptions::Group
|
|
||||||
before_action :set_api_key
|
|
||||||
requires_login except: %i[index contributors show]
|
|
||||||
|
|
||||||
def index
|
|
||||||
begin
|
|
||||||
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 { |p| serialize_product(p) }
|
|
||||||
end
|
|
||||||
|
|
||||||
render_json_dump products
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def contributors
|
|
||||||
return unless SiteSetting.discourse_subscriptions_campaign_show_contributors
|
|
||||||
contributor_ids = Set.new
|
|
||||||
|
|
||||||
campaign_product = SiteSetting.discourse_subscriptions_campaign_product
|
|
||||||
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)
|
|
||||||
|
|
||||||
render_serialized(contributors, UserSerializer)
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
|
||||||
params.require(:id)
|
|
||||||
begin
|
|
||||||
product = ::Stripe::Product.retrieve(params[:id])
|
|
||||||
plans = ::Stripe::Price.list(active: true, product: params[:id])
|
|
||||||
|
|
||||||
response = { product: serialize_product(product), plans: serialize_plans(plans) }
|
|
||||||
|
|
||||||
render_json_dump response
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
params.require(%i[source plan])
|
|
||||||
begin
|
|
||||||
customer =
|
|
||||||
find_or_create_customer(
|
|
||||||
params[:source],
|
|
||||||
params[:cardholder_name],
|
|
||||||
params[:cardholder_address],
|
|
||||||
)
|
|
||||||
plan = ::Stripe::Price.retrieve(params[:plan])
|
|
||||||
|
|
||||||
if params[:promo].present?
|
|
||||||
promo_code = ::Stripe::PromotionCode.list({ code: params[:promo] })
|
|
||||||
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")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
recurring_plan = plan[:type] == "recurring"
|
|
||||||
|
|
||||||
if recurring_plan
|
|
||||||
trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] &&
|
|
||||||
plan[:metadata][:trial_period_days]
|
|
||||||
|
|
||||||
promo_code_id = promo_code[:id] if promo_code
|
|
||||||
|
|
||||||
transaction =
|
|
||||||
::Stripe::Subscription.create(
|
|
||||||
customer: customer[:id],
|
|
||||||
items: [{ price: params[:plan] }],
|
|
||||||
metadata: metadata_user,
|
|
||||||
trial_period_days: trial_days,
|
|
||||||
promotion_code: promo_code_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
payment_intent = retrieve_payment_intent(transaction[:latest_invoice]) if transaction[
|
|
||||||
:status
|
|
||||||
] == "incomplete"
|
|
||||||
else
|
|
||||||
coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] &&
|
|
||||||
promo_code[:coupon][:id]
|
|
||||||
invoice_item =
|
|
||||||
::Stripe::InvoiceItem.create(
|
|
||||||
customer: customer[:id],
|
|
||||||
price: params[:plan],
|
|
||||||
discounts: [{ coupon: coupon_id }],
|
|
||||||
)
|
|
||||||
invoice = ::Stripe::Invoice.create(customer: customer[:id])
|
|
||||||
transaction = ::Stripe::Invoice.finalize_invoice(invoice[:id])
|
|
||||||
payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] ==
|
|
||||||
"open"
|
|
||||||
transaction = ::Stripe::Invoice.pay(invoice[:id]) if payment_intent[:status] ==
|
|
||||||
"successful"
|
|
||||||
end
|
|
||||||
|
|
||||||
finalize_transaction(transaction, plan) if transaction_ok(transaction)
|
|
||||||
|
|
||||||
transaction = transaction.to_h.merge(transaction, payment_intent: payment_intent)
|
|
||||||
|
|
||||||
render_json_dump transaction
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def finalize
|
|
||||||
params.require(%i[plan transaction])
|
|
||||||
begin
|
|
||||||
price = ::Stripe::Price.retrieve(params[:plan])
|
|
||||||
transaction = retrieve_transaction(params[:transaction])
|
|
||||||
finalize_transaction(transaction, price) if transaction_ok(transaction)
|
|
||||||
|
|
||||||
render_json_dump params[:transaction]
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def finalize_transaction(transaction, plan)
|
|
||||||
group = plan_group(plan)
|
|
||||||
|
|
||||||
group.add(current_user) if group
|
|
||||||
|
|
||||||
customer =
|
|
||||||
Customer.create(
|
|
||||||
user_id: current_user.id,
|
|
||||||
customer_id: transaction[:customer],
|
|
||||||
product_id: plan[:product],
|
|
||||||
)
|
|
||||||
|
|
||||||
if transaction[:object] == "subscription"
|
|
||||||
Subscription.create(customer_id: customer.id, external_id: transaction[:id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def serialize_product(product)
|
|
||||||
{
|
|
||||||
id: product[:id],
|
|
||||||
name: product[:name],
|
|
||||||
description: PrettyText.cook(product[:metadata][:description]),
|
|
||||||
subscribed: current_user_products.include?(product[:id]),
|
|
||||||
repurchaseable: product[:metadata][:repurchaseable],
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_user_products
|
|
||||||
return [] if current_user.nil?
|
|
||||||
|
|
||||||
Customer.select(:product_id).where(user_id: current_user.id).map { |c| c.product_id }.compact
|
|
||||||
end
|
|
||||||
|
|
||||||
def serialize_plans(plans)
|
|
||||||
plans[:data]
|
|
||||||
.map { |plan| plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring) }
|
|
||||||
.sort_by { |plan| plan[:amount] }
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_or_create_customer(source, cardholder_name = nil, cardholder_address = nil)
|
|
||||||
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?
|
|
||||||
::Stripe::Customer.retrieve(customer.customer_id)
|
|
||||||
else
|
|
||||||
::Stripe::Customer.create(
|
|
||||||
email: current_user.email,
|
|
||||||
source: source,
|
|
||||||
name: cardholder_name,
|
|
||||||
address: cardholder_address,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def retrieve_payment_intent(invoice_id)
|
|
||||||
invoice = ::Stripe::Invoice.retrieve(invoice_id)
|
|
||||||
::Stripe::PaymentIntent.retrieve(invoice[:payment_intent])
|
|
||||||
end
|
|
||||||
|
|
||||||
def retrieve_transaction(transaction)
|
|
||||||
begin
|
|
||||||
case transaction
|
|
||||||
when /^sub_/
|
|
||||||
::Stripe::Subscription.retrieve(transaction)
|
|
||||||
when /^in_/
|
|
||||||
::Stripe::Invoice.retrieve(transaction)
|
|
||||||
end
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def metadata_user
|
|
||||||
{ user_id: current_user.id, username: current_user.username_lower }
|
|
||||||
end
|
|
||||||
|
|
||||||
def transaction_ok(transaction)
|
|
||||||
%w[active trialing paid].include?(transaction[:status])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
module User
|
|
||||||
class PaymentsController < ::ApplicationController
|
|
||||||
include DiscourseSubscriptions::Stripe
|
|
||||||
before_action :set_api_key
|
|
||||||
requires_login
|
|
||||||
|
|
||||||
def index
|
|
||||||
begin
|
|
||||||
customer = Customer.where(user_id: current_user.id)
|
|
||||||
customer_ids = customer.map { |c| c.customer_id } if customer
|
|
||||||
product_ids = Product.all.pluck(:external_id)
|
|
||||||
|
|
||||||
data = []
|
|
||||||
|
|
||||||
if customer_ids.present? && product_ids.present?
|
|
||||||
customer_ids.each do |customer_id|
|
|
||||||
# lots of matching because the Stripe API doesn't make it easy to match products => payments except from invoices
|
|
||||||
all_invoices = ::Stripe::Invoice.list(customer: customer_id)
|
|
||||||
invoices_with_products = parse_invoices(all_invoices, product_ids)
|
|
||||||
invoice_ids = invoices_with_products.map { |invoice| invoice[:id] }
|
|
||||||
payments = ::Stripe::PaymentIntent.list(customer: customer_id)
|
|
||||||
payments_from_invoices =
|
|
||||||
payments[:data].select { |payment| invoice_ids.include?(payment[:invoice]) }
|
|
||||||
data = data | payments_from_invoices
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
data = data.sort_by { |pmt| pmt[:created] }.reverse
|
|
||||||
|
|
||||||
render_json_dump data
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def parse_invoices(all_invoices, product_ids)
|
|
||||||
invoices_with_products =
|
|
||||||
all_invoices[:data].select do |invoice|
|
|
||||||
invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data]
|
|
||||||
invoice_product_id = parse_invoice_lines(invoice_lines)
|
|
||||||
product_ids.include?(invoice_product_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_invoice_lines(invoice_lines)
|
|
||||||
invoice_product_id = invoice_lines[:price][:product] if invoice_lines[:price] &&
|
|
||||||
invoice_lines[:price][:product]
|
|
||||||
invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] &&
|
|
||||||
invoice_lines[:plan][:product]
|
|
||||||
invoice_product_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
module User
|
|
||||||
class SubscriptionsController < ::ApplicationController
|
|
||||||
include DiscourseSubscriptions::Stripe
|
|
||||||
include DiscourseSubscriptions::Group
|
|
||||||
before_action :set_api_key
|
|
||||||
requires_login
|
|
||||||
|
|
||||||
def index
|
|
||||||
begin
|
|
||||||
customer = Customer.where(user_id: current_user.id)
|
|
||||||
customer_ids = customer.map { |c| c.id } if customer
|
|
||||||
subscription_ids =
|
|
||||||
Subscription.where("customer_id in (?)", customer_ids).pluck(
|
|
||||||
:external_id,
|
|
||||||
) if customer_ids
|
|
||||||
|
|
||||||
subscriptions = []
|
|
||||||
|
|
||||||
if subscription_ids
|
|
||||||
plans = ::Stripe::Price.list(expand: ["data.product"], limit: 100)
|
|
||||||
|
|
||||||
customers =
|
|
||||||
::Stripe::Customer.list(email: current_user.email, expand: ["data.subscriptions"])
|
|
||||||
|
|
||||||
subscriptions =
|
|
||||||
customers[:data].map { |sub_customer| sub_customer[:subscriptions][:data] }.flatten(1)
|
|
||||||
|
|
||||||
subscriptions = subscriptions.select { |sub| subscription_ids.include?(sub[:id]) }
|
|
||||||
|
|
||||||
subscriptions.map! do |subscription|
|
|
||||||
plan = plans[:data].find { |p| p[:id] == subscription[:items][:data][0][:price][:id] }
|
|
||||||
subscription.to_h.except!(:plan)
|
|
||||||
subscription.to_h.merge(plan: plan, product: plan[:product].to_h.slice(:id, :name))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
render_json_dump subscriptions
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
# we cancel but don't remove until the end of the period
|
|
||||||
# full removal is done via webhooks
|
|
||||||
begin
|
|
||||||
subscription = ::Stripe::Subscription.update(params[:id], { cancel_at_period_end: true })
|
|
||||||
|
|
||||||
if subscription
|
|
||||||
render_json_dump subscription
|
|
||||||
else
|
|
||||||
render_json_error I18n.t("discourse_subscriptions.customer_not_found")
|
|
||||||
end
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
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
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
module DiscourseDonations
|
||||||
|
module ApplicationHelper
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
load File.expand_path('../regular/donation_user.rb', __FILE__)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
module Jobs
|
||||||
|
class DonationUser < ::Jobs::Base
|
||||||
|
def execute(args)
|
||||||
|
user = User.create!(args.slice(:username, :password, :name, :email))
|
||||||
|
return unless user.persisted?
|
||||||
|
Jobs.enqueue(
|
||||||
|
:critical_user_email,
|
||||||
|
type: :signup, user_id: user.id, email_token: user.email_tokens.first.token
|
||||||
|
)
|
||||||
|
rewards = DiscourseDonations::Rewards.new(user)
|
||||||
|
args[:rewards].to_a.each do |reward|
|
||||||
|
rewards.grant_badge(reward[:name]) if reward[:type] == 'badge'
|
||||||
|
rewards.add_to_group(reward[:name]) if reward[:type] == 'group'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ::Jobs
|
|
||||||
class ManuallyUpdateCampaignData < ::Jobs::Base
|
|
||||||
def execute(args)
|
|
||||||
return unless SiteSetting.discourse_subscriptions_campaign_enabled
|
|
||||||
DiscourseSubscriptions::Campaign.new.refresh_data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user