Index: trunk/extensions/DonationInterface/payflowpro_gateway/stompPFPPendingProcessor.php |
— | — | @@ -1,413 +0,0 @@ |
2 | | -<?php |
3 | | -/** |
4 | | - * Processes pending PayflowPro transactions in a queueing service using Stomp |
5 | | - * |
6 | | - * Uses the MediaWiki maintenance system for command line execution. Requires, at the |
7 | | - * very least, the Maintenance.php file found in Mediawiki > 1.16. If you are running |
8 | | - * MW < 1.16, you can grab a copy of Maintenance.php and place it somewhere accessible |
9 | | - * and run this script with: |
10 | | - * php stompPFPPendingProcessor.php -m "/path/to/Maintenance.php" |
11 | | - * |
12 | | - * Also note all the other config options that can be passed in via the CLI arguments |
13 | | - * below. |
14 | | - * |
15 | | - * This was built to verify pending PayflowPro transactions in an ActiveMQ queue system. |
16 | | - * It pulls a transaction out of a 'pending' queue and submits the message to PayflowPro |
17 | | - * for verification. If PayflowPro verifies the transaction, it is then passed off to a |
18 | | - * 'confirmed' queue for processing elsewhere. If PayflowPro rejects for a small variety |
19 | | - * of reasons (ones that require some user intervention), the message is reloaded to the |
20 | | - * queue. If PayflowPro completely rejects the message, it is pruned from the queue |
21 | | - * altogether. |
22 | | - * |
23 | | - * This performs some logging (depending on the log_level setting), which if not set to 0 |
24 | | - * just gets output to the screen. |
25 | | - * |
26 | | - * Config options (key = var name for localSettings.php, value = command line arg name): |
27 | | - * $options = array ( |
28 | | - * 'wgPayflowProURL' => 'pfp-url', |
29 | | - * 'wgPayflowProPartnerID' => 'pfp-partner-id', |
30 | | - * 'wgPayflowProVendorID' => 'pfp-vendor-id', |
31 | | - * 'wgPayflowProUserID' => 'pfp-user-id', |
32 | | - * 'wgPayflowProPassword' => 'pfp-password', |
33 | | - * 'wgActiveMQStompURI' => 'activemq-stomp-uri', |
34 | | - * 'wgActiveMQPendingPath' => 'activemq-pending-queue', |
35 | | - * 'wgActiveMQConfirmedPath' => 'activemq-confirmed-queue', |
36 | | - * 'wgActiveMQPendingProcessingBatchSize' => 'batch-size', |
37 | | - * 'wgActiveMQPendingProcessLogLevel' => 'log-level' ); |
38 | | - * Each of these config options gets set as a class property with the name of the command line arg, |
39 | | - * with the '-' replaced with a '_' (eg 'pfp-url' becomes $this->pfp_url). |
40 | | - * |
41 | | - * @author: Arthur Richards <arichards@wikimedia.org> |
42 | | - */ |
43 | | - |
44 | | -// add optional Maintenance.php argument for cli (useful for systems < 1.16) |
45 | | -$opts = getopt( "m:" ); |
46 | | -// if the path is not specified from CLI, default Maintenance.php path |
47 | | -$maint = ( $opts['m'] ) ? $opts['m'] : MW_INSTALL_PATH . "/maintenance/Maintenance.php"; |
48 | | -require_once( $maint ); |
49 | | - |
50 | | -// load necessary stomp files from DonationInterface/active_mq |
51 | | -//require_once( dirname( __FILE__ ) . "/../extensions/DonationInterface/activemq_stomp/Stomp.php" ); |
52 | | -require_once('/var/www/sites/all/modules/queue2civicrm/Stomp.php'); // why are these Stomps different?! |
53 | | -//require_once( dirname( __FILE__ ) . "/../extensions/DonationInterface/activemq_stomp/Stomp/Exception.php" ); |
54 | | -require_once('/var/www/sites/all/modules/queue2civicrm/Stomp/Exception.php'); |
55 | | - |
56 | | -define( 'LOG_LEVEL_QUIET', 0 ); // disables all logging |
57 | | -define( 'LOG_LEVEL_INFO', 1 ); // some useful logging information |
58 | | -define( 'LOG_LEVEL_DEBUG', 2 ); // verbose logging for debug |
59 | | - |
60 | | -class StompPFPPendingProcessor extends Maintenance { |
61 | | - |
62 | | - /** If TRUE, output extra information for debug purposes **/ |
63 | | - protected $log_level = LOG_LEVEL_INFO; |
64 | | - |
65 | | - /** Holds our Stomp connection instance **/ |
66 | | - protected $stomp; |
67 | | - |
68 | | - /** The number of items to process **/ |
69 | | - protected $batch_size = 50; |
70 | | - |
71 | | - public function __construct() { |
72 | | - parent::__construct(); |
73 | | - |
74 | | - // register command line params with the parent class |
75 | | - $this->register_params(); |
76 | | - } |
77 | | - |
78 | | - public function execute() { |
79 | | - // load configuration options |
80 | | - $this->load_config_options(); |
81 | | - $this->log( "Pending queue processor bootstrapped and ready to go!" ); |
82 | | - |
83 | | - // estamplish a connection to the stomp listener |
84 | | - $this->set_stomp_connection(); |
85 | | - |
86 | | - $this->log( "Preparing to process up to {$this->batch_size} pending transactions.", LOG_LEVEL_DEBUG ); |
87 | | - |
88 | | - // batch process pending transactions |
89 | | - for ( $i = 0; $i < $this->batch_size; $i++ ) { |
90 | | - // empty pending_transaction |
91 | | - if ( isset( $message )) unset( $message ); |
92 | | - |
93 | | - // fetch the latest pending transaction from the queue (Stomp_Frame object) |
94 | | - $message = $this->fetch_message( $this->activemq_pending_queue ); |
95 | | - // if we do not get a pending transaction back... |
96 | | - if ( !$message ) { |
97 | | - $this->log( "There are no more pending transactions to process.", LOG_LEVEL_DEBUG ); |
98 | | - break; |
99 | | - } |
100 | | - |
101 | | - // the message is in it's raw format, we need to decode just it's body |
102 | | - $pending_transaction = json_decode( $message->body, TRUE ); |
103 | | - $this->log( "Pending transaction: " . print_r( $pending_transaction, TRUE ), LOG_LEVEL_DEBUG ); |
104 | | - |
105 | | - // fetch the payflow pro status of this transaction |
106 | | - $status = $this->fetch_payflow_transaction_status( $pending_transaction['gateway_txn_id'] ); |
107 | | - |
108 | | - // determine the result code from the payflow pro status message |
109 | | - $result_code = $this->parse_payflow_transaction_status( $status ); |
110 | | - |
111 | | - // handle the pending transaction based on the payflow pro result code |
112 | | - $this->handle_pending_transaction( $result_code, json_encode( $pending_transaction )); |
113 | | - |
114 | | - $ack_response = $this->stomp->ack( $message ); |
115 | | - $this->log( "Ack response: $ack_response", LOG_LEVEL_DEBUG ); |
116 | | - } |
117 | | - $this->log( "Processed $i messages." ); |
118 | | - } |
119 | | - |
120 | | - /** |
121 | | - * Fetch latest raw message from a queue |
122 | | - * |
123 | | - * @param $destination string of the destination path from where to fetch a message |
124 | | - * @return mixed raw message (Stomp_Frame object) from Stomp client or False if no msg present |
125 | | - */ |
126 | | - protected function fetch_message( $destination ) { |
127 | | - $this->log( "Attempting to connect to queue at: $destination", LOG_LEVEL_DEBUG ); |
128 | | - |
129 | | - $this->stomp->subscribe( $destination ); |
130 | | - |
131 | | - $this->log( "Attempting to pull queued item", LOG_LEVEL_DEBUG ); |
132 | | - $message = $this->stomp->readFrame(); |
133 | | - return $message; |
134 | | - } |
135 | | - |
136 | | - /** |
137 | | - * Send a message to the queue |
138 | | - * |
139 | | - * @param $destination string of the destination path for where to send a message |
140 | | - * @param $message string the (formatted) message to send to the queue |
141 | | - * @param $options array of additional Stomp options |
142 | | - * @return bool result from send, FALSE on failure |
143 | | - */ |
144 | | - protected function queue_message( $destination, $message, $options = array( 'persistent' => TRUE )) { |
145 | | - $this->log( "Attempting to queue message...", LOG_LEVEL_DEBUG ); |
146 | | - $sent = $this->stomp->send( $destination, $message, $options ); |
147 | | - $this->log( "Result of queuing message: $sent", LOG_LEVEL_DEBUG ); |
148 | | - return $sent; |
149 | | - } |
150 | | - |
151 | | - /** |
152 | | - * Fetch the PayflowPro status of a transaction. |
153 | | - * |
154 | | - * @param $transaction_id string of the original ID of the transaction to status check |
155 | | - * @return string containing the raw status message returned by PayflowPro |
156 | | - */ |
157 | | - protected function fetch_payflow_transaction_status( $transaction_id ) { |
158 | | - $this->log( "Transaction ID: $transaction_id", LOG_LEVEL_DEBUG ); |
159 | | - // create payflow query string, include string lengths |
160 | | - $queryArray = array( |
161 | | - 'TRXTYPE' => 'I', |
162 | | - 'TENDER' => 'C', |
163 | | - 'USER' => $this->pfp_user_id, //$payflow_data['user'], |
164 | | - 'VENDOR' => $this->pfp_vendor_id, //$payflow_data['vendor'], |
165 | | - 'PARTNER' => $this->pfp_partner_id, //$payflow_data['partner'], |
166 | | - 'PWD' => $this->pfp_password, //$payflow_data['password'], |
167 | | - 'ORIGID' => $transaction_id, |
168 | | - ); |
169 | | - $this->log( "PayflowPro query array: " . print_r( $queryArray, TRUE ), LOG_LEVEL_DEBUG ); |
170 | | - |
171 | | - // format the query string for PayflowPro |
172 | | - foreach ( $queryArray as $name => $value ) { |
173 | | - $query[] = $name . '[' . strlen( $value ) . ']=' . $value; |
174 | | - } |
175 | | - $payflow_query = implode( '&', $query ); |
176 | | - $this->log( "PayflowPro query array (formatted): " . print_r( $payflow_query, TRUE ), LOG_LEVEL_DEBUG ); |
177 | | - |
178 | | - // assign header data necessary for the curl_setopt() function |
179 | | - $order_id = date( 'ymdH' ) . rand( 1000, 9999 ); //why? |
180 | | - $user_agent = $_SERVER['HTTP_USER_AGENT']; |
181 | | - $headers[] = 'Content-Type: text/namevalue'; |
182 | | - $headers[] = 'Content-Length : ' . strlen( $payflow_query ); |
183 | | - $headers[] = 'X-VPS-Client-Timeout: 45'; |
184 | | - $headers[] = 'X-VPS-Request-ID:' . $order_id; |
185 | | - $ch = curl_init(); |
186 | | - |
187 | | - curl_setopt( $ch, CURLOPT_URL, $this->pfp_url ); |
188 | | - curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers ); |
189 | | - curl_setopt( $ch, CURLOPT_USERAGENT, $user_agent ); |
190 | | - curl_setopt( $ch, CURLOPT_HEADER, 1 ); |
191 | | - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); |
192 | | - curl_setopt( $ch, CURLOPT_TIMEOUT, 90 ); |
193 | | - curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 0 ); |
194 | | - curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 ); |
195 | | - curl_setopt( $ch, CURLOPT_POSTFIELDS, $payflow_query ); |
196 | | - curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 ); |
197 | | - curl_setopt( $ch, CURLOPT_FORBID_REUSE, true ); |
198 | | - curl_setopt( $ch, CURLOPT_POST, 1 ); |
199 | | - |
200 | | - // As suggested in the PayPal developer forum sample code, try more than once to get a response |
201 | | - // in case there is a general network issue |
202 | | - for ( $i=1; $i <=3; $i++ ) { |
203 | | - $this->log( "Attempt #$i to connect to PayflowPro...", LOG_LEVEL_DEBUG ); |
204 | | - $status = curl_exec( $ch ); |
205 | | - $headers = curl_getinfo( $ch ); |
206 | | - |
207 | | - if ( $headers['http_code'] != 200 && $headers['http_code'] != 403 ) { |
208 | | - sleep( 5 ); |
209 | | - } elseif ( $headers['http_code'] == 200 || $headers['http_code'] == 403 ) { |
210 | | - $this->log( "Succesfully connected to PayflowPro", LOG_LEVEL_DEBUG ); |
211 | | - break; |
212 | | - } |
213 | | - } |
214 | | - |
215 | | - if ( $headers['http_code'] != 200 ) { |
216 | | - $this->log( "No response from PayflowPro after $i attempts." ); |
217 | | - curl_close( $ch ); |
218 | | - exit(1); |
219 | | - } |
220 | | - |
221 | | - curl_close( $ch ); |
222 | | - |
223 | | - $this->log( "PayflowPro reported status: $status", LOG_LEVEL_DEBUG ); |
224 | | - return $status; |
225 | | - } |
226 | | - |
227 | | - /** |
228 | | - * Parse the result code out of PayflowPro's status message. |
229 | | - * |
230 | | - * This is modified from queue2civicrm_payflow_result() in the Drupal queue2civicrm module. |
231 | | - * That code, however, seemed to be cataloging all of the key/value pairs in the status |
232 | | - * message. Since we only care about the result code, that's all I'm looking for. |
233 | | - * Perhaps it is more favorable to return an aray of the key/value pairs in the status |
234 | | - * message... |
235 | | - * |
236 | | - * @status string The full status message returned by a PayflowPro queyry |
237 | | - * @return int PayflowPro result code, FALSE on failure |
238 | | - */ |
239 | | - protected function parse_payflow_transaction_status( $status ) { |
240 | | - // we only really care about the 'RESULT' portion of the status message |
241 | | - $result = strstr( $status, 'RESULT' ); |
242 | | - |
243 | | - // log the result string? |
244 | | - $this->log( "PayflowPro RESULT string: $result", LOG_LEVEL_DEBUG ); |
245 | | - |
246 | | - // establish our key/value positions in the string to facilitate extracting the value |
247 | | - $key_position = strpos( $result, '=' ); |
248 | | - $value_position = strpos( $result, '&' ) ? strpos( $result, '&' ) : strlen( $result) ; |
249 | | - |
250 | | - $result_code = substr( $result, $key_position + 1, $value_position - $key_position - 1 ); |
251 | | - $this->log( "PayflowPro result code: $result_code", LOG_LEVEL_DEBUG ); |
252 | | - return $result_code; |
253 | | - } |
254 | | - |
255 | | - /** |
256 | | - * Apropriately handles pending transactions based on the PayflowPro result code |
257 | | - * |
258 | | - * @param int PayflowPro result code |
259 | | - * @param string Formatted message to send to a queue |
260 | | - */ |
261 | | - protected function handle_pending_transaction( $result_code, $message ) { |
262 | | - switch ( $result_code ) { |
263 | | - case "0": // push to confirmed queue |
264 | | - $this->log( "Attempting to push message to confirmed queue: " . print_r( $message, TRUE ), LOG_LEVEL_DEBUG ); |
265 | | - if ( $this->queue_message( $this->activemq_confirmed_queue, $message )) { |
266 | | - $this->log( "Succesfully pushed message to confirmed queue.", LOG_LEVEL_DEBUG ); |
267 | | - } |
268 | | - break; |
269 | | - case "126": // push back to pending queue |
270 | | - case "26": //push back to pending queue |
271 | | - $this->log( "Attempting to push message back to pending queue: " . print_r( $message, TRUE ), LOG_LEVEL_DEBUG ); |
272 | | - if ( $this->queue_message( $this->activemq_pending_queue, $message )) { |
273 | | - $this->log( "Succesfully pushed message back to pending queue", LOG_LEVEL_DEBUG ); |
274 | | - } |
275 | | - break; |
276 | | - default: |
277 | | - $this->log( "Message ignored: " . print_r( $message, TRUE ), LOG_LEVEL_DEBUG ); |
278 | | - break; |
279 | | - } |
280 | | - } |
281 | | - |
282 | | - /** |
283 | | - * Loads configuration options |
284 | | - * |
285 | | - * Config options can be set in localSettings.php or with arguments passed in via the command |
286 | | - * line. Command line arguments will override localSettings.php defined options. |
287 | | - */ |
288 | | - protected function load_config_options() { |
289 | | - // Associative array of mediawiki option => maintenance arg name |
290 | | - $options = array ( |
291 | | - 'wgPayflowProURL' => 'pfp-url', |
292 | | - 'wgPayflowProPartnerID' => 'pfp-partner-id', |
293 | | - 'wgPayflowProVendorID' => 'pfp-vendor-id', |
294 | | - 'wgPayflowProUserID' => 'pfp-user-id', |
295 | | - 'wgPayflowProPassword' => 'pfp-password', |
296 | | - 'wgActiveMQStompURI' => 'activemq-stomp-uri', |
297 | | - 'wgActiveMQPendingPath' => 'activemq-pending-queue', |
298 | | - 'wgActiveMQConfirmedPath' => 'activemq-confirmed-queue', |
299 | | - 'wgActiveMQPendingProcessingBatchSize' => 'batch-size', |
300 | | - 'wgActiveMQPendingProcessLogLevel' => 'log-level' ); |
301 | | - |
302 | | - // loop through our options and set their values, |
303 | | - // overrides local settings with command line options |
304 | | - foreach ( $options as $mw_option => $m_arg_name ) { |
305 | | - global ${$mw_option}; |
306 | | - |
307 | | - // replace - with _ from CL args to map to class properties |
308 | | - $property = str_replace( "-", "_", $m_arg_name ); |
309 | | - |
310 | | - // set class property with the config option |
311 | | - $this->$property = parent::getOption( $m_arg_name, ${$mw_option} ); |
312 | | - |
313 | | - $this->log( "$property = $mw_option, $m_arg_name, ${$mw_option}", LOG_LEVEL_DEBUG ); |
314 | | - $this->log( "$property = {$this->$property}", LOG_LEVEL_DEBUG ); |
315 | | - } |
316 | | - } |
317 | | - |
318 | | - /** |
319 | | - * Registers parameters with the maintenance system |
320 | | - */ |
321 | | - protected function register_params() { |
322 | | - //parent::addOption( $name, $description, $required=false, $withArg=false ) |
323 | | - parent::addOption( 'pfp-url', 'The PayflowPro URL', FALSE, TRUE ); |
324 | | - parent::addOption( |
325 | | - 'pfp-partner-id', |
326 | | - 'Authorized reseller ID for PayflowPro (eg "PayPal")', |
327 | | - FALSE, |
328 | | - TRUE ); |
329 | | - parent::addOption( 'pfp-vendor-id', 'The PayflowPro merchant login ID', FALSE, TRUE ); |
330 | | - parent::addOption( |
331 | | - 'pfp-user-id', |
332 | | - "If one or more users are set up, this should be the authorized PayflowPro user ID, otherwise this should be the same as pfp-vendor-id", |
333 | | - FALSE, |
334 | | - TRUE ); |
335 | | - parent::addOption( |
336 | | - 'pfp-password', |
337 | | - 'The PayflowPro merchant login password. ** Declaring this here could be a security risk.', |
338 | | - FALSE, |
339 | | - TRUE ); |
340 | | - parent::addOption( |
341 | | - 'activemq-stomp-uri', |
342 | | - 'The URI to the ActiveMQ Stomp listener', |
343 | | - FALSE, |
344 | | - TRUE ); |
345 | | - parent::addOption( |
346 | | - 'activemq-pending-queue', |
347 | | - 'The path to the ActiveMQ pending queue', |
348 | | - FALSE, |
349 | | - TRUE ); |
350 | | - parent::addOption( |
351 | | - 'activemq-confirmed-queue', |
352 | | - 'The path to the ActiveMQ confirmed queue', |
353 | | - FALSE, |
354 | | - TRUE ); |
355 | | - // I know there is a method to handle batch size this in the parent class, but it makes me |
356 | | - // do things I don't want to do. |
357 | | - parent::addOption( |
358 | | - 'batch-size', |
359 | | - 'The number of queue items to process. Default: ' . $this->batch_size, |
360 | | - FALSE, |
361 | | - TRUE ); |
362 | | - parent::addOption( |
363 | | - 'log-level', |
364 | | - "The level of logging you would like to enable. Options:\n\t0 - No output\n\t1 - Normal, minimal output\n\t2 - Debug, verbose output", |
365 | | - FALSE, |
366 | | - TRUE ); |
367 | | - } |
368 | | - |
369 | | - /** |
370 | | - * Establishes a connection to the stomp listener |
371 | | - * |
372 | | - * Stomp listner URI set in config options (via command line or localSettings.php). |
373 | | - * If a connection cannot be established, will exit with non-0 status. |
374 | | - */ |
375 | | - protected function set_stomp_connection() { |
376 | | - //attempt to connect, otherwise throw exception and exit |
377 | | - $this->log( "Attempting to connect to Stomp listener: {$this->activemq_stomp_uri}", LOG_LEVEL_DEBUG ); |
378 | | - try { |
379 | | - //establish stomp connection |
380 | | - $this->stomp = new Stomp( $this->activemq_stomp_uri ); |
381 | | - $this->stomp->connect(); |
382 | | - $this->log( "Successfully connected to Stomp listener", LOG_LEVEL_DEBUG ); |
383 | | - } catch (Stomp_Exception $e) { |
384 | | - $this->log( "Stomp connection failed: " . $e->getMessage() ); |
385 | | - exit(1); |
386 | | - } |
387 | | - } |
388 | | - |
389 | | - /** |
390 | | - * Logs messages of less than or equal value to the defined log level. |
391 | | - * |
392 | | - * Log levels available are defined by the constants LOG_LEVEL_*. The log level for the script |
393 | | - * defaults to LOG_LEVEL_INFO but can be overridden in LocalSettings.php or via a command line |
394 | | - * argument passed in at run time. |
395 | | - * |
396 | | - * @param $message string containing the message you wish to log |
397 | | - * @param $level int of the highest log level you wish to output messages for |
398 | | - */ |
399 | | - protected function log( $message, $level=LOG_LEVEL_INFO ) { |
400 | | - if ( $this->log_level >= $level ) echo date('c') . ": " . $level . " : " . $message . "\n"; |
401 | | - } |
402 | | - |
403 | | - public function __destruct() { |
404 | | - // clean up our stomp connection |
405 | | - $this->log( "Cleaning up stomp connection...", LOG_LEVEL_DEBUG ); |
406 | | - if ( isset( $this->stomp )) $this->stomp->disconnect(); |
407 | | - $this->log( "Stomp connection cleaned up", LOG_LEVEL_DEBUG ); |
408 | | - $this->log( "Exiting gracefully" ); |
409 | | - |
410 | | - } |
411 | | -} |
412 | | - |
413 | | -$maintClass = "StompPFPPendingProcessor"; |
414 | | -require_once( DO_MAINTENANCE ); |