Issues (1686)

subscriptions.php (3 issues)

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
 * @package   ElkArte Forum
8
 * @copyright ElkArte Forum contributors
9
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
10
 *
11
 * This file contains code covered by:
12
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
13
 *
14
 * @version 2.0 dev
15
 *
16
 */
17
18
use ElkArte\Helper\Util;
19
use ElkArte\Http\Headers;
20
use ElkArte\Languages\Txt;
21
22
// Start things rolling by getting the forum alive...
23
if (!file_exists(__DIR__ . '/bootstrap.php'))
24
{
25
	die('Unable to initialize');
26
}
27
28
global $ssi_guest_access;
29
30
require_once(__DIR__ . '/bootstrap.php');
31
$ssi_guest_access = true;
32
new Bootstrap(true);
33
34
global $txt, $modSettings, $context, $scripturl;
35
36
// Need lots of help
37
require_once(SUBSDIR . '/PaidSubscriptions.subs.php');
38
require_once(SUBSDIR . '/Admin.subs.php');
39
require_once(SUBSDIR . '/Members.subs.php');
40
41
Txt::load('ManagePaid');
42
43
// If there's literally nothing coming in, let's take flight!
44
if (empty($_POST))
45
{
46
	Headers::instance()->contentType('text/html', 'UTF-8')->sendHeaders();
47
	die($txt['paid_no_data']);
48
}
49
50
// I assume we're even active?
51
if (empty($modSettings['paid_enabled']))
52
{
53
	exit;
54
}
55
56
// If we have some custom people who find out about problems load them here.
57
$notify_users = array();
58
if (!empty($modSettings['paid_email_to']))
59
{
60
	foreach (explode(',', $modSettings['paid_email_to']) as $email)
61
	{
62
		$notify_users[] = array(
63
			'email' => $email,
64
			'name' => $txt['who_member'],
65
			'id' => 0,
66
		);
67
	}
68
}
69
70
$db = database();
71
72
// We need to see whether we can find the correct payment gateway,
73
// Go through all our gateway scripts and find out if they are happy with what we have.
74
$txnType = '';
75
$gatewayHandles = loadPaymentGateways();
76
foreach ($gatewayHandles as $gateway)
77
{
78
	$gatewayClass = new $gateway['payment_class']();
79
	if ($gatewayClass->isValid())
80
	{
81
		$txnType = $gateway['code'];
82
		break;
83
	}
84
}
85
86
if (empty($txnType))
87
{
88
	generateSubscriptionError($txt['paid_unknown_transaction_type'], $notify_users);
89
}
90
91
// Get the subscription and member ID amongst others...
92
@[$subscription_id, $member_id] = $gatewayClass->precheck();
93
94
// Integer these just in case.
95
$subscription_id = (int) $subscription_id;
96
$member_id = (int) $member_id;
97
98
// This would be bad...
99
if (empty($member_id))
100
{
101
	generateSubscriptionError($txt['paid_empty_member'], $notify_users);
102
}
103
104
// Verify the member.
105
$member_info = getBasicMemberData($member_id);
106
107
// Didn't find them?
108
if (empty($member_info))
109
{
110
	generateSubscriptionError(sprintf($txt['paid_could_not_find_member'], $member_id), $notify_users);
111
}
112
113
// Get the subscription details.
114
$request = $db->query('', '
115
	SELECT 
116
		cost, length, name
117
	FROM {db_prefix}subscriptions
118
	WHERE id_subscribe = {int:current_subscription}',
119
	array(
120
		'current_subscription' => $subscription_id,
121
	)
122
);
123
124
// Didn't find it?
125
if ($request->num_rows() === 0)
126
{
127
	generateSubscriptionError(sprintf($txt['paid_count_not_find_subscription'], $member_id, $subscription_id), $notify_users);
128
}
129
130
$subscription_info = $request->fetch_assoc();
131
$request->free_result();
132
133
// We wish to check the pending payments to make sure we are expecting this.
134
$request = $db->query('', '
135
	SELECT 
136
		id_sublog, id_subscribe, payments_pending, pending_details, end_time
137
	FROM {db_prefix}log_subscribed
138
	WHERE id_subscribe = {int:current_subscription}
139
		AND id_member = {int:current_member}
140
	LIMIT 1',
141
	array(
142
		'current_subscription' => $subscription_id,
143
		'current_member' => $member_id,
144
	)
145
);
146
if ($request->num_rows() === 0)
147
{
148
	generateSubscriptionError(sprintf($txt['paid_count_not_find_subscription_log'], $member_id, $subscription_id), $notify_users);
149
}
150
151
$subscription_info += $request->fetch_assoc();
152
$request->free_result();
153
154
// Is this a refund?
155
if ($gatewayClass->isRefund())
156
{
157
	handleRefund($subscription_info, $member_id, $context['subscriptions'][$subscription_id]['num_length']);
158
159
	// Receipt?
160
	if (!empty($modSettings['paid_email']) && $modSettings['paid_email'] == 2)
161
	{
162
		$replacements = array(
163
			'NAME' => $subscription_info['name'],
164
			'REFUNDNAME' => $member_info['member_name'],
165
			'REFUNDUSER' => $member_info['real_name'],
166
			'PROFILELINK' => getUrl('profile', ['action' => 'profile', 'u' => $member_id, 'name' => $member_info['real_name']]),
167
			'DATE' => standardTime(time(), false),
168
		);
169
170
		emailAdmins('paid_subscription_refund', $replacements, $notify_users);
0 ignored issues
show
It seems like $notify_users can also be of type array<mixed,array<string,integer|mixed|string>>; however, parameter $additional_recipients of emailAdmins() does only seem to accept integer[], maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

170
		emailAdmins('paid_subscription_refund', $replacements, /** @scrutinizer ignore-type */ $notify_users);
Loading history...
171
	}
172
}
173
// Otherwise is it what we want, a purchase?
174
elseif ($gatewayClass->isPayment() || $gatewayClass->isSubscription())
175
{
176
	$cost = Util::unserialize($subscription_info['cost']);
177
	$total_cost = $gatewayClass->getCost();
178
	$notify = false;
179
180
	// For one off's we want to only capture them once!
181
	if (!$gatewayClass->isSubscription())
182
	{
183
		$real_details = Util::unserialize($subscription_info['pending_details']);
184
		if (empty($real_details))
185
		{
186
			generateSubscriptionError(sprintf($txt['paid_count_not_find_outstanding_payment'], $member_id, $subscription_id), $notify_users);
187
		}
188
189
		// Now we just try to find anything pending.
190
		// We don't really care which it is as security happens later.
191
		foreach ($real_details as $id => $detail)
192
		{
193
			unset($real_details[$id]);
194
			if ($detail[3] == 'payback' && $subscription_info['payments_pending'])
195
			{
196
				$subscription_info['payments_pending']--;
197
			}
198
199
			break;
200
		}
201
202
		$subscription_info['pending_details'] = empty($real_details) ? '' : serialize($real_details);
203
204
		updateNonrecurrent($subscription_info);
205
	}
206
207
	// Is this flexible?
208
	if ($subscription_info['length'] == 'F')
209
	{
210
		$found_duration = 0;
211
212
		// This is a little harder, can we find the right duration?
213
		foreach ($cost as $duration => $value)
214
		{
215
			if ($duration == 'fixed')
216
			{
217
				continue;
218
			}
219
220
			if ((float) $value === (float) $total_cost)
221
			{
222
				$found_duration = strtoupper(substr($duration, 0, 1));
223
			}
224
		}
225
226
		// If we have the duration then we're done.
227
		if ($found_duration !== 0)
228
		{
229
			$notify = true;
230
			addSubscription($subscription_id, $member_id, $found_duration);
231
		}
232
	}
233
	else
234
	{
235
		$actual_cost = $cost['fixed'];
236
237
		// It must be at least the right amount.
238
		if ($total_cost != 0 && $total_cost >= $actual_cost)
239
		{
240
			// Add the subscription.
241
			$notify = true;
242
			addSubscription($subscription_id, $member_id);
243
		}
244
	}
245
246
	// Send a receipt?
247
	if (!empty($modSettings['paid_email']) && $modSettings['paid_email'] == 2 && $notify)
248
	{
249
		$replacements = array(
250
			'NAME' => $subscription_info['name'],
251
			'SUBNAME' => $member_info['member_name'],
252
			'SUBUSER' => $member_info['real_name'],
253
			'SUBEMAIL' => $member_info['email_address'],
254
			'PRICE' => sprintf($modSettings['paid_currency_symbol'], $total_cost),
255
			'PROFILELINK' => getUrl('profile', ['action' => 'profile', 'u' => $member_id, 'name' => $member_info['real_name']]),
256
			'DATE' => standardTime(time(), false),
257
		);
258
259
		emailAdmins('paid_subscription_new', $replacements, $notify_users);
260
	}
261
}
262
// Maybe they're cancelling. This allows payment gateways to perform processing if needed
263
elseif ($gatewayClass->isCancellation())
264
{
265
	if (method_exists($gatewayClass, 'processCancelation'))
266
	{
267
		$gatewayClass->processCancelation($subscription_id, $member_id, $subscription_info);
268
	}
269
}
270
else
271
{
272
	// Some other "valid" transaction such as:
273
	//
274
	// subscr_signup: This IPN response (txn_type) is sent only the first time the user signs up for a subscription.
275
	// It then does not fire in any event later. This response is received somewhere before or after the first payment of
276
	// subscription is received (txn_type=subscr_payment) which is what we do process
277
	//
278
	// Should we log any of these ...
279
}
280
281
// In case we have anything specific to do.
282
$gatewayClass->close($subscription_id);
283
284
/**
285
 * Log an error then exit
286
 *
287
 * @param string $text
288
 * @param array $notify_users
289
 */
290
function generateSubscriptionError($text, $notify_users = [])
291
{
292
	global $modSettings;
293
294
	// Send an email?
295
	if (!empty($modSettings['paid_email']))
296
	{
297
		$replacements = array(
298
			'ERROR' => $text,
299
		);
300
301
		emailAdmins('paid_subscription_error', $replacements, $notify_users);
302
	}
303
304
	// Maybe we can try to give them the post data?
305
	if (!empty($_POST))
306
	{
307
		foreach ($_POST as $key => $val)
308
		{
309
			$text .= '<br />' . Util::htmlspecialchars($key) . ': ' . Util::htmlspecialchars($val);
310
		}
311
	}
312
313
	// Then just log and die.
314
	\ElkArte\Errors\Errors::instance()->log_error($text);
0 ignored issues
show
The type ElkArte\Errors\Errors was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
315
316
	exit;
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
317
}
318