Solon – Sagepay VSP Server
Introduction
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.
Installation (Rails 3)
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
Integration Guide
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.
Testing
1. Set vendor name in spec helper. 2. run bundle 3. bundle exec rspec