Using SagePay with ASP.NET MVC

Yesterday I posted about the SagePay .NET integration kit and how frustrating it was to try and integrate this into an ASP.NET MVC-based application.

Rather than wasting the time converting their sample code from VB to C#, removing the MySql references and de-coupling it from WebForms we decided it would be quicker to write our own implementation of SagePay's VSP Server protocol.

This is now available in the form of the SagePayMvc library on GitHub and released under the Apache 2 license.

Great! How do we use it?

The first thing you'll need to do is download the source from github and build the solution. Once we've given the project some more testing I'll create a binary release, but for now you have to compile it yourself.

Once compiled, add a reference to SagePayMvc.dll to your project.

Configuration

Next, you'll need to add the relevant configuration section to your web.config:

<configuration>
	<configSections>
		<section name="sagePay" type="System.Configuration.NameValueSectionHandler"/>
	</configSections>
	<sagePay>
		<add key="NotificationHostName" value="my_site_hostname.com"/>
		<add key="VatMultiplier" value="1.15" />
		<add key="VendorName" value="MyVendorName"/>
		<add key="Mode" value="Simulator" />
	</sagePay>
        ...
</configuration>

The above four settings are required:

  • NotificationHostName - The public-facing hostname that SagePay should use when sending HTTP posts containing payment notifications (the controller and action that should be posted to will be generated using the ASP.NET routing infrastructure - the default controller name is PaymentResponse and the default action is Index. These can be overriden).
  • VatMultiplier - The multiplier to use when calculating VAT.
  • VendorName - Your SagePay Vendor name.
  • Mode - The server that you will be connecting to (Simulator, Test or Live).

There are are additional settings too, but these are not required. At some point in the next couple of weeks I'll be putting together some more detailed documentation on all the settings.

The configuration can also be set up programatically by calling Configuration.Configure and passing in an instance of the Configuration class.

Registering a Transaction

When you are ready to make a transaction, you need to call the Send method on the TransactionRegistrar object. This takes several parameters - the current RequestContext (used to generate URLs), a "vendor TX code" (SagePay's name for a unique transaction id), an instance of a ShoppingBasket object, an the customer's email address and two instances of the Address object (one for shipping, one for billing).

This will then build the HTTP POST in the format required by SagePay's protocol, send it and then deserialize the response into a TransactionRegistrationResponse object which contains information on whether the transaction registration succeeded etc. The code from our system looks something like this:

public class TransactionService : ITransactionSerivce {
	private readonly ITransactionRegistrar transactionRegistrar;
	private readonly Configuration configuration;
 
	public TransactionService(ITransactionRegistrar transactionRegistrar, Configuration config) {
		this.transactionRegistrar = transactionRegistrar;
		this.config = config;
	}
 
	public TransactionRegistrationResponse SendTransaction(Order order, Customer customer, RequestContext context) {
		var basket = new ShoppingBasket("Shopping basket for " + customer.Name);
 
		//fill the basket
		foreach(var orderDetail in order.OrderDetail) {
			var basketItem = new BasketItem(
				orderDetail.Quantity, orderDetail.Product.Description, 
				orderDetail.Product.Price, config.VatMultiplier);
 
			basket.Add(basketItem);
		}
 
		var address = new Address { 
			Surname = customer.Surname,
			Firstnames = customer.Forename,
			Address1 = customer.Address.Property,
			Address2 = customer.Address.District,
			City = customer.Address.PostalTown,
			Country = customer.Address.Country,
			Phone = customer.Address.Telephone,
			PostCode = customer.Address.Postcode
		};
 
		//Always specify same address for billing/shipping as there is no physical shipping of products
 
		var response = transactionRegistrar.Send(context, order.OrderId, basket, address, address, customer.Address.Email);
 
		if(response.Status != ResponseType.Ok) {
			string error = "Transaction {0} did not register successfully. Status returned was {1} ({2})";
			error = string.Format(error, order.OrderId, response.Status, response.StatusDetail);
			throw new TransactionRegistrationException(error);
		}
 
		//Update properties on the order object
		order.SecurityKey = response.SecurityKey;
		//etc, etc
 
		return response;
	}
}

Note that the ITransactionRegistrar is DI friendly, although use of a container is not required.

Receiving the Response

After the customer has then entered his/her payment details on the SagePay site, SagePay will send a response back to the NotificationUrl. This is generated from the configuration based on the NotificationHostName, NotificationController and NotificationAction. Our notification controller looks something like this:

public class PaymentResponseController : Controller {
	IRepository repository;
	Configuration configuration;
 
	public PaymentResponseController(IRepository repository, Configuration configuration) {
		this.repository = repository;
		this.configuration = configuration;
	}
 
	[AcceptVerbs(HttpVerbs.Post)]
	public ActionResult Index(SagePayResponse response) {
 
		if(string.IsNullOrEmpty(response.VendorTxCode)) {
			return new ErrorResult();
		}
 
		var order = repository.FindById<OrderHeaderEntity>(response.VendorTxCode);
 
		if(order == null) {
			return new TransactionNotFoundResult(response.VendorTxCode);				
		}
 
		//*Snip* Update the order instance with the values on the SagePayResponse object. 
 
		if(! response.IsSignatureValid(order.SecurityKey, configuration.VendorName)) {
			return new InvalidSignatureResult(response.VendorTxCode);
		}
 
		return new ValidOrderResult(order.OrderId, response);
	}
}

Note that the a custom IModelBinder implementation (part of SagePayMvc) is responsible for taking the post data and deserializing it into a SagePayResponse object which is available to the controller action as an argument. We're also using several custom ActionResult classes that are part of the SagePayMvc library. These deal with generating the correctly formatted response that the SagePay protocol requires. In SagePay's own kit, you have to manually write out the correct data to the response stream.

If you're thinking of using SagePay to handle payments from an ASP.NET MVC application, feel free to check out the SagePayMvc code from github.

Written on September 27, 2009