From 57fb5085143034b7a74a93e248101b9955206999 Mon Sep 17 00:00:00 2001 From: Rimian Perkins Date: Fri, 13 Dec 2019 10:41:14 +1100 Subject: [PATCH] buttons for selecting price and one time payment --- app/controllers/payments_controller.rb | 31 +++++ .../components/payment-options.js.es6 | 13 ++ .../discourse/components/product-list.js.es6 | 2 +- .../controllers/s-subscribe-show.js.es6 | 114 ++++++++++++------ .../discourse/models/payment.js.es6 | 15 +++ .../templates/components/payment-options.hbs | 46 ++++--- .../discourse/templates/s/subscribe/show.hbs | 7 +- assets/stylesheets/common/subscribe.scss | 2 +- config/locales/client.en.yml | 23 ++-- config/routes.rb | 1 + plugin.rb | 1 + spec/requests/payments_controller_spec.rb | 40 ++++++ .../components/payment-options-test.js.es6 | 59 ++++----- 13 files changed, 264 insertions(+), 90 deletions(-) create mode 100644 app/controllers/payments_controller.rb create mode 100644 assets/javascripts/discourse/models/payment.js.es6 create mode 100644 spec/requests/payments_controller_spec.rb diff --git a/app/controllers/payments_controller.rb b/app/controllers/payments_controller.rb new file mode 100644 index 0000000..8d47804 --- /dev/null +++ b/app/controllers/payments_controller.rb @@ -0,0 +1,31 @@ +# 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 + payment = ::Stripe::PaymentIntent.create( + payment_method_types: ['card'], + payment_method: params[:payment_method], + amount: params[:amount], + currency: params[:currency], + confirm: true + ) + + render_json_dump payment + + rescue ::Stripe::InvalidRequestError => e + render_json_error e.message + rescue ::Stripe::CardError => e + render_json_error 'Card Declined' + end + end + end +end diff --git a/assets/javascripts/discourse/components/payment-options.js.es6 b/assets/javascripts/discourse/components/payment-options.js.es6 index f4a96a3..c6c1048 100644 --- a/assets/javascripts/discourse/components/payment-options.js.es6 +++ b/assets/javascripts/discourse/components/payment-options.js.es6 @@ -1,5 +1,18 @@ +import { equal } from "@ember/object/computed"; + export default Ember.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); diff --git a/assets/javascripts/discourse/components/product-list.js.es6 b/assets/javascripts/discourse/components/product-list.js.es6 index 0f338f1..2d01ba9 100644 --- a/assets/javascripts/discourse/components/product-list.js.es6 +++ b/assets/javascripts/discourse/components/product-list.js.es6 @@ -1,4 +1,4 @@ -import computed from "ember-addons/ember-computed-decorators"; +import computed from "discourse-common/utils/decorators"; import User from "discourse/models/user"; export default Ember.Component.extend({ diff --git a/assets/javascripts/discourse/controllers/s-subscribe-show.js.es6 b/assets/javascripts/discourse/controllers/s-subscribe-show.js.es6 index b69ca0f..0b8f25b 100644 --- a/assets/javascripts/discourse/controllers/s-subscribe-show.js.es6 +++ b/assets/javascripts/discourse/controllers/s-subscribe-show.js.es6 @@ -1,7 +1,22 @@ 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 computed from "discourse-common/utils/decorators"; +import { i18n } from "discourse/lib/computed"; export default Ember.Controller.extend({ + planTypeIsSelected: true, + + @computed("planTypeIsSelected") + type(planTypeIsSelected) { + return planTypeIsSelected ? "plans" : "payment"; + }, + + @computed("type") + buttonText(type) { + return I18n.t(`discourse_subscriptions.${this.get("type")}.payment_button`); + }, + init() { this._super(...arguments); this.set( @@ -13,54 +28,83 @@ export default Ember.Controller.extend({ this.set("cardElement", elements.create("card", { hidePostalCode: true })); }, + alert(path) { + 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(); + }); + }, + + createSubsciption(plan) { + return this.stripe.createToken(this.get("cardElement")).then(result => { + if (result.error) { + return result; + } else { + const customer = Customer.create({ source: result.token.id }); + + return customer.save().then(c => { + const subscription = Subscription.create({ + customer: c.id, + plan: plan.get("id") + }); + + return subscription.save(); + }); + } + }); + }, + actions: { stripePaymentHandler() { this.set("loading", true); + const type = this.get("type"); const plan = this.get("model.plans") .filterBy("selected") .get("firstObject"); if (!plan) { - bootbox.alert( - I18n.t( - "discourse_subscriptions.transactions.payment.validate.plan.required" - ) - ); - + this.alert(`${type}.validate.payment_options.required`); this.set("loading", false); return; } - this.stripe.createToken(this.get("cardElement")).then(result => { - if (result.error) { - bootbox.alert(result.error.message); + let transaction; + + if (this.planTypeIsSelected) { + transaction = this.createSubsciption(plan); + } else { + transaction = this.createPayment(plan); + } + + transaction + .then(result => { + if (result.error) { + bootbox.alert(result.error.message || result.error); + } else { + this.alert(`${type}.success`); + this.transitionToRoute( + "user.subscriptions", + Discourse.User.current().username.toLowerCase() + ); + } + }) + .catch(result => { + bootbox.alert(result.errorThrown); + }) + .finally(() => { this.set("loading", false); - } else { - const customer = Customer.create({ source: result.token.id }); - - customer.save().then(c => { - const subscription = Subscription.create({ - customer: c.id, - plan: plan.get("id") - }); - - subscription - .save() - .then(() => { - bootbox.alert( - I18n.t("discourse_subscriptions.transactions.payment.success") - ); - this.transitionToRoute( - "user.subscriptions", - Discourse.User.current().username.toLowerCase() - ); - }) - .finally(() => { - this.set("loading", false); - }); - }); - } - }); + }); } } }); diff --git a/assets/javascripts/discourse/models/payment.js.es6 b/assets/javascripts/discourse/models/payment.js.es6 new file mode 100644 index 0000000..1f7fe19 --- /dev/null +++ b/assets/javascripts/discourse/models/payment.js.es6 @@ -0,0 +1,15 @@ +import { ajax } from "discourse/lib/ajax"; + +const Payment = Discourse.Model.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/templates/components/payment-options.hbs b/assets/javascripts/discourse/templates/components/payment-options.hbs index 62dbd42..c3ee8c1 100644 --- a/assets/javascripts/discourse/templates/components/payment-options.hbs +++ b/assets/javascripts/discourse/templates/components/payment-options.hbs @@ -1,27 +1,35 @@ -{{#ds-button - id="discourse-subscriptions-payment-type-plan" - selected=true - class="btn-discourse-subscriptions-payment-type" -}} - {{i18n "discourse_subscriptions.plans.purchase"}} -{{/ds-button}} +
+ {{#ds-button + id="discourse-subscriptions-payment-type-plan" + selected=planButtonSelected + action="selectPlans" + class="btn-discourse-subscriptions-payment-type" + }} + {{i18n "discourse_subscriptions.plans.purchase"}} + {{/ds-button}} -{{#ds-button - id="discourse-subscriptions-payment-type-payment" - selected=false - class="btn-discourse-subscriptions-payment-type" -}} - {{i18n "discourse_subscriptions.payment.purchase"}} -{{/ds-button}} + {{#ds-button + id="discourse-subscriptions-payment-type-payment" + selected=paymentButtonSelected + action="selectPayments" + class="btn-discourse-subscriptions-payment-type" + }} + {{i18n "discourse_subscriptions.payment.purchase"}} + {{/ds-button}} +

- {{i18n "discourse_subscriptions.plans.select"}} + {{#if planTypeIsSelected}} + {{i18n "discourse_subscriptions.plans.select"}} + {{else}} + {{i18n "discourse_subscriptions.payment.select"}} + {{/if}}

-
+