There has been a lot written about how to handle the IPN return from PayPal so I will not go too deeply into that process, but it seems that there has not been too much written about the initial call to PayPal and that is where I will focus.
This article summarizes how to perform a user registration process which requires payment and how to integrate that with PayPal. This sort of process would exist for instance on a web form where you want to charge for registration.
The problem I set out to solve was to create a web site where I charged some nominal fee for registration. I chose to use PayPal b/c everything had already been set up for you and I did not want to have to worry about ssl and handling credit card information. I understood how to handle things once the PayPal transaction was completed, but I could not figure out how to have a user register, do some database processing, and then if there were some problems with the registration process prevent the user from being forwarded onto PayPal and instead display an error on the registration form such as "Duplicate Email Address" or "UserName already exists". These sorts of scenarios would first require a user to correct them and only after they corrected them, to then be forwarded onto PayPal to pay for the registration.
I first tried downloading the PayPal SDK and using the BuyNowButton. It is an excellent SDK and I was very happy with it, but I could not solve the problem I set out to solve. I tried overloading the OnClick event in my own form, but no matter what it would always forward the user onto PayPal. This is not what I wanted to do. So I took a look at what they were doing and used the same concept for my needs.
The basic premise here is the following:
-
User comes to the Register.aspx page
-
User completes form and clicks Submit
-
The system then performs the relevant checks to ensure that this user has a unique username and email address
-
if the above condition is true, then forward onto PayPal, if not, then display a message to the user and allow them to correct.
The concept consists of 2 files:
-
Register.aspx
-
BuyNow.htm
Register.aspx
--------------------------------------------------------------------
public
class Register : System.Web.UI.Page
{
protected System.Web.UI.WebControls.TextBox Email;
protected System.Web.UI.WebControls.TextBox UserName;
protected System.Web.UI.WebControls.TextBox Zip;
protected System.Web.UI.WebControls.Button btnRegister;
protected System.Web.UI.WebControls.Label Message;
protected System.Web.UI.WebControls.TextBox Password;
private void Page_Load(object sender, System.EventArgs e)
{
}
private void btnRegister_Click(object sender, System.EventArgs e)
{
//here is where you would go to the db, perform the necessary checks
//and if there are no errors then you can go to paypal
//One way to handle this, is to insert your user in an INACTIVE status in the db
//and put their UserID into the return url as a parameter. So once they successfully
//make their payment, you can update this userid to an ACTIVE status.
if(!DBErrors)
{
string buyNow = Path.Combine(Server.MapPath("."), "BuyNow.htm");
StreamReader reader = new StreamReader(@buyNow);
string html = reader.ReadToEnd();
html = String.Format(html, UserID);
HttpResponse resp = this.Context.Response;
resp.Clear();
resp.Write(html);
resp.Flush();
}
else
{
Message.Text = "There were errors, please try again!";
}
}
private bool DBErrors
{
get { return false; }
}
private int UserID
{
get { return 50; }
}
}
BuyNow.htm
----------------------------------------------------------------------------
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title></title>
<meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1">
<meta name=ProgId content=VisualStudio.HTML>
<meta name=Originator content="Microsoft Visual Studio .NET 7.1">
</head>
<body onload="PayPalForm.submit();">
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" name="PayPalForm">
<input type="hidden" name="cmd" value="_xclick">
<input type="hidden" name="business" value="[email protected]">
<input type="hidden" name="item_name" value="Registration">
<input type="hidden" name="item_number" value="1001">
<input type="hidden" name="amount" value="1.99">
<input type="hidden" name="no_shipping" value="1">
<input type="hidden" name="return" value="http://www.yourhost.com/IPNHandler.aspx?UserID={0}">
<input type="hidden" name="cancel_return" value="http://www.yourhost.com">
<input type="hidden" name="no_note" value="1">
<input type="hidden" name="currency_code" value="USD">
</form>
</body>
</html>
So as you can see if there are no errors that would prevent a successful registration, load up the BuyNow.htm template and the onLoad event of the <body> will submit the form to PayPal and start the process.
In addition to this I am filling in the UserID in the template with the userID that the database generated when I inserted this user. The reason for this is that the "return" parameter in the template will be used once the user has completed the payment and then clicks continue in PayPal. This will send the client to:
http://www.yourhost.com/IPNHandler.aspx?UserID={0} (the {0} is filled in with the userID from the database). You could then update their status from INACTIVE to ACTIVE. I have included the IPNHandler.aspx code as well.
IPHandler
-------------------------------------------------------------------------------
public class IPNHandler : System.Web.UI.Page
{
protected UserController userCtrl = new UserController();
private void Page_Load(object sender, System.EventArgs e)
{
int userID = Convert.ToInt32(Request.Params["UserID"]);
string strFormValues = Request.Form.ToString();
string strNewValue;
string strResponse;
// Create the request back
HttpWebRequest req = (HttpWebRequest) WebRequest.Create(https://www.paypal.com/cgi-bin/webscr);
// Set values for the request back
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
strNewValue = strFormValues + "&cmd=_notify-validate";
req.ContentLength = strNewValue.Length;
// Write the request back IPN strings
StreamWriter stOut = new StreamWriter (req.GetRequestStream(), System.Text.Encoding.ASCII);
stOut.Write(strNewValue);
stOut.Close();
// Do the request to PayPal and get the response
StreamReader stIn = new StreamReader(req.GetResponse().GetResponseStream());
strResponse = stIn.ReadToEnd();
stIn.Close();
// Confirm whether the IPN was VERIFIED or INVALID. If INVALID, just ignore the IPN
if (strResponse == "VERIFIED")
{
string email = Request.Params["receiver_email"].ToString();
string status = Request.Params["payment_status"].ToString();
string txn_id = Request.Params["txn_id"].ToString();
// check that paymentStatus=Completed
// check that txnId has not been previously processed
// check that receiverEmail is your Primary PayPal email
// check that paymentAmount/paymentCurrency are correct
// process payment
if(status.Equals("Completed"))
{
//we have passed here so we can go ahead and insert their
//payment into the db and change their status to A
if(userID != userCtrl.GetPayment(txn_id))
{
try
{
//record the transaction in the db
userCtrl.InsertPayment(userID, txn_id, DateTime.Now);
//change the user's status to ACTIVE
userCtrl.UpdateUserStatus(userID, 'A', DateTime.Now);
}
catch (ApplicationException ex)
{
}
Response.Redirect("Login.aspx");
}
else
Response.Redirect("Default.aspx");
}
}
else if(strResponse == "INVALID")
{
// log for investigation
}
else
{
// error
}
}
}
The reason I filled in the UserID parameter in the template is so when the user completes their payment, paypal will send them back to IPNHandler.aspx?UserID=123 then we can check if the payment transaction completed successfully and then change the status of the user from 'I' (INACTIVE) to 'A' (ACTIVE)