1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-17 06:52:27 +01:00

set the stripe keys from the UI

This commit is contained in:
Sylvain 2020-06-09 13:09:31 +02:00
parent 593c38e9de
commit d5939a9de5
11 changed files with 322 additions and 22 deletions

View File

@ -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();
}
]);

View File

@ -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';
}
}
});
}

View File

@ -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 }

View File

@ -53,8 +53,7 @@
<div class="form-group" ng-class="{'has-error': couponForm['coupon[amount_off]'].$dirty && couponForm['coupon[amount_off]'].$invalid}" ng-show="coupon.type == 'amount_off'">
<label for="coupon[amount_off]">{{ 'app.shared.coupon.amount_off' | translate }} *</label>
<div class="input-group">
²<div class="input-group">
<span class="input-group-addon">{{currencySymbol}}</span>
<input type="number" id="coupon[amount_off]"
name="coupon[amount_off]"

View File

@ -10,8 +10,86 @@
settings="allSettings"
label="app.admin.invoices.payment.enable_online_payment"
classes="m-l"
on-before-save="requireStripeKeys"
fa-icon="fa-font">
</boolean-setting>
</div>
<div class="row m-t" ng-show="allSettings.online_payment_module === 'true'">
<h3 class="m-l" translate>{{ 'app.admin.invoices.payment.stripe_keys' }}</h3>
<div class="col-md-4 m-l">
<label for="stripe_public_key" class="control-label">{{ 'app.admin.invoices.payment.public_key' | translate }}</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-info"></i></span>
<input type="text"
class="form-control"
id="stripe_public_key"
ng-model="allSettings.stripe_public_key"
readonly>
</div>
</div>
<div class="col-md-4 col-md-offset-1">
<label for="stripe_secret_key" class="control-label">{{ 'app.admin.invoices.payment.secret_key' | translate }}</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-key"></i></span>
<input type="password"
class="form-control"
id="stripe_secret_key"
ng-model="stripeSecretKey"
readonly>
</div>
</div>
<div class="col-md-1">
<button class="btn btn-default m-t-lg" ng-click="requireStripeKeys(allSettings.online_payment_module)" translate>{{ 'app.admin.invoices.payment.edit_keys' }}</button>
</div>
</div>
</div>
</div>
<script type="text/ng-template" id="stripeKeys.html">
<div>
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'app.admin.invoices.payment.stripe_keys' }}</h3>
</div>
<div class="modal-body">
<div class="alert alert-info" ng-bind-html="'app.admin.invoices.payment.stripe_keys_info_html' | translate"></div>
<div class="row m-md">
<label for="stripe_public_key" class="control-label">{{ 'app.admin.invoices.payment.public_key' | translate }} *</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-info"></i></span>
<input type="text"
class="form-control"
id="stripe_public_key"
ng-model="publicKey"
ng-model-options='{ debounce: 200 }'
ng-change='testPublicKey()'
required>
<span class="input-group-addon" ng-class="{'label-success': publicKeyStatus, 'label-danger text-white': !publicKeyStatus}" ng-show="publicKeyStatus !== undefined && publicKey">
<i class="fa fa-times" ng-show="!publicKeyStatus"></i>
<i class="fa fa-check" ng-show="publicKeyStatus"></i>
</span>
</div>
</div>
<div class="row m-md">
<label for="stripe_secret_key" class="control-label">{{ 'app.admin.invoices.payment.secret_key' | translate }} *</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-key"></i></span>
<input type="text"
class="form-control"
id="stripe_secret_key"
ng-model="secretKey"
ng-model-options='{ debounce: 200 }'
ng-change='testSecretKey()'
required>
<span class="input-group-addon" ng-class="{'label-success': secretKeyStatus, 'label-danger text-white': !secretKeyStatus}" ng-show="secretKeyStatus !== undefined && secretKey">
<i class="fa fa-times" ng-show="!secretKeyStatus"></i>
<i class="fa fa-check" ng-show="secretKeyStatus"></i>
</span>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>
</script>

View File

@ -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

View File

@ -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
##

View File

@ -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: "<p>To be able to collect online payments, you must configure the <a href='https://stripe.com' target='_blank'>Stripe</a> API keys.</p><p>Retrieve them from <a href='https://dashboard.stripe.com/account/apikeys' target='_blank'>your dashboard</a>.</p>"
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"

View File

@ -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: "<p>Pour pouvoir encaisser des paiements en ligne, vous devez configurer les clefs d'API <a href='https://stripe.com' target='_blank'>Stripe</a>.</p><p>Retrouvez les dans <a href='https://dashboard.stripe.com/account/apikeys' target='_blank'>votre tableau de bord</a>.</p>"
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"

View File

@ -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

View File

@ -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|