r98299 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r98298‎ | r98299 | r98300 >
Date:01:47, 28 September 2011
Author:khorn
Status:resolved (Comments)
Tags:fundraising 
Comment:
Rebuilding the payflowpro gateway and adapter, Part 1 of Several!
...sending transactions should work now, in the abstracted new code. Receiving has yet to get worked over.
Modified paths:
  • /branches/fundraising/extensions/DonationInterface/gateway_common/gateway.adapter.php (modified) (history)
  • /branches/fundraising/extensions/DonationInterface/payflowpro_gateway/payflowpro.adapter.php (modified) (history)
  • /branches/fundraising/extensions/DonationInterface/payflowpro_gateway/payflowpro_gateway.body.php (modified) (history)

Diff [purge]

Index: branches/fundraising/extensions/DonationInterface/payflowpro_gateway/payflowpro_gateway.body.php
@@ -1,49 +1,13 @@
22 <?php
33
4 -class PayflowProGateway extends UnlistedSpecialPage {
 4+class PayflowProGateway extends GatewayForm {
55
66 /**
7 - * Defines the action to take on a PFP transaction.
8 - *
9 - * Possible values include 'process', 'challenge',
10 - * 'review', 'reject'. These values can be set during
11 - * data processing validation, for instance.
12 - *
13 - * Hooks are exposed to handle the different actions.
14 - *
15 - * Defaults to 'process'.
16 - * @var string
17 - */
18 - public $action = 'process';
19 -
20 - /**
21 - * Holds the PayflowPro response from a transaction
22 - * @var array
23 - */
24 - public $payflow_response = array( );
25 -
26 - /**
27 - * A container for the form class
28 - *
29 - * Used to loard the form object to display the CC form
30 - * @var object
31 - */
32 - public $form_class;
33 -
34 - /**
35 - * An array of form errors
36 - * @var array
37 - */
38 - public $errors = array( );
39 -
40 - /**
417 * Constructor - set up the new special page
428 */
439 public function __construct() {
44 - parent::__construct( 'PayflowProGateway' );
45 - $this->errors = $this->getPossibleErrors();
46 -
4710 $this->adapter = new PayflowProAdapter();
 11+ parent::__construct(); //the next layer up will know who we are.
4812 }
4913
5014 /**
@@ -52,37 +16,19 @@
5317 * @param $par Mixed: parameter passed to the page or null
5418 */
5519 public function execute( $par ) {
56 - global $wgRequest, $wgOut, $wgScriptPath,
57 - $wgPayFlowProGatewayCSSVersion,
58 - $wgPayflowProGatewaySalt;
 20+ global $wgRequest, $wgOut, $wgExtensionAssetsPath;
 21+ $CSSVersion = $this->adapter->getGlobal( 'CSSVersion' );
5922
6023 $wgOut->addExtensionStyle(
61 - "{$wgScriptPath}/extensions/DonationInterface/payflowpro_gateway/payflowpro_gateway.css?284" .
62 - $wgPayFlowProGatewayCSSVersion );
 24+ $wgExtensionAssetsPath . '/DonationInterface/gateway_forms/css/gateway.css?284' .
 25+ $CSSVersion );
6326
6427 // Hide unneeded interface elements
6528 $wgOut->addModules( 'donationInterface.skinOverride' );
6629
67 - $scriptVars = array(
68 - 'payflowproGatewayErrorMsgJs' => wfMsg( 'payflowpro_gateway-error-msg-js' ),
69 - 'payflowproGatewayErrorMsgEmail' => wfMsg( 'payflowpro_gateway-error-msg-email' ),
70 - 'payflowproGatewayErrorMsgAmount' => wfMsg( 'payflowpro_gateway-error-msg-amount' ),
71 - 'payflowproGatewayErrorMsgEmailAdd' => wfMsg( 'payflowpro_gateway-error-msg-emailAdd' ),
72 - 'payflowproGatewayErrorMsgFname' => wfMsg( 'payflowpro_gateway-error-msg-fname' ),
73 - 'payflowproGatewayErrorMsgLname' => wfMsg( 'payflowpro_gateway-error-msg-lname' ),
74 - 'payflowproGatewayErrorMsgStreet' => wfMsg( 'payflowpro_gateway-error-msg-street' ),
75 - 'payflowproGatewayErrorMsgCity' => wfMsg( 'payflowpro_gateway-error-msg-city' ),
76 - 'payflowproGatewayErrorMsgState' => wfMsg( 'payflowpro_gateway-error-msg-state' ),
77 - 'payflowproGatewayErrorMsgZip' => wfMsg( 'payflowpro_gateway-error-msg-zip' ),
78 - 'payflowproGatewayErrorMsgCountry' => wfMsg( 'payflowpro_gateway-error-msg-country' ),
79 - 'payflowproGatewayErrorMsgCardType' => wfMsg( 'payflowpro_gateway-error-msg-card_type' ),
80 - 'payflowproGatewayErrorMsgCardNum' => wfMsg( 'payflowpro_gateway-error-msg-card_num' ),
81 - 'payflowproGatewayErrorMsgExpiration' => wfMsg( 'payflowpro_gateway-error-msg-expiration' ),
82 - 'payflowproGatewayErrorMsgCvv' => wfMsg( 'payflowpro_gateway-error-msg-cvv' ),
83 - 'payflowproGatewayCVVExplain' => wfMsg( 'payflowpro_gateway-cvv-explain' ),
84 - );
 30+ $gateway_id = $this->adapter->getIdentifier();
8531
86 - $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) );
 32+ $this->addErrorMessageScript();
8733
8834 // @fixme can this be moved into the form generators?
8935 $js = <<<EOT
@@ -94,69 +40,8 @@
9541 EOT;
9642 $wgOut->addHeadItem( 'logolinkoverride', $js );
9743
98 - // find out if amount was a radio button or textbox, set amount
99 - if ( isset( $_REQUEST['amount'] ) && preg_match( '/^\d+(\.(\d+)?)?$/', $wgRequest->getText( 'amount' ) ) ) {
100 - $amount = $wgRequest->getText( 'amount' );
101 - } elseif ( isset( $_REQUEST['amountGiven'] ) && preg_match( '/^\d+(\.(\d+)?)?$/', $wgRequest->getText( 'amountGiven' ) ) ) {
102 - $amount = number_format( $wgRequest->getText( 'amountGiven' ), 2, '.', '' );
103 - } elseif ( isset( $_REQUEST['amount'] ) ) {
104 - $amount = '0.00';
105 - } elseif ( $wgRequest->getText( 'amount' ) == '-1' ) {
106 - $amount = $wgRequest->getText( 'amountOther' );
107 - } else {
108 - $amount = '0.00';
109 - }
110 -
111 - // Get array of default account values necessary for Payflow
112 - require_once( 'includes/payflowUser.inc' );
113 -
114 - $payflow_data = payflowUser();
115 -
116 - // track the number of attempts the user has made
117 - $numAttempt = $wgRequest->getVal( 'numAttempt', 0 );
118 -
119 - // make a log entry if the user has submitted the cc form
120 - if ( $wgRequest->wasPosted() && $wgRequest->getText( 'process', 0 ) ) {
121 - self::log( $payflow_data['order_id'] . " Transaction initiated." );
122 - } else {
123 - self::log( $payflow_data['order_id'] . " " . $payflow_data['i_order_id'] . " Initial credit card form request.", 'payflowpro_gateway', LOG_DEBUG );
124 - }
125 -
126 - // if _cache_ is requested by the user, do not set a session/token; dynamic data will be loaded via ajax
127 - if ( $wgRequest->getText( '_cache_', false ) ) {
128 - self::log( $payflow_data['order_id'] . " " . $payflow_data['i_order_id'] . " Cache requested", 'payflowpro_gateway', LOG_DEBUG );
129 - $cache = true;
130 - $token = '';
131 - $token_match = false;
132 -
133 - // if we have squid caching enabled, set the maxage
134 - global $wgUseSquid, $wgPayflowProSMaxAge;
135 - if ( $wgUseSquid ) {
136 - self::log( $payflow_data['order_id'] . " " . $payflow_data['i_order_id'] . " Setting s-max-age: " . $wgPayflowProSMaxAge, 'payflowpro_gateway', LOG_DEBUG );
137 - $wgOut->setSquidMaxage( $wgPayflowProSMaxAge );
138 - }
139 - } else {
140 - $cache = false;
141 -
142 - // establish the edit token to prevent csrf
143 - $token = self::fnPayflowEditToken( $wgPayflowProGatewaySalt );
144 -
145 - self::log( $payflow_data['order_id'] . " " . $payflow_data['i_order_id'] . " fnPayflowEditToken: " . $token, 'payflowpro_gateway', LOG_DEBUG );
146 -
147 - // match token
148 - $token_check = ( $wgRequest->getText( 'token' ) ) ? $wgRequest->getText( 'token' ) : $token;
149 - $token_match = $this->fnPayflowMatchEditToken( $token_check, $wgPayflowProGatewaySalt );
150 - if ( $wgRequest->wasPosted() ) {
151 - self::log( $payflow_data['order_id'] . " " . $payflow_data['i_order_id'] . " Submitted edit token: " . $wgRequest->getText( 'token', 'None' ), 'payflowpro_gateway', LOG_DEBUG );
152 - self::log( $payflow_data['order_id'] . " " . $payflow_data['i_order_id'] . " Token match: " . ($token_match ? 'true' : 'false' ), 'payflowpro_gateway', LOG_DEBUG );
153 - }
154 - }
155 -
15644 $this->setHeaders();
15745
158 - // Populate form data
159 - $data = $this->fnGetFormData( $amount, $numAttempt, $token, $payflow_data['order_id'], $payflow_data['i_order_id'] );
160 -
16146 /**
16247 * handle PayPal redirection
16348 *
@@ -164,22 +49,28 @@
16550 * and the PaypalRedirect form value must be true
16651 */
16752 if ( $wgRequest->getText( 'PaypalRedirect', 0 ) ) {
168 - $this->paypalRedirect( $data );
 53+ $this->paypalRedirect();
16954 return;
17055 }
17156
 57+ //TODO: This is short-circuiting what I really want to do here.
 58+ //so stop it.
 59+ $data = $this->adapter->getDisplayData();
 60+
17261 // dispatch forms/handling
173 - if ( $token_match ) {
174 - if ( $data['payment_method'] == 'processed' ) {
 62+ if ( $this->adapter->checkTokens() ) {
 63+ if ( $this->adapter->posted && $data['payment_method'] == 'processed' ) {
 64+ // The form was submitted and the payment method has been set
 65+ $this->adapter->log( "Form posted and payment method set." );
17566
17667 // increase the count of attempts
177 - ++$data['numAttempt'];
 68+ //++$data['numAttempt'];
 69+ // Check form for errors
 70+ $form_errors = $this->fnValidateForm( $data, $this->errors );
17871
179 - // Check form for errors and redisplay with messages
180 - $form_errors = $this->fnPayflowValidateForm( $data, $this->errors );
181 -
 72+ // If there were errors, redisplay form, otherwise proceed to next step
18273 if ( $form_errors ) {
183 - $this->fnPayflowDisplayForm( $data, $this->errors );
 74+ $this->displayForm( $data, $this->errors );
18475 } else { // The submitted form data is valid, so process it
18576 // allow any external validators to have their way with the data
18677 self::log( $data['order_id'] . " Preparing to query MaxMind" );
@@ -219,151 +110,18 @@
220111 }
221112 } else {
222113 // Display form for the first time
223 - $this->fnPayflowDisplayForm( $data, $this->errors );
 114+ $this->displayForm( $data, $this->errors );
224115 }
225116 } else {
226 - if ( !$cache ) {
 117+ if ( !$this->adapter->isCache() ) {
227118 // if we're not caching, there's a token mismatch
228 - $this->errors['general']['token-mismatch'] = wfMsg( 'payflowpro_gateway-token-mismatch' );
 119+ $this->errors['general']['token-mismatch'] = wfMsg( $gateway_id . '_gateway-token-mismatch' );
229120 }
230 - $this->fnPayflowDisplayForm( $data, $this->errors );
 121+ $this->displayForm( $data, $this->errors );
231122 }
232123 }
233124
234125 /**
235 - * Build and display form to user
236 - *
237 - * @param $data Array: array of posted user input
238 - * @param $error Array: array of error messages returned by validate_form function
239 - *
240 - * The message at the top of the form can be edited in the payflow_gateway.i18n.php file
241 - */
242 - public function fnPayflowDisplayForm( &$data, &$error ) {
243 - global $wgOut, $wgRequest;
244 -
245 - // save contrib tracking id early to track abondonment
246 - if ( $data['numAttempt'] == '0' && (!$wgRequest->getText( 'utm_source_id', false ) || $wgRequest->getText( '_nocache_' ) == 'true' ) ) {
247 - $tracked = $this->fnPayflowSaveContributionTracking( $data );
248 - if ( !$tracked ) {
249 - $when = time();
250 - self::log( $data['order_id'] . ' Unable to save data to the contribution_tracking table ' . $when );
251 - }
252 - }
253 -
254 - $form_class = $this->getFormClass();
255 - $form_obj = new $form_class( $data, $error, $this->adapter );
256 - $form = $form_obj->getForm();
257 - $wgOut->addHTML( $form );
258 - }
259 -
260 - /**
261 - * Set the form class to use to generate the CC form
262 - *
263 - * @param string $class_name The class name of the form to use
264 - */
265 - public function setFormClass( $class_name = NULL ) {
266 - if ( !$class_name ) {
267 - global $wgRequest, $wgPayflowProGatewayDefaultForm;
268 - $form_class = $wgRequest->getText( 'form_name', $wgPayflowProGatewayDefaultForm );
269 -
270 - // make sure our form class exists before going on, if not try loading default form class
271 - $class_name = "Gateway_Form_" . $form_class;
272 - if ( !class_exists( $class_name ) ) {
273 - $class_name_orig = $class_name;
274 - $class_name = "Gateway_Form_" . $wgPayflowProGatewayDefaultForm;
275 - if ( !class_exists( $class_name ) ) {
276 - throw new MWException( 'Could not load form ' . $class_name_orig . ' nor default form ' . $class_name );
277 - }
278 - }
279 - }
280 - $this->form_class = $class_name;
281 - }
282 -
283 - /**
284 - * Get the currently set form class
285 - *
286 - * Will set the form class if the form class not already set
287 - * Using logic in setFormClass()
288 - * @return string
289 - */
290 - public function getFormClass() {
291 - if ( !isset( $this->form_class ) ) {
292 - $this->setFormClass();
293 - }
294 - return $this->form_class;
295 - }
296 -
297 - /**
298 - * Checks posted form data for errors and returns array of messages
299 - */
300 - private function fnPayflowValidateForm( &$data, &$error ) {
301 - global $wgPayflowProGatewayPriceFloor, $wgPayflowProGatewayPriceCeiling;
302 -
303 - // begin with no errors
304 - $error_result = '0';
305 -
306 - // create the human-speak message for required fields
307 - // does not include fields that are not required
308 - $msg = array(
309 - 'amount' => wfMsg( 'payflowpro_gateway-error-msg-amount' ),
310 - 'emailAdd' => wfMsg( 'payflowpro_gateway-error-msg-emailAdd' ),
311 - 'fname' => wfMsg( 'payflowpro_gateway-error-msg-fname' ),
312 - 'lname' => wfMsg( 'payflowpro_gateway-error-msg-lname' ),
313 - 'street' => wfMsg( 'payflowpro_gateway-error-msg-street' ),
314 - 'city' => wfMsg( 'payflowpro_gateway-error-msg-city' ),
315 - 'state' => wfMsg( 'payflowpro_gateway-error-msg-state' ),
316 - 'zip' => wfMsg( 'payflowpro_gateway-error-msg-zip' ),
317 - 'card_num' => wfMsg( 'payflowpro_gateway-error-msg-card_num' ),
318 - 'expiration' => wfMsg( 'payflowpro_gateway-error-msg-expiration' ),
319 - 'cvv' => wfMsg( 'payflowpro_gateway-error-msg-cvv' ),
320 - );
321 -
322 - // find all empty fields and create message
323 - foreach ( $data as $key => $value ) {
324 - if ( $value == '' || ($key == 'state' && $value == 'YY' ) ) {
325 - // ignore fields that are not required
326 - if ( isset( $msg[$key] ) ) {
327 - $error[$key] = "**" . wfMsg( 'payflowpro_gateway-error-msg', $msg[$key] ) . "**<br />";
328 - $error_result = '1';
329 - }
330 - }
331 - }
332 -
333 - // check amount
334 - if ( !preg_match( '/^\d+(\.(\d+)?)?$/', $data['amount'] ) ||
335 - ( ( float ) $this->convert_to_usd( $data['currency'], $data['amount'] ) < ( float ) $wgPayflowProGatewayPriceFloor ||
336 - ( float ) $this->convert_to_usd( $data['currency'], $data['amount'] ) > ( float ) $wgPayflowProGatewayPriceCeiling ) ) {
337 - $error['invalidamount'] = wfMsg( 'payflowpro_gateway-error-msg-invalid-amount' );
338 - $error_result = '1';
339 - }
340 -
341 - // is email address valid?
342 - $isEmail = User::isValidEmailAddr( $data['email'] );
343 -
344 - // create error message (supercedes empty field message)
345 - if ( !$isEmail ) {
346 - $error['emailAdd'] = wfMsg( 'payflowpro_gateway-error-msg-email' );
347 - $error_result = '1';
348 - }
349 -
350 - // validate that credit card number entered is correct and set the card type
351 - if ( preg_match( '/^3[47][0-9]{13}$/', $data['card_num'] ) ) { // american express
352 - $data['card'] = 'american';
353 - } elseif ( preg_match( '/^5[1-5][0-9]{14}$/', $data['card_num'] ) ) { // mastercard
354 - $data['card'] = 'mastercard';
355 - } elseif ( preg_match( '/^4[0-9]{12}(?:[0-9]{3})?$/', $data['card_num'] ) ) {// visa
356 - $data['card'] = 'visa';
357 - } elseif ( preg_match( '/^6(?:011|5[0-9]{2})[0-9]{12}$/', $data['card_num'] ) ) { // discover
358 - $data['card'] = 'discover';
359 - } else { // an invalid credit card number was entered
360 - $error_result = '1';
361 - $error['card_num'] = wfMsg( 'payflowpro_gateway-error-msg-card-num' );
362 - }
363 -
364 - return $error_result;
365 - }
366 -
367 - /**
368126 * Sends a name-value pair string to Payflow gateway
369127 *
370128 * @param $data Array: array of user input
@@ -597,43 +355,6 @@
598356 }
599357
600358 /**
601 - * Prepares the transactional message to be sent via Stomp to queueing service
602 - *
603 - * @param array $data
604 - * @param array $resposneArray
605 - * @param array $responseMsg
606 - * @return array
607 - */
608 - public function prepareStompTransaction( $data, $responseArray, $responseMsg ) {
609 - $countries = $this->getCountries();
610 -
611 - $transaction = array( );
612 -
613 - // include response message
614 - $transaction['response'] = $responseMsg;
615 -
616 - // include date
617 - $transaction['date'] = time();
618 -
619 - // put all data into one array
620 - $optout = $this->determineOptOut( $data );
621 - $data['anonymous'] = $optout['anonymous'];
622 - $data['optout'] = $optout['optout'];
623 -
624 - $transaction += array_merge( $data, $responseArray );
625 -
626 - return $transaction;
627 - }
628 -
629 - /**
630 - * Fetch an array of country abbrevs => country names
631 - */
632 - public static function getCountries() {
633 - require_once( 'includes/countryCodes.inc' );
634 - return countryCodes();
635 - }
636 -
637 - /**
638359 * Display response message to user with submitted user-supplied data
639360 *
640361 * @param $data Array: array of posted data from form
@@ -730,617 +451,35 @@
731452 $this->fnPayflowUnsetEditToken();
732453 }
733454
734 - /**
735 - * Determine proper opt-out settings for contribution tracking
736 - *
737 - * because the form elements for comment anonymization and email opt-out
738 - * are backwards (they are really opt-in) relative to contribution_tracking
739 - * (which is opt-out), we need to reverse the values
740 - */
741 - public static function determineOptOut( $data ) {
742 - $optout['optout'] = ( isset( $data['email-opt'] ) && $data['email-opt'] == "1" ) ? '0' : '1';
743 - $optout['anonymous'] = ( isset( $data['comment-option'] ) && $data['comment-option'] == "1" ) ? '0' : '1';
744 - return $optout;
745 - }
 455+ //TODO: Remember why the heck I decided to leave this here...
 456+ //arguably, it's because it's slightly more "view" related, but... still, shouldn't you get stashed
 457+ //in the new GatewayForm class so we can override in chlidren if we feel like it? Odd.
 458+ function addErrorMessageScript() {
 459+ global $wgOut;
 460+ $gateway_id = $this->adapter->getIdentifier();
746461
747 - function fnPayflowSaveContributionTracking( &$data ) {
748 - // determine opt-out settings
749 - $optout = self::determineOptOut( $data );
750 -
751 - $tracked_contribution = array(
752 - 'note' => $data['comment'],
753 - 'referrer' => $data['referrer'],
754 - 'anonymous' => $optout['anonymous'],
755 - 'utm_source' => $data['utm_source'],
756 - 'utm_medium' => $data['utm_medium'],
757 - 'utm_campaign' => $data['utm_campaign'],
758 - 'optout' => $optout['optout'],
759 - 'language' => $data['language'],
760 - 'ts' => '',
 462+ $scriptVars = array(
 463+ $gateway_id . 'GatewayErrorMsgJs' => wfMsg( $gateway_id . '_gateway-error-msg-js' ),
 464+ $gateway_id . 'GatewayErrorMsgEmail' => wfMsg( $gateway_id . '_gateway-error-msg-email' ),
 465+ $gateway_id . 'GatewayErrorMsgAmount' => wfMsg( $gateway_id . '_gateway-error-msg-amount' ),
 466+ $gateway_id . 'GatewayErrorMsgEmailAdd' => wfMsg( $gateway_id . '_gateway-error-msg-emailAdd' ),
 467+ $gateway_id . 'GatewayErrorMsgFname' => wfMsg( $gateway_id . '_gateway-error-msg-fname' ),
 468+ $gateway_id . 'GatewayErrorMsgLname' => wfMsg( $gateway_id . '_gateway-error-msg-lname' ),
 469+ $gateway_id . 'GatewayErrorMsgStreet' => wfMsg( $gateway_id . '_gateway-error-msg-street' ),
 470+ $gateway_id . 'GatewayErrorMsgCity' => wfMsg( $gateway_id . '_gateway-error-msg-city' ),
 471+ $gateway_id . 'GatewayErrorMsgState' => wfMsg( $gateway_id . '_gateway-error-msg-state' ),
 472+ $gateway_id . 'GatewayErrorMsgZip' => wfMsg( $gateway_id . '_gateway-error-msg-zip' ),
 473+ $gateway_id . 'GatewayErrorMsgCountry' => wfMsg( $gateway_id . '_gateway-error-msg-country' ),
 474+ $gateway_id . 'GatewayErrorMsgCardType' => wfMsg( $gateway_id . '_gateway-error-msg-card_type' ),
 475+ $gateway_id . 'GatewayErrorMsgCardNum' => wfMsg( $gateway_id . '_gateway-error-msg-card_num' ),
 476+ $gateway_id . 'GatewayErrorMsgExpiration' => wfMsg( $gateway_id . '_gateway-error-msg-expiration' ),
 477+ $gateway_id . 'GatewayErrorMsgCvv' => wfMsg( $gateway_id . '_gateway-error-msg-cvv' ),
 478+ $gateway_id . 'GatewayCVVExplain' => wfMsg( $gateway_id . '_gateway-cvv-explain' ),
761479 );
762480
763 - // insert tracking data and get the tracking id
764 - $data['contribution_tracking_id'] = self::insertContributionTracking( $tracked_contribution );
765 -
766 - if ( !$data['contribution_tracking_id'] ) {
767 - return false;
768 - }
769 - return true;
 481+ $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) );
770482 }
771483
772 - /**
773 - * Insert a record into the contribution_tracking table
774 - *
775 - * @param array $tracking_data The array of tracking data to insert to contribution_tracking
776 - * NOTE: this should probably be run thru self::cleanTrackingData to ensure data integrity
777 - * @return mixed Contribution tracking ID or false on failure
778 - */
779 - public static function insertContributionTracking( $tracking_data ) {
780 - $db = ContributionTrackingProcessor::contributionTrackingConnection();
781 -
782 - if ( !$db ) {
783 - return false;
784 - }
785 -
786 - // set the time stamp if it's not already set
787 - if ( !isset( $tracking_data['ts'] ) || !strlen( $tracking_data['ts'] ) ) {
788 - $tracking_data['ts'] = $db->timestamp();
789 - }
790 -
791 - // Store the contribution data
792 - if ( $db->insert( 'contribution_tracking', $tracking_data ) ) {
793 - return $db->insertId();
794 - } else {
795 - return false;
796 - }
797 - }
798 -
799 - /**
800 - * Clean array of tracking data to contain valid fields
801 - *
802 - * Compares tracking data array to list of valid tracking fields and
803 - * removes any extra tracking fields/data. Also sets empty values to
804 - * 'null' values.
805 - * @param array $tracking_data
806 - * @param bool $clean_opouts If true, form opt-out values will be run through $this->determineOptOut
807 - * for cleanup.
808 - */
809 - public static function cleanTrackingData( $tracking_data, $clean_optouts = false ) {
810 - // clean up the optout values if necessary
811 - if ( $clean_optouts ) {
812 - $optouts = self::determineOptOut( $tracking_data );
813 - $tracking_data['optout'] = $optouts['optout'];
814 - $tracking_data['anonymous'] = $optouts['anonymous'];
815 - }
816 -
817 - // define valid tracking fields
818 - $tracking_fields = array(
819 - 'note',
820 - 'referrer',
821 - 'anonymous',
822 - 'utm_source',
823 - 'utm_medium',
824 - 'utm_campaign',
825 - 'optout',
826 - 'language',
827 - 'ts'
828 - );
829 -
830 - // loop through tracking data and clean it up
831 - foreach ( $tracking_data as $key => $value ) {
832 - // Make sure we only have valid fields
833 - if ( !in_array( $key, $tracking_fields ) ) {
834 - unset( $tracking_data[$key] );
835 - }
836 -
837 - // Make all empty strings NULL
838 - if ( !strlen( $value ) ) {
839 - $tracking_data[$key] = null;
840 - }
841 - }
842 -
843 - return $tracking_data;
844 - }
845 -
846 - /**
847 - * Establish an 'edit' token to help prevent CSRF, etc
848 - *
849 - * We use this in place of $wgUser->editToken() b/c currently
850 - * $wgUser->editToken() is broken (apparently by design) for
851 - * anonymous users. Using $wgUser->editToken() currently exposes
852 - * a security risk for non-authenticated users. Until this is
853 - * resolved in $wgUser, we'll use our own methods for token
854 - * handling.
855 - *
856 - * @var mixed $salt
857 - * @return string
858 - */
859 - public static function fnPayflowEditToken( $salt = '' ) {
860 -
861 - // make sure we have a session open for tracking a CSRF-prevention token
862 - self::fnPayflowEnsureSession();
863 -
864 - if ( !isset( $_SESSION['payflowEditToken'] ) ) {
865 - // generate unsalted token to place in the session
866 - $token = self::fnPayflowGenerateToken();
867 - $_SESSION['payflowEditToken'] = $token;
868 - } else {
869 - $token = $_SESSION['payflowEditToken'];
870 - }
871 -
872 - if ( is_array( $salt ) ) {
873 - $salt = implode( "|", $salt );
874 - }
875 - return md5( $token . $salt ) . EDIT_TOKEN_SUFFIX;
876 - }
877 -
878 - /**
879 - * Generate a token string
880 - *
881 - * @var mixed $salt
882 - * @return string
883 - */
884 - public static function fnPayflowGenerateToken( $salt = '' ) {
885 - $token = dechex( mt_rand() ) . dechex( mt_rand() );
886 - return md5( $token . $salt );
887 - }
888 -
889 - /**
890 - * Determine the validity of a token
891 - *
892 - * @var string $val
893 - * @var mixed $salt
894 - * @return bool
895 - */
896 - function fnPayflowMatchEditToken( $val, $salt = '' ) {
897 - // fetch a salted version of the session token
898 - $sessionToken = self::fnPayflowEditToken( $salt );
899 - if ( $val != $sessionToken ) {
900 - wfDebug( "PayflowproGateway::fnPayflowMatchEditToken: broken session data\n" );
901 - }
902 - return $val == $sessionToken;
903 - }
904 -
905 - /**
906 - * Unset the payflow edit token from a user's session
907 - */
908 - function fnPayflowUnsetEditToken() {
909 - unset( $_SESSION['payflowEditToken'] );
910 - }
911 -
912 - /**
913 - * Ensure that we have a session set for the current user
914 - *
915 - * If we do not have a session set for the current user,
916 - * start the session.
917 - */
918 - public static function fnPayflowEnsureSession() {
919 - // if the session is already started, do nothing
920 - if ( session_id() )
921 - return;
922 -
923 - // otherwise, fire it up using global mw function wfSetupSession
924 - wfSetupSession();
925 - }
926 -
927 - /**
928 - * Populate the $data array for the credit card form
929 - *
930 - * Provides a way to prepopulate the form with test data using $wgDonationInterfaceTest
931 - * @return array
932 - */
933 - public function fnGetFormData( $amount, $numAttempt, $token, $order_id, $i_order_id=0 ) {
934 - global $wgDonationInterfaceTest, $wgRequest;
935 -
936 - // fetch ID for the url reference for OWA tracking
937 - $owa_ref = $wgRequest->getText( 'owa_ref', null );
938 - if ( $owa_ref != null && !is_numeric( $owa_ref ) ) {
939 - $owa_ref = $this->get_owa_ref_id( $owa_ref );
940 - }
941 -
942 - // if we're in testing mode and an action hasn't yet be specified, prepopulate the form
943 - if ( !$wgRequest->getText( 'action', false ) && !$wgRequest->getText( 'process', 0 ) && $wgDonationInterfaceTest ) {
944 - // define arrays of cc's and cc #s for random selection
945 - $cards = array( 'american' );
946 - $card_nums = array(
947 - 'american' => array(
948 - 378282246310005
949 - ),
950 - );
951 -
952 - // randomly select a credit card
953 - $card_index = array_rand( $cards );
954 -
955 - // randomly select a credit card #
956 - $card_num_index = array_rand( $card_nums[$cards[$card_index]] );
957 -
958 - $data = array(
959 - 'amount' => ( $amount != "0.00" ) ? $amount : "35",
960 - 'amountOther' => '',
961 - 'email' => 'test@example.com',
962 - 'fname' => 'Tester',
963 - 'mname' => 'T.',
964 - 'lname' => 'Testington',
965 - 'street' => '548 Market St.',
966 - 'city' => 'San Francisco',
967 - 'state' => 'CA',
968 - 'zip' => '94104',
969 - 'country' => 'US',
970 - 'fname2' => 'Testy',
971 - 'lname2' => 'Testerson',
972 - 'street2' => '123 Telegraph Ave.',
973 - 'city2' => 'Berkeley',
974 - 'state2' => 'CA',
975 - 'zip2' => '94703',
976 - 'country2' => 'US',
977 - 'size' => 'small',
978 - 'premium_language' => 'es',
979 - 'card_num' => $card_nums[$cards[$card_index]][$card_num_index],
980 - 'card_type' => $cards[$card_index],
981 - 'expiration' => date( 'my', strtotime( '+1 year 1 month' ) ),
982 - 'cvv' => '001',
983 - 'currency' => 'USD',
984 - 'payment_method' => $wgRequest->getText( 'payment_method' ),
985 - 'order_id' => $order_id,
986 - 'i_order_id' => $i_order_id,
987 - 'numAttempt' => $numAttempt,
988 - 'referrer' => 'http://www.baz.test.com/index.php?action=foo&action=bar',
989 - 'utm_source' => self::getUtmSource(),
990 - 'utm_medium' => $wgRequest->getText( 'utm_medium' ),
991 - 'utm_campaign' => $wgRequest->getText( 'utm_campaign' ),
992 - 'language' => 'en',
993 - 'comment-option' => $wgRequest->getText( 'comment-option' ),
994 - 'comment' => $wgRequest->getText( 'comment' ),
995 - 'email-opt' => $wgRequest->getText( 'email-opt' ),
996 - 'test_string' => $wgRequest->getText( 'process' ),
997 - 'token' => $token,
998 - 'contribution_tracking_id' => $wgRequest->getText( 'contribution_tracking_id' ),
999 - 'data_hash' => $wgRequest->getText( 'data_hash' ),
1000 - 'action' => $wgRequest->getText( 'action' ),
1001 - 'gateway' => 'payflowpro',
1002 - 'owa_session' => $wgRequest->getText( 'owa_session', null ),
1003 - 'owa_ref' => $owa_ref,
1004 - );
1005 - } else {
1006 - $data = array(
1007 - 'amount' => $amount,
1008 - 'amountOther' => $wgRequest->getText( 'amountOther' ),
1009 - 'email' => $wgRequest->getText( 'emailAdd' ),
1010 - 'fname' => $wgRequest->getText( 'fname' ),
1011 - 'mname' => $wgRequest->getText( 'mname' ),
1012 - 'lname' => $wgRequest->getText( 'lname' ),
1013 - 'street' => $wgRequest->getText( 'street' ),
1014 - 'city' => $wgRequest->getText( 'city' ),
1015 - 'state' => $wgRequest->getText( 'state' ),
1016 - 'zip' => $wgRequest->getText( 'zip' ),
1017 - 'country' => $wgRequest->getText( 'country' ),
1018 - 'fname2' => $wgRequest->getText( 'fname' ),
1019 - 'lname2' => $wgRequest->getText( 'lname' ),
1020 - 'street2' => $wgRequest->getText( 'street' ),
1021 - 'city2' => $wgRequest->getText( 'city' ),
1022 - 'state2' => $wgRequest->getText( 'state' ),
1023 - 'zip2' => $wgRequest->getText( 'zip' ),
1024 - /**
1025 - * For legacy reasons, we might get a 0-length string passed into the form for country2. If this happens, we need to set country2
1026 - * to be 'country' for downstream processing (until we fully support passing in two separate addresses). I thought about completely
1027 - * disabling country2 support in the forms, etc but realized there's a chance it'll be resurrected shortly. Hence this silly hack.
1028 - */
1029 - 'country2' => ( strlen( $wgRequest->getText( 'country2' ) )) ? $wgRequest->getText( 'country2' ) : $wgRequest->getText( 'country' ),
1030 - 'size' => $wgRequest->getText( 'size' ),
1031 - 'premium_language' => $wgRequest->getText( 'premium_language', "en" ),
1032 - 'card_num' => str_replace( ' ', '', $wgRequest->getText( 'card_num' ) ),
1033 - 'card_type' => $wgRequest->getText( 'card_type' ),
1034 - 'expiration' => $wgRequest->getText( 'mos' ) . substr( $wgRequest->getText( 'year' ), 2, 2 ),
1035 - 'cvv' => $wgRequest->getText( 'cvv' ),
1036 - 'currency' => $wgRequest->getText( 'currency_code' ),
1037 - 'payment_method' => $wgRequest->getText( 'payment_method' ),
1038 - 'order_id' => $order_id,
1039 - 'i_order_id' => $i_order_id,
1040 - 'numAttempt' => $numAttempt,
1041 - 'referrer' => ( $wgRequest->getVal( 'referrer' ) ) ? $wgRequest->getVal( 'referrer' ) : $wgRequest->getHeader( 'referer' ),
1042 - 'utm_source' => self::getUtmSource(),
1043 - 'utm_medium' => $wgRequest->getText( 'utm_medium' ),
1044 - 'utm_campaign' => $wgRequest->getText( 'utm_campaign' ),
1045 - // try to honor the user-set language (uselang), otherwise the language set in the URL (language)
1046 - 'language' => $wgRequest->getText( 'uselang', $wgRequest->getText( 'language' ) ),
1047 - 'comment-option' => $wgRequest->getText( 'comment-option' ),
1048 - 'comment' => $wgRequest->getText( 'comment' ),
1049 - 'email-opt' => $wgRequest->getText( 'email-opt' ),
1050 - 'test_string' => $wgRequest->getText( 'process' ), // for showing payflow string during testing
1051 - 'token' => $token,
1052 - 'contribution_tracking_id' => $wgRequest->getText( 'contribution_tracking_id' ),
1053 - 'data_hash' => $wgRequest->getText( 'data_hash' ),
1054 - 'action' => $wgRequest->getText( 'action' ),
1055 - 'gateway' => 'payflowpro', // this may need to become dynamic in the future
1056 - 'owa_session' => $wgRequest->getText( 'owa_session', null ),
1057 - 'owa_ref' => $owa_ref,
1058 - );
1059 - }
1060 -
1061 - // sanitize user input
1062 - array_walk( $data, array( $this, 'sanitizeInput' ) );
1063 -
1064 - return $data;
1065 - }
1066 -
1067 - /**
1068 - * Sanitize user input
1069 - *
1070 - * Intended to be used with something like array_walk
1071 - *
1072 - * @param $value The value of the array
1073 - * @param $key The key of the array
1074 - * @param $flags The flag constant for htmlspecialchars
1075 - * @param $double_encode Whether or not to double-encode strings
1076 - */
1077 - public function sanitizeInput( &$value, $key, $flags=ENT_COMPAT, $double_encode=false ) {
1078 - $value = htmlspecialchars( $value, $flags, 'UTF-8', $double_encode );
1079 - }
1080 -
1081 - public function getPossibleErrors() {
1082 - return array(
1083 - 'general' => '',
1084 - 'retryMsg' => '',
1085 - 'invalidamount' => '',
1086 - 'card_num' => '',
1087 - 'card_type' => '',
1088 - 'cvv' => '',
1089 - 'fname' => '',
1090 - 'lname' => '',
1091 - 'city' => '',
1092 - 'country' => '',
1093 - 'street' => '',
1094 - 'state' => '',
1095 - 'zip' => '',
1096 - 'emailAdd' => '',
1097 - );
1098 - }
1099 -
1100 - /**
1101 - * Get the utm_source string
1102 - *
1103 - * Checks to see if the utm_source is set properly for the credit card
1104 - * form including any cc form variants (identified by utm_source_id). If
1105 - * anything cc form related is out of place for the utm_source, this
1106 - * will fix it.
1107 - *
1108 - * the utm_source is structured as: banner.landing_page.payment_instrument
1109 - *
1110 - * @param string $utm_source The utm_source for tracking - if not passed directly,
1111 - * we try to figure it out from the request object
1112 - * @param int $utm_source_id The utm_source_id for tracking - if not passed directly,
1113 - * we try to figure it out from the request object
1114 - * @return string The full utm_source
1115 - */
1116 - public static function getUtmSource( $utm_source = null, $utm_source_id = null ) {
1117 - global $wgRequest;
1118 -
1119 - /**
1120 - * fetch whatever was passed in as the utm_source
1121 - *
1122 - * if utm_source was not passed in as a param, we try to divine it from
1123 - * the request. if it's not set there, no big deal, we'll just be
1124 - * missing some tracking data.
1125 - */
1126 - if ( is_null( $utm_source ) ) {
1127 - $utm_source = $wgRequest->getText( 'utm_source' );
1128 - }
1129 -
1130 - /**
1131 - * if we have a utm_source_id, then the user is on a single-step credit card form.
1132 - * if that's the case, we treat the single-step credit card form as a landing page,
1133 - * which we label as cc#, where # = the utm_source_id
1134 - */
1135 - if ( is_null( $utm_source_id ) ) {
1136 - $utm_source_id = $wgRequest->getVal( 'utm_source_id', 0 );
1137 - }
1138 -
1139 - // this is how the CC portion of the utm_source should be defined
1140 - $correct_cc_source = ( $utm_source_id ) ? 'cc' . $utm_source_id . '.cc' : 'cc';
1141 -
1142 - // check to see if the utm_source is already correct - if so, return
1143 - if ( preg_match( '/' . str_replace( ".", "\.", $correct_cc_source ) . '$/', $utm_source ) ) {
1144 - return $utm_source;
1145 - }
1146 -
1147 - // split the utm_source into its parts for easier manipulation
1148 - $source_parts = explode( ".", $utm_source );
1149 -
1150 - // if there are no sourceparts element, then the banner portion of the string needs to be set.
1151 - // since we don't know what it is, set it to an empty string
1152 - if ( !count( $source_parts ) )
1153 - $source_parts[0] = '';
1154 -
1155 - // if the utm_source_id is set, set the landing page portion of the string to cc#
1156 - $source_parts[1] = ( $utm_source_id ) ? 'cc' . $utm_source_id : ( isset( $source_parts[1] ) ? $source_parts[1] : '' );
1157 -
1158 - // the payment instrument portion should always be 'cc' if this method is being accessed
1159 - $source_parts[2] = 'cc';
1160 -
1161 - // return a reconstructed string
1162 - return implode( ".", $source_parts );
1163 - }
1164 -
1165 - /**
1166 - * Update contribution_tracking table
1167 - *
1168 - * @param array $data Form data
1169 - * @param bool $force If set to true, will ensure that contribution tracking is updated
1170 - */
1171 - public function updateContributionTracking( &$data, $force = false ) {
1172 - // ony update contrib tracking if we're coming from a single-step landing page
1173 - // which we know with cc# in utm_source or if force=true or if contribution_tracking_id is not set
1174 - if ( !$force &&
1175 - !preg_match( "/cc[0-9]/", $data['utm_source'] ) &&
1176 - is_numeric( $data['contribution_tracking_id'] ) ) {
1177 - return;
1178 - }
1179 -
1180 -
1181 - // determine opt-out settings
1182 - $optout = self::determineOptOut( $data );
1183 -
1184 - $db = payflowGatewayConnection();
1185 -
1186 - if ( !$db ) {
1187 - return true;
1188 - }
1189 -
1190 - $tracked_contribution = array(
1191 - 'note' => $data['comment'],
1192 - 'referrer' => $data['referrer'],
1193 - 'anonymous' => $optout['anonymous'],
1194 - 'utm_source' => $data['utm_source'],
1195 - 'utm_medium' => $data['utm_medium'],
1196 - 'utm_campaign' => $data['utm_campaign'],
1197 - 'owa_session' => $data['owa_session'],
1198 - 'owa_ref' => $data['owa_ref'],
1199 - 'optout' => $optout['optout'],
1200 - 'language' => $data['language'],
1201 - );
1202 -
1203 - // Make all empty strings NULL
1204 - foreach ( $tracked_contribution as $key => $value ) {
1205 - if ( $value === '' ) {
1206 - $tracked_contribution[$key] = null;
1207 - }
1208 - }
1209 -
1210 - // if contrib tracking id is not already set, we need to insert the data, otherwise update
1211 - if ( !$data['contribution_tracking_id'] ) {
1212 - $data['contribution_tracking_id'] = $this->insertContributionTracking( $tracked_contribution );
1213 - } else {
1214 - $db->update( 'contribution_tracking', $tracked_contribution, array( 'id' => $data['contribution_tracking_id'] ) );
1215 - }
1216 - }
1217 -
1218 - /**
1219 - * Handle redirection of form content to PayPal
1220 - *
1221 - * @fixme If we can update contrib tracking table in ContributionTracking
1222 - * extension, we can probably get rid of this method and just submit the form
1223 - * directly to the paypal URL, and have all processing handled by ContributionTracking
1224 - * This would make this a lot less hack-ish
1225 - */
1226 - public function paypalRedirect( &$data ) {
1227 - global $wgPayflowProGatewayPaypalURL, $wgOut;
1228 -
1229 - // if we don't have a URL enabled throw a graceful error to the user
1230 - if ( !strlen( $wgPayflowProGatewayPaypalURL ) ) {
1231 - $this->errors['general']['nopaypal'] = wfMsg( 'payflow_gateway-error-msg-nopaypal' );
1232 - return;
1233 - }
1234 -
1235 - // update the utm source to set the payment instrument to pp rather than cc
1236 - $utm_source_parts = explode( ".", $data['utm_source'] );
1237 - $utm_source_parts[2] = 'pp';
1238 - $data['utm_source'] = implode( ".", $utm_source_parts );
1239 - $data['gateway'] = 'paypal';
1240 - $data['currency_code'] = $data['currency'];
1241 - /**
1242 - * update contribution tracking
1243 - */
1244 - $this->updateContributionTracking( $data, true );
1245 -
1246 - $wgPayflowProGatewayPaypalURL .= "/" . $data['language'] . "?gateway=paypal";
1247 -
1248 - // submit the data to the paypal redirect URL
1249 - $wgOut->redirect( $wgPayflowProGatewayPaypalURL . '&' . http_build_query( $data ) );
1250 - }
1251 -
1252 - public static function log( $msg, $identifier='payflowpro_gateway', $log_level=LOG_INFO ) {
1253 - global $wgPayflowGatewayUseSyslog;
1254 -
1255 - // if we're not using the syslog facility, use wfDebugLog
1256 - if ( !$wgPayflowGatewayUseSyslog ) {
1257 - wfDebugLog( $identifier, $msg );
1258 - return;
1259 - }
1260 -
1261 - // otherwise, use syslogging
1262 - openlog( $identifier, LOG_ODELAY, LOG_SYSLOG );
1263 - syslog( $log_level, $msg );
1264 - closelog();
1265 - }
1266 -
1267 - /**
1268 - * Convert an amount for a particular currency to an amount in USD
1269 - *
1270 - * This is grosley rudimentary and likely wildly inaccurate.
1271 - * This mimicks the hard-coded values used by the WMF to convert currencies
1272 - * for validatoin on the front-end on the first step landing pages of their
1273 - * donation process - the idea being that we can get a close approximation
1274 - * of converted currencies to ensure that contributors are not going above
1275 - * or below the price ceiling/floor, even if they are using a non-US currency.
1276 - *
1277 - * In reality, this probably ought to use some sort of webservice to get real-time
1278 - * conversion rates.
1279 - *
1280 - * @param $currency_code
1281 - * @param $amount
1282 - * @return unknown_type
1283 - */
1284 - public function convert_to_usd( $currency_code, $amount ) {
1285 - switch ( strtoupper( $currency_code ) ) {
1286 - case 'USD':
1287 - $usd_amount = $amount / 1;
1288 - break;
1289 - case 'GBP':
1290 - $usd_amount = $amount / 1;
1291 - break;
1292 - case 'EUR':
1293 - $usd_amount = $amount / 1;
1294 - break;
1295 - case 'AUD':
1296 - $usd_amount = $amount / 2;
1297 - break;
1298 - case 'CAD':
1299 - $usd_amount = $amount / 1;
1300 - break;
1301 - case 'CHF':
1302 - $usd_amount = $amount / 1;
1303 - break;
1304 - case 'CZK':
1305 - $usd_amount = $amount / 20;
1306 - break;
1307 - case 'DKK':
1308 - $usd_amount = $amount / 5;
1309 - break;
1310 - case 'HKD':
1311 - $usd_amount = $amount / 10;
1312 - break;
1313 - case 'HUF':
1314 - $usd_amount = $amount / 200;
1315 - break;
1316 - case 'JPY':
1317 - $usd_amount = $amount / 100;
1318 - break;
1319 - case 'NZD':
1320 - $usd_amount = $amount / 2;
1321 - break;
1322 - case 'NOK':
1323 - $usd_amount = $amount / 10;
1324 - break;
1325 - case 'PLN':
1326 - $usd_amount = $amount / 5;
1327 - break;
1328 - case 'SGD':
1329 - $usd_amount = $amount / 2;
1330 - break;
1331 - case 'SEK':
1332 - $usd_amount = $amount / 10;
1333 - break;
1334 - case 'ILS':
1335 - $usd_amount = $amount / 5;
1336 - break;
1337 - default:
1338 - $usd_amount = $amount;
1339 - break;
1340 - }
1341 -
1342 - return $usd_amount;
1343 - }
1344 -
1345484 }
1346485
1347486 // end class
Index: branches/fundraising/extensions/DonationInterface/payflowpro_gateway/payflowpro.adapter.php
@@ -1,154 +1,123 @@
22 <?php
33
44 class PayflowProAdapter extends GatewayAdapter {
5 - //Contains the map of THEIR var names, to OURS.
6 - //I'd have gone the other way, but we'd run into 1:many pretty quick.
7 -
85 const GATEWAY_NAME = 'Payflow Pro';
96 const IDENTIFIER = 'payflowpro_gateway';
107 const COMMUNICATION_TYPE = 'namevalue';
118 const GLOBAL_PREFIX = 'wgPayflowProGateway';
129
13 - /**
14 - * stageData should alter the postdata array in all ways necessary in preparation for
15 - * communication with the gateway.
16 - */
17 - function stageData() {
18 -
19 - }
20 -
2110 function defineAccountInfo() {
2211 $this->accountInfo = array(
23 - 'MERCHANTID' => self::getGlobal( 'MerchantID' ),
24 - //'IPADDRESS' => '', //TODO: Not sure if this should be OUR ip, or the user's ip. Hurm.
25 - 'VERSION' => "1.0",
 12+ 'PARTNER' => self::getGlobal( 'PartnerID' ), // PayPal or original authorized reseller
 13+ 'VENDOR' => self::getGlobal( 'VendorID' ), // paypal merchant login ID
 14+ 'USER' => self::getGlobal( 'UserID' ), // if one or more users are set up, authorized user ID, else same as VENDOR
 15+ 'PWD' => self::getGlobal( 'Password' ), // merchant login password
2616 );
2717 }
2818
2919 function defineVarMap() {
3020 $this->var_map = array(
31 - 'ORDERID' => 'order_id',
32 - 'AMOUNT' => 'amount',
33 - 'CURRENCYCODE' => 'currency',
34 - 'LANGUAGECODE' => 'language',
35 - 'COUNTRYCODE' => 'country',
36 - 'MERCHANTREFERENCE' => 'order_id',
37 - 'RETURNURL' => 'returnto', //I think. It might not even BE here yet. Boo-urns.
38 - 'IPADDRESS' => 'user_ip', //TODO: Not sure if this should be OUR ip, or the user's ip. Hurm.
 21+ 'ACCT' => 'card_num',
 22+ 'EXPDATE' => 'expiration',
 23+ 'AMT' => 'amount',
 24+ 'FIRSTNAME' => 'fname',
 25+ 'LASTNAME' => 'lname',
 26+ 'STREET' => 'street',
 27+ 'CITY' => 'city',
 28+ 'STATE' => 'state',
 29+ 'COUNTRY' => 'country',
 30+ 'ZIP' => 'zip',
 31+ 'INVNUM' => 'order_id',
 32+ 'CVV2' => 'cvv',
 33+ 'CURRENCY' => 'currency',
 34+ 'CUSTIP' => 'user_ip',
 35+// 'ORDERID' => 'order_id',
 36+// 'AMOUNT' => 'amount',
 37+// 'CURRENCYCODE' => 'currency',
 38+// 'LANGUAGECODE' => 'language',
 39+// 'COUNTRYCODE' => 'country',
 40+// 'MERCHANTREFERENCE' => 'order_id',
 41+// 'RETURNURL' => 'returnto', //I think. It might not even BE here yet. Boo-urns.
 42+// 'IPADDRESS' => 'user_ip', //TODO: Not sure if this should be OUR ip, or the user's ip. Hurm.
3943 );
4044 }
4145
4246 function defineReturnValueMap() {
43 - $this->return_value_map = array(
44 - 'OK' => true,
45 - 'NOK' => false,
46 - );
 47+ $this->return_value_map = array( ); //we don't really need this... maybe.
4748 }
4849
4950 function defineTransactions() {
5051 $this->transactions = array( );
5152
52 - $this->transactions['INSERT_ORDERWITHPAYMENT'] = array(
 53+ $this->transactions['Card'] = array(
5354 'request' => array(
54 - 'REQUEST' => array(
55 - 'ACTION',
56 - 'META' => array(
57 - 'MERCHANTID',
58 - // 'IPADDRESS',
59 - 'VERSION'
60 - ),
61 - 'PARAMS' => array(
62 - 'ORDER' => array(
63 - 'ORDERID',
64 - 'AMOUNT',
65 - 'CURRENCYCODE',
66 - 'LANGUAGECODE',
67 - 'COUNTRYCODE',
68 - 'MERCHANTREFERENCE'
69 - ),
70 - 'PAYMENT' => array(
71 - 'PAYMENTPRODUCTID',
72 - 'AMOUNT',
73 - 'CURRENCYCODE',
74 - 'LANGUAGECODE',
75 - 'COUNTRYCODE',
76 - 'HOSTEDINDICATOR',
77 - 'RETURNURL',
78 - )
79 - )
80 - )
 55+ 'TRXTYPE',
 56+ 'TENDER',
 57+ 'USER',
 58+ 'VENDOR',
 59+ 'PARTNER',
 60+ 'PWD',
 61+ 'ACCT',
 62+ 'EXPDATE',
 63+ 'AMT',
 64+ 'FIRSTNAME',
 65+ 'LASTNAME',
 66+ 'STREET',
 67+ 'CITY',
 68+ 'STATE',
 69+ 'COUNTRY',
 70+ 'ZIP',
 71+ 'INVNUM',
 72+ 'CVV2',
 73+ 'CURRENCY',
 74+ 'VERBOSITY',
 75+ 'CUSTIP',
8176 ),
8277 'values' => array(
83 - 'ACTION' => 'INSERT_ORDERWITHPAYMENT',
84 - 'HOSTEDINDICATOR' => '1',
85 - 'PAYMENTPRODUCTID' => '3',
 78+ 'TRXTYPE' => 'S',
 79+ 'TENDER' => 'C',
 80+ 'VERBOSITY' => 'MEDIUM',
8681 ),
8782 );
88 -
89 - $this->transactions['TEST_CONNECTION'] = array(
90 - 'request' => array(
91 - 'REQUEST' => array(
92 - 'ACTION',
93 - 'META' => array(
94 - 'MERCHANTID',
95 -// 'IPADDRESS',
96 - 'VERSION'
97 - ),
98 - 'PARAMS' => array( )
99 - )
100 - ),
101 - 'values' => array(
102 - 'ACTION' => 'TEST_CONNECTION'
103 - )
104 - );
10583 }
10684
107 - function do_transaction( $transaction ) {
108 - $this->currentTransaction( $transaction );
109 - if ( $this->getCommunicationType() === 'xml' ) {
110 - $xml = $this->buildRequestXML();
111 - $response = $this->curl_transaction( $xml );
112 - //put the response in a universal form, and return it.
113 - }
114 -
115 - //TODO: Actually pull these status messages from somewhere legit.
116 - if ( $response['result'] !== false ) {
117 - //now, parse the thing.
118 -
119 -
120 - if ( $response['status'] === true ) {
121 - $response['message'] = "$transaction Transaction Successful!";
122 - } elseif ( $response['status'] === false ) {
123 - $response['message'] = "$transaction Transaction FAILED!";
124 - } else {
125 - $response['message'] = "$transaction Transaction... weird. I have no idea what happened there.";
126 - }
127 - }
128 -
129 - return $response;
130 -
131 - //speaking of universal form:
132 - //$result['status'] = something I wish could be boiled down to a bool, but that's way too optimistic, I think.
133 - //$result['message'] = whatever we want to display back?
134 - //$result['errors'][]['code'] = their error code
135 - //$result['errors'][]['value'] = Error message
136 - //$result['return'][$whatever] = values they pass back to us for whatever reason. We might... log it, or pieces of it, or something?
137 - }
138 -
13985 /**
14086 * Take the entire response string, and strip everything we don't care about.
14187 * For instance: If it's XML, we only want correctly-formatted XML. Headers must be killed off.
14288 * return a string.
14389 */
144 - function getTrimmedResponse( $rawResponse ) {
145 -
 90+ function getFormattedResponse( $rawResponse ) {
 91+ $nvString = $this->stripNameValueResponseHeaders( $rawResponse );
 92+
 93+ // prepare NVP response for sorting and outputting
 94+ $responseArray = array( );
 95+
 96+ /**
 97+ * The result response string looks like:
 98+ * RESULT=7&PNREF=E79P2C651DC2&RESPMSG=Field format error&HOSTCODE=10747&DUPLICATE=1
 99+ * We want to turn this into an array of key value pairs, so explode on '&' and then
 100+ * split up the resulting strings into $key => $value
 101+ */
 102+ $result_arr = explode( "&", $nvString );
 103+ foreach ( $result_arr as $result_pair ) {
 104+ list( $key, $value ) = preg_split( "/=/", $result_pair );
 105+ $responseArray[$key] = $value;
 106+ }
 107+
 108+ self::log( "Here is the response as an array: " . print_r( $responseArray, true ) ); //I am apparently a huge fibber.
 109+ return $responseArray;
146110 }
147111
148112 /**
149113 * Parse the response to get the status. Not sure if this should return a bool, or something more... telling.
150114 */
151115 function getResponseStatus( $response ) {
152 -
 116+ //this function is only supposed to make sure the communication was well-formed...
 117+ if ( is_array( $response ) && array_key_exists( 'RESULT', $response ) ) {
 118+ return true;
 119+ } else {
 120+ return false;
 121+ }
153122 }
154123
155124 /**
@@ -156,7 +125,51 @@
157126 * return a key/value array of codes (if they exist) and messages.
158127 */
159128 function getResponseErrors( $response ) {
160 -
 129+
 130+ if ( is_array( $response ) && array_key_exists( 'RESULT', $response ) ) {
 131+ $resultCode = $response['RESULT'];
 132+ } else {
 133+ return;
 134+ }
 135+
 136+ $errors = array( );
 137+
 138+ switch ( $resultCode ) {
 139+ case '0':
 140+ $errors['1'] = wfMsg( 'payflowpro_gateway-response-0' );
 141+ break;
 142+ case '126':
 143+ $errors['5'] = wfMsg( 'payflowpro_gateway-response-126-2' );
 144+ break;
 145+ case '12':
 146+ $errors['2'] = wfMsg( 'payflowpro_gateway-response-12' );
 147+ break;
 148+ case '13':
 149+ $errors['2'] = wfMsg( 'payflowpro_gateway-response-13' );
 150+ break;
 151+ case '114':
 152+ $errors['2'] = wfMsg( 'payflowpro_gateway-response-114' );
 153+ break;
 154+ case '4':
 155+ $errors['3'] = wfMsg( 'payflowpro_gateway-response-4' );
 156+ break;
 157+ case '23':
 158+ $errors['3'] = wfMsg( 'payflowpro_gateway-response-23' );
 159+ break;
 160+ case '24':
 161+ $errors['3'] = wfMsg( 'payflowpro_gateway-response-24' );
 162+ break;
 163+ case '112':
 164+ $errors['3'] = wfMsg( 'payflowpro_gateway-response-112' );
 165+ break;
 166+ case '125':
 167+ $errors['3'] = wfMsg( 'payflowpro_gateway-response-125-2' );
 168+ break;
 169+ default:
 170+ $errors['4'] = wfMsg( 'payflowpro_gateway-response-default' );
 171+ }
 172+
 173+ return $errors;
161174 }
162175
163176 /**
@@ -164,19 +177,12 @@
165178 * return a key/value array
166179 */
167180 function getResponseData( $response ) {
168 -
 181+ if ( is_array( $response ) && !empty( $response ) ) {
 182+ return $response;
 183+ }
169184 }
170185
171186 /**
172 - * Take the entire response string, and strip everything we don't care about.
173 - * For instance: If it's XML, we only want correctly-formatted XML. Headers must be killed off.
174 - * return a string.
175 - */
176 - function getFormattedResponse( $rawResponse ) {
177 -
178 - }
179 -
180 - /**
181187 * Actually do... stuff. Here.
182188 * TODO: Better comment.
183189 * Process the entire response gott'd by the last four functions.
@@ -185,75 +191,16 @@
186192
187193 }
188194
189 - function parseXMLResponse( $rawResponse ) {
190 - //TODO: Something.
191 - $rawXML = $this->stripResponseHeaders( $rawResponse );
192 - if ( $rawXML === false ) {
193 - return false;
194 - } else {
195 - $rawXML = $this->formatXmlString( $rawXML );
196 - }
197 - $realXML = new DomDocument( '1.0' );
198 - self::log( "Here is the Raw XML: " . $rawXML );
199 - $realXML->loadXML( trim( $rawXML ) );
200 -
201 - $aok = true;
202 -
203 - //find the node specified by the transaction structure.
204 - //TODO: Error handling, because: Ugh.
205 - $result_structure = $this->transactions[$this->currentTransaction()]['result'];
206 - if ( is_array( $result_structure ) ) {
207 - foreach ( $result_structure as $key => $value ) { //should only be one.
208 - foreach ( $realXML->getElementsByTagName( $key ) as $node ) {
209 - if ( $value === 'value' ) { //...stupid. But it's 'value' as opposed to 'attribute'
210 - if ( array_key_exists( $node->nodeValue, $this->return_value_map ) && $this->return_value_map[$node->nodeValue] !== true ) {
211 - $aok = false;
212 - }
213 - }
214 - if ( $value === 'attribute' ) {
215 - //TODO: Figure this out. This should mean the key name of one array up, which sounds painful.
216 - }
217 - if ( is_array( $value ) ) {
218 - //TODO: ...this is looking like a recursive deal, here. Again. So do that.
219 - }
220 - }
221 - }
222 - }
223 -
224 - //TODO: Make this... you know: abstracty.
225 - if ( $aok === false ) {
226 - foreach ( $realXML->getElementsByTagName( 'ERROR' ) as $node ) {
227 - $errdata = '';
228 - foreach ( $node->childNodes as $childnode ) {
229 - $errdata .= "\n" . $childnode->nodeName . " " . $childnode->nodeValue;
230 -// if ($childnode->nodeName == "CODE"){
231 -// //Excellent place to deal with the particular codes if we want to.
232 -// }
233 - }
234 - self::log( "ON NOES! ERROR: $errdata" );
235 - }
236 - } else {
237 - self::log( "Response OK" );
238 - }
239 -
240 - //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.
241 - //We need to be handing more back here than just the... success or failure. Things need to be processed! I mean, probably.
242 -
243 -
244 - return $aok;
 195+ function defineStagedVars() {
 196+ //OUR field names.
 197+ $this->staged_vars = array(
 198+ 'card_num',
 199+ );
245200 }
246201
247 - function stripResponseHeaders( $rawResponse ) {
248 - $xmlStart = strpos( $rawResponse, '<?xml' );
249 - if ( $xmlStart == false ) { //I totally saw this happen one time. No XML, just <RESPONSE>...
250 - $xmlStart = strpos( $rawResponse, '<RESPONSE' );
251 - }
252 - if ( $xmlStart == false ) { //Still false. Your Head Asplode.
253 - 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 );
254 - return false;
255 - }
256 - $justXML = substr( $rawResponse, $xmlStart );
257 - return $justXML;
 202+ protected function stage_card_num( $type = 'request' ) {
 203+ //I realize that the $type isn't used. Voodoo.
 204+ $this->postdata['card_num'] = str_replace( ' ', '', $this->postdata['card_num'] );
258205 }
259206
260207 }
Index: branches/fundraising/extensions/DonationInterface/gateway_common/gateway.adapter.php
@@ -233,6 +233,26 @@
234234 die( "getValue found NOTHING for $gateway_field_name, $transaction." );
235235 }
236236
 237+ function buildRequestNameValueString() {
 238+ $structure = $this->transactions[$this->currentTransaction()]['request'];
 239+ if ( !is_array( $structure ) ) {
 240+ return '';
 241+ }
 242+
 243+ $queryvals = array( );
 244+
 245+ //we are going to assume a flat array, because... namevalue.
 246+ foreach ( $structure as $fieldname ) {
 247+ $fieldvalue = $this->getValue( $fieldname );
 248+ if ( $fieldvalue !== '' && $fieldvalue !== false ) {
 249+ $queryvals[] = $fieldname . '[' . strlen( $fieldvalue ) . ']=' . $fieldvalue;
 250+ }
 251+ }
 252+
 253+ $ret = implode( '&', $queryvals );
 254+ return $ret;
 255+ }
 256+
237257 function buildRequestXML() {
238258 $this->xmlDoc = new DomDocument( '1.0' );
239259 $node = $this->xmlDoc->createElement( 'XML' );
@@ -305,9 +325,11 @@
306326
307327 if ( $this->getCommunicationType() === 'namevalue' ) {
308328 $namevalue = $this->postdata;
309 - $this->getStopwatch( __FUNCTION__ );
310 - $returned = $this->curl_transaction( $namevalue );
311 - $this->saveCommunicationStats();
 329+ //buildRequestNameValueString()
 330+ $this->getStopwatch( "buildRequestNameValueString" );
 331+ $namevalstring = $this->buildRequestNameValueString();
 332+ $this->saveCommunicationStats( "buildRequestNameValueString", $transaction );
 333+ $returned = $this->curl_transaction( $namevalstring );
312334 //put the response in a universal form, and return it.
313335 }
314336
@@ -405,7 +427,11 @@
406428 $headers = $this->getCurlBaseHeaders();
407429 $headers[] = 'Content-Length: ' . strlen( $data );
408430
409 - self::log( "Sending Data: " . $this->formatXmlString( $data ) );
 431+ if ( $this->getCommunicationType() === 'xml' ) {
 432+ self::log( "Sending Data: " . $this->formatXmlString( $data ) );
 433+ } else {
 434+ self::log( "Sending Data: " . $data );
 435+ }
410436
411437 $curl_opts = $this->getCurlBaseOpts();
412438 $curl_opts[CURLOPT_HTTPHEADER] = $headers;
@@ -466,6 +492,11 @@
467493 return $justXML;
468494 }
469495
 496+ function stripNameValueResponseHeaders( $rawResponse ) {
 497+ $result = strstr( $rawResponse, 'RESULT' );
 498+ return $result;
 499+ }
 500+
470501 public static function log( $msg, $log_level=LOG_INFO ) {
471502 $identifier = self::getIdentifier() . "_gateway";
472503

Comments

#Comment by Awjrichards (talk | contribs)   00:22, 30 September 2011

In gateway.adapter.php:

function buildRequestNameValueString() {
		$structure = $this->transactions[$this->currentTransaction()]['request'];
		if ( !is_array( $structure ) ) {
			return '';
		}

		$queryvals = array( );

		//we are going to assume a flat array, because... namevalue. 
		foreach ( $structure as $fieldname ) {
			$fieldvalue = $this->getValue( $fieldname );
			if ( $fieldvalue !== '' && $fieldvalue !== false ) {
				$queryvals[] = $fieldname . '[' . strlen( $fieldvalue ) . ']=' . $fieldvalue;
			}
		}

		$ret = implode( '&', $queryvals );
		return $ret;
	}

You should be using MW magic to build the query string: wfArrayToCGI() [1] or if you want to just build a full URL with query params you can use wfAppendQuery() [2]

#Comment by Khorn (WMF) (talk | contribs)   01:24, 12 October 2011

Actually, as it turns out, I can't. This isn't actually a querystring: It's a name/value string for cURL, which requires the data length to appear in actual brackets in the "key" portion of the show. If I try to run it through mw[anything that makes a querystring apparently], the brackets get escaped and payflowPro has a bit of a tantrum (says we're not a valid vendor, which is apparently always what it says when it's unhappy for any reason with the overall data formatting).

#Comment by Awjrichards (talk | contribs)   21:20, 21 October 2011

oh! carry on then.

Status & tagging log