Extension:PayPal.php

From Organic Design
Jump to: navigation, search
Legacy.svg Legacy: This article describes a concept that has been superseded in the course of ongoing development on the Organic Design wiki. Please do not develop this any further or base work on this concept, this is only useful for a historic record of work done. You may find a link to the currently used concept or function in this article, if not you can contact the author to find out what has taken the place of this legacy item.

<php><?php /* IpbWiki Paypal WikiMedia 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
 - Log IPN post errors to log file
  • /

if (!defined('MEDIAWIKI')) die('Not an entry point.');

define('PAYPAL_VERSION','1.0.3, 2007-12-13');

$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';

  1. If returning from merchant, get item number (form id)

$wgPayPalAfterPurchase = (isset($_POST['item_number']) && !($_REQUEST['title'] == '__ipn_post')) ? $_POST['item_number'] : false;

  1. 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_date TINYTEXT, 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 = "
PayPal IPN Error! Could not create IPN database table, IPN functionality is disabled.
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; $date = $_POST['payment_date']; $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,$date,$item,$from,$amount,$status); # Update DB else wfPayPalLog("$log returned $res, status: $status"); } fclose ($fp); } } }

  1. The callback function for converting the input text to HTML output

function wfPayPalRenderButton( $input, $argv ) { global $ipbwiki_paypal, $wgAuth, $wgSiteNotice, $wgPayPalIPN;

$error = '
warning, specified paypal button not found, defaulting to button 1
';

$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 # Don't display any text if no donations $input = $total_donations ? wfMsgReplaceArgs($input,array($total_donated,$total_donations,)) : ; }

$output = "
$form$input
";

return $output; }

  1. Obtain the amount donated and total number of donations for an item (form number) from the DB
  2. - must sum all completed transactions associated with the passed item number

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); }

  1. Update a transaction (id) with a new status or insert a new transaction
  2. - if transaction id already exists, only the date and status are updated

function wfPayPalUpdateItem($id,$date,$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_date' => $date, 'ipn_status' => $status),array("ipn_id = '$id'")); else $db->insert($wgPayPalIPNTable,array( 'ipn_id' => $id, 'ipn_date' => $date, 'ipn_item' => $item, 'ipn_from' => $from, 'ipn_amount' => $amount, 'ipn_status' => $status )); }

  1. Adds a JS function to poll for changes to the forms totals after returning from purchase

function wfPayPalAddAjaxUpdater(&$out) { global $wgJsMimeType,$wgPayPalPollms; $id = $_POST['txn_id']; $out->addScript("<script type='$wgJsMimeType'> var poll = setInterval('paypalPollUpdater()',$wgPayPalPollms); function paypalResponseHandler(xmlhttp) { var totals = new Array(); totals = xmlhttp.responseText.split('|'); if (totals[2] == 'Completed') clearTimeout(poll); document.getElementById('paypal_donated').innerHTML = totals[0]; document.getElementById('paypal_donations').innerHTML = totals[1]; document.getElementById('paypal_updating').innerHTML = totals[2]; } function paypalPollUpdater() { sajax_do_call('wfPayPalAjaxUpdater',['$id'],paypalResponseHandler); }</script>"); return true; }

  1. The function called by the AJAX dispatcher to return the current totals of the form used to make the purchase

function wfPayPalAjaxUpdater($id) { global $wgPayPalIPNTable; $db = &wfGetDB(DB_SLAVE); $result = $db->query("SELECT ipn_status,ipn_item FROM $wgPayPalIPNTable WHERE ipn_id = '$id'"); if ($result instanceof ResultWrapper) $result = $result->result; $total_donated = $total_donations = 0; $status = "Transaction $id not found!"; if ($row = $db->fetchRow($result)) { list($status,$item) = $row; wfPayPalGetTotals($item,$total_donated,$total_donations); } $total_donated = number_format($total_donated,2); return "$total_donated|$total_donations|$status"; }

  1. 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); } </php>