1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-20 09:52:19 +01:00

read stripe_currency from the UI

We prevent the currency from being changed if any stripe payment was made, because a stripe user cannot made pay with different currencies. If we try to charge a user with a different currency than the currency he used for a previous payment, this will fail; so we must prevent this case
This commit is contained in:
Sylvain 2020-06-10 16:37:11 +02:00
parent 401cf6b7ec
commit 78518e17fb
25 changed files with 100 additions and 37 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', 'stripeSecretKey', '_t', 'Member', 'uiTourService',
function ($scope, $state, Invoice, AccountingPeriod, AuthService, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, stripeSecretKey, _t, Member, uiTourService) {
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'AuthService', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', 'stripeSecretKey', '_t', 'Member', 'uiTourService', 'Payment', 'onlinePaymentStatus',
function ($scope, $state, Invoice, AccountingPeriod, AuthService, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, stripeSecretKey, _t, Member, uiTourService, Payment, onlinePaymentStatus) {
/* PRIVATE STATIC CONSTANTS */
// number of invoices loaded each time we click on 'load more...'
@ -178,6 +178,9 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
// is the stripe private set?
$scope.stripeSecretKey = (stripeSecretKey.isPresent ? STRIPE_SK_HIDDEN : '');
// has any online payment been already made?
$scope.onlinePaymentStatus = onlinePaymentStatus.status;
// Placeholding date for the invoice creation
$scope.today = moment();
@ -619,6 +622,9 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
Setting.isPresent({ name: 'stripe_secret_key' }, function (res) {
$scope.stripeSecretKey = (res.isPresent ? STRIPE_SK_HIDDEN : '');
})
Payment.onlinePaymentStatus(function (res) {
$scope.onlinePaymentStatus = res.status;
});
}
})

View File

@ -10,7 +10,10 @@ Application.Directives.directive('textSetting', ['Setting', 'growl', '_t',
faIcon: '@',
placeholder: '@',
required: '<',
type: '@'
type: '@',
maxLength: '@',
minLength: '@',
readOnly: '<'
},
templateUrl: '<%= asset_path "admin/settings/text.html" %>',
link ($scope, element, attributes) {

View File

@ -839,9 +839,10 @@ 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', 'stripe_public_key']` }).$promise;
'feature_tour_display', 'online_payment_module', 'stripe_public_key', 'stripe_currency']` }).$promise;
}],
stripeSecretKey: ['Setting', function (Setting) { return Setting.isPresent({ name: 'stripe_secret_key' }).$promise; }],
onlinePaymentStatus: ['Payment', function (Payment) { return Payment.onlinePaymentStatus().$promise; }],
invoices: [ 'Invoice', function (Invoice) {
return Invoice.list({
query: { number: '', customer: '', date: null, order_by: '-reference', page: 1, size: 20 }

View File

@ -7,6 +7,10 @@ Application.Services.factory('Payment', ['$resource', function ($resource) {
method: 'POST',
url: '/api/payments/confirm_payment',
isArray: false
},
onlinePaymentStatus: {
method: 'GET',
url: '/api/payments/online_payment_status'
}
}
);

View File

@ -42,6 +42,23 @@
<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 class="row m-t" ng-show="allSettings.online_payment_module === 'true'">
<h3 class="m-l" translate>{{ 'app.admin.invoices.payment.currency' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.invoices.payment.currency_info_html' | translate"></p>
<p class="alert alert-danger m-h-md" ng-bind-html="'app.admin.invoices.payment.currency_alert_html' | translate"></p>
<div class="col-md-4 m-l">
<text-setting name="stripe_currency"
settings="allSettings"
label="app.admin.invoices.payment.stripe_currency"
fa-icon="fa-money"
placeholder="XXX"
required="true"
min-length="3"
max-length="3"
read-only="onlinePaymentStatus">
</text-setting>
</div>
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
<form class="{{classes}}" name="setting-number-form">
<form class="{{classes}}" name="settingNumberForm">
<label for="setting-{{setting.name}}" class="control-label m-r" translate>{{ label }}</label>
<div class="form-group">
<div class="input-group">
@ -11,5 +11,5 @@
<i class="fa fa-lightbulb-o"></i> {{ helperText | translate }}
</span>
</div>
<button name="button" class="btn btn-warning" ng-click="save(setting)" ng-disabled="setting-number-form.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
<button name="button" class="btn btn-warning" ng-click="save(setting)" ng-disabled="settingNumberForm.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
</form>

View File

@ -1,4 +1,4 @@
<form class="{{classes}}" name="setting-select-multiple-form">
<form class="{{classes}}" name="settingSelectMultipleForm">
<div class="form-group">
<label for="setting-{{setting.name}}" class="control-label m-r" translate>{{ label }}</label>
<select class="form-control"
@ -13,7 +13,7 @@
<button ng-click="removeItem()" class="btn btn-default"><i class="fa fa-trash"></i></button>
<button ng-click="addItem()" class="btn btn-default"><i class="fa fa-plus"></i></button>
</div>
<button name="button" class="btn btn-warning m-t" ng-click="save(setting)" ng-disabled="setting-select-multiple-form.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
<button name="button" class="btn btn-warning m-t" ng-click="save(setting)" ng-disabled="settingSelectMultipleForm.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
</form>
<script type="text/ng-template" id="newSelectOption.html">

View File

@ -1,4 +1,4 @@
<form class="{{classes}}" name="setting-select-form">
<form class="{{classes}}" name="settingSelectForm">
<div class="form-group">
<label for="setting-{{setting.name}}" class="control-label m-r" translate>{{ label }}</label>
<select class="form-control"
@ -12,5 +12,5 @@
<option ng-if="option5" ng-value="option5[0]" translate>{{ option5[1] }}</option>
</select>
</div>
<button name="button" class="btn btn-warning m-t" ng-click="save(setting)" ng-disabled="setting-select-form.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
<button name="button" class="btn btn-warning m-t" ng-click="save(setting)" ng-disabled="settingSelectForm.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
</form>

View File

@ -1,4 +1,4 @@
<form class="{{classes}}" name="setting-text-form">
<form class="{{classes}}" name="settingTextForm">
<label for="setting-{{setting.name}}" class="control-label m-r" translate>{{ label }}</label>
<div ng-class="{'form-group': faIcon}">
<div ng-class="{'input-group': faIcon}">
@ -10,8 +10,11 @@
id="setting-{{setting.name}}"
placeholder="{{placeholder}}"
ng-model="setting.value"
ng-required="required">
ng-required="required"
ng-minlength="minLength"
ng-maxlength="maxLength"
ng-readonly="readOnly">
</div>
</div>
<button name="button" class="btn btn-warning m-t" ng-click="save(setting)" ng-disabled="setting-text-form.$invalid" translate>{{ 'app.shared.buttons.save' }}</button>
<button name="button" class="btn btn-warning m-t" ng-click="save(setting)" ng-disabled="settingTextForm.$invalid || readOnly" translate>{{ 'app.shared.buttons.save' }}</button>
</form>

View File

@ -27,7 +27,7 @@ class API::PaymentsController < API::ApiController
{
payment_method: params[:payment_method_id],
amount: amount[:amount],
currency: Rails.application.secrets.stripe_currency,
currency: Setting.get('stripe_currency'),
confirmation_method: 'manual',
confirm: true,
customer: current_user.stp_customer_id
@ -56,6 +56,16 @@ class API::PaymentsController < API::ApiController
render generate_payment_response(intent, res)
end
def online_payment_status
authorize :payment
key = Setting.get('stripe_secret_key')
render json: { status: false } and return unless key
charges = Stripe::Charge.list({ limit: 1 }, { api_key: key })
render json: { status: charges.data.length.positive? }
end
private
def on_reservation_success(intent, details)

View File

@ -98,7 +98,8 @@ class Setting < ApplicationRecord
openlab_default
online_payment_module
stripe_public_key
stripe_secret_key] }
stripe_secret_key
stripe_currency] }
# WARNING: when adding a new key, you may also want to add it in app/policies/setting_policy.rb#public_whitelist
def value

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
# Check the access policies for API::PaymentsController
class PaymentPolicy < ApplicationPolicy
def online_payment_status?
user.admin?
end
end

View File

@ -12,9 +12,9 @@ class SyncMembersOnStripeWorker
logger.debug "#{index} / #{total}"
begin
stp_customer = Stripe::Customer.retrieve(member.stp_customer_id, api_key: Setting.get('stripe_secret_key'))
StripeWorker.perform(:create_stripe_customer, member.id) if stp_customer.nil? || stp_customer[:deleted]
StripeWorker.new.create_stripe_customer(member.id) if stp_customer.nil? || stp_customer[:deleted]
rescue Stripe::InvalidRequestError
StripeWorker.perform(:create_stripe_customer, member.id)
StripeWorker.new.create_stripe_customer(member.id)
end
end
logger.debug 'Sync is done'

View File

@ -627,6 +627,10 @@ en:
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"
currency: "Currency"
currency_info_html: "Please specify below the currency used for online payment. You should provide a three-letter ISO code, from the list of <a href='https://stripe.com/docs/currencies' target='_blank'>Stripe supported currencies</a>."
currency_alert_html: "<strong>Warning</strong>: the currency cannot be changed after the first online payment was made. Please define this setting carefully before opening Fab-manager to your members."
stripe_currency: "Stripe currency"
#management of users, labels, groups, and so on
members:
users_management: "Users management"
@ -1096,6 +1100,7 @@ en:
openlab_app_secret: "OpenLab secret"
openlab_default: "default gallery view"
online_payment_module: "online payment module"
stripe_currency: "Stripe currency"
general:
general: "General"
title: "Title"

View File

@ -627,6 +627,10 @@ fr:
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"
currency: "Devise"
currency_info_html: "Veuillez indiquer la devise à utiliser lors des paiements en ligne. Vous devez fournir un code ISO à trois lettres, issu de la liste des <a href='https://stripe.com/docs/currencies' target='_blank'>devises supportées par Stripe</a>."
currency_alert_html: "<strong>Attention</strong> : la devise ne peut pas être modifiée après que le premier payment en ligne ait été effectué. Veuillez définir attentivement ce paramètre avant d'ouvrir Fab-manager à vos members."
stripe_currency: "Devise Stripe"
#management of users, labels, groups, and so on
members:
users_management: "Gestion des utilisateurs"
@ -1096,6 +1100,7 @@ fr:
openlab_app_secret: "secret OpenLab"
openlab_default: "l'affichage par défaut de la galerie"
online_payment_module: "module de paiement en ligne"
stripe_currency: "la devise Stripe"
general:
general: "Général"
title: "Titre"

View File

@ -164,6 +164,7 @@ Rails.application.routes.draw do
# payments handling
post 'payments/confirm_payment' => 'payments/confirm_payment'
get 'payments/online_payment_status' => 'payments/online_payment_status'
# FabAnalytics
get 'analytics/data' => 'analytics#data'

View File

@ -12,7 +12,6 @@
development:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
stripe_currency: <%= ENV["STRIPE_CURRENCY"] %>
fablab_without_wallet: <%= ENV["FABLAB_WITHOUT_WALLET"] %>
user_confirmation_needed_to_sign_in: <%= ENV["USER_CONFIRMATION_NEEDED_TO_SIGN_IN"] %>
default_host: <%= ENV["DEFAULT_HOST"] %>
@ -42,7 +41,6 @@ development:
test:
secret_key_base: 83daf5e7b80d990f037407bab78dff9904aaf3c195a50f84fa8695a22287e707dfbd9524b403b1dcf116ae1d8c06844c3d7ed942564e5b46be6ae3ead93a9d30
stripe_currency: usd
fablab_without_wallet: false
user_confirmation_needed_to_sign_in: <%= ENV["USER_CONFIRMATION_NEEDED_TO_SIGN_IN"] %>
default_host: <%= ENV["DEFAULT_HOST"] %>
@ -72,7 +70,6 @@ test:
staging:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
stripe_currency: <%= ENV["STRIPE_CURRENCY"] %>
fablab_without_wallet: <%= ENV["FABLAB_WITHOUT_WALLET"] %>
user_confirmation_needed_to_sign_in: <%= ENV["USER_CONFIRMATION_NEEDED_TO_SIGN_IN"] %>
default_host: <%= ENV["DEFAULT_HOST"] %>
@ -113,7 +110,6 @@ staging:
# instead read values from the environment.
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
stripe_currency: <%= ENV["STRIPE_CURRENCY"] %>
fablab_without_wallet: <%= ENV["FABLAB_WITHOUT_WALLET"] %>
user_confirmation_needed_to_sign_in: <%= ENV["USER_CONFIRMATION_NEEDED_TO_SIGN_IN"] %>
default_host: <%= ENV["DEFAULT_HOST"] %>

View File

@ -880,6 +880,8 @@ unless Setting.find_by(name: 'allowed_cad_mime_types').try(:value)
)
end
Setting.set('stripe_currency', 'EUR') unless Setting.find_by(name: 'stripe_currency').try(:value)
if StatisticCustomAggregation.count.zero?
# available reservations hours for machines
machine_hours = StatisticType.find_by(key: 'hour', statistic_index_id: 2)

View File

@ -51,16 +51,6 @@ When using docker-compose, you should provide the name of the service in your [d
Used by the authentication system to generate random tokens, eg. for resetting passwords.
Used by Rails to verify the integrity of signed cookies.
You can generate such a random key by running `rails secret`.
<a name="STRIPE_CURRENCY"></a>
STRIPE_CURRENCY
Currency used by stripe to charge the final customer.
See https://support.stripe.com/questions/which-currencies-does-stripe-support for a list of available 3-letters ISO code.
**BEWARE**: stripe currency cannot be changed during the application life.
Changing the currency after the application has already run, may result in several bugs and prevent the users to pay through stripe.
So set this setting carefully before starting the application for the first time.
<a name="INVOICE_PREFIX"></a>
INVOICE_PREFIX

View File

@ -6,9 +6,7 @@ POSTGRES_PASSWORD=
REDIS_HOST=fabmanager-redis
ELASTICSEARCH_HOST=fabmanager-elastic
# Stripe
SECRET_KEY_BASE=83daf5e7b80d990f037407bab78dff9904aaf3c195a50f84fa8695a22287e707dfbd9524b403b1dcf116ae1d8c06844c3d7ed942564e5b46be6ae3ead93a9d30
STRIPE_CURRENCY=eur
# Invoices
INVOICE_PREFIX=Demo-FabLab_facture

View File

@ -128,7 +128,8 @@ namespace :fablab do
%w[_ OPENLAB_DEFAULT openlab_default],
%w[! FABLAB_WITHOUT_ONLINE_PAYMENT online_payment_module false],
%w[_ STRIPE_PUBLISHABLE_KEY stripe_public_key],
%w[_ STRIPE_API_KEY stripe_secret_key]
%w[_ STRIPE_API_KEY stripe_secret_key],
%w[_ STRIPE_CURRENCY stripe_currency]
]
mapping.each do |m|

View File

@ -4,8 +4,6 @@ ELASTICSEARCH_HOST=elasticsearch
SECRET_KEY_BASE=
STRIPE_CURRENCY=eur
INVOICE_PREFIX=Demo-FabLab_facture
FABLAB_WITHOUT_ONLINE_PAYMENT=false
PHONE_REQUIRED=false

View File

@ -234,7 +234,7 @@ configure_env_file()
local doc variables secret
doc=$(\curl -sSL https://raw.githubusercontent.com/sleede/fab-manager/master/doc/environment.md)
variables=(STRIPE_CURRENCY INVOICE_PREFIX FABLAB_WITHOUT_ONLINE_PAYMENT FABLAB_WITHOUT_WALLET \
variables=(INVOICE_PREFIX FABLAB_WITHOUT_ONLINE_PAYMENT FABLAB_WITHOUT_WALLET \
USER_CONFIRMATION_NEEDED_TO_SIGN_IN DEFAULT_HOST DEFAULT_PROTOCOL DELIVERY_METHOD SMTP_ADDRESS SMTP_PORT SMTP_USER_NAME SMTP_PASSWORD SMTP_AUTHENTICATION \
SMTP_ENABLE_STARTTLS_AUTO SMTP_OPENSSL_VERIFY_MODE SMTP_TLS \
LOG_LEVEL MAX_IMAGE_SIZE MAX_CAO_SIZE MAX_IMPORT_SIZE DISK_SPACE_MB_ALERT \

View File

@ -653,3 +653,11 @@ history_value_68:
value: <%= ENV["STRIPE_API_KEY"] %>
created_at: 2020-06-08 17:12:16.846525000 Z
updated_at: 2020-06-08 17:12:16.846525000 Z
history_value_69:
id: 69
setting_id: 69
invoicing_profile_id: 1
value: usd
created_at: 2020-06-08 17:12:16.846525000 Z
updated_at: 2020-06-08 17:12:16.846525000 Z

View File

@ -400,3 +400,9 @@ setting_68:
name: stripe_secret_key
created_at: 2020-06-08 17:12:16.846525000 Z
updated_at: 2020-06-08 17:12:16.846525000 Z
setting_69:
id: 69
name: stripe_currency
created_at: 2020-06-08 17:12:16.846525000 Z
updated_at: 2020-06-08 17:12:16.846525000 Z