Completed
Pull Request — release-2.1 (#5058)
by 01
06:23
created

subscriptions.php (1 issue)

Upgrade to new PHP Analysis Engine

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 2018 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();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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)
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)
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 = $smcFunc['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 = $smcFunc['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) ? '' : $smcFunc['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
?>