#!/usr/bin/perl -wT # # Modification History # # 2004-January-9 Jason Rohrer # Created. # Copied from DHB P2P fund. # Made separate directories for public/private data. # Fixed custom field. # Fixed refund bug. # # 2004-February-26 Jason Rohrer # Changed to format dollar amounts with two decimal places. # # 2004-March-7 Jason Rohrer # Changed to match new IPN refund format. # # This script must have write permissions to its dataDirectory # it must be able to create files in this directory # On most web servers, this means the directory must be world-writable. my $pubDataDirectory = "../htdocs/donationData"; my $privDataDirectory = "../cgi-data/donationTracker"; # If this $privDataDirectory setting is changed, you must also change it below # where the error LOG is opened # setup a local error log use CGI::Carp qw( carpout ); BEGIN { use CGI::Carp qw( carpout ); open( LOG, ">>../cgi-data/donationTracker/errors.log" ) or die( "Unable to open errors.log: $!\n" ); carpout( LOG ); } use strict; use CGI; # Object-Oriented CGI library # script settings my $donationFormURL = "http://mute-net.sf.net"; my $scriptURL = "http://mute-net.sf.net/cgi-bin/donationTracker.pl"; my $scriptFileName = "donationTracker.pl"; my $donationLogFile = "$privDataDirectory/donationLog.txt"; my $overallSumFile = "$pubDataDirectory/overallSum.html"; my $donationCountFile = "$pubDataDirectory/donationCount.html"; # where non-fatal errors and other information is logged my $logFile = "$privDataDirectory/log.txt"; # IP of notify.paypal.com # my $paypalNotifyIP = "65.206.229.140"; # New IP as of October 23, 2003 my $paypalNotifyIP = "64.4.241.140"; # end of script settings # setup stuff, make sure our needed files are initialized if( not doesFileExist( $overallSumFile ) ) { writeFile( $overallSumFile, "0" ); } if( not doesFileExist( $donationCountFile ) ) { writeFile( $donationCountFile, "0" ); } # create object to extract the CGI query elements my $cgiQuery = CGI->new(); # always at least send an HTTP OK header print $cgiQuery->header( -type=>'text/html', -expires=>'now', -Cache_control=>'no-cache' ); my $remoteAddress = $cgiQuery->remote_host(); my $action = $cgiQuery->param( "action" ) || ''; if( $action eq "showSource" ) { # print the sourcecode for our script print "
";

    my $source = readFileValue( $scriptFileName );
    
    # replace any &'s in code with &
    $source =~ s/&/&/g;
    # replace HTML brackets with < and > 
    $source =~ s//>/g;

    print $source;

    print "
\n"; } # make sure the request is coming from paypal elsif( $remoteAddress eq $paypalNotifyIP ) { my $donorName; # was this paypal payment generated by us? my $customField = $cgiQuery->param( "custom" ) || ''; if( $customField eq "MUTE_donate" ) { my $amount = $cgiQuery->param( "mc_gross" ) || ''; my $currency = $cgiQuery->param( "mc_currency" ) || ''; my $fee = $cgiQuery->param( "mc_fee" ) || '0'; my $date = $cgiQuery->param( "payment_date" ) || ''; my $transactionID = $cgiQuery->param( "txn_id" ) || ''; # these are for our private log only, for tech support, etc. # this information should not be stored in a web-accessible # directory my $payerFirstName = $cgiQuery->param( "first_name" ) || ''; my $payerLastName = $cgiQuery->param( "last_name" ) || ''; my $payerEmail = $cgiQuery->param( "payer_email" ) || ''; # only track US Dollars # (can't add apples to oranges to get a final sum) if( $currency eq "USD" ) { my $status = $cgiQuery->param( "payment_status" ) || ''; my $completed = $status eq "Completed"; my $pending = $status eq "Pending"; my $refunded = $status eq "Refunded"; if( $completed or $pending or $refunded ) { # write all relevant payment info into our private log addToFile( $donationLogFile, "$transactionID $date\n" . "From: $payerFirstName $payerLastName " . "($payerEmail)\n" . "Amount: \$$amount\n" . "Fee: \$$fee\n" . "Status: $status\n\n" ); if( $refunded ) { # subtract from total sum my $oldSum = readTrimmedFileValue( $overallSumFile ); # fee on refunds now reported as negative # this changed as of February 13, 2004 my $newSum = $oldSum - $amount - $fee; # format to show 2 decimal places my $newSumString = sprintf( "%.2f", $newSum ); writeFile( $overallSumFile, $newSumString ); my $oldCount = readTrimmedFileValue( $donationCountFile ); my $newCount = $oldCount - 1; writeFile( $donationCountFile, $newCount ); } # ignore negative completed transactions, since # they are reported for each refund (in addition to # the payment with Status: Refunded) if( $completed and $amount > 0 ) { # fee has not been subtracted yet # (fee is not reported for Pending transactions) my $oldSum = readTrimmedFileValue( $overallSumFile ); my $newSum = $oldSum + $amount - $fee; # format to show 2 decimal places my $newSumString = sprintf( "%.2f", $newSum ); writeFile( $overallSumFile, $newSumString ); my $oldCount = readTrimmedFileValue( $donationCountFile ); my $newCount = $oldCount + 1; writeFile( $donationCountFile, $newCount ); } } else { addToFile( $logFile, "Payment status unexpected\n" ); addToFile( $logFile, "status = $status\n" ); } } else { addToFile( $logFile, "Currency not USD\n" ); addToFile( $logFile, "currency = $currency\n" ); } } else { addToFile( $logFile, "Custom field does not contain our tag\n" ); addToFile( $logFile, "custom = $customField\n" ); } } else { # else not from paypal, so it might be a user accessing the script # URL directly for some reason # make sure we're not getting IPN from another address my $customField = $cgiQuery->param( "custom" ) || ''; if( $customField eq "MUTE-donate" ) { my $date = $cgiQuery->param( "payment_date" ) || ''; my $transactionID = $cgiQuery->param( "txn_id" ) || ''; my $amount = $cgiQuery->param( "mc_gross" ) || ''; my $payerFirstName = $cgiQuery->param( "first_name" ) || ''; my $payerLastName = $cgiQuery->param( "last_name" ) || ''; my $payerEmail = $cgiQuery->param( "payer_email" ) || ''; my $fee = $cgiQuery->param( "mc_fee" ) || '0'; my $status = $cgiQuery->param( "payment_status" ) || ''; # log it addToFile( $donationLogFile, "WARNING: got IPN from unexpected IP address\n" . "IP address: $remoteAddress\n" . "$transactionID $date\n" . "From: $payerFirstName $payerLastName " . "($payerEmail)\n" . "Amount: \$$amount\n" . "Fee: \$$fee\n" . "Status: $status\n\n" ); } # Redirect to the donation form page print "\n"; } ## # Reads file as a string. # # @param0 the name of the file. # # @return the file contents as a string. # # Example: # my $value = readFileValue( "myFile.txt" ); ## sub readFileValue { my $fileName = $_[0]; open( FILE, "$fileName" ) or die( "Failed to open file $fileName: $!\n" ); flock( FILE, 1 ) or die( "Failed to lock file $fileName: $!\n" ); my @lineList = ; my $value = join( "", @lineList ); close FILE; return $value; } ## # Reads file as a string, trimming leading and trailing whitespace off. # # @param0 the name of the file. # # @return the trimmed file contents as a string. # # Example: # my $value = readFileValue( "myFile.txt" ); ## sub readTrimmedFileValue { my $returnString = readFileValue( $_[0] ); trimWhitespace( $returnString ); return $returnString; } ## # Writes a string to a file. # # @param0 the name of the file. # @param1 the string to print. # # Example: # writeFile( "myFile.txt", "the new contents of this file" ); ## sub writeFile { my $fileName = $_[0]; my $stringToPrint = $_[1]; open( FILE, ">$fileName" ) or die( "Failed to open file $fileName: $!\n" ); flock( FILE, 2 ) or die( "Failed to lock file $fileName: $!\n" ); print FILE $stringToPrint; close FILE; } ## # Checks if a file exists in the filesystem. # # @param0 the name of the file. # # @return 1 if it exists, and 0 otherwise. # # Example: # $exists = doesFileExist( "myFile.txt" ); ## sub doesFileExist { my $fileName = $_[0]; if( -e $fileName ) { return 1; } else { return 0; } } ## # Trims any whitespace from the beginning and end of a string. # # @param0 the string to trim. ## sub trimWhitespace { # trim from front of string $_[0] =~ s/^\s+//; # trim from end of string $_[0] =~ s/\s+$//; } ## # Appends a string to a file. # # @param0 the name of the file. # @param1 the string to append. # # Example: # addToFile( "myFile.txt", "the new contents of this file" ); ## sub addToFile { my $fileName = $_[0]; my $stringToPrint = $_[1]; open( FILE, ">>$fileName" ) or die( "Failed to open file $fileName: $!\n" ); flock( FILE, 2 ) or die( "Failed to lock file $fileName: $!\n" ); print FILE $stringToPrint; close FILE; } ## # Makes a directory file. # # @param0 the name of the directory. # @param1 the octal permission mask. # # Example: # makeDirectory( "myDir", oct( "0777" ) ); ## sub makeDirectory { my $fileName = $_[0]; my $permissionMask = $_[1]; mkdir( $fileName, $permissionMask ); }