PluginProbe
MONEI Payments for WooCommerce / trunk
MONEI Payments for WooCommerce vtrunk
7.3.3 7.3.2 7.3.1 7.3.0 7.2.4 7.2.3 7.2.2 7.2.0 7.2.1 7.1.3 2.1.0 3.0.0 3.1.0 3.1.1 4.0.0 4.1.0 4.1.1 4.2.0 4.2.1 5.0 5.1.0 5.1.1 5.1.2 5.2.2 5.2.3 All 87 releases
← All changes | src/Repositories/PaymentMethodsRepository.php +65 -4 7.3.1trunk View file →
@@ -1,8 +1,9 @@
1 1 <?php
2 2
3 3 namespace Monei\Repositories;
4 4
5 +use Monei\ApiException;
5 6 use Monei\MoneiClient;
6 7 use Exception;
7 8
8 9 class PaymentMethodsRepository implements PaymentMethodsRepositoryInterface {
@@ -11,14 +12,28 @@
11 12 * cache can hold it: an empty array is falsy and would be re-fetched.
12 13 */
13 14 private const UNAVAILABLE = array(
14 15 'paymentMethods' => array(),
15 - 'metadata' => array(),
16 + 'metadata' => array(),
16 17 );
17 18
19 + /**
20 + * Option that holds off the API after it rejected the key. An option, not a
21 + * transient: on the hosts that made this necessary transients do not persist,
22 + * so a transient-backed marker is gone by the next request.
23 + */
24 + public const BACKOFF_OPTION = 'monei_payment_methods_backoff';
25 +
18 26 private $accountId;
19 27 private MoneiClient $moneiClient;
20 28
29 + /**
30 + * Answers already resolved during this request, keyed by transient key. Every
31 + * gateway asks several times per render; without this each ask re-reads the
32 + * transient, and on a host where transients do not persist, calls the API again.
33 + */
34 + private array $memo = array();
35 +
21 36 public function __construct( string $accountId, MoneiClient $moneiClient ) {
22 37 $this->accountId = $accountId;
23 38 $this->moneiClient = $moneiClient;
24 39 }
@@ -30,9 +45,9 @@
30 45 // The account id no longer reaches the API — getAllowed() derives the account
31 46 // from the API key. It still gates the call because it is what separates the
32 47 // test cache from the live one, and because an unset one means the plugin is
33 48 // not configured yet.
34 - if ( ! $this->accountId ) {
49 + if ( ! $this->accountId || $this->getBackoffUntil() ) {
35 50 return null;
36 51 }
37 52 try {
38 53 // /allowed-payment-methods, the API key authenticated replacement for the
@@ -39,12 +54,23 @@
39 54 // deprecated /payment-methods. Amount, currency and country are left out on
40 55 // purpose: this repository is a container singleton that answers admin
41 56 // screens as well as the checkout, so it has no one cart to describe.
42 57 $response = $this->moneiClient->paymentMethods->getAllowed();
58 + } catch ( ApiException $e ) {
59 + // A rejected key does not fix itself, so retrying every 30 seconds only
60 + // costs. Anything else (network, 5xx) keeps the short retry.
61 + if ( in_array( $e->getCode(), array( 401, 403 ), true ) ) {
62 + $this->extendBackoff();
63 + }
64 + $response = null;
43 65 } catch ( Exception $e ) {
44 66 $response = null;
45 67 }
46 68
69 + if ( $response ) {
70 + delete_option( self::BACKOFF_OPTION );
71 + }
72 +
47 73 return $response ? json_decode( $response, true ) : array();
48 74 }
49 75
50 76 /**
@@ -51,9 +77,12 @@
51 77 * Get payment methods (fetch from transient or API).
52 78 */
53 79 public function getPaymentMethods(): array {
54 80 $transientKey = $this->generateTransientKey( $this->accountId );
55 - $data = get_transient( $transientKey );
81 + if ( isset( $this->memo[ $transientKey ] ) ) {
82 + return $this->memo[ $transientKey ];
83 + }
84 + $data = get_transient( $transientKey );
56 85
57 86 if ( ! $data ) {
58 87 $data = $this->fetchFromAPI();
59 88 if ( $data ) {
@@ -77,9 +106,41 @@
77 106 set_transient( $transientKey, $data, 30 );
78 107 }
79 108 }
80 109
81 - return $data === self::UNAVAILABLE ? array() : ( $data ?: array() );
110 + $this->memo[ $transientKey ] = $data === self::UNAVAILABLE ? array() : ( $data ?: array() );
111 +
112 + return $this->memo[ $transientKey ];
113 + }
114 +
115 + /**
116 + * When the plugin will next ask the API after it rejected the key, as a Unix
117 + * timestamp. Null while the plugin is asking normally.
118 + */
119 + public function getBackoffUntil(): ?int {
120 + $backoff = get_option( self::BACKOFF_OPTION );
121 + $until = is_array( $backoff ) ? (int) ( $backoff['until'] ?? 0 ) : 0;
122 +
123 + return $until > time() ? $until : null;
124 + }
125 +
126 + /**
127 + * Hold off the API for an hour, doubling on every further rejection up to a day.
128 + */
129 + private function extendBackoff(): void {
130 + $previous = get_option( self::BACKOFF_OPTION );
131 + $delay = HOUR_IN_SECONDS;
132 + if ( is_array( $previous ) ) {
133 + $delay = min( max( $delay, 2 * (int) ( $previous['delay'] ?? 0 ) ), DAY_IN_SECONDS );
134 + }
135 + update_option(
136 + self::BACKOFF_OPTION,
137 + array(
138 + 'until' => time() + $delay,
139 + 'delay' => $delay,
140 + ),
141 + false
142 + );
82 143 }
83 144
84 145 /**
85 146 * Generate a transient key.