Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Sagepay VSP Server Integration

branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 lib
Octocat-spinner-32 spec
Octocat-spinner-32 .gitignore
Octocat-spinner-32 Gemfile
Octocat-spinner-32 Gemfile.lock
Octocat-spinner-32 LICENSE.txt
Octocat-spinner-32 README.textile
Octocat-spinner-32 release_gem.sh
Octocat-spinner-32 solon.gemspec
README.textile

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

Something went wrong with that request. Please try again.