MDL-69166 core_payment: Renamed plugintype name from pg to paygw
[moodle.git] / payment / classes / helper.php
CommitLineData
4865d2a0
SR
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Contains helper class for the payment subsystem.
19 *
20 * @package core_payment
21 * @copyright 2019 Shamim Rezaie <shamim@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25namespace core_payment;
26
9476b489
MG
27use core_payment\event\account_created;
28use core_payment\event\account_deleted;
29use core_payment\event\account_updated;
30
4865d2a0
SR
31/**
32 * Helper class for the payment subsystem.
33 *
34 * @copyright 2019 Shamim Rezaie <shamim@moodle.com>
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 */
37class helper {
38
39 /**
40 * Returns an accumulated list of supported currencies by all payment gateways.
41 *
42 * @return string[] An array of the currency codes in the three-character ISO-4217 format
43 */
44 public static function get_supported_currencies(): array {
45 $currencies = [];
46
6b3d163a 47 $plugins = \core_plugin_manager::instance()->get_enabled_plugins('paygw');
4865d2a0 48 foreach ($plugins as $plugin) {
6b3d163a
SR
49 /** @var \paygw_paypal\gateway $classname */
50 $classname = '\paygw_' . $plugin . '\gateway';
4865d2a0 51
15a4e4c8 52 $currencies += component_class_callback($classname, 'get_supported_currencies', [], []);
4865d2a0
SR
53 }
54
55 $currencies = array_unique($currencies);
56
57 return $currencies;
58 }
f3d75264
SR
59
60 /**
61 * Returns the list of gateways that can process payments in the given currency.
62 *
15a4e4c8 63 * @param string $component
7d10f352 64 * @param string $paymentarea
15a4e4c8 65 * @param int $componentid
f3d75264
SR
66 * @return string[]
67 */
90fbc58d 68 public static function get_available_gateways(string $component, string $paymentarea, int $componentid): array {
f3d75264
SR
69 $gateways = [];
70
15a4e4c8
MG
71 [
72 'amount' => $amount,
73 'currency' => $currency,
74 'accountid' => $accountid,
7d10f352 75 ] = self::get_cost($component, $paymentarea, $componentid);
895f38cc
MG
76 $account = new account($accountid);
77 if (!$account->get('id') || !$account->get('enabled')) {
78 return $gateways;
79 }
80
81 foreach ($account->get_gateways() as $plugin => $gateway) {
82 if (!$gateway->get('enabled')) {
83 continue;
84 }
85 /** @var gateway $classname */
6b3d163a 86 $classname = '\paygw_' . $plugin . '\gateway';
f3d75264 87
15a4e4c8 88 $currencies = component_class_callback($classname, 'get_supported_currencies', [], []);
f3d75264
SR
89 if (in_array($currency, $currencies)) {
90 $gateways[] = $plugin;
91 }
92 }
93
94 return $gateways;
95 }
c2321a26 96
15a4e4c8 97 /**
90fbc58d 98 * Rounds the cost based on the currency fractional digits, can also apply surcharge
15a4e4c8
MG
99 *
100 * @param float $amount amount in the currency units
15a4e4c8 101 * @param string $currency currency, used for calculating the number of fractional digits
90fbc58d 102 * @param float $surcharge surcharge in percents
15a4e4c8
MG
103 * @return float
104 */
90fbc58d
MG
105 public static function get_rounded_cost(float $amount, string $currency, float $surcharge = 0): float {
106 $amount = $amount * (100 + $surcharge) / 100;
107
108 $locale = get_string('localecldr', 'langconfig');
109 $fmt = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY);
110 $localisedcost = numfmt_format_currency($fmt, $amount, $currency);
111
112 return numfmt_parse_currency($fmt, $localisedcost, $currency);
15a4e4c8
MG
113 }
114
115 /**
90fbc58d 116 * Returns human-readable amount with correct number of fractional digits and currency indicator, can also apply surcharge
15a4e4c8 117 *
90fbc58d
MG
118 * @param float $amount amount in the currency units
119 * @param string $currency The currency
120 * @param float $surcharge surcharge in percents
15a4e4c8 121 * @return string
15a4e4c8 122 */
90fbc58d
MG
123 public static function get_cost_as_string(float $amount, string $currency, float $surcharge = 0): string {
124 $amount = $amount * (100 + $surcharge) / 100;
125
126 $locale = get_string('localecldr', 'langconfig');
127 $fmt = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY);
128 $localisedcost = numfmt_format_currency($fmt, $amount, $currency);
15a4e4c8
MG
129
130 return $localisedcost;
131 }
132
1882bb47
SR
133 /**
134 * Returns the percentage of surcharge that is applied when using a gateway
135 *
136 * @param string $gateway Name of the gateway
15a4e4c8 137 * @return float
1882bb47 138 */
15a4e4c8 139 public static function get_gateway_surcharge(string $gateway): float {
6b3d163a 140 return (float)get_config('paygw_' . $gateway, 'surcharge');
1882bb47
SR
141 }
142
c2321a26
SR
143 /**
144 * Returns the attributes to place on a pay button.
145 *
ab6ca275 146 * @param string $component Name of the component that the componentid belongs to
7d10f352 147 * @param string $paymentarea
ab6ca275
SR
148 * @param int $componentid An internal identifier that is used by the component
149 * @param string $description Description of the payment
c2321a26
SR
150 * @return array
151 */
7d10f352 152 public static function gateways_modal_link_params(string $component, string $paymentarea, int $componentid, string $description): array {
7e112616
SR
153 [
154 'amount' => $amount,
155 'currency' => $currency
7d10f352 156 ] = self::get_cost($component, $paymentarea, $componentid);
7e112616 157
c2321a26
SR
158 return [
159 'id' => 'gateways-modal-trigger',
160 'role' => 'button',
ed04c382 161 'data-component' => $component,
7d10f352 162 'data-paymentarea' => $paymentarea,
ed04c382 163 'data-componentid' => $componentid,
7e112616 164 'data-cost' => self::get_cost_as_string($amount, $currency),
ab6ca275 165 'data-description' => $description,
c2321a26
SR
166 ];
167 }
5337ca48
SR
168
169 /**
170 * Asks the cost from the related component.
171 *
172 * @param string $component Name of the component that the componentid belongs to
7d10f352 173 * @param string $paymentarea
5337ca48 174 * @param int $componentid An internal identifier that is used by the component
895f38cc 175 * @return array['amount' => float, 'currency' => string, 'accountid' => int]
5337ca48
SR
176 * @throws \moodle_exception
177 */
7d10f352
SR
178 public static function get_cost(string $component, string $paymentarea, int $componentid): array {
179 $cost = component_class_callback("$component\\payment\\provider", 'get_cost', [$paymentarea, $componentid]);
5337ca48 180
895f38cc
MG
181 if ($cost === null || !is_array($cost) || !array_key_exists('amount', $cost)
182 || !array_key_exists('currency', $cost) || !array_key_exists('accountid', $cost) ) {
5337ca48
SR
183 throw new \moodle_exception('callbacknotimplemented', 'core_payment', '', $component);
184 }
185
186 return $cost;
187 }
188
895f38cc
MG
189 /**
190 * Returns the gateway configuration for given component and gateway
191 *
192 * @param string $component
7d10f352 193 * @param string $paymentarea
895f38cc
MG
194 * @param int $componentid
195 * @param string $gatewayname
196 * @return array
197 * @throws \moodle_exception
198 */
7d10f352
SR
199 public static function get_gateway_configuration(string $component, string $paymentarea, int $componentid,
200 string $gatewayname): array {
201 $x = self::get_cost($component, $paymentarea, $componentid);
895f38cc
MG
202 $gateway = null;
203 $account = new account($x['accountid']);
204 if ($account && $account->get('enabled')) {
205 $gateway = $account->get_gateways()[$gatewayname] ?? null;
206 }
207 if (!$gateway) {
208 throw new \moodle_exception('gatewaynotfound', 'payment');
209 }
210 return $gateway->get_configuration();
211 }
212
5337ca48
SR
213 /**
214 * Delivers what the user paid for.
215 *
15a4e4c8
MG
216 * @uses \core_payment\local\callback\provider::deliver_order()
217 *
5337ca48 218 * @param string $component Name of the component that the componentid belongs to
7d10f352 219 * @param string $paymentarea
5337ca48 220 * @param int $componentid An internal identifier that is used by the component
15a4e4c8 221 * @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference
5337ca48 222 * @return bool Whether successful or not
5337ca48 223 */
7d10f352
SR
224 public static function deliver_order(string $component, string $paymentarea, int $componentid, int $paymentid): bool {
225 $result = component_class_callback("$component\\payment\\provider", 'deliver_order',
226 [$paymentarea, $componentid, $paymentid]);
5337ca48
SR
227
228 if ($result === null) {
229 throw new \moodle_exception('callbacknotimplemented', 'core_payment', '', $component);
230 }
231
232 return $result;
233 }
3c3b43a5
SR
234
235 /**
236 * Stores essential information about the payment and returns the "id" field of the payment record in DB.
237 * Each payment gateway may then store the additional information their way.
238 *
15a4e4c8 239 * @param int $accountid Account id
3c3b43a5 240 * @param string $component Name of the component that the componentid belongs to
7d10f352 241 * @param string $paymentarea
3c3b43a5
SR
242 * @param int $componentid An internal identifier that is used by the component
243 * @param int $userid Id of the user who is paying
244 * @param float $amount Amount of payment
245 * @param string $currency Currency of payment
246 * @param string $gateway The gateway that is used for the payment
247 * @return int
248 */
7d10f352
SR
249 public static function save_payment(int $accountid, string $component, string $paymentarea, int $componentid, int $userid,
250 float $amount, string $currency, string $gateway): int {
3c3b43a5
SR
251 global $DB;
252
253 $record = new \stdClass();
254 $record->component = $component;
7d10f352 255 $record->paymentarea = $paymentarea;
3c3b43a5
SR
256 $record->componentid = $componentid;
257 $record->userid = $userid;
258 $record->amount = $amount;
259 $record->currency = $currency;
260 $record->gateway = $gateway;
15a4e4c8 261 $record->accountid = $accountid;
3c3b43a5
SR
262 $record->timecreated = $record->timemodified = time();
263
264 $id = $DB->insert_record('payments', $record);
265
266 return $id;
267 }
1882bb47
SR
268
269 /**
270 * This functions adds the settings that are common for all payment gateways.
271 *
272 * @param \admin_settingpage $settings The settings object
6b3d163a 273 * @param string $gateway The gateway name prefixed with paygw_
1882bb47
SR
274 */
275 public static function add_common_gateway_settings(\admin_settingpage $settings, string $gateway): void {
276 $settings->add(new \admin_setting_configtext($gateway . '/surcharge', get_string('surcharge', 'core_payment'),
277 get_string('surcharge_desc', 'core_payment'), 0, PARAM_INT));
278
279 }
895f38cc
MG
280
281 /**
282 * Save a new or edited payment account (used in management interface)
283 *
284 * @param \stdClass $data
9476b489 285 * @return account
895f38cc 286 */
9476b489 287 public static function save_payment_account(\stdClass $data): account {
895f38cc
MG
288
289 if (empty($data->id)) {
290 $account = new account(0, $data);
9476b489
MG
291 $account->save();
292 account_created::create_from_account($account)->trigger();
895f38cc
MG
293 } else {
294 $account = new account($data->id);
295 $account->from_record($data);
9476b489
MG
296 $account->save();
297 account_updated::create_from_account($account)->trigger();
895f38cc
MG
298 }
299
9476b489 300 return $account;
895f38cc
MG
301 }
302
303 /**
304 * Delete a payment account (used in management interface)
305 *
306 * @param account $account
307 */
9476b489
MG
308 public static function delete_payment_account(account $account): void {
309 global $DB;
310 if ($DB->record_exists('payments', ['accountid' => $account->get('id')])) {
311 $account->set('archived', 1);
312 $account->save();
313 account_updated::create_from_account($account, ['archived' => 1])->trigger();
314 return;
315 }
316
895f38cc
MG
317 foreach ($account->get_gateways(false) as $gateway) {
318 if ($gateway->get('id')) {
319 $gateway->delete();
320 }
321 }
9476b489 322 $event = account_deleted::create_from_account($account);
895f38cc 323 $account->delete();
9476b489
MG
324 $event->trigger();
325 }
326
327 /**
328 * Restore archived payment account (used in management interface)
329 *
330 * @param account $account
331 */
332 public static function restore_payment_account(account $account): void {
333 $account->set('archived', 0);
334 $account->save();
335 account_updated::create_from_account($account, ['restored' => 1])->trigger();
895f38cc
MG
336 }
337
338 /**
339 * Save a payment gateway linked to an existing account (used in management interface)
340 *
341 * @param \stdClass $data
9476b489 342 * @return account_gateway
895f38cc 343 */
9476b489 344 public static function save_payment_gateway(\stdClass $data): account_gateway {
895f38cc 345 if (empty($data->id)) {
9476b489
MG
346 $records = account_gateway::get_records(['accountid' => $data->accountid, 'gateway' => $data->gateway]);
347 if ($records) {
348 $gateway = reset($records);
349 } else {
350 $gateway = new account_gateway(0, $data);
351 }
895f38cc
MG
352 } else {
353 $gateway = new account_gateway($data->id);
895f38cc 354 }
9476b489
MG
355 unset($data->accountid, $data->gateway, $data->id);
356 $gateway->from_record($data);
895f38cc 357
9476b489 358 $account = $gateway->get_account();
895f38cc 359 $gateway->save();
9476b489
MG
360 account_updated::create_from_account($account)->trigger();
361 return $gateway;
895f38cc
MG
362 }
363
364 /**
365 * Returns the list of payment accounts in the given context (used in management interface)
366 *
367 * @param \context $context
368 * @return account[]
369 */
9476b489
MG
370 public static function get_payment_accounts_to_manage(\context $context, bool $showarchived = false): array {
371 $records = account::get_records(['contextid' => $context->id] + ($showarchived ? [] : ['archived' => 0]));
372 \core_collator::asort_objects_by_method($records, 'get_formatted_name');
373 return $records;
895f38cc
MG
374 }
375
376 /**
377 * Get list of accounts available in the given context
378 *
379 * @param \context $context
380 * @return array
381 */
382 public static function get_payment_accounts_menu(\context $context): array {
383 global $DB;
384 [$sql, $params] = $DB->get_in_or_equal($context->get_parent_context_ids(true));
385 $accounts = array_filter(account::get_records_select('contextid '.$sql, $params), function($account) {
9476b489 386 return $account->is_available() && !$account->get('archived');
895f38cc
MG
387 });
388 return array_map(function($account) {
389 return $account->get_formatted_name();
390 }, $accounts);
391 }
4865d2a0 392}