Index: trunk/extensions/DonationInterface/SpecialPaypalIPNProcessing.php |
— | — | @@ -1,261 +0,0 @@ |
2 | | -<?php |
3 | | -/** |
4 | | - * Special class to act as IPN listener and handler. Also pushes messages into the ActiveMQ |
5 | | - * queueing system. |
6 | | - * |
7 | | - * NOTE: THIS IS EXPERIMENTAL AND INCOMPLETE |
8 | | - * |
9 | | - * Requires ContributionTracking extension. |
10 | | - * |
11 | | - * Configurable variables: |
12 | | - * $wgPayPalIPNProcessingLogLevel - can be one of the defined LOG_LEVEL_* constants. |
13 | | - * |
14 | | - * PayPal IPN docs: https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_admin_IPNIntro |
15 | | - * |
16 | | - * @author Arthur Richards <arichards@wikimedia.org> |
17 | | - * @TODO: add a better mechanism for changing log level |
18 | | - */ |
19 | | - |
20 | | -/** Set available log levels **/ |
21 | | -DEFINE( 'LOG_LEVEL_QUIET', 0 ); // output nothing |
22 | | -DEFINE( 'LOG_LEVEL_INFO', 1 ); // output minimal info |
23 | | -DEFINE( 'LOG_LEVEL_DEBUG', 2 ); // output lots of info |
24 | | - |
25 | | -class PaypalIPNProcessing extends UnlistedSpecialPage { |
26 | | - |
27 | | - // set the apropriate logging level |
28 | | - protected $log_level = LOG_LEVEL_INFO; |
29 | | - |
30 | | - // path to Stomp |
31 | | - protected $stomp_path = dirname( __FILE__ ) . "/../../activemq_stomp/Stomp.php"; |
32 | | - |
33 | | - // path to pending queue |
34 | | - protected $pending_queue = '/queue/pending_paypal'; |
35 | | - |
36 | | - function __construct() { |
37 | | - parent::__construct( 'PaypalIPNProcessing' ); |
38 | | - wfLoadExtensionMessages( 'PaypalIPNProcessing' ); |
39 | | - $this->out( "Loading Paypal IPN processor" ); |
40 | | - |
41 | | - if ( isset( $wgPayPalIPNProcessingLogLevel )) { |
42 | | - $this->log_level = $wgPayPalIPNProcessingLogLevel; |
43 | | - } |
44 | | - |
45 | | - if ( isset( $wgPayPalIPNProcessingStompPath )) { |
46 | | - $this->stomp_path = $wgPayPalIPNProcessingStompPath; |
47 | | - } |
48 | | - |
49 | | - if ( isset( $wgPayPalIPNProcessingPendingQueue )) { |
50 | | - $this->pending_queue = $wgPayPalIPNProcessingPendingQueue; |
51 | | - } |
52 | | - } |
53 | | - |
54 | | - /** |
55 | | - * Output in plain text? |
56 | | - */ |
57 | | - function execute( $par ) { |
58 | | - global $wgRequest, $wgOut; |
59 | | - $wgOut->disable(); |
60 | | - header( "Content-type: text/plain; charset=utf-8" ); |
61 | | - |
62 | | - //make sure we're actually getting something posted to the page. |
63 | | - if ( empty( $_POST )) { |
64 | | - $this->out( "Received an empty post object." ); |
65 | | - return; |
66 | | - } |
67 | | - |
68 | | - // connect to stomp |
69 | | - $this->set_stomp_connection(); |
70 | | - |
71 | | - //push message to pending queue |
72 | | - $contribution = $this->ipn_parse( $_POST ); |
73 | | - // do the queueing - perhaps move out the tracking checking to its own func? |
74 | | - if ( !$this->queue_message( $this->pending_queue, $contribution )){ |
75 | | - $this->out( "There was a problem queueing the message to the queue: " . $this->pending_queue ); |
76 | | - $this->out( "Message: " . print_r( $contribution, TRUE ), LOG_LEVEL_DEBUG ); |
77 | | - } |
78 | | - |
79 | | - |
80 | | - //verify the message with PayPal |
81 | | - if ( !$this->ipn_verify( $_POST )) { |
82 | | - $this->out( "Message did not pass PayPal verification." ); |
83 | | - $this->out( "\$_POST contents: " . print_r( $_POST, TRUE ), LOG_LEVEL_DEBUG ); |
84 | | - return; |
85 | | - } |
86 | | - |
87 | | - //push to donations queue, remove from pending |
88 | | - } |
89 | | - |
90 | | - /** |
91 | | - * Verify IPN's message validitiy |
92 | | - * |
93 | | - * Yoinked from fundcore_paypal_verify() in fundcore/gateways/fundcore_paypal.module Drupal module |
94 | | - * @param $post_data array of post data - the message received from PayPal |
95 | | - * @return bool |
96 | | - */ |
97 | | - protected function ipn_verify( $post_data ) { |
98 | | - if ( $post_data[ 'payment_status' ] != 'Completed' ) { |
99 | | - // order not completed |
100 | | - $this->out( "Message not marked as complete." ); |
101 | | - return FALSE; |
102 | | - } |
103 | | - |
104 | | - if ( $post_data[ 'mc_gross' ] <= 0 ) { |
105 | | - $this->out( "Message has 0 or less in the mc_gross field." ); |
106 | | - return FALSE; |
107 | | - } |
108 | | - |
109 | | - // url to respond to paypal with verification response |
110 | | - $postback_url = 'https://www.paypal.com/cgi-bin/webscr'; |
111 | | - if (isset($post_data['test_ipn'])) { |
112 | | - $postback_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr'; |
113 | | - } |
114 | | - |
115 | | - // respond with exact same data/structure + cmd=_notify-validate |
116 | | - $attr = $post_data; |
117 | | - $attr['cmd'] = '_notify-validate'; |
118 | | - |
119 | | - // send the message back to PayPal for verification |
120 | | - $status = $this->curl_download( $postback_url, $attr ); |
121 | | - if ($status != 'VERIFIED') { |
122 | | - $this->out( "The message could not be verified." ); |
123 | | - $this->out( "Returned with status: $status", LOG_LEVEL_DEBUG ); |
124 | | - return FALSE; |
125 | | - } |
126 | | - |
127 | | - return TRUE; |
128 | | - } |
129 | | - |
130 | | - /** |
131 | | - * Parse the PayPal message/post data into the format we need for ActiveMQ |
132 | | - * |
133 | | - * @param $post_data array containing the $_POST data from PayPal |
134 | | - * @return array containing the parsed/formatted message for stuffing into ActiveMQ |
135 | | - */ |
136 | | - protected function ipn_parse( $post_data ) { |
137 | | - $contribution = array(); |
138 | | - |
139 | | - $timestamp = strtotime($post_data['payment_date']); |
140 | | - |
141 | | - // Detect if we're using the new-style |
142 | | - if (is_numeric($post_data['option_selection1'])) { |
143 | | - // get the database connection to the tracking table |
144 | | - $tracking_db = contributionTrackingConnection(); |
145 | | - |
146 | | - // Query from Drupal: $tracking_data = db_fetch_array(db_query('SELECT * FROM {contribution_tracking} WHERE id = %d', $post_data['option_selection1'])); |
147 | | - $tracking_query = $tracking_db->select( |
148 | | - 'contribution_tracking', |
149 | | - array( 'optout', 'anonymous', 'note' ), |
150 | | - array( 'id' => $post_data[ 'option_selection1' ]); |
151 | | - $tracking_data = $tracking_query->fetchRow(); |
152 | | - |
153 | | - $contribution['contribution_tracking_id'] = $post_data['option_selection1']; |
154 | | - $contribution['optout'] = $tracking_data['optout']; |
155 | | - $contribution['anonymous'] = $tracking_data['anonymous']; |
156 | | - $contribution['comment'] = $tracking_data['note']; |
157 | | - } else { |
158 | | - $split = explode(';', $post_data['option_selection1']); |
159 | | - $contribution['anonymous'] = ($split[0] != 'public' && $split[0] != 'Mention my name'); |
160 | | - $contribution['comment'] = $post_data['option_selection2']; |
161 | | - } |
162 | | - |
163 | | - $contribution['email'] = $post_data['payer_email']; |
164 | | - $contribution['first_name'] = $post_data['first_name']; |
165 | | - $contribution['last_name'] = $post_data['last_name']; |
166 | | - |
167 | | - $split = split("\n", str_replace("\r", '', $post_data['address_street'])); |
168 | | - |
169 | | - $contribution['street_address'] = $split[0]; |
170 | | - $contribution['supplemental_address_1'] = $split[1]; |
171 | | - $contribution['city'] = $post_data['address_city']; |
172 | | - $contribution['original_currency'] = $post_data['mc_currency']; |
173 | | - $contribution['original_gross'] = $post_data['mc_gross']; |
174 | | - $contribution['fee'] = $post_data['mc_fee'], |
175 | | - $contribution['gross'] = $post_data['mc_gross'], |
176 | | - $contribution['net'] = $contribution['gross'] - $contribution['fee']; |
177 | | - $contribution['date'] = $timestamp; |
178 | | - |
179 | | - //print_r the contribution? |
180 | | - |
181 | | - return $contribution; |
182 | | - } |
183 | | - |
184 | | - /** |
185 | | - * Connect to a URL, send optional post variables, return data |
186 | | - * |
187 | | - * Yoinked from _fundcore_paypal_download in fundcore/gateways/fundcore_paypal.module Drupal module |
188 | | - * @param $url String of the URL to connect to |
189 | | - * @param $vars Array of POST variables |
190 | | - * @return String containing the output returned from Server |
191 | | - */ |
192 | | - protected function curl_download( $url, $vars = NULL ) { |
193 | | - $ch = curl_init(); |
194 | | - curl_setopt($ch, CURLOPT_URL, $url); |
195 | | - curl_setopt($ch, CURLOPT_HEADER, 0); |
196 | | - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); |
197 | | - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
198 | | - |
199 | | - if ($vars !== NULL) { |
200 | | - curl_setopt($ch, CURLOPT_POST, 1); |
201 | | - curl_setopt($ch, CURLOPT_POSTFIELDS, $vars); |
202 | | - } |
203 | | - $data = curl_exec($ch); |
204 | | - if (!$data) { |
205 | | - $data = curl_error($ch); |
206 | | - } |
207 | | - curl_close($ch); |
208 | | - return $data; |
209 | | - } |
210 | | - |
211 | | - /** |
212 | | - * Establishes a connection to the stomp listener |
213 | | - * |
214 | | - * Stomp listner URI set in config options (via command line or localSettings.php). |
215 | | - * If a connection cannot be established, will exit with non-0 status. |
216 | | - */ |
217 | | - protected function set_stomp_connection() { |
218 | | - require_once( $this->stomp_path ); |
219 | | - //attempt to connect, otherwise throw exception and exit |
220 | | - $this->out( "Attempting to connect to Stomp listener: {$this->activemq_stomp_uri}", LOG_LEVEL_DEBUG ); |
221 | | - try { |
222 | | - //establish stomp connection |
223 | | - $this->stomp = new Stomp( $this->activemq_stomp_uri ); |
224 | | - $this->stomp->connect(); |
225 | | - $this->out( "Successfully connected to Stomp listener", LOG_LEVEL_DEBUG ); |
226 | | - } catch (Stomp_Exception $e) { |
227 | | - $this->out( "Stomp connection failed: " . $e->getMessage() ); |
228 | | - exit(1); |
229 | | - } |
230 | | - } |
231 | | - |
232 | | - /** |
233 | | - * Send a message to the queue |
234 | | - * |
235 | | - * @param $destination string of the destination path for where to send a message |
236 | | - * @param $message string the (formatted) message to send to the queue |
237 | | - * @param $options array of additional Stomp options |
238 | | - * @return bool result from send, FALSE on failure |
239 | | - */ |
240 | | - protected function queue_message( $destination, $message, $options = array( 'persistent' => TRUE )) { |
241 | | - $this->out( "Attempting to queue message...", LOG_LEVEL_DEBUG ); |
242 | | - $sent = $this->stomp->send( $destination, $message, $options ); |
243 | | - $this->out( "Result of queuing message: $sent", LOG_LEVEL_DEBUG ); |
244 | | - return $sent; |
245 | | - } |
246 | | - |
247 | | - |
248 | | - |
249 | | - /** |
250 | | - * Formats text for output. |
251 | | - * |
252 | | - * @param $msg String a message to output. |
253 | | - * @param $level the Level at which the message should be output. |
254 | | - */ |
255 | | - protected function out( $msg, $level=LOG_LEVEL_INFO ) { |
256 | | - if ( $this->log_level >= $level ) echo date( 'c' ) . ": " . $msg . "\n"; |
257 | | - } |
258 | | - |
259 | | - public function __destruct() { |
260 | | - $this->out( "Exiting gracefully." ); |
261 | | - } |
262 | | -} |
Index: trunk/extensions/DonationInterface/paypal_gateway/IPN/PaypalIPNListener.php |
— | — | @@ -0,0 +1,380 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * PayPal IPN listener and handler. Also pushes messages into the ActiveMQ queueing system. |
| 5 | + * |
| 6 | + * This is currently designed to act as a mechanism for pushing transactions received from PayPal's |
| 7 | + * IPN system into a 'pending' queue from ActiveMQ. Once a transaction is verified, it is removed |
| 8 | + * from the pending queue and pushed into a 'verified' queue. If it is not verified, a copy is left |
| 9 | + * in the pending queue. This particular logic takes place in execute(). |
| 10 | + * |
| 11 | + * Generally speaking, this should likely be abstracted to allow for more flexible use cases, as what |
| 12 | + * is outlined above is pretty specific, but most of the other methods should allow for some flexibility - |
| 13 | + * particularly if you were to subclass this. |
| 14 | + * |
| 15 | + * Also, this is close to being useable with other queueing systems that can talk Stomp. However, this |
| 16 | + * currently employs some things that are likely unique to ActiveMQ, namely setting some custom header |
| 17 | + * information for items going into a pending queue and then using ActiveMQ 'selectors' to pull out |
| 18 | + * a specific message. |
| 19 | + * |
| 20 | + * Does not actually require Mediawiki to run, can be run as stand alone or can be integrated |
| 21 | + * with a Mediawiki extension. See StandaloneListener.php.example for a guide on how to do this. |
| 22 | + * |
| 23 | + * Configurable variables: |
| 24 | + * log_level => $int // 0, 1, or 2 (see constant definitions below for options) |
| 25 | + * stomp_path => path to Stomp.php |
| 26 | + * pending_queue => the queue to push pending items to |
| 27 | + * verified_queue => the queue to push verfiied items to |
| 28 | + * activemq_stomp_uri => the URI for the activemq broker |
| 29 | + * contrib_db_host => the hostname where the contribution_tracking table lives |
| 30 | + * contrib_db_username => the username for the db where contribution_tracking lives |
| 31 | + * contrib_db_password => the pw for accessing the db where contribution_tracking lives |
| 32 | + * conrtib_db_name => the db name where contribution_tracking lives |
| 33 | + * |
| 34 | + * Note that the contrib_db* variables are likely of no use to you, unless you're using CiviCRM with Drupal and |
| 35 | + * are using a special contribution tracking module... So if you're not doing that, you can likely |
| 36 | + * leave those out of your config. |
| 37 | + * |
| 38 | + * PayPal IPN docs: https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_admin_IPNIntro |
| 39 | + * |
| 40 | + * @author Arthur Richards <arichards@wikimedia.org> |
| 41 | + * @TODO: |
| 42 | + * add output for DB connection/query |
| 43 | + * abstract out the contribution_tracking stuff so this is more flexible? |
| 44 | + */ |
| 45 | + |
| 46 | +/** Set available log levels **/ |
| 47 | +DEFINE( 'LOG_LEVEL_QUIET', 0 ); // output nothing |
| 48 | +DEFINE( 'LOG_LEVEL_INFO', 1 ); // output minimal info |
| 49 | +DEFINE( 'LOG_LEVEL_DEBUG', 2 ); // output lots of info |
| 50 | + |
| 51 | +class PaypalIPNProcessor { |
| 52 | + |
| 53 | + // set the apropriate logging level |
| 54 | + $log_level = LOG_LEVEL_INFO; |
| 55 | + |
| 56 | + // path to Stomp |
| 57 | + $stomp_path = "../../activemq_stomp/Stomp.php"; |
| 58 | + |
| 59 | + // path to pending queue |
| 60 | + $pending_queue = '/queue/pending_paypal'; |
| 61 | + |
| 62 | + // path to the verified queue |
| 63 | + $verified_queue = '/queue/donations'; |
| 64 | + |
| 65 | + // URI to activeMQ |
| 66 | + $activemq_stomp_uri = 'tcp://localhost:61613'; |
| 67 | + |
| 68 | + /** |
| 69 | + * Class constructor, sets configurable parameters |
| 70 | + * |
| 71 | + * @param $opts array of key, value pairs where the 'key' is the parameter name and the |
| 72 | + * value is the value you wish to set |
| 73 | + */ |
| 74 | + function __construct( $opts = array() ) { |
| 75 | + // set the log level |
| 76 | + if ( array_key_exists( 'log_level', $opts )) { |
| 77 | + $this->log_level = $opts[ 'log_level' ]; |
| 78 | + unset( $opts[ 'log_level'] ); |
| 79 | + } |
| 80 | + |
| 81 | + $this->out( "Loading Paypal IPN processor" ); |
| 82 | + |
| 83 | + // set parameters |
| 84 | + foreach ( $opts as $key => $value ) { |
| 85 | + $this->{$key} = $value; |
| 86 | + |
| 87 | + // star out passwords in the log!!!! |
| 88 | + if ( $key == 'contrib_db_password' ) $value = '******'; |
| 89 | + |
| 90 | + $this->out( "Setting parameter $key as $value.", LOG_LEVEL_DEBUG ); |
| 91 | + } |
| 92 | + |
| 93 | + //prepare our stomp connection |
| 94 | + $this->set_stomp_connection(); |
| 95 | + } |
| 96 | + |
| 97 | + /** |
| 98 | + * Execute IPN procesing. |
| 99 | + * |
| 100 | + * Take the data sent from a PayPal IPN request, verify it against the IPN, then push the |
| 101 | + * transaction to the queue. Before verifying the transaction against the IPN, this will |
| 102 | + * place the transaction originally received in the pending queue. If the transaction is |
| 103 | + * verified, it will be removed from the pending queue and placed in an accepted queue. If |
| 104 | + * it is not verified, it will be left in the pending queue for dealing with in some other |
| 105 | + * fashion. |
| 106 | + * |
| 107 | + * @param $data Array containing the message received from the IPN, likely the contents of |
| 108 | + * $_POST |
| 109 | + */ |
| 110 | + function execute( $data ) { |
| 111 | + |
| 112 | + //make sure we're actually getting something posted to the page. |
| 113 | + if ( empty( $data )) { |
| 114 | + $this->out( "Received an empty object, nothing to verify." ); |
| 115 | + return; |
| 116 | + } |
| 117 | + |
| 118 | + // connect to stomp |
| 119 | + $this->set_stomp_connection(); |
| 120 | + |
| 121 | + //push message to pending queue |
| 122 | + $contribution = $this->ipn_parse( $data ); |
| 123 | + |
| 124 | + // generate a unique id for the message 2 ensure we're manipulating the correct message later on |
| 125 | + $tx_id = time() . '_' . mt_rand(); //should be sufficiently unique... |
| 126 | + $headers = array( 'persistent' => TRUE, 'JMSCorrelationID' => $tx_id ); |
| 127 | + $this->out( "Setting JMSCorrelationID: $tx_id", LOG_LEVEL_DEBUG ); |
| 128 | + |
| 129 | + // do the queueing - perhaps move out the tracking checking to its own func? |
| 130 | + if ( !$this->queue_message( $this->pending_queue, json_encode( $contribution ), $headers )) { |
| 131 | + $this->out( "There was a problem queueing the message to the queue: " . $this->pending_queue ); |
| 132 | + $this->out( "Message: " . print_r( $contribution, TRUE ), LOG_LEVEL_DEBUG ); |
| 133 | + } |
| 134 | + |
| 135 | + |
| 136 | + //verify the message with PayPal |
| 137 | + if ( !$this->ipn_verify( $data )) { |
| 138 | + $this->out( "Message did not pass PayPal verification." ); |
| 139 | + $this->out( "\$_POST contents: " . print_r( $data, TRUE ), LOG_LEVEL_DEBUG ); |
| 140 | + return; |
| 141 | + } |
| 142 | + |
| 143 | + // pull the message off of the pending queue using a 'selector' to make sure we're getting the right msg |
| 144 | + $properties['selector'] = "JMSCorrelationID = '$tx_id'"; |
| 145 | + $this->out( "Attempting to pull mssage from pending queue with JMSCorrelationID = $tx_id", LOG_LEVEL_DEBUG ); |
| 146 | + $msg = $this->fetch_message( $this->pending_queue, $properties ); |
| 147 | + if ( $msg ) { |
| 148 | + $this->out( "Pulled message from pending queue: " . print_r( json_decode( $msg ), TRUE ), LOG_LEVEL_DEBUG); |
| 149 | + } else { |
| 150 | + $this->out( "FAILED retrieving message from pending queue.", LOG_LEVEL_DEBUG ); |
| 151 | + return; |
| 152 | + } |
| 153 | + |
| 154 | + // push to verified queue |
| 155 | + if ( !$this->queue_message( $this->verified_queue, $msg->body )) { |
| 156 | + $this->out( "There was a problem queueing the message to the quque: " . $this->verified_queue ); |
| 157 | + $this->out( "Message: " . print_r( $contribution, TRUE ), LOG_LEVEL_DEBUG ); |
| 158 | + return; |
| 159 | + } |
| 160 | + |
| 161 | + // remove from pending |
| 162 | + $this->out( "Attempting to remove message from pending.", LOG_LEVEL_DEBUG ); |
| 163 | + if ( !$this->stomp->ack( $msg )) { |
| 164 | + $this->out( "There was a problem remoivng the verified message from the pending queue: " . print_r( json_decode( $msg, TRUE ))); |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + /** |
| 169 | + * Verify IPN's message validitiy |
| 170 | + * |
| 171 | + * Yoinked from fundcore_paypal_verify() in fundcore/gateways/fundcore_paypal.module Drupal module |
| 172 | + * @param $post_data array of post data - the message received from PayPal |
| 173 | + * @return bool |
| 174 | + */ |
| 175 | + public function ipn_verify( $post_data ) { |
| 176 | + if ( $post_data[ 'payment_status' ] != 'Completed' ) { |
| 177 | + // order not completed |
| 178 | + $this->out( "Message not marked as complete." ); |
| 179 | + return FALSE; |
| 180 | + } |
| 181 | + |
| 182 | + if ( $post_data[ 'mc_gross' ] <= 0 ) { |
| 183 | + $this->out( "Message has 0 or less in the mc_gross field." ); |
| 184 | + return FALSE; |
| 185 | + } |
| 186 | + |
| 187 | + // url to respond to paypal with verification response |
| 188 | + $postback_url = 'https://www.paypal.com/cgi-bin/webscr'; // should this be configurable? |
| 189 | + if (isset($post_data['test_ipn'])) { |
| 190 | + $postback_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr'; |
| 191 | + } |
| 192 | + |
| 193 | + // respond with exact same data/structure + cmd=_notify-validate |
| 194 | + $attr = $post_data; |
| 195 | + $attr['cmd'] = '_notify-validate'; |
| 196 | + |
| 197 | + // send the message back to PayPal for verification |
| 198 | + $status = $this->curl_download( $postback_url, $attr ); |
| 199 | + if ($status != 'VERIFIED') { |
| 200 | + $this->out( "The message could not be verified." ); |
| 201 | + $this->out( "Returned with status: $status", LOG_LEVEL_DEBUG ); |
| 202 | + return FALSE; |
| 203 | + } |
| 204 | + |
| 205 | + return TRUE; |
| 206 | + } |
| 207 | + |
| 208 | + /** |
| 209 | + * Parse the PayPal message/post data into the format we need for ActiveMQ |
| 210 | + * |
| 211 | + * @param $post_data array containing the $_POST data from PayPal |
| 212 | + * @return array containing the parsed/formatted message for stuffing into ActiveMQ |
| 213 | + */ |
| 214 | + public function ipn_parse( $post_data ) { |
| 215 | + $this->out( "Attempting to parse: " . print_r( $post_data, TRUE ), LOG_LEVEL_DEBUG ); |
| 216 | + $contribution = array(); |
| 217 | + |
| 218 | + $timestamp = strtotime($post_data['payment_date']); |
| 219 | + |
| 220 | + // Detect if we're using the new-style (likely unique to Wikimedia) - this should be handled elsewhere |
| 221 | + if (is_numeric($post_data['option_selection1'])) { |
| 222 | + // get the database connection to the tracking table |
| 223 | + $this->contribution_tracking_connection(); |
| 224 | + $tracking_data = $this->get_tracking_data( $post_data['option_selection1'] ); |
| 225 | + if ( !$tracking_data ) { //we have a problem! |
| 226 | + $this->out( "There is no contribution ID associated with this transaction." ); |
| 227 | + exit(); |
| 228 | + } |
| 229 | + $contribution['contribution_tracking_id'] = $post_data['option_selection1']; |
| 230 | + $contribution['optout'] = $tracking_data['optout']; |
| 231 | + $contribution['anonymous'] = $tracking_data['anonymous']; |
| 232 | + $contribution['comment'] = $tracking_data['note']; |
| 233 | + } else { |
| 234 | + $split = explode(';', $post_data['option_selection1']); |
| 235 | + $contribution['anonymous'] = ($split[0] != 'public' && $split[0] != 'Mention my name'); |
| 236 | + $contribution['comment'] = $post_data['option_selection2']; |
| 237 | + } |
| 238 | + |
| 239 | + $contribution['email'] = $post_data['payer_email']; |
| 240 | + $contribution['first_name'] = $post_data['first_name']; |
| 241 | + $contribution['last_name'] = $post_data['last_name']; |
| 242 | + |
| 243 | + $split = split("\n", str_replace("\r", '', $post_data['address_street'])); |
| 244 | + |
| 245 | + $contribution['street_address'] = $split[0]; |
| 246 | + $contribution['supplemental_address_1'] = $split[1]; |
| 247 | + $contribution['city'] = $post_data['address_city']; |
| 248 | + $contribution['original_currency'] = $post_data['mc_currency']; |
| 249 | + $contribution['original_gross'] = $post_data['mc_gross']; |
| 250 | + $contribution['fee'] = $post_data['mc_fee']; |
| 251 | + $contribution['gross'] = $post_data['mc_gross']; |
| 252 | + $contribution['net'] = $contribution['gross'] - $contribution['fee']; |
| 253 | + $contribution['date'] = $timestamp; |
| 254 | + |
| 255 | + return $contribution; |
| 256 | + } |
| 257 | + |
| 258 | + /** |
| 259 | + * Connect to a URL, send optional post variables, return data |
| 260 | + * |
| 261 | + * Yoinked from _fundcore_paypal_download in fundcore/gateways/fundcore_paypal.module Drupal module |
| 262 | + * @param $url String of the URL to connect to |
| 263 | + * @param $vars Array of POST variables |
| 264 | + * @return String containing the output returned from Server |
| 265 | + */ |
| 266 | + public function curl_download( $url, $vars = NULL ) { |
| 267 | + $ch = curl_init(); |
| 268 | + curl_setopt($ch, CURLOPT_URL, $url); |
| 269 | + curl_setopt($ch, CURLOPT_HEADER, 0); |
| 270 | + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); |
| 271 | + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
| 272 | + |
| 273 | + if ($vars !== NULL) { |
| 274 | + curl_setopt($ch, CURLOPT_POST, 1); |
| 275 | + curl_setopt($ch, CURLOPT_POSTFIELDS, $vars); |
| 276 | + } |
| 277 | + $data = curl_exec($ch); |
| 278 | + if (!$data) { |
| 279 | + $data = curl_error($ch); |
| 280 | + $this->out( "Curl error: " . $data ); |
| 281 | + } |
| 282 | + curl_close($ch); |
| 283 | + return $data; |
| 284 | + } |
| 285 | + |
| 286 | + /** |
| 287 | + * Establishes a connection to the stomp listener |
| 288 | + * |
| 289 | + * Stomp listner URI set in config options (via command line or localSettings.php). |
| 290 | + * If a connection cannot be established, will exit with non-0 status. |
| 291 | + */ |
| 292 | + protected function set_stomp_connection() { |
| 293 | + require_once( $this->stomp_path ); |
| 294 | + //attempt to connect, otherwise throw exception and exit |
| 295 | + $this->out( "Attempting to connect to Stomp listener: {$this->activemq_stomp_uri}", LOG_LEVEL_DEBUG ); |
| 296 | + try { |
| 297 | + //establish stomp connection |
| 298 | + $this->stomp = new Stomp( $this->activemq_stomp_uri ); |
| 299 | + $this->stomp->connect(); |
| 300 | + $this->out( "Successfully connected to Stomp listener", LOG_LEVEL_DEBUG ); |
| 301 | + } catch (Stomp_Exception $e) { |
| 302 | + $this->out( "Stomp connection failed: " . $e->getMessage() ); |
| 303 | + exit(1); |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + /** |
| 308 | + * Send a message to the queue |
| 309 | + * |
| 310 | + * @param $destination string of the destination path for where to send a message |
| 311 | + * @param $message string the (formatted) message to send to the queue |
| 312 | + * @param $options array of additional Stomp options |
| 313 | + * @return bool result from send, FALSE on failure |
| 314 | + */ |
| 315 | + public function queue_message( $destination, $message, $options = array( 'persistent' => TRUE )) { |
| 316 | + $this->out( "Attempting to queue message to $destination", LOG_LEVEL_DEBUG ); |
| 317 | + $sent = $this->stomp->send( $destination, $message, $options ); |
| 318 | + $this->out( "Result of queuing message: $sent", LOG_LEVEL_DEBUG ); |
| 319 | + return $sent; |
| 320 | + } |
| 321 | + |
| 322 | + /** |
| 323 | + * Fetch latest raw message from a queue |
| 324 | + * |
| 325 | + * @param $destination string of the destination path from where to fetch a message |
| 326 | + * @return mixed raw message (Stomp_Frame object) from Stomp client or False if no msg present |
| 327 | + */ |
| 328 | + public function fetch_message( $destination, $properties = NULL ) { |
| 329 | + $this->out( "Attempting to connect to queue at: $destination", LOG_LEVEL_DEBUG ); |
| 330 | + if ( $properties ) $this->out( "With the following properties: " . print_r( $properties, TRUE )); |
| 331 | + $this->stomp->subscribe( $destination, $properties ); |
| 332 | + $this->out( "Attempting to pull queued item", LOG_LEVEL_DEBUG ); |
| 333 | + $message = $this->stomp->readFrame(); |
| 334 | + return $message; |
| 335 | + } |
| 336 | + |
| 337 | + /** |
| 338 | + * Establish a connection with the contribution database. |
| 339 | + * |
| 340 | + * The properties contrib_db_host, contrib_db_username, contrib_db_password and |
| 341 | + * contrib_db_name should be set prior to the execution of this method. |
| 342 | + */ |
| 343 | + protected function contribution_tracking_connection() { |
| 344 | + $this->contrib_db = mysql_connect( |
| 345 | + $this->contrib_db_host, |
| 346 | + $this->contrib_db_username, |
| 347 | + $this->contrib_db_password ); |
| 348 | + mysql_select_db( $this->contrib_db_name, $this->contrib_db ); |
| 349 | + } |
| 350 | + |
| 351 | + /** |
| 352 | + * Fetches tracking data we need to for this transaction from the contribution_tracking table |
| 353 | + * |
| 354 | + * @param int the ID of the transaction we care about |
| 355 | + * @return array containing the key=>value pairs of data from the contribution_tracking table |
| 356 | + */ |
| 357 | + protected function get_tracking_data( $id ) { |
| 358 | + //sanitize the $id |
| 359 | + $id = mysql_real_escape_string( $id ); |
| 360 | + $query = "SELECT * FROM contribution_tracking WHERE id=$id"; |
| 361 | + $this->out( "Preparing to run query on contribution_tracking: $query", LOG_LEVEL_DEBUG ); |
| 362 | + $result = mysql_query( $query ); |
| 363 | + $row = mysql_fetch_assoc( $result ); |
| 364 | + $this->out( "Query result: " . print_r( $row, TRUE ), LOG_LEVEL_DEBUG ); |
| 365 | + return $row; |
| 366 | + } |
| 367 | + |
| 368 | + /** |
| 369 | + * Formats text for output. |
| 370 | + * |
| 371 | + * @param $msg String a message to output. |
| 372 | + * @param $level the Level at which the message should be output. |
| 373 | + */ |
| 374 | + protected function out( $msg, $level=LOG_LEVEL_INFO ) { |
| 375 | + if ( $this->log_level >= $level ) echo date( 'c' ) . ": " . $msg . "\n"; |
| 376 | + } |
| 377 | + |
| 378 | + public function __destruct() { |
| 379 | + $this->out( "Exiting gracefully." ); |
| 380 | + } |
| 381 | +} |
Index: trunk/extensions/DonationInterface/paypal_gateway/IPN/StandaloneListener.php.example |
— | — | @@ -0,0 +1,54 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * This is an example of how you might run the PaypalIPNListener in stand alone mode |
| 5 | + * |
| 6 | + * It's just an example, so do what you want. Note that the contrib_db* config settings |
| 7 | + * are likely only important to you if you are using CiviCRM with Drupal and taking |
| 8 | + * donations via Mediawiki with the ContributionTracking extension installed. Point being, |
| 9 | + * it probably doesn't apply to you. Take a look in PaypalIPNListener.php to see more about |
| 10 | + * how that stuff might get used. |
| 11 | + * |
| 12 | + * @author Arthur Richards <arichards@wikimedia.org> |
| 13 | + */ |
| 14 | + |
| 15 | +// turn on output buffering so we can capture the output |
| 16 | +ob_start(); |
| 17 | + |
| 18 | +// require the actual listener |
| 19 | +require_once( '/path/to/PaypalIPNListener.php' ); |
| 20 | + |
| 21 | +// set some configuration variables (for more info, check in PaypalIPNListener.php |
| 22 | +$config = array( |
| 23 | + 'log_level' => 2, //debug |
| 24 | + 'stomp_path' => '/path/to/Stomp.php', |
| 25 | + 'pending_queue' => '/queue/pending_paypal', |
| 26 | + 'verified_queue' => '/queue/verified_paypal', |
| 27 | + 'activemq_stomp_uri' => 'tcp://localhost:61613/', |
| 28 | + 'contrib_db_host' => 'localhost', |
| 29 | + 'contrib_db_username' => 'user', |
| 30 | + 'contrib_db_password' => 'password', |
| 31 | + 'contrib_db_name' => 'drupal', |
| 32 | +); |
| 33 | + |
| 34 | +// define a file where you can log the output from the listener |
| 35 | +$log_file = "out_" . date( 'Ymd' ) . '.log'; //for logging the output |
| 36 | + |
| 37 | +// instantaite the listener with our config options |
| 38 | +$listener = new PaypalIPNProcessor( $config ); |
| 39 | + |
| 40 | +// pass some data to the listner, usually this will be posted from PayPal's IPN |
| 41 | +$listener->execute( $_POST ); |
| 42 | + |
| 43 | +// shutdown the listener |
| 44 | +unset( $listener ); |
| 45 | + |
| 46 | +// get the output from the buffer |
| 47 | +$output = ob_get_clean(); |
| 48 | +ob_flush(); |
| 49 | + |
| 50 | +// open our log file |
| 51 | +$handle = fopen( $log_file, 'a' ); |
| 52 | + |
| 53 | +// write the output to the log file |
| 54 | +fwrite( $handle, $output ); |
| 55 | +?> |