http://www.mediawiki.org/wiki/Special:Code/MediaWiki/97341
Revision: 97341 Author: khorn Date: 2011-09-16 22:02:26 +0000 (Fri, 16 Sep 2011) Log Message: ----------- Adds a processor-type page for the iframe to come back from, and a way to handle error code translation in a gateway's transaction responses. Modified Paths: -------------- branches/fundraising/extensions/DonationInterface/donationinterface.php branches/fundraising/extensions/DonationInterface/gateway_common/gateway.adapter.php branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.php Added Paths: ----------- branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_resultswitcher.body.php branches/fundraising/extensions/DonationInterface/modules/iframe.liberator.js Modified: branches/fundraising/extensions/DonationInterface/donationinterface.php =================================================================== --- branches/fundraising/extensions/DonationInterface/donationinterface.php 2011-09-16 21:54:35 UTC (rev 97340) +++ branches/fundraising/extensions/DonationInterface/donationinterface.php 2011-09-16 22:02:26 UTC (rev 97341) @@ -89,17 +89,17 @@ # Unit tests $wgHooks['UnitTestsList'][] = 'efDonationInterfaceUnitTests'; -// -//// Resource modules -//$wgResourceTemplate = array( -// 'localBasePath' => $donationinterface_dir . 'modules', -// 'remoteExtPath' => 'DonationInterface/modules', -//); -//$wgResourceModules['jquery.donationInterface'] = array( -// 'scripts' => 'jquery.donationInterface.js', -// 'dependencies' => 'jquery.json', -//) + $wgResourceTemplate; +// Resource modules +$wgResourceTemplate = array( + 'localBasePath' => $donationinterface_dir . 'modules', + 'remoteExtPath' => 'DonationInterface/modules', +); +$wgResourceModules['iframe.liberator'] = array( + 'scripts' => 'iframe.liberator.js', + 'position' => 'top' +) + $wgResourceTemplate; + function efDonationInterfaceUnitTests( &$files ) { $files[] = dirname( __FILE__ ) . '/tests/GatewayAdapterTest.php'; $files[] = dirname( __FILE__ ) . '/tests/DonationDataTest.php'; Modified: branches/fundraising/extensions/DonationInterface/gateway_common/gateway.adapter.php =================================================================== --- branches/fundraising/extensions/DonationInterface/gateway_common/gateway.adapter.php 2011-09-16 21:54:35 UTC (rev 97340) +++ branches/fundraising/extensions/DonationInterface/gateway_common/gateway.adapter.php 2011-09-16 22:02:26 UTC (rev 97341) @@ -124,8 +124,8 @@ * Override this in children if you want different defaults. */ function setPostDefaults() { - $returnTitle = Title::newFromText( 'Donate-thanks/en' ); -// $returnTitle = Title::newFromText( 'Special:GlobalCollectGateway' ); +// $returnTitle = Title::newFromText( 'Donate-thanks/en' ); + $returnTitle = Title::newFromText( 'Special:GlobalCollectGatewayResult' ); $returnto = $returnTitle->getFullURL(); $this->postdatadefaults = array( @@ -192,7 +192,7 @@ //If there's a value in the post data (name-translated by the var_map), use that. if ( array_key_exists( $gateway_field_name, $this->var_map ) ) { - if ($token === true){ //we just want the field name to use, so short-circuit all that mess. + if ( $token === true ) { //we just want the field name to use, so short-circuit all that mess. return '@' . $this->var_map[$gateway_field_name]; } if ( array_key_exists( $this->var_map[$gateway_field_name], $this->postdata ) && @@ -249,9 +249,9 @@ $node->appendChild( $temp ); } } - + //TODO: You can actually take this out if we never ever want to use ajax for a gateway. - function buildTransactionFormat($transaction){ + function buildTransactionFormat( $transaction ) { $this->currentTransaction( $transaction ); $this->xmlDoc = new DomDocument( '1.0' ); $node = $this->xmlDoc->createElement( 'XML' ); @@ -261,34 +261,34 @@ $this->buildTransactionNodes( $structure, $node, true ); $this->xmlDoc->appendChild( $node ); $xml = $this->xmlDoc->saveXML(); - $xmlStart = strpos($xml, "<XML>"); - self::log("XML START" . $xmlStart); + $xmlStart = strpos( $xml, "<XML>" ); + self::log( "XML START" . $xmlStart ); $xml = substr( $xml, $xmlStart ); - self::log("XML stubby thing..." . $xml); - + self::log( "XML stubby thing..." . $xml ); + return $xml; } - + function do_transaction( $transaction ) { $this->currentTransaction( $transaction ); //update the contribution tracking data $this->dataObj->updateContributionTracking( defined( 'OWA' ) ); if ( $this->getCommunicationType() === 'xml' ) { - $this->getStopwatch(__FUNCTION__); + $this->getStopwatch( "buildRequestXML" ); $xml = $this->buildRequestXML(); - $this->saveCommunicationStats(__FUNCTION__, "Building Request XML", "Transaction = $transaction"); + $this->saveCommunicationStats( "buildRequestXML", $transaction ); $returned = $this->curl_transaction( $xml ); //put the response in a universal form, and return it. } if ( $this->getCommunicationType() === 'namevalue' ) { $namevalue = $this->postdata; - $this->getStopwatch(__FUNCTION__); + $this->getStopwatch( __FUNCTION__ ); $returned = $this->curl_transaction( $namevalue ); $this->saveCommunicationStats(); //put the response in a universal form, and return it. } - + self::log( "RETURNED FROM CURL:" . print_r( $returned, true ) ); if ( $returned['result'] === false ) { //couldn't make contact. Bail. return $returned; @@ -376,8 +376,8 @@ */ protected function curl_transaction( $data ) { // assign header data necessary for the curl_setopt() function - $this->getStopwatch(__FUNCTION__, true); - + $this->getStopwatch( __FUNCTION__, true ); + $ch = curl_init(); $headers = $this->getCurlBaseHeaders(); @@ -413,8 +413,8 @@ } } - $this->saveCommunicationStats(__FUNCTION__, $this->currentTransaction(), "Request:" . print_r($data, true) . "\nResponse" . print_r($return, true)); - + $this->saveCommunicationStats( __FUNCTION__, $this->currentTransaction(), "Request:" . print_r( $data, true ) . "\nResponse" . print_r( $return, true ) ); + if ( $return['headers']['http_code'] != 200 ) { $return['result'] = false; //TODO: i18n here! @@ -516,19 +516,19 @@ $c = get_called_class(); return $c::IDENTIFIER; } - - public function getStopwatch($string, $reset = false){ + + public function getStopwatch( $string, $reset = false ) { static $start = null; - $now = microtime(true); + $now = microtime( true ); - if ($start == null || $reset === true){ + if ( $start == null || $reset === true ) { $start = $now; } - $clock = round($now - $start, 4); - self::log("\nClock at $string: $clock ($now)"); + $clock = round( $now - $start, 4 ); + self::log( "\nClock at $string: $clock ($now)" ); return $clock; } - + /** * * @param type $function @@ -536,12 +536,12 @@ * @param type $vars */ function saveCommunicationStats( $function = '', $additional = '', $vars = '' ) { //easier than looking at logs... - $params = array(); - if (self::getGlobal('SaveCommStats')){ //TODO: I should do this for real at some point. + $params = array( ); + if ( self::getGlobal( 'SaveCommStats' ) ) { //TODO: I should do this for real at some point. $db = ContributionTrackingProcessor::contributionTrackingConnection(); $params['contribution_id'] = $this->dataObj->getVal( 'contribution_tracking_id' ); $params['ts'] = $db->timestamp(); - $params['duration'] = $this->getStopwatch(__FUNCTION__); + $params['duration'] = $this->getStopwatch( __FUNCTION__ ); $params['gateway'] = self::getGatewayName(); $params['function'] = $function; $params['vars'] = $vars; @@ -551,11 +551,10 @@ $db->insert( 'communication_stats', $params ); } + } - } - - function xmlChildrenToArray($xml, $nodename){ - $data = array(); + function xmlChildrenToArray( $xml, $nodename ) { + $data = array( ); foreach ( $xml->getElementsByTagName( $nodename ) as $node ) { foreach ( $node->childNodes as $childnode ) { if ( trim( $childnode->nodeValue ) != '' ) { @@ -566,4 +565,59 @@ return $data; } + /** + * DO NOT DEFINE OVERLAPPING RANGES! + * TODO: Make sure it won't let you add overlapping ranges. That would probably necessitate the sort moving to here, too. + * @param type $transaction + * @param type $key + * @param type $action + * @param type $lower + * @param type $upper + */ + function addCodeRange( $transaction, $key, $action, $lower, $upper = null ) { + if ( $upper === null ) { + $this->return_value_map[$transaction][$key][$lower] = $action; + } else { + $this->return_value_map[$transaction][$key][$upper] = array( 'action' => $action, 'lower' => $lower ); + } + } + + function findCodeAction( $transaction, $key, $code ) { + $this->getStopwatch( __FUNCTION__, true ); + if ( !array_key_exists( $transaction, $this->return_value_map ) || !array_key_exists( $key, $this->return_value_map[$transaction] ) ) { + return null; + } + if ( !is_array( $this->return_value_map[$transaction][$key] ) ) { + return null; + } + //sort the array so we can do this quickly. + ksort( $this->return_value_map[$transaction][$key], SORT_NUMERIC ); + + $ranges = $this->return_value_map[$transaction][$key]; + //so, you have a code, which is a number. You also have a numerically sorted array. + //loop through until you find an upper >= your code. + //make sure it's in the range, and return the action. + foreach ( $ranges as $upper => $val ) { + if ( $upper >= $code ) { //you've arrived. It's either here or it's nowhere. + if ( is_array( $val ) ) { + if ( $val['lower'] <= $code ) { + $this->saveCommunicationStats( __FUNCTION__, $transaction, "code = $code" ); + return $val['action']; + } else { + return null; + } + } else { + if ( $upper === $code ) { + $this->saveCommunicationStats( __FUNCTION__, $transaction, "code = $code" ); + return $val; + } else { + return null; + } + } + } + } + //if we walk straight off the end... + return null; + } + } Modified: branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php =================================================================== --- branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php 2011-09-16 21:54:35 UTC (rev 97340) +++ branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php 2011-09-16 22:02:26 UTC (rev 97341) @@ -12,14 +12,14 @@ */ function stageData() { $this->postdata['amount'] = $this->postdata['amount'] * 100; - + $card_type = ''; - if (array_key_exists('card_type', $this->postdata) && !empty($this->postdata['card_type'])){ + if ( array_key_exists( 'card_type', $this->postdata ) && !empty( $this->postdata['card_type'] ) ) { $card_type = $this->postdata['card_type']; } else { $card_type = $this->postdatadefaults['card_type']; } - + switch ( $card_type ) { case 'visa': $this->postdata['card_type'] = 1; @@ -34,19 +34,18 @@ $this->postdata['card_type'] = 128; break; } - + $this->postdata['expiry'] = $this->postdata['expiration']; //. ($this->postdata['year'] % 100); - $this->postdata['card_num'] = str_replace(' ', '', $this->postdata['card_num']); - + $this->postdata['card_num'] = str_replace( ' ', '', $this->postdata['card_num'] ); + $returnto = ''; - if (array_key_exists('returnto', $this->postdata)){ + if ( array_key_exists( 'returnto', $this->postdata ) ) { $returnto = $this->postdata['returnto']; } else { $returnto = $this->postdatadefaults['returnto']; } - + $this->postdata['returnto'] = $returnto . "?order_id=" . $this->postdata['order_id']; - } function defineAccountInfo() { @@ -70,7 +69,7 @@ 'PAYMENTPRODUCTID' => 'card_type', 'CVV' => 'cvv', 'EXPIRYDATE' => 'expiry', - 'CREDITCARDNUMBER' => 'card_num', + 'CREDITCARDNUMBER' => 'card_num', 'FIRSTNAME' => 'fname', 'SURNAME' => 'lname', 'STREET' => 'street', @@ -86,6 +85,19 @@ 'OK' => true, 'NOK' => false, ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending', 0, 70 ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'failed', 100, 180 ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending', 200 ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'failed', 220, 280 ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending', 300 ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'failed', 310, 350 ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'revised', 400 ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending_poke', 525 ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending', 550, 850 ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending', 900 ); //There's two 900s in the doc. The heck? + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'pending', 950, 975 ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'complete', 1000, 1050 ); + $this->addCodeRange( 'GET_ORDERSTATUS', 'STATUSID', 'failed', 1100, 99999 ); } function defineTransactions() { @@ -235,22 +247,23 @@ */ function getResponseData( $response ) { $data = array( ); - + $transaction = $this->currentTransaction(); switch ( $transaction ) { case 'INSERT_ORDERWITHPAYMENT': - $data = $this->xmlChildrenToArray($response, 'ROW'); - $data['ORDER'] = $this->xmlChildrenToArray($response, 'ORDER'); - $data['PAYMENT'] = $this->xmlChildrenToArray($response, 'PAYMENT'); + $data = $this->xmlChildrenToArray( $response, 'ROW' ); + $data['ORDER'] = $this->xmlChildrenToArray( $response, 'ORDER' ); + $data['PAYMENT'] = $this->xmlChildrenToArray( $response, 'PAYMENT' ); break; case 'GET_ORDERSTATUS': - $data = $this->xmlChildrenToArray($response, 'STATUS'); - $data['ORDER'] = $this->xmlChildrenToArray($response, 'ORDER'); + $data = $this->xmlChildrenToArray( $response, 'STATUS' ); + $data['WMF_TRANSLATEDCODE'] = $this->findCodeAction( 'GET_ORDERSTATUS', 'STATUSID', $data['STATUSID'] ); + $data['ORDER'] = $this->xmlChildrenToArray( $response, 'ORDER' ); break; } - - + + self::log( "Returned Data: " . print_r( $data, true ) ); return $data; } Modified: branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.php =================================================================== --- branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.php 2011-09-16 21:54:35 UTC (rev 97340) +++ branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.php 2011-09-16 22:02:26 UTC (rev 97341) @@ -23,12 +23,14 @@ // Set up the new special page $dir = dirname( __FILE__ ) . '/'; $wgAutoloadClasses['GlobalCollectGateway'] = $dir . 'globalcollect_gateway.body.php'; +$wgAutoloadClasses['GlobalCollectGatewayResult'] = $dir . 'globalcollect_resultswitcher.body.php'; $wgAutoloadClasses['GlobalCollectAdapter'] = $dir . 'globalcollect.adapter.php'; $wgExtensionMessagesFiles['GlobalCollectGateway'] = $dir . '../payflowpro_gateway/payflowpro_gateway.i18n.php'; $wgExtensionMessagesFiles['GlobalCollectGatewayCountries'] = $dir . '../payflowpro_gateway/payflowpro_gateway.countries.i18n.php'; $wgExtensionMessagesFiles['GlobalCollectGatewayUSStates'] = $dir . '../payflowpro_gateway/payflowpro_gateway.us-states.i18n.php'; $wgExtensionAliasesFiles['GlobalCollectGateway'] = $dir . '../payflowpro_gateway/payflowpro_gateway.alias.php'; $wgSpecialPages['GlobalCollectGateway'] = 'GlobalCollectGateway'; +$wgSpecialPages['GlobalCollectGatewayResult'] = 'GlobalCollectGatewayResult'; //$wgAjaxExportList[] = "fnGlobalCollectofofWork"; // set defaults, these should be assigned in LocalSettings.php $wgGlobalCollectGatewayURL = 'https://ps.gcsip.nl/wdl/wdl'; Added: branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_resultswitcher.body.php =================================================================== --- branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_resultswitcher.body.php (rev 0) +++ branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_resultswitcher.body.php 2011-09-16 22:02:26 UTC (rev 97341) @@ -0,0 +1,190 @@ +<?php + +class GlobalCollectGatewayResult extends UnlistedSpecialPage { + + /** + * Defines the action to take on a PFP transaction. + * + * Possible values include 'process', 'challenge', + * 'review', 'reject'. These values can be set during + * data processing validation, for instance. + * + * Hooks are exposed to handle the different actions. + * + * Defaults to 'process'. + * @var string + */ + public $action = 'process'; + + /** + * An array of form errors + * @var array + */ + public $errors = array( ); + + /** + * Constructor - set up the new special page + */ + public function __construct() { + parent::__construct( 'GlobalCollectGatewayResult' ); + $this->errors = $this->getPossibleErrors(); + + $this->adapter = new GlobalCollectAdapter(); + } + + /** + * Show the special page + * + * @param $par Mixed: parameter passed to the page or null + */ + public function execute( $par ) { + global $wgRequest, $wgOut, $wgExtensionAssetsPath, + $wgPayFlowProGatewayCSSVersion; + + $wgOut->allowClickjacking(); + $wgOut->addModules( 'iframe.liberator' ); + + $wgOut->addExtensionStyle( + $wgExtensionAssetsPath . '/DonationInterface/gateway_forms/css/gateway.css?284' . + $wgPayFlowProGatewayCSSVersion ); + + $this->setHeaders(); + + + // dispatch forms/handling + if ( $this->adapter->checkTokens() ) { + // Display form for the first time + $oid = $wgRequest->getText( 'order_id' ); + if ( $oid && !empty( $oid ) ) { + $result = $this->adapter->do_transaction( 'GET_ORDERSTATUS' ); + $this->displayResultsForDebug( $result ); + } + $this->adapter->log( "Not posted, or not processed. Showing the form for the first time." ); + } else { + if ( !$this->adapter->isCache() ) { + // if we're not caching, there's a token mismatch + $this->errors['general']['token-mismatch'] = wfMsg( 'payflowpro_gateway-token-mismatch' ); + } + } + } + + function displayResultsForDebug( $results ) { + global $wgOut; + $wgOut->addHTML( $results['message'] ); + + if ( !empty( $results['errors'] ) ) { + $wgOut->addHTML( "<ul>" ); + foreach ( $results['errors'] as $code => $value ) { + $wgOut->addHTML( "<li>Error $code: $value" ); + } + $wgOut->addHTML( "</ul>" ); + } + + if ( !empty( $results['data'] ) ) { + $wgOut->addHTML( "<ul>" ); + foreach ( $results['data'] as $key => $value ) { + if ( is_array( $value ) ) { + $wgOut->addHTML( "<li>$key:<ul>" ); + foreach ( $value as $key2 => $val2 ) { + $wgOut->addHTML( "<li>$key2: $val2" ); + } + $wgOut->addHTML( "</ul>" ); + } else { + $wgOut->addHTML( "<li>$key: $value" ); + } + } + $wgOut->addHTML( "</ul>" ); + } else { + $wgOut->addHTML( "Empty Results" ); + } + } + + /** + * Prepares the transactional message to be sent via Stomp to queueing service + * + * @param array $data + * @param array $resposneArray + * @param array $responseMsg + * @return array + */ + public function prepareStompTransaction( $data, $responseArray, $responseMsg ) { + $countries = $this->getCountries(); + + $transaction = array( ); + + // include response message + $transaction['response'] = $responseMsg; + + // include date + $transaction['date'] = time(); + + // put all data into one array + $optout = $this->determineOptOut( $data ); + $data['anonymous'] = $optout['anonymous']; + $data['optout'] = $optout['optout']; + + $transaction += array_merge( $data, $responseArray ); + + return $transaction; + } + + public function getPossibleErrors() { + return array( + 'general' => '', + 'retryMsg' => '', + 'invalidamount' => '', + 'card_num' => '', + 'card_type' => '', + 'cvv' => '', + 'fname' => '', + 'lname' => '', + 'city' => '', + 'country' => '', + 'street' => '', + 'state' => '', + 'zip' => '', + 'emailAdd' => '', + ); + } + + /** + * Handle redirection of form content to PayPal + * + * @fixme If we can update contrib tracking table in ContributionTracking + * extension, we can probably get rid of this method and just submit the form + * directly to the paypal URL, and have all processing handled by ContributionTracking + * This would make this a lot less hack-ish + */ + public function paypalRedirect( &$data ) { + global $wgPayflowProGatewayPaypalURL, $wgOut; + + // if we don't have a URL enabled throw a graceful error to the user + if ( !strlen( $wgPayflowProGatewayPaypalURL ) ) { + $this->errors['general']['nopaypal'] = wfMsg( 'payflow_gateway-error-msg-nopaypal' ); + return; + } + + // update the utm source to set the payment instrument to pp rather than cc + $utm_source_parts = explode( ".", $data['utm_source'] ); + $utm_source_parts[2] = 'pp'; + $data['utm_source'] = implode( ".", $utm_source_parts ); + $data['gateway'] = 'paypal'; + $data['currency_code'] = $data['currency']; + /** + * update contribution tracking + */ + $this->updateContributionTracking( $data, true ); + + $wgPayflowProGatewayPaypalURL .= "/" . $data['language'] . "?gateway=paypal"; + + // submit the data to the paypal redirect URL + $wgOut->redirect( $wgPayflowProGatewayPaypalURL . '&' . http_build_query( $data ) ); + } + + public static function log( $msg, $log_level=LOG_INFO ) { + $this->adapter->log( $msg, $log_level ); + } + +} + +// end class Property changes on: branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_resultswitcher.body.php ___________________________________________________________________ Added: svn:eol-style + native Added: branches/fundraising/extensions/DonationInterface/modules/iframe.liberator.js =================================================================== --- branches/fundraising/extensions/DonationInterface/modules/iframe.liberator.js (rev 0) +++ branches/fundraising/extensions/DonationInterface/modules/iframe.liberator.js 2011-09-16 22:02:26 UTC (rev 97341) @@ -0,0 +1,4 @@ + +if (top.frames.length!=0){ + top.location=self.document.location; +} \ No newline at end of file Property changes on: branches/fundraising/extensions/DonationInterface/modules/iframe.liberator.js ___________________________________________________________________ Added: svn:eol-style + native _______________________________________________ MediaWiki-CVS mailing list MediaWiki-CVS@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs