diff --git a/app/controllers/invoices_controller.rb b/app/controllers/invoices_controller.rb new file mode 100644 index 0000000..57dc385 --- /dev/null +++ b/app/controllers/invoices_controller.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module DiscoursePatrons + class InvoicesController < ::ApplicationController + include DiscoursePatrons::Stripe + + requires_login + + before_action :set_api_key + + 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 + return render_json_error e.message + end + end + + private + + def viewing_own_invoices + current_user.id == params[:user_id].to_i + end + + def find_customer + DiscoursePatrons::Customer.find_user(current_user) + end + end +end diff --git a/assets/javascripts/discourse/models/invoice.js.es6 b/assets/javascripts/discourse/models/invoice.js.es6 new file mode 100644 index 0000000..f4a447b --- /dev/null +++ b/assets/javascripts/discourse/models/invoice.js.es6 @@ -0,0 +1,19 @@ +import computed from "ember-addons/ember-computed-decorators"; +import { ajax } from "discourse/lib/ajax"; + +const Invoice = Discourse.Model.extend({ + @computed("created") + createdFormatted(created) { + return moment.unix(created).format(); + } +}); + +Invoice.reopenClass({ + findAll() { + return ajax("/patrons/invoices", { method: "get" }).then(result => + result.map(invoice => Invoice.create(invoice)) + ); + } +}); + +export default Invoice; diff --git a/assets/javascripts/discourse/routes/user-billing.js.es6 b/assets/javascripts/discourse/routes/user-billing.js.es6 index 9ce293f..ad03653 100644 --- a/assets/javascripts/discourse/routes/user-billing.js.es6 +++ b/assets/javascripts/discourse/routes/user-billing.js.es6 @@ -1,6 +1,8 @@ +import Invoice from "discourse/plugins/discourse-patrons/discourse/models/invoice"; + export default Discourse.Route.extend({ model() { - return {}; + return Invoice.findAll(); }, setupController(controller, model) { diff --git a/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-dashboard.hbs b/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-dashboard.hbs index cda4b3b..1aa2d58 100644 --- a/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-dashboard.hbs +++ b/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-dashboard.hbs @@ -1,9 +1,9 @@

{{i18n 'discourse_patrons.admin.dashboard.title'}}

-{{#load-more selector=".discourse-patrons-admin tr" action=(action "loadMore")}} +{{#load-more selector=".discourse-patrons-table tr" action=(action "loadMore")}} {{#if model}} - +
diff --git a/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-plans-index.hbs b/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-plans-index.hbs index b89639d..1b37a2e 100644 --- a/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-plans-index.hbs +++ b/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-plans-index.hbs @@ -1,5 +1,5 @@ -
{{i18n 'discourse_patrons.admin.dashboard.table.head.user'}}
+
diff --git a/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-products-index.hbs b/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-products-index.hbs index 69d785d..9eb8bf5 100644 --- a/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-products-index.hbs +++ b/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-products-index.hbs @@ -7,7 +7,7 @@

{{#if model}} -
{{i18n 'discourse_patrons.admin.plans.plan.plan_id'}} {{i18n 'discourse_patrons.admin.plans.plan.nickname.title'}}
+
diff --git a/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-products-show.hbs b/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-products-show.hbs index f596cc2..35071b6 100644 --- a/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-products-show.hbs +++ b/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-products-show.hbs @@ -24,7 +24,7 @@

{{i18n 'discourse_patrons.admin.plans.title'}}

-

{{i18n 'discourse_patrons.admin.products.product.name'}} {{i18n 'discourse_patrons.admin.products.product.created_at'}}
+
diff --git a/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-subscriptions.hbs b/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-subscriptions.hbs index 383f50a..92598f1 100644 --- a/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-subscriptions.hbs +++ b/assets/javascripts/discourse/templates/admin/plugins-discourse-patrons-subscriptions.hbs @@ -1,5 +1,5 @@ -
{{i18n 'discourse_patrons.admin.plans.plan.nickname'}} {{i18n 'discourse_patrons.admin.plans.plan.interval'}}
+
diff --git a/assets/javascripts/discourse/templates/user/billing.hbs b/assets/javascripts/discourse/templates/user/billing.hbs index de05057..d1ea854 100644 --- a/assets/javascripts/discourse/templates/user/billing.hbs +++ b/assets/javascripts/discourse/templates/user/billing.hbs @@ -1,3 +1,27 @@ +

{{i18n 'discourse_patrons.user.billing.title'}}

-user/billing +{{#if model}} +
{{i18n 'discourse_patrons.admin.subscriptions.subscription.customer'}}
+ + + + + + + {{#each model as |invoice|}} + + + + + + + {{/each}} +
{{i18n 'discourse_patrons.user.billing.invoices.amount'}}{{i18n 'discourse_patrons.user.billing.invoices.number'}}{{i18n 'discourse_patrons.user.billing.invoices.created_at'}}
{{invoice.amount_paid}}{{invoice.number}}{{format-date invoice.createdFormatted}} + + {{d-icon "download"}} + +
+{{else}} +

{{i18n 'discourse_patrons.user.billing_help'}}

+{{/if}} diff --git a/assets/stylesheets/common/discourse-patrons-layout.scss b/assets/stylesheets/common/discourse-patrons-layout.scss new file mode 100644 index 0000000..0211bbd --- /dev/null +++ b/assets/stylesheets/common/discourse-patrons-layout.scss @@ -0,0 +1,33 @@ +.discourse-patrons-section-columns { + display: flex; + justify-content: space-between; + + @include breakpoint(medium) { + flex-direction: column; + } + + .section-column { + min-width: calc(50% - 0.5em); + max-width: 100%; + + &:last-child { + margin-left: 0.5em; + } + + &:first-child { + margin-right: 0.5em; + } + + @include breakpoint(medium) { + min-width: 100%; + + &:last-child { + order: 2; + } + + &:first-child { + order: 1; + } + } + } +} diff --git a/assets/stylesheets/common/discourse-patrons.scss b/assets/stylesheets/common/discourse-patrons.scss index 219ff4d..504d76e 100644 --- a/assets/stylesheets/common/discourse-patrons.scss +++ b/assets/stylesheets/common/discourse-patrons.scss @@ -10,47 +10,13 @@ textarea[readonly] { border-color: #e9e9e9; } -.discourse-patrons-section-columns { - display: flex; - justify-content: space-between; - - @include breakpoint(medium) { - flex-direction: column; - } - - .section-column { - min-width: calc(50% - 0.5em); - max-width: 100%; - - &:last-child { - margin-left: 0.5em; - } - - &:first-child { - margin-right: 0.5em; - } - - @include breakpoint(medium) { - min-width: 100%; - - &:last-child { - order: 2; - } - - &:first-child { - order: 1; - } - } - } -} - #discourse-patrons-admin { .btn-right { text-align: right; } } -table.discourse-patrons-admin { +table.discourse-patrons-table { .td-right { text-align: right; } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 99b4a6e..64edaaa 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -20,7 +20,15 @@ en: navigation: subscribe: Subscribe billing: Billing - subscribe: + user: + billing_help: We couldn't find a customer identifier in our system. + billing: + title: Billing + invoices: + amount: Amount + number: Invoice Number + created_at: Created + subscribe: title: Subscribe card: title: Payment diff --git a/config/routes.rb b/config/routes.rb index 59692a5..5d16e81 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,10 +13,11 @@ DiscoursePatrons::Engine.routes.draw do end resources :customers, only: [:create] - resources :subscriptions, only: [:create] + resources :invoices, only: [:index] + resources :patrons, only: [:index, :create] resources :plans, only: [:index] resources :products, only: [:index] - resources :patrons, only: [:index, :create] + resources :subscriptions, only: [:create] get '/' => 'patrons#index' end diff --git a/plugin.rb b/plugin.rb index 7e0be0c..6b48b1d 100644 --- a/plugin.rb +++ b/plugin.rb @@ -11,6 +11,7 @@ enabled_site_setting :discourse_patrons_enabled gem 'stripe', '5.7.1' register_asset "stylesheets/common/discourse-patrons.scss" +register_asset "stylesheets/common/discourse-patrons-layout.scss" register_asset "stylesheets/mobile/discourse-patrons.scss" register_svg_icon "credit-card" if respond_to?(:register_svg_icon) @@ -50,6 +51,7 @@ after_initialize do "../app/controllers/admin/products_controller", "../app/controllers/admin/subscriptions_controller", "../app/controllers/customers_controller", + "../app/controllers/invoices_controller", "../app/controllers/patrons_controller", "../app/controllers/plans_controller", "../app/controllers/products_controller", diff --git a/spec/requests/invoices_controller_spec.rb b/spec/requests/invoices_controller_spec.rb new file mode 100644 index 0000000..1620075 --- /dev/null +++ b/spec/requests/invoices_controller_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'rails_helper' + +module DiscoursePatrons + RSpec.describe InvoicesController do + describe "index" do + describe "not authenticated" do + it "does not list the invoices" do + ::Stripe::Invoice.expects(:list).never + get "/patrons/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 "/patrons/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 "/patrons/invoices.json", params: { user_id: user.id } + expect(response.body).to eq "[]" + end + end + + context "stripe customer exists" do + before do + DiscoursePatrons::Customer.create_customer(user, stripe_customer) + end + + it "lists the invoices" do + ::Stripe::Invoice.expects(:list).with(customer: 'cus_id4567') + get "/patrons/invoices.json", params: { user_id: user.id } + end + end + end + end + end + end +end