Making a Donation Center With PHP, MySQL and PayPal's APIs


It all started last month when I received the following email:

Hey there Tutorialzine!

Thanks for publishing such great tutorials, you have truly helped me increase my (growing) development skills. I know you enjoy getting ideas for tutorials, so I thought I'd toss one your way.

Donation buttons are a very common among blogs and websites in general, but they often don't provide much else than a button for someone to click and pay. Why not kick it up a notch and create a sweet "donation center", which doesn't just take donations, but perhaps includes a "meter", a top-donator list, and various other bells and whistles that may encourage the visitor to donate?


You are right, Devon, it is always nice to hear fresh ideas for tutorials. And a donation center is a great chance to experiment with PayPal's APIs and build something that many people can benefit from.

Note: This tutorial is quite old and doesn't work in PHP7 and above. We are keeping it online only as a reference.

The Idea

PayPal provides numerous APIs and integration options for third-party developers. One of these is the Donation button, which you can generate straight from PayPal's site and include directly into your pages. Conversion rate for these buttons is typically minimal, but the right incentive can make a big difference.

The idea is to have a dedicated Donation Center. This is a place, where you get to see what a difference your donation would make, and a list of people who have already donated.

After choosing the amount you wish to donate and clicking the button, you are redirected to to complete the transaction. After this is done, you are redirected back to the donation center where you can fill a form and get included in the official Donor List.


Step 1 - Database

The script uses two tables - dc_donations and dc_comments. The first stores the donation data which is passed in a request from PayPal (we will come back to this in the PHP step of the tutorial).


The second table holds the information about the donors, which they fill for themselves on the thank you page.


You can create these tables in your database by running the SQL code from tables.sql (found in the download archive) from the SQL tab of phpMyAdmin. You can also just upload the file via the "Import" feature.

After this remember to add your MySQL login details to connect.php.

Step 2 - XHTML

Moving to the markup of the donation center iself. Donate PHP is the only page which your visitors see. The other page is thankyou.php, which is displayed only to the donors, once they've completed the donation process.


<div id="main">
    <h1>Donation Center</h1>
    <h2>Show Your Support for our Cause</h2>

    <div class="lightSection">
        <h3>Hello, dear visitor!</h3>
        <p>This is Tutorialzine's Donation Center...</p>    

        <!-- The PayPal Donation Button -->


    <div class="chart">
        Our Goal

    <div class="donations">
        <?php echo $percent?>% done

    <div class="clear"></div>

    <div class="donors">
        <h3>The Donor List</h3>
        <h4>Folks Who Showed Their Support</h4>

        <div class="comments">

        <!-- PHP Code that generates the comments -->

        </div> <!-- Closing the comments div -->

    </div> <!-- Closing the donors div -->

</div> <!-- Closing the main div -->

The .lightSection div holds the message that we show to the visitor of the page and the markup of the PayPal donation button, which you can see below. After this is the Pie chart which shows how much of our initial goal is met. The chart is generated with Google's Charting API. Lastly is the .donors div, which holds a list of donors that have already donated.


<form action="<?php echo $payPalURL?>" method="post" class="payPalForm">
        <input type="hidden" name="cmd" value="_donations" />
        <input type="hidden" name="item_name" value="Donation" />

        <!-- Your PayPal email: -->
        <input type="hidden" name="business"
        value="<?php echo $myPayPalEmail?>"/>

        <!-- PayPal will send an IPN notification to this URL: -->
        <input type="hidden" name="notify_url"
        value="<?php echo $url.'/ipn.php'?>" /> 

        <!-- The return page to which the user is
        navigated after the donations is complete: -->

        <input type="hidden" name="return"
        value="<?php echo $url.'/thankyou.php'?>" /> 

        <!-- Signifies that the transaction data will be
        passed to the return page by POST: -->

        <input type="hidden" name="rm" value="2" /> 

        <!-- General configuration variables for the paypal landing page. -->

        <input type="hidden" name="no_note" value="1" />
        <input type="hidden" name="cbt" value="Go Back To The Site" />
        <input type="hidden" name="no_shipping" value="1" />
        <input type="hidden" name="lc" value="US" />
        <input type="hidden" name="currency_code" value="USD" />

        <!-- The amount of the transaction: -->

        <select name="amount">
        <option value="50">$50</option>
        <option value="20">$20</option>
        <option value="10" selected="selected">$10</option>
        <option value="5">$5</option>
        <option value="2">$2</option>
        <option value="1">$1</option>

        <input type="hidden" name="bn" value="
        PP-DonationsBF:btn_donate_LG.gif:NonHostedGuest" />

        <!-- You can change the image of the button: -->
        <input type="image" src="" name="submit" alt="PayPal - The safer, easier way to pay online!" />

    <img alt="" src=""
    width="1" height="1" />


The PayPal Donation button is structured as a regular html form. When you click the Donate graphic, the form is submitted to PayPal. A number of customizations are available, such as the the amount of the donation, the email of the recipient of the donation and many more (the full list of options is located here). Each of this options is added as a hidden field to the form.

Note the return and the notify_url variables. The first takes the URL of the page to which the user is redirected after the completion of the payment (in our case thankyou.php), and the other is a special page, which listens for PayPal's Instant Payment Notifications (IPN).

When a payment occurs, PayPal sends a special IPN notification to a page on your server. Data about the transaction is passed along the way, including the amount received, the email of the payee, currency and more. For example our IPN listener page, ipn.php, inserts the amount received to the donations table, which is later used by the Donation Center to calculate how much money has been gathered and what percentage of the goal has been met.

Now lets move to the PHP part of the tutorial.


Step 3 - PHP

As mentioned in the previous step, when a payment occurs, PayPal sends an IPN notification to your script, which contains all kinds of data for the transaction. Implementing this IPN listener page involves a lot of coding, as the script must validate that the notification was issued by PayPal.

Lucky for us, there are nice people out there who have already implemented these validations and packed them into an easy to use PHP5 PayPal class. Now lets take a look at how this is used:


require "paypal_integration_class/paypal.class.php";
require "config.php";
require "connect.php";

$p = new paypal_class;
$p->paypal_url = $payPalURL; // $payPalURL is defined in config.php

if ($p->validate_ipn()) {
        $amount = $p->ipn_data['mc_gross'] - $p->ipn_data['mc_fee'];

        mysql_query("   INSERT INTO dc_donations (transaction_id,donor_email,amount,original_request)
                        VALUES (

function esc($str)
    global $link;
    return mysql_real_escape_string($str,$link);

First we create a new paypal_class and assign it PayPal's URL. The rest is just a matter of calling the $p->validate_ipn() method and it will do all the hard work for us. If everything went OK, we insert the amount of the transaction, the email of the payee and the transaction id to the dc_donations table. It is probably a good place to note, that this page is not visible to the end user, it only exists to listen for IPN notifications from paypal which happen on the backend.


require "config.php";
require "connect.php";

if(isset($_POST['submitform']) && isset($_POST['txn_id']))
    $_POST['nameField'] = esc($_POST['nameField']);
    $_POST['websiteField'] =  esc($_POST['websiteField']);
    $_POST['messageField'] = esc($_POST['messageField']);

    $error = array();

        $error[] = 'Please fill in a valid name.';

        $error[] = 'Please fill in a longer message.';

        $error[] = 'The URL you entered is invalid.';

    $errorString = '';
        $errorString = join('<br />',$error);
        mysql_query("   INSERT INTO dc_comments (transaction_id, name, url, message)
                        VALUES (

            $messageString = '<a href="donate.php">You were added to our donor section!</a>';

After the use has donated, he is redirected by PayPal to our thank you page. Here he can add himself to the donor list by filling in a form. Wen redirecting, PayPal places transaction data in the $_POST array, which is available to thankyou.php. We can use this data to confirm that the user has in fact donated, and not just navigated manually to thankyou.php, bypassing PayPal. After the form is submitted, the user is added to the dc_comments table.


require "config.php";
require "connect.php";

// Determining the URL of the page:
$url = 'http://'.$_SERVER['SERVER_NAME'].dirname($_SERVER["REQUEST_URI"]);

// Fetching the number and the sum of the donations:
list($number,$sum) = mysql_fetch_array(mysql_query("SELECT COUNT(*),SUM(amount) FROM dc_donations"));

// Calculating how many percent of the goal are met:
$percent = round(min(100*($sum/$goal),100));

// Building a URL with Google's Chart API:
$chartURL = ',s,f9faf7&cht=p&chd=t:'.$percent.',-'.(100-$percent).'&chs=200x200&chco=639600&chp=1.57';

Google's Chart Api is an easy way to generate any kind of chart. To use it you just have to create a URL to which contains settings such as background color, type of chart (in our case a Pie), and a comma-separated list of values which serve as the data. You can include this URL as you would do a regular image, and a chart will be dynamically generated for you.


    $comments = mysql_query("SELECT * FROM dc_comments ORDER BY id DESC");

    // Building the Donor List:

        while($row = mysql_fetch_assoc($comments))

                <div class="entry">
                    <p class="comment">
                        echo nl2br($row['message']); // Converting the newlines of the comment to <br /> tags
                    <span class="tip"></span>

                    <div class="name">
                        <?php echo $row['name']?> <a class="url" href="<?php echo $row['url']?>"><?php echo $row['url']?></a>


Later in donate.php, we are building the donor list after running a query against the dc_comments table.


Step 4 - CSS

Having all the markup generated and included in the page, it is time to style it. You may find it surprising but the entire design is entirely CSS based and does not use a single image (apart from PayPal's Donate button image that is).

styles.css - Part 1

    /* Universal page reset */

    /* Setting default text color, background and a font stack */
    font-family:Rockwell, 'Courier New', Courier, Georgia, serif;

    font-family:Arial, Helvetica, sans-serif;

    /* The Donation Center text */
    border-bottom:1px solid #E3EBD2;
    padding:0 0 0 30px;

    /* The subtitle, notice the line-height property */
    border-left:1px solid #E3EBD2;
    margin:-70px 0 0;
    padding:55px 0 0 10px;


    /* The main container div */
    margin:40px auto;
    padding:0 0 0 20px;

You may notice in the demo page, how the Donation Center heading and subheading form an interesting set of lines. These are in fact the bottom and left borders of the h1 and h2 respectively.

styles.css - Part 2

    /* The Google Chart is set as this div's background via the style attribute.
    The negative text-indent moves the text outside the box */

    margin:0 0 0 300px;


.donations{ text-align:right; width:340px;}
.comments{ margin-top:60px;}
.entry{ margin-bottom:50px;}



.name a.url{


    /* A neat CSS trick which creates a triangle shape*/
    /* from the div's top border */

    border:20px solid #f9faf7;
    border-width:20px 15px;

Creating a triangle shape with CSS is also possible by setting the width and the height of the div to zero and specifying a big value for the widths of the borders.

With this our PHP & MySQL Donation Center is complete!


You can use this donation center to turn the usually anonymous act of donating funds into a more social and open process. You can possibly modify the code to include Twitter & Facebook integration or improve it in any other way you see fit.

What do you think? How would you improve this code?

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles

Marcell |

Great tutorial! I have to implement this technique into one of my sites :)

Deluxe Blog Tips

I've tested the demo. It's very cool. I just want to ask about the paypal PHP class, I don't see any link to it in the article, can you give a link to me?

Thanks for sharing a nice tutorial.

Martin Angelov

You can find the class here:

Added the link to the article.


original and well explained as usual. This is going to be extremely useful for a lot of people. Bookmarked!

Nice tutorial :)

Hey Martin,
I've been waiting for this! Thanks for a excellent tut and paying attention to wants of your audience.


i'm so impressed as usual, nice idea devon and good work to you martin!

Huge tutorial and well documented!

Great tutorial and good work, clean and very useful. Thanks.

Sarah William

Great tutorial..nicely executed...thanks for share...

Working on a pet project for a non-profit & I can tell you, this is gold mate !
Thanks so much for the great tutorials you provide us with every week...

Thanks for the tutorial, I will certainly use parts of it in my online payment-center.

Just wondering, why do you use varchar(64) for the transaction_id? If you know the value will be 64 characters, why not use char(64)? And let's move the varchar() fields after the fixed-width columns.

Federica Sibella

Nice tutorial! I'm gonna try for in
Thanks for sharing :)


Very, very good tutorial. Thanks so much for sharing!

I must admit - I have learned a lot from your tuts. They are detailed and easy to learn.

Keep up the good work.


Martin Angelov

Thank you for your awesome comments folks!

@ Thyone, I am not sure of the exact length of the transaction IDs, so I set it to a comfortable 64 chars. In addition to your suggested optimizations, If you plan to receive a lot of donations you can also move the text column to a different table. But if this is the case you could probably hire someone to do it for you.

Great code examples. Making it really esy for anyone to start doing some good in the world. :)


El Boletaire

Great tut!

I've one question:

In US you need to be declared as non-profit entity? Because I'm from Spain and here we must do it..

William Rouse

I am not able to run this in demo mode and I wonder what I may be doing wrong. Should this app run in demo mode with the launching of donate.php on my local server given that I have created the two databases.

Martin Angelov

@ El Boletaire, I am not sure about the legal side of things. It is probably a good idea to research this prior to running a big donation campaign.

@ William Rouse, Demo mode was mainly done to showcase the features of the donation center (otherwise you'd have to donate to add a test comment). You should disable it from config.php. There are also a couple of other settings in this file that you should take a look at.

Also, make sure that you've entered the correct MySQL connection details in connect.php.

I used it for one of my sites! It's still in Demo Mode so you can look at it and test it out.

I made a lot of superficial changes (as you can plainly see). However, I also included a pretty vital PHP change (shoutout to my co-developer Slava Markeyev for doing it):

in thankyou.php we changed:

if($_POST['websiteField'] != "" && !validateURL($_POST['websiteField']))

Basically, this allows people who don't want to leave a URL the option to leave it blank.

Thanks again for the great little demo & script!

Used this as a concept site for my online donation center for charities. I made quite a few changes behind the scenes to allow for multiple causes. So each charity/organization could have its own page each with their own goals, comments and separate pay pal accounts. I am already working on a new layout and removing pay pal as two charities have already turned me down since they don't want to register a pay pal account. Just had to get charities to understand the idea behind the site.

Only a demo cause up now. Also comments now have to be approved.

Shout outs to in the about page

If this posts multiple times I apologize. Its redirecting me to a blank page :(

Any ideas on what would be causing the following fatal error?

Fatal error: Call to undefined function mysql_set_charset() in ../connect.php on line 15

Thanks in advance!

Martin Angelov

Luke, probably your PHP version is a bit outdated. You can replace the call to this function in connect.php with:

mysql_query("SET NAMES 'utf8'");

And it will work fine.

Hi, I am looking for something like this, but I need the donor to be able to choose between 4 different topics to make the donations to and then be able to show the donations for those 4 separately. Any way of tweaking this to make that possible? (Like, giving the person who donates 4 choices of what the money will be used for)

thanks in advance for any responses.

Thank you for this tutorial. I needed a donation form for my site :)

@Camulos that's how is set up. Except the causes are entirely independent. Wouldn't be too hard to make them all go to one paypal account. There is currently only one cause on refresh hope since I am still waiting for a few groups to get back to me. After that we will officially launch.

If you email me at [email protected] I may be able to help you out.


How do I make the website field not required? I am taking donations from regular people with no websites.

Martin Angelov

@ DesignWannabe

To make the website field not required, comment out lines 22 - 25 of thankyou.php.

Andrew Pryde

I love the "heres an idea for a tutorial" emails... normally analogous with the "please do my job for me" emails.

Have you got a guide of how to make this implement recurring payments?
This is ideal for me to setup a sponsors page but just need to make it so the payments are monthly.

This is awesome-it worked perfectly for a donation portal I'm working on. I added a little jQuery snippet to make an auto show text box for an 'Other' donation amount, and also added a monthly subscription option to it.


Hi Andrew,
Can I know where i can get those changes .. I like it.

Then...i have change currency in EURO but Paypal reply that this don't accpet payment in USD... you have any suggetion.


Ricky Gamble

Hi, i am wanting to change the pulldown price to a manual enter price could you help me please?


Don Noray

How could I use this tutorial to allow the donor to input their own amount to donate and multiple causes?

Sergio Giudici

Thank you so much for such a wonderful tutorial!
Now, I was wondering how would I go to implement this into WordPress.
Any ideas?
Thank you again!
(Facebook Liked)

I have to say...this is old, but the donation script is as timely as ever. Beautiful, web 3.0 like. I love it.


I have been looking for something like this and thanks for posting this. It looks like the demo is not working for me; the pie chart is not showing up and even though I am entering data in the name and comments field in the Thank you page it comes with the error message.


Hey this is an amazing tutorial! Love this site!

A question tho am from Northern Ireland and I was wondering how to change the monetary value to GBP from USD which it is set to at the moment... in both the form and on Paypal...

Any help much apprectiated thanks!!!!

Ricky Gamble

Hi, in donate.php

<input type="hidden" name="currency_code" value="USD" />

change to
<input type="hidden" name="currency_code" value="GBP" />

Hi. Thanks for this great tutorial. I'm failing to get it to work though. I uploaded the files to my site and the donation system works but the percentage chart never shows, just a white box. Please help.



Thanks for the great tutorials, how can you show the amount of the donation in the donate.php page in each comment?

Delroy Brown

I really like your script. i have added a lot to it but there is one thing i wanted to share with others out their that may not know php/myqsql well.

I needed my script to start the count over every month do i replace this like of code

list($number,$sum) = mysql_fetch_array(mysql_query("SELECT COUNT(*),SUM(amount) FROM dc_donations"));

list($number,$sum) = mysql_fetch_array(mysql_query("SELECT COUNT(*),SUM(amount) FROM dc_donations WHERE MONTH(dt) = MONTH(NOW())"));

I made other changes as well but its not worth commenting on.


If someone just puts the URL for the thankyou.php script they can add their name and website link without any donation. They might pay $1 and add a whole laundry list of sites. Not a very good idea with all those nutty spammers out there.
It would be better if the return from paypal sent them an email with a link to the thankyou.php script that is in a "members only" restricted area. Or maybe just a onetime scrambled link to a hidden thankyou.php script in an isolated folder.
How would one do this?

Awesome Job, Martin. Everytime, i search the script and design with deeply know-how, i can always see your great work. Thank you.

Hi Martin,
thanks for that great tutorial!

one question, what is stored in "original_request" and what is the esc() function in: esc(http_build_query($_POST)). when I use it, it stores the button action.

Thanks again,

Question When a donation is made it takes me to the thankyou.php I enter the info and submit Then try to go back to thankyou.php and enter again it will not take it. Even from paypal. What could be causing this. I am only able to get one comment then will not take anymore Any ideas


is it possible to set the PayPal Page to german and the Ammount to €?


Ricky Gamble

Hi, I want to keep everything the same but swap the pulldown menu for an manual add your own price in a text box field so you can put in there a price of say $26.71 then click the donate button.


Jens Duenhaupt

Works this with €?

Tomas Maly

This is great except it's missing the paypal url to submit to! I looked around and guessed the URL from memory, but seems that it needs to be HTTPS (SSL):

For production use (as the form action):

For sandbox:

If you forget the 's' in https, you won't go to the right place and it will redirect to the homepage again and again.

I am getting this error in my thankyou.php. I haven't edited any code.

Notice: Undefined variable: errorString in D:\xampp\htdocs5\donation\thankyou.php on line 95

Notice: Undefined variable: messageString in D:\xampp\htdocs5\donation\thankyou.php on line 99

Ok. I figured it out. had to declare the variable up top of the file.


Having the same problem.
How did you solve this problem?
Thanks in advance.

I have finished my donation box. Thank you for the script.

Hi Martin,

Great tutorial. Thanks.

But i have a question:

Any idea how to separete the donations by amounts ?

I mean intervals, between, for example, 1 to 10 a color 11 t0 25 another ...

Sorry my poor English.

Thanks and Greetings

Trying to get the IPN to work, I've had problems with not getting this to work for years now

tonight's project was to try to get it to work.

I know opencart works without needing to enable in my paypal profile, so far no luck on getting it to work.

It doesn't insert into my database when I do paypal sandbox, any suggestions, I really want to be able to do this in one of my sites

I cannot get it to insert into my database.... Any thoughts as to why?

Use Mysqli instead of mysql statement all so check the sql syntax output and try it manually to see what errors are received.

Harold Rau

I built donation module using the code in your tutorial.
PayPay is now updating their security to SHA-256.
Is your script compatible? If not, can I make it compatible?

What Harold said!

It's not working correctly since January, any suggestions?