diff --git a/app/controllers/discourse_subscriptions/admin/plans_controller.rb b/app/controllers/discourse_subscriptions/admin/plans_controller.rb index b37f7c2..2326f62 100644 --- a/app/controllers/discourse_subscriptions/admin/plans_controller.rb +++ b/app/controllers/discourse_subscriptions/admin/plans_controller.rb @@ -12,7 +12,6 @@ module DiscourseSubscriptions plans = ::Stripe::Price.list(product_params) render_json_dump plans.data - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -20,12 +19,9 @@ module DiscourseSubscriptions def create begin - plan = ::Stripe::Price.create( + price_object = { nickname: params[:nickname], unit_amount: params[:amount], - recurring: { - interval: params[:interval], - }, product: params[:product], currency: params[:currency], active: params[:active], @@ -33,10 +29,17 @@ module DiscourseSubscriptions group_name: params[:metadata][:group_name], trial_period_days: params[:trial_period_days] } - ) + } + + if params[:type] == 'recurring' + price_object[:recurring] = { + interval: params[:interval] + } + end + + plan = ::Stripe::Price.create(price_object) render_json_dump plan - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -74,7 +77,6 @@ module DiscourseSubscriptions ) render_json_dump plan - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end diff --git a/app/controllers/discourse_subscriptions/hooks_controller.rb b/app/controllers/discourse_subscriptions/hooks_controller.rb index 1c3e2c1..1c64768 100644 --- a/app/controllers/discourse_subscriptions/hooks_controller.rb +++ b/app/controllers/discourse_subscriptions/hooks_controller.rb @@ -12,7 +12,6 @@ module DiscourseSubscriptions webhook_secret = SiteSetting.discourse_subscriptions_webhook_secret event = ::Stripe::Webhook.construct_event(payload, sig_header, webhook_secret) - rescue JSON::ParserError => e render_json_error e.message return diff --git a/app/controllers/discourse_subscriptions/invoices_controller.rb b/app/controllers/discourse_subscriptions/invoices_controller.rb deleted file mode 100644 index fe35d7d..0000000 --- a/app/controllers/discourse_subscriptions/invoices_controller.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module DiscourseSubscriptions - class InvoicesController < ::ApplicationController - include DiscourseSubscriptions::Stripe - before_action :set_api_key - requires_login - - def index - begin - customer = find_customer - - if viewing_own_invoices && customer.present? - invoices = ::Stripe::Invoice.list(customer: customer.customer_id) - - render_json_dump invoices.data - else - render_json_dump [] - end - rescue ::Stripe::InvalidRequestError => e - render_json_error e.message - end - end - - private - - def viewing_own_invoices - current_user.id == params[:user_id].to_i - end - - def find_customer - Customer.find_user(current_user) - end - end -end diff --git a/app/controllers/discourse_subscriptions/payments_controller.rb b/app/controllers/discourse_subscriptions/payments_controller.rb deleted file mode 100644 index 72f6bc0..0000000 --- a/app/controllers/discourse_subscriptions/payments_controller.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module DiscourseSubscriptions - class PaymentsController < ::ApplicationController - include DiscourseSubscriptions::Stripe - - skip_before_action :verify_authenticity_token, only: [:create] - before_action :set_api_key - - requires_login - - def create - begin - customer = Customer.where(user_id: current_user.id, product_id: nil).first_or_create do |c| - new_customer = ::Stripe::Customer.create( - email: current_user.email - ) - - c.customer_id = new_customer[:id] - end - - payment = ::Stripe::PaymentIntent.create( - payment_method_types: ['card'], - payment_method: params[:payment_method], - amount: params[:amount], - currency: params[:currency], - customer: customer[:customer_id], - confirm: true - ) - - render_json_dump payment - - rescue ::Stripe::InvalidRequestError => e - render_json_error e.message - rescue ::Stripe::CardError => e - render_json_error I18n.t('discourse_subscriptions.card.declined') - end - end - end -end diff --git a/app/controllers/discourse_subscriptions/plans_controller.rb b/app/controllers/discourse_subscriptions/plans_controller.rb index 0ae171f..15a4c8e 100644 --- a/app/controllers/discourse_subscriptions/plans_controller.rb +++ b/app/controllers/discourse_subscriptions/plans_controller.rb @@ -15,11 +15,10 @@ module DiscourseSubscriptions end serialized = plans[:data].map do |plan| - plan.to_h.slice(:id, :unit_amount, :currency, :recurring) + plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring) end.sort_by { |plan| plan[:amount] } render_json_dump serialized - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end diff --git a/app/controllers/discourse_subscriptions/subscriptions_controller.rb b/app/controllers/discourse_subscriptions/subscriptions_controller.rb index 55dfaa8..0ad7650 100644 --- a/app/controllers/discourse_subscriptions/subscriptions_controller.rb +++ b/app/controllers/discourse_subscriptions/subscriptions_controller.rb @@ -19,7 +19,6 @@ module DiscourseSubscriptions end render_json_dump subscriptions - rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -29,36 +28,48 @@ module DiscourseSubscriptions begin plan = ::Stripe::Price.retrieve(params[:plan]) - if plan[:metadata] && plan[:metadata][:trial_period_days] - trial_days = plan[:metadata][:trial_period_days] + recurring_plan = plan[:type] == 'recurring' + + if recurring_plan + trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] && plan[:metadata][:trial_period_days] + + transaction = ::Stripe::Subscription.create( + customer: params[:customer], + items: [{ price: params[:plan] }], + metadata: metadata_user, + trial_period_days: trial_days + ) + else + invoice_item = ::Stripe::InvoiceItem.create( + customer: params[:customer], + price: params[:plan] + ) + invoice = ::Stripe::Invoice.create( + customer: params[:customer] + ) + transaction = ::Stripe::Invoice.pay(invoice[:id]) end - @subscription = ::Stripe::Subscription.create( - customer: params[:customer], - items: [ { price: params[:plan] } ], - metadata: metadata_user, - trial_period_days: trial_days - ) + if transaction_ok(transaction) + group = plan_group(plan) - group = plan_group(plan) + group.add(current_user) if group - if subscription_ok && group - group.add(current_user) + customer = Customer.create( + user_id: current_user.id, + customer_id: params[:customer], + product_id: plan[:product] + ) + + if transaction[:object] == 'subscription' + Subscription.create( + customer_id: customer.id, + external_id: transaction[:id] + ) + end end - customer = Customer.create( - user_id: current_user.id, - customer_id: params[:customer], - product_id: plan[:product] - ) - - Subscription.create( - customer_id: customer.id, - external_id: @subscription[:id] - ) - - render_json_dump @subscription - + render_json_dump transaction rescue ::Stripe::InvalidRequestError => e render_json_error e.message end @@ -70,8 +81,8 @@ module DiscourseSubscriptions { user_id: current_user.id, username: current_user.username_lower } end - def subscription_ok - ['active', 'trialing'].include?(@subscription[:status]) + def transaction_ok(transaction) + %w[active trialing paid].include?(transaction[:status]) end end end diff --git a/app/controllers/discourse_subscriptions/user/payments_controller.rb b/app/controllers/discourse_subscriptions/user/payments_controller.rb index 95b7688..7f870a6 100644 --- a/app/controllers/discourse_subscriptions/user/payments_controller.rb +++ b/app/controllers/discourse_subscriptions/user/payments_controller.rb @@ -21,9 +21,10 @@ module DiscourseSubscriptions all_invoices = ::Stripe::Invoice.list(customer: customer_id) invoices_with_products = all_invoices[:data].select do |invoice| # i cannot dig it so we must get iffy with it - if invoice[:lines] && invoice[:lines][:data] && invoice[:lines][:data][0] && invoice[:lines][:data][0][:plan] && invoice[:lines][:data][0][:plan][:product] - product_ids.include?(invoice[:lines][:data][0][:plan][:product]) - end + invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data] + 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] + product_ids.include?(invoice_product_id) end invoice_ids = invoices_with_products.map { |invoice| invoice[:id] } payments = ::Stripe::PaymentIntent.list(customer: customer_id) diff --git a/assets/javascripts/discourse/components/payment-options.js.es6 b/assets/javascripts/discourse/components/payment-options.js.es6 index f702f1d..c19f52b 100644 --- a/assets/javascripts/discourse/components/payment-options.js.es6 +++ b/assets/javascripts/discourse/components/payment-options.js.es6 @@ -1,22 +1,9 @@ -import { equal } from "@ember/object/computed"; import Component from "@ember/component"; export default Component.extend({ - planButtonSelected: equal("planTypeIsSelected", true), - paymentButtonSelected: equal("planTypeIsSelected", false), - actions: { - selectPlans() { - this.set("planTypeIsSelected", true); - }, - - selectPayments() { - this.set("planTypeIsSelected", false); - }, - clickPlan(plan) { - this.plans.map(p => p.set("selected", false)); - plan.set("selected", true); + this.set("selectedPlan", plan.id); } } }); diff --git a/assets/javascripts/discourse/components/payment-plan.js.es6 b/assets/javascripts/discourse/components/payment-plan.js.es6 new file mode 100644 index 0000000..efb9a16 --- /dev/null +++ b/assets/javascripts/discourse/components/payment-plan.js.es6 @@ -0,0 +1,23 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; + +const RECURRING = "recurring"; + +export default Component.extend({ + @discourseComputed("selectedPlan") + selected(planId) { + return planId === this.plan.id; + }, + + @discourseComputed("plan.type") + recurringPlan(type) { + return type === RECURRING; + }, + + actions: { + planClick() { + this.clickPlan(this.plan); + return false; + } + } +}); diff --git a/assets/javascripts/discourse/controllers/admin-plugins-discourse-subscriptions-products-show-plans-show.js.es6 b/assets/javascripts/discourse/controllers/admin-plugins-discourse-subscriptions-products-show-plans-show.js.es6 index a34d2a3..c8c8b28 100644 --- a/assets/javascripts/discourse/controllers/admin-plugins-discourse-subscriptions-products-show-plans-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-plugins-discourse-subscriptions-products-show-plans-show.js.es6 @@ -2,6 +2,9 @@ import discourseComputed from "discourse-common/utils/decorators"; import DiscourseURL from "discourse/lib/url"; import Controller from "@ember/controller"; +const RECURRING = "recurring"; +const ONE_TIME = "one_time"; + export default Controller.extend({ // Also defined in settings. selectedCurrency: Ember.computed.alias("model.plan.currency"), @@ -47,6 +50,12 @@ export default Controller.extend({ }, actions: { + changeRecurring() { + const recurring = this.get("model.plan.isRecurring"); + this.set("model.plan.type", recurring ? ONE_TIME : RECURRING); + this.set("model.plan.isRecurring", !recurring); + }, + createPlan() { // TODO: set default group name beforehand if (this.get("model.plan.metadata.group_name") === undefined) { diff --git a/assets/javascripts/discourse/controllers/s-show.js.es6 b/assets/javascripts/discourse/controllers/s-show.js.es6 index 6b01d85..a623572 100644 --- a/assets/javascripts/discourse/controllers/s-show.js.es6 +++ b/assets/javascripts/discourse/controllers/s-show.js.es6 @@ -1,29 +1,13 @@ import Controller from "@ember/controller"; import Customer from "discourse/plugins/discourse-subscriptions/discourse/models/customer"; -import Payment from "discourse/plugins/discourse-subscriptions/discourse/models/payment"; import Subscription from "discourse/plugins/discourse-subscriptions/discourse/models/subscription"; -import discourseComputed from "discourse-common/utils/decorators"; import I18n from "I18n"; export default Controller.extend({ - planTypeIsSelected: true, - - @discourseComputed("planTypeIsSelected") - type(planTypeIsSelected) { - return planTypeIsSelected ? "plans" : "payment"; - }, - - @discourseComputed("type") - buttonText(type) { - return I18n.t(`discourse_subscriptions.${type}.payment_button`); - }, + selectedPlan: null, init() { this._super(...arguments); - this.set( - "paymentsAllowed", - Discourse.SiteSettings.discourse_subscriptions_allow_payments - ); this.set( "stripe", Stripe(Discourse.SiteSettings.discourse_subscriptions_public_key) @@ -37,20 +21,6 @@ export default Controller.extend({ bootbox.alert(I18n.t(`discourse_subscriptions.${path}`)); }, - createPayment(plan) { - return this.stripe - .createPaymentMethod("card", this.get("cardElement")) - .then(result => { - const payment = Payment.create({ - payment_method: result.paymentMethod.id, - amount: plan.get("amount"), - currency: plan.get("currency") - }); - - return payment.save(); - }); - }, - createSubscription(plan) { return this.stripe.createToken(this.get("cardElement")).then(result => { if (result.error) { @@ -73,24 +43,17 @@ export default Controller.extend({ actions: { stripePaymentHandler() { this.set("loading", true); - const type = this.get("type"); const plan = this.get("model.plans") - .filterBy("selected") + .filterBy("id", this.selectedPlan) .get("firstObject"); if (!plan) { - this.alert(`${type}.validate.payment_options.required`); + this.alert("plans.validate.payment_options.required"); this.set("loading", false); return; } - let transaction; - - if (this.planTypeIsSelected) { - transaction = this.createSubscription(plan); - } else { - transaction = this.createPayment(plan); - } + let transaction = this.createSubscription(plan); transaction .then(result => { @@ -98,17 +61,15 @@ export default Controller.extend({ bootbox.alert(result.error.message || result.error); } else { if (result.status === "incomplete") { - this.alert(`${type}.incomplete`); + this.alert("plans.incomplete"); } else { - this.alert(`${type}.success`); + this.alert("plans.success"); } - const success_route = this.planTypeIsSelected - ? "user.billing.subscriptions" - : "user.billing.payments"; - this.transitionToRoute( - success_route, + plan.type === "recurring" + ? "user.billing.subscriptions" + : "user.billing.payments", Discourse.User.current().username.toLowerCase() ); } diff --git a/assets/javascripts/discourse/models/admin-plan.js.es6 b/assets/javascripts/discourse/models/admin-plan.js.es6 index bc72a21..446897a 100644 --- a/assets/javascripts/discourse/models/admin-plan.js.es6 +++ b/assets/javascripts/discourse/models/admin-plan.js.es6 @@ -26,6 +26,7 @@ const AdminPlan = Plan.extend({ amount: this.unit_amount, currency: this.currency, trial_period_days: this.parseTrialPeriodDays, + type: this.type, product: this.product, metadata: this.metadata, active: this.active diff --git a/assets/javascripts/discourse/models/invoice.js.es6 b/assets/javascripts/discourse/models/invoice.js.es6 deleted file mode 100644 index 8bbb88d..0000000 --- a/assets/javascripts/discourse/models/invoice.js.es6 +++ /dev/null @@ -1,14 +0,0 @@ -import { ajax } from "discourse/lib/ajax"; -import EmberObject from "@ember/object"; - -const Invoice = EmberObject.extend({}); - -Invoice.reopenClass({ - findAll() { - return ajax("/s/invoices", { method: "get" }).then(result => - result.map(invoice => Invoice.create(invoice)) - ); - } -}); - -export default Invoice; diff --git a/assets/javascripts/discourse/models/payment.js.es6 b/assets/javascripts/discourse/models/payment.js.es6 deleted file mode 100644 index 5a2cd17..0000000 --- a/assets/javascripts/discourse/models/payment.js.es6 +++ /dev/null @@ -1,16 +0,0 @@ -import EmberObject from "@ember/object"; -import { ajax } from "discourse/lib/ajax"; - -const Payment = EmberObject.extend({ - save() { - const data = { - payment_method: this.payment_method, - amount: this.amount, - currency: this.currency - }; - - return ajax("/s/payments", { method: "post", data }); - } -}); - -export default Payment; diff --git a/assets/javascripts/discourse/routes/admin-plugins-discourse-subscriptions-products-show-plans-show.js.es6 b/assets/javascripts/discourse/routes/admin-plugins-discourse-subscriptions-products-show-plans-show.js.es6 index a89cafc..dcbb3ab 100644 --- a/assets/javascripts/discourse/routes/admin-plugins-discourse-subscriptions-products-show-plans-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-plugins-discourse-subscriptions-products-show-plans-show.js.es6 @@ -16,11 +16,14 @@ export default Route.extend({ active: true, isNew: true, interval: "month", + type: "recurring", + isRecurring: true, currency: Discourse.SiteSettings.discourse_subscriptions_currency, product: product.get("id") }); } else { plan = AdminPlan.find(id); + plan.isRecurring = plan.type === "recurring"; } const groups = Group.findAll({ ignore_automatic: true }); diff --git a/assets/javascripts/discourse/templates/admin/plugins-discourse-subscriptions-products-show-plans-show.hbs b/assets/javascripts/discourse/templates/admin/plugins-discourse-subscriptions-products-show-plans-show.hbs index fd61f37..5285e76 100644 --- a/assets/javascripts/discourse/templates/admin/plugins-discourse-subscriptions-products-show-plans-show.hbs +++ b/assets/javascripts/discourse/templates/admin/plugins-discourse-subscriptions-products-show-plans-show.hbs @@ -40,30 +40,47 @@ {{input class="plan-amount" type="text" name="name" value=model.plan.amountDollars disabled=planFieldDisabled}}
- - {{input type="text" name="trial" value=model.plan.trial_period_days}} -
-
+ {{#if model.plan.isRecurring}} +
+
+
- {{#if planTypeIsSelected}}
- {{i18n "discourse_subscriptions.plans.select"}}
- {{else}}
- {{i18n "discourse_subscriptions.payment.select"}}
- {{/if}}
+ {{i18n "discourse_subscriptions.plans.select"}}
{{plan.nickname}}
- {{plan.interval}}
+ {{plan.recurring.interval}}
{{format-unix-date plan.created}}
{{plan.metadata.group_name}}
{{plan.active}}
diff --git a/assets/javascripts/discourse/templates/components/payment-options.hbs b/assets/javascripts/discourse/templates/components/payment-options.hbs
index 0b03f27..62552fa 100644
--- a/assets/javascripts/discourse/templates/components/payment-options.hbs
+++ b/assets/javascripts/discourse/templates/components/payment-options.hbs
@@ -1,54 +1,9 @@
-
-{{#if paymentsAllowed}}
-
-{{/if}}
-
-
-
@@ -32,12 +31,12 @@
{{#if loading}}
{{loading-spinner}}
{{else}}
- {{#d-button
+ {{d-button
disabled=loading
action="stripePaymentHandler"
- class="btn btn-primary btn-payment"}}
- {{buttonText}}
- {{/d-button}}
+ class="btn btn-primary btn-payment"
+ label="discourse_subscriptions.plans.payment_button"
+ }}
{{/if}}
{{/unless}}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index ad1ebd7..e744e60 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -6,7 +6,6 @@ en:
discourse_subscriptions_secret_key: Stripe Secret Key
discourse_subscriptions_webhook_secret: Stripe Webhook Secret
discourse_subscriptions_currency: Default Currency Code. This can be overridden when creating a subscription plan.
- discourse_subscriptions_allow_payments: Allow single payments
errors:
discourse_patrons_amount_must_be_currency: "Currency amounts must be currencies without dollar symbol (eg 1.50)"
js:
@@ -21,6 +20,7 @@ en:
subscribe: Subscribe
user_activity:
payments: Payments
+ one_time_payment: One-Time Payment
plans:
purchase: Purchase a subscription
select: Select subscription plan
@@ -36,18 +36,6 @@ en:
validate:
payment_options:
required: Please select a subscription plan.
- payment:
- purchase: Make just one payment
- select: Select a payment option
- interval:
- One payment
- payment_button:
- Pay Once
- success: Thank you!
- incomplete: Payment is incomplete.
- validate:
- payment_options:
- required: Please select a payment option.
user:
payments:
id: Payment ID
@@ -140,6 +128,7 @@ en:
group_help: This is the discourse user group the customer gets added to when the subscription is created.
active: Active
created_at: Created
+ recurring: Recurring Plan?
subscriptions:
title: Subscriptions
subscription:
diff --git a/config/routes.rb b/config/routes.rb
index e453ea5..56a372a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -20,8 +20,6 @@ DiscourseSubscriptions::Engine.routes.draw do
resources :customers, only: [:create]
resources :hooks, only: [:create]
- resources :invoices, only: [:index]
- resources :payments, only: [:create]
resources :plans, only: [:index], constraints: SubscriptionsUserConstraint.new
resources :products, only: [:index, :show]
resources :subscriptions, only: [:create]
diff --git a/config/settings.yml b/config/settings.yml
index 90a3d25..1e4ce95 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -13,9 +13,6 @@ plugins:
discourse_subscriptions_webhook_secret:
default: ''
client: false
- discourse_subscriptions_allow_payments:
- default: false
- client: true
discourse_subscriptions_currency:
client: true
default: "USD"
diff --git a/plugin.rb b/plugin.rb
index 051ccdd..06b2656 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -27,14 +27,14 @@ extend_content_security_policy(
add_admin_route 'discourse_subscriptions.admin_navigation', 'discourse-subscriptions.products'
Discourse::Application.routes.append do
- get '/admin/plugins/discourse-subscriptions' => 'admin/plugins#index'
- get '/admin/plugins/discourse-subscriptions/products' => 'admin/plugins#index'
- get '/admin/plugins/discourse-subscriptions/products/:product_id' => 'admin/plugins#index'
- get '/admin/plugins/discourse-subscriptions/products/:product_id/plans' => 'admin/plugins#index'
- get '/admin/plugins/discourse-subscriptions/products/:product_id/plans/:plan_id' => 'admin/plugins#index'
- get '/admin/plugins/discourse-subscriptions/subscriptions' => 'admin/plugins#index'
- get '/admin/plugins/discourse-subscriptions/plans' => 'admin/plugins#index'
- get '/admin/plugins/discourse-subscriptions/plans/:plan_id' => 'admin/plugins#index'
+ get '/admin/plugins/discourse-subscriptions' => 'admin/plugins#index', constraints: AdminConstraint.new
+ get '/admin/plugins/discourse-subscriptions/products' => 'admin/plugins#index', constraints: AdminConstraint.new
+ get '/admin/plugins/discourse-subscriptions/products/:product_id' => 'admin/plugins#index', constraints: AdminConstraint.new
+ get '/admin/plugins/discourse-subscriptions/products/:product_id/plans' => 'admin/plugins#index', constraints: AdminConstraint.new
+ get '/admin/plugins/discourse-subscriptions/products/:product_id/plans/:plan_id' => 'admin/plugins#index', constraints: AdminConstraint.new
+ get '/admin/plugins/discourse-subscriptions/subscriptions' => 'admin/plugins#index', constraints: AdminConstraint.new
+ get '/admin/plugins/discourse-subscriptions/plans' => 'admin/plugins#index', constraints: AdminConstraint.new
+ get '/admin/plugins/discourse-subscriptions/plans/:plan_id' => 'admin/plugins#index', constraints: AdminConstraint.new
get 'u/:username/billing' => 'users#show', constraints: { username: USERNAME_ROUTE_FORMAT }
get 'u/:username/billing/:id' => 'users#show', constraints: { username: USERNAME_ROUTE_FORMAT }
end
diff --git a/spec/requests/admin/plans_controller_spec.rb b/spec/requests/admin/plans_controller_spec.rb
index 4f628df..6846727 100644
--- a/spec/requests/admin/plans_controller_spec.rb
+++ b/spec/requests/admin/plans_controller_spec.rb
@@ -98,7 +98,12 @@ module DiscourseSubscriptions
it "creates a plan with an interval" do
::Stripe::Price.expects(:create).with(has_entry(recurring: { interval: 'week' }))
- post "/s/admin/plans.json", params: { interval: 'week', metadata: { group_name: '' } }
+ post "/s/admin/plans.json", params: { type: 'recurring', interval: 'week', metadata: { group_name: '' } }
+ end
+
+ it "creates a plan as a one-time purchase" do
+ ::Stripe::Price.expects(:create).with(Not(has_key(:recurring)))
+ post "/s/admin/plans.json", params: { metadata: { group_name: '' } }
end
it "creates a plan with an amount" do
diff --git a/spec/requests/invoices_controller_spec.rb b/spec/requests/invoices_controller_spec.rb
deleted file mode 100644
index 054c56f..0000000
--- a/spec/requests/invoices_controller_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-module DiscourseSubscriptions
- RSpec.describe InvoicesController do
- describe "index" do
- describe "not authenticated" do
- it "does not list the invoices" do
- ::Stripe::Invoice.expects(:list).never
- get "/s/invoices.json"
- expect(response.status).to eq 403
- end
- end
-
- describe "authenticated" do
- let(:user) { Fabricate(:user) }
- let(:stripe_customer) { { id: 'cus_id4567' } }
-
- before do
- sign_in(user)
- end
-
- describe "other user invoices" do
- it "does not list the invoices" do
- ::Stripe::Invoice.expects(:list).never
- get "/s/invoices.json", params: { user_id: 999999 }
- end
- end
-
- describe "own invoices" do
- context "stripe customer does not exist" do
- it "lists empty" do
- ::Stripe::Invoice.expects(:list).never
- get "/s/invoices.json", params: { user_id: user.id }
- expect(response.body).to eq "[]"
- end
- end
-
- context "stripe customer exists" do
- before do
- DiscourseSubscriptions::Customer.create_customer(user, stripe_customer)
- end
-
- it "lists the invoices" do
- ::Stripe::Invoice.expects(:list).with(customer: 'cus_id4567')
- get "/s/invoices.json", params: { user_id: user.id }
- end
- end
- end
- end
- end
- end
-end
diff --git a/spec/requests/payments_controller_spec.rb b/spec/requests/payments_controller_spec.rb
deleted file mode 100644
index f4fdc17..0000000
--- a/spec/requests/payments_controller_spec.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-module DiscourseSubscriptions
- RSpec.describe PaymentsController do
- context "not authenticated" do
- it "does not create a payment intent" do
- ::Stripe::PaymentIntent.expects(:create).never
-
- post "/s/payments.json", params: {
- payment_method: 'pm_123',
- amount: 999,
- currency: 'gdp'
- }
- end
- end
-
- context "authenticated" do
- let(:user) { Fabricate(:user) }
-
- before do
- sign_in(user)
- end
-
- describe "create" do
- it "creates a payment intent" do
- ::Stripe::Customer.expects(:create).with(
- email: user.email
- ).returns(id: 'cus_87653')
-
- ::Stripe::PaymentIntent.expects(:create).with(
- payment_method_types: ['card'],
- payment_method: 'pm_123',
- amount: '999',
- currency: 'gdp',
- confirm: true,
- customer: 'cus_87653'
- )
-
- post "/s/payments.json", params: {
- payment_method: 'pm_123',
- amount: 999,
- currency: 'gdp'
- }
- end
- end
- end
- end
-end
diff --git a/spec/requests/subscriptions_controller_spec.rb b/spec/requests/subscriptions_controller_spec.rb
index e077edf..4660e24 100644
--- a/spec/requests/subscriptions_controller_spec.rb
+++ b/spec/requests/subscriptions_controller_spec.rb
@@ -22,6 +22,7 @@ module DiscourseSubscriptions
describe "create" do
it "creates a subscription" do
::Stripe::Price.expects(:retrieve).returns(
+ type: 'recurring',
product: 'product_12345',
metadata: {
group_name: 'awesome',
@@ -41,8 +42,29 @@ module DiscourseSubscriptions
}.to change { DiscourseSubscriptions::Customer.count }
end
+ it "creates a one time payment subscription" do
+ ::Stripe::Price.expects(:retrieve).returns(
+ type: 'one_time',
+ product: 'product_12345',
+ metadata: {
+ group_name: 'awesome'
+ }
+ )
+
+ ::Stripe::InvoiceItem.expects(:create)
+
+ ::Stripe::Invoice.expects(:create).returns(id: 'in_123')
+
+ ::Stripe::Invoice.expects(:pay).returns(status: 'paid')
+
+ expect {
+ post '/s/subscriptions.json', params: { plan: 'plan_1234', customer: 'cus_1234' }
+ }.to change { DiscourseSubscriptions::Customer.count }
+
+ end
+
it "creates a customer model" do
- ::Stripe::Price.expects(:retrieve).returns(metadata: {})
+ ::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: {})
::Stripe::Subscription.expects(:create).returns(status: 'active')
expect {
@@ -61,13 +83,13 @@ module DiscourseSubscriptions
end
it "does not add the user to the admins group" do
- ::Stripe::Price.expects(:retrieve).returns(metadata: { group_name: 'admins' })
+ ::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: 'admins' })
post "/s/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
expect(user.admin).to eq false
end
it "does not add the user to other group" do
- ::Stripe::Price.expects(:retrieve).returns(metadata: { group_name: 'other' })
+ ::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: 'other' })
post "/s/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
expect(user.groups).to be_empty
end
@@ -75,7 +97,7 @@ module DiscourseSubscriptions
context "plan has group in metadata" do
before do
- ::Stripe::Price.expects(:retrieve).returns(metadata: { group_name: group_name })
+ ::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: group_name })
end
it "does not add the user to the group when subscription fails" do
diff --git a/test/javascripts/components/payment-options-test.js.es6 b/test/javascripts/components/payment-options-test.js.es6
index f7bb274..84233ff 100644
--- a/test/javascripts/components/payment-options-test.js.es6
+++ b/test/javascripts/components/payment-options-test.js.es6
@@ -18,9 +18,9 @@ componentTest("Discourse Subscriptions payment options have no plans", {
componentTest("Discourse Subscriptions payment options has content", {
template: `{{payment-options
- paymentsAllowed=paymentsAllowed
- plans=plans
- planTypeIsSelected=planTypeIsSelected}}`,
+ plans=plans
+ selectedPlan=selectedPlan
+ }}`,
beforeEach() {
this.set("plans", [
@@ -35,110 +35,9 @@ componentTest("Discourse Subscriptions payment options has content", {
amountDollars: "9.99"
}
]);
-
- this.set("planTypeIsSelected", true);
- this.set("paymentsAllowed", true);
},
async test(assert) {
- assert.equal(
- find(".btn-discourse-subscriptions-payment-type").length,
- 2,
- "The payment type buttons are shown"
- );
- assert.equal(
- find(".btn-discourse-subscriptions-subscribe").length,
- 2,
- "The plan buttons are shown"
- );
- assert.equal(
- find("#subscribe-buttons .btn-primary").length,
- 0,
- "No plan buttons are selected by default"
- );
- assert.equal(
- find(".btn-discourse-subscriptions-subscribe:first-child .interval")
- .text()
- .trim(),
- "Yearly",
- "The plan interval is shown"
- );
- assert.equal(
- find(".btn-discourse-subscriptions-subscribe:first-child .amount")
- .text()
- .trim(),
- "$AUD 44.99",
- "The plan amount and currency is shown"
- );
- }
-});
-
-componentTest("Discourse Subscriptions payments allowed setting", {
- template: `{{payment-options plans=plans paymentsAllowed=paymentsAllowed}}`,
-
- async test(assert) {
- this.set("paymentsAllowed", true);
-
- assert.ok(
- find("#discourse-subscriptions-payment-type-plan").length,
- "The plan type button displayed"
- );
- assert.ok(
- find("#discourse-subscriptions-payment-type-payment").length,
- "The payment type button displayed"
- );
-
- this.set("paymentsAllowed", false);
-
- assert.notOk(
- find("#discourse-subscriptions-payment-type-plan").length,
- "The plan type button hidden"
- );
- assert.notOk(
- find("#discourse-subscriptions-payment-type-payment").length,
- "The payment type button hidden"
- );
- }
-});
-
-componentTest("Discourse Subscriptions payment type plan", {
- template: `{{payment-options
- paymentsAllowed=paymentsAllowed
- plans=plans
- planTypeIsSelected=planTypeIsSelected}}`,
-
- async test(assert) {
- this.set("plans", [
- { currency: "aud", interval: "year", amountDollars: "44.99" }
- ]);
-
- this.set("paymentsAllowed", true);
- this.set("planTypeIsSelected", true);
-
- assert.equal(
- find("#discourse-subscriptions-payment-type-plan.btn-primary").length,
- 1,
- "The plan type button is selected"
- );
-
- assert.equal(
- find("#discourse-subscriptions-payment-type-payment.btn-primary").length,
- 0,
- "The payment type button is not selected"
- );
-
- await click("#discourse-subscriptions-payment-type-payment");
-
- assert.equal(
- find("#discourse-subscriptions-payment-type-plan.btn-primary").length,
- 0,
- "The plan type button is selected"
- );
-
- assert.equal(
- find("#discourse-subscriptions-payment-type-payment.btn-primary").length,
- 1,
- "The payment type button is not selected"
- );
+ assert.equal(this.selectedPlan, null, "No plans are selected by default");
}
});
diff --git a/test/javascripts/components/payment-plan-test.js.es6 b/test/javascripts/components/payment-plan-test.js.es6
new file mode 100644
index 0000000..e4b5dfa
--- /dev/null
+++ b/test/javascripts/components/payment-plan-test.js.es6
@@ -0,0 +1,68 @@
+import componentTest from "helpers/component-test";
+
+moduleForComponent("payment-plan", { integration: true });
+
+componentTest("Payment plan subscription button rendered", {
+ template: `{{payment-plan
+ plan=plan
+ selectedPlan=selectedPlan
+ }}`,
+
+ beforeEach() {
+ this.set("plan", {
+ type: "recurring",
+ currency: "aud",
+ recurring: { interval: "year" },
+ amountDollars: "44.99"
+ });
+ },
+
+ async test(assert) {
+ assert.equal(
+ find(".btn-discourse-subscriptions-subscribe").length,
+ 1,
+ "The payment button is shown"
+ );
+
+ assert.equal(
+ find(".btn-discourse-subscriptions-subscribe:first-child .interval")
+ .text()
+ .trim(),
+ "Yearly",
+ "The plan interval is shown -- Yearly"
+ );
+
+ assert.equal(
+ find(".btn-discourse-subscriptions-subscribe:first-child .amount")
+ .text()
+ .trim(),
+ "$AUD 44.99",
+ "The plan amount and currency is shown"
+ );
+ }
+});
+
+componentTest("Payment plan one-time-payment button rendered", {
+ template: `{{payment-plan
+ plan=plan
+ selectedPlan=selectedPlan
+ }}`,
+
+ beforeEach() {
+ this.set("plan", {
+ type: "one_time",
+ currency: "USD",
+ amountDollars: "3.99"
+ });
+ },
+
+ async test(assert) {
+ assert.equal(
+ find(".btn-discourse-subscriptions-subscribe:first-child .interval")
+ .text()
+ .trim(),
+ "One-Time Payment",
+ "Shown as one time payment"
+ );
+ }
+});
diff --git a/test/javascripts/helpers/discourse-patrons-pretender.js.es6 b/test/javascripts/helpers/discourse-patrons-pretender.js.es6
deleted file mode 100644
index 8944550..0000000
--- a/test/javascripts/helpers/discourse-patrons-pretender.js.es6
+++ /dev/null
@@ -1,13 +0,0 @@
-export default function(helpers) {
- const { response } = helpers;
-
- this.get("/patrons", () => response({ email: "hello@example.com" }));
-
- this.get("/groups/:plan", () => {
- return response({ full_name: "Saboo", bio_cooked: "This is the plan" });
- });
-
- this.get("/patrons/plans", () => {
- return response({ plans: [] });
- });
-}