Solon is another library for interacting with the sagepay VSP server. It is largely the codebase from the seemingly defunct Peeves library with some modifications.
This library is not the cleanest out there and you may want to look at the other sagepay offerings before using this library.
Add the following to your Gemfile
gem 'solon'
bundle
In each of your environments you can define the sagepay mode of operation accordingly.
In config/environments/(development|test|production|[staging]).rb
Solon::Config.vendor = [vendor_name] Solon::Config.gateway_mode = :simulator | :test | :live
Best plan is to generate yourself an controller for dealing with the integration.
rails g controller sages
in routes.rb
resources :sages
Assumes an order has an identifier in order.identifier
require 'digest/sha1' before_create :set_identifier def set_identifier self.identifier = Digest::SHA1.hexdigest(rand(1000).to_s + Time.now.to_s.split(//).sort_by {rand}.join) end
It is a good plan to create an order_transaction model to store order transactions locally for debugging.
The first step is registering the payment.
def create @order.update_attribute(:transaction_reference, Solon::UniqueId.generate(@order.id.to_s)) p = SolonGateway.new @response = p.payment Solon::Money.new(@order.amount.to_f, "GBP"), { :transaction_reference => @order.transaction_reference, :description => "Description of payment", :notification_url => callback_sage_url(@order.identifier), :customer_data => @order.sage_customer_data } if @response.next_url @order.transactions.create(:params => @response, :success => true, :message => "Purchase Transaction", :amount => @order.amount.to_f, :reference => @order.transaction_reference) @order.update_attributes(:security_key => @response.security_key, :user_location => 'away') @order.send_pending_email redirect_to @response.next_url else @order.transactions.create(:params => @response, :success => false, :message => "Purchase Transaction") flash[:warning] = "Unable to make payment, please contact support." redirect_to root_url end end
The next step is handling to callbacks from sage page.
def callback # Parse the callback callback = SolonGateway.parse_callback(request.raw_post) # Get the order from identifier begin @order = Order.find_by_identifier!(params[:id]) @order.sage_callback = callback rescue ActiveRecord::RecordNotFound => msg # Error if cannot find. render :text => SolonGateway.response('INVALID', no_order_sage_url, 'Transaction complete'), :layout => false return end # If the order has already been paid, render an OK response. if @order.paid? render_response(@order) return end if @order.valid_callback? # all is well else # error end render_response(@order) end
Various responses tell the app different things about the order and you need to render a response in the right manner.
private def render_response(order) if order.valid_callback? render :text => SolonGateway.response('OK', sage_url(order), 'Transaction complete'), :layout => false elsif order.invalid_callback? render :text => SolonGateway.response('INVALID', invalid_order_sage_url(order), 'Transaction complete'), :layout => false elsif order.sage_callback.st(:NOTAUTHED) render :text => SolonGateway.response('OK', notauthed_sage_url(order), 'Transaction complete'), :layout => false elsif order.sage_callback.st(:ABORT) render :text => SolonGateway.response('OK', abort_sage_url(order), 'Transaction complete'), :layout => false elsif order.sage_callback.st(:REJECTED) render :text => SolonGateway.response('OK', rejected_sage_url(order), 'Transaction complete'), :layout => false elsif order.sage_callback.error? render :text => SolonGateway.response('INVALID', invalid_sage_url(order), 'Transaction complete'), :layout => false else render :text => SolonGateway.response('ERROR', error_sage_url(order), 'Transaction complete'), :layout => false end end
Valid and invalid callbacks not only check that the callback is approved (callback.approved?) but also check that the value of the order locally has not changed since the transaction began. This is a danger with offsite payment gateways.
The checking of whether a callback is valid is left to the app developer.
1. Set vendor name in spec helper.
2. run bundle
3. bundle exec rspec