| Subcribe via RSS

Implementing DataCash 3D Secure with ASP.net

February 23rd, 2010 | 2 Comments | Posted in Geek Speak

This is an article I’ve been meaning to write for a while now…

Mainly, to help others out, who are struggling with the near non-existent documentation provided by Datacash, when trying to plug 3D Secure into my ASP.net application.

I’m sure you’re already familiar with what 3D Secure is, so I won’t go in to too much detail, nor will I go into much detail with regards to the process.

The purpose of this article is to get you familiar with how to send the details to the DataCash MPI, receive a response, and display and use the 3D Secure window in an iFrame (or framed window for that matter)

Please note:
This code is for tutorial purposes only. It’s dirty, crude, buggy, and not refactored in any way shape or form.
I simplified the code down to bare bones, in order to better explain each element.
Obviously, it comes without warranty… it should work as expected, but has no logging, error trapping or any of that good stuff… and when dealing with online payments, you really should take more care!

Download

zip-icon

I’ve made the entire source code for this tutorial available for download.
You will need to edit the web.config to add your own VTID and Password

 

 

Donate

btn_donate_SMI don’t normally do this, but this article took me literally months to pull together, so any small contribution would be gratefully received!


Payment Page

This is the functionality that processes payment – for example, allows the customer to enter their credit card details etc…

The mark-up is fairly straight forward.

Two things to note are the body, and the form tag:

<body id="myBody" runat="server">

Note that the body tag is runat server

<form id="MainForm" runat="server">

Note that  the name is “MainForm” – we will need this later.

Since in this tutorial, I’m showing how to use an iFrame, I have this bit of code, below the payBtn:

<asp:Panel ID="ACSFramePanel" runat="server" Visible="false">
    Please verify.....<br />
    <br />
    <iframe src="" name="ACSFrame" width="450" height="400" frameborder="0" />
</asp:Panel>

Line 4 of the above is basically where the 3D Secure frame will appear.

I wrapped it in a Panel, so that we can hide it, while the customer enters their card details etc…

Now, let’s look at some of the code-behind:

private Config config;
private Agent agent;

//this would be our order id / reference in production
private string ourReference = Guid.NewGuid().ToString("n").Substring(0, 8);

protected void Page_Load(object sender, EventArgs e)
{
    config = new Config(AppDomain.CurrentDomain.BaseDirectory + "datacash.conf"); // would probably need to come from web.config
    agent = new Agent(config);
}

In the above code, Config and Agent are both DataCash objects.

In the Page_Load event, we basically set these two objects up.

config is set from an xml file (located in the project root) called datacash.conf – this can of course be located anywhere you like (and called anything you like)

agent is then set using the defined config object.

The config file looks like this:

<Configuration>
  <logfile>datacash.log</logfile>
  <logging>5</logging>
  <Obscure>
    <element>Transaction.CardTxn.Card.pan</element>
    <element>Authentication.password</element>
    <element>Transaction.CardTxn.Card.Cv2Avs.cv2</element>
  </Obscure>

  <!--this would be for the live server-->
  <!--<host>https://mars.transaction.datacash.com/Transaction</host>-->

  <host>https://testserver.datacash.com/Transaction</host>
  <port>443</port>
  <timeout>90</timeout>
  <setstrict>true</setstrict>
</Configuration>

Of course, this doesn’t have to be done in Page_Load – in “the real world” this would all be part of a payment helper class. But for demo purposes, it’s fine in the Page_Load – just means we have to repeat ourselves on other pages…

Next, we assume the user fills out the form, and presses the Pay button.

Sending the request to DataCash

protected void payBtn_Click(object sender, EventArgs e)
{
    //so they don't press the button twice.
    payBtn.Enabled = false;

    //get browser info (for 3d secure stuff)
    var browser = Request.Browser.Browser;

    var request = buildAuthDataCashDocument(
        ourReference,
        cardNumber.Text,
        expiryMonth.Text,
        expiryYear.Text,
        startMonth.Text,
        startYear.Text,
        issueNumber.Text,
        secCode.Text,
        billingAddress1.Text,
        billingAddress2.Text,
        billingAddress3.Text,
        billingAddress4.Text,
        billingAddressPostCode.Text,
        browser);

    //send the request document to the agent.
    //todo: could implement some kind of error trapping / retry here
    var authResponse = agent.send(request);

    //get the datacash transaction reference (just in case we need to try authorizing the payment without 3D Secure)
    var datacashRef = authResponse.get("Response.datacash_reference");

    //get the response code
    var responseCode = authResponse.get("Response.status");

This code basically generates a XML document with the payment authorisation request, and submits it to DataCash.

I refactored buildAuthDataCashDocument as it was pretty large, and made the code a bit too dirty, even for this demo!

Couple of bits of note within buildAuthDataCashDocument are:

request.set("Request.Authentication.client", ConfigurationManager.AppSettings["DataCashVtid"]);
request.set("Request.Authentication.password", ConfigurationManager.AppSettings["DataCashPassword"]);

This basically sets the VTID and Password from config (in our case, web.config) which looks like:

<appSettings>
   <add key="DataCashVtid" value="99******"/>
   <add key="DataCashPassword" value="bK*******"/>
</appSettings>

Also, within buildAuthDataCashDocument is:

request.set("Request.Transaction.TxnDetails.ThreeDSecure.verify", "yes");
request.set("Request.Transaction.TxnDetails.ThreeDSecure.merchant_url", "www.crocus.co.uk"); //or whatever brand??
request.set("Request.Transaction.TxnDetails.ThreeDSecure.purchase_desc", "Items from Crocus"); //or some other short sumary
request.set("Request.Transaction.TxnDetails.ThreeDSecure.purchase_datetime", DateTime.Now.ToString("yyyyMMdd HH:mm:ss"));

This is important, for 3D Secure – Fairly self explanatory.

After building our request Document, we need to send it to DataCash, and get another Document back – as a response.

To do that, it’s as simple as:

var authResponse = agent.send(request);

This sets authResponse to the response Document.

We can then use the .get() method on authResponse to retrieve elements from it.

We are most interested in Response.datacash_reference and Response.status

The datacash_reference is unique to each and every transaction processed, so it is useful for later things like refunds, reporting etc…

Response.status is the DataCash status code for this transaction.

It’s from this, that we determine how to proceed.

switch (responseCode)
{
    //handle 3DS error responses....`
    //Basically, if it's one of these, the transaction is screwed, and shouldn't proceed....
    case "151": 		//	3DS Invalid Transaction type
    case "152": 		//	3DS Manual Authorization not supported
    case "153": 		//	3DS verify element missing
    case "154": 		//	3DS Invalid verify value
    case "155": 		//	3DS field missing
    case "156": 		//	3DS Invalid Browser.device_category
    case "157": 		//	3DS Merchant not enabled
    case "159": 		//	3DS No VERes from DS
    case "160": 		//	3DS Invalid VERes from DS
    case "161": 		//	3DS call auth centre

        //log the error here
        break; //- throw them out of transaction process. Should redirec

    case "56": //speed limit - too many transactions on that card number in short space of time
        break;

    //3ds payer verification required....
    case "60":
    case "150":
        show3DSIframe(authResponse);
        Response.Clear();
        break;
}

I won’t go in to too much detail, as the comments kind of speak for themselves…

Basically, if you get a 60 or 150 then the transaction requires you to show the 3D Secure window.

Showing the 3D Secure iFrame

I’ll spend a bit of time on show3DSIframe, as this is the part that caused me the most hassle – How to actually display the ACS in an iFrame, especially in ASP.net

private void show3DSIframe(Document doc)
{
    ACSFramePanel.Visible = true;

    //this is the data cash reference number for this transaction
    var dataCashReference = doc.get("Response.datacash_reference");

    //this is the url of the ACS - the page generated by the bank, that contains the
    //boxes where the customer enters information etc...
    var acsUrl = doc.get("Response.CardTxn.ThreeDSecure.acs_url");

    //this is a long message / code that is generated for the transaction
    var pareq = doc.get("Response.CardTxn.ThreeDSecure.pareq_message");

Ok, so in the markup from Part 1, you may remember I wrapped the iFrame in a Panel, called ACSFRamePanel. Now, we need to set the visibility to true.

These first few lines are self explanatory – we need to get the dataCashReference, acsUrl (the URL of the 3D Secure page – this is usually returned by the customers issuing bank) and the pareq – this is a long string, that’s a bit like a password for the transaction.

These elements are retrieved from the passed in DataCash Document.

We then need to create our Term URL – This is basically a URL that the 3D Secure window POSTs back to:

var termUrlPrefix = Request.ServerVariables["HTTPS"] == "ON" ? "https://" : "http://";

//termUrl is where the ACS page posts back to.
var termUrl = string.Format("{0}{1}",
    termUrlPrefix,
    Request.Url.Authority + "/3DSResponse.aspx");

In our demo case, it’s just on our local host machine – this could however, for example, be something like www.myUrl.com/checkout/3DSresponse.aspx

This next part is the important part.

This set’s the required hidden fields (PaReq and TermUrl) on our form.

We then register another hidden form – “MD” – This is our order number / reference for this transaction – so we can retrieve it from our database, and update the status when we come out the other side of 3D Secure.

The next line generates a little bit of JavaScript that basically causes this form to submit itself to the acs url.

The result of this, is then output to the target (parameter 3 in our string.format) – ACSFrame.

Remember we called our form “MainForm” :-)

Then, myBody.Attributes.Add inserts the resulting JavaScript to the onLoad function of our body tag.

Remember we made runat server earlier

//ClientScript.RegisterHiddenField adds a hidden field to the form...
ClientScript.RegisterHiddenField("PaReq", pareq);
ClientScript.RegisterHiddenField("TermUrl", termUrl);

//this is the data cash reference, and our reference -
//so we can update the order on the other side of the verification (paid, failed etc...)
ClientScript.RegisterHiddenField("MD", dataCashReference + "|" + ourReference);

var js = string.Format("javascript: document.{0}.action='{1}'; document.{2}.target=\"{3}\"; document.{4}.submit();",
    MainForm.ID,
    acsUrl,
    MainForm.ID,
    "ACSFrame",
    MainForm.ID
);

//since the body tag of this page is called myBody, and is runat=server, we can access
//it here, and inject our javascript.
myBody.Attributes.Add("onLoad", js);

And there we have it – the 3D Secure window is displayed in an iFrame

I’ll edit this post with a link to 3DSResponse.aspx when I finish that article…

Hope this helps! Feel free to ;-)

btn_donate_SM

VN:F [1.8.2_1042]
Rating: 8.0/10 (1 vote cast)
VN:F [1.8.2_1042]
Rating: 0 (from 0 votes)
Tags: , , ,

Creating a new post in Wordpress using the JoeBlogs library

October 12th, 2009 | 5 Comments | Posted in Development

A few people have recently been asking how the NewPost method works within JoeBlogs

First, you need to create an instance of Post.

Then, set the following properties:

dateCreated
Fairly self explanatory, but you should set this to today’s date (or whatever date you wish the post to be set as published)

title
The title of the post

description
The body of the post.
This can of course contain HTML

categories
This is a string array of categories to associate with the post

mt_keywords
Another string array, representing the tags for the post

Then, using your presumably already instantiated Wrapper class, you can call the NewPost method, which takes the above Post object as a parameter, and a boolean – indicating if the post is to be set as published. Note – if this is set to false, the post is set in draft mode.

Here’s some sample code:

//create a new post
var post = new Post();

//since this is a struct, we can't have a constructor that does this!
post.dateCreated=DateTime.Now;
post.title="This is a title";
post.description="this is the body of the post. it <strong>could</strong> be html.";

//create the post!
wp.NewPost(post,true);

Hope this helps!

VN:F [1.8.2_1042]
Rating: 0.0/10 (0 votes cast)
VN:F [1.8.2_1042]
Rating: 0 (from 0 votes)
Tags: , ,

NVelocity template with decimal to two decimal places

October 2nd, 2009 | No Comments | Posted in Development

I wanted to be able to output a decimal value from an object in my NVelocity template.

For example, the value of the decimal was: 3.40000 to represent 3.4.

The end result:

Total Order Value: 3.40 GBP

I needed to display this as currency format. Sure, I could of used this String.Format method, but that adds £ or $ or whatever (depending on your environment setup) to the start of the resulting string.

In the end, I used ToString with “N2” formatter.

So, in my nVelocity template, here is what I would use:

Total Order Value: $Order.GrandTotal.ToString("N2") GBP

There are some other alternatives discussed here:
http://stackoverflow.com/questions/1048643/format-a-double-value-like-currency-but-without-the-currency-sign-c

VN:F [1.8.2_1042]
Rating: 0.0/10 (0 votes cast)
VN:F [1.8.2_1042]
Rating: 0 (from 0 votes)
Tags: ,

C# Ternary conditional operator – Question mark in an if statement

September 9th, 2009 | No Comments | Posted in Development

I’m all for shortening the amount of code we write, if it makes it more readable.

One enhancement I make use of in C# is the ternary conditional operator.

Consider the following block of code:

            string input = "hi";
            bool saidHello;

            if (input = "hello")
            {
                saidHello = true;
            }
            else
            {
                saidHello = false;
            }

In this example, we can see, the saidHello boolean value would be false,  as the input string actually said ‘hi’ rather than hello.

This could be shortened down to a single line like so (or 2, if we count the input initialisation)

            string input = "hi";
            var saidHello = input == "hello" ? true : false;

This makes use of the ternary operator – the ? (and of course var – as explained in another article)

the ? acts as a ‘then’ and the : acts as an else…

So you can think of it as something like below:

saidHello equals (if input equals hello then) true, else false.

These can of course be nested, as explained in this article, however in my opinion, this kind of defeats the whole point of this from my point of view – to increase readability of code.

VN:F [1.8.2_1042]
Rating: 0.0/10 (0 votes cast)
VN:F [1.8.2_1042]
Rating: 0 (from 0 votes)
Tags: ,

JoeBlogs – Typo in dummy URL fixed

August 31st, 2009 | 1 Comment | Posted in Development

I’ve recently had a few comments that the JoeBlogs wrapper wasn’t working – and they were getting an “invalid response” or more specifically “Response XML not valid XML-RPC – missing methodResponse element.”

It seems that I had a typo in my comments in JoeBlogs.TestHarness/Program.cs on line 15:

//typically http://www.yourdomain.com/xmlprc.php (if your wordpress blog is installed in root dir)

The problem, is the xmlprc.php – this should be xmlrpc.php.

Those of you that copy pasted / uncommented out that line, would be executing requests against a file that didn’t exist on your server!

I’ve changed this typo, and committed to Codeplex: revision #27138

Thanks to Felix for pointing this out!

VN:F [1.8.2_1042]
Rating: 0.0/10 (0 votes cast)
VN:F [1.8.2_1042]
Rating: 0 (from 0 votes)
Tags: ,

Reverse GeoCoding with Google API and C#

August 9th, 2009 | 1 Comment | Posted in Development

I needed to write a function to get a set of co-ordinates from an address, supplied as a string.
Using the Google API, I came up with this:

First, I created a struct, to represent the lat and lon co-ordinates:

public struct Coordinate
{
	private double _latitude;
	private double _longitude;

	public Coordinate(double latitude, double longitude)
	{
		_latitude = latitude;
		_longitude = longitude;
	}

	public double Latitude { get { return _latitude; } set { _latitude = value; } }
	public double Longitude { get { return _longitude; } set { _longitude = value; } }
}

This is a simple representation of a set of latitude and longitude co-ordinates.

The main method, which returns the above Coordinate struct is:

public static Coordinate GetCoordinates(string address)
{
	using (var client = new WebClient())
	{
		Uri uri = YOUR_URI_HERE;

		/* The first number is the status code,
		* the second is the accuracy,
		* the third is the latitude,
		* the fourth one is the longitude.
		*/
		string[] geocodeInfo = client.DownloadString(uri)		.Split(',');

		return new Coordinate(
			Convert.ToDouble(geocodeInfo[2]),
			Convert.ToDouble(geocodeInfo[3]));
	}
}

On line 5, you need to specify your Google API URL, in the format of:

http://maps.google.com/maps/geo?q=ADDRESS GOES HERE&output=csv&key=YOUR KEY HERE

And that’s it!

Obviously, there are usage restrictions when using the Google API for this, as outlined on the documentation pages

VN:F [1.8.2_1042]
Rating: 0.0/10 (0 votes cast)
VN:F [1.8.2_1042]
Rating: 0 (from 0 votes)
Tags: , ,

Using JoeBlogs -metaWeblog API Wrapper

July 7th, 2009 | 22 Comments | Posted in Blogging, Geek Speak, Web

Step 1

Download the latest release of JoeBlogs from Codeplex
(Click the Downloads tab, and select the latest download)

Step 2

Unzip the contents of the downloaded zip file.
(I usually copy DLLs I am going to use into a “lib” folder at the root of my solution)

Step 3


Add a reference to BOTH CookComputing.XmlRpcV2.dll AND JoeBlogs.dll

image

Include AlexJamesBrown.JoeBlogs in your class, with using / Imports:

C#

using AlexJamesBrown.JoeBlogs;

VB

Imports AlexJamesBrown.JoeBlogs

Step 4:

Instantiate a new Wrapper object.

So far, the available wrappers are:

MetaWeblogWrapper

WordPressWrapper

C#

WordPressWrapper wrapper = new WordPressWrapper(Url, Username, Password);

VB

Dim wrapper As New WordPressWrapper(Url, Username, Password)

Please note -

The above example has the Url, Username and Password strings omitted. Simply replace them with the relevant information.

Step 5

You can now take full advantage of all the methods on your instantiated wrapper.

C#

string Url = "www.alexjamesbrown.com";
string User = "MyUser"; //enter your username
string Password = "MyPassword"; //enter your password

var wp = new WordPressWrapper(Url, User, Password);

//a few test functions...
var userBlogs = wp.GetUserBlogs();
var tags = wp.GetTags();
var categories = wp.GetCategories();

var authors = wp.GetAuthors();
VN:F [1.8.2_1042]
Rating: 9.5/10 (6 votes cast)
VN:F [1.8.2_1042]
Rating: 0 (from 0 votes)
Tags: , , ,

Why Are Interfaces Useful?

July 6th, 2009 | No Comments | Posted in Development

Interfaces, usually prefixed by “I” are useful in software engineering, for a number of reasons.
Primarily, they allow you to create “pluggable” code.
By this, I mean that your code is easier to manage, easier to maintain, easier to change the way certain parts of your application work, without changing the entire way it works.

Lets look at an example.

Our application is an photo sharing web application, allowing users to view and upload photos.
The main functions of the site, are handled by our ImageManager class. This facilitates the retrieval, and storing of images. Fairly integral to the application.

As we are designing our application using SoC (Separation of Concerns), all our ImageManager class will do, is get, or put images. (in reality, we should really have a separate concern for each operation – get & put)
It doesn’t care about storing user details or meta data, against the image in a database, nor does it care about checking to see if a user can view that image or anything like that.
It purely puts, or gets, an image.

Now. this operation sounds simple, to get an image, I just need to connect to my file store, and retrieve my image?
Yes. But what if, one day, you need to be able to switch where you store your images.
For example..on day 1, you are storing your images on the file system, in a directory within your web application project.
This works fine, especially during beta testing, however as demand grows, the strain on your server and your bandwidth become a problem, and you need to change where images are stored.

You also want to be able to use a storage service, such as Amazon S3 to store your images.
(I’m not going to go into how S3 or any other service works in this article, it’s just an example.)

Luckily, we have a couple of ImageManagers….
FileSystemImageManager, and S3ImageManager

Both of which, implement our interface – IImageManager (notice the prefix "I”)

Here’s the interface:

C#

public interface IImageManager
{
  void PutImage(Image image, string fileName);
  Image GetImage(string fileName);
}

VB

Public Interface IImageManager
  Sub PutImage(ByVal image As Image, ByVal fileName As String)
  Function GetImage(ByVal fileName As String) As Image
End Interface

Note the empty method signatures…

Interfaces contain no logic – they are simply a contract that each implementation agrees to follow.

So lets have a look at an implementation of IImageManager:

C#

public class FileSystemImageManager : IImageManager
{
  public void PutImage(Image image, string fileName)
  {
    //code to save the image on the file system
  }

  public Image GetImage(string fileName)
  {
    //code to get the image from the file system
  }
}

VB

Public Class FileSystemImageManager
    Implements IImageManager
    Public Sub PutImage(ByVal image As Image, ByVal fileName As String)
        'code to save the image on the file system
    End Sub

    Public Function GetImage(ByVal fileName As String) As Image
        'code to get the image from the file system
    End Function
End Class

The FileSystemImageManager implements IImageManager (indicated by the : in c#)

note, in visual studio, you can right on your interface implementation declaration and select Implement Interface – this will create the required method signatures within your class.

Implement Interface

Our class now conforms to IImageManager.

But – there’s no code! That doesnt technically matter.

As long as you’ve implemented each method (even by throwing a NotImplementedException), your class will compile.

So what’s the point?

Well, we now have a FileSystemImageManager class, that (after you’ve added your code) gets and puts images on the file system.

In our application, we can do something like:

C#

      //get the image:
      IImageManager imageManager = new FileSystemImage();
      var image = imageManager.GetImage("my_filename");

VB

'get the image:
Dim imageManager As IImageManager = New FileSystemImage()
Dim image = imageManager.GetImage("my_filename")

“Great” you say….

But that means you can “swap out” your functionality, really easily.

Instead of declaring imageManager as a new FileSystemImage() we could have another class, that implements IImageManager called “FlickrImage”

This could then use Flickr to get and put images (obviously additional functionality would be required to authenticate against Flickr API)

I’ll expand this tutorial at a later stage to explain how to effectively use config, factory patterns, IoC etc…

VN:F [1.8.2_1042]
Rating: 0.0/10 (0 votes cast)
VN:F [1.8.2_1042]
Rating: 0 (from 0 votes)
Tags: , , ,

Specify XML-RPC Endpoints at Run Time With CookComputing XMLRPC.net

July 6th, 2009 | 1 Comment | Posted in Uncategorized

A common question when using Cook Computing XML RPC.net to talk to blogs etc… is how to specify a blog / endpoint at runtime? Most of the examples seem to specify the details in an attribute… not much use if you’re trying to develop a wrapper.

To enable me to create JoeBlogs, I made use of the IXmlRpcProxy Proxy classes…

I created my interface, implementing IXmlRpcProxy:

We define the method as an XML RPC method using XmlRpcMethod.
The name of the method is also specified.
In this case, we are using getPost from the metaWeblog XML RPC API – Wordpress supports this, as well as the Blogger API and the Movable Type API.
They also have their own (which is an extension to the Moveable Type API) –

http://codex.wordpress.org/XML-RPC_wp

Anyway…..

As per the metaWeblog.getPost specification, we need to pass in the postid, username, and password. So we create our interface as follows:

	public interface IMyProxy: IXmlRpcProxy
	{
		[XmlRpcMethod("metaWeblog.getPost")]
		Post GetPost(string postid, string username, string password);
  	}

As we can see, this returns a “Post”

This is a struct, that basically defines the structure of the response.

	[XmlRpcMissingMapping(MappingAction.Ignore)]
	public struct Post
	{
		public DateTime dateCreated;
		public string description;
		public string title;
		public string postid;
		public string[] categories;

		public override string ToString()
		{
			return this.description;
		}
	}

To use this,  you need to create an instance of the proxy object.

XML RPC.net provides a method called XmlRpcProxyGen to do this for us.

We then set the Url, which is declared as part of IXmlRpcProxy (remember IMyProxy implemented this interface…)

Since our proxy declared the method GetPost, we can now use this, pass in the required post id, username and password.

    public void myTest()
    {
      string postId = "1234";
      string username = "myUsername";
      string password = "password";

      var proxy = (IMyProxy)XmlRpcProxyGen.Create(typeof(IMyProxy));
      proxy.Url = "www.alexjamesbrown.com";

      var post = proxy.GetPost(postId, username, password);
    }

And there we have it.

Of course this is a very bare bones example.

JoeBlogs contains much more separation of these concerns.

VN:F [1.8.2_1042]
Rating: 0.0/10 (0 votes cast)
VN:F [1.8.2_1042]
Rating: 0 (from 0 votes)
Tags: , , ,

C# WordPress Wrapper

May 22nd, 2009 | 5 Comments | Posted in Blogging, Geek Speak, Web

Ok, I’ve barely finished this as a stable release, but I thought I’d post this up any way….

A little open source project I’m working on – Joe Blogs.

In a nutshell, it allows easy communication to your WordPress (or other blog) via an xml-rpc interface.

Big thanks to the work by Charles Cook at http://www.xml-rpc.net/

Joe Blogs is currently hosted on CodePlex – http://joeblogs.codeplex.com/

I’ll be posting more info, tutorials, documentation etc… in the coming days and weeks.

Update

See here for usage instructions:
http://www.alexjamesbrown.com/geek/using-joeblogs/

VN:F [1.8.2_1042]
Rating: 10.0/10 (1 vote cast)
VN:F [1.8.2_1042]
Rating: 0 (from 0 votes)
Tags: , , , , ,