With PayPal, I can create a BuyNow button pretty easily, containing my merchant info, price, tax, shipping, etc. Is this possible in SagePay?
The desired system is called SagePay Form as far as similarity to PayPal's BuyNow button + PDT process. First, you need to create a FORM like so:
<form action="https://live.sagepay.com/gateway/service/vspform-register.vsp" method="POST" id="SagePayForm" name="SagePayForm">
<input type="hidden" name="VPSProtocol" value="2.23" />
<input type="hidden" name="TxType" value="PAYMENT" />
<input type="hidden" name="Vendor" value="<?= $YOUR_VENDOR_LOGIN_NAME ?>" />
<input type="hidden" name="Crypt" value="<?= $PAYMENT_CRYPT ?>">
<input type="image" src="images/buynow-sagepay.png" />
</form>
(One can swap the live URL with a test one here: https://test.sagepay.com/gateway/service/vspform-register.vsp)
As for the $PAYMENT_CRYPT, you have to first create a string like so:
VendorTxCode=406227821909
&Amount=32.00
&Currency=USD
&Description=1 ACME Widget
&SuccessURL=http://example.com/success.php
&FailureURL=http://example.com/fail.php
&BillingSurname=Smith
&BillingFirstnames=John
&BillingAddress1=123 Main Street
&BillingCity=Anywhere
&BillingPostCode=29555
&BillingCountry=USA
&DeliverySurname=Smith
&DeliveryFirstnames=John
&DeliverAddress1=123 Main Street
&DeliveryCity=Anywhere
&DeliveryPostCode=29555
&DeliveryCountry=USA
It's kind of dumb why they need some of this information when PayPal doesn't, but oh well. The docs clearly say that the thing will error out if they don't receive legitimate values like a real postal code that validates for that city and country, and is also used for problem dispute arbitration.
Note in my example that there's no tax or shipping breakout as PP has, so you'll need to display that as necessary before showing this BuyNow buton, or perhaps on your confirmation page or confirmation email you send. They do have a "&Basket=" parameter where the tax can be specified, but it's redundant to what you can display on your form page yourself and not necessary. Therefore, the &Amount value must be the gross value, not net. There is also no quantity value (which the &Basket parameter could be used to specify). More than likely you'll find the &Basket parameter just redundant to what you can already display on your own form during the checkout process. So, that's why my example didn't include it.
As for the VendorTxCode, that's something you create so that you can track the order back to the appropriate customer.
This $PAYMENT_CRYPT is then encrypted using XOR + Base64 encoding, using the encryption password provided by SagePay. They have an AES + Binhex encryption option, but it's overkill and your server has to have the mcrypt library enabled. Some shared hosting plans STILL don't have the enabled yet!
They provide an XOR example, but it's the typical one you see countless times in Computer Science courses where you repeatedly loop through each ASCII code of the password and each ASCII code of the data and take the complement bit of the other (the XOR process). Once done, feed it through Base64 encoding for safe POST transfer. The Base64 encoding uses the built-in function from PHP.
The response from this is more like PayPal's PDT process than PayPal's IPN process. They do direct someone to success.php and fail.php along with an encrypted URL response via GET that you can unencrypt and parse (base64 decode + XOR), but the difficulty is that the customer can close the form before waiting for the page to redirect. In that case, one will see this in their control panel in SagePay and have to fulfill the transaction manually for the customer.
On the success.php and fail.php it's up to you what you want to do. Once the query string &crypt parameter is unencrypted, you'll be able to parse out whether the transaction is complete or not by looking at Status parameter being "OK".
Note that you don't have to go direct to success.php. You can make it be like success.php?custom=value to pass extra information on the transaction that you can parse. Their code will automatically figure this out and tack on the &crypt= parameter on the end. The same goes with fail.php.
They do have ways to make SagePay send emails to the customer and to the vendor, but it's really overkill because you can do the same thing in your own PHP code with the mail() statement when doing the order.
REFERENCE: Note that the following doc URL may change in the future. To get the latest version of the doc, visit the website, enroll as a developer (a 1 minute process), and search on "form protocol".
http://www.sagepay.com/sites/default/files/downloads/sagepayformprotocolandintegrationguidelines_0.pdf
EDIT: New Link (25th Oct 2017) - https://www.sagepay.co.uk/file/25041/download-document/FORM_Integration_and_Protocol_Guidelines_270815.pdf