diff --git a/plugins/mollie/README.md b/plugins/mollie/README.md index 50ab4ea41..782931503 100644 --- a/plugins/mollie/README.md +++ b/plugins/mollie/README.md @@ -1,23 +1,32 @@ -FoodsoftMollie -============== +# FoodsoftMollie -This project adds support for various online payment methods using Mollie to Foodsoft. -* Make sure the gem is uncommented in foodsoft's `Gemfile` -* Enter your Mollie account details in `config/app_config.yml` +This plugin adds support for various online payment methods to Foodsoft, using [Mollie](https://www.mollie.com), a Dutch payment provider who offers this service in the European Economic Area (EEA). + +> Currently, `v1.0.1` ONLY iDEAL payments are supported + +## Setup + +* Make sure the `Mollie` gem is uncommented in foodsoft's [Gemfile](../../Gemfile). +* Enter your Mollie account details in [config/app_config.yml](../../config/app_config.yml)` ```yaml + # Enable the plugin option use_mollie: true # Mollie payment settings mollie: - # API key for account: 1234567, website profile: FooInc + # API key for account as is provided by Mollie (check your Mollie dashboard) api_key: test_1234567890abcdef1234567890abcd - # Transaction fee as provided by mollie api (only EUR supported) + # Charge transaction fee as provided by mollie api + # When false: fees are not added to the total amount so the coop will pay any fee related to the transaction charge_fees: true - currency: EUR # should match the foodcoop's currency + # Tax to apply on the fee (which is communicated by Mollie without tax!!) + tax: 21 + # Only EUR supported (in the plugin at this time, Mollie does support other currencies) so this has to match the foodcoop's currency + currency: EUR ``` -When charge_fees is set true, the transaction fee will be added on each payment. At the moment fees are only supported with EUR. +When charge_fees is set `true`, the transaction fee will be added on each payment. At the moment fees are only supported with EUR. It is disabled by default, meaning that the foodcoop will pay any transaction costs (out of the margin). To initiate a payment, redirect to `new_payments_mollie_path` at `/:foodcoop/payments/mollie/new`. @@ -31,3 +40,37 @@ The following url parameters are recognised: This plugin also introduces the foodcoop config option `use_mollie`, which can be set to `false` to disable this plugin's functionality. May be useful in multicoop deployments. + +## Testing + +For testing it is helpful to allow Mollie to execute the callback, assuming you have an endpoint which can be reached, e.g. external address. See also [SSL](#ssl) as that is required by the Mollie callback. + +For this to work: add a file `callback_url.txt` in the `tmp` folder with a single line: + +```txt +https:// +``` + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +!!! BE SURE TO USE THE MOLLIE TEST API_KEY WHEN TESTING +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +## SSL + +For properly testing Mollie with callbacks, `SSL/TLS` is required. +See for instance [Let's Encrypt](https://letsencrypt.org/docs/client-options/) to generate and configure SSL. + +We assume the default Webserver (Puma) here. + +> See your specific webserver, like Apache or Nginx, for details. + +Start `RAILS` with SSL. + +```bash +bundle exec rails server --binding=0.0.0.0 -b 'ssl://0.0.0.0:3001?key=/local-certs/privkey.pem&cert=some local place>/local-certs/fullchain.pem' +``` + +> !! Be aware that you should NEVER commit your certificates to the repository!! + +Check the status to see if the webserver did start with SSL. +Connect using `https:///` \ No newline at end of file diff --git a/plugins/mollie/app/controllers/payments/mollie_controller.rb b/plugins/mollie/app/controllers/payments/mollie_controller.rb index 426ad701c..36da40569 100644 --- a/plugins/mollie/app/controllers/payments/mollie_controller.rb +++ b/plugins/mollie/app/controllers/payments/mollie_controller.rb @@ -1,41 +1,78 @@ # Mollie payment page +# +# ! NOTE Currently ONLY iDEAL is supported, All other methods supported by Mollie are ignored. Even when the account has them enabled +# +# TODO: add support for SEPA bank transfer +# TODO: add support for Credit Cards + class Payments::MollieController < ApplicationController + include MollieHelper + before_action -> { require_plugin_enabled FoodsoftMollie } skip_before_action :authenticate, only: [:check] skip_before_action :verify_authenticity_token, only: [:check] before_action :validate_ordergroup_presence, only: %i[new create result] - before_action :payment_methods, only: %i[new create] + before_action :available_payment_methods, only: %i[new create] + # + # Called when a new mollie payment is started and page is opened + # + # @return [hash] list of available payment methods with amount and fee (currently limited to iDEAL only) + # def new params.permit(:amount, :min) - # if amount or minimum is given use that, otherwise use a default based on the ordergroups funds or 10 - @amount = [params[:min], params[:amount]].compact.max || [FoodsoftMollie.default_amount, @ordergroup.get_available_funds * -1].max # @todo extract + # if amount or minimum is given use that, otherwise use a default based on the ordergroups funds or amount defined + @amount = [params[:min], params[:amount]].compact.max || [FoodsoftMollie.default_amount, @ordergroup.get_available_funds * -1].max # @TODO: extract + params[:min] = params[:amount].to_f > 0 && params[:min].to_f < params[:amount].to_f ? params[:amount] : FoodsoftMollie.default_amount + logger.debug ">>> Method: #{@available_payment_methods['ideal'].id} Params: #{FoodsoftMollie.default_amount} => #{params[:min]} - #{params[:amount]}}" + + # TODO: support looping and collecting available_payment_methods. For now only iDEAL is supported + @fee = payment_fee(@available_payment_methods['ideal'], @amount) + payment = { description: @available_payment_methods['ideal'].description, amount: @amount, fee: @fee } + @payment_options = { ideal: payment } + logger.debug "Collected: #{@payment_options}" end + # + # Create new payment, based on choice made + # def create # store parameters so we can redirect to original form on problems - session[:mollie_params] = params.permit(:amount, :payment_method, :label, :title, :fixed, :min, :text, :payment_fee) + session[:mollie_params] = params.permit(:amount, :payment_method, :label, :title, :fixed, :min, :text) + # Check if given amount has not been lowered below minimal allowed amount + redirect_on_error(t('.invalid_amount', currency: t('number.currency.format.unit'), amount: params[:amount], minimum: params[:min])) and return if params[:min].to_f > params[:amount].to_f - amount = [params[:min].to_f, params[:amount].to_f].compact.max - payment_fee = params[:payment_fee].to_f - amount += payment_fee + logger.debug ">>>> #{params[:payment_method]} with #{params[:amount]}" - redirect_on_error(t('.invalid_amount')) and return if amount <= 0 + @method_chosen = @available_payment_methods[params[:payment_method]] + redirect_on_error(t('.invalid_method', method: params[:payment_method])) and return if @method_chosen.nil? - method = fetch_mollie_methods.find { |m| m.id == params[:payment_method] } - transaction = create_transaction(amount, payment_fee, method) - payment = create_payment(transaction, amount, method) - transaction.update payment_id: payment.id - logger.info "Mollie start: #{amount} for ##{@current_user.id} (#{@current_user.display})" - redirect_to payment.checkout_url, allow_other_host: true + # Fee calculated over the amount to pay + fee = payment_fee(@method_chosen, params[:amount]) + # Create simplified hash for payment info + @payment_info = { id: params[:payment_method], description: @method_chosen.description, amount: params[:amount], fee: fee } + + # Create transaction record + transaction = create_transaction(@payment_info) + # Execute the payment command + mollie_payment = create_payment(transaction) + # Execute the transaction with the returned payment.id from Mollie. + transaction.update payment_id: mollie_payment.id + logger.info "Mollie start: #{mollie_payment.id} - Amount: #{@payment_info[:amount].to_f + @payment_info[:fee].to_f} for ##{@current_user.id} (#{@current_user.display})" + redirect_to mollie_payment.checkout_url, allow_other_host: true rescue Mollie::Exception => e - Rails.logger.info "Mollie create warning: #{e}" + Rails.logger.info "Mollie reported an error: #{e}" redirect_on_error t('errors.general_msg', msg: e.message) end + # # Endpoint that Mollie calls when a payment status changes. # See: https://docs.mollie.com/overview/webhooks + # + # @return handle succesful transaction | report Mollie error on failure + # def check + logger.debug "MOLLIE check #{params.require(:id)}" transaction = FinancialTransaction.find_by_payment_plugin_and_payment_id!('mollie', params.require(:id)) render plain: update_transaction(transaction) rescue StandardError => e @@ -46,82 +83,97 @@ def check # User is redirect here after payment def result transaction = @ordergroup.financial_transactions.find(params.require(:id)) + logger.debug "MOLLIE result #{params.require(:id)}\nTRANSACTiON PROCESSED #{transaction.inspect}" update_transaction transaction case transaction.payment_state when 'paid' - redirect_to root_path, notice: t('.controller.result.notice', amount: "#{transaction.payment_currency} #{transaction.amount}") + redirect_to root_path, notice: t('.controller.result.notice', amount: "#{transaction.payment_currency} #{format('%.2f', transaction.payment_amount)}") when 'open', 'pending' redirect_to root_path, notice: t('.controller.result.wait') + when 'canceled' + redirect_on_error t('.controller.result.canceled') else redirect_on_error t('.controller.result.failed') end end def cancel - redirect_to root_path + redirect_to root_path, notice: t('.controller.result.canceled') end private - # Query Mollie status and update financial transaction - def update_transaction(transaction) - payment = Mollie::Payment.get(transaction.payment_id, api_key: FoodsoftMollie.api_key) - logger.debug "Mollie update_transaction: #{transaction.inspect} with payment: #{payment.inspect}" - if payment.paid? - amount = payment.amount.value.to_f - amount -= transaction.payment_fee if FoodsoftMollie.charge_fees? - transaction.update! amount: amount - end - transaction.update! payment_state: payment.status - end - - def payment_methods - @payment_methods = fetch_mollie_methods - @payment_methods_fees = @payment_methods.to_h do |method| - [method.id, method.pricing.map do |pricing| - { - description: pricing.description, - fixed: { currency: pricing.fixed.currency, value: pricing.fixed.value.to_f }, - variable: pricing.variable.to_f - } - end.to_json] - end - end - + # + # + # + # @return [] + # def validate_ordergroup_presence @ordergroup = current_user.ordergroup.presence redirect_to root_path, alert: t('.no_ordergroup') and return if @ordergroup.nil? end - def create_transaction(amount, payment_fee, method) + # + # + # + # @param [Hash] payment_info All payment info, type, amount and fee + # + # @return [FinancialTransaction] Trancaction + # + def create_transaction(payment_info) + logger.debug payment_info.inspect.to_s financial_transaction_type = FinancialTransactionType.find_by_id(FoodsoftConfig[:mollie][:financial_transaction_type]) || FinancialTransactionType.first - note = t('.controller.transaction_note', method: method.description) - + note = t('.controller.transaction_note', method: payment_info[:description]) FinancialTransaction.create!( amount: nil, ordergroup: @ordergroup, user: @current_user, payment_plugin: 'mollie', - payment_amount: amount, - payment_fee: payment_fee, + payment_amount: payment_info[:amount], # TODO: unclear whether this attribute is used? As the paid amount seems to be copied to `amount` + payment_fee: payment_info[:fee], payment_currency: FoodsoftMollie.currency, payment_state: 'open', - payment_method: method.id, + payment_method: payment_info[:id], financial_transaction_type: financial_transaction_type, note: note ) end - def create_payment(transaction, amount, method) + # + # Query Mollie status and update financial transaction status + # + # @param [FinancialTransaction] transaction + # + # TODO: Check how data is stored. It seems confusing and error prone that the balance is recorded seperatly instead of taken from the existing data. See ordergroep.rb|update_balance + def update_transaction(transaction) + payment = Mollie::Payment.get(transaction.payment_id, api_key: FoodsoftMollie.api_key) + logger.debug ">>>>>>> \n\nMollie update_transaction: #{transaction.inspect}\n\n with payment:\n #{payment.inspect} \n => #{payment.status}" + transaction.update! amount: payment.amount.value.to_f - transaction.payment_fee if FoodsoftMollie.charge_fees? && payment.status == 'paid' + transaction.update! payment_state: payment.status + end + + # + # Execute the Mollie payment transaction + # + # @param [FinancialTransaction] transaction All information for the transaction + # + # @return [Mollie::Payment] Mollie payment object + # + def create_payment(transaction) + logger.debug ">> #{transaction.inspect}" + full_amount_due = transaction.payment_amount + transaction.payment_fee + logger.debug "Create Payment #{full_amount_due} with #{transaction.payment_method} to #{FoodsoftMollie.callback_url}" + Mollie::Payment.create( amount: { - value: format('%.2f', amount), + value: format('%.2f', full_amount_due), currency: FoodsoftMollie.currency }, - method: method.id, - description: "#{FoodsoftConfig[:name]}: Continue to add credit to #{@ordergroup.name}", - redirectUrl: result_payments_mollie_url(id: transaction.id), - webhookUrl: request.local? ? 'https://localhost.com' : check_payments_mollie_url, # Workaround for local development + method: transaction.payment_method, + description: t('.controller.decription', coop: FoodsoftConfig[:name], ordergroup: @ordergroup.name), + # In case Mollie testing callback is defined, use that one. + redirectUrl: FoodsoftMollie.callback_url.nil? ? result_payments_mollie_url(id: transaction.id) : "#{FoodsoftMollie.callback_url}/:foodcoop/payments/mollie/result?id=#{transaction.id}", + webhookUrl: FoodsoftMollie.callback_url.nil? ? check_payments_mollie_url : "#{FoodsoftMollie.callback_url}/:foodcoop/payments/mollie/check", metadata: { scope: FoodsoftConfig.scope, transaction_id: transaction.id, @@ -138,7 +190,112 @@ def redirect_on_error(alert_message) redirect_to new_payments_mollie_path(pms), alert: alert_message end + # + # Retrieve all supported payment methodes for the Mollie account + # Uses low-level cache so minimize repeated calls to this very expensive call + # Expire in 24 hours (not many changes are normally expected) + # + # @return [] All allowed payment methods on the account + # + def available_payment_methods + logger.debug "From cache: #{Rails.cache.read(FoodsoftMollie.api_key)}" + # For development: `rails dev:cache` + @available_payment_methods = Rails.cache.fetch(FoodsoftMollie.api_key, expires_in: 1.day) do + retrieve_payment_methods + end + logger.debug "MOLLIE TOTAL methods available: #{@available_payment_methods.count}" + end + + # + # Calculate fee, when applicable + # + # @param [Hash] method Mollie payment method + # @param [string] amount The payable amount to calculate the fee for + # + # @return [float] The fee due + # + + # Disable as the implicit "return last assignment" mantra breaks the actual function logic + # rubocop:disable Style/RedundantReturn + def payment_fee(method, amount) + return unless FoodsoftMollie.charge_fees? + + # Calculate when fees are charged (otherwise the recipient/coop pays the fee) + fee = method.pricing[0].fixed.value.to_f + (amount.to_f * (method.pricing[0].variable.to_f / 100)) + ## Add tax (split for clarity) + fee_with_tax = fee + (fee * FoodsoftMollie.tax_for_mollie.to_f / 100).round(2) + logger.debug "Fee calculated for method #{method.id}: #{fee_with_tax} for amount: #{amount} with tax #{FoodsoftMollie.tax_for_mollie}%" + # Make sure we explicitly return the fee_with_tax as the logger returns the number of characters - duh + return fee_with_tax + end + # rubocop:enable Style/RedundantReturn + + # + # Collect ENABLED payment methods for a Mollie account + # + # @return [hash] available_payment_methods for the account + # + + # Disable as the implicit "return last assignment" mantra breaks the actual function logic + # rubocop:disable Style/RedundantReturn + def retrieve_payment_methods + @payment_methods_all = fetch_mollie_methods + # Check for every method whether the account has it enabled + @available_payment_methods = {} + @payment_methods_all.each do |method| + # TODO: note that currently this only allows iDEAL, others are effectively disabled + if eligible_mollie_method(method.id) + # Method is supported, so add to the list + logger.debug "ADD TO LIST: #{method.inspect}" + @available_payment_methods[method.id] = method + end + logger.debug "MOLLIE Available #{@available_payment_methods.size}" + end + # Make sure this is the object we return (and not rely on the Python implicit return) + return @available_payment_methods + end + # rubocop:enable Style/RedundantReturn + + # + # Retrieve all payment methods on Mollie + # Due to an API change 04/2025 this is the _only_ function that returns the pricing information, solves #1178 + # Unfortunately this function does not filter on methods active for the account + # + # @return [List] List of ALL payment methods supported by Mollie, regardless whether the account has them enabled or not + # def fetch_mollie_methods - Mollie::Method.all(include: 'pricing,issuers', amount: { currency: FoodsoftMollie.currency, value: format('%.2f', FoodsoftMollie.default_amount) }, api_key: FoodsoftMollie.api_key) + Mollie::Method.all_available(include: 'pricing', api_key: FoodsoftMollie.api_key) + end + + # + # Check for eligible payment methods + # Since Mollie changed its API, this is now needed to filter for methods which are supported by the account + # A RequestError will indicate not supported or active: those we can ignore + # + # @param [string] method_id payment method + # + # @return [boolean] true|false supported or not supported + # + def eligible_mollie_method(method_id) + # logger.debug "MOLLIE CHECKING #{method_id}" + # ! ------------------------------------------------------------------------------------------------------------ + # ! For the time being we only support iDEAL payments. Others need more work to properly support, notably cards. + # ! ------------------------------------------------------------------------------------------------------------ + unless ['ideal'].include?(method_id) + logger.info "MOLLIE method [#{method_id}] currently not supported" + return false + end + # Check if method is enabled on account + begin + Mollie::Method.get(method_id, api_key: FoodsoftMollie.api_key) + rescue Mollie::RequestError + # Either + # - 404 Not Found (not present on account) + # - 403 Not enabled on account + # TODO: Other errors may be present but effectively this payment method is not available + logger.debug "MOLLIE method [#{method_id}] not active" + end + # Method is supported and active on account + logger.debug "MOLLIE method [#{method_id}] enabled" end end diff --git a/plugins/mollie/app/helpers/mollie_helper.rb b/plugins/mollie/app/helpers/mollie_helper.rb new file mode 100644 index 000000000..498eb6dac --- /dev/null +++ b/plugins/mollie/app/helpers/mollie_helper.rb @@ -0,0 +1,24 @@ +# Helper for Mollie + +module MollieHelper + # rubocop: disable Style/ConditionalAssignment + + def format_state(state) + return nil if state.nil? + + if state['paid'] + class_name = 'state_paid' + elsif state['open'] + class_name = 'state_open' + elsif state['canceled'] + class_name = 'state_canceled' + elsif state['authorized'] + class_name = 'state_authorized' + else + class_name = 'state_fail' + end + + content_tag :span, I18n.t(state), class: class_name + end + # rubocop: enable Style/ConditionalAssignment +end diff --git a/plugins/mollie/app/overrides/admin/configs/_tab_others/add_config.html.haml.deface b/plugins/mollie/app/overrides/admin/configs/_tab_others/add_config.html.haml.deface index cb26af671..f7d9a818b 100644 --- a/plugins/mollie/app/overrides/admin/configs/_tab_others/add_config.html.haml.deface +++ b/plugins/mollie/app/overrides/admin/configs/_tab_others/add_config.html.haml.deface @@ -5,5 +5,6 @@ = config_input fields, :api_key, as: :string, input_html: {class: 'input-xlarge'} = config_input fields, :financial_transaction_type, :as => :select, :collection => FinancialTransactionType.order(:name).map { |t| [ t.name, t.id ] } = config_input fields, :charge_fees, as: :boolean + = config_input fields, :tax, as: :float = config_input fields, :currency, as: :string, input_html: {class: 'input-xlarge'} = config_input fields, :default_amount, as: :float \ No newline at end of file diff --git a/plugins/mollie/app/views/payments/mollie/_form.html.haml b/plugins/mollie/app/views/payments/mollie/_form.html.haml index b9824e659..18f2d5661 100644 --- a/plugins/mollie/app/views/payments/mollie/_form.html.haml +++ b/plugins/mollie/app/views/payments/mollie/_form.html.haml @@ -1,27 +1,3 @@ -- content_for :javascript do - - if FoodsoftMollie.charge_fees? - :javascript - function paymentFee(amount, fixed, variable) { - return fixed + (amount * (variable/100)); - } - - function handleInputAmount(){ - const payment_method = $('#payment_method').val(); - const amount = parseFloat($('#amount').val()); - $('#payment_fee').empty(); - $('#fee_list').data(payment_method).forEach (fee => { - const calculatedFee = paymentFee(amount, fee.fixed.value, fee.variable).toFixed(2); - const currency = fee.fixed.currency; - $('#payment_fee') - .append($("") - .attr("value", calculatedFee) - .text(`${currency} ${calculatedFee} (${fee.description})`)); - }); - } - $('#amount').on('keyup', handleInputAmount); - $('#payment_method').on('change', handleInputAmount); - $(document).ready(handleInputAmount); - = form_tag payments_mollie_path, method: :post do - if params[:text] .well= params[:text] @@ -29,24 +5,27 @@ .control-label = label_tag 'amount', ((params[:label] or t('.amount_pay'))) .controls + - # TODO For iDEAL the fee is fixed. Recalculate fee for other methods as soon as that is supported .input-prepend %span.add-on= t 'number.currency.format.unit' = text_field_tag 'amount', @amount, readonly: (params[:fixed]=='true'), class: 'input-mini' - if params[:min] .help-inline{style: 'margin-bottom: 10px'} - = "(min #{number_to_currency params[:min], precision: 0})" + = "(min #{number_to_currency params[:min], precision: 2})" = hidden_field_tag 'min', params[:min] .control-group .control-label = label_tag 'payment_method', t('.payment_method') .controls - = select_tag 'payment_method', options_for_select(@payment_methods.map { |p| [p.description, p.id] }, params[:payment_method]), class: 'input-large' + - # TODO leave map for testing with other methods so they are shown. + = select_tag 'payment_method', options_for_select(@payment_options.map { |id, m| [m[:description], id.to_s] }, params[:payment_method]), class: 'input-large' - if FoodsoftMollie.charge_fees? - .control-group - .control-label - = label_tag 'payment_fee', t('.fee') - .controls - #fee_list{data: @payment_methods_fees }= select_tag 'payment_fee' + .control-label + = label_tag 'payment_fee', t('.payment_fee') + .input-prepend + %span.add-on= t 'number.currency.format.unit' + .help-inline{style: 'margin-bottom: 10px'} + = "#{number_to_currency @payment_options.dig(:ideal, :fee), precision: 2, unit: ''}" .control-group .controls = submit_tag t('.submit') diff --git a/plugins/mollie/config/locales/en.yml b/plugins/mollie/config/locales/en.yml index da8c8fb7a..8be414c51 100644 --- a/plugins/mollie/config/locales/en.yml +++ b/plugins/mollie/config/locales/en.yml @@ -1,13 +1,15 @@ +# yaml-language-server: $schema=language_schema.json + en: activerecord: attributes: home: credit_your_account: Credit your Account - home: - index: - credit_your_account: Credit your Account - ordergroup: - credit_your_account: Credit your Account + home: + index: + credit_your_account: Credit your Account + ordergroup: + credit_your_account: Credit your Account payments: mollie: new: @@ -15,18 +17,28 @@ en: no_ordergroup: You need to be member of an ordergroup create: invalid_amount: Invalid amount + invalid_method: No valid payment methods found form: + payment_fee: Transaction fee + payment_method: Payment method amount_pay: Amount to pay - method: Pay using submit: Pay online financial_transaction_type: Financial Transaction Type - fee: Select the appropriate transaction costs controller: result: notice: Your account was credited %{amount}. + canceled: Payment aborted. failed: Payment failed. wait: Your account will be credited when the payment is received. transaction_note: '%{method} payment' + decription: '%{coop}: add credit to household %{ordergroup}' + result: + paid: paid + canceled: aborted by user + open: waiting for payment + failed: failed + authorized: authorized + expired: expired config: hints: use_mollie: Let members credit their own Foodsoft ordergroup account with online payments using Mollie. @@ -36,6 +48,7 @@ en: charge_fees: Charge ordergroups the transaction fees applied by mollie. This is currently only available for EUR. currency: Choose the currency mollie should use (ISO code) default_amount: The default amount to credit the ordergroup with. + tax: Tax percentage to be applied to the Mollie transaction fee keys: use_mollie: Use Mollie mollie: @@ -43,4 +56,5 @@ en: financial_transaction_type: Transaction type charge_fees: Charge transaction fees currency: Currency - default_amount: Default amount \ No newline at end of file + default_amount: Default amount + tax: Tax % applied to fee \ No newline at end of file diff --git a/plugins/mollie/config/locales/language_schema.json b/plugins/mollie/config/locales/language_schema.json new file mode 100644 index 000000000..f6d12481a --- /dev/null +++ b/plugins/mollie/config/locales/language_schema.json @@ -0,0 +1,324 @@ + + +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "language", + "patternProperties": { + "(en|de|nl|es|fr|tr)": { + "type": "object", + "properties": { + "activerecord": { + "type": "object", + "properties": { + "attributes": { + "type": "object", + "properties": { + "home": { + "type": "object", + "properties": { + "credit_your_account": { + "type": "string" + } + }, + "required": [ + "credit_your_account" + ] + } + }, + "required": [ + "home" + ] + } + }, + "required": [ + "attributes" + ] + }, + "home": { + "type": "object", + "properties": { + "index": { + "type": "object", + "properties": { + "credit_your_account": { + "type": "string" + } + }, + "required": [ + "credit_your_account" + ] + }, + "ordergroup": { + "type": "object", + "properties": { + "credit_your_account": { + "type": "string" + } + }, + "required": [ + "credit_your_account" + ] + } + }, + "required": [ + "index", + "ordergroup" + ] + }, + "payments": { + "type": "object", + "properties": { + "mollie": { + "type": "object", + "properties": { + "new": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "no_ordergroup": { + "type": "string" + } + }, + "required": [ + "title", + "no_ordergroup" + ] + }, + "create": { + "type": "object", + "properties": { + "invalid_amount": { + "type": "string" + }, + "invalid_method": { + "type": "string" + } + }, + "required": [ + "invalid_amount", + "invalid_method" + ] + }, + "form": { + "type": "object", + "properties": { + "payment_fee": { + "type": "string" + }, + "payment_method": { + "type": "string" + }, + "amount_pay": { + "type": "string" + }, + "submit": { + "type": "string" + }, + "financial_transaction_type": { + "type": "string" + } + }, + "required": [ + "payment_fee", + "payment_method", + "amount_pay", + "submit", + "financial_transaction_type" + ] + }, + "controller": { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "notice": { + "type": "string" + }, + "canceled": { + "type": "string" + }, + "failed": { + "type": "string" + }, + "wait": { + "type": "string" + } + }, + "required": [ + "notice", + "canceled", + "failed", + "wait" + ] + }, + "transaction_note": { + "type": "string" + }, + "decription": { + "type": "string" + } + }, + "required": [ + "result", + "transaction_note", + "decription" + ] + }, + "result": { + "type": "object", + "properties": { + "paid": { + "type": "string" + }, + "canceled": { + "type": "string" + }, + "open": { + "type": "string" + }, + "failed": { + "type": "string" + }, + "authorized": { + "type": "string" + }, + "expired": { + "type": "string" + } + }, + "required": [ + "paid", + "canceled", + "open", + "failed", + "authorized", + "expired" + ] + } + }, + "required": [ + "new", + "create", + "form", + "controller", + "result" + ] + } + }, + "required": [ + "mollie" + ] + }, + "config": { + "type": "object", + "properties": { + "hints": { + "type": "object", + "properties": { + "use_mollie": { + "type": "string" + }, + "mollie": { + "type": "object", + "properties": { + "api_key": { + "type": "string" + }, + "financial_transaction_type": { + "type": "string" + }, + "charge_fees": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "default_amount": { + "type": "string" + }, + "tax": { + "type": "string" + } + }, + "required": [ + "api_key", + "financial_transaction_type", + "charge_fees", + "currency", + "default_amount", + "tax" + ] + } + }, + "required": [ + "use_mollie", + "mollie" + ] + }, + "keys": { + "type": "object", + "properties": { + "use_mollie": { + "type": "string" + }, + "mollie": { + "type": "object", + "properties": { + "api_key": { + "type": "string" + }, + "financial_transaction_type": { + "type": "string" + }, + "charge_fees": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "default_amount": { + "type": "string" + }, + "tax": { + "type": "string" + } + }, + "required": [ + "api_key", + "financial_transaction_type", + "charge_fees", + "currency", + "default_amount", + "tax" + ] + } + }, + "required": [ + "use_mollie", + "mollie" + ] + } + }, + "required": [ + "hints", + "keys" + ] + } + }, + "required": [ + "activerecord", + "home", + "payments", + "config" + ] + } + }, + "additionalProperties": false, + "maxProperties": 1, + "minProperties": 1 +} + diff --git a/plugins/mollie/config/locales/nl.yml b/plugins/mollie/config/locales/nl.yml index 75edb9565..f0a7f5b56 100644 --- a/plugins/mollie/config/locales/nl.yml +++ b/plugins/mollie/config/locales/nl.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=language_schema.json + nl: activerecord: attributes: @@ -14,17 +16,29 @@ nl: title: Stort geld op je account no_ordergroup: U dient lid te zijn van een huishouden create: - invalid_amount: Ongeldig bedrag + invalid_amount: Bedrag %{currency}%{amount} is lager dan vereist minimum %{currency}%{minimum} + invalid_method: Geen geldige betaalwijze gevonden voor %{method} form: + payment_fee: Transactiekosten + payment_method: Betaalwijze amount_pay: Te betalen bedrag - method: Hoe te betalen submit: Betaal online financial_transaction_type: Financiƫle-transactietype - fee: Selecteer de juiste transactiekosten controller: result: notice: Er is %{amount} bijgeschreven op uw account. + canceled: Betaling afgebroken. failed: Betaling mislukt. + wait: Je tegoed wordt aangepast zodra de betaling is ontvangen en verwerkt. + transaction_note: '%{method} betaling' + decription: '%{coop}: tegoed aanvullen voor huishouden %{ordergroup}' + result: + paid: betaald + canceled: geannuleerd + open: wachten op betaling + failed: mislukt + authorized: goedgekeurd + expired: verlopen config: hints: use_mollie: Laat gebruikers zelf geld op de rekening van hun Foodsoft huishouden zetten met online betalingen via Mollie. @@ -34,6 +48,7 @@ nl: charge_fees: Breng huishouden de door Mollie gehanteerde transactiekosten in rekening. Dit is momenteel alleen beschikbaar voor EUR. currency: Kies de valuta die Mollie moet gebruiken (ISO code) default_amount: Het standaardbedrag waarmee de ordergroep wordt gecrediteerd. + tax: Het belastingtarief voor de transactiekosten keys: use_mollie: Accepteer online betalingen via Mollie mollie: @@ -41,4 +56,5 @@ nl: financial_transaction_type: Transactietype charge_fees: Transactiekosten in rekening brengen currency: Valuta - default_amount: Standaard bedrag \ No newline at end of file + default_amount: Standaard bedrag + tax: Belasting % voor kosten \ No newline at end of file diff --git a/plugins/mollie/foodsoft_mollie.gemspec b/plugins/mollie/foodsoft_mollie.gemspec index be68ae189..1ff34f076 100644 --- a/plugins/mollie/foodsoft_mollie.gemspec +++ b/plugins/mollie/foodsoft_mollie.gemspec @@ -7,16 +7,16 @@ require 'foodsoft_mollie/version' Gem::Specification.new do |s| s.name = 'foodsoft_mollie' s.version = FoodsoftMollie::VERSION - s.authors = %w[wvengen yksflip] - s.email = ['dev-foodsoft@willem.engen.nl', 'foodsoft@yksflip.de'] + s.authors = %w[rayoei yksflip wvengen] + s.email = ['foodsoft@reepie.nl', 'foodsoft@yksflip.de'] s.homepage = 'https://github.com/foodcoops/foodsoft' s.summary = 'Mollie payment plugin for foodsoft.' - s.description = 'Integration with Mollie payments.' + s.description = 'Integration with Mollie payments APIv2.' s.files = Dir['{app,config,db,lib}/**/*'] + ['LICENSE', 'Rakefile', 'README.md'] s.add_dependency 'rails' - s.add_dependency 'mollie-api-ruby' + s.add_dependency 'mollie-api-ruby', '~> 4.17.0' s.metadata['rubygems_mfa_required'] = 'true' s.required_ruby_version = '>= 2.7' end diff --git a/plugins/mollie/lib/foodsoft_mollie.rb b/plugins/mollie/lib/foodsoft_mollie.rb index a9e45ab41..4c01bb51b 100644 --- a/plugins/mollie/lib/foodsoft_mollie.rb +++ b/plugins/mollie/lib/foodsoft_mollie.rb @@ -17,10 +17,21 @@ def self.charge_fees? end def self.default_amount - FoodsoftConfig[:mollie][:default_amount] || 10 + FoodsoftConfig[:mollie][:default_amount] || 10.00 end def self.api_key FoodsoftConfig[:mollie][:api_key] end + + def self.tax_for_mollie + FoodsoftConfig[:mollie][:tax] + end + + # Only for testing + def self.callback_url + return unless Rails.root.join('tmp/callback_url.txt').exist? + + Rails.root.join('tmp/callback_url.txt').read + end end diff --git a/plugins/mollie/lib/foodsoft_mollie/version.rb b/plugins/mollie/lib/foodsoft_mollie/version.rb index ee8979ee2..e0f8127b7 100644 --- a/plugins/mollie/lib/foodsoft_mollie/version.rb +++ b/plugins/mollie/lib/foodsoft_mollie/version.rb @@ -1,3 +1,3 @@ module FoodsoftMollie - VERSION = '0.0.1' + VERSION = '1.0.1' end