foodcoop-adam/foodsoft

View on GitHub
lib/foodsoft_mollie/app/controllers/payments/mollie_ideal_controller.rb

Summary

Maintainability
A
35 mins
Test Coverage
#
# This is a quick hack to get iDEAL payments working without modifying
# foodsoft's database model. Transactions are not stored while in process,
# only on success. Failed transactions leave no trace in the database,
# but they are logged in the server log.
#
# Mollie's check url that is used contains the userid as the last path
# component, so that a financial transaction can be created on success
# for that user and ordergroup.
#
# Perhaps a cleaner approach would be to create a financial transaction
# without amount zero when the payment process starts, and keep track
# of the state using that. Then the transaction id would be enough to
# process it, and also an error message could be given.
#
# Or start using activemerchant - e.g.
#   https://github.com/moneybird/active_merchant_mollie
#
class Payments::MollieIdealController < ApplicationController
  before_filter -> { require_plugin_enabled FoodsoftMollie }
  skip_before_filter :authenticate, :only => [:check]
  before_filter :get_ordergroup, only: [:new, :create]
  before_filter :accept_return_to, only: [:new]

  def new
    set_mollie_cfg
    @banks = IdealMollie.banks
    @amount = (params[:amount] or [0, -@ordergroup.get_available_funds].min)
    @amount = [params[:min], @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,v| %w(amount label title fixed min text).include?(k)}

    bank_id = params[:bank_id]
    amount = params[:amount].to_f
    amount = [params[:min].to_f, amount].max if params[:min]

    set_mollie_cfg
    IdealMollie::Config.return_url = result_payments_mollie_url
    IdealMollie::Config.report_url = check_payments_mollie_url(:id => @current_user.id)
    request = IdealMollie.new_order(
      amount: (amount*100.0).to_i,
      description: "#{@current_user.ordergroup.id}, #{FoodsoftConfig[:name]}",
      bank_id: bank_id
    )

    transaction_id = request.transaction_id
    logger.info "Mollie start: #{amount} for \##{@current_user.id} (#{@current_user.display}) with bank #{bank_id}"

    redirect_to request.url
  end

  def check
    set_mollie_cfg
    transaction_id = params[:transaction_id]
    response = IdealMollie.check_order(transaction_id)
    logger.info "Mollie check: #{response.inspect}"

    if response.paid
      user = User.find(params[:id])
      notice = self.ideal_note(transaction_id)
      amount = response.amount/100.0
      @transaction = FinancialTransaction.new(:user=>user, :ordergroup=>user.ordergroup, :amount=>amount, :note=>notice)
      @transaction.add_transaction!
    end
    render :nothing => true
  end

  def result
    transaction_id = params[:transaction_id]
    @transaction = FinancialTransaction.where(:note => self.ideal_note(transaction_id)).first
    if @transaction
      logger.info "Mollie result: transaction #{transaction_id} succeeded"
      redirect_to_return_or root_path, :notice => I18n.t('payments.mollie_ideal.controller.result.notice')
    else
      logger.info "Mollie result: transaction #{transaction_id} 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
  end

  def cancel
    redirect_to_return_or root_path
  end

  protected

  def ideal_note(transaction_id)
    # this is _not_ translated, because this exact string is used to find the transaction
    "iDEAL payment (Mollie #{transaction_id})"
  end

  def get_ordergroup
    # TODO what if the current user doesn't have one?
    @ordergroup = current_user.ordergroup
  end

  def set_mollie_cfg
    if mcfg = FoodsoftConfig[:mollie].try(&:symbolize_keys)
      IdealMollie::Config.partner_id  = mcfg[:partner_id]  if mcfg[:partner_id]
      IdealMollie::Config.profile_key = mcfg[:profile_key] if mcfg[:profile_key]
      IdealMollie::Config.test_mode   = mcfg[:test_mode]   if mcfg[:test_mode]
    end
  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 unless params[:return_to].present?
    if params[:return_to].starts_with?(root_path) or params[:return_to].starts_with?(root_url)
      session[:return_to] = params[:return_to]
    end
  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
end