r95821 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r95820‎ | r95821 | r95822 >
Date:23:27, 30 August 2011
Author:khorn
Status:ok (Comments)
Tags:fundraising 
Comment:
Now able to see the results of a transaction on the globalcollect results page. Nothing has been abstracted out yet, really. That's the next step.
Modified paths:
  • /branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php (modified) (history)
  • /branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.body.php (modified) (history)
  • /branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.php (modified) (history)

Diff [purge]

Index: branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.body.php
@@ -104,6 +104,17 @@
105105 // track the number of attempts the user has made
106106 $numAttempt = $wgRequest->getVal( 'numAttempt', 0 );
107107
 108+
 109+
 110+
 111+
 112+
 113+
 114+
 115+
 116+
 117+
 118+
108119 // Get array of default account values necessary for Payflow
109120 // require_once( 'includes/payflowUser.inc' );
110121
@@ -150,29 +161,17 @@
151162
152163 // Populate form data
153164 // $data = $this->fnGetFormData( $amount, $numAttempt, $token, $payflow_data['order_id'], $payflow_data['i_order_id'] );
154 - $data = $this->fnGetFormData( $amount, $numAttempt, null, null, null );
155165
156 -
157 - //This is just me screwing around.
158 - $dir = dirname( __FILE__ ) . '/';
159 - require_once($dir . 'globalcollect.adapter.php');
160 - $adapter = new GlobalCollectAdapter($data);
161 - error_log(print_r($data, true));
162 - error_log($adapter->buildRequestXML( 'INSERT_ORDERWITHPAYMENT' ));
163 - //$wgOut->addHTML('<pre>' . $adapter->buildRequestXML( 'INSERT_ORDERWITHPAYMENT' ) . '</pre>');
164166
165167
166 - /**
167 - * handle PayPal redirection
168 - *
169 - * if paypal redirection is enabled ($wgPayflowGatewayPaypalURL must be defined)
170 - * and the PaypalRedirect form value must be true
171 - */
172 - if ( $wgRequest->getText( 'PaypalRedirect', 0 ) ) {
173 - $this->paypalRedirect( $data );
174 - return;
175 - }
 168+
 169+
 170+
 171+
 172+
 173+ $data = $this->fnGetFormData( $amount, $numAttempt, null, null, null );
176174
 175+
177176 // dispatch forms/handling
178177 // if ( $token_match ) {
179178 if ( $data['payment_method'] == 'processed' ) {
@@ -187,40 +186,55 @@
188187 $this->fnPayflowDisplayForm( $data, $this->errors );
189188 } else { // The submitted form data is valid, so process it
190189 // allow any external validators to have their way with the data
191 - self::log( $data[ 'order_id' ] . " Preparing to query MaxMind" );
192 - wfRunHooks( 'PayflowGatewayValidate', array( &$this, &$data ) );
193 - self::log( $data[ 'order_id' ] . ' Finished querying Maxmind' );
194 -
195 - // if the transaction was flagged for review
196 - if ( $this->action == 'review' ) {
197 - // expose a hook for external handling of trxns flagged for review
198 - wfRunHooks( 'PayflowGatewayReview', array( &$this, &$data ));
199 - }
200 -
201 - // if the transaction was flagged to be 'challenged'
202 - if ( $this->action == 'challenge' ) {
203 - // expose a hook for external handling of trxns flagged for challenge (eg captcha)
204 - wfRunHooks( 'PayflowGatewayChallenge', array( &$this, &$data ) );
205 - }
206 -
207 - // if the transaction was flagged for rejection
208 - if ( $this->action == 'reject' ) {
209 - // expose a hook for external handling of trxns flagged for rejection
210 - wfRunHooks( 'PayflowGatewayReject', array( &$this, &$data ) );
211 -
212 - $this->fnPayflowDisplayDeclinedResults( '' );
213 - $this->fnPayflowUnsetEditToken();
214 - }
215 -
216 - // if the transaction was flagged for processing
217 - if ( $this->action == 'process' ) {
218 - // expose a hook for external handling of trxns ready for processing
219 - wfRunHooks( 'PayflowGatewayProcess', array( &$this, &$data ) );
220 - $this->fnGlobalCollectProcessTransaction( $data, $payflow_data );
221 - }
222 -
223 - // expose a hook for any post processing
224 - wfRunHooks( 'PayflowGatewayPostProcess', array( &$this, &$data ) );
 190+
 191+
 192+
 193+ //This is just me screwing around.
 194+ $dir = dirname( __FILE__ ) . '/';
 195+ require_once($dir . 'globalcollect.adapter.php');
 196+ $adapter = new GlobalCollectAdapter($data);
 197+ self::log("Data used for adapter init: " . print_r($data, true));
 198+ $result = $adapter->do_transaction( 'INSERT_ORDERWITHPAYMENT' );
 199+ //$result = $adapter->do_transaction( 'TEST_CONNECTION' );
 200+
 201+ $wgOut->addHTML($result['message']);
 202+
 203+
 204+
 205+// self::log( $data[ 'order_id' ] . " Preparing to query MaxMind" );
 206+// wfRunHooks( 'PayflowGatewayValidate', array( &$this, &$data ) );
 207+// self::log( $data[ 'order_id' ] . ' Finished querying Maxmind' );
 208+//
 209+// // if the transaction was flagged for review
 210+// if ( $this->action == 'review' ) {
 211+// // expose a hook for external handling of trxns flagged for review
 212+// wfRunHooks( 'PayflowGatewayReview', array( &$this, &$data ));
 213+// }
 214+//
 215+// // if the transaction was flagged to be 'challenged'
 216+// if ( $this->action == 'challenge' ) {
 217+// // expose a hook for external handling of trxns flagged for challenge (eg captcha)
 218+// wfRunHooks( 'PayflowGatewayChallenge', array( &$this, &$data ) );
 219+// }
 220+//
 221+// // if the transaction was flagged for rejection
 222+// if ( $this->action == 'reject' ) {
 223+// // expose a hook for external handling of trxns flagged for rejection
 224+// wfRunHooks( 'PayflowGatewayReject', array( &$this, &$data ) );
 225+//
 226+// $this->fnPayflowDisplayDeclinedResults( '' );
 227+// $this->fnPayflowUnsetEditToken();
 228+// }
 229+//
 230+// // if the transaction was flagged for processing
 231+// if ( $this->action == 'process' ) {
 232+// // expose a hook for external handling of trxns ready for processing
 233+// wfRunHooks( 'PayflowGatewayProcess', array( &$this, &$data ) );
 234+// $this->fnGlobalCollectProcessTransaction( $data, $payflow_data );
 235+// }
 236+//
 237+// // expose a hook for any post processing
 238+// wfRunHooks( 'PayflowGatewayPostProcess', array( &$this, &$data ) );
225239 }
226240 } else {
227241 // Display form for the first time
@@ -361,122 +375,15 @@
362376 } elseif ( preg_match( '/^6(?:011|5[0-9]{2})[0-9]{12}$/', $data[ 'card_num' ] ) ) { // discover
363377 $data[ 'card' ] = 'discover';
364378 } else { // an invalid credit card number was entered
365 - $error_result = '1';
366 - $error[ 'card_num' ] = wfMsg( 'globalcollect_gateway-error-msg-card-num' );
 379+ //TODO: Make sure this is uncommented when you commit for reals!
 380+ //$error_result = '1';
 381+ //$error[ 'card_num' ] = wfMsg( 'globalcollect_gateway-error-msg-card-num' );
367382 }
368383
369384 return $error_result;
370385 }
371386
372387 /**
373 - * Sends a name-value pair string to Payflow gateway
374 - *
375 - * @param $data Array: array of user input
376 - * @param $payflow_data Array: array of necessary Payflow variables to
377 - * include in string (i.e. Vendor, password)
378 - */
379 - private function fnGlobalCollectProcessTransaction( $data, $payflow_data ) {
380 - global $wgOut, $wgDonationTestingMode, $wgPayflowGatewayUseHTTPProxy, $wgPayflowGatewayHTTPProxy, $wgGlobalCollectTimeout;
381 -
382 - // update contribution tracking
383 - $this->updateContributionTracking( $data, defined( 'OWA' ) );
384 -
385 - // create payflow query string, include string lengths
386 - $queryArray = array(
387 - 'TRXTYPE' => $payflow_data['trxtype'],
388 - 'TENDER' => $payflow_data['tender'],
389 - 'USER' => $payflow_data['user'],
390 - 'VENDOR' => $payflow_data['vendor'],
391 - 'PARTNER' => $payflow_data['partner'],
392 - 'PWD' => $payflow_data['password'],
393 - 'ACCT' => $data['card_num'],
394 - 'EXPDATE' => $data['expiration'],
395 - 'AMT' => $data['amount'],
396 - 'FIRSTNAME' => $data['fname'],
397 - 'LASTNAME' => $data['lname'],
398 - 'STREET' => $data['street'],
399 - 'CITY' => $data['city'],
400 - 'STATE' => $data['state'],
401 - 'COUNTRY' => $data['country'],
402 - 'ZIP' => $data['zip'],
403 - 'INVNUM' => $data['order_id'],
404 - 'CVV2' => $data['cvv'],
405 - 'CURRENCY' => $data['currency'],
406 - 'VERBOSITY' => $payflow_data['verbosity'],
407 - 'CUSTIP' => $payflow_data['user_ip'],
408 - );
409 -
410 - foreach ( $queryArray as $name => $value ) {
411 - $query[] = $name . '[' . strlen( $value ) . ']=' . $value;
412 - }
413 -
414 - $queryString = implode( '&', $query );
415 -
416 - $payflow_query = $queryString;
417 -
418 - // assign header data necessary for the curl_setopt() function
419 - $user_agent = Http::userAgent();
420 - $headers[] = 'Content-Type: text/namevalue';
421 - $headers[] = 'Content-Length : ' . strlen( $payflow_query );
422 - $headers[] = 'X-VPS-Client-Timeout: 45';
423 - $headers[] = 'X-VPS-Request-ID:' . $data['order_id'];
424 - $ch = curl_init();
425 - $paypalPostTo = isset ( $wgDonationTestingMode ) ? 'testingurl' : 'paypalurl';
426 - curl_setopt( $ch, CURLOPT_URL, $payflow_data[ $paypalPostTo ] );
427 - curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
428 - curl_setopt( $ch, CURLOPT_USERAGENT, $user_agent );
429 - curl_setopt( $ch, CURLOPT_HEADER, 1 );
430 - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
431 - curl_setopt( $ch, CURLOPT_TIMEOUT, $wgGlobalCollectTimeout );
432 - curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 0 );
433 - curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
434 - curl_setopt( $ch, CURLOPT_POSTFIELDS, $payflow_query );
435 - curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
436 - curl_setopt( $ch, CURLOPT_FORBID_REUSE, true );
437 - curl_setopt( $ch, CURLOPT_POST, 1 );
438 -
439 - // set proxy settings if necessary
440 - if ( $wgPayflowGatewayUseHTTPProxy ) {
441 - curl_setopt( $ch, CURLOPT_HTTPPROXYTUNNEL, 1 );
442 - curl_setopt( $ch, CURLOPT_PROXY, $wgPayflowGatewayHTTPProxy );
443 - }
444 -
445 - // As suggested in the PayPal developer forum sample code, try more than once to get a response
446 - // in case there is a general network issue
447 - $i = 1;
448 -
449 - while ( $i++ <= 3 ) {
450 - self::log( $data[ 'order_id' ] . ' Preparing to send transaction to GlobalCollect' );
451 - $result = curl_exec( $ch );
452 - $headers = curl_getinfo( $ch );
453 -
454 - if ( $headers['http_code'] != 200 && $headers['http_code'] != 403 ) {
455 - self::log( $data[ 'order_id' ] . ' Failed sending transaction to GlobalCollect, retrying' );
456 - sleep( 1 );
457 - } elseif ( $headers['http_code'] == 200 || $headers['http_code'] == 403 ) {
458 - self::log( $data[ 'order_id' ] . ' Finished sending transaction to GlobalCollect' );
459 - break;
460 - }
461 - }
462 -
463 - if ( $headers['http_code'] != 200 ) {
464 - $wgOut->addHTML( '<h3>No response from credit card processor. Please try again later!</h3><p>' );
465 - $when = time();
466 - self::log( $data[ 'order_id' ] . ' No response from credit card processor: ' . curl_error( $ch ) );
467 - curl_close( $ch );
468 - return;
469 - }
470 -
471 - curl_close( $ch );
472 -
473 - // get result string
474 - $result = strstr( $result, 'RESULT' );
475 -
476 - // parse string and display results to the user
477 - $this->fnPayflowGetResults( $data, $result );
478 - }
479 -
480 - /**
481388 * "Reads" the name-value pair result string returned by Payflow and creates corresponding error messages
482389 *
483390 * @param $data Array: array of user input
@@ -1294,10 +1201,10 @@
12951202 }
12961203
12971204 public static function log( $msg, $identifier='globalcollect_gateway', $log_level=LOG_INFO ) {
1298 - global $wgPayflowGatewayUseSyslog;
 1205+ global $wgGlobalCollectGatewayUseSyslog;
12991206
13001207 // if we're not using the syslog facility, use wfDebugLog
1301 - if ( !$wgPayflowGatewayUseSyslog ) {
 1208+ if ( !$wgGlobalCollectGatewayUseSyslog ) {
13021209 wfDebugLog( $identifier, $msg );
13031210 return;
13041211 }
Index: branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php
@@ -14,13 +14,19 @@
1515 private $xmlDoc;
1616
1717 function __construct($data){
 18+ //TODO: Squish ALL the globals here, in the constructor.
 19+ //easier to abstract out that way.
1820 global $wgGatewayTest;
1921
2022 $this->postdata = $data;
2123
 24+ //TODO: Make a Thing in which we do things like this.
 25+ $this->postdata['amount'] = $this->postdata['amount'] * 100;
2226
 27+
2328 $returnTitle = Title::newFromText( 'Donate-thanks/en' );
2429 $returnto = $returnTitle->getFullURL();
 30+ $returnto = "http://www.katiehorn.com";
2531
2632 //this DEFINITELY needs to be defined in the parent class, and contain everything anybody might want to know.
2733 $this->postdatadefaults = array(
@@ -46,12 +52,22 @@
4753 'COUNTRYCODE' => 'country',
4854 'MERCHANTREFERENCE' => 'order_id',
4955 'RETURNURL' => 'returnto', //I think. It might not even BE here yet. Boo-urns.
50 - 'IPADDRESS' => 'user_ip' //TODO: Not sure if this should be OUR ip, or the user's ip. Hurm.
 56+ 'IPADDRESS' => 'user_ip', //TODO: Not sure if this should be OUR ip, or the user's ip. Hurm.
 57+
5158 );
5259
 60+ $this->return_value_map = array(
 61+ 'OK' => true,
 62+ 'NOK' => false,
 63+ );
 64+
5365 global $wgGlobalCollectURL, $wgGlobalCollectMerchantID;
 66+ if ($wgGatewayTest){
 67+ $this->url = $wgGlobalCollectURL;
 68+ } else {
 69+ $this->url = $wgGlobalCollectURL;
 70+ }
5471
55 - $this->url = $wgGlobalCollectURL;
5672
5773 $this->accountInfo = array(
5874 // 'PAYMENTPRODUCTID' => '3', //actually, these are almost certainly transaction-specific.
@@ -61,6 +77,7 @@
6278 'VERSION' => "1.0",
6379 );
6480
 81+ //oof. This is getting a little long and unwieldy. Maybe we should build it. Or maybe that sucks. I can't tell yet.
6582 /* General idea here:
6683 * This bad boy will (probably) contain the structure of all possible transactions as defined by the gateway.
6784 * First array key: Some way for us to id the transaction. Doesn't actually have to be the gateway's name for it, but
@@ -80,7 +97,7 @@
8198 'ACTION',
8299 'META' => array(
83100 'MERCHANTID',
84 - 'IPADDRESS',
 101+// 'IPADDRESS',
85102 'VERSION'
86103 ),
87104 'PARAMS' => array(
@@ -109,12 +126,61 @@
110127 'HOSTEDINDICATOR' => '1',
111128 'PAYMENTPRODUCTID' => '3',
112129 ),
113 - )
 130+ 'result' => array( //just like the rest: This is still in flux. But the idea here is that this is the sctucture you'd scan for.
 131+ 'RESULT' => 'value'
 132+ ),
 133+ 'result_errors' => array(
 134+ 'ERROR' => array(
 135+ 'CODE' => 'value', //as opposed to "attribute", which would imply that it belongs to the parent...
 136+ 'MESSAGE' => 'value',
 137+ )
 138+ ),
 139+ 'result_data' => array(
 140+ 'ROW' => array(
 141+ //uhh... presumably we'd look for some Stuff in here.
 142+ )
 143+ )
 144+ ),
 145+ 'TEST_CONNECTION' => array(
 146+ 'request' => array(
 147+ 'REQUEST' => array(
 148+ 'ACTION',
 149+ 'META' => array(
 150+ 'MERCHANTID',
 151+// 'IPADDRESS',
 152+ 'VERSION'
 153+ ),
 154+ 'PARAMS' => array()
 155+ )
 156+ ),
 157+ 'values' => array(
 158+ 'ACTION' => 'TEST_CONNECTION'
 159+ ),
 160+ 'result' => array( //just like the rest: This is still in flux. But the idea here is that this is the sctucture you'd scan for.
 161+ 'RESULT' => 'value'
 162+ ),
 163+ 'result_errors' => array(
 164+ 'ERROR' => array(
 165+ 'CODE' => 'value', //as opposed to "attribute", which would imply that it belongs to the parent...
 166+ 'MESSAGE' => 'value',
 167+ )
 168+ ),
 169+ 'result_data' => array(
 170+ 'ROW' => array(
 171+ //uhh... presumably we'd look for some Stuff in here.
 172+ )
 173+ )
 174+ ),
114175 );
115176
116177 }
117178
118 - function getValue($gateway_field_name, $transaction){
 179+ function getCommunicationType(){
 180+ return 'xml'; //'xml' or 'namevalue'.
 181+ }
 182+
 183+
 184+ function getValue($gateway_field_name, $transaction = ''){
119185 //How do we determine the value of a field asked for in a particular transaction?
120186
121187 //If there's a hard-coded value in the transaction definition, use that.
@@ -150,30 +216,31 @@
151217
152218 }
153219
154 - function buildRequestXML($transaction){
 220+ function buildRequestXML(){
155221 $this->xmlDoc = new DomDocument('1.0');
156222 $node = $this->xmlDoc->createElement('XML');
157223
158 - $structure = $this->transactions[$transaction]['request'];
 224+ $structure = $this->transactions[$this->currentTransaction()]['request'];
159225
160 - $this->buildTransactionNodes($transaction, $structure, $node);
 226+ $this->buildTransactionNodes($structure, $node);
161227 $this->xmlDoc->appendChild($node);
162228 return $this->xmlDoc->saveXML();
163229 }
164230
165231
166 - function buildTransactionNodes($transaction, $structure, &$node){
 232+ function buildTransactionNodes($structure, &$node){
 233+ $transaction = $this->currentTransaction();
167234
168235 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.
169 - $this->appendNodeIfValue($structure, $transaction, $node);
 236+ $this->appendNodeIfValue($structure, $node);
170237 } else {
171238 foreach ($structure as $key => $value){
172239 if (!is_array($value)){
173240 //do not use $key. $key is meaningless in this case.
174 - $this->appendNodeIfValue($value, $transaction, $node);
 241+ $this->appendNodeIfValue($value, $node);
175242 } else {
176243 $keynode = $this->xmlDoc->createElement($key);
177 - $this->buildTransactionNodes($transaction, $value, $keynode);
 244+ $this->buildTransactionNodes( $value, $keynode);
178245 $node->appendChild($keynode);
179246 }
180247 }
@@ -181,180 +248,299 @@
182249 //not actually returning anything. It's all side-effects. Because I suck like that.
183250 }
184251
185 - function appendNodeIfValue($value, $transaction, &$node){
186 - $nodevalue = $this->getValue($value, $transaction);
 252+ function appendNodeIfValue($value, &$node){
 253+ $nodevalue = $this->getValue($value, $this->currentTransaction());
187254 if ($nodevalue !== '' && $nodevalue !== false){
188255 $temp = $this->xmlDoc->createElement($value, $nodevalue);
189256 $node->appendChild($temp);
190257 }
191258 }
192259
193 - function sendRequestXML($transaction){
194 - $xml = $this->buildRequestXML($transaction);
 260+ function do_transaction($transaction){
 261+ $this->currentTransaction($transaction);
 262+ if ($this->getCommunicationType() === 'xml'){
 263+ $xml = $this->buildRequestXML();
 264+ $response = $this->curl_transaction($xml);
 265+ //put the response in a universal form, and return it.
 266+ }
195267
 268+ //TODO: Actually pull these from somewhere legit.
 269+ if ($response['status'] === true){
 270+ $response['message'] = "$transaction Transaction Successful!";
 271+ } elseif ($response['status'] === false){
 272+ $response['message'] = "$transaction Transaction FAILED!";
 273+ } else {
 274+ $response['message'] = "$transaction Transaction... weird. I have no idea what happened there.";
 275+ }
 276+
 277+ return $response;
 278+
 279+ //speaking of universal form:
 280+ //$result['status'] = something I wish could be boiled down to a bool, but that's way too optimistic, I think.
 281+ //$result['message'] = whatever we want to display back?
 282+ //$result['errors'][]['code'] = their error code
 283+ //$result['errors'][]['value'] = Error message
 284+ //$result['return'][$whatever] = values they pass back to us for whatever reason. We might... log it, or pieces of it, or something?
196285 }
 286+
 287+ function getCurlBaseOpts(){
 288+ //I chose to return this as a function so it's easy to override.
 289+ //TODO: probably this for all the junk I currently have stashed in the constructor.
 290+ //...maybe.
 291+ global $wgGlobalCollectTimeout, $wgPayflowGatewayUseHTTPProxy;
 292+ $opts = array(
 293+ CURLOPT_URL => $this->url,
 294+ //CURLOPT_USERAGENT => Http::userAgent(),
 295+ CURLOPT_HEADER => 1,
 296+ CURLOPT_RETURNTRANSFER => 1,
 297+ CURLOPT_TIMEOUT => $wgGlobalCollectTimeout,
 298+ //CURLOPT_FOLLOWLOCATION => 0,
 299+ //CURLOPT_SSL_VERIFYPEER => 0,
 300+ //CURLOPT_SSL_VERIFYHOST => 2,
 301+ //CURLOPT_FORBID_REUSE => true,
 302+ CURLOPT_POST => 1,
 303+ );
197304
 305+ // set proxy settings if necessary
 306+ if ( $wgPayflowGatewayUseHTTPProxy ) {
 307+ $opts[CURLOPT_HTTPPROXYTUNNEL] = 1;
 308+ $opts[CURLOPT_PROXY] = $wgPayflowGatewayHTTPProxy;
 309+ }
 310+ return $opts;
 311+ }
 312+
 313+
 314+ function getCurlBaseHeaders(){
 315+ $headers = array (
 316+ 'Content-Type: text/' . $this->getCommunicationType() . '; charset=utf-8',
 317+ // 'X-VPS-Client-Timeout: 45',
 318+ // 'X-VPS-Request-ID:' . $this->postdatadefaults['order_id'],
 319+ );
 320+ return $headers;
 321+ }
 322+
 323+ private function currentTransaction($transaction = ''){ //get&set in one!
 324+ static $current_transaction;
 325+ if ($transaction != ''){
 326+ $current_transaction = $transaction;
 327+ }
 328+ if (!isset($current_transaction)){
 329+ return false;
 330+ }
 331+ return $current_transaction;
 332+ }
 333+
198334 /**
199335 * Sends a name-value pair string to Payflow gateway
200336 *
201 - * @param $data Array: array of user input
202 - * @param $payflow_data Array: array of necessary Payflow variables to
203 - * include in string (i.e. Vendor, password)
 337+ * @param $data String: The exact thing we want to send.
204338 */
205 - private function fnGlobalCollectProcessTransaction( $data, $payflow_data ) {
206 - global $wgOut, $wgDonationTestingMode, $wgPayflowGatewayUseHTTPProxy, $wgPayflowGatewayHTTPProxy, $wgGlobalCollectTimeout;
 339+ private function curl_transaction( $data ) {
 340+ global $wgOut; //TODO: Uhm, this shouldn't touch the view. Something further upstream should decide what to do with this.
207341
208 - // update contribution tracking
209 - $this->updateContributionTracking( $data, defined( 'OWA' ) );
 342+ // TODO: This, but way before we get here.
 343+ //$this->updateContributionTracking( $data, defined( 'OWA' ) );
210344
211 - // create payflow query string, include string lengths
212 - $queryArray = array(
213 - 'TRXTYPE' => $payflow_data['trxtype'],
214 - 'TENDER' => $payflow_data['tender'],
215 - 'USER' => $payflow_data['user'],
216 - 'VENDOR' => $payflow_data['vendor'],
217 - 'PARTNER' => $payflow_data['partner'],
218 - 'PWD' => $payflow_data['password'],
219 - 'ACCT' => $data['card_num'],
220 - 'EXPDATE' => $data['expiration'],
221 - 'AMT' => $data['amount'],
222 - 'FIRSTNAME' => $data['fname'],
223 - 'LASTNAME' => $data['lname'],
224 - 'STREET' => $data['street'],
225 - 'CITY' => $data['city'],
226 - 'STATE' => $data['state'],
227 - 'COUNTRY' => $data['country'],
228 - 'ZIP' => $data['zip'],
229 - 'INVNUM' => $data['order_id'],
230 - 'CVV2' => $data['cvv'],
231 - 'CURRENCY' => $data['currency'],
232 - 'VERBOSITY' => $payflow_data['verbosity'],
233 - 'CUSTIP' => $payflow_data['user_ip'],
234 - );
235 -
236 - foreach ( $queryArray as $name => $value ) {
237 - $query[] = $name . '[' . strlen( $value ) . ']=' . $value;
238 - }
239 -
240 - $queryString = implode( '&', $query );
241 -
242 - $payflow_query = $queryString;
243 -
244345 // assign header data necessary for the curl_setopt() function
245 - $user_agent = Http::userAgent();
246 - $headers[] = 'Content-Type: text/namevalue';
247 - $headers[] = 'Content-Length : ' . strlen( $payflow_query );
248 - $headers[] = 'X-VPS-Client-Timeout: 45';
249 - $headers[] = 'X-VPS-Request-ID:' . $data['order_id'];
 346+
250347 $ch = curl_init();
251 - $paypalPostTo = isset ( $wgDonationTestingMode ) ? 'testingurl' : 'paypalurl';
252 - curl_setopt( $ch, CURLOPT_URL, $payflow_data[ $paypalPostTo ] );
253 - curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
254 - curl_setopt( $ch, CURLOPT_USERAGENT, $user_agent );
255 - curl_setopt( $ch, CURLOPT_HEADER, 1 );
256 - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
257 - curl_setopt( $ch, CURLOPT_TIMEOUT, $wgGlobalCollectTimeout );
258 - curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 0 );
259 - curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
260 - curl_setopt( $ch, CURLOPT_POSTFIELDS, $payflow_query );
261 - curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
262 - curl_setopt( $ch, CURLOPT_FORBID_REUSE, true );
263 - curl_setopt( $ch, CURLOPT_POST, 1 );
264 -
265 - // set proxy settings if necessary
266 - if ( $wgPayflowGatewayUseHTTPProxy ) {
267 - curl_setopt( $ch, CURLOPT_HTTPPROXYTUNNEL, 1 );
268 - curl_setopt( $ch, CURLOPT_PROXY, $wgPayflowGatewayHTTPProxy );
 348+
 349+ $headers = $this->getCurlBaseHeaders();
 350+ $headers[] = 'Content-Length: ' . strlen( $data );
 351+
 352+ self::log("Sending Data: " . $data);
 353+
 354+ $curl_opts = $this->getCurlBaseOpts();
 355+ $curl_opts[CURLOPT_HTTPHEADER] = $headers;
 356+ $curl_opts[CURLOPT_POSTFIELDS] = $data;
 357+
 358+ foreach ($curl_opts as $option => $value){
 359+ curl_setopt($ch, $option, $value);
269360 }
270361
271362 // As suggested in the PayPal developer forum sample code, try more than once to get a response
272363 // in case there is a general network issue
273364 $i = 1;
 365+
 366+ $return = array();
274367
275368 while ( $i++ <= 3 ) {
276 - self::log( $data[ 'order_id' ] . ' Preparing to send transaction to GlobalCollect' );
277 - $result = curl_exec( $ch );
278 - $headers = curl_getinfo( $ch );
 369+ self::log( $this->postdatadefaults['order_id'] . ' Preparing to send transaction to GlobalCollect' );
 370+ $return['result'] = curl_exec( $ch );
 371+ $return['headers'] = curl_getinfo( $ch );
279372
280 - if ( $headers['http_code'] != 200 && $headers['http_code'] != 403 ) {
281 - self::log( $data[ 'order_id' ] . ' Failed sending transaction to GlobalCollect, retrying' );
 373+ if ( $return['headers']['http_code'] != 200 && $return['headers']['http_code'] != 403 ) {
 374+ self::log( $this->postdatadefaults['order_id'] . ' Failed sending transaction to GlobalCollect, retrying' );
282375 sleep( 1 );
283 - } elseif ( $headers['http_code'] == 200 || $headers['http_code'] == 403 ) {
284 - self::log( $data[ 'order_id' ] . ' Finished sending transaction to GlobalCollect' );
 376+ } elseif ( $return['headers']['http_code'] == 200 || $return['headers']['http_code'] == 403 ) {
 377+ self::log( $this->postdatadefaults['order_id'] . ' Finished sending transaction to GlobalCollect' );
285378 break;
286379 }
287380 }
288381
289 - if ( $headers['http_code'] != 200 ) {
290 - $wgOut->addHTML( '<h3>No response from credit card processor. Please try again later!</h3><p>' );
 382+ if ( $return['headers']['http_code'] != 200 ) {
 383+ $return['result'] = false;
 384+ //TODO: i18n here!
 385+ $return['message'] = 'No response from credit card processor. Please try again later!';
291386 $when = time();
292 - self::log( $data[ 'order_id' ] . ' No response from credit card processor: ' . curl_error( $ch ) );
 387+ self::log( $this->postdatadefaults['order_id'] . ' No response from credit card processor: ' . curl_error( $ch ) );
293388 curl_close( $ch );
294 - return;
 389+ return $return;
295390 }
296391
297392 curl_close( $ch );
 393+ self::log("Results: " . print_r($return['result'], true));
 394+
 395+// if ($this->getCommunicationType() === 'namevalue'){
 396+// $return['result'] = strstr( $return['result'], 'RESULT' );
 397+// //TODO: Finish this for namevalue.
 398+// }
 399+ if ($this->getCommunicationType() === 'xml'){
 400+ //$return['result'] = $this->stripResponseHeaders($return['result']);
 401+ $return['status'] = $this->parseXMLResponse($return['result']);
 402+ }
 403+
 404+ return $return;
298405
299 - // get result string
300 - $result = strstr( $result, 'RESULT' );
301 -
302406 // parse string and display results to the user
303 - $this->fnPayflowGetResults( $data, $result );
 407+ //TODO: NO NO NO. NO DISPLAY HERE.
 408+ //$this->fnPayflowGetResults( $data, $return['result'] );
304409 }
 410+
 411+ function parseXMLResponse($rawResponse){
 412+ //TODO: Something.
 413+ $rawXML = $this->stripResponseHeaders($rawResponse);
 414+ if ($rawXML === false){
 415+ return false;
 416+ }
 417+ $realXML = new DomDocument('1.0');
 418+ self::log("Here is the Raw XML: " . $rawXML);
 419+ $realXML->loadXML(trim($rawXML));
305420
 421+ $aok = true;
 422+
 423+ //find the node specified by the transaction structure.
 424+ //TODO: Error handling, because: Ugh.
 425+ $result_structure = $this->transactions[$this->currentTransaction()]['result'];
 426+ if(is_array($result_structure)){
 427+ foreach ($result_structure as $key=>$value){ //should only be one.
 428+ foreach($realXML->getElementsByTagName($key) as $node) {
 429+ if ($value === 'value') { //...stupid. But it's 'value' as opposed to 'attribute'
 430+ if (array_key_exists($node->nodeValue, $this->return_value_map) && $this->return_value_map[$node->nodeValue] !== true){
 431+ $aok = false;
 432+ }
 433+ }
 434+ if ($value === 'attribute') {
 435+ //TODO: Figure this out. This should mean the key name of one array up, which sounds painful.
 436+ }
 437+ if (is_array($value)){
 438+ //TODO: ...this is looking like a recursive deal, here. Again. So do that.
 439+ }
 440+ }
 441+ }
 442+ }
 443+
 444+ //TODO: Make this... you know: abstracty.
 445+ if ($aok === false){
 446+ foreach($realXML->getElementsByTagName('ERROR') as $node) {
 447+ $errdata = '';
 448+ foreach($node->childNodes as $childnode){
 449+ $errdata .= "\n" . $childnode->nodeName . " " . $childnode->nodeValue;
 450+// if ($childnode->nodeName == "CODE"){
 451+// //Excellent place to deal with the particular codes if we want to.
 452+// }
 453+ }
 454+ self::log("ON NOES! ERROR: $errdata");
 455+ }
 456+ } else {
 457+ self::log("Response OK");
 458+ }
 459+
 460+ //TODO: The bit where you strip all the response data that we might want to save, toss it into an array, and hand it back.
 461+ //We need to be handing more back here than just the... success or failure. Things need to be processed! I mean, probably.
 462+
306463
 464+ return $aok;
 465+ }
307466
 467+ function stripResponseHeaders($rawResponse){
 468+ $xmlStart = strpos($rawResponse, '<?xml');
 469+ if ($xmlStart == false){ //I totally saw this happen one time. No XML, just <RESPONSE>...
 470+ $xmlStart = strpos($rawResponse, '<RESPONSE');
 471+ }
 472+ if ($xmlStart == false){ //Still false. Your Head Asplode.
 473+ self::log("Wow, that was so messed up I couldn't even parse the response, so here's the thing in its entirety:\n" . $rawResponse, true);
 474+ return false;
 475+ }
 476+ $justXML = substr($rawResponse, $xmlStart);
 477+ return $justXML;
 478+ }
 479+
 480+
 481+ public static function log( $msg, $identifier='globalcollect_gateway', $log_level=LOG_INFO ) {
 482+ global $wgGlobalCollectGatewayUseSyslog;
 483+
 484+ // if we're not using the syslog facility, use wfDebugLog
 485+ if ( !$wgGlobalCollectGatewayUseSyslog ) {
 486+ wfDebugLog( $identifier, $msg );
 487+ return;
 488+ }
 489+
 490+ // otherwise, use syslogging
 491+ openlog( $identifier, LOG_ODELAY, LOG_SYSLOG );
 492+ syslog( $log_level, $msg );
 493+ closelog();
 494+ }
308495
309496
 497+ //_______________________________________________________________
 498+ //copied from payflowpro_gateway/includes/payflowUser.inc
310499
311 -//_______________________________________________________________
312 -//copied from payflowpro_gateway/includes/payflowUser.inc
 500+ /**
 501+ * Fetch and return the 'order_id' for a transaction
 502+ *
 503+ * Since transactions to PayPal are initially matched internally on their end
 504+ * with the 'order_id' field, but we don't actually care what the order id is,
 505+ * we generate a sufficiently random number to avoid duplication.
 506+ *
 507+ * We go ahead and always generate a random order id becuse if PayPal detects
 508+ * the same order_id more than once, it considers the request a duplicate, even
 509+ * if the data is completely different.
 510+ *
 511+ * @return int
 512+ */
 513+ function getOrderId() {
 514+ return $this->generateOrderId();
 515+ }
313516
314 -/**
315 - * Fetch and return the 'order_id' for a transaction
316 - *
317 - * Since transactions to PayPal are initially matched internally on their end
318 - * with the 'order_id' field, but we don't actually care what the order id is,
319 - * we generate a sufficiently random number to avoid duplication.
320 - *
321 - * We go ahead and always generate a random order id becuse if PayPal detects
322 - * the same order_id more than once, it considers the request a duplicate, even
323 - * if the data is completely different.
324 - *
325 - * @return int
326 - */
327 -function getOrderId() {
328 - return $this->generateOrderId();
329 -}
 517+ /**
 518+ * Generate an internal order id
 519+ *
 520+ * This is only used internally for tracking a user's 'session' with the credit
 521+ * card form. I mean 'session' in the sense of the moment a credit card page
 522+ * is loaded for the first time (nothing posted to it - a discrete donation
 523+ * session) as opposed to the $_SESSION - as the $_SESSION id could potentially
 524+ * not change between contribution attempts.
 525+ */
 526+ function getInternalOrderId() {
 527+ global $wgRequest;
330528
331 -/**
332 - * Generate an internal order id
333 - *
334 - * This is only used internally for tracking a user's 'session' with the credit
335 - * card form. I mean 'session' in the sense of the moment a credit card page
336 - * is loaded for the first time (nothing posted to it - a discrete donation
337 - * session) as opposed to the $_SESSION - as the $_SESSION id could potentially
338 - * not change between contribution attempts.
339 - */
340 -function getInternalOrderId() {
341 - global $wgRequest;
342 -
343 - // is an order_id already set?
344 - //TODO: Change all these to look instead at $this->postdata... I think.
345 - $i_order_id = $wgRequest->getText( 'i_order_id', 0 );
346 -
347 - // if the form was not just posted OR there's no order_id set, generate one.
348 - if ( !$wgRequest->wasPosted() || !$i_order_id ) {
349 - $i_order_id = $this->generateOrderId();
 529+ // is an order_id already set?
 530+ //TODO: Change all these to look instead at $this->postdata... I think.
 531+ $i_order_id = $wgRequest->getText( 'i_order_id', 0 );
 532+
 533+ // if the form was not just posted OR there's no order_id set, generate one.
 534+ if ( !$wgRequest->wasPosted() || !$i_order_id ) {
 535+ $i_order_id = $this->generateOrderId();
 536+ }
 537+
 538+ return $i_order_id;
350539 }
351540
352 - return $i_order_id;
 541+ /**
 542+ * Generate an order id
 543+ */
 544+ function generateOrderId() {
 545+ return (double) microtime() * 1000000 . mt_rand(1000, 9999);
 546+ }
353547 }
354 -
355 -/**
356 - * Generate an order id
357 - */
358 -function generateOrderId() {
359 - return (double) microtime() * 1000000 . mt_rand();
360 -}
361 -}
\ No newline at end of file
Index: branches/fundraising/extensions/DonationInterface/globalcollect_gateway/globalcollect_gateway.php
@@ -18,6 +18,8 @@
1919 'url' => 'http://www.mediawiki.org/wiki/Extension:GlobalCollectGateway',
2020 );
2121
 22+$wgGlobalCollectGatewayUseSyslog = false;
 23+
2224 // Set up the new special page
2325 $dir = dirname( __FILE__ ) . '/';
2426 $wgAutoloadClasses['GlobalCollectGateway'] = $dir . 'globalcollect_gateway.body.php';
@@ -50,15 +52,15 @@
5153
5254 // set defaults, these should be assigned in LocalSettings.php
5355 $wgGlobalCollectURL = 'https://ps.gcsip.nl/wdl/wdl';
54 -//$wgGlobalCollectTestingURL = 'https://pilot-globalcollect.paypal.com'; // GlobalCollect testing URL
 56+$wgGlobalCollectTestingURL = 'https://'; // GlobalCollect testing URL
5557
5658 $wgGlobalCollectMerchantID = ''; // GlobalCollect ID
5759
5860 // a boolean to determine if we're in testing mode
5961 $wgGlobalCollectGatewayTest = FALSE;
6062
61 -// timeout in seconds for communicating with paypal
62 -$wgGlobalCollectTimeout = 5;
 63+// timeout in seconds for communicating with [gateway]
 64+$wgGlobalCollectTimeout = 2;
6365
6466 /**
6567 * The default form to use
@@ -131,13 +133,6 @@
132134 $wgGlobalCollectAllowedHtmlForms = array( $wgGlobalCollectHtmlFormDir . "/demo.html" );
133135
134136 /**
135 - * Configure GlobalCollectproGateway to use syslog for log messages rather than wfDebugLog
136 - *
137 - * @var bool
138 - */
139 -$wgGlobalCollectGatewayUseSyslog = false;
140 -
141 -/**
142137 * Configure price cieling and floor for valid contribution amount. Values
143138 * should be in USD.
144139 */

Comments

#Comment by Khorn (WMF) (talk | contribs)   18:39, 23 September 2011

Please note that this branch is in the middle of a serious refactoring, and is by no means ready to be merged back into trunk. (Latest DonationInterface branch rev at the time of this comment is r97833.)

#Comment by Jpostlethwaite (talk | contribs)   19:20, 23 September 2011

This code is in development.

#Comment by Reedy (talk | contribs)   19:21, 23 September 2011
 
+		
+		
+		
+		
+		
+		
+		
+		
+		
+		
+		

Does that mean we need to add loads of whitespace in for no obvious reason? :D

#Comment by Khorn (WMF) (talk | contribs)   20:34, 23 September 2011

...apparently! :p (Hah, this ghost block does get nuked in r96391... )

Status & tagging log