Extension:PayPal.php
<?php /* IpbWiki Paypal WikiMedia extension
Template:Php{{#filesync:/var/www/extensions/PayPal.php}}
- IpbWiki (c) 2006
- Installation Instructions: http://www.ipbwiki.com/IpbWiki_Paypal_Extension
Changes by User:Nad (1.0.3 - 2007-12-04):
- Added IPN support (which can also work with AJAX updating if $wgUseAjax set) - Change button definitions to work from LocalSettings rather than code hack - Output warnings into sitenotice in error box
- /
if (!defined('MEDIAWIKI')) die('Not an entry point.');
define('PAYPAL_VERSION','1.0.3, 2007-12-13');
$wgPayPalAmount = false; # Add an input field to enter the amount to donate $wgPayPalIPN = false; # Use IPN notification $wgPayPalTest = false; # Set to the email address of the test merchant account if testing IPN in the PayPal sandbox site $wgPayPalPollms = 5000; # Number of milliseconds to wait between each call to AJAX updater $wgPayPalUpdating = '(updating...)'; # Message to add to form totals text (in $3) before IPN post arrives
$wgExtensionCredits['parserhook'][] = array( 'name' => 'IpbWiki PayPal', 'version' => PAYPAL_VERSION, 'author' => 'Peter De Decker, (IPN support by User:Nad)', 'url' => 'http://www.ipbwiki.com/IpbWiki_Paypal_Extension', 'description' => 'Mediawiki PayPal Extension' );
$wgPayPalLog = str_replace('.php','.log',__FILE__); $ipbwiki_paypal = array(); $wgExtensionFunctions[] = "wfPayPalExtension"; $wgAjaxExportList[] = 'wfPayPalAjaxUpdater';
- If returning from merchant, get item number (form id)
$wgPayPalAfterPurchase = (isset($_POST['item_number']) && !($_REQUEST['title'] == '__ipn_post')) ? $_POST['item_number'] : false;
- Set up the extension
function wfPayPalExtension() { global $wgParser, $ipbwiki_paypal, $wgPayPalIPN;
# Register the extension with the WikiText parser $wgParser->setHook( "paypal", "wfPayPalRenderButton" );
# Set button 1 to a default value if not defined if (!isset($ipbwiki_paypal[1])) $ipbwiki_paypal[1] = '<form action="https://www.paypal.com/cgi-bin/webscr" method="post"><input type="hidden" name="cmd" value="_xclick"><input type="hidden" name="business" value="ipbwiki@gmail.com"><input type="hidden" name="item_name" value="IpbWiki PayPal"><input type="hidden" name="no_shipping" value="1"><input type="hidden" name="cn" value="Optional Comments"><input type="hidden" name="currency_code" value="EUR"><input type="hidden" name="tax" value="0"><input type="hidden" name="bn" value="PP-DonationsBF"><input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-but04.gif" border="0" name="submit" alt="Make payments with PayPal - it\'s fast, free and secure!"></form>';
# IPN if ($wgPayPalIPN) {
global $wgHooks, $wgPayPalTest, $wgPayPalIPNTable, $wgUseAjax, $wgPayPalAfterPurchase; $db = &wfGetDB(DB_MASTER);
# If using AJAX add JS updater after page rendered if ($wgUseAjax && $wgPayPalAfterPurchase) $wgHooks['OutputPageBeforeHTML'][] = 'wfPayPalAddAjaxUpdater';
# Create the IPN database table if it doesn't exist $wgPayPalIPNTable = $db->tableName('PayPalIPN'); if (!$db->tableExists($wgPayPalIPNTable)) { $query = "CREATE TABLE $wgPayPalIPNTable (ipn_id VARCHAR(32), ipn_item INTEGER NOT NULL, ipn_from TINYTEXT, ipn_amount NUMERIC, ipn_status TINYTEXT, PRIMARY KEY (ipn_id));"; $result = $db->query($query); $db->freeResult($result); }
# If the table couldn't be created, disable IPN and add error to site notice if (!$db->tableExists($wgPayPalIPNTable)) { global $wgSiteNotice; $wgPayPalIPN = false;
$wgSiteNotice = "
Please ensure the wiki database user has CREATE permission, or add the table manually with the following query:
$query
";
}
# If this is an IPN post from paypal, validate and update the DB if verified if ($_REQUEST['title'] == '__ipn_post') {
# Disable normal wiki rendering and output global $wgOut; $wgOut->disable(); wfResetOutputBuffers();
# Read the relavent info from posted data $id = $_POST['txn_id']; $item = isset($_POST['item_number']) ? $_POST['item_number'] : 1; $from = $_POST['payer_email']; $cur = $_POST['mc_currency']; $amount = $_POST['payment_gross']; $status = $_POST['payment_status'];
# Post variables back to PayPal (with cmd appended) system (or sandbox if testing) to validate $log = "Transaction $id ($cur$amt) from $from"; $req = 'cmd=_notify-validate'; foreach ($_POST as $k => $v) $req .= "&$k=".urlencode(stripslashes($v)); $header .= "POST /cgi-bin/webscr HTTP/1.0\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Content-Length: ".strlen($req)."\r\n\r\n"; $domain = $wgPayPalTest ? 'www.sandbox.paypal.com' : 'www.paypal.com'; $fp = fsockopen ($domain, 80, $errno, $errstr, 30); if (!$fp) wfPayPalLog("$log: Could not open HTTP socket to $domain!"); else { fputs($fp, $header.$req); while (!feof($fp)) $res = fgets($fp, 1024); if ($res === 'VERIFIED') wfPayPalUpdateItem($id,$item,$from,$amount,$status); # Update DB else wfPayPalLog("$log returned $res, status: $status"); } fclose ($fp); } } }
- The callback function for converting the input text to HTML output
function wfPayPalRenderButton( $input, $argv ) { global $ipbwiki_paypal, $wgAuth, $wgSiteNotice, $wgPayPalIPN;
$error = '
';
$pos_space = strpos($input,' '); if (!$pos_space) { if (is_numeric($input)) { // format <paypal>number</paypal> $part1 = $input; $part2 = ; if (!$ipbwiki_paypal[$part1]) { $wgSiteNotice .= $error; $part1 = 1; $part2 = $input; } } else { // format <paypal>text</paypal> & format <paypal></paypal> $part1 = 1; $part2 = $input; } } else { // format <paypal>number text</paypal> $part1 = substr($input,0,$pos_space); $part2 = substr($input,$pos_space+1); if (is_numeric($part1)) { if (!$ipbwiki_paypal[$part1]) { $wgSiteNotice .= $error; $part1 = 1; } } else { // format <paypal>text</paypal> $part1 = 1; $part2 = $input; } } $form = $ipbwiki_paypal[$part1]; // if the ipbwiki interface is available, then use the clean function which is defined there, otherwise just clean the necessities... if (class_exists ('ipbwiki')) { $input = $wgAuth->ipbwiki->ipbwiki->clean_value ($part2); } else { $part2 = str_replace( ">", ">", $part2 ); $part2 = str_replace( "<", "<", $part2 ); $part2 = str_replace( "\"", """, $part2 ); $part2 = str_replace( "!", "!", $part2 ); $part2 = str_replace( "'", "'", $part2 ); $input = $part2; }
# IPN if ($wgPayPalIPN) { global $wgParser, $wgServer, $wgScript, $wgTitle, $wgPayPalIPNTable, $wgPayPalTest, $wgPayPalUpdating, $wgPayPalAfterPurchase; $wgParser->disableCache();
# Set item_number to this forms id $form = str_replace('</form>',"<input type=\"hidden\" name=\"item_number\" value=\"$part1\" /></form>",$form);
# Add a notify_url value in the form to tell paypal to post account changes to this script # todo: append shared secret to notify url $form = str_replace('</form>',"<input type=\"hidden\" name=\"notify_url\" value=\"$wgServer$wgScript/__ipn_post\" /></form>",$form);
# Add the receiver_email and change the URL's to sandbox site if testing in the paypal sandbox if ($wgPayPalTest) { $form = str_replace('www.paypal.com','www.sandbox.paypal.com',$form); $form = str_replace('business','receiver_email',$form); $form = str_replace('</form>',"<input type=\"hidden\" name=\"business\" value=\"$wgPayPalTest\" /></form>",$form); }
# Set return URL to this page and method to GET (and include the item id of the submitted form) $url = $wgTitle->getFullUrl(); $form = str_replace('</form>',"<input type=\"hidden\" name=\"return\" value=\"$url\" /></form>",$form); #$form = str_replace('</form>',"<input type=\"hidden\" name=\"rm\" value=\"1\" /></form>",$form);
# Get the current totals for this form from DB wfPayPalGetTotals($part1,$total_donated,$total_donations); $total_donated = number_format($total_donated,2);
# If returning from paypal purchase which used this form, wrap totals in spans with id's so they can be updated by AJAX if ($wgPayPalAfterPurchase == $part1) { $total_donated = "$total_donated"; $total_donations = "$total_donations"; $total_updating = "$wgPayPalUpdating"; $input = wfMsgReplaceArgs($input,array($total_donated,$total_donations,$total_updating)); }
# Replace $1, $2 and $3 in the text with the total amount donated and the total number of donations $input = wfMsgReplaceArgs($input,array($total_donated,$total_donations,)); }
$output = "
$form | $input |
";
return $output; }
- Obtain the amount donated and total number of donations for this id form the DB
function wfPayPalGetTotals($item,&$total_donated,&$total_donations) { global $wgPayPalIPNTable; $db = &wfGetDB(DB_SLAVE); $result = $db->query("SELECT ipn_amount FROM $wgPayPalIPNTable WHERE ipn_item = $item AND ipn_status = 'Completed'"); if ($result instanceof ResultWrapper) $result = $result->result; $total_donated = $total_donations = 0; while ($row = $db->fetchRow($result)) { $total_donations++; $total_donated += $row[0]; } $db->freeResult($result); }
- Update a transaction with a new status or insert a new transaction
function wfPayPalUpdateItem($id,$item,$from,$amount,$status) { global $wgPayPalIPNTable; $db = &wfGetDB(DB_MASTER); $result = $db->query("SELECT ipn_status FROM $wgPayPalIPNTable WHERE ipn_id = '$id'"); if ($result instanceof ResultWrapper) $result = $result->result; $exists = $db->fetchRow($result); $db->freeResult($result); if ($exists) $db->update($wgPayPalIPNTable,array('ipn_status' => $status),array("ipn_id = '$id'")); else $db->insert($wgPayPalIPNTable,array('ipn_id' => $id, 'ipn_item' => $item, 'ipn_from' => $from, 'ipn_amount' => $amount, 'ipn_status' => $status)); }
- Adds a JS function to poll for changes to the forms totals after returning from purchase
function wfPayPalAddAjaxUpdater(&$out) { global $wgJsMimeType,$wgPayPalPollms,$wgPayPalAfterPurchase; $out->addScript("<script type='$wgJsMimeType'> var poll = setInterval('paypalPollUpdater()',$wgPayPalPollms); function paypalResponseHandler(xmlhttp) { var totals = new Array(); totals = xmlhttp.responseText.split('|'); if (totals[1] > document.getElementById('paypal_donated').innerHTML) { clearTimeout(poll); document.getElementById('paypal_donated').innerHTML = totals[0]; document.getElementById('paypal_donations').innerHTML = totals[1]; document.getElementById('paypal_updating').innerHTML = ; } } function paypalPollUpdater() { sajax_do_call('wfPayPalAjaxUpdater',['$wgPayPalAfterPurchase'],paypalResponseHandler); }</script>"); return true; }
- The function called by the AJAX dispatcher to return the current totals of the form used to make the purchase
function wfPayPalAjaxUpdater($item) { $db = &wfGetDB(DB_SLAVE); wfPayPalGetTotals($item,$total_donated,$total_donations); $total_donated = number_format($total_donated,2); return "$total_donated|$total_donations"; }
- A log file for recording IPN error conditions
function wfPayPalLog($text) { global $wgPayPalLog; $ts = date("Y-m-d H:i:s"); @file_put_contents($wgPayPalLog,"$ts $text\n",FILE_APPEND); }