FEATURE: Allow creation of coupons in admin panel (#43)

FEATURE: Allow creation of coupons in admin panel (#43)

Adds full support to create coupon/promo codes in the Admin > Plugins > Subscriptions section of the plugin. The Create Coupon button opens a form on the same page, and the active checkboxes toggle the active status of the coupon code.

diff --git a/app/controllers/discourse_subscriptions/admin/coupons_controller.rb b/app/controllers/discourse_subscriptions/admin/coupons_controller.rb
new file mode 100644
index 0000000..072a5be
--- /dev/null
+++ b/app/controllers/discourse_subscriptions/admin/coupons_controller.rb
@@ -0,0 +1,72 @@
+# 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([: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([: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
diff --git a/assets/javascripts/discourse/components/create-coupon-form.js.es6 b/assets/javascripts/discourse/components/create-coupon-form.js.es6
new file mode 100644
index 0000000..338a4e8
--- /dev/null
+++ b/assets/javascripts/discourse/components/create-coupon-form.js.es6
@@ -0,0 +1,32 @@
+import Component from "@ember/component";
+import discourseComputed from "discourse-common/utils/decorators";
+
+export default Component.extend({
+  @discourseComputed
+  discountTypes() {
+    return [
+      { id: "amount", name: "Amount" },
+      { id: "percent", name: "Percent" },
+    ];
+  },
+  discountType: "amount",
+  discount: null,
+  promoCode: null,
+  active: false,
+
+  actions: {
+    createNewCoupon() {
+      const createParams = {
+        promo: this.promoCode,
+        discount_type: this.discountType,
+        discount: this.discount,
+        active: this.active,
+      };
+
+      this.create(createParams);
+    },
+    cancelCreate() {
+      this.cancel();
+    },
+  },
+});
diff --git a/assets/javascripts/discourse/controllers/admin-plugins-discourse-subscriptions-coupons.js.es6 b/assets/javascripts/discourse/controllers/admin-plugins-discourse-subscriptions-coupons.js.es6
new file mode 100644
index 0000000..14ea1d7
--- /dev/null
+++ b/assets/javascripts/discourse/controllers/admin-plugins-discourse-subscriptions-coupons.js.es6
@@ -0,0 +1,42 @@
+import Controller from "@ember/controller";
+import AdminCoupon from "discourse/plugins/discourse-subscriptions/discourse/models/admin-coupon";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+
+export default Controller.extend({
+  creating: null,
+
+  actions: {
+    openCreateForm() {
+      this.set("creating", true);
+    },
+    closeCreateForm() {
+      this.set("creating", false);
+    },
+    createNewCoupon(params) {
+      AdminCoupon.save(params)
+        .then(() => {
+          this.send("closeCreateForm");
+          this.send("reloadModel");
+        })
+        .catch(popupAjaxError);
+    },
+    deleteCoupon(coupon) {
+      AdminCoupon.destroy(coupon)
+        .then(() => {
+          this.send("reloadModel");
+        })
+        .catch(popupAjaxError);
+    },
+    toggleActive(coupon) {
+      const couponData = {
+        id: coupon.id,
+        active: !coupon.active,
+      };
+      AdminCoupon.update(couponData)
+        .then(() => {
+          this.send("reloadModel");
+        })
+        .catch(popupAjaxError);
+    },
+  },
+});
diff --git a/assets/javascripts/discourse/discourse-subscriptions-route-map.js.es6 b/assets/javascripts/discourse/discourse-subscriptions-route-map.js.es6
index 0ea72b8..be24a39 100644
--- a/assets/javascripts/discourse/discourse-subscriptions-route-map.js.es6
+++ b/assets/javascripts/discourse/discourse-subscriptions-route-map.js.es6
@@ -14,6 +14,8 @@ export default {
         });
       });
 
+      this.route("coupons");
+
       this.route("subscriptions");
     });
   },
diff --git a/assets/javascripts/discourse/models/admin-coupon.js.es6 b/assets/javascripts/discourse/models/admin-coupon.js.es6
new file mode 100644
index 0000000..e3b12d5
--- /dev/null
+++ b/assets/javascripts/discourse/models/admin-coupon.js.es6
@@ -0,0 +1,64 @@
+import { ajax } from "discourse/lib/ajax";
+import EmberObject from "@ember/object";
+import discourseComputed from "discourse-common/utils/decorators";
+
+const AdminCoupon = EmberObject.extend({
+  @discourseComputed("coupon.amount_off", "coupon.percent_off")
+  discount(amount_off, percent_off) {
+    if (amount_off) {
+      return `${parseFloat(amount_off * 0.01).toFixed(2)}`;
+    } else if (percent_off) {
+      return `${percent_off}%`;
+    }
+  },
+});
+
+AdminCoupon.reopenClass({
+  list() {
+    return ajax("/s/admin/coupons", {
+      method: "get",
+    }).then((result) => {
+      if (result === null) {
+        return { unconfigured: true };
+      }
+      return result.map((coupon) => AdminCoupon.create(coupon));
+    });
+  },
+  save(params) {
+    const data = {
+      promo: params.promo,
+      discount_type: params.discount_type,
+      discount: params.discount,
+      active: params.active,
+    };
+
+    return ajax("/s/admin/coupons", {
+      method: "post",
+      data,
+    }).then((coupon) => AdminCoupon.create(coupon));
+  },
+
+  update(params) {
+    const data = {
+      id: params.id,
+      active: params.active,
+    };
+
+    return ajax("/s/admin/coupons", {
+      method: "put",
+      data,
+    }).then((coupon) => AdminCoupon.create(coupon));
+  },
+
+  destroy(params) {
+    const data = {
+      coupon_id: params.coupon.id,
+    };
+    return ajax("/s/admin/coupons", {
+      method: "delete",
+      data,
+    });
+  },
+});
+
+export default AdminCoupon;
diff --git a/assets/javascripts/discourse/routes/admin-plugins-discourse-subscriptions-coupons.js.es6 b/assets/javascripts/discourse/routes/admin-plugins-discourse-subscriptions-coupons.js.es6
new file mode 100644
index 0000000..85db462
--- /dev/null
+++ b/assets/javascripts/discourse/routes/admin-plugins-discourse-subscriptions-coupons.js.es6
@@ -0,0 +1,14 @@
+import Route from "@ember/routing/route";
+import AdminCoupon from "discourse/plugins/discourse-subscriptions/discourse/models/admin-coupon";
+
+export default Route.extend({
+  model() {
+    return AdminCoupon.list();
+  },
+
+  actions: {
+    reloadModel() {
+      this.refresh();
+    },
+  },
+});

[... diff too long, it was truncated ...]

GitHub sha: 400313cd

This commit appears in #43 which was approved by pmusaraj. It was merged by justindirose.