Index: branches/fundraising/extensions/DonationInterface/donationinterface.php =================================================================== --- branches/fundraising/extensions/DonationInterface/donationinterface.php (revision 97340) +++ branches/fundraising/extensions/DonationInterface/donationinterface.php (revision 97341) @@ -1,107 +1,107 @@ 'Donation Interface', 'author' => 'Katie Horn', 'version' => '1.0.0', 'descriptionmsg' => 'donationinterface-desc', 'url' => 'http://www.mediawiki.org/wiki/Extension:DonationInterface', ); $donationinterface_dir = dirname( __FILE__ ) . '/'; require_once( $donationinterface_dir . 'donate_interface/donate_interface.php' ); /** * Global form dir and whitelist */ $wgDonationInterfaceHtmlFormDir = dirname( __FILE__ ) . "/gateway_forms/html"; //ffname is the $key from now on. $wgDonationInterfaceAllowedHtmlForms = array( 'demo' => $wgDonationInterfaceHtmlFormDir . "/demo.html", 'globalcollect_test' => $wgDonationInterfaceHtmlFormDir . "/globalcollect_test.html", ); //load all possible form classes $wgAutoloadClasses['Gateway_Form'] = $donationinterface_dir . 'gateway_forms/Form.php'; $wgAutoloadClasses['Gateway_Form_OneStepTwoColumn'] = $donationinterface_dir . 'gateway_forms/OneStepTwoColumn.php'; $wgAutoloadClasses['Gateway_Form_TwoStepTwoColumn'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumn.php'; $wgAutoloadClasses['Gateway_Form_TwoColumnPayPal'] = $donationinterface_dir . 'gateway_forms/TwoColumnPayPal.php'; $wgAutoloadClasses['Gateway_Form_TwoColumnLetter'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter.php'; $wgAutoloadClasses['Gateway_Form_TwoColumnLetter2'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter2.php'; $wgAutoloadClasses['Gateway_Form_TwoColumnLetter3'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter3.php'; $wgAutoloadClasses['Gateway_Form_TwoColumnLetter4'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter4.php'; $wgAutoloadClasses['Gateway_Form_TwoColumnLetter5'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter5.php'; $wgAutoloadClasses['Gateway_Form_TwoColumnLetter6'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter6.php'; $wgAutoloadClasses['Gateway_Form_TwoColumnLetter7'] = $donationinterface_dir . 'gateway_forms/TwoColumnLetter7.php'; $wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnLetter'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnLetter.php'; $wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnLetterCA'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnLetterCA.php'; $wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnLetter2'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnLetter2.php'; $wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnLetter3'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnLetter3.php'; $wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnPremium'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnPremium.php'; $wgAutoloadClasses['Gateway_Form_TwoStepTwoColumnPremiumUS'] = $donationinterface_dir . 'gateway_forms/TwoStepTwoColumnPremiumUS.php'; $wgAutoloadClasses['Gateway_Form_RapidHtml'] = $donationinterface_dir . 'gateway_forms/RapidHtml.php'; $wgAutoloadClasses['Gateway_Form_SingleColumn'] = $donationinterface_dir . 'gateway_forms/SingleColumn.php'; $wgAutoloadClasses['DonationData'] = $donationinterface_dir . 'gateway_common/DonationData.php'; $wgAutoloadClasses['GatewayAdapter'] = $donationinterface_dir . 'gateway_common/gateway.adapter.php'; //THE GATEWAYS WILL RESET THIS when they are instantiated. You can override it, but it won't stick around that way. $wgDonationInterfaceTest = false; //This is going to be a little funky. //Override this in LocalSettings.php BEFORE you include this file, if you want //to disable gateways. //TODO: Unfunktify, if you have a better idea here for auto-loading the classes after LocalSettings.php runs all the way. if ( !isset( $wgDonationInterfaceEnabledGateways ) ) { $wgDonationInterfaceEnabledGateways = array( 'paypal', 'payflowpro', 'globalcollect' ); } foreach ( $wgDonationInterfaceEnabledGateways as $gateway ) { //include 'em require_once( $donationinterface_dir . $gateway . '_gateway/' . $gateway . '_gateway.php' ); } /** * Hooks required to interface with the donation extension (include on page) * * gwValue supplies the value of the form option, the name that appears on the form * and the currencies supported by the gateway in the $values array */ //$wgHooks['DonationInterface_Value'][] = 'pfpGatewayValue'; //$wgHooks['DonationInterface_Page'][] = 'pfpGatewayPage'; # 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'; return true; } Index: branches/fundraising/extensions/DonationInterface/gateway_common/gateway.adapter.php =================================================================== --- branches/fundraising/extensions/DonationInterface/gateway_common/gateway.adapter.php (revision 97340) +++ branches/fundraising/extensions/DonationInterface/gateway_common/gateway.adapter.php (revision 97341) @@ -1,569 +1,623 @@ url = self::getGlobal( 'URL' ); $wgDonationInterfaceTest = false; } else { $this->url = self::getGlobal( 'TestingURL' ); $wgDonationInterfaceTest = true; } $this->dataObj = new DonationData( get_called_class(), $wgDonationInterfaceTest ); $this->postdata = $this->dataObj->getData(); //TODO: Fix this a bit. $this->posted = $this->dataObj->wasPosted(); $this->setPostDefaults(); $this->defineTransactions(); $this->defineVarMap(); $this->defineAccountInfo(); $this->defineReturnValueMap(); $this->displaydata = $this->postdata; $this->stageData(); } /** * 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( 'order_id' => '112358' . rand(), 'amount' => '11.38', 'currency' => 'USD', 'language' => 'en', 'country' => 'US', 'returnto' => $returnto, 'user_ip' => ( self::getGlobal( 'Test' ) ) ? '12.12.12.12' : wfGetIP(), // current user's IP address 'card_type' => 'visa', ); } function checkTokens() { return $this->dataObj->checkTokens(); } function getData() { return $this->postdata; } function getDisplayData() { return $this->displaydata; } function isCache() { return $this->dataObj->isCache(); } static function getGlobal( $varname ) { static $gotten = array( ); //cache. $globalname = self::getGlobalPrefix() . $varname; if ( !array_key_exists( $globalname, $gotten ) ) { global $$globalname; $gotten[$globalname] = $$globalname; } return $gotten[$globalname]; } function getValue( $gateway_field_name, $token = false ) { if ( empty( $this->transactions ) ) { //TODO: These dies should all just throw fatal errors instead. die( 'Transactions structure is empty! Aborting.' ); } //How do we determine the value of a field asked for in a particular transaction? $transaction = $this->currentTransaction(); //If there's a hard-coded value in the transaction definition, use that. if ( !empty( $transaction ) ) { if ( array_key_exists( $transaction, $this->transactions ) && is_array( $this->transactions[$transaction] ) && array_key_exists( 'values', $this->transactions[$transaction] ) && array_key_exists( $gateway_field_name, $this->transactions[$transaction]['values'] ) ) { return $this->transactions[$transaction]['values'][$gateway_field_name]; } } //if it's account info, use that. //$this->accountInfo; if ( array_key_exists( $gateway_field_name, $this->accountInfo ) ) { return $this->accountInfo[$gateway_field_name]; } //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 ) && $this->postdata[$this->var_map[$gateway_field_name]] !== '' ) { //if it was sent, use that. return $this->postdata[$this->var_map[$gateway_field_name]]; } else { //return the default for that form value return $this->postdatadefaults[$this->var_map[$gateway_field_name]]; } } //not in the map, or hard coded. What then? //Complain furiously, for your code is faulty. //TODO: Something that plays nice with others, instead of... die( "getValue found NOTHING for $gateway_field_name, $transaction." ); } function buildRequestXML() { $this->xmlDoc = new DomDocument( '1.0' ); $node = $this->xmlDoc->createElement( 'XML' ); $structure = $this->transactions[$this->currentTransaction()]['request']; $this->buildTransactionNodes( $structure, $node ); $this->xmlDoc->appendChild( $node ); return $this->xmlDoc->saveXML(); } function buildTransactionNodes( $structure, &$node, $js = false ) { $transaction = $this->currentTransaction(); if ( !is_array( $structure ) ) { //this is a weird case that shouldn't ever happen. I'm just being... thorough. But, yeah: It's like... the base-1 case. $this->appendNodeIfValue( $structure, $node, $js ); } else { foreach ( $structure as $key => $value ) { if ( !is_array( $value ) ) { //do not use $key. $key is meaningless in this case. $this->appendNodeIfValue( $value, $node, $js ); } else { $keynode = $this->xmlDoc->createElement( $key ); $this->buildTransactionNodes( $value, $keynode, $js ); $node->appendChild( $keynode ); } } } //not actually returning anything. It's all side-effects. Because I suck like that. } function appendNodeIfValue( $value, &$node, $js = false ) { $nodevalue = $this->getValue( $value, $js ); if ( $nodevalue !== '' && $nodevalue !== false ) { $temp = $this->xmlDoc->createElement( $value, $nodevalue ); $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' ); $structure = $this->transactions[$this->currentTransaction()]['request']; $this->buildTransactionNodes( $structure, $node, true ); $this->xmlDoc->appendChild( $node ); $xml = $this->xmlDoc->saveXML(); - $xmlStart = strpos($xml, ""); - self::log("XML START" . $xmlStart); + $xmlStart = strpos( $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; } //get the status of the response $formatted = $this->getFormattedResponse( $returned['result'] ); $returned['status'] = $this->getResponseStatus( $formatted ); //get errors $returned['errors'] = $this->getResponseErrors( $formatted ); //if we're still okay (hey, even if we're not), get relevent dataz. $returned['data'] = $this->getResponseData( $formatted ); $this->processResponse( $returned ); //TODO: Actually pull these from somewhere legit. if ( $returned['status'] === true ) { $returned['message'] = "$transaction Transaction Successful!"; } elseif ( $returned['status'] === false ) { $returned['message'] = "$transaction Transaction FAILED!"; } else { $returned['message'] = "$transaction Transaction... weird. I have no idea what happened there."; } return $returned; //speaking of universal form: //$result['status'] = something I wish could be boiled down to a bool, but that's way too optimistic, I think. //$result['message'] = whatever we want to display back? //$result['errors']['code']['message'] = //$result['data'][$whatever] = values they pass back to us for whatever reason. We might... log it, or pieces of it, or something? } function getCurlBaseOpts() { //I chose to return this as a function so it's easy to override. //TODO: probably this for all the junk I currently have stashed in the constructor. //...maybe. $opts = array( CURLOPT_URL => $this->url, CURLOPT_USERAGENT => Http::userAgent(), CURLOPT_HEADER => 1, CURLOPT_RETURNTRANSFER => 1, CURLOPT_TIMEOUT => self::getGlobal( 'Timeout' ), CURLOPT_FOLLOWLOCATION => 0, CURLOPT_SSL_VERIFYPEER => 0, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_FORBID_REUSE => true, CURLOPT_POST => 1, ); // set proxy settings if necessary if ( self::getGlobal( 'UseHTTPProxy' ) ) { $opts[CURLOPT_HTTPPROXYTUNNEL] = 1; $opts[CURLOPT_PROXY] = self::getGlobal( 'UseHTTPProxy' ); } return $opts; } function getCurlBaseHeaders() { $headers = array( 'Content-Type: text/' . $this->getCommunicationType() . '; charset=utf-8', 'X-VPS-Client-Timeout: 45', 'X-VPS-Request-ID:' . $this->postdatadefaults['order_id'], ); return $headers; } protected function currentTransaction( $transaction = '' ) { //get&set in one! static $current_transaction; if ( $transaction != '' ) { $current_transaction = $transaction; } if ( !isset( $current_transaction ) ) { return false; } return $current_transaction; } /** * Sends a name-value pair string to Payflow gateway * * @param $data String: The exact thing we want to send. */ 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(); $headers[] = 'Content-Length: ' . strlen( $data ); self::log( "Sending Data: " . $this->formatXmlString( $data ) ); $curl_opts = $this->getCurlBaseOpts(); $curl_opts[CURLOPT_HTTPHEADER] = $headers; $curl_opts[CURLOPT_POSTFIELDS] = $data; foreach ( $curl_opts as $option => $value ) { curl_setopt( $ch, $option, $value ); } // As suggested in the PayPal developer forum sample code, try more than once to get a response // in case there is a general network issue $i = 1; $return = array( ); while ( $i++ <= 3 ) { self::log( $this->postdatadefaults['order_id'] . ' Preparing to send transaction to ' . self::getGatewayName() ); $return['result'] = curl_exec( $ch ); $return['headers'] = curl_getinfo( $ch ); if ( $return['headers']['http_code'] != 200 && $return['headers']['http_code'] != 403 ) { self::log( $this->postdatadefaults['order_id'] . ' Failed sending transaction to ' . self::getGatewayName() . ', retrying' ); sleep( 1 ); } elseif ( $return['headers']['http_code'] == 200 || $return['headers']['http_code'] == 403 ) { self::log( $this->postdatadefaults['order_id'] . ' Finished sending transaction to ' . self::getGatewayName() ); break; } } - $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! //TODO: But also, fire off some kind of "No response from the gateway" thing to somebody so we know right away. $return['message'] = 'No response from ' . self::getGatewayName() . '. Please try again later!'; $when = time(); self::log( $this->postdatadefaults['order_id'] . ' No response from ' . self::getGatewayName() . ': ' . curl_error( $ch ) ); curl_close( $ch ); return $return; } curl_close( $ch ); return $return; } function stripXMLResponseHeaders( $rawResponse ) { $xmlStart = strpos( $rawResponse, '... $xmlStart = strpos( $rawResponse, ')(<)(\/*)/', "$1\n$2$3", $xml ); // now indent the tags $token = strtok( $xml, "\n" ); $result = ''; // holds formatted version as it is built $pad = 0; // initial indent $matches = array( ); // returns from preg_matches() // scan each line and adjust indent based on opening/closing tags while ( $token !== false ) : // test for the various tag states // 1. open and closing tags on same line - no change if ( preg_match( '/.+<\/\w[^>]*>$/', $token, $matches ) ) : $indent = 0; // 2. closing tag - outdent now elseif ( preg_match( '/^<\/\w/', $token, $matches ) ) : $pad--; // 3. opening tag - don't pad this one, only subsequent tags elseif ( preg_match( '/^<\w[^>]*[^\/]>.*$/', $token, $matches ) ) : $indent = 1; // 4. no indentation needed else : $indent = 0; endif; // pad the line with the required number of leading spaces $line = str_pad( $token, strlen( $token ) + $pad, ' ', STR_PAD_LEFT ); $result .= $line . "\n"; // add to the cumulative result, with linefeed $token = strtok( "\n" ); // get the next token $pad += $indent; // update the pad size for subsequent lines endwhile; return $result; } static function getCommunicationType() { $c = get_called_class(); return $c::COMMUNICATION_TYPE; } static function getGatewayName() { $c = get_called_class(); return $c::GATEWAY_NAME; } static function getGlobalPrefix() { $c = get_called_class(); return $c::GLOBAL_PREFIX; } static function getIdentifier() { $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 * @param type $additional * @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; $params['additional'] = $additional; //can we check to see if the table exists? Bah, I should almost certianly do this Fer Reals. $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 ) != '' ) { $data[$childnode->nodeName] = $childnode->nodeValue; } } } 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; + } + } Index: branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php =================================================================== --- branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php (revision 97340) +++ branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php (revision 97341) @@ -1,262 +1,275 @@ 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; break; case 'mastercard': $this->postdata['card_type'] = 3; break; case 'american': $this->postdata['card_type'] = 2; break; case 'discover': $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() { $this->accountInfo = array( 'MERCHANTID' => self::getGlobal( 'MerchantID' ), //'IPADDRESS' => '', //TODO: Not sure if this should be OUR ip, or the user's ip. Hurm. 'VERSION' => "1.0", ); } function defineVarMap() { $this->var_map = array( 'ORDERID' => 'order_id', 'AMOUNT' => 'amount', 'CURRENCYCODE' => 'currency', 'LANGUAGECODE' => 'language', 'COUNTRYCODE' => 'country', 'MERCHANTREFERENCE' => 'order_id', 'RETURNURL' => 'returnto', //TODO: Fund out where the returnto URL is supposed to be coming from. 'IPADDRESS' => 'user_ip', //TODO: Not sure if this should be OUR ip, or the user's ip. Hurm. 'PAYMENTPRODUCTID' => 'card_type', 'CVV' => 'cvv', 'EXPIRYDATE' => 'expiry', - 'CREDITCARDNUMBER' => 'card_num', + 'CREDITCARDNUMBER' => 'card_num', 'FIRSTNAME' => 'fname', 'SURNAME' => 'lname', 'STREET' => 'street', 'CITY' => 'city', 'STATE' => 'state', 'ZIP' => 'zip', 'EMAIL' => 'email', ); } function defineReturnValueMap() { $this->return_value_map = array( '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() { $this->transactions = array( ); $this->transactions['INSERT_ORDERWITHPAYMENT'] = array( 'request' => array( 'REQUEST' => array( 'ACTION', 'META' => array( 'MERCHANTID', // 'IPADDRESS', 'VERSION' ), 'PARAMS' => array( 'ORDER' => array( 'ORDERID', 'AMOUNT', 'CURRENCYCODE', 'LANGUAGECODE', 'COUNTRYCODE', 'MERCHANTREFERENCE' ), 'PAYMENT' => array( 'PAYMENTPRODUCTID', 'AMOUNT', 'CURRENCYCODE', 'LANGUAGECODE', 'COUNTRYCODE', 'HOSTEDINDICATOR', 'RETURNURL', // 'CVV', // 'EXPIRYDATE', // 'CREDITCARDNUMBER', 'FIRSTNAME', 'SURNAME', 'STREET', 'CITY', 'STATE', 'ZIP', 'EMAIL', ) ) ) ), 'values' => array( 'ACTION' => 'INSERT_ORDERWITHPAYMENT', 'HOSTEDINDICATOR' => '1' ), ); $this->transactions['TEST_CONNECTION'] = array( 'request' => array( 'REQUEST' => array( 'ACTION', 'META' => array( 'MERCHANTID', // 'IPADDRESS', 'VERSION' ), 'PARAMS' => array( ) ) ), 'values' => array( 'ACTION' => 'TEST_CONNECTION' ) ); $this->transactions['GET_ORDERSTATUS'] = array( 'request' => array( 'REQUEST' => array( 'ACTION', 'META' => array( 'MERCHANTID', // 'IPADDRESS', 'VERSION' ), 'PARAMS' => array( 'ORDER' => array( 'ORDERID', ), ) ) ), 'values' => array( 'ACTION' => 'GET_ORDERSTATUS', 'VERSION' => '2.0' ) ); } /** * Take the entire response string, and strip everything we don't care about. * For instance: If it's XML, we only want correctly-formatted XML. Headers must be killed off. * return a string. */ function getFormattedResponse( $rawResponse ) { $xmlString = $this->stripXMLResponseHeaders( $rawResponse ); $displayXML = $this->formatXmlString( $xmlString ); $realXML = new DomDocument( '1.0' ); self::log( "Here is the Raw XML: " . $displayXML ); //I am apparently a huge fibber. $realXML->loadXML( trim( $xmlString ) ); return $realXML; } /** * Parse the response to get the status. Not sure if this should return a bool, or something more... telling. */ function getResponseStatus( $response ) { $aok = true; foreach ( $response->getElementsByTagName( 'RESULT' ) as $node ) { if ( array_key_exists( $node->nodeValue, $this->return_value_map ) && $this->return_value_map[$node->nodeValue] !== true ) { $aok = false; } } return $aok; } /** * Parse the response to get the errors in a format we can log and otherwise deal with. * return a key/value array of codes (if they exist) and messages. */ function getResponseErrors( $response ) { $errors = array( ); foreach ( $response->getElementsByTagName( 'ERROR' ) as $node ) { $code = ''; $message = ''; foreach ( $node->childNodes as $childnode ) { if ( $childnode->nodeName === "CODE" ) { $code = $childnode->nodeValue; } if ( $childnode->nodeName === "MESSAGE" ) { $message = $childnode->nodeValue; } } $errors[$code] = $message; } return $errors; } /** * Harvest the data we need back from the gateway. * return a key/value array */ 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; } function processResponse( $response ) { //TODO: Stuff. } } Index: branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.php =================================================================== --- branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.php (revision 97340) +++ branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.php (revision 97341) @@ -1,115 +1,117 @@ 'GlobalCollect Gateway', 'author' => 'Four Kitchens', 'version' => '1.0.0', 'descriptionmsg' => 'globalcollect_gateway-desc', 'url' => 'http://www.mediawiki.org/wiki/Extension:GlobalCollectGateway', ); $wgGlobalCollectGatewayUseSyslog = false; // 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'; $wgGlobalCollectGatewayTestingURL = 'https://'; // GlobalCollect testing URL $wgGlobalCollectGatewayMerchantID = ''; // GlobalCollect ID // a boolean to determine if we're in testing mode $wgGlobalCollectGatewayTest = FALSE; // timeout in seconds for communicating with [gateway] $wgGlobalCollectGatewayTimeout = 2; /** * The default form to use */ $wgGlobalCollectGatewayDefaultForm = 'TwoStepTwoColumn'; /** * A string or array of strings for making tokens more secure * * Please set this! If you do not, tokens are easy to get around, which can * potentially leave you and your users vulnerable to CSRF or other forms of * attack. */ $wgGlobalCollectGatewaySalt = $wgSecretKey; /** * A string that can contain wikitext to display at the head of the credit card form * * This string gets run like so: $wg->addHtml( $wg->Parse( $wgpayflowGatewayHeader )) * You can use '@language' as a placeholder token to extract the user's language. * */ $wgGlobalCollectGatewayHeader = NULL; /** * A string containing full URL for Javascript-disabled credit card form redirect */ $wgGlobalCollectGatewayNoScriptRedirect = null; /** * Proxy settings * * If you need to use an HTTP proxy for outgoing traffic, * set wgGlobalCollectGatweayUseHTTPProxy=TRUE and set $wgGlobalCollectGatewayHTTPProxy * to the proxy desination. * eg: * $wgGlobalCollectGatewayUseHTTPProxy=TRUE; * $wgGlobalCollectGatewayHTTPProxy='192.168.1.1:3128' */ $wgGlobalCollectGatewayUseHTTPProxy = FALSE; $wgGlobalCollectGatewayHTTPProxy = ''; /** * Set the max-age value for Squid * * If you have Squid enabled for caching, use this variable to configure * the s-max-age for cached requests. * @var int Time in seconds */ $wgGlobalCollectGatewaySMaxAge = 6000; /** * Directory for HTML forms (used by RapidHtml form class) * @var string */ $wgGlobalCollectGatewayHtmlFormDir = dirname( __FILE__ ) . "/forms/html"; /** * An array of allowed HTML forms. * * Be sure to use full paths. If your HTML form is not listed here, it will * /never/ be loaded by the rapid html form loader! * @var string */ $wgGlobalCollectGatewayAllowedHtmlForms = $wgDonationInterfaceAllowedHtmlForms; /** * Configure price cieling and floor for valid contribution amount. Values * should be in USD. */ $wgGlobalCollectGatewayPriceFloor = '1.00'; $wgGlobalCollectGatewayPriceCieling = '10000.00'; Index: branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_resultswitcher.body.php =================================================================== --- branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_resultswitcher.body.php (nonexistent) +++ branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_resultswitcher.body.php (revision 97341) @@ -0,0 +1,190 @@ +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( "
    " ); + foreach ( $results['errors'] as $code => $value ) { + $wgOut->addHTML( "
  • Error $code: $value" ); + } + $wgOut->addHTML( "
" ); + } + + if ( !empty( $results['data'] ) ) { + $wgOut->addHTML( "
    " ); + foreach ( $results['data'] as $key => $value ) { + if ( is_array( $value ) ) { + $wgOut->addHTML( "
  • $key:
      " ); + foreach ( $value as $key2 => $val2 ) { + $wgOut->addHTML( "
    • $key2: $val2" ); + } + $wgOut->addHTML( "
    " ); + } else { + $wgOut->addHTML( "
  • $key: $value" ); + } + } + $wgOut->addHTML( "
" ); + } 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 ## -0,0 +1 ## +native \ No newline at end of property Index: branches/fundraising/extensions/DonationInterface/modules/iframe.liberator.js =================================================================== --- branches/fundraising/extensions/DonationInterface/modules/iframe.liberator.js (nonexistent) +++ branches/fundraising/extensions/DonationInterface/modules/iframe.liberator.js (revision 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 ## -0,0 +1 ## +native \ No newline at end of property