PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / payment-gateways.php

payment-gateways.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/payment-gateways.php

743 lines 22.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Payment Gateways
4 *
5 * @package StoreEngine/PaymentGateways
6 */
7
8 namespace StoreEngine;
9
10 use StoreEngine;
11 use StoreEngine\Addons\Subscription\Classes\Subscription;
12 use StoreEngine\Classes\Logger;
13 use StoreEngine\Classes\Order;
14 use StoreEngine\Traits\Singleton;
15 use StoreEngine\Payment\Gateways\PaymentGateway;
16 use StoreEngine\Payment\Gateways\GatewayCod;
17 use StoreEngine\Payment\Gateways\GatewayBacs;
18 use StoreEngine\Payment\Gateways\GatewayCheck;
19 use StoreEngine\Utils\Formatting;
20 use StoreEngine\Utils\Helper;
21 use StoreEngine\Utils\PaymentUtil;
22
23 if ( ! defined( 'ABSPATH' ) ) {
24 exit;
25 }
26
27 /**
28 * Registry of available payment gateways.
29 */
30 final class Payment_Gateways {
31 use Singleton;
32
33 /**
34 * Option storing the enabled state + sort index of every gateway, so the
35 * loader can know what is enabled without instantiating a single gateway.
36 *
37 * Shape: [ id => [ 'enabled' => bool, 'index' => int ] ]
38 */
39 const MANIFEST_OPTION = 'storeengine_payment_gateways_state';
40
41 /**
42 * Materialized gateway instances, keyed by gateway id. Populated lazily.
43 *
44 * @var PaymentGateway[]
45 */
46 protected array $gateways = [];
47
48 /**
49 * Registry of gateway id => class-string|callable factory. Built on `init`
50 * without instantiating anything.
51 *
52 * @var array<string, string|callable>
53 */
54 protected array $gateway_classes = [];
55
56 protected ?array $manifest = null;
57
58 protected bool $loaded = false;
59
60 protected bool $all_loaded = false;
61
62 protected bool $enabled_loaded = false;
63
64 protected function __construct() {
65 add_action( 'init', [ $this, 'load_gateways' ] );
66
67 add_filter( 'storeengine/api/settings', [ $this, 'add_to_settings_api' ] );
68 add_filter( 'storeengine/payment_settings_fields', [ $this, 'add_to_payment_settings_fields' ] );
69
70 add_action( 'storeengine/payment_gateways_initialized', [ __CLASS__, 'on_init_gateways' ] );
71 }
72
73 public function load_gateways() {
74 if ( $this->loaded ) {
75 return;
76 }
77
78 $this->loaded = true;
79
80 /*
81 * Legacy registration: bare class-name array. Core offline gateways and
82 * the in-core addons (stripe, paypal) register this way. We can't learn a
83 * gateway's id without constructing it, so these are built eagerly — the
84 * set is small and bounded.
85 */
86 $legacy = [
87 GatewayCod::class,
88 GatewayBacs::class,
89 GatewayCheck::class,
90 ];
91
92 $legacy = apply_filters( 'storeengine/payment_gateways', $legacy );
93
94 foreach ( $legacy as $class ) {
95 if ( ! is_string( $class ) || ! class_exists( $class ) ) {
96 continue;
97 }
98
99 $gateway = new $class();
100 if ( ! is_a( $gateway, PaymentGateway::class ) ) {
101 continue;
102 }
103
104 $this->gateway_classes[ $gateway->id ] = $class;
105 $this->gateways[ $gateway->id ] = $gateway;
106 $this->boot_gateway( $gateway );
107 }
108
109 /*
110 * Lazy registration: id => class-string|callable factory. Gateways
111 * registered here are NOT instantiated until something actually needs
112 * them (an enabled gateway on checkout, the admin settings screen, or a
113 * by-id lookup such as a refund). A callable factory lets a plugin hand
114 * back a configured shared base class without a subclass per provider.
115 *
116 * @example
117 * ```php
118 * add_filter( 'storeengine/payment_gateway_classes', function ( array $gateways ) {
119 * $gateways['mollie'] = fn() => new AbstractRedirectGateway( $mollie_config );
120 * $gateways['foo'] = Foo\GatewayFoo::class;
121 * return $gateways;
122 * } );
123 * ```
124 *
125 * @param array<string, string|callable> $registry
126 */
127 $registry = apply_filters( 'storeengine/payment_gateway_classes', [] );
128
129 if ( is_array( $registry ) ) {
130 foreach ( $registry as $id => $def ) {
131 if ( ! is_string( $id ) || '' === $id || isset( $this->gateway_classes[ $id ] ) ) {
132 continue;
133 }
134
135 $this->gateway_classes[ $id ] = $def;
136 }
137 }
138
139 /*
140 * Register the settings verify/update actions for every registered id
141 * up-front, but resolve the instance lazily inside the closure. This keeps
142 * saving a gateway's settings working without instantiating all of them.
143 */
144 foreach ( array_keys( $this->gateway_classes ) as $id ) {
145 add_action( "storeengine/api/settings/payment-gateways/verify/$id", function ( $payload ) use ( $id ) {
146 $gateway = $this->get_gateway( $id );
147 if ( $gateway && $gateway->need_config_verification() && method_exists( $gateway, 'verify_config' ) ) {
148 $gateway->verify_config( $payload );
149 }
150 } );
151
152 add_action( "storeengine/api/settings/payment-gateways/update/$id", function ( $payload ) use ( $id ) {
153 $gateway = $this->get_gateway( $id );
154 if ( ! $gateway ) {
155 return;
156 }
157 $this->payment_gateway_settings_option_changed( $gateway, $payload, $gateway->get_settings() );
158 $gateway->handle_save_request( $payload );
159 $this->sync_manifest( $gateway );
160 } );
161 }
162
163 /**
164 * Hook that is called when the payment gateways have been registered.
165 *
166 * Note: with lazy loading this fires after the registry is built, not
167 * after every gateway is instantiated.
168 *
169 * @param Payment_Gateways $payment_gateways The payment gateways instance.
170 */
171 do_action( 'storeengine/payment_gateways_initialized', $this );
172
173 // Materialize ENABLED gateways now (on `init`) so their `storeengine/gateway/{id}/init`
174 // hooks fire early enough to register frontend assets / checkout glue. Disabled
175 // gateways stay lazy — they're only built on demand (admin settings, by-id lookups).
176 $this->ensure_enabled();
177 }
178
179 /**
180 * Instantiate (once) and return a single gateway by id, or null if unknown.
181 *
182 * Works for disabled gateways too, so by-id lookups (refunds, renewals,
183 * webhooks) keep resolving even when a gateway is turned off.
184 */
185 protected function materialize_gateway( string $id ): ?PaymentGateway {
186 $this->load_gateways();
187
188 if ( isset( $this->gateways[ $id ] ) ) {
189 return $this->gateways[ $id ];
190 }
191
192 if ( ! isset( $this->gateway_classes[ $id ] ) ) {
193 return null;
194 }
195
196 $def = $this->gateway_classes[ $id ];
197 $gateway = is_string( $def ) ? new $def() : $def();
198
199 if ( ! is_a( $gateway, PaymentGateway::class ) ) {
200 return null;
201 }
202
203 $this->gateways[ $gateway->id ] = $gateway;
204 $this->boot_gateway( $gateway );
205
206 return $gateway;
207 }
208
209 /**
210 * Fire the gateway-specific init hook (wires up Hooks/Assets) for an enabled
211 * gateway, preserving the contract from the eager loader.
212 *
213 * @example
214 * ```php
215 * add_action( 'storeengine/gateway/stripe/init', function( GatewayStripe $gateway ) {} );
216 * ```
217 */
218 protected function boot_gateway( PaymentGateway $gateway ): void {
219 if ( has_action( "storeengine/gateway/{$gateway->id}/init" ) && $gateway->is_enabled() ) {
220 do_action_ref_array( "storeengine/gateway/{$gateway->id}/init", [ &$gateway ] );
221 }
222 }
223
224 /**
225 * Enabled state + sort index of every registered gateway, without
226 * instantiating any of them. Reads a single autoloaded option; if missing it
227 * is back-filled once from the per-gateway settings options (cheap reads, no
228 * instantiation) and cached.
229 */
230 protected function get_manifest(): array {
231 if ( null !== $this->manifest ) {
232 return $this->manifest;
233 }
234
235 $manifest = get_option( self::MANIFEST_OPTION, null );
236
237 // Use the cached manifest only if it already covers every registered id;
238 // otherwise (newly installed gateway, migration) rebuild it.
239 if ( is_array( $manifest ) && ! array_diff_key( $this->gateway_classes, $manifest ) ) {
240 $this->manifest = $manifest;
241
242 return $this->manifest;
243 }
244
245 // Back-fill from per-gateway option keys (no gateway is constructed).
246 $manifest = is_array( $manifest ) ? $manifest : [];
247 foreach ( array_keys( $this->gateway_classes ) as $id ) {
248 $settings = get_option( 'storeengine_payment_' . $id . '_settings', null );
249 $enabled = is_array( $settings ) && in_array( $settings['is_enabled'] ?? false, [ true, 'true', 'yes', 'on', '1', 1 ], true );
250
251 $manifest[ $id ] = [
252 'enabled' => $enabled,
253 'index' => is_array( $settings ) ? (int) ( $settings['index'] ?? 0 ) : 0,
254 ];
255 }
256
257 update_option( self::MANIFEST_OPTION, $manifest, true );
258
259 $this->manifest = $manifest;
260
261 return $this->manifest;
262 }
263
264 /**
265 * Update a single gateway's enabled/index entry in the manifest after its
266 * settings are saved, so the next request's frontend hot path sees the change
267 * without instantiating every gateway.
268 */
269 protected function sync_manifest( PaymentGateway $gateway ): void {
270 $manifest = $this->get_manifest();
271
272 $manifest[ $gateway->id ] = [
273 'enabled' => $gateway->is_enabled(),
274 'index' => $gateway->get_index(),
275 ];
276
277 update_option( self::MANIFEST_OPTION, $manifest, true );
278
279 $this->manifest = $manifest;
280 }
281
282 /**
283 * Instantiate every registered gateway. Used by admin / settings contexts
284 * that must render the full list. The cost is paid only when those run.
285 */
286 protected function ensure_all(): void {
287 $this->load_gateways();
288
289 if ( $this->all_loaded ) {
290 return;
291 }
292
293 foreach ( array_keys( $this->gateway_classes ) as $id ) {
294 $this->materialize_gateway( $id );
295 }
296
297 $this->all_loaded = true;
298 $this->sort_gateways();
299 }
300
301 /**
302 * Instantiate only the gateways flagged enabled in the manifest. Used by the
303 * frontend/checkout hot path — typically 2–4 gateways instead of 20+.
304 */
305 protected function ensure_enabled(): void {
306 $this->load_gateways();
307
308 if ( $this->all_loaded || $this->enabled_loaded ) {
309 return;
310 }
311
312 foreach ( $this->get_manifest() as $id => $state ) {
313 if ( ! empty( $state['enabled'] ) ) {
314 $this->materialize_gateway( $id );
315 }
316 }
317
318 $this->enabled_loaded = true;
319 $this->sort_gateways();
320 }
321
322 protected function sort_gateways(): void {
323 uasort( $this->gateways, fn( $a, $b ) => $a->get_index() <=> $b->get_index() );
324 }
325
326 public static function on_init_gateways() {
327 add_filter( 'storeengine/frontend_scripts_data', [ __CLASS__, 'gateway_javascript_params' ] );
328 }
329
330 public static function gateway_javascript_params( array $data ): array {
331 $cart = StoreEngine::init()->get_cart();
332 $is_pay_for_order = PaymentUtil::is_valid_order_pay_page();
333 $is_change_payment_method = PaymentUtil::is_changing_payment_method_for_subscription();
334
335 $order = null;
336 if ( $is_pay_for_order ) {
337 $order = Helper::get_order( absint( get_query_var( 'order_id' ) ) );
338 }
339
340 if ( $is_pay_for_order && ! is_wp_error( $order ) && $order ) {
341 $data['payment_data'] = [
342 'currency' => $order->get_currency() ? $order->get_currency() : Formatting::get_currency(),
343 'cart_total' => (float) $order->get_total( 'payment' ),
344 'has_subscription' => PaymentUtil::has_subscription( $order ),
345 'has_trial' => PaymentUtil::has_subscription_trial( $order ),
346 'customerBillingData' => null,
347 ];
348 $data['is_page']['order_id'] = (int) $order->get_id();
349 } else {
350 $data['payment_data'] = [
351 'currency' => Formatting::get_currency(),
352 'cart_total' => $cart ? (float) $cart->get_total( 'payment' ) : 0,
353 'has_subscription' => $cart && Helper::get_addon_active_status( 'subscription' ) && $cart->get_meta( 'has_subscription' ),
354 'has_trial' => $cart && $cart->get_meta( 'has_trial' ),
355 'customerBillingData' => null,
356 ];
357 }
358
359 if ( Helper::is_endpoint( 'add-payment-method' ) || $is_pay_for_order || $is_change_payment_method ) {
360 $customer = storeengine()->get_customer();
361
362 if ( $customer ) {
363 $data['payment_data']['customerBillingData'] = [
364 'name' => trim( $customer->get_billing_first_name() . ' ' . $customer->get_billing_last_name() ),
365 'email' => $customer->get_billing_email(),
366 'phone' => $customer->get_billing_phone(),
367 'address' => [
368 'country' => $customer->get_billing_country(),
369 'line1' => $customer->get_billing_address_1(),
370 'line2' => $customer->get_billing_address_2(),
371 'city' => $customer->get_billing_city(),
372 'state' => $customer->get_billing_state(),
373 'postal_code' => $customer->get_billing_postcode(),
374 ],
375 ];
376 }
377 }
378
379 return $data;
380 }
381
382
383 public function get_enabled_gateways(): array {
384 $this->ensure_enabled();
385
386 return array_filter( $this->gateways, fn( $gateway ) => $gateway->is_enabled );
387 }
388
389 public function get_available_gateways(): array {
390 $this->ensure_enabled();
391
392 return array_filter( $this->gateways, fn( $gateway ) => $gateway->is_available() );
393 }
394
395 /**
396 * @return PaymentGateway[]
397 */
398 public function get_gateways(): array {
399 $this->ensure_all();
400
401 return $this->gateways;
402 }
403
404 public function get_gateway( string $id ): ?PaymentGateway {
405 if ( ! $id ) {
406 return null;
407 }
408
409 return $this->materialize_gateway( $id );
410 }
411
412 public function add_gateways_data( array $data ): array {
413 $this->ensure_all();
414
415 return array_merge( $data, [
416 'payment_gateways' => array_map( fn( $gateway ) => [
417 'id' => $gateway->id,
418 'index' => $gateway->get_index(),
419 'label' => $gateway->get_method_title(),
420 'description' => $gateway->get_method_description(),
421 'fields' => $gateway->get_admin_fields_sorted(),
422 'settings' => $gateway->get_settings(),
423 'verify_config' => $gateway->need_config_verification(),
424 ], array_values( $this->gateways ) ),
425 ] );
426 }
427
428 public function add_to_settings_api( $settings ) {
429 $this->ensure_all();
430
431 $settings->payment_gateways = array_map( fn( $gateway ) => [
432 'id' => $gateway->id,
433 'index' => $gateway->get_index(),
434 'label' => $gateway->get_method_title(),
435 'description' => $gateway->get_method_description(),
436 'fields' => $gateway->get_admin_fields_sorted(),
437 'verify_config' => $gateway->need_config_verification(),
438 'icon' => $gateway->get_icon_url(),
439 'settings' => $gateway->get_settings(),
440 ], array_values( $this->gateways ) );
441
442 return $settings;
443 }
444
445 public function add_to_payment_settings_fields( array $fields ): array {
446 $this->ensure_all();
447
448 $type_mapping = [
449 'password' => 'text',
450 'checkbox' => 'boolean',
451 // Display-only field (read-only URL with a copy button); validate as a
452 // plain string — it is never submitted by the form.
453 'copy_text' => 'text',
454 ];
455
456 foreach ( $this->gateways as $gateway ) {
457 $fields[ $gateway->id ] = [
458 'is_enabled' => 'boolean',
459 'index' => 'int',
460 ];
461 foreach ( $gateway->get_admin_fields() as $field => $cfg ) {
462 $type = $cfg['type'] ?? 'text';
463 if ( 'repeater' === $type ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
464 // @TODO repeater ...
465 } else {
466 $fields[ $gateway->id ][ $field ] = $type_mapping[ $type ] ?? $type;
467 }
468 }
469 }
470
471 return $fields;
472 }
473
474 /**
475 * Callback for when a gateway settings option was added or updated.
476 *
477 * @param PaymentGateway $gateway The gateway for which the option was added or updated.
478 * @param array $payload New value.
479 * @param ?array $old_settings Option name.
480 */
481 private function payment_gateway_settings_option_changed( PaymentGateway $gateway, array $payload, ?array $old_settings = null ) {
482 if ( ! $this->was_gateway_enabled( $payload, $old_settings ) ) {
483 return;
484 }
485
486 // This is a change to a payment gateway's settings and it was just enabled. Let's send an email to the admin.
487 // "untitled" shouldn't happen, but just in case.
488 $this->notify_admin_payment_gateway_enabled( $gateway );
489 }
490
491 /**
492 * Email the site admin when a payment gateway has been enabled.
493 *
494 * @param PaymentGateway $gateway The gateway that was enabled.
495 *
496 * @return bool Whether the email was sent or not.
497 */
498 private function notify_admin_payment_gateway_enabled( $gateway ) {
499 $admin_email = get_option( 'admin_email' );
500 $user = get_user_by( 'email', $admin_email );
501 $username = $user ? $user->user_login : $admin_email;
502 $gateway_title = $gateway->get_method_title();
503 $gateway_settings_url = esc_url_raw( self_admin_url( 'admin.php?page=storeengine-settings&path=payment-method&method=' . $gateway->id ) );
504 $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
505 $site_url = home_url();
506
507 /**
508 * Allows adding to the addresses that receive payment gateway enabled notifications.
509 *
510 * @param array $email_addresses The array of email addresses to notify.
511 * @param PaymentGateway $gateway The gateway that was enabled.
512 *
513 * @return array The augmented array of email addresses to notify.
514 */
515 $email_addresses = apply_filters( 'storeengine/payment_gateway_enabled_notification_email_addresses', [], $gateway );
516 $email_addresses[] = $admin_email;
517 $email_addresses = array_unique( array_filter( $email_addresses, fn( $email_address ) => filter_var( $email_address, FILTER_VALIDATE_EMAIL ) ) );
518
519 Logger::log( 'Payment gateway enabled', "Payment gateway {$gateway_title} enabled.", Logger::INFO, 'payment-gateway' );
520
521 $email_text = sprintf(
522 /* translators: Payment gateway enabled notification email. 1: Username, 2: Gateway Title, 3: Site URL, 4: Gateway Settings URL, 5: Admin Email, 6: Site Name, 7: Site URL. */
523 __(
524 'Howdy %1$s,
525
526 The payment gateway "%2$s" was just enabled on this site:
527 %3$s
528
529 If this was intentional you can safely ignore and delete this email.
530
531 If you did not enable this payment gateway, please log in to your site and consider disabling it here:
532 %4$s
533
534 This email has been sent to %5$s
535
536 Regards,
537 All at %6$s
538 %7$s',
539 'storeengine'
540 ),
541 $username,
542 $gateway_title,
543 $site_url,
544 $gateway_settings_url,
545 $admin_email,
546 $site_name,
547 $site_url
548 );
549
550 if ( '' !== get_option( 'blogname' ) ) {
551 $site_title = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
552 } else {
553 $site_title = wp_parse_url( home_url(), PHP_URL_HOST );
554 }
555
556 return wp_mail(
557 $email_addresses,
558 sprintf(
559 /* translators: Payment gateway enabled notification email subject. %s1: Site title, $s2: Gateway title. */
560 __( '[%1$s] Payment gateway "%2$s" enabled', 'storeengine' ),
561 $site_title,
562 $gateway_title
563 ),
564 $email_text
565 );
566 }
567
568 /**
569 * Determines from changes in settings if a gateway was enabled.
570 *
571 * @param array $value New value.
572 * @param array|null $old_value Old value.
573 *
574 * @return bool Whether the gateway was enabled or not.
575 */
576 private function was_gateway_enabled( array $value, ?array $old_value = null ): bool {
577 if ( null === $old_value ) {
578 // There was no old value, so this is a new option.
579 return array_key_exists( 'is_enabled', $value ) && $value['is_enabled'];
580 }
581
582 $new_val = array_key_exists( 'is_enabled', $value ) ? $value['is_enabled'] : null;
583 $old_val = $old_value && array_key_exists( 'is_enabled', $old_value ) ? $old_value['is_enabled'] : null;
584
585 // There was an old value, so this is an update.
586 return $new_val && ! $old_val;
587 }
588
589 /**
590 * Get gateways.
591 *
592 * @return PaymentGateway[]
593 */
594 public function payment_gateways(): array {
595 $this->ensure_enabled();
596
597 $_available_gateways = [];
598
599 if ( count( $this->gateways ) > 0 ) {
600 foreach ( $this->gateways as $gateway ) {
601 $_available_gateways[ $gateway->id ] = $gateway;
602 }
603 }
604
605 return $_available_gateways;
606 }
607
608 /**
609 * Get array of registered gateway ids.
610 *
611 * Returns the full registered set without instantiating any gateway.
612 *
613 * @return array of strings
614 */
615 public function get_payment_gateway_ids() {
616 $this->load_gateways();
617
618 return array_keys( $this->gateway_classes );
619 }
620
621 /**
622 * Get available gateways.
623 *
624 * @return PaymentGateway[]
625 */
626 public function get_available_payment_gateways(): array {
627 $this->ensure_enabled();
628
629 $_available_gateways = [];
630
631 foreach ( $this->gateways as $gateway ) {
632 if ( $gateway->is_available() ) {
633 if ( ! Helper::is_add_payment_method_page() ) {
634 $_available_gateways[ $gateway->id ] = $gateway;
635 } elseif ( $gateway->supports( 'add_payment_method' ) || $gateway->supports( 'tokenization' ) ) {
636 $_available_gateways[ $gateway->id ] = $gateway;
637 }
638 }
639 }
640
641 return array_filter( (array) apply_filters( 'storeengine/available_payment_gateways', $_available_gateways ), [
642 $this,
643 'filter_valid_gateway_class'
644 ] );
645 }
646
647 protected static array $one_gateway_supports = [];
648
649 public function one_gateway_supports( string $supports_flag ) {
650 // Only check if we haven't already run the check
651 if ( ! isset( self::$one_gateway_supports[ $supports_flag ] ) ) {
652 self::$one_gateway_supports[ $supports_flag ] = false;
653
654 foreach ( $this->get_available_payment_gateways() as $gateway ) {
655 if ( $gateway->supports( $supports_flag ) ) {
656 self::$one_gateway_supports[ $supports_flag ] = true;
657 break;
658 }
659 }
660 }
661
662 return self::$one_gateway_supports[ $supports_flag ];
663 }
664
665 /**
666 * Get payment gateway class by order data.
667 *
668 * @param Order|Subscription $order Order instance.
669 *
670 * @return PaymentGateway|bool
671 */
672 public static function get_payment_gateway_by_order( $order ) {
673 // Resolve by id so a now-disabled gateway still loads for refunds/renewals.
674 $gateway = Helper::get_payment_gateways()->get_gateway( $order->get_payment_method() );
675
676 return $gateway ?: false;
677 }
678
679 public function get_available_payment_gateway( string $id ): ?PaymentGateway {
680 if ( ! $id ) {
681 return null;
682 }
683
684 $available_gateways = $this->get_available_payment_gateways();
685
686 return $available_gateways[ $id ] ?? null;
687 }
688
689 /**
690 * Callback for array filter. Returns true if gateway is of correct type.
691 *
692 * @param object $gateway Gateway to check.
693 *
694 * @return bool
695 */
696 protected function filter_valid_gateway_class( object $gateway ): bool {
697 return $gateway && is_a( $gateway, '\StoreEngine\Payment\Gateways\PaymentGateway' );
698 }
699
700 /**
701 * Set the current, active gateway.
702 *
703 * @param PaymentGateway[] $gateways Available payment gateways.
704 * @param string $preferred Optional gateway id to prefer (e.g. the
705 * order's saved payment method on the
706 * order-pay page, where there is no draft
707 * cart order to read from).
708 */
709 public function set_current_gateway( array $gateways, string $preferred = '' ) {
710 // Be on the defensive.
711 if ( empty( $gateways ) ) {
712 return;
713 }
714
715 $current_gateway = false;
716
717 // Prefer an explicitly-requested gateway when it is available (used by the
718 // order-pay page to keep the order's existing payment method selected).
719 if ( $preferred && isset( $gateways[ $preferred ] ) ) {
720 $current_gateway = $gateways[ $preferred ];
721 }
722
723 if ( ! $current_gateway ) {
724 $draft_order = Helper::get_recent_draft_order();
725
726 if ( $draft_order ) {
727 $current = $draft_order->get_payment_method( 'edit' );
728
729 if ( $current && isset( $gateways[ $current ] ) ) {
730 $current_gateway = $gateways[ $current ];
731 }
732 }
733 }
734
735 if ( ! $current_gateway ) {
736 $current_gateway = current( $gateways );
737 }
738
739 // Ensure we can make a call to set_current() without triggering an error.
740 $current_gateway->set_current();
741 }
742 }
743