PageRenderTime 52ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/payment/classes/helper.php

https://github.com/alanbarrett/moodle
PHP | 399 lines | 188 code | 49 blank | 162 comment | 18 complexity | 727ae0d52608384a82120540400004c4 MD5 | raw file
  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. * Contains helper class for the payment subsystem.
  18. *
  19. * @package core_payment
  20. * @copyright 2019 Shamim Rezaie <shamim@moodle.com>
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. namespace core_payment;
  24. use core_payment\event\account_created;
  25. use core_payment\event\account_deleted;
  26. use core_payment\event\account_updated;
  27. /**
  28. * Helper class for the payment subsystem.
  29. *
  30. * @copyright 2019 Shamim Rezaie <shamim@moodle.com>
  31. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32. */
  33. class helper {
  34. /**
  35. * Returns an accumulated list of supported currencies by all payment gateways.
  36. *
  37. * @return string[] An array of the currency codes in the three-character ISO-4217 format
  38. */
  39. public static function get_supported_currencies(): array {
  40. $currencies = [];
  41. $plugins = \core_plugin_manager::instance()->get_enabled_plugins('paygw');
  42. foreach ($plugins as $plugin) {
  43. /** @var \paygw_paypal\gateway $classname */
  44. $classname = '\paygw_' . $plugin . '\gateway';
  45. $currencies = array_merge($currencies, component_class_callback($classname, 'get_supported_currencies', [], []));
  46. }
  47. $currencies = array_unique($currencies);
  48. return $currencies;
  49. }
  50. /**
  51. * Returns the list of gateways that can process payments in the given currency.
  52. *
  53. * @param string $component
  54. * @param string $paymentarea
  55. * @param int $itemid
  56. * @return string[]
  57. */
  58. public static function get_available_gateways(string $component, string $paymentarea, int $itemid): array {
  59. $gateways = [];
  60. $payable = static::get_payable($component, $paymentarea, $itemid);
  61. $account = new account($payable->get_account_id());
  62. if (!$account->get('id') || !$account->get('enabled')) {
  63. return $gateways;
  64. }
  65. $currency = $payable->get_currency();
  66. foreach ($account->get_gateways() as $plugin => $gateway) {
  67. if (!$gateway->get('enabled')) {
  68. continue;
  69. }
  70. /** @var gateway $classname */
  71. $classname = '\paygw_' . $plugin . '\gateway';
  72. $currencies = component_class_callback($classname, 'get_supported_currencies', [], []);
  73. if (in_array($currency, $currencies)) {
  74. $gateways[] = $plugin;
  75. }
  76. }
  77. return $gateways;
  78. }
  79. /**
  80. * Rounds the cost based on the currency fractional digits, can also apply surcharge
  81. *
  82. * @param float $amount amount in the currency units
  83. * @param string $currency currency, used for calculating the number of fractional digits
  84. * @param float $surcharge surcharge in percents
  85. * @return float
  86. */
  87. public static function get_rounded_cost(float $amount, string $currency, float $surcharge = 0): float {
  88. $amount = $amount * (100 + $surcharge) / 100;
  89. $locale = get_string('localecldr', 'langconfig');
  90. $fmt = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY);
  91. $localisedcost = numfmt_format_currency($fmt, $amount, $currency);
  92. return numfmt_parse_currency($fmt, $localisedcost, $currency);
  93. }
  94. /**
  95. * Returns human-readable amount with correct number of fractional digits and currency indicator, can also apply surcharge
  96. *
  97. * @param float $amount amount in the currency units
  98. * @param string $currency The currency
  99. * @param float $surcharge surcharge in percents
  100. * @return string
  101. */
  102. public static function get_cost_as_string(float $amount, string $currency, float $surcharge = 0): string {
  103. $amount = $amount * (100 + $surcharge) / 100;
  104. $locale = get_string('localecldr', 'langconfig');
  105. $fmt = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY);
  106. $localisedcost = numfmt_format_currency($fmt, $amount, $currency);
  107. return $localisedcost;
  108. }
  109. /**
  110. * Returns the percentage of surcharge that is applied when using a gateway
  111. *
  112. * @param string $gateway Name of the gateway
  113. * @return float
  114. */
  115. public static function get_gateway_surcharge(string $gateway): float {
  116. return (float)get_config('paygw_' . $gateway, 'surcharge');
  117. }
  118. /**
  119. * Returns the attributes to place on a pay button.
  120. *
  121. * @param string $component Name of the component that the itemid belongs to
  122. * @param string $paymentarea
  123. * @param int $itemid An internal identifier that is used by the component
  124. * @param string $description Description of the payment
  125. * @return array
  126. */
  127. public static function gateways_modal_link_params(string $component, string $paymentarea, int $itemid,
  128. string $description): array {
  129. $payable = static::get_payable($component, $paymentarea, $itemid);
  130. return [
  131. 'id' => 'gateways-modal-trigger',
  132. 'role' => 'button',
  133. 'data-action' => 'core_payment/triggerPayment',
  134. 'data-component' => $component,
  135. 'data-paymentarea' => $paymentarea,
  136. 'data-itemid' => $itemid,
  137. 'data-cost' => static::get_cost_as_string($payable->get_amount(), $payable->get_currency()),
  138. 'data-description' => $description,
  139. ];
  140. }
  141. /**
  142. * @param string $component
  143. * @return string
  144. * @throws \coding_exception
  145. */
  146. private static function get_service_provider_classname(string $component) {
  147. $providerclass = "$component\\payment\\service_provider";
  148. if (class_exists($providerclass)) {
  149. $rc = new \ReflectionClass($providerclass);
  150. if ($rc->implementsInterface(local\callback\service_provider::class)) {
  151. return $providerclass;
  152. }
  153. }
  154. throw new \coding_exception("$component does not have an eligible implementation of payment service_provider.");
  155. }
  156. /**
  157. * Asks the payable from the related component.
  158. *
  159. * @param string $component Name of the component that the itemid belongs to
  160. * @param string $paymentarea
  161. * @param int $itemid An internal identifier that is used by the component
  162. * @return local\entities\payable
  163. */
  164. public static function get_payable(string $component, string $paymentarea, int $itemid): local\entities\payable {
  165. $providerclass = static::get_service_provider_classname($component);
  166. return component_class_callback($providerclass, 'get_payable', [$paymentarea, $itemid]);
  167. }
  168. /**
  169. * Returns the gateway configuration for given component and gateway
  170. *
  171. * @param string $component
  172. * @param string $paymentarea
  173. * @param int $itemid
  174. * @param string $gatewayname
  175. * @return array
  176. * @throws \moodle_exception
  177. */
  178. public static function get_gateway_configuration(string $component, string $paymentarea, int $itemid,
  179. string $gatewayname): array {
  180. $payable = self::get_payable($component, $paymentarea, $itemid);
  181. $gateway = null;
  182. $account = new account($payable->get_account_id());
  183. if ($account && $account->get('enabled')) {
  184. $gateway = $account->get_gateways()[$gatewayname] ?? null;
  185. }
  186. if (!$gateway) {
  187. throw new \moodle_exception('gatewaynotfound', 'payment');
  188. }
  189. return $gateway->get_configuration();
  190. }
  191. /**
  192. * Delivers what the user paid for.
  193. *
  194. * @uses \core_payment\local\callback\service_provider::deliver_order()
  195. *
  196. * @param string $component Name of the component that the itemid belongs to
  197. * @param string $paymentarea
  198. * @param int $itemid An internal identifier that is used by the component
  199. * @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference
  200. * @param int $userid The userid the order is going to deliver to
  201. * @return bool Whether successful or not
  202. */
  203. public static function deliver_order(string $component, string $paymentarea, int $itemid, int $paymentid, int $userid): bool {
  204. $providerclass = static::get_service_provider_classname($component);
  205. $result = component_class_callback($providerclass, 'deliver_order', [$paymentarea, $itemid, $paymentid, $userid]);
  206. return $result;
  207. }
  208. /**
  209. * Stores essential information about the payment and returns the "id" field of the payment record in DB.
  210. * Each payment gateway may then store the additional information their way.
  211. *
  212. * @param int $accountid Account id
  213. * @param string $component Name of the component that the itemid belongs to
  214. * @param string $paymentarea
  215. * @param int $itemid An internal identifier that is used by the component
  216. * @param int $userid Id of the user who is paying
  217. * @param float $amount Amount of payment
  218. * @param string $currency Currency of payment
  219. * @param string $gateway The gateway that is used for the payment
  220. * @return int
  221. */
  222. public static function save_payment(int $accountid, string $component, string $paymentarea, int $itemid, int $userid,
  223. float $amount, string $currency, string $gateway): int {
  224. global $DB;
  225. $record = new \stdClass();
  226. $record->component = $component;
  227. $record->paymentarea = $paymentarea;
  228. $record->itemid = $itemid;
  229. $record->userid = $userid;
  230. $record->amount = $amount;
  231. $record->currency = $currency;
  232. $record->gateway = $gateway;
  233. $record->accountid = $accountid;
  234. $record->timecreated = $record->timemodified = time();
  235. $id = $DB->insert_record('payments', $record);
  236. return $id;
  237. }
  238. /**
  239. * This functions adds the settings that are common for all payment gateways.
  240. *
  241. * @param \admin_settingpage $settings The settings object
  242. * @param string $gateway The gateway name prefixed with paygw_
  243. */
  244. public static function add_common_gateway_settings(\admin_settingpage $settings, string $gateway): void {
  245. $settings->add(new \admin_setting_configtext($gateway . '/surcharge', get_string('surcharge', 'core_payment'),
  246. get_string('surcharge_desc', 'core_payment'), 0, PARAM_INT));
  247. }
  248. /**
  249. * Save a new or edited payment account (used in management interface)
  250. *
  251. * @param \stdClass $data
  252. * @return account
  253. */
  254. public static function save_payment_account(\stdClass $data): account {
  255. if (empty($data->id)) {
  256. $account = new account(0, $data);
  257. $account->save();
  258. account_created::create_from_account($account)->trigger();
  259. } else {
  260. $account = new account($data->id);
  261. $account->from_record($data);
  262. $account->save();
  263. account_updated::create_from_account($account)->trigger();
  264. }
  265. return $account;
  266. }
  267. /**
  268. * Delete a payment account (used in management interface)
  269. *
  270. * @param account $account
  271. */
  272. public static function delete_payment_account(account $account): void {
  273. global $DB;
  274. if ($DB->record_exists('payments', ['accountid' => $account->get('id')])) {
  275. $account->set('archived', 1);
  276. $account->save();
  277. account_updated::create_from_account($account, ['archived' => 1])->trigger();
  278. return;
  279. }
  280. foreach ($account->get_gateways(false) as $gateway) {
  281. if ($gateway->get('id')) {
  282. $gateway->delete();
  283. }
  284. }
  285. $event = account_deleted::create_from_account($account);
  286. $account->delete();
  287. $event->trigger();
  288. }
  289. /**
  290. * Restore archived payment account (used in management interface)
  291. *
  292. * @param account $account
  293. */
  294. public static function restore_payment_account(account $account): void {
  295. $account->set('archived', 0);
  296. $account->save();
  297. account_updated::create_from_account($account, ['restored' => 1])->trigger();
  298. }
  299. /**
  300. * Save a payment gateway linked to an existing account (used in management interface)
  301. *
  302. * @param \stdClass $data
  303. * @return account_gateway
  304. */
  305. public static function save_payment_gateway(\stdClass $data): account_gateway {
  306. if (empty($data->id)) {
  307. $records = account_gateway::get_records(['accountid' => $data->accountid, 'gateway' => $data->gateway]);
  308. if ($records) {
  309. $gateway = reset($records);
  310. } else {
  311. $gateway = new account_gateway(0, $data);
  312. }
  313. } else {
  314. $gateway = new account_gateway($data->id);
  315. }
  316. unset($data->accountid, $data->gateway, $data->id);
  317. $gateway->from_record($data);
  318. $account = $gateway->get_account();
  319. $gateway->save();
  320. account_updated::create_from_account($account)->trigger();
  321. return $gateway;
  322. }
  323. /**
  324. * Returns the list of payment accounts in the given context (used in management interface)
  325. *
  326. * @param \context $context
  327. * @return account[]
  328. */
  329. public static function get_payment_accounts_to_manage(\context $context, bool $showarchived = false): array {
  330. $records = account::get_records(['contextid' => $context->id] + ($showarchived ? [] : ['archived' => 0]));
  331. \core_collator::asort_objects_by_method($records, 'get_formatted_name');
  332. return $records;
  333. }
  334. /**
  335. * Get list of accounts available in the given context
  336. *
  337. * @param \context $context
  338. * @return array
  339. */
  340. public static function get_payment_accounts_menu(\context $context): array {
  341. global $DB;
  342. [$sql, $params] = $DB->get_in_or_equal($context->get_parent_context_ids(true));
  343. $accounts = array_filter(account::get_records_select('contextid '.$sql, $params), function($account) {
  344. return $account->is_available() && !$account->get('archived');
  345. });
  346. return array_map(function($account) {
  347. return $account->get_formatted_name();
  348. }, $accounts);
  349. }
  350. }