From d5939a9de55faa5d8c1e303b6cdf61f4c4bae7e5 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 9 Jun 2020 13:09:31 +0200 Subject: [PATCH] set the stripe keys from the UI --- .../controllers/admin/invoices.js.erb | 168 +++++++++++++++++- .../settings/boolean-setting.js.erb | 38 +++- app/assets/javascripts/router.js.erb | 5 +- .../templates/admin/coupons/_form.html.erb | 3 +- .../templates/admin/invoices/payment.html.erb | 78 ++++++++ app/models/setting.rb | 6 +- app/policies/setting_policy.rb | 2 +- config/locales/app.admin.en.yml | 9 + config/locales/app.admin.fr.yml | 9 + db/schema.rb | 22 +-- lib/tasks/fablab/setup.rake | 4 +- 11 files changed, 322 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/invoices.js.erb b/app/assets/javascripts/controllers/admin/invoices.js.erb index fbf482ea5..f9932c812 100644 --- a/app/assets/javascripts/controllers/admin/invoices.js.erb +++ b/app/assets/javascripts/controllers/admin/invoices.js.erb @@ -17,8 +17,8 @@ /** * Controller used in the admin invoices listing page */ -Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'AuthService', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', '_t', 'Member', 'uiTourService', - function ($scope, $state, Invoice, AccountingPeriod, AuthService, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, _t, Member, uiTourService) { +Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'AuthService', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', 'stripeSecretKey', '_t', 'Member', 'uiTourService', + function ($scope, $state, Invoice, AccountingPeriod, AuthService, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, stripeSecretKey, _t, Member, uiTourService) { /* PRIVATE STATIC CONSTANTS */ // number of invoices loaded each time we click on 'load more...' @@ -172,6 +172,9 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I // all settings $scope.allSettings = settings; + // is the stripe private set? + $scope.stripeSecretKey = stripeSecretKey.isPresent; + // Placeholding date for the invoice creation $scope.today = moment(); @@ -587,6 +590,39 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I return `${invoice.operator.first_name} ${invoice.operator.last_name}`; } + /** + * Open a modal dialog which ask for the stripe keys + * @param onlinePaymentModule {{name: String, value: String}} setting that defines the next status of the online payment module + * @return {boolean} false if the keys were not provided + */ + $scope.requireStripeKeys = function(onlinePaymentModule) { + // if the online payment is about to be disabled, accept the change without any further question + if (onlinePaymentModule.value === false) return true; + + // otherwise, open a modal to ask for the stripe keys + const modalInstance = $uibModal.open({ + templateUrl: 'stripeKeys.html', + controller: 'StripeKeysModalController', + resolve: { + stripeKeys: ['Setting', function (Setting) { return Setting.query({ names: "['stripe_public_key', 'stripe_secret_key']" }).$promise; }] + } + }); + + modalInstance.result.then(function (success) { + if (success) { + Setting.get({ name: 'stripe_public_key' }, function (res) { + $scope.allSettings.stripe_public_key = res.setting.value; + }) + Setting.isPresent({ name: 'stripe_private_key' }, function (res) { + $scope.stripeSecretKey = res.isPresent; + }) + } + }) + + // return the promise + return modalInstance.result; + } + /** * Setup the feature-tour for the admin/invoices page. * This is intended as a contextual help (when pressing F1) @@ -1212,3 +1248,131 @@ Application.Controllers.controller('AccountingExportModalController', ['$scope', // !!! MUST BE CALLED AT THE END of the controller return initialize(); }]); + + + +/** + * Controller used in the modal window allowing an admin to close an accounting period + */ +Application.Controllers.controller('StripeKeysModalController', ['$scope', '$uibModalInstance', '$http', '$httpParamSerializerJQLike', 'stripeKeys', 'Setting', 'growl', '_t', + function ($scope, $uibModalInstance, $http, $httpParamSerializerJQLike, stripeKeys, Setting, growl, _t) { + /* PUBLIC SCOPE */ + + // public key + $scope.publicKey = stripeKeys.stripe_public_key; + + // test status of the public key + $scope.publicKeyStatus = undefined; + + // secret key + $scope.secretKey = stripeKeys.stripe_secret_key; + + // test status of the secret key + $scope.secretKeyStatus = undefined; + + /** + * Trigger the test of the secret key and set the result in $scope.secretKeyStatus + */ + $scope.testSecretKey = function () { + if (!$scope.secretKey.match(/^sk_/)) { + $scope.secretKeyStatus = false; + return; + } + $http({ + method: 'GET', + url: 'https://api.stripe.com/v1/charges', + headers: { + Authorization: `Bearer ${$scope.secretKey}`, + } + }).then(function () { + $scope.secretKeyStatus = true; + }, function (err) { + if (err.status === 401) $scope.secretKeyStatus = false; + }); + } + + + /** + * Trigger the test of the secret key and set the result in $scope.secretKeyStatus + */ + $scope.testPublicKey = function () { + if (!$scope.publicKey.match(/^pk_/)) { + $scope.publicKeyStatus = false; + return; + } + const today = new Date(); + $http({ + method: 'POST', + url: 'https://api.stripe.com/v1/tokens', + headers: { + Authorization: `Bearer ${$scope.publicKey}`, + 'Content-Type': 'application/x-www-form-urlencoded' + }, + data: $httpParamSerializerJQLike({ + 'card[number]': 4242424242424242, + 'card[cvc]': 123, + 'card[exp_month]': 12, + 'card[exp_year]': today.getFullYear().toString().substring(2), + }) + }).then(function () { + $scope.publicKeyStatus = true; + }, function (err) { + if (err.status === 401) $scope.publicKeyStatus = false; + }); + } + + /** + * Validate the keys + */ + $scope.ok = function () { + if ($scope.secretKeyStatus && $scope.publicKeyStatus) { + Setting.bulkUpdate( + { settings: [ + { + name: 'stripe_public_key', + value: $scope.publicKey + }, + { + name: 'stripe_secret_key', + value: $scope.secretKey + } + ] }, + function () { + growl.success(_t('app.admin.invoices.payment.stripe_keys_saved')); + $uibModalInstance.close(true); + }, + function (error) { + growl.error('app.admin.invoices.payment.error_saving_stripe_keys'); + console.error(error); + } + ); + } else { + growl.error(_t('app.admin.invoices.payment.error_check_keys')) + } + }; + + /** + * Just dismiss the modal window + */ + $scope.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + + /* PRIVATE SCOPE */ + + /** + * Kind of constructor: these actions will be realized first when the controller is loaded + */ + const initialize = function () { + if (stripeKeys.stripe_public_key) { + $scope.testPublicKey(); + } + if (stripeKeys.stripe_secret_key) { + $scope.testSecretKey(); + } + }; + + // !!! MUST BE CALLED AT THE END of the controller! + return initialize(); + } +]); diff --git a/app/assets/javascripts/directives/settings/boolean-setting.js.erb b/app/assets/javascripts/directives/settings/boolean-setting.js.erb index a2640a211..661116386 100644 --- a/app/assets/javascripts/directives/settings/boolean-setting.js.erb +++ b/app/assets/javascripts/directives/settings/boolean-setting.js.erb @@ -8,7 +8,8 @@ Application.Directives.directive('booleanSetting', ['Setting', 'growl', '_t', settings: '=', yesLabel: '@', noLabel: '@', - classes: '@' + classes: '@', + onBeforeSave: '=' }, templateUrl: '<%= asset_path "admin/settings/boolean.html" %>', link ($scope, element, attributes) { @@ -27,6 +28,32 @@ Application.Directives.directive('booleanSetting', ['Setting', 'growl', '_t', * @param setting {{value:*, name:string}} note that the value will be stringified */ $scope.save = function (setting) { + if (typeof $scope.onBeforeSave === 'function') { + const res = $scope.onBeforeSave(setting); + if (res && _.isFunction(res.then)) { + // res is a promise, wait for it before proceed + res.then(function (success) { + if (success) saveValue(setting); + else resetValue(); + }, function () { + resetValue(); + }); + } else { + if (res) saveValue(setting); + else resetValue(); + } + } else { + saveValue(setting); + } + }; + + /* PRIVATE SCOPE */ + + /** + * Save the setting's new value in DB + * @param setting + */ + const saveValue = function (setting) { const value = setting.value.toString(); Setting.update( @@ -43,7 +70,14 @@ Application.Directives.directive('booleanSetting', ['Setting', 'growl', '_t', console.log(error); } ); - }; + } + + /** + * Reset the value of the setting to its original state (when the component loads) + */ + const resetValue = function () { + $scope.setting.value = $scope.settings[$scope.name] === 'true'; + } } }); } diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 89db2d429..5635bcf90 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -349,7 +349,7 @@ angular.module('application.router', ['ui.router']) settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: `['machine_explications_alert', 'booking_window_start', 'booking_window_end', 'booking_move_enable', \ - 'booking_move_delay', 'booking_cancel_enable', 'booking_cancel_delay', 'subscription_explications_alert' \ + 'booking_move_delay', 'booking_cancel_enable', 'booking_cancel_delay', 'subscription_explications_alert', \ 'online_payment_module']` }).$promise; }] @@ -839,8 +839,9 @@ angular.module('application.router', ['ui.router']) 'accounting_VAT_code', 'accounting_VAT_label', 'accounting_subscription_code', 'accounting_subscription_label', \ 'accounting_Machine_code', 'accounting_Machine_label', 'accounting_Training_code', 'accounting_Training_label', \ 'accounting_Event_code', 'accounting_Event_label', 'accounting_Space_code', 'accounting_Space_label', \ - 'feature_tour_display', 'online_payment_module']` }).$promise; + 'feature_tour_display', 'online_payment_module', 'stripe_public_key']` }).$promise; }], + stripeSecretKey: ['Setting', function (Setting) { return Setting.isPresent({ name: 'stripe_private_key' }).$promise; }], invoices: [ 'Invoice', function (Invoice) { return Invoice.list({ query: { number: '', customer: '', date: null, order_by: '-reference', page: 1, size: 20 } diff --git a/app/assets/templates/admin/coupons/_form.html.erb b/app/assets/templates/admin/coupons/_form.html.erb index 15682d2ba..972cc06df 100644 --- a/app/assets/templates/admin/coupons/_form.html.erb +++ b/app/assets/templates/admin/coupons/_form.html.erb @@ -53,8 +53,7 @@
- -
+ ²
{{currencySymbol}}
+
+

{{ 'app.admin.invoices.payment.stripe_keys' }}

+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+
+ + diff --git a/app/models/setting.rb b/app/models/setting.rb index 7a4b9c671..80b961c63 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -96,7 +96,11 @@ class Setting < ApplicationRecord openlab_app_id openlab_app_secret openlab_default - online_payment_module] } + online_payment_module + stripe_public_key + stripe_secret_key] } + # WARNING: when adding a new key, you may also want to add it in app/policies/setting_policy.rb#public_whitelist + def value last_value = history_values.order(HistoryValue.arel_table['created_at'].desc).first last_value&.value diff --git a/app/policies/setting_policy.rb b/app/policies/setting_policy.rb index 6eb5b6135..45125a2a7 100644 --- a/app/policies/setting_policy.rb +++ b/app/policies/setting_policy.rb @@ -38,7 +38,7 @@ class SettingPolicy < ApplicationPolicy fablab_name name_genre event_explications_alert space_explications_alert link_name home_content phone_required tracking_id book_overlapping_slots slot_duration events_in_calendar spaces_module plans_module invoicing_module recaptcha_site_key feature_tour_display disqus_shortname allowed_cad_extensions openlab_app_id openlab_default - online_payment_module] + online_payment_module stripe_public_key] end ## diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index cc430b356..0fb87a268 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -619,6 +619,14 @@ en: online_payment: "Online payment" online_payment_info_html: "You can enable your members to book directly online, paying by card. Alternatively, you can restrict the booking and payment processes for administrators and managers." enable_online_payment: "Enable online payment" + stripe_keys: "Stripe keys" + stripe_keys_info_html: "

To be able to collect online payments, you must configure the Stripe API keys.

Retrieve them from your dashboard.

" + public_key: "Public key" + secret_key: "Secret key" + error_check_keys: "Error: please check your Stripe keys." + stripe_keys_saved: "Stripe keys successfully saved." + error_saving_stripe_keys: "Unable to save the Stripe keys. Please try again later." + edit_keys: "Edit keys" #management of users, labels, groups, and so on members: users_management: "Users management" @@ -1087,6 +1095,7 @@ en: openlab_app_id: "OpenLab ID" openlab_app_secret: "OpenLab secret" openlab_default: "default gallery view" + online_payment_module: "online payment module" general: general: "General" title: "Title" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index d781b4711..ad2125932 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -619,6 +619,14 @@ fr: online_payment: "Paiement en ligne" online_payment_info_html: "Vous pouvez permettre à vos membres de réserver directement en ligne, en payment par carte bancaire. De manière alternative, vous pouvez restreindre les processus de réservation et de paiement aux administrateurs et aux gestionnaires." enable_online_payment: "Activer le paiement en ligne" + stripe_keys: "Clefs Stripe" + stripe_keys_info_html: "

Pour pouvoir encaisser des paiements en ligne, vous devez configurer les clefs d'API Stripe.

Retrouvez les dans votre tableau de bord.

" + public_key: "Clef publique" + secret_key: "Clef secrète" + error_check_keys: "Erreur : veuillez vérifier vos clefs Stripe." + stripe_keys_saved: "Les clefs Stripe ont bien été enregistrées." + error_saving_stripe_keys: "Impossible d'enregitrer les clefs Stripe. Veuillez réessayer ultérieurement." + edit_keys: "Modifier les clefs" #management of users, labels, groups, and so on members: users_management: "Gestion des utilisateurs" @@ -1087,6 +1095,7 @@ fr: openlab_app_id: "l'identifiant OpenLab" openlab_app_secret: "secret OpenLab" openlab_default: "l'affichage par défaut de la galerie" + online_payment_module: "module de paiement en ligne" general: general: "Général" title: "Titre" diff --git a/db/schema.rb b/db/schema.rb index b86050026..fd65d29b9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -18,8 +18,8 @@ ActiveRecord::Schema.define(version: 2020_05_11_075933) do enable_extension "unaccent" create_table "abuses", id: :serial, force: :cascade do |t| - t.integer "signaled_id" t.string "signaled_type" + t.integer "signaled_id" t.string "first_name" t.string "last_name" t.string "email" @@ -48,8 +48,8 @@ ActiveRecord::Schema.define(version: 2020_05_11_075933) do t.string "locality" t.string "country" t.string "postal_code" - t.integer "placeable_id" t.string "placeable_type" + t.integer "placeable_id" t.datetime "created_at" t.datetime "updated_at" end @@ -63,8 +63,8 @@ ActiveRecord::Schema.define(version: 2020_05_11_075933) do end create_table "assets", id: :serial, force: :cascade do |t| - t.integer "viewable_id" t.string "viewable_type" + t.integer "viewable_id" t.string "attachment" t.string "type" t.datetime "created_at" @@ -132,8 +132,8 @@ ActiveRecord::Schema.define(version: 2020_05_11_075933) do end create_table "credits", id: :serial, force: :cascade do |t| - t.integer "creditable_id" t.string "creditable_type" + t.integer "creditable_id" t.integer "plan_id" t.integer "hours" t.datetime "created_at" @@ -285,8 +285,8 @@ ActiveRecord::Schema.define(version: 2020_05_11_075933) do end create_table "invoices", id: :serial, force: :cascade do |t| - t.integer "invoiced_id" t.string "invoiced_type" + t.integer "invoiced_id" t.string "stp_invoice_id" t.integer "total" t.datetime "created_at" @@ -349,15 +349,15 @@ ActiveRecord::Schema.define(version: 2020_05_11_075933) do create_table "notifications", id: :serial, force: :cascade do |t| t.integer "receiver_id" - t.integer "attached_object_id" t.string "attached_object_type" + t.integer "attached_object_id" t.integer "notification_type_id" t.boolean "is_read", default: false t.datetime "created_at" t.datetime "updated_at" t.string "receiver_type" t.boolean "is_send", default: false - t.jsonb "meta_data", default: {} + t.jsonb "meta_data", default: "{}" t.index ["notification_type_id"], name: "index_notifications_on_notification_type_id" t.index ["receiver_id"], name: "index_notifications_on_receiver_id" end @@ -457,8 +457,8 @@ ActiveRecord::Schema.define(version: 2020_05_11_075933) do create_table "prices", id: :serial, force: :cascade do |t| t.integer "group_id" t.integer "plan_id" - t.integer "priceable_id" t.string "priceable_type" + t.integer "priceable_id" t.integer "amount" t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -565,8 +565,8 @@ ActiveRecord::Schema.define(version: 2020_05_11_075933) do t.text "message" t.datetime "created_at" t.datetime "updated_at" - t.integer "reservable_id" t.string "reservable_type" + t.integer "reservable_id" t.integer "nb_reserve_places" t.integer "statistic_profile_id" t.index ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id" @@ -575,8 +575,8 @@ ActiveRecord::Schema.define(version: 2020_05_11_075933) do create_table "roles", id: :serial, force: :cascade do |t| t.string "name" - t.integer "resource_id" t.string "resource_type" + t.integer "resource_id" t.datetime "created_at" t.datetime "updated_at" t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id" @@ -867,8 +867,8 @@ ActiveRecord::Schema.define(version: 2020_05_11_075933) do create_table "wallet_transactions", id: :serial, force: :cascade do |t| t.integer "wallet_id" - t.integer "transactable_id" t.string "transactable_type" + t.integer "transactable_id" t.string "transaction_type" t.integer "amount" t.datetime "created_at", null: false diff --git a/lib/tasks/fablab/setup.rake b/lib/tasks/fablab/setup.rake index 7d31ffb26..1a46272e7 100644 --- a/lib/tasks/fablab/setup.rake +++ b/lib/tasks/fablab/setup.rake @@ -126,7 +126,9 @@ namespace :fablab do %w[_ OPENLAB_APP_ID openlab_app_id], %w[_ OPENLAB_APP_SECRET openlab_app_secret], %w[_ OPENLAB_DEFAULT openlab_default], - %w[! FABLAB_WITHOUT_ONLINE_PAYMENT online_payment_module false] + %w[! FABLAB_WITHOUT_ONLINE_PAYMENT online_payment_module false], + %w[_ STRIPE_PUBLISHABLE_KEY stripe_public_key], + %w[_ STRIPE_API_KEY stripe_secret_key] ] mapping.each do |m|