Rupeal
RSSLinkedInTwitterFacebook

Posts Tagged ‘RubyOnRails’

Pagar online – Vencer o medo em pt

Ao oferecer um serviço exclusivamente online em Portugal (www.invoicexpress.com) enfrentamos uma série de barreiras associadas ao medo da mudança / adopção de novas tecnologias.

O maior desses medos é o de fornecer dados bancários para efectuar o pagamento. Mesmo com o MBNET e o PAYPAL ainda há muita gente que prefere os meios mais tradicionais.

A forma que encontrá-mos para contornar este problema foi oferecer um meio de pagamento exclusivo para Portugal, suportando os nossos bem conhecidos MultiBancos.
Após analisar algumas alternativas escolhemos como parceiro para este serviço a Hi-Media e o seu Serviço Compra Fácil.

Este serviço desponibiliza um webservice através do qual podemos pedir a geração de referências multibanco notificando-nos quando estas são pagas.

De uma perspectiva mais técnica como decorre o processo e como o integramos na nossa aplicação Rails?

  1. O utilizador, cliente do invoic€xpress escolhe o plano desejado, selecciona a opção de pagamento com multibanco e selecciona a opção gerar referência.
  2. O invoic€xpress gera um token único para a referência
    token = Digest::SHA1.hexdigest([Time.now, rand].join)
  3. Constroi o url para onde pretende ser notificado do pagamento, incluindo este token único
    url = "https://www.invoicexpress.net/references/payed/#{@mbreference.token}"
  4. Coloca esse URL e os restantes parametros necessários no pedido para o webservice compra fácil e efectua o pedido.
    factory = SOAP::WSDLDriverFactory.new("http://hm.comprafacil.pt/SIBSClick/webservice/clicksmsV4.asmx?WSDL")
    soap = factory.create_rpc_driver
    soapResponse = soap.SaveCompraToBDValor1(:origem=>url, :IDCliente=>"cliente_teste",
     :password=>"teste_password", :valor=> value , :informacao=>"teste", :IDUserBackoffice=>"-1")
  5. Guarda a referência multibanco gerada na BD
    @mbreference.reference = soapResponse.referencia
  6. O cliente desloca-se ao MB e efectua o pagamento
  7. O sistema compra-fácil notifica o invoic€xpress usando URL que indicámos no pedido (passo 3)
  8. O invoic€xpress recebe a notificação, e procura a referência através do token único, activando assim a subscrição paga pelo cliente.
    mbreference = Mbreference.find_by_token(params[:reference_token])
    mbreference.state = "payed"
    mbreference.save

Para os mais distraidos foram menos de 20 linhas de código…

O código é apresentada numa versão simplificada apenas para efeitos demonstrativos.

Não poderá ser descurado o facto de estarmos a trabalhar com dinheiro e com dados de clientes, é sempre necessário garantir o máximo de segurança possível usando HTTPS.

Apesar de ser uma solução especifica para Portugal, é de simples implementação e é uma forte ajuda para quebrar uma barreira à adopção deste tipo de aplicações web.

Rui Alves on July 14th, 2009

Software Development

1
 

One user – Multiple accounts

One of the new features we have for the next release of InvoicExpress is to allow a user to be a member of multiple accounts.

The advantage of this model is that you only need one login credential to use all the accounts that you have access to.

In this post we show you a glimpse of our development process while developing this feature.

Our starting data model was (simplified here for clarity), a user belongs to an account, an account has many users, a user has many roles, nothing fancy.

class Account < ActiveRecord::Base
  has_many :users
end

class User < ActiveRecord::Base
  belongs_to :account
  has_and_belongs_to_many :roles
end

class Roles < ActiveRecord
  has_and_belongs_to_many :users
end

Users, Accounts and Memberships

The most obvious change is that the relation between user and account evolves from a one-to-many to many-to-many.

This change means user roles now have to be scoped by account. This isn’t easy with a regular many-to-many association.

In plain english we say “a user is a member of an account”. Member? We don’t have that in our data model. The relation between a user and its account is called Membership. Naming this showed us the missing link between a user and its accounts.

So we add a Membership model to map the relation between a user and it’s account.

User roles are now associated to an account through a membership.

class Account < ActiveRecord::Base
  belongs_to :o wner, :class_name => "User",
                     :foreign_key => "owner_id" 

  has_many :memberships, :dependent => :destroy
  has_many :users,:through => :memberships

end

class User<; ActiveRecord::Base
  has_many :memberships
  has_many :accounts, :through => :memberships,
                      :uniq => true
end

class Membership < ActiveRecord::Base
  belongs_to :user
  belongs_to :account

  has_and_belongs_to_many :roles
end

class Roles < ActiveRecord::Base
end

Keeping track

With multiple accounts per user we need know the currently used account. Our choice was to do it at the database level, because it would imply the least changes to our code base.

Knowing the current account means we also know the current membership. User roles can now be scoped to the current membership.

class User < ActiveRecord::Base

  has_many :memberships
  has_many :accounts, :through => :memberships,
                                   :uniq => true

  belongs_to :current_account, :class_name => "Account"
  has_one :current_membership,
          :class_name => "Membership",
          :foreign_key => "user_id",
          :conditions => 'current_account_id = #{self.current_account.id}'

  def roles
    self.current_membership.roles unless current_membership.blank?
  end
end

And that’s it for the data model changes

Changing accounts

Having multiple accounts, the user needs to know which one he is currently working on, as well as, be able to easily select another account.

For this double duty, we chose to use a always visible combo box located on the header of InvoicExpress.

The following code samples are simplified for readability.

Here we create a select input on our header partial and wire the onChange event to an ajax call to the users_controller.

# _header.html.erb
...
<%if current_user.accounts.count > 1 -%>;
  <%= select "change",:account, current_user.accounts.map{|a| [a.name, a.id]},
                                {:selected => current_user.account.id},
                                :o nchange => change_account
  %>
<% end -%>
...
# application_helper.rb
...
def change_account
  remote_function(:url => change_current_account_path, :method => :post,
                  :with =>"$('change_account').serialize()"
                 )
end

The change_current_account action fetches the selected account from the accounts the user is a member of.

If none is found the save will fail and we return a 412 – Precondition Failed error. On a successful save the user is now scoped the selected account, and we just have to redirect to the selected account account home screen.

# user_controller.rb
...
def change_current_account
  current_user.current_account = current_user.accounts.find(params[:change][:account])

  respond_to do |format|
    if current_user.save
      flash[:notice] = "You are now using account: #{account.name}"
      format.js do
        render :update do |page|
           page.redirect_to home_url
        end
      end
    else
        format.js{render :text => "change account error", :status =>; 412}
    end
  end
end

Wrapping up

Some say that every programming problem can be solved by another layer of indirection.
Others say that naming things is the hardest part of programming, and in this case both were true.

After we acknowledged and named the relation between a user and its accounts as a membership, all issues found almost resolved themselves. We were following a clear path. We were on rails after all.

Ps: In case you’re wondering the plugins we’re using for authentication and authorization are:

both available on github

Bruno Coelho on July 3rd, 2009

Software Development

0