Total Complexity | 66 |
Total Lines | 301 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like paypal_payment often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use paypal_payment, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
149 | class paypal_payment |
||
150 | { |
||
151 | /** |
||
152 | * @var string $return_data The data to return |
||
153 | */ |
||
154 | private $return_data; |
||
155 | |||
156 | /** |
||
157 | * This function returns true/false for whether this gateway thinks the data is intended for it. |
||
158 | * |
||
159 | * @return boolean Whether this gateway things the data is valid |
||
160 | */ |
||
161 | public function isValid() |
||
162 | { |
||
163 | global $modSettings; |
||
164 | |||
165 | // Has the user set up an email address? |
||
166 | if ((empty($modSettings['paidsubs_test']) && empty($modSettings['paypal_email'])) || (!empty($modSettings['paidsubs_test']) && empty($modSettings['paypal_sandbox_email']))) |
||
167 | return false; |
||
168 | // Check the correct transaction types are even here. |
||
169 | if ((!isset($_POST['txn_type']) && !isset($_POST['payment_status'])) || (!isset($_POST['business']) && !isset($_POST['receiver_email']))) |
||
170 | return false; |
||
171 | // Correct email address? |
||
172 | if (!isset($_POST['business'])) |
||
173 | $_POST['business'] = $_POST['receiver_email']; |
||
174 | |||
175 | // Are we testing? |
||
176 | if (!empty($modSettings['paidsubs_test']) && strtolower($modSettings['paypal_sandbox_email']) != strtolower($_POST['business']) && (empty($modSettings['paypal_additional_emails']) || !in_array(strtolower($_POST['business']), explode(',', strtolower($modSettings['paypal_additional_emails']))))) |
||
177 | return false; |
||
178 | elseif (strtolower($modSettings['paypal_email']) != strtolower($_POST['business']) && (empty($modSettings['paypal_additional_emails']) || !in_array(strtolower($_POST['business']), explode(',', $modSettings['paypal_additional_emails'])))) |
||
179 | return false; |
||
180 | return true; |
||
181 | } |
||
182 | |||
183 | /** |
||
184 | * Post the IPN data received back to paypal for validation |
||
185 | * Sends the complete unaltered message back to PayPal. The message must contain the same fields |
||
186 | * in the same order and be encoded in the same way as the original message |
||
187 | * PayPal will respond back with a single word, which is either VERIFIED if the message originated with PayPal or INVALID |
||
188 | * |
||
189 | * If valid returns the subscription and member IDs we are going to process if it passes |
||
190 | * |
||
191 | * @return string A string containing the subscription ID and member ID, separated by a + |
||
192 | */ |
||
193 | public function precheck() |
||
194 | { |
||
195 | global $modSettings, $txt; |
||
196 | |||
197 | // Put this to some default value. |
||
198 | if (!isset($_POST['txn_type'])) |
||
199 | $_POST['txn_type'] = ''; |
||
200 | |||
201 | // Build the request string - starting with the minimum requirement. |
||
202 | $requestString = 'cmd=_notify-validate'; |
||
203 | |||
204 | // Now my dear, add all the posted bits in the order we got them |
||
205 | foreach ($_POST as $k => $v) |
||
206 | $requestString .= '&' . $k . '=' . urlencode($v); |
||
207 | |||
208 | // Can we use curl? |
||
209 | if (function_exists('curl_init') && $curl = curl_init((!empty($modSettings['paidsubs_test']) ? 'https://www.sandbox.' : 'https://www.') . 'paypal.com/cgi-bin/webscr')) |
||
210 | { |
||
211 | // Set the post data. |
||
212 | curl_setopt($curl, CURLOPT_POST, true); |
||
213 | curl_setopt($curl, CURLOPT_POSTFIELDS, $requestString); |
||
214 | |||
215 | // Set up the headers so paypal will accept the post |
||
216 | curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); |
||
217 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1); |
||
218 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); |
||
219 | curl_setopt($curl, CURLOPT_FORBID_REUSE, 1); |
||
220 | curl_setopt($curl, CURLOPT_HTTPHEADER, array( |
||
221 | 'Host: www.' . (!empty($modSettings['paidsubs_test']) ? 'sandbox.' : '') . 'paypal.com', |
||
222 | 'Connection: close' |
||
223 | )); |
||
224 | |||
225 | // Fetch the data returned as a string. |
||
226 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); |
||
227 | |||
228 | // Fetch the data. |
||
229 | $this->return_data = curl_exec($curl); |
||
|
|||
230 | |||
231 | // Close the session. |
||
232 | curl_close($curl); |
||
233 | } |
||
234 | // Otherwise good old HTTP. |
||
235 | else |
||
236 | { |
||
237 | // Setup the headers. |
||
238 | $header = 'POST /cgi-bin/webscr HTTP/1.1' . "\r\n"; |
||
239 | $header .= 'content-type: application/x-www-form-urlencoded' . "\r\n"; |
||
240 | $header .= 'Host: www.' . (!empty($modSettings['paidsubs_test']) ? 'sandbox.' : '') . 'paypal.com' . "\r\n"; |
||
241 | $header .= 'content-length: ' . strlen($requestString) . "\r\n"; |
||
242 | $header .= 'connection: close' . "\r\n\r\n"; |
||
243 | |||
244 | // Open the connection. |
||
245 | if (!empty($modSettings['paidsubs_test'])) |
||
246 | $fp = fsockopen('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30); |
||
247 | else |
||
248 | $fp = fsockopen('www.paypal.com', 80, $errno, $errstr, 30); |
||
249 | |||
250 | // Did it work? |
||
251 | if (!$fp) |
||
252 | generateSubscriptionError($txt['paypal_could_not_connect']); |
||
253 | |||
254 | // Put the data to the port. |
||
255 | fputs($fp, $header . $requestString); |
||
256 | |||
257 | // Get the data back... |
||
258 | while (!feof($fp)) |
||
259 | { |
||
260 | $this->return_data = fgets($fp, 1024); |
||
261 | if (strcmp(trim($this->return_data), 'VERIFIED') === 0) |
||
262 | break; |
||
263 | } |
||
264 | |||
265 | // Clean up. |
||
266 | fclose($fp); |
||
267 | } |
||
268 | |||
269 | // If this isn't verified then give up... |
||
270 | if (strcmp(trim($this->return_data), 'VERIFIED') !== 0) |
||
271 | exit; |
||
272 | |||
273 | // Check that this is intended for us. |
||
274 | if (strtolower($modSettings['paypal_email']) != strtolower($_POST['business']) && (empty($modSettings['paypal_additional_emails']) || !in_array(strtolower($_POST['business']), explode(',', strtolower($modSettings['paypal_additional_emails']))))) |
||
275 | exit; |
||
276 | |||
277 | // Is this a subscription - and if so is it a secondary payment that we need to process? |
||
278 | // If so, make sure we get it in the expected format. Seems PayPal sometimes sends it without urlencoding. |
||
279 | if (!empty($_POST['item_number']) && strpos($_POST['item_number'], ' ') !== false) |
||
280 | $_POST['item_number'] = str_replace(' ', '+', $_POST['item_number']); |
||
281 | if ($this->isSubscription() && (empty($_POST['item_number']) || strpos($_POST['item_number'], '+') === false)) |
||
282 | // Calculate the subscription it relates to! |
||
283 | $this->_findSubscription(); |
||
284 | |||
285 | // Verify the currency! |
||
286 | if (strtolower($_POST['mc_currency']) !== strtolower($modSettings['paid_currency_code'])) |
||
287 | exit; |
||
288 | |||
289 | // Can't exist if it doesn't contain anything. |
||
290 | if (empty($_POST['item_number'])) |
||
291 | exit; |
||
292 | |||
293 | // Return the id_sub and id_member |
||
294 | return explode('+', $_POST['item_number']); |
||
295 | } |
||
296 | |||
297 | /** |
||
298 | * Is this a refund? |
||
299 | * |
||
300 | * @return boolean Whether this is a refund |
||
301 | */ |
||
302 | public function isRefund() |
||
303 | { |
||
304 | if ($_POST['payment_status'] === 'Refunded' || $_POST['payment_status'] === 'Reversed' || $_POST['txn_type'] === 'Refunded' || ($_POST['txn_type'] === 'reversal' && $_POST['payment_status'] === 'Completed')) |
||
305 | return true; |
||
306 | else |
||
307 | return false; |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * Is this a subscription? |
||
312 | * |
||
313 | * @return boolean Whether this is a subscription |
||
314 | */ |
||
315 | public function isSubscription() |
||
316 | { |
||
317 | if (substr($_POST['txn_type'], 0, 14) === 'subscr_payment' && $_POST['payment_status'] === 'Completed') |
||
318 | return true; |
||
319 | else |
||
320 | return false; |
||
321 | } |
||
322 | |||
323 | /** |
||
324 | * Is this a normal payment? |
||
325 | * |
||
326 | * @return boolean Whether this is a normal payment |
||
327 | */ |
||
328 | public function isPayment() |
||
329 | { |
||
330 | if ($_POST['payment_status'] === 'Completed' && $_POST['txn_type'] === 'web_accept') |
||
331 | return true; |
||
332 | else |
||
333 | return false; |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Is this a cancellation? |
||
338 | * |
||
339 | * @return boolean Whether this is a cancellation |
||
340 | */ |
||
341 | public function isCancellation() |
||
342 | { |
||
343 | // subscr_cancel is sent when the user cancels, subscr_eot is sent when the subscription reaches final payment |
||
344 | // Neither require us to *do* anything as per performCancel(). |
||
345 | // subscr_eot, if sent, indicates an end of payments term. |
||
346 | if (substr($_POST['txn_type'], 0, 13) === 'subscr_cancel' || substr($_POST['txn_type'], 0, 10) === 'subscr_eot') |
||
347 | return true; |
||
348 | else |
||
349 | return false; |
||
350 | } |
||
351 | |||
352 | /** |
||
353 | * Things to do in the event of a cancellation |
||
354 | * |
||
355 | * @param string $subscription_id |
||
356 | * @param int $member_id |
||
357 | * @param array $subscription_info |
||
358 | */ |
||
359 | public function performCancel($subscription_id, $member_id, $subscription_info) |
||
360 | { |
||
361 | // PayPal doesn't require SMF to notify it every time the subscription is up for renewal. |
||
362 | // A cancellation should not cause the user to be immediately dropped from their subscription, but |
||
363 | // let it expire normally. Some systems require taking action in the database to deal with this, but |
||
364 | // PayPal does not, so we actually just do nothing. But this is a nice prototype/example just in case. |
||
365 | } |
||
366 | |||
367 | /** |
||
368 | * How much was paid? |
||
369 | * |
||
370 | * @return float The amount paid |
||
371 | */ |
||
372 | public function getCost() |
||
373 | { |
||
374 | return (isset($_POST['tax']) ? $_POST['tax'] : 0) + $_POST['mc_gross']; |
||
375 | } |
||
376 | |||
377 | /** |
||
378 | * Record the transaction reference to finish up. |
||
379 | * |
||
380 | */ |
||
381 | public function close() |
||
382 | { |
||
383 | global $smcFunc, $subscription_id; |
||
384 | |||
385 | // If it's a subscription record the reference. |
||
386 | if ($_POST['txn_type'] == 'subscr_payment' && !empty($_POST['subscr_id'])) |
||
387 | { |
||
388 | $smcFunc['db_query']('', ' |
||
389 | UPDATE {db_prefix}log_subscribed |
||
390 | SET vendor_ref = {string:vendor_ref} |
||
391 | WHERE id_sublog = {int:current_subscription}', |
||
392 | array( |
||
393 | 'current_subscription' => $subscription_id, |
||
394 | 'vendor_ref' => $_POST['subscr_id'], |
||
395 | ) |
||
396 | ); |
||
397 | } |
||
398 | } |
||
399 | |||
400 | /** |
||
401 | * A private function to find out the subscription details. |
||
402 | * |
||
403 | * @access private |
||
404 | * @return boolean|void False on failure, otherwise just sets $_POST['item_number'] |
||
405 | */ |
||
406 | private function _findSubscription() |
||
450 | } |
||
451 | } |
||
452 | |||
453 | ?> |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.