As it stands, there’s no way for the user to actually send the funds for their orders. This is somewhat problematic and wouldn’t make for a very good ecommerce site! Fortu- nately, the Lift module that you added to the project dependency earlier in this chap- ter provides out-of-the-box support for the online payment service provider PayPal.
The integration supports the two most common forms of electronic payment used by PayPal: Payment Data Transfer (PDT) and Instant Payment Notification (IPN).
NOTE You can read more about PDT and IPN on the PayPal site. PDT is dis- cussed at https://mng.bz/3gvM and IPN is covered at https://mng.bz/5tOy.
The PayPal integration means you don’t have to set up your own dispatch functions for handling the responses from PayPal and parsing their postback to your applica- tion. Typically, all you need to do is create a handler object that implements methods required by the PaypalIPN and PaypalPDT traits, and hook up the dispatchers sup- plied by those traits in your application boot.
5.3.1 Environment setup
Before we cover the PayPal implementation, there are several things you must know about PayPal and their developer process. First, you’ll need to register yourself on developer.paypal.com. Then you should do some background reading on PDT and IPN. The online documentation is very comprehensive, and as this is a book about Lift, and not about PayPal, we won’t repeat it here other than to touch on some high- level configuration instructions.
With your account created and logged in to developer.paypal.com follow the steps in table 5.1.
Table 5.1 Process of configuring the PayPal sandbox environment
Configuring your PayPal sandbox
Action Result
1 Set up both a “preconfigured seller”
and “preconfigured buyer” account in the sandbox.
PayPal generates random account names and allows you to log in as those “people.”
Now that your environment is set up, you need to wire up the Lift side of things, or the communications from PayPal will disappear into the ether. As I mentioned, there are two main traits in the PayPal package to take responsibility for the two supported trans- action systems: PaypalIPN and PaypalPDT. In this example, let’s create a handler that implements these traits called PaypalHandler with the implementation shown in the following listing
import net.liftweb.common.{Loggable,Full,Box,Empty,Failure}
import net.liftweb.paypal.{PaypalIPN,PaypalPDT, PaypalTransactionStatus,PayPalInfo}
import net.liftweb.http.DoRedirectResponse import net.liftweb.mapper.By
import example.travel.model.{Order,OrderStatus}
object PaypalHandler extends PaypalIPN with PaypalPDT with Loggable { import PaypalTransactionStatus._
val paypalAuthToken = "yourtokengoeshere"
def pdtResponse = {
case (info, resp) => info.paymentStatus match { case Full(CompletedPayment) =>
DoRedirectResponse.apply("/paypal/success") case _ =>
DoRedirectResponse.apply("/paypal/failure") }
}
def actions = {
case (CompletedPayment,info,_) =>
updateOrder(info,OrderStatus.Complete) case (FailedPayment,info,_) =>
updateOrder(info,OrderStatus.Failed) case (status, info, resp) =>
} 2 Log into the sandbox as the seller account
you just created, and enable Auto Return in the selling preferences. The PayPal documentation can be found here:
http://mng.bz/M8NJ.
Enabling Auto Return ensures that the user is sent back to the application when their transaction is completed. Your autoreturn URL will now be http://
YOUREXTERNALHOST/paypal/pdt.
3 Enable PayPal Data Transfer (it’s on the same screen as the Auto Return configuration).
Enabling PDT will ensure that when the transaction completes, PayPal passes back a few details about the order, such as the transaction token.
4 Edit the IPN callback URL and enable IPN.
The PayPal documentation can be found here: https://www.paypal.com/ipn.
IPN is an asynchronous call back to your server with all the specific details about a given transaction.
Listing 5.12 The PaypalHandler implementation
Table 5.1 Process of configuring the PayPal sandbox environment (continued)
Configuring your PayPal sandbox
Action Result
PDT token key
B
Successful PDT action
C
Failure PDT action
D
IPN response handlers
E
101 Collecting payment with PayPal
private def updateOrder(info: PayPalInfo,
status: OrderStatus.Value){
Order.find(By(Order.reference, info.itemNumber.map(_.toLong)
➥ .openOr(0L))) match { case Full(order) => order.status(status).save case _ =>
} } }
In short, this code block composes the two PayPal traits supplied by the Lift PayPal integration and implements the required methods to handle the various responses PayPal might provide. It goes without saying, however, that this is only an example. In a real production system, you’d account for a lot more responses and intelligently handle them. This, in comparison, is a mere stub to illustrate the process and ease of implementing ecommerce in Lift.
When you enabled PDT in the PayPal sandbox, you were assigned a security token to use in the PDT request. It needs to be entered in this code so PayPal can know that it’s your application calling back to it for information on the transaction B. In this implementation, PDT serves only to display the correct response screen back to the user; the pdtResponse cases C and D dictate which URL the user should be redi- rected to based on the result of the transaction.
The actions are somewhat more complex E, because when the PayPal servers make the IPN callback to the application, it contains much more data and is generally considered the best way to then update order information with the transaction data.
The updateOrder helper method F will look up an order by the order reference number that was generated when the order was created; then, depending upon the status received from the IPN data, the order has its status set appropriately.
Although this code is functional, it’s currently not wired into the application boot cycle, so it won’t ever be invoked by Lift. In order for Lift to be able to call the PayPal- Handler you need to add the following code within your Boot class:
import net.liftweb.paypal.PaypalRules import example.travel.lib.PaypalHandler class Boot extends Loggable {
def boot { ...
PaypalRules.init
PaypalHandler.dispatch.foreach([LiftRules.dispatch.append(_)) ...
} }
The PaypalRules and PaypalHandler lines of code first initialize the PaypalRules object that contains configuration information used to determine a range of factors about the transaction. For example, it contains a function configuration that lets you dynamically determine which currency should be used when communicating with Find and update order by ref
F
PayPal. In this case, it uses the defaults, so it will select a currency based upon the locale of the JVM running the example. Second, they take the dispatch functions in the PaypalHandler object (inherited from the two Lift PayPal traits), which respond to the IPN and PDT callbacks, and map them into the application using Lift’s dis- patching mechanism. We’ll discuss Lift’s dispatching mechanism in chapter 8.
5.3.2 The Buy Now button
With the backend all wired up and ready to go, you need to supply the user with a sim- ple one-click button to instantiate the transaction process in the familiar PayPal way: a bright orange button! In Lift’s PayPal support there’s a mechanism for automatically generating these buttons. You simply need to implement the BuyNowSnippet trait into one of your snippet classes and populate the required methods so it knows the value and other metadata you wish to send to PayPal.
Earlier in the chapter, listing 5.10 detailed the OrderSummary snippet that com- puted the overall value of the order and presented the user with a rundown of what they were going to purchase. As this already has the data we need for the button, the simplest approach is to just compose the BuyNowSnippet with the existing class. The next listing shows the changes made to the class.
import net.liftweb.paypal.snippet.BuyNowSnippet class OrderSummary extends BuyNowSnippet { ...
override val values = Map(
"business" -> "me@business.com", "item_number" -> reference, "item_name" -> ("Auction Order: " + reference)) }
As you can see in the listing, very few lines of code are required to implement the but- ton. The main code overrides the Map[String,String] of the key-value pairs that need to be included in the form submission to PayPal B, but these parameters are detailed in the PayPal documentation (https://mng.bz/YnC1).
Finally, ensure that you add the PayPal response pages to the SiteMap in the Boot class:
Menu("Transaction Complete") / "paypal" / "success"
>> LocGroup("public") >> Hidden,
Menu("Transaction Failure") / "paypal" / "failure"
>> LocGroup("public") >> Hidden,
For simplicity’s sake, we’ve populated these two files with a friendly message so the user is aware of the transaction’s outcome. You might also want to customize the pages with customer information about their order; you have everything at your disposal in the PDT and IPN responses supplied by PayPal.
Listing 5.13 Implementing the BuyNowSnippet trait
Configure PayPal fields
B
103 Summary