diff --git a/app/controllers/payments_controller.rb b/app/controllers/payments_controller.rb index d796ba2..e6479f0 100644 --- a/app/controllers/payments_controller.rb +++ b/app/controllers/payments_controller.rb @@ -11,22 +11,21 @@ module DiscourseSubscriptions def create begin - customer = ::Stripe::Customer.create( - email: current_user.email, - ) + customer = DiscourseSubscriptions::Customer.where(user_id: current_user.id, product_id: nil).first_or_create do |c| + new_customer = ::Stripe::Customer.create( + email: current_user.email + ) - DiscourseSubscriptions::Customer.create( - user_id: current_user.id, - customer_id: customer[:id], - ) + 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], - confirm: true, - customer: customer[:id], + customer: customer[:customer_id], + confirm: true ) render_json_dump payment @@ -34,7 +33,7 @@ module DiscourseSubscriptions rescue ::Stripe::InvalidRequestError => e render_json_error e.message rescue ::Stripe::CardError => e - render_json_error 'Card Declined' + render_json_error I18n.t('discourse_subscriptions.card.declined') end end end diff --git a/app/controllers/user/payments_controller.rb b/app/controllers/user/payments_controller.rb new file mode 100644 index 0000000..650aaa8 --- /dev/null +++ b/app/controllers/user/payments_controller.rb @@ -0,0 +1,29 @@ +# 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 = DiscourseSubscriptions::Customer.find_by(user_id: current_user.id, product_id: nil) + + data = [] + + if customer.present? + payments = ::Stripe::PaymentIntent.list(customer: customer[:customer_id]) + data = payments[:data] + end + + render_json_dump data + + rescue ::Stripe::InvalidRequestError => e + render_json_error e.message + end + end + end + end +end diff --git a/assets/javascripts/discourse/components/donation-list.js.es6 b/assets/javascripts/discourse/components/donation-list.js.es6 deleted file mode 100644 index b9fcea7..0000000 --- a/assets/javascripts/discourse/components/donation-list.js.es6 +++ /dev/null @@ -1,5 +0,0 @@ -export default Ember.Component.extend({ - classNames: "donation-list", - hasSubscriptions: Ember.computed.notEmpty("subscriptions"), - hasCharges: Ember.computed.notEmpty("charges") -}); diff --git a/assets/javascripts/discourse/models/payment.js.es6 b/assets/javascripts/discourse/models/payment.js.es6 index 1f7fe19..5a2cd17 100644 --- a/assets/javascripts/discourse/models/payment.js.es6 +++ b/assets/javascripts/discourse/models/payment.js.es6 @@ -1,6 +1,7 @@ +import EmberObject from "@ember/object"; import { ajax } from "discourse/lib/ajax"; -const Payment = Discourse.Model.extend({ +const Payment = EmberObject.extend({ save() { const data = { payment_method: this.payment_method, diff --git a/assets/javascripts/discourse/models/plan.js.es6 b/assets/javascripts/discourse/models/plan.js.es6 index 5f94a6a..62be08c 100644 --- a/assets/javascripts/discourse/models/plan.js.es6 +++ b/assets/javascripts/discourse/models/plan.js.es6 @@ -1,7 +1,8 @@ -import computed from "ember-addons/ember-computed-decorators"; +import EmberObject from "@ember/object"; +import computed from "discourse-common/utils/decorators"; import { ajax } from "discourse/lib/ajax"; -const Plan = Discourse.Model.extend({ +const Plan = EmberObject.extend({ amountDollars: Ember.computed("amount", { get() { return parseFloat(this.get("amount") / 100).toFixed(2); diff --git a/assets/javascripts/discourse/models/product.js.es6 b/assets/javascripts/discourse/models/product.js.es6 index b3e8ff7..609672e 100644 --- a/assets/javascripts/discourse/models/product.js.es6 +++ b/assets/javascripts/discourse/models/product.js.es6 @@ -1,6 +1,7 @@ +import EmberObject from "@ember/object"; import { ajax } from "discourse/lib/ajax"; -const Product = Discourse.Model.extend({}); +const Product = EmberObject.extend({}); Product.reopenClass({ findAll() { diff --git a/assets/javascripts/discourse/models/user-payment.js.es6 b/assets/javascripts/discourse/models/user-payment.js.es6 new file mode 100644 index 0000000..65eb95f --- /dev/null +++ b/assets/javascripts/discourse/models/user-payment.js.es6 @@ -0,0 +1,22 @@ +import EmberObject from "@ember/object"; +import computed from "discourse-common/utils/decorators"; +import { ajax } from "discourse/lib/ajax"; + +const UserPayment = EmberObject.extend({ + @computed("amount") + amountDollars(amount){ + return parseFloat(amount / 100).toFixed(2); + } +}); + +UserPayment.reopenClass({ + findAll() { + return ajax("/s/user/payments", { method: "get" }).then(result => + result.map(payment => { + return UserPayment.create(payment); + }) + ); + } +}); + +export default UserPayment; diff --git a/assets/javascripts/discourse/models/user-subscription.js.es6 b/assets/javascripts/discourse/models/user-subscription.js.es6 index 5900a6f..857a244 100644 --- a/assets/javascripts/discourse/models/user-subscription.js.es6 +++ b/assets/javascripts/discourse/models/user-subscription.js.es6 @@ -1,8 +1,9 @@ +import EmberObject from "@ember/object"; import computed from "ember-addons/ember-computed-decorators"; import { ajax } from "discourse/lib/ajax"; import Plan from "discourse/plugins/discourse-subscriptions/discourse/models/plan"; -const UserSubscription = Discourse.Model.extend({ +const UserSubscription = EmberObject.extend({ @computed("status") canceled(status) { return status === "canceled"; diff --git a/assets/javascripts/discourse/routes/s-show.js.es6 b/assets/javascripts/discourse/routes/s-show.js.es6 index 9547e21..55f9cd6 100644 --- a/assets/javascripts/discourse/routes/s-show.js.es6 +++ b/assets/javascripts/discourse/routes/s-show.js.es6 @@ -7,7 +7,7 @@ export default Route.extend({ const product_id = params["subscription-id"]; const product = Product.find(product_id); - const plans = Plan.findAll({ product_id: product_id }); + const plans = Plan.findAll({ product_id }); return Ember.RSVP.hash({ plans, product }); } diff --git a/assets/javascripts/discourse/routes/user-billing-payments.js.es6 b/assets/javascripts/discourse/routes/user-billing-payments.js.es6 index 38cc6c3..cab08de 100644 --- a/assets/javascripts/discourse/routes/user-billing-payments.js.es6 +++ b/assets/javascripts/discourse/routes/user-billing-payments.js.es6 @@ -1,10 +1,10 @@ import Route from "@ember/routing/route"; -import Invoice from "discourse/plugins/discourse-subscriptions/discourse/models/invoice"; +import UserPayment from "discourse/plugins/discourse-subscriptions/discourse/models/user-payment"; export default Route.extend({ templateName: "user/billing/payments", model() { - return Invoice.findAll(); + return UserPayment.findAll(); } }); diff --git a/assets/javascripts/discourse/templates/components/donation-list.hbs b/assets/javascripts/discourse/templates/components/donation-list.hbs deleted file mode 100644 index 4a23964..0000000 --- a/assets/javascripts/discourse/templates/components/donation-list.hbs +++ /dev/null @@ -1,28 +0,0 @@ -{{#if hasSubscriptions}} -
-
{{i18n 'discourse_donations.donations.subscriptions'}}
- -
-{{/if}} - -{{#if hasCharges}} -
-
{{i18n 'discourse_donations.donations.charges'}}
- -
-{{/if}} diff --git a/assets/javascripts/discourse/templates/user/billing/payments.hbs b/assets/javascripts/discourse/templates/user/billing/payments.hbs index e69df3a..66be06b 100644 --- a/assets/javascripts/discourse/templates/user/billing/payments.hbs +++ b/assets/javascripts/discourse/templates/user/billing/payments.hbs @@ -1,22 +1,15 @@ - {{#if model}} - +
- - - - + + + - {{#each model as |invoice|}} + {{#each model as |payment|}} - - - - + + + {{/each}}
{{i18n 'discourse_subscriptions.user.billing.invoices.amount'}}{{i18n 'discourse_subscriptions.user.billing.invoices.number'}}{{i18n 'discourse_subscriptions.user.billing.invoices.created_at'}}{{i18n 'discourse_subscriptions.user.payments.id'}}{{i18n 'discourse_subscriptions.user.payments.amount'}}{{i18n 'discourse_subscriptions.user.payments.created_at'}}
{{invoice.amount_paid}}{{invoice.number}}{{format-unix-date invoice.created}} - - {{d-icon "download"}} - - {{payment.id}}{{format-currency payment.currency payment.amountDollars}}{{format-unix-date payment.created}}
diff --git a/assets/javascripts/discourse/templates/user/billing/subscriptions.hbs b/assets/javascripts/discourse/templates/user/billing/subscriptions.hbs index 827314a..52868b0 100644 --- a/assets/javascripts/discourse/templates/user/billing/subscriptions.hbs +++ b/assets/javascripts/discourse/templates/user/billing/subscriptions.hbs @@ -1,34 +1,32 @@ -{{#d-section pageClass="user-subscriptions" class="user-content"}} - {{#if model}} - - - - - - - - - - {{#each model as |subscription|}} - - - - - - - - - {{/each}} -
{{i18n 'discourse_subscriptions.user.subscriptions.id'}}{{i18n 'discourse_subscriptions.user.plans.product'}}{{i18n 'discourse_subscriptions.user.plans.rate'}}{{i18n 'discourse_subscriptions.user.subscriptions.status'}}{{i18n 'discourse_subscriptions.user.subscriptions.created_at'}}
{{subscription.id}}{{subscription.product.name}}{{subscription.plan.subscriptionRate}}{{subscription.status}}{{format-unix-date subscription.created}} - {{#if subscription.loading}} - {{loading-spinner size="small"}} - {{else}} - {{d-button disabled=subscription.canceled label="cancel" action=(route-action "cancelSubscription" subscription) icon="times"}} - {{/if}} -
- {{else}} -
- {{i18n 'discourse_subscriptions.user.subscriptions_help'}} -
- {{/if}} -{{/d-section}} +{{#if model}} + + + + + + + + + + {{#each model as |subscription|}} + + + + + + + + + {{/each}} +
{{i18n 'discourse_subscriptions.user.subscriptions.id'}}{{i18n 'discourse_subscriptions.user.plans.product'}}{{i18n 'discourse_subscriptions.user.plans.rate'}}{{i18n 'discourse_subscriptions.user.subscriptions.status'}}{{i18n 'discourse_subscriptions.user.subscriptions.created_at'}}
{{subscription.id}}{{subscription.product.name}}{{subscription.plan.subscriptionRate}}{{subscription.status}}{{format-unix-date subscription.created}} + {{#if subscription.loading}} + {{loading-spinner size="small"}} + {{else}} + {{d-button disabled=subscription.canceled label="cancel" action=(route-action "cancelSubscription" subscription) icon="times"}} + {{/if}} +
+{{else}} +
+ {{i18n 'discourse_subscriptions.user.subscriptions_help'}} +
+{{/if}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 4a5f460..fd2cc39 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -50,19 +50,11 @@ en: validate: payment_options: required: Please select a payment option. - one_time: - heading: - payment: Make a Payment - success: Thank you! - payment: - optional: Optional - receipt_info: A receipt is sent to this email address - your_information: Your information - payment_information: Payment information - payment_confirmation: Confirm information - amount: Amount - payment_intent_id: Payment ID user: + payments: + id: Payment ID + amount: Amount + created_at: Created payments_help: There are no payments plans: rate: Rate diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 6d86f6d..e9f328b 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1,3 +1,5 @@ en: discourse_subscriptions: customer_not_found: Customer not found + card: + declined: Card Declined diff --git a/config/routes.rb b/config/routes.rb index 0e4ac2e..13cb24c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,6 +13,7 @@ DiscourseSubscriptions::Engine.routes.draw do end namespace :user do + resources :payments, only: [:index] resources :subscriptions, only: [:index, :destroy] end diff --git a/plugin.rb b/plugin.rb index 2d95cbc..c427faf 100644 --- a/plugin.rb +++ b/plugin.rb @@ -57,6 +57,7 @@ after_initialize do "../app/controllers/admin/plans_controller", "../app/controllers/admin/products_controller", "../app/controllers/admin/subscriptions_controller", + "../app/controllers/user/payments_controller", "../app/controllers/user/subscriptions_controller", "../app/controllers/customers_controller", "../app/controllers/invoices_controller", diff --git a/spec/requests/user/payments_controller_spec.rb b/spec/requests/user/payments_controller_spec.rb new file mode 100644 index 0000000..9804f92 --- /dev/null +++ b/spec/requests/user/payments_controller_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'rails_helper' + +module DiscourseSubscriptions + RSpec.describe User::PaymentsController do + it 'is a subclass of ApplicationController' do + expect(DiscourseSubscriptions::User::PaymentsController < ::ApplicationController).to eq(true) + end + + context "not authenticated" do + it "does not get the payment intents" do + ::Stripe::PaymentIntent.expects(:list).never + get "/s/user/payments.json" + end + end + + context "authenticated" do + let(:user) { Fabricate(:user, email: 'zasch@example.com') } + + before do + sign_in(user) + Fabricate(:customer, customer_id: 'c_345678', user_id: user.id) + end + + it "gets payment intents" do + ::Stripe::PaymentIntent.expects(:list).with( + customer: 'c_345678' + ) + + get "/s/user/payments.json" + end + + end + end +end diff --git a/test/javascripts/components/stripe-card-test.js.es6 b/test/javascripts/components/stripe-card-test.js.es6 deleted file mode 100644 index d94be42..0000000 --- a/test/javascripts/components/stripe-card-test.js.es6 +++ /dev/null @@ -1,31 +0,0 @@ -import componentTest from "helpers/component-test"; -import { stubStripe } from "discourse/plugins/discourse-subscriptions/helpers/stripe"; - -moduleForComponent("stripe-card", { integration: true }); - -componentTest("Discourse Patrons stripe card success", { - template: `{{stripe-card handleConfirmStripeCard=onSubmit billing=billing}}`, - - beforeEach() { - stubStripe(); - - this.set( - "billing", - Ember.Object.create({ - name: "", - email: "", - phone: "" - }) - ); - }, - - async test(assert) { - assert.expect(1); - - this.set("onSubmit", () => { - assert.ok(true, "payment method created"); - }); - - await click(".btn-payment"); - } -});