Yoshi2889 /
SMF2.1
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * This file is the file which all subscription gateways should call |
||
| 5 | * when a payment has been received - it sorts out the user status. |
||
| 6 | * |
||
| 7 | * Simple Machines Forum (SMF) |
||
| 8 | * |
||
| 9 | * @package SMF |
||
| 10 | * @author Simple Machines http://www.simplemachines.org |
||
| 11 | * @copyright 2017 Simple Machines and individual contributors |
||
| 12 | * @license http://www.simplemachines.org/about/smf/license.php BSD |
||
| 13 | * |
||
| 14 | * @version 2.1 Beta 4 |
||
| 15 | */ |
||
| 16 | |||
| 17 | // Start things rolling by getting SMF alive... |
||
| 18 | $ssi_guest_access = true; |
||
| 19 | if (!file_exists(dirname(__FILE__) . '/SSI.php')) |
||
| 20 | die('Cannot find SSI.php'); |
||
| 21 | |||
| 22 | require_once(dirname(__FILE__) . '/SSI.php'); |
||
| 23 | require_once($sourcedir . '/ManagePaid.php'); |
||
| 24 | |||
| 25 | // For any admin emailing. |
||
| 26 | require_once($sourcedir . '/Subs-Admin.php'); |
||
| 27 | |||
| 28 | loadLanguage('ManagePaid'); |
||
| 29 | |||
| 30 | // If there's literally nothing coming in, let's take flight! |
||
| 31 | if (empty($_POST)) |
||
| 32 | { |
||
| 33 | header('Content-Type: text/html; charset=' . (empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'ISO-8859-1' : $txt['lang_character_set']) : $modSettings['global_character_set'])); |
||
| 34 | die($txt['paid_no_data']); |
||
| 35 | } |
||
| 36 | |||
| 37 | // I assume we're even active? |
||
| 38 | if (empty($modSettings['paid_enabled'])) |
||
| 39 | exit; |
||
| 40 | |||
| 41 | // If we have some custom people who find out about problems load them here. |
||
| 42 | $notify_users = array(); |
||
| 43 | if (!empty($modSettings['paid_email_to'])) |
||
| 44 | { |
||
| 45 | foreach (explode(',', $modSettings['paid_email_to']) as $email) |
||
| 46 | $notify_users[] = array( |
||
| 47 | 'email' => $email, |
||
| 48 | 'name' => $txt['who_member'], |
||
| 49 | 'id' => 0, |
||
| 50 | ); |
||
| 51 | } |
||
| 52 | |||
| 53 | // We need to see whether we can find the correct payment gateway, |
||
| 54 | // we'll going to go through all our gateway scripts and find out |
||
| 55 | // if they are happy with what we have. |
||
| 56 | $txnType = ''; |
||
| 57 | $gatewayHandles = loadPaymentGateways(); |
||
| 58 | foreach ($gatewayHandles as $gateway) |
||
| 59 | { |
||
| 60 | $gatewayClass = new $gateway['payment_class'](); |
||
| 61 | if ($gatewayClass->isValid()) |
||
| 62 | { |
||
| 63 | $txnType = $gateway['code']; |
||
| 64 | break; |
||
| 65 | } |
||
| 66 | } |
||
| 67 | |||
| 68 | if (empty($txnType)) |
||
| 69 | generateSubscriptionError($txt['paid_unknown_transaction_type']); |
||
| 70 | |||
| 71 | // Get the subscription and member ID amoungst others... |
||
| 72 | @list($subscription_id, $member_id) = $gatewayClass->precheck(); |
||
| 73 | |||
| 74 | // Integer these just in case. |
||
| 75 | $subscription_id = (int) $subscription_id; |
||
| 76 | $member_id = (int) $member_id; |
||
| 77 | |||
| 78 | // This would be bad... |
||
| 79 | if (empty($member_id)) |
||
| 80 | generateSubscriptionError($txt['paid_empty_member']); |
||
| 81 | |||
| 82 | // Verify the member. |
||
| 83 | $request = $smcFunc['db_query']('', ' |
||
| 84 | SELECT id_member, member_name, real_name, email_address |
||
| 85 | FROM {db_prefix}members |
||
| 86 | WHERE id_member = {int:current_member}', |
||
| 87 | array( |
||
| 88 | 'current_member' => $member_id, |
||
| 89 | ) |
||
| 90 | ); |
||
| 91 | // Didn't find them? |
||
| 92 | if ($smcFunc['db_num_rows']($request) === 0) |
||
| 93 | generateSubscriptionError(sprintf($txt['paid_could_not_find_member'], $member_id)); |
||
| 94 | $member_info = $smcFunc['db_fetch_assoc']($request); |
||
| 95 | $smcFunc['db_free_result']($request); |
||
| 96 | |||
| 97 | // Get the subscription details. |
||
| 98 | $request = $smcFunc['db_query']('', ' |
||
| 99 | SELECT cost, length, name |
||
| 100 | FROM {db_prefix}subscriptions |
||
| 101 | WHERE id_subscribe = {int:current_subscription}', |
||
| 102 | array( |
||
| 103 | 'current_subscription' => $subscription_id, |
||
| 104 | ) |
||
| 105 | ); |
||
| 106 | |||
| 107 | // Didn't find it? |
||
| 108 | View Code Duplication | if ($smcFunc['db_num_rows']($request) === 0) |
|
|
0 ignored issues
–
show
|
|||
| 109 | generateSubscriptionError(sprintf($txt['paid_count_not_find_subscription'], $member_id, $subscription_id)); |
||
| 110 | |||
| 111 | $subscription_info = $smcFunc['db_fetch_assoc']($request); |
||
| 112 | $smcFunc['db_free_result']($request); |
||
| 113 | |||
| 114 | // We wish to check the pending payments to make sure we are expecting this. |
||
| 115 | $request = $smcFunc['db_query']('', ' |
||
| 116 | SELECT id_sublog, payments_pending, pending_details, end_time |
||
| 117 | FROM {db_prefix}log_subscribed |
||
| 118 | WHERE id_subscribe = {int:current_subscription} |
||
| 119 | AND id_member = {int:current_member} |
||
| 120 | LIMIT 1', |
||
| 121 | array( |
||
| 122 | 'current_subscription' => $subscription_id, |
||
| 123 | 'current_member' => $member_id, |
||
| 124 | ) |
||
| 125 | ); |
||
| 126 | View Code Duplication | if ($smcFunc['db_num_rows']($request) === 0) |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 127 | generateSubscriptionError(sprintf($txt['paid_count_not_find_subscription_log'], $member_id, $subscription_id)); |
||
| 128 | $subscription_info += $smcFunc['db_fetch_assoc']($request); |
||
| 129 | $smcFunc['db_free_result']($request); |
||
| 130 | |||
| 131 | // Is this a refund etc? |
||
| 132 | if ($gatewayClass->isRefund()) |
||
| 133 | { |
||
| 134 | // If the end time subtracted by current time, is not greater |
||
| 135 | // than the duration (ie length of subscription), then we close it. |
||
| 136 | if ($subscription_info['end_time'] - time() < $subscription_info['length']) |
||
| 137 | { |
||
| 138 | // Delete user subscription. |
||
| 139 | removeSubscription($subscription_id, $member_id); |
||
| 140 | $subscription_act = time(); |
||
| 141 | $status = 0; |
||
| 142 | } |
||
| 143 | else |
||
| 144 | { |
||
| 145 | loadSubscriptions(); |
||
| 146 | $subscription_act = $subscription_info['end_time'] - $context['subscriptions'][$subscription_id]['num_length']; |
||
| 147 | $status = 1; |
||
| 148 | } |
||
| 149 | |||
| 150 | // Mark it as complete so we have a record. |
||
| 151 | $smcFunc['db_query']('', ' |
||
| 152 | UPDATE {db_prefix}log_subscribed |
||
| 153 | SET end_time = {int:current_time} |
||
| 154 | WHERE id_subscribe = {int:current_subscription} |
||
| 155 | AND id_member = {int:current_member} |
||
| 156 | AND status = {int:status}', |
||
| 157 | array( |
||
| 158 | 'current_time' => $subscription_act, |
||
| 159 | 'current_subscription' => $subscription_id, |
||
| 160 | 'current_member' => $member_id, |
||
| 161 | 'status' => $status, |
||
| 162 | ) |
||
| 163 | ); |
||
| 164 | |||
| 165 | // Receipt? |
||
| 166 | if (!empty($modSettings['paid_email']) && $modSettings['paid_email'] == 2) |
||
| 167 | { |
||
| 168 | $replacements = array( |
||
| 169 | 'NAME' => $subscription_info['name'], |
||
| 170 | 'REFUNDNAME' => $member_info['member_name'], |
||
| 171 | 'REFUNDUSER' => $member_info['real_name'], |
||
| 172 | 'PROFILELINK' => $scripturl . '?action=profile;u=' . $member_id, |
||
| 173 | 'DATE' => timeformat(time(), false), |
||
| 174 | ); |
||
| 175 | |||
| 176 | emailAdmins('paid_subscription_refund', $replacements, $notify_users); |
||
| 177 | } |
||
| 178 | |||
| 179 | } |
||
| 180 | // Otherwise is it what we want, a purchase? |
||
| 181 | elseif ($gatewayClass->isPayment() || $gatewayClass->isSubscription()) |
||
| 182 | { |
||
| 183 | $cost = json_decode($subscription_info['cost'], true); |
||
| 184 | $total_cost = $gatewayClass->getCost(); |
||
| 185 | $notify = false; |
||
| 186 | |||
| 187 | // For one off's we want to only capture them once! |
||
| 188 | if (!$gatewayClass->isSubscription()) |
||
| 189 | { |
||
| 190 | $real_details = json_decode($subscription_info['pending_details'], true); |
||
| 191 | if (empty($real_details)) |
||
| 192 | generateSubscriptionError(sprintf($txt['paid_count_not_find_outstanding_payment'], $member_id, $subscription_id)); |
||
| 193 | |||
| 194 | // Now we just try to find anything pending. |
||
| 195 | // We don't really care which it is as security happens later. |
||
| 196 | foreach ($real_details as $id => $detail) |
||
| 197 | { |
||
| 198 | unset($real_details[$id]); |
||
| 199 | if ($detail[3] == 'payback' && $subscription_info['payments_pending']) |
||
| 200 | $subscription_info['payments_pending']--; |
||
| 201 | break; |
||
| 202 | } |
||
| 203 | |||
| 204 | $subscription_info['pending_details'] = empty($real_details) ? '' : json_encode($real_details); |
||
| 205 | |||
| 206 | $smcFunc['db_query']('', ' |
||
| 207 | UPDATE {db_prefix}log_subscribed |
||
| 208 | SET payments_pending = {int:payments_pending}, pending_details = {string:pending_details} |
||
| 209 | WHERE id_sublog = {int:current_subscription_item}', |
||
| 210 | array( |
||
| 211 | 'payments_pending' => $subscription_info['payments_pending'], |
||
| 212 | 'current_subscription_item' => $subscription_info['id_sublog'], |
||
| 213 | 'pending_details' => $subscription_info['pending_details'], |
||
| 214 | ) |
||
| 215 | ); |
||
| 216 | } |
||
| 217 | |||
| 218 | // Is this flexible? |
||
| 219 | if ($subscription_info['length'] == 'F') |
||
| 220 | { |
||
| 221 | $found_duration = 0; |
||
| 222 | |||
| 223 | // This is a little harder, can we find the right duration? |
||
| 224 | foreach ($cost as $duration => $value) |
||
| 225 | { |
||
| 226 | if ($duration == 'fixed') |
||
| 227 | continue; |
||
| 228 | elseif ((float) $value == (float) $total_cost) |
||
| 229 | $found_duration = strtoupper(substr($duration, 0, 1)); |
||
| 230 | } |
||
| 231 | |||
| 232 | // If we have the duration then we're done. |
||
| 233 | if ($found_duration !== 0) |
||
| 234 | { |
||
| 235 | $notify = true; |
||
| 236 | addSubscription($subscription_id, $member_id, $found_duration); |
||
| 237 | } |
||
| 238 | } |
||
| 239 | else |
||
| 240 | { |
||
| 241 | $actual_cost = $cost['fixed']; |
||
| 242 | |||
| 243 | // It must be at least the right amount. |
||
| 244 | if ($total_cost != 0 && $total_cost >= $actual_cost) |
||
| 245 | { |
||
| 246 | // Add the subscription. |
||
| 247 | $notify = true; |
||
| 248 | addSubscription($subscription_id, $member_id); |
||
| 249 | } |
||
| 250 | } |
||
| 251 | |||
| 252 | // Send a receipt? |
||
| 253 | if (!empty($modSettings['paid_email']) && $modSettings['paid_email'] == 2 && $notify) |
||
| 254 | { |
||
| 255 | $replacements = array( |
||
| 256 | 'NAME' => $subscription_info['name'], |
||
| 257 | 'SUBNAME' => $member_info['member_name'], |
||
| 258 | 'SUBUSER' => $member_info['real_name'], |
||
| 259 | 'SUBEMAIL' => $member_info['email_address'], |
||
| 260 | 'PRICE' => sprintf($modSettings['paid_currency_symbol'], $total_cost), |
||
| 261 | 'PROFILELINK' => $scripturl . '?action=profile;u=' . $member_id, |
||
| 262 | 'DATE' => timeformat(time(), false), |
||
| 263 | ); |
||
| 264 | |||
| 265 | emailAdmins('paid_subscription_new', $replacements, $notify_users); |
||
| 266 | } |
||
| 267 | } |
||
| 268 | // Maybe they're cancelling. Some subscriptions may require actively doing something, but PayPal doesn't, for example. |
||
| 269 | elseif ($gatewayClass->isCancellation()) |
||
| 270 | { |
||
| 271 | if (method_exists($gatewayClass, 'performCancel')) |
||
| 272 | $gatewayClass->performCancel($subscription_id, $member_id, $subscription_info); |
||
| 273 | } |
||
| 274 | else |
||
| 275 | { |
||
| 276 | // Some other "valid" transaction such as: |
||
| 277 | // |
||
| 278 | // subscr_signup: This IPN response (txn_type) is sent only the first time the user signs up for a subscription. |
||
| 279 | // It then does not fire in any event later. This response is received somewhere before or after the first payment of |
||
| 280 | // subscription is received (txn_type=subscr_payment) which is what we do process |
||
| 281 | // |
||
| 282 | // Should we log any of these ... |
||
| 283 | } |
||
| 284 | |||
| 285 | // In case we have anything specific to do. |
||
| 286 | $gatewayClass->close(); |
||
| 287 | |||
| 288 | /** |
||
| 289 | * Log an error then exit |
||
| 290 | * |
||
| 291 | * @param string $text The error to log |
||
| 292 | * @return void |
||
| 293 | */ |
||
| 294 | function generateSubscriptionError($text) |
||
| 295 | { |
||
| 296 | global $modSettings, $notify_users, $smcFunc; |
||
| 297 | |||
| 298 | // Send an email? |
||
| 299 | if (!empty($modSettings['paid_email'])) |
||
| 300 | { |
||
| 301 | $replacements = array( |
||
| 302 | 'ERROR' => $text, |
||
| 303 | ); |
||
| 304 | |||
| 305 | emailAdmins('paid_subscription_error', $replacements, $notify_users); |
||
| 306 | } |
||
| 307 | |||
| 308 | // Maybe we can try to give them the post data? |
||
| 309 | if (!empty($_POST)) |
||
| 310 | { |
||
| 311 | foreach ($_POST as $key => $val) |
||
| 312 | $text .= '<br>' . $smcFunc['htmlspecialchars']($key) . ': ' . $smcFunc['htmlspecialchars']($val); |
||
| 313 | } |
||
| 314 | |||
| 315 | // Then just log and die. |
||
| 316 | log_error($text, 'paidsubs'); |
||
| 317 | |||
| 318 | exit; |
||
| 319 | } |
||
| 320 | |||
| 321 | ?> |
||
|
0 ignored issues
–
show
It is not recommended to use PHP's closing tag
?> in files other than templates.
Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore. A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever. Loading history...
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.