How PayPal Integration works

PayPal integration in the Web Store is driven through the OrderForm.aspx page which handles the interaction with PayPal as a payment mechanism. The idea is that unlike regular credit card processing in the store, the PayPal processing redirects to the PayPal site from within the Orderform.aspx page. PayPal then processes the page and on completion returns to the Orderform.aspx page.

The following image demonstrates the flow of the process.

This process looks pretty complicated and while there is quite a bit of back and forth, it's a pretty straight forward once you understand the flow. The Web Store delegates most of the processing out of the main order processing logic and uses PayPal in a way that lets PayPal operation work with the existing mechanisms of processing and saving orders.

You need 2 pages on your site to make this work right:

Order Page
This form is the jumping off and return point and in my example it's called OrderForm.aspx. This page needs some internal logic to redirect to PayPal if PayPal is selected as payment. I'll provide you with a helper class that makes this a snap by simply setting a few properties. The same OrderForm.aspx also serves as the return page from PayPal on completion. When you redirect to PayPal, you provide URLs for success and cancelled operations, and these URLs point back to this page with special querystring parameters (for example: OrderForm.aspx?PayPal=Success). I like to use a single page here to keep the PayPal specific logic in one place, but you can also use separate pages.

Unlike credit card processing it's somewhat difficult to keep PayPal logic inside of a business object, since this interaction relies on the HTTP mechanisms and Page callback, so some of the routing logic ends up embedded as part of your Web UI layer (ASPX code behind). The helper class abstracts most of the internal knowledge but some 'operational' code still squeaks into the ASP.NET page.

For my Web Store application it also makes more sense to return to this page in order to complete the order process. The OrderForm can simply pick up right where it left off before redirecting to PayPal. In short, it makes PayPal integration easy with minimal changes to the existing processing.

It's important that you understand that the return from PayPal does not guarantee that the order went through! It's easy to spoof the return URL you sent to PayPal since it's visible on the querystring (or if you POST in the POST buffer). Therefore a user could simply type the Confirmation Url in directly and you should not confirm the order at this point. You can manually check for orders on the PayPal site or wait for PayPal's confirmation emails etc. all of which let you know for sure that the order was processed in a 'manual' way.

Instant Payment Notification Callback Page
To automate this process, PayPal can optionally ping you back at another URL with order completion information. This is where the second IPN Confirmation page shown in Figure 1 comes in. It uses a mechanism called Instant Payment Notification (IPN) which is essentially a Web based callback mechanism that calls a pre-configured URL on your site. IPN must be enabled on the PayPal site and when enabled IPN sends a confirmation to you at this URL after the order was processed. PayPal then expects a return from you within a certain timeframe (a few minutes) and return a response to you to confirm your that the customer has paid. To do this you have to POST the data back to PayPal by echoing back all the form data that PayPal sends to you.

IPN is optional, but it's a requirement if you need to confirm your orders immediately with your customers. For example on the West Wind site we confirm new software purchases by sending an email with download URLs to get the software almost immediately. For credit card transactions we send the email immediately as part of the Confirmation.aspx page. When the order is verified and complete, email confirmations are sent. For PayPal orders however we cannot do this through the Confirmation page, so rather the confirmation page just displays the completion notice. The IPN return page then is then used to send the final email confirmations for emailing the download links.

Hooking up the code

PayPal payments are enabled through one of the payment options on the OrderForm. When selected and the user presses the Place your Order button, the code fires into the Button Click operation. The code there handles validation of user input first. At that point the form is ready to process payments.

if (this.txtCCType.Text == "PP" && !this.PayPalReturnRequest)  
{
	// *** We have to Save Invoice so that IPN confirm can see it!
	// *** We'll mark it as an UNPROCESSED order.
	this.invRow.Ccresult = "UNPROCESSED";
	if ( !Invoice.Save(true) )
	{
		this.ShowError(this.Invoice.ErrorMessage);
		return;
	}
	// *** Now redirect to PayPal
	this.HandlePayPalRedirection();  // Redirects 
}

// *** We're processing Credit Cards
// *** the method decides whether CC processing actually takes place
if ( !this.ProcessCreditCard() ) 
{
...

Note that unlike in 'regular' orders PayPal orders first save the order to disk with UNPROCESSED status. Regular orders wait until after processing to Save, but with PayPal we need to be able to look at this order independently of the current request, so it needs to be saved for later retrieval. The call to HandlePayPalRedirection() then handles saving off additional information and generating a URL that is used to redirect to PayPal.

The code for this method looks like this:

private void HandlePayPalRedirection() 
{
	this.invRow = this.Invoice.GetTypedDataRow();

	// *** Save the Notes and Types so we can restore them later
	PayPalState ppState = new PayPalState();
	ppState.Notes = this.txtNotes.Text;
	ppState.HeardFrom = this.txtHeardFrom.Text;
	ppState.ToolUsed = this.txtToolUsed.Text;

	// *** Store the object into a Session
	Session["PayPal_State"] = ppState;

	PayPalHelper PayPal = new PayPalHelper();
	PayPal.PayPalBaseUrl = App.Configuration.CCPayPalUrl;
	PayPal.AccountEmail = App.Configuration.CCPayPalEmail;

	PayPal.LogoUrl = "https://www.west-wind.com/images/wwtoollogo_text.gif";
	
	PayPal.Amount = invRow.Invtotal;
	PayPal.InvoiceNo = invRow.Invno;
	PayPal.BuyerEmail = this.Invoice.Customer.GetTypedDataRow().Email;

	PayPal.ItemName = App.Configuration.StoreName + " Invoice #" + invRow.Invno;

	PayPal.SuccessUrl =Request.Url + "?PayPal=Success";
	PayPal.CancelUrl = Request.Url + "?PayPal=Cancel";

	Response.Redirect(PayPal.GetSubmitUrl());

	return;
}

We basically assign the order relevant properties to the PayPalHelper object and then generating a URL that can be used to redirect to the PayPal site. We also save the content of several of the form's input fields into a temporary object, that gets stored into the Session() object. We'll use this as a PayPal state store that we can retrieve again when PayPal returns to us.

At this point processing will continue on the PayPal site. Note that we are passing a SuccessUrl and CancelUrl property to PayPal. These are used by PayPal to return back to our Web application. Here we're telling PayPal to return back to the current page (Request.Url) with a special querystring attached. For example:

https://www.west-wind.com/wwstore/orderform.aspx?PayPal=Success

At the end of the Page_Load() method of the orderform.aspx page there's a router that checks for this querystring:

// *** Check for PayPal responses - 
// *** if we have no CC selection and PayPal QueryString we need to handle it
if (Request.QueryString["PayPal"] != null && this.txtCCType.SelectedValue == "")
		this.HandlePayPalReturn();

This code fires into HandlePayPalReturn() which basically re-establishes the state of the page and then fires the Button Click handler to 'simulate' regular order processing as usual. Here's what HandlPayPalReturn() looks like:

private void HandlePayPalReturn() 
{

	// *** Otherwise we need to retrieve the previous state
	string Result = Request.QueryString["PayPal"];
	PayPalState ppState  = Session["PayPal_State"] as PayPalState;
	
	// *** Only do this if we are redirected!
	if (ppState != null) 
	{
		Session.Remove("PayPal_State");

		// *** Set flag so we know not to go TO PayPal again
		this.PayPalReturnRequest = true;

		// *** Retrieve saved Page content
		this.txtNotes.Text = ppState.Notes;
		this.txtHeardFrom.Text = ppState.HeardFrom; ;
		this.txtToolUsed.Text = ppState.ToolUsed;

		if (Result == "Cancel") 
		{
			this.ShowError("PayPal Payment Processing Failed");
		}
		else 
		{
			// *** We returned successfully - simulate button click to save the order
			this.txtCCType.Text = "PP";	   // PayPal
			// *** If IPN fired already we're already ready to go
			if (this.invRow.Ccresult.Trim() == "APPROVED") 
				this.txtOrderCode.Text = "";  
			else
				// *** Leave a visible note and cause not to Auto-Ship
				this.txtOrderCode.Text = "PayPal";  

			this.btnSubmit_Click(this,EventArgs.Empty);
		}
	}
}

Note the PayPalReturnRequest flag which gets set to true to make sure we don't redirect back to PayPal again. We'll pick up the State object we previously saved in the Session object and restore the values to the form's variables. At this point our order looks pretty much the same way as it did before we went off to PayPal. If we had a Success return from PayPal we can now mark the order as approved otherwise we'll leave it as UNPROCCESSED.

Instant Payment Notification

Note, it's important that you don't assume at this point that your order has actually been processed, because the return URL can be easily spoofed. Instead another mechanism called Instant Payment Notification can be used to have PayPal confirm the actual payment. The IPN return URL must be configured on the PayPal Account Management Site, under Profile | Selling Preferences | Instant Payment Notification.

IPN is a callback mechanism in which PayPal posts all the order information back to a predetermined URL on your site. Your code can look at the posted content to verify the order and based on that you can reliably assume the order went through.

The reason we saved the content of our order in UNPROCESSED mode previously is so that the IPN response page can pick up order information to update the order status. Here's what the IPN confirmation page (PayPalIPNConfirmation.aspx) looks like:

protected void Page_Load(object sender, System.EventArgs e)
{
	busInvoice Invoice = WebStoreFactory.GetbusInvoice();
	
	// *** Reload our invoice and try to match it
	// *** a real invoice
	string InvoiceNo = Request.Form["invoice"];
	
	if (!Invoice.LoadByInvNo(InvoiceNo) )
	{
		this.Log("Invalid PayPal IPN Request - Couldn't find invoice number.");
		return;
	}

	DataRowContainers.wws_invoiceRow Inv = Invoice.GetTypedDataRow();

	if (InvoiceNo != Inv.Invno.Trim()  ) 
	{
		this.Log("Invalid PayPal IPN Request - IPN invoice numer doesn't match Order invoice number.");
		return;
	}
		
	if (Inv.Ccresult == "APPROVED")
		// *** Already done!
		return;

	decimal InvTotal = Inv.Invtotal;
			
	// *** Send the response data back to PayPal for confirmation
	PayPal = new PayPalHelper();
	bool Result = PayPal.IPNPostDataToPayPal(App.Configuration.CCPayPalUrl,
				                                App.Configuration.CCPayPalEmail,
									    InvTotal);
	
	if (Result) 
	{
		// *** Let's set up the invoice so that it can Auto-Approve
		Inv.Ccresult = "APPROVED";
		Inv.Ordercode = ""; // Clear this out so the order can autoconfirm
		Invoice.Save();

		// *** Auto Confirmation will happen throuh the Orderform.aspx page
	}
	else  
	{
		this.Log(PayPal.LastResponse);
	}
}

This page basically takes the data that PayPal posts to you and echos it back to PayPal. In addition the page makes a few checks against the data to verify we're actually in sync with what we're expecting. The post back occurs through the PayPalHelper.IPNPostDataToPayPal() method which loops through all of the form vars posted and posts them right back to PayPal. PayPal, then responsds with a confirmation or failure that determines whether the method succeeds. In addition the method also compares the order amount to insure that the value matches our original order and wasn't tempered with by the customer.

Finally the order is updated to APPROVED. Note that this doesn't actually perform any action here, but rather defers the Order fullfilment back to the Orderform.aspx page which will generally fire AFTER this page was called. This is because IPN confirmation is instantly sent while PayPal will display another final confirmation page from which you finally redirect back to your own site.

The btnSubmit Click method then re-saves the order and checks for the APPROVED tag and if found sends confirmation emails:

// *** Save the invoice by copynig the Temporary LineItems to the real lineitems
if (!this.Invoice.Save(true)) 
{
	this.ShowError(this.Invoice.ErrorMessage);
	return;
}

// *** Confirm the order items
if ( invRow.Ccresult.Trim() == "APPROVED" && this.Invoice.CanAutoConfirm() )
	this.Invoice.SendEmailConfirmation();


And voila the whole order process... It takes some work to implement, but most of the logic that needs to be implemented is application specific and thus can't be fully automated due to the UI requirements of sending the PayPal request to PayPal and handling the response.

There's more detailed information along with screen shots of this whole process at:

http://www.west-wind.com/presentations/PayPalIntegration/PayPalIntegration.asp


This article also discusses Pros and Cons of using PayPal and a few pitfalls to look out for.


© West Wind Technologies, 1996-2018 • Updated: 09/03/04
Comment or report problem with topic