# Mollie payment page class Payments::MollieIdealController < ApplicationController before_action -> { require_plugin_enabled FoodsoftMollie } skip_before_action :authenticate, :only => [:check] skip_before_action :verify_authenticity_token, :only => [:check] before_action :accept_return_to, only: [:new] before_action :ordergroup, only: [:new, :create, :result] before_action :transaction, only: [:result] before_action :configure_api_key def new @amount = (params[:amount] or [0, -@ordergroup.get_available_funds].max) @amount = [params[:min], params[:amount]].max if params[:min] end def create # store parameters so we can redirect to original form on problems session[:mollie_params] = params.select { |k, _| %w[amount label title fixed min text].include?(k) }.to_h amount = params[:amount].to_f amount = [params[:min].to_f, amount].max if params[:min] # @todo check amount > transaction feex ft_type = FinancialTransactionType.find_by_id(FoodsoftConfig[:mollie][:financial_transaction_type]) || FinancialTransactionType.first @transaction = FinancialTransaction.create!( amount: nil, ordergroup: @ordergroup, user: @current_user, payment_plugin: 'mollie', payment_amount: amount, # @todo payment_currency payment_state: 'created', financial_transaction_type_id: ft_type.id, note: I18n.t('payments.mollie_ideal.controller.transaction_note', method: nil), ) payment = Mollie::Payment.create( amount: { value: '%.2f' % [amount], # @todo use format over % currency: 'EUR' # @todo payment_currency }, description: "#{@transaction.id}, #{@ordergroup.id}, #{FoodsoftConfig[:name]}", redirectUrl: result_payments_mollie_url(id: @transaction.id), webhookUrl: (request.local? ? check_payments_mollie_url : 'https://example.com'), # Mollie doesn't accept localhost here metadata: { scope: FoodsoftConfig.scope, transaction_id: @transaction.id, user: @current_user.id, ordergroup: @ordergroup.id } ) @transaction.update payment_id: payment.id, payment_state: 'pending' logger.info "Mollie start: #{amount} for ##{@current_user.id} (#{@current_user.display})" redirect_to payment.checkout_url, allow_other_host: true rescue Mollie::Exception => e Rails.logger.info "Mollie create warning: #{e}" redirect_to new_payments_mollie_path(session[:mollie_params]), :alert => I18n.t('errors.general_msg', msg: e.message) end # Endpoint that Mollie calls when a payment status changes. def check logger.info "Mollie check: #{params[:id]}" @transaction = FinancialTransaction.find_by_payment_plugin_and_payment_id!('mollie', params[:id]) logger.debug " financial transaction: #{@transaction.inspect}" render plain: update_transaction(@transaction) rescue StandardError => e Rails.logger.error "Mollie check error: #{e}" render plain: "Error: #{e.message}" end # User is redirect here after payment def result update_transaction @transaction # @todo if request.local? # so localhost works too logger.info "Mollie result: transaction #{@transaction.id} (#{@transaction.payment_id}) is #{@transaction.payment_state}" case @transaction.payment_state when 'paid' redirect_to_return_or root_path, :notice => I18n.t('payments.mollie_ideal.controller.result.notice', amount: @transaction.amount, fee: @transaction.payment_fee) when 'pending' redirect_to_return_or root_path, :notice => I18n.t('payments.mollie_ideal.controller.result.wait') else payment_is_failed end end def payment_is_failed # redirect to form with same parameters as original page pms = { foodcoop: FoodsoftConfig.scope }.merge((session[:mollie_params] or {})) session[:mollie_params] = nil redirect_to new_payments_mollie_path(pms), :alert => I18n.t('payments.mollie_ideal.controller.result.failed') # TODO: recall check's response.message end def cancel redirect_to_return_or root_path end protected def ordergroup # @todo what if the current user doesn't have one? @ordergroup = current_user.ordergroup end def transaction @transaction = @ordergroup.financial_transactions.find(params[:id]) end # @todo move this to ApplicationController, use it in SessionController too # @todo use a stack of return_to urls def accept_return_to session[:return_to] = nil # or else an unfollowed previous return_to may interfere return if params[:return_to].blank? return unless params[:return_to].starts_with?(root_path) || params[:return_to].starts_with?(root_url) session[:return_to] = params[:return_to] end def redirect_to_return_or(fallback_url, options = {}) if session[:return_to].present? redirect_to_url = session[:return_to] session[:return_to] = nil else redirect_to_url = fallback_url end redirect_to redirect_to_url, options end # Query Mollie status and update financial transaction def update_transaction(transaction) payment = Mollie::Payment.get transaction.payment_id logger.debug "Mollie update_transaction: #{payment.inspect}" update_transaction_details(transaction, payment) update_transaction_amount(transaction, payment) payment.status end def update_transaction_amount(transaction, payment) if payment.status == 'paid' payment_fee = FoodsoftMollie.payment_fee payment.amount.value, transaction.payment_method amount = payment.amount.value.to_f - payment_fee.to_f end transaction.update payment_state: payment.status, amount: amount || 0, payment_fee: payment_fee || nil end def update_transaction_details(transaction, payment) if payment.details transaction.payment_acct_number = payment.details.consumerAccount transaction.payment_acct_name = payment.details.consumerName end transaction.payment_method = payment.method transaction.note = I18n.t('payments.mollie_ideal.controller.transaction_note', method: payment.method) # @todo add amount + fee to note end private def configure_api_key Mollie::Client.configure do |config| config.api_key = FoodsoftConfig[:mollie]['api_key'] end end end