Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
96.57% |
478 / 495 |
|
93.31% |
237 / 254 |
|
14.46% |
96 / 664 |
|
73.17% |
30 / 41 |
CRAP | |
0.00% |
0 / 1 |
| WC_Gateway_Monokulo | |
96.57% |
478 / 495 |
|
93.31% |
237 / 254 |
|
14.46% |
96 / 664 |
|
82.93% |
34 / 41 |
12408.65 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| init_form_fields | |
100.00% |
63 / 63 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| is_available | |
100.00% |
3 / 3 |
|
100.00% |
5 / 5 |
|
66.67% |
2 / 3 |
|
100.00% |
1 / 1 |
3.33 | |||
| has_credentials | |
100.00% |
1 / 1 |
|
100.00% |
5 / 5 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
4.12 | |||
| needs_reconnect | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| settings_need_reconnect | |
100.00% |
5 / 5 |
|
100.00% |
12 / 12 |
|
23.53% |
4 / 17 |
|
100.00% |
1 / 1 |
22.10 | |||
| render_reconnect_notice | |
91.67% |
11 / 12 |
|
80.00% |
4 / 5 |
|
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
3.33 | |||
| process_payment | |
93.75% |
15 / 16 |
|
66.67% |
2 / 3 |
|
50.00% |
1 / 2 |
|
0.00% |
0 / 1 |
2.50 | |||
| create_monokulo_order | |
100.00% |
60 / 60 |
|
94.44% |
17 / 18 |
|
30.00% |
6 / 20 |
|
100.00% |
1 / 1 |
36.78 | |||
| idempotency_key | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| format_amount | |
100.00% |
4 / 4 |
|
83.33% |
5 / 6 |
|
66.67% |
2 / 3 |
|
100.00% |
1 / 1 |
3.33 | |||
| customer_message_for_status | |
100.00% |
5 / 5 |
|
100.00% |
7 / 7 |
|
80.00% |
4 / 5 |
|
100.00% |
1 / 1 |
4.13 | |||
| hint_for_status | |
100.00% |
9 / 9 |
|
100.00% |
11 / 11 |
|
50.00% |
5 / 10 |
|
100.00% |
1 / 1 |
10.50 | |||
| get_orders_endpoint_url | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| get_checkout_url | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| get_control_plane_base_url | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| get_connect_return_url | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| connect_nonce_transient_key | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| build_connect_start_url | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| generate_monokulo_connect_html | |
100.00% |
31 / 31 |
|
100.00% |
8 / 8 |
|
50.00% |
3 / 6 |
|
100.00% |
1 / 1 |
6.00 | |||
| validate_monokulo_connect_field | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| handle_connect_return | |
40.00% |
2 / 5 |
|
66.67% |
2 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2.86 | |||
| process_connect_return | |
100.00% |
20 / 20 |
|
95.45% |
21 / 22 |
|
3.75% |
6 / 160 |
|
100.00% |
1 / 1 |
99.17 | |||
| call_connect_finish | |
100.00% |
31 / 31 |
|
100.00% |
13 / 13 |
|
22.22% |
4 / 18 |
|
100.00% |
1 / 1 |
30.05 | |||
| get_webhook_receiver_url | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| handle_webhook | |
0.00% |
0 / 7 |
|
0.00% |
0 / 7 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| process_webhook_request | |
100.00% |
23 / 23 |
|
100.00% |
21 / 21 |
|
2.33% |
6 / 257 |
|
100.00% |
1 / 1 |
123.72 | |||
| verify_webhook_signature | |
100.00% |
7 / 7 |
|
100.00% |
12 / 12 |
|
30.00% |
6 / 20 |
|
100.00% |
1 / 1 |
18.35 | |||
| find_order_by_order_id | |
100.00% |
9 / 9 |
|
100.00% |
6 / 6 |
|
50.00% |
2 / 4 |
|
100.00% |
1 / 1 |
4.12 | |||
| event_already_applied | |
100.00% |
2 / 2 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| mark_event_applied | |
100.00% |
4 / 4 |
|
100.00% |
4 / 4 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| apply_webhook_event | |
100.00% |
34 / 34 |
|
91.67% |
11 / 12 |
|
57.14% |
4 / 7 |
|
100.00% |
1 / 1 |
8.83 | |||
| apply_order_status_event | |
90.91% |
20 / 22 |
|
92.31% |
12 / 13 |
|
41.67% |
5 / 12 |
|
0.00% |
0 / 1 |
16.73 | |||
| build_settings_url | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| maybe_render_connect_notice | |
100.00% |
14 / 14 |
|
100.00% |
17 / 17 |
|
6.25% |
4 / 64 |
|
100.00% |
1 / 1 |
60.73 | |||
| trace_id | |
100.00% |
3 / 3 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| adopt_traceparent | |
100.00% |
6 / 6 |
|
100.00% |
7 / 7 |
|
80.00% |
4 / 5 |
|
100.00% |
1 / 1 |
4.13 | |||
| traceparent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| log | |
100.00% |
11 / 11 |
|
100.00% |
3 / 3 |
|
50.00% |
1 / 2 |
|
100.00% |
1 / 1 |
2.50 | |||
| maybe_forward | |
88.89% |
16 / 18 |
|
90.91% |
10 / 11 |
|
14.29% |
2 / 14 |
|
0.00% |
0 / 1 |
28.67 | |||
| flush_forwarded | |
100.00% |
15 / 15 |
|
100.00% |
3 / 3 |
|
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * `WC_Gateway_Monokulo`: the WooCommerce-facing shell for Monokulo. |
| 4 | * |
| 5 | * @package Monokulo |
| 6 | */ |
| 7 | |
| 8 | if ( ! defined( 'ABSPATH' ) ) { |
| 9 | exit; |
| 10 | } |
| 11 | |
| 12 | /** |
| 13 | * Registers "Monero (via Monokulo)" as a WooCommerce checkout option and |
| 14 | * hands each order off to Monokulo at checkout. |
| 15 | * |
| 16 | * The plugin talks to Monokulo only, never to the engine behind it (which is |
| 17 | * private). Connecting (`process_connect_return()`) stores Monokulo's public |
| 18 | * address (`endpoint`), the store's public key and its secret key. At |
| 19 | * checkout `process_payment()` creates the order with |
| 20 | * `POST {endpoint}/pay/{pk}/orders`, authenticated with |
| 21 | * `Authorization: Bearer {secret key}`, and sends the customer to Monokulo's |
| 22 | * checkout page, `{endpoint}/pay/{pk}/orders/{order_id}`. Monokulo prices |
| 23 | * the order into XMR. Payment updates arrive as webhooks the engine sends |
| 24 | * straight to this site (`handle_webhook()`), registered for it by Monokulo |
| 25 | * during connect. |
| 26 | * |
| 27 | * The gateway ships disabled by default (`docs/WOOCOMMERCE_ROADMAP.md` |
| 28 | * Stage 8): connecting enables it. `is_available()` also keeps it off the |
| 29 | * checkout while it has no usable connection, including a connection made |
| 30 | * by a plugin version before this one (see `CONNECTION_VERSION`). |
| 31 | * |
| 32 | * Every real decision - order creation, payment matching, exchange rates, |
| 33 | * webhook delivery - lives in the Rust engine (`scanner`) and the |
| 34 | * control plane sitting in front of it, per this repo's Stage 7 framing |
| 35 | * ("PHP is a thin adapter, not a second implementation of anything"). This |
| 36 | * class's job stops at being a faithful WooCommerce citizen: register |
| 37 | * correctly, expose the right settings fields, and make one outbound HTTP |
| 38 | * call per order. Nothing more belongs here by design - in particular, this |
| 39 | * gateway never decides an order is paid (see `process_payment()`'s own doc |
| 40 | * comment on why `payment_complete()` is never called here) and never |
| 41 | * verifies a payment itself (WBS 1.5.4's webhook receiver's job). |
| 42 | */ |
| 43 | class WC_Gateway_Monokulo extends WC_Payment_Gateway { |
| 44 | |
| 45 | /** |
| 46 | * The one, fixed address of Monokulo's own hosted service - where the |
| 47 | * connect flow starts (`crates/monokulo/src/http/connect.rs`). The |
| 48 | * address orders are then created at is the `endpoint` that flow's |
| 49 | * `/finish` returns (`$api_base_url`), which is the same Monokulo |
| 50 | * instance's configured public address; keeping the two separate lets |
| 51 | * an instance serve its connect pages and its checkout from different |
| 52 | * addresses (for example a clearnet dashboard and an onion checkout). |
| 53 | * For the hosted product there is exactly one such service, so this is |
| 54 | * a class constant rather than something a merchant types in - the way |
| 55 | * a Stripe or WooCommerce Payments extension hardcodes its own API host. |
| 56 | * |
| 57 | * **This is a placeholder, deliberately, not a real address**: as of |
| 58 | * this step, Monokulo's control plane has no real, decided |
| 59 | * production domain yet (checked `docs/WOOCOMMERCE_ROADMAP.md` and |
| 60 | * `work_notes.md` directly - neither names one). `cloud.monokulo.example` |
| 61 | * is lifted verbatim from the roadmap doc's own illustrative URL (Stage |
| 62 | * 6) specifically *because* it's already an obvious non-address - |
| 63 | * `.example` is the IANA-reserved TLD that can never resolve to anything |
| 64 | * real (RFC 2606) - rather than inventing a new placeholder that could |
| 65 | * later be mistaken for a real one. Whoever picks the real production |
| 66 | * domain only has to change this one line (and, ideally, drop the filter |
| 67 | * override below instead of leaving both). |
| 68 | * |
| 69 | * @var string |
| 70 | */ |
| 71 | const CONTROL_PLANE_BASE_URL = 'https://cloud.monokulo.example'; |
| 72 | |
| 73 | /** |
| 74 | * The `platform` path segment this plugin identifies itself as to the |
| 75 | * control plane's generic, platform-agnostic connect flow |
| 76 | * (`GET /connect/{platform}` etc., see `connect.rs`'s own module doc |
| 77 | * comment) - `"woocommerce"`, matching exactly what `control-plane`'s own |
| 78 | * tests and `mock-woocommerce`'s driver already use for this platform, so |
| 79 | * every `store_connections.platform` row this plugin ever produces is |
| 80 | * consistent with what the mock already exercised end to end. |
| 81 | */ |
| 82 | const CONNECT_PLATFORM = 'woocommerce'; |
| 83 | |
| 84 | /** |
| 85 | * The version of the connection this plugin stores, written to the |
| 86 | * `connection_version` setting by `process_connect_return()` whenever |
| 87 | * the connect flow succeeds. |
| 88 | * |
| 89 | * Version `2` is the first where `endpoint` is Monokulo's own public |
| 90 | * address. Before it, `/finish` handed plugins the engine's address, |
| 91 | * which no longer accepts orders from anything but Monokulo. An install |
| 92 | * connected by an older plugin has credentials but no |
| 93 | * `connection_version` at all, which is how it is recognised - never by |
| 94 | * looking at the URL it holds. Such an install needs a reconnect: |
| 95 | * `is_available()` keeps the gateway off the checkout (rather than |
| 96 | * letting orders fail) and an admin notice asks the merchant to |
| 97 | * reconnect (`render_reconnect_notice()`). |
| 98 | * |
| 99 | * Bump this whenever a stored connection from an older plugin can no |
| 100 | * longer work as-is. |
| 101 | * |
| 102 | * @var string |
| 103 | */ |
| 104 | const CONNECTION_VERSION = '2'; |
| 105 | |
| 106 | /** |
| 107 | * How this plugin names itself to Monokulo when it creates an order, in |
| 108 | * a `Monokulo-Client` header: Monokulo records the order as the |
| 109 | * WooCommerce plugin's (`crates/monokulo/src/http/pay.rs`, |
| 110 | * `order_source`). A store has no platform of its own; the order says |
| 111 | * where it came from. Keep the version with the plugin header's. |
| 112 | */ |
| 113 | const CLIENT = 'woocommerce/0.1.0'; |
| 114 | |
| 115 | /** |
| 116 | * The `admin-post.php` action name this gateway registers as `return_url` |
| 117 | * for the connect flow - see `get_connect_return_url()`'s own doc comment |
| 118 | * for why `admin-post.php`, specifically, is the callback mechanism this |
| 119 | * step chose. |
| 120 | */ |
| 121 | const CONNECT_RETURN_ACTION = 'monokulo_connect_return'; |
| 122 | |
| 123 | /** |
| 124 | * The order-meta key `process_payment()` writes the engine's own |
| 125 | * `order_id` under, and `find_order_by_order_id()` (WBS 1.5.4) reads |
| 126 | * back to map an incoming webhook to a `WC_Order` - promoted to a |
| 127 | * constant, rather than the literal string 1.5.2 originally hardcoded |
| 128 | * inline, specifically because this step adds a second, independent call |
| 129 | * site that must agree with the first byte-for-byte: a silent drift |
| 130 | * between "what `process_payment()` writes" and "what the webhook |
| 131 | * receiver queries for" would make every real webhook fail to find its |
| 132 | * order, with no error anywhere near either call site to explain why. |
| 133 | * |
| 134 | * @var string |
| 135 | */ |
| 136 | const META_ORDER_ID = '_monokulo_order_id'; |
| 137 | |
| 138 | /** |
| 139 | * The order-meta key `event_already_applied()`/`mark_event_applied()` |
| 140 | * (WBS 1.5.4) use to record which webhook `event_id`s have already been |
| 141 | * applied to a given order - a JSON-encoded array of `evt_...` strings, |
| 142 | * not a comma-joined one, purely so `event_id`s (which this codebase |
| 143 | * never puts commas in, but nothing guarantees that structurally) can |
| 144 | * never be mis-split. See `event_already_applied()`'s own doc comment for |
| 145 | * why this exists at all. |
| 146 | * |
| 147 | * @var string |
| 148 | */ |
| 149 | const META_APPLIED_EVENT_IDS = '_monokulo_applied_webhook_event_ids'; |
| 150 | |
| 151 | /** |
| 152 | * The `X-Monokulo-Signature` header, as PHP exposes it in `$_SERVER`: |
| 153 | * every incoming HTTP header `Name-Like-This` is normalized to |
| 154 | * `HTTP_NAME_LIKE_THIS` by PHP's own SAPI before user code ever sees it |
| 155 | * (a PHP/CGI convention, not a WordPress one) - named here as a constant |
| 156 | * so `handle_webhook()`'s one read of it and this doc comment's own |
| 157 | * explanation stay next to each other rather than the transform being |
| 158 | * re-derived silently at the call site. |
| 159 | * |
| 160 | * @var string |
| 161 | */ |
| 162 | const WEBHOOK_SIGNATURE_SERVER_KEY = 'HTTP_X_MONOKULO_SIGNATURE'; |
| 163 | |
| 164 | /** |
| 165 | * Maps an engine `OrderStatus` (`src/status.rs::OrderStatus`, read |
| 166 | * directly - the seven-variant, authoritative enum, not assumed |
| 167 | * exhaustive from docs) to the WooCommerce order status this gateway |
| 168 | * transitions an order to when that engine status is announced by an |
| 169 | * `order.<status>` webhook event. `paid`/`overpaid` are deliberately |
| 170 | * **absent** from this table - see `apply_order_status_event()`'s own |
| 171 | * doc comment for why those two go through `WC_Order::payment_complete()` |
| 172 | * instead of a plain table lookup. |
| 173 | * |
| 174 | * Each row's reasoning (why *this* WooCommerce status, not some other |
| 175 | * plausible one) is documented in full on `apply_order_status_event()`, |
| 176 | * not repeated here - this table is the answer, that method's doc |
| 177 | * comment is the "why". |
| 178 | * |
| 179 | * @var array<string, string> |
| 180 | */ |
| 181 | const STATUS_MAP = array( |
| 182 | 'pending' => 'pending', |
| 183 | 'unconfirmed' => 'on-hold', |
| 184 | 'confirming' => 'on-hold', |
| 185 | 'partial' => 'on-hold', |
| 186 | 'expired' => 'cancelled', |
| 187 | ); |
| 188 | |
| 189 | /** |
| 190 | * Monokulo's public address this store talks to, e.g. |
| 191 | * `https://pay.example.com` - the `endpoint` `/finish` returned when the |
| 192 | * store connected (`connect.rs::FinishResponse::endpoint`). Order |
| 193 | * creation and the checkout redirect both go here; the engine behind |
| 194 | * Monokulo is never contacted. No trailing slash is needed (stripped |
| 195 | * where URLs are built). |
| 196 | * |
| 197 | * @var string |
| 198 | */ |
| 199 | private $api_base_url; |
| 200 | |
| 201 | /** |
| 202 | * The store's public key (`pk_...`) this gateway creates orders under, |
| 203 | * saved by the connect flow. |
| 204 | * |
| 205 | * @var string |
| 206 | */ |
| 207 | private $tenant_public_key; |
| 208 | |
| 209 | /** |
| 210 | * The store's secret key (`sk_...`), returned by the connect flow's |
| 211 | * `/finish` call (`FinishResponse::secret_token`). Sent as |
| 212 | * `Authorization: Bearer ...` when creating orders |
| 213 | * (`create_monokulo_order()`), which is how Monokulo knows the order |
| 214 | * comes from the store's own server: it skips the per-address rate |
| 215 | * limit and is accepted even when the store only allows its checkout on |
| 216 | * its verified domains. Never sent anywhere else, and never shown to |
| 217 | * customers. |
| 218 | * |
| 219 | * @var string |
| 220 | */ |
| 221 | private $secret_token; |
| 222 | |
| 223 | /** |
| 224 | * The webhook's own signing secret (`whsec_...`), returned by |
| 225 | * `call_connect_finish()`'s `/finish` response and saved (since WBS |
| 226 | * 1.5.3) under the `webhook_signing_secret` option key - see that |
| 227 | * method's own doc comment for why it's saved outside `$this->form_fields` |
| 228 | * entirely. Read here, in this step, for the first time: it's the HMAC |
| 229 | * key `verify_webhook_signature()` below checks every incoming webhook |
| 230 | * delivery against. |
| 231 | * |
| 232 | * @var string |
| 233 | */ |
| 234 | private $webhook_signing_secret; |
| 235 | |
| 236 | /** |
| 237 | * The trace this PHP request belongs to (32 lowercase hex characters, |
| 238 | * W3C Trace Context), shared by every gateway instance in the request. |
| 239 | * Sent to Monokulo as a `traceparent` header on every call, so |
| 240 | * Monokulo's own logs for the call land in the same trace, and written |
| 241 | * on every plugin log line, so the two can be matched. An incoming |
| 242 | * webhook adopts the engine's trace instead. `null` until first used. |
| 243 | * |
| 244 | * @var string|null |
| 245 | */ |
| 246 | private static $trace_id = null; |
| 247 | |
| 248 | /** |
| 249 | * Warnings and errors waiting to be sent to Monokulo at the end of this |
| 250 | * PHP request (the "Send errors to Monokulo" option), at most |
| 251 | * `MAX_FORWARDED` of them. |
| 252 | * |
| 253 | * @var array<int, array{level: string, message: string, trace_id: string}> |
| 254 | */ |
| 255 | private static $forward_queue = array(); |
| 256 | |
| 257 | /** |
| 258 | * Whether the end-of-request send is already registered. |
| 259 | * |
| 260 | * @var bool |
| 261 | */ |
| 262 | private static $forward_registered = false; |
| 263 | |
| 264 | /** |
| 265 | * Most entries sent per PHP request; Monokulo refuses more. |
| 266 | */ |
| 267 | const MAX_FORWARDED = 20; |
| 268 | |
| 269 | /** |
| 270 | * WooCommerce log levels that are forwarded. |
| 271 | */ |
| 272 | const FORWARDED_LEVELS = array( 'warning', 'error', 'critical', 'alert', 'emergency' ); |
| 273 | |
| 274 | /** |
| 275 | * Sets up the gateway's identity and settings fields. |
| 276 | * |
| 277 | * WooCommerce instantiates every class registered via the |
| 278 | * `woocommerce_payment_gateways` filter (see `monokulo.php`) |
| 279 | * with no constructor arguments, so every property this gateway needs |
| 280 | * has to be derivable here with no external input beyond WordPress's |
| 281 | * own options table (via `get_option()`, populated by |
| 282 | * `init_settings()` below) - there is no other injection point at |
| 283 | * this stage of WooCommerce's own lifecycle. |
| 284 | */ |
| 285 | public function __construct() { |
| 286 | // `id` is the gateway's permanent identifier: it's the array key |
| 287 | // WooCommerce's settings are stored under |
| 288 | // (`woocommerce_monokulo_settings` in `wp_options`), the |
| 289 | // value that ends up as `$order->get_payment_method()` on every |
| 290 | // order this gateway processes, and the suffix of the webhook |
| 291 | // endpoint WBS 1.5.4 registers (`woocommerce_api_{$this->id}`). |
| 292 | // Changing it later would silently orphan every existing order's |
| 293 | // payment-method record and any already-registered webhook URL, so |
| 294 | // it's treated as fixed from this first step onward, not a cosmetic |
| 295 | // label. Chosen to read as "the id", not a version-agnostic English |
| 296 | // phrase like "monero" (which a future unrelated Monero gateway |
| 297 | // could just as reasonably claim) - `monokulo` names the |
| 298 | // actual product per `docs/WOOCOMMERCE_ROADMAP.md`'s own framing of |
| 299 | // the hosted service by that name throughout. |
| 300 | $this->id = 'monokulo'; |
| 301 | |
| 302 | // The Monokulo mark beside the payment method at checkout: the square |
| 303 | // version, shipped with the plugin so the shopper's browser loads it |
| 304 | // from the store, not from monokulo. Written by `cargo xtask logo`. |
| 305 | $this->icon = plugins_url( 'assets/monokulo-icon.svg', dirname( __DIR__ ) . '/monokulo.php' ); |
| 306 | |
| 307 | // This gateway never renders its own fields inline at WooCommerce's |
| 308 | // checkout - per `docs/WOOCOMMERCE_ROADMAP.md` Stage 7's explicit |
| 309 | // integration choice, paying with Monero means redirecting the |
| 310 | // customer to Monokulo's own checkout page |
| 311 | // (`/pay/{pk}/orders/{order_id}`), not embedding a new widget into |
| 312 | // WooCommerce's checkout form. `has_fields = false` is |
| 313 | // what tells WooCommerce's checkout template not to reserve any |
| 314 | // inline space for this gateway. |
| 315 | $this->has_fields = false; |
| 316 | |
| 317 | // `method_title`/`method_description` are what a merchant sees on |
| 318 | // WooCommerce's own "Payments" settings list (Settings > Payments) - |
| 319 | // distinct from `title`/`description` below, which is what the |
| 320 | // *customer* sees at checkout. Conflating the two is a common |
| 321 | // mistake in WooCommerce gateway plugins; kept deliberately separate |
| 322 | // here even though nothing yet enforces the distinction functionally, |
| 323 | // because getting it right from the first version avoids a later |
| 324 | // "why does the merchant admin screen say something meant for |
| 325 | // customers" bug report. |
| 326 | $this->method_title = __( 'Monokulo', 'monokulo' ); |
| 327 | $this->method_description = __( |
| 328 | 'Accept Monero at checkout without ever holding customer funds or your own spend key - Monokulo watches the chain for you and only ever needs a view key. Connect your wallet to enable this gateway for real.', |
| 329 | 'monokulo' |
| 330 | ); |
| 331 | |
| 332 | // `supports` is WooCommerce's own mechanism for a gateway to declare |
| 333 | // which of its many optional checkout/order features it implements |
| 334 | // (refunds, subscriptions, pre-orders, tokenization, etc.) - |
| 335 | // WooCommerce's core checkout and admin-order-screen code both |
| 336 | // consult this array before offering a feature (e.g. it won't show |
| 337 | // a "Refund" button for a gateway that hasn't declared `refunds`), |
| 338 | // rather than assuming every gateway implements everything. |
| 339 | // `'products'` is the baseline every ordinary one-time-payment |
| 340 | // gateway declares (WooCommerce's own bundled gateways all include |
| 341 | // it) - it means "this gateway works for a cart containing ordinary |
| 342 | // products", as opposed to gateways that only make sense for |
| 343 | // recurring subscriptions. Nothing else is declared here |
| 344 | // deliberately: refunds specifically would be a false claim right |
| 345 | // now (there is no refund flow) and no other `supports` value is |
| 346 | // developed as of WBS 1.5.1's own scope. |
| 347 | $this->supports = array( 'products' ); |
| 348 | |
| 349 | // Populates `$this->form_fields`, the schema `init_settings()` (next) |
| 350 | // reads defaults from and WooCommerce's own settings-page renderer |
| 351 | // walks to draw the actual HTML form. |
| 352 | $this->init_form_fields(); |
| 353 | |
| 354 | // Loads whatever's actually been saved in `wp_options` into |
| 355 | // `$this->settings`, falling back to each field's own `default` from |
| 356 | // `init_form_fields()` above for anything never saved yet (e.g. a |
| 357 | // brand new install). This has to run *after* `init_form_fields()` |
| 358 | // - there'd be nothing to load defaults from otherwise - and |
| 359 | // *before* the `get_option()` calls immediately following it, which |
| 360 | // read out of the `$this->settings` array this call populates. |
| 361 | $this->init_settings(); |
| 362 | |
| 363 | // `title`/`description` are the customer-facing checkout strings - |
| 364 | // pulled from saved settings when present, otherwise the same |
| 365 | // literal WBS 1.5.1's own acceptance outcome names verbatim |
| 366 | // ("Monero (via Monokulo)"), so a fresh install already shows |
| 367 | // the right label with zero configuration, while still letting a |
| 368 | // merchant override the wording later without a code change. |
| 369 | $this->title = $this->get_option( 'title', __( 'Monero (via Monokulo)', 'monokulo' ) ); |
| 370 | $this->description = $this->get_option( |
| 371 | 'description', |
| 372 | __( 'Pay with Monero. You will be redirected to a secure Monokulo payment page to complete your purchase.', 'monokulo' ) |
| 373 | ); |
| 374 | |
| 375 | // The connection the connect flow saved. `trim()`, not just a raw |
| 376 | // `get_option()` read: a value pasted by hand into the settings form |
| 377 | // notoriously picks up leading/trailing whitespace, and an |
| 378 | // un-trimmed `pk_...\n` would fail `create_monokulo_order()`'s |
| 379 | // outbound call in a way that looks identical to a genuinely wrong |
| 380 | // key. |
| 381 | $this->api_base_url = trim( (string) $this->get_option( 'endpoint', '' ) ); |
| 382 | $this->tenant_public_key = trim( (string) $this->get_option( 'public_key', '' ) ); |
| 383 | $this->secret_token = trim( (string) $this->get_option( 'secret_token', '' ) ); |
| 384 | $this->webhook_signing_secret = trim( (string) $this->get_option( 'webhook_signing_secret', '' ) ); |
| 385 | |
| 386 | // No explicit `$this->enabled = $this->get_option( 'enabled' )` line |
| 387 | // here, deliberately - checked directly against WooCommerce's own |
| 388 | // source (`WC_Payment_Gateway::init_settings()`, |
| 389 | // includes/abstracts/abstract-wc-payment-gateway.php) rather than |
| 390 | // assumed: that method, called just above, already sets |
| 391 | // `$this->enabled` itself from `$this->settings['enabled']` |
| 392 | // (`'yes' === $this->settings['enabled'] ? 'yes' : 'no'`) - a second |
| 393 | // assignment here would be pure duplication, not a safety net. This |
| 394 | // matches the pattern WooCommerce's own bundled gateways use (e.g. |
| 395 | // `WC_Gateway_BACS`'s constructor never touches `$this->enabled` |
| 396 | // either, for the identical reason - confirmed by reading |
| 397 | // `includes/gateways/bacs/class-wc-gateway-bacs.php` directly). |
| 398 | // |
| 399 | // What actually makes this gateway disabled by default is the |
| 400 | // `enabled` field's own `'default' => 'no'` in `init_form_fields()` |
| 401 | // below, which `init_settings()` falls back to for a fresh install |
| 402 | // with nothing saved yet - and that default is not an oversight this |
| 403 | // step left for later, it is WBS 1.5.1's own stated outcome |
| 404 | // ("disabled state is fine at this step"). A gateway with no |
| 405 | // connected wallet has no `pk_`/`sk_`/`endpoint` to call yet (that |
| 406 | // only exists once WBS 1.5.3's connect flow runs), so registering it |
| 407 | // pre-enabled would let a merchant offer a payment method that WBS |
| 408 | // 1.5.2's `process_payment()` cannot yet honor - exactly the failure |
| 409 | // mode a disabled-by-default gateway avoids. |
| 410 | |
| 411 | // The standard WooCommerce hook a gateway registers so that |
| 412 | // submitting its own settings form (Settings > Payments > this |
| 413 | // gateway) actually persists - WooCommerce fires |
| 414 | // `woocommerce_update_options_payment_gateways_{$this->id}` on save, |
| 415 | // and `process_admin_options()` (inherited from `WC_Payment_Gateway`, |
| 416 | // not overridden here - its default behavior of validating and |
| 417 | // saving `$this->form_fields` against submitted POST data is exactly |
| 418 | // right for this step's plain checkbox/text fields) is what actually |
| 419 | // writes the new values back to `wp_options`. Without this line, the |
| 420 | // settings form would render correctly but silently discard every |
| 421 | // change on submit. |
| 422 | add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) ); |
| 423 | |
| 424 | // The real connect-flow callback (WBS 1.5.3) - see |
| 425 | // `get_connect_return_url()`'s own doc comment for why |
| 426 | // `admin_post_{action}` specifically is the right WordPress |
| 427 | // mechanism for this, and `handle_connect_return()`'s for why |
| 428 | // registering it here in the constructor is safe (identical |
| 429 | // reasoning to the `woocommerce_update_options_...` registration |
| 430 | // immediately above: this constructor only ever runs once |
| 431 | // WooCommerce's own `WC_Payment_Gateways::init()` has already fired, |
| 432 | // which happens on every request including the one `admin-post.php` |
| 433 | // itself bootstraps). |
| 434 | add_action( 'admin_post_' . self::CONNECT_RETURN_ACTION, array( $this, 'handle_connect_return' ) ); |
| 435 | |
| 436 | // The real webhook receiver (WBS 1.5.4) - WooCommerce's own real, |
| 437 | // documented mechanism for a plugin's webhook endpoint, confirmed |
| 438 | // directly against the *installed* WooCommerce source before relying |
| 439 | // on it (not just its doc comments): `WC()->api_request_url( $id )` |
| 440 | // (already used by `get_webhook_receiver_url()` above, since 1.5.3) |
| 441 | // builds a URL carrying a `wc-api={id}` query var (or `/wc-api/{id}/` |
| 442 | // under pretty permalinks); `src/Internal/Utilities/ |
| 443 | // LegacyRestApiStub.php::maybe_process_wc_api_query_var()` (hooked on |
| 444 | // `parse_request`, read directly) is what actually dispatches that |
| 445 | // var, firing `do_action( 'woocommerce_api_' . $api_request )` for |
| 446 | // *any* site that has this hook registered - regardless of whether |
| 447 | // the separate, full "WooCommerce Legacy REST API" extension is |
| 448 | // installed. That file's own doc comment explains why the stub |
| 449 | // exists at all: the versioned `/wc-api/v1-3/...` REST endpoints |
| 450 | // were genuinely removed from core in WC 9.0, but the plain |
| 451 | // `woocommerce_api_{id}` gateway-callback mechanism (what PayPal's |
| 452 | // own legacy IPN handler still uses too, per |
| 453 | // `wc-deprecated-functions.php::woocommerce_legacy_paypal_ipn()`, |
| 454 | // read directly) was deliberately kept working without it - this is |
| 455 | // real, current, installed-source-confirmed behavior on this |
| 456 | // environment's WooCommerce 11.1.0, not an assumption carried over |
| 457 | // from an older WooCommerce version where the whole legacy API was |
| 458 | // still in core wholesale. |
| 459 | add_action( 'woocommerce_api_' . $this->id, array( $this, 'handle_webhook' ) ); |
| 460 | |
| 461 | // Flashes a one-line success/error notice on this gateway's own |
| 462 | // settings screen after a connect attempt redirects back to it - see |
| 463 | // `maybe_render_connect_notice()`'s own doc comment for why a plain |
| 464 | // query-string flag is used here rather than `WC_Admin_Settings:: |
| 465 | // add_error()` (which only works within the same request that |
| 466 | // renders the settings form, and the connect callback is a separate |
| 467 | // request). |
| 468 | add_action( 'admin_notices', array( $this, 'maybe_render_connect_notice' ) ); |
| 469 | // The site-wide "please reconnect" notice is registered in |
| 470 | // `monokulo.php` instead, so it shows on every admin screen whether |
| 471 | // or not WooCommerce has built its gateways on that request. |
| 472 | } |
| 473 | |
| 474 | /** |
| 475 | * Defines the settings fields WooCommerce renders on this gateway's own |
| 476 | * settings screen (Settings > Payments > Monokulo) and that |
| 477 | * `init_settings()`/`get_option()` above read defaults from. |
| 478 | * |
| 479 | * Kept to the minimum this step actually needs, not a placeholder set of |
| 480 | * every field a *later* step will eventually want (an endpoint override, |
| 481 | * a webhook secret) - those belong to whichever step actually uses them. |
| 482 | * Building empty settings fields now, with nothing yet reading or |
| 483 | * writing them for real, would be exactly the kind of unrequested scope |
| 484 | * this project's own established practice (see `work_notes.md`'s |
| 485 | * progress log) has repeatedly flagged and avoided elsewhere. |
| 486 | * |
| 487 | * `endpoint`/`public_key`/`secret_token` are written by the connect flow |
| 488 | * (`process_connect_return()`); they stay visible (the key masked) so a |
| 489 | * merchant can see what the store is connected with. Neither |
| 490 | * `connection_version` (see `CONNECTION_VERSION`) nor |
| 491 | * `webhook_signing_secret` is a form field. No `webhook_signing_secret` |
| 492 | * field here, deliberately: unlike the three |
| 493 | * fields above, a merchant has no legitimate way to independently know |
| 494 | * that value by hand - it's minted fresh by the engine only when a |
| 495 | * webhook is registered (`connect.rs::finish`), so it's only ever |
| 496 | * written programmatically (`process_connect_return()` below, via a |
| 497 | * plain `update_option()` call outside `$this->form_fields` entirely - |
| 498 | * confirmed directly against `WC_Settings_API::update_option()` |
| 499 | * /`get_option()`, both read/write `$this->settings[$key]` by string key |
| 500 | * with no dependency on the key being declared here), never rendered as |
| 501 | * an editable field a merchant could accidentally paste a wrong value |
| 502 | * into. |
| 503 | */ |
| 504 | public function init_form_fields() { |
| 505 | $this->form_fields = array( |
| 506 | 'enabled' => array( |
| 507 | 'title' => __( 'Enable/Disable', 'monokulo' ), |
| 508 | 'type' => 'checkbox', |
| 509 | 'label' => __( 'Enable Monero payments via Monokulo', 'monokulo' ), |
| 510 | // See the constructor's own comment on `$this->enabled` - |
| 511 | // this default is what makes "disabled state is fine at |
| 512 | // this step" the actual out-of-the-box behavior, not just |
| 513 | // documentation of an intention. WBS 1.5.3's own connect |
| 514 | // callback (`process_connect_return()` below) is now a real |
| 515 | // second way this flips to `'yes'` - see that method's own |
| 516 | // doc comment for why flipping it there, not just leaving it |
| 517 | // for the merchant to toggle afterward, is the WBS's own |
| 518 | // stated outcome, not an extra liberty this step is taking. |
| 519 | 'default' => 'no', |
| 520 | ), |
| 521 | 'title' => array( |
| 522 | 'title' => __( 'Title', 'monokulo' ), |
| 523 | 'type' => 'text', |
| 524 | 'description' => __( 'The label the customer sees for this payment method at checkout.', 'monokulo' ), |
| 525 | 'default' => __( 'Monero (via Monokulo)', 'monokulo' ), |
| 526 | 'desc_tip' => true, |
| 527 | ), |
| 528 | 'description' => array( |
| 529 | 'title' => __( 'Description', 'monokulo' ), |
| 530 | 'type' => 'textarea', |
| 531 | 'description' => __( 'The explanatory text the customer sees for this payment method at checkout.', 'monokulo' ), |
| 532 | 'default' => __( 'Pay with Monero. You will be redirected to a secure Monokulo payment page to complete your purchase.', 'monokulo' ), |
| 533 | 'desc_tip' => true, |
| 534 | ), |
| 535 | |
| 536 | 'connection' => array( |
| 537 | 'title' => __( 'Connection', 'monokulo' ), |
| 538 | 'type' => 'title', |
| 539 | 'description' => __( 'Click below to connect your Monero wallet through Monokulo - this fills in everything below automatically and enables this gateway.', 'monokulo' ), |
| 540 | ), |
| 541 | |
| 542 | // The real WBS 1.5.3 button - see `generate_monokulo_connect_html()`'s |
| 543 | // own doc comment for exactly what it renders and why this needs |
| 544 | // a genuinely custom field type rather than reusing `'title'` |
| 545 | // (whose own `generate_title_html()` renders fixed markup this |
| 546 | // step needs to deviate from: a real `<a href=...>` built fresh |
| 547 | // on every render, not static text). |
| 548 | 'connect' => array( |
| 549 | 'type' => 'monokulo_connect', |
| 550 | 'description' => __( 'You will be sent to Monokulo to sign in (or sign up) and confirm the connection, then returned here automatically.', 'monokulo' ), |
| 551 | ), |
| 552 | |
| 553 | 'endpoint' => array( |
| 554 | 'title' => __( 'Monokulo address', 'monokulo' ), |
| 555 | 'type' => 'text', |
| 556 | 'description' => __( 'The public address of the Monokulo instance this store uses. Filled in automatically by Connect above.', 'monokulo' ), |
| 557 | 'default' => '', |
| 558 | 'placeholder' => 'https://pay.example.com', |
| 559 | 'desc_tip' => true, |
| 560 | ), |
| 561 | 'public_key' => array( |
| 562 | 'title' => __( 'Store public key', 'monokulo' ), |
| 563 | 'type' => 'text', |
| 564 | 'description' => __( 'Your Monokulo store\'s public key (starts with pk_). Filled in automatically by Connect above.', 'monokulo' ), |
| 565 | 'default' => '', |
| 566 | 'placeholder' => 'pk_...', |
| 567 | 'desc_tip' => true, |
| 568 | ), |
| 569 | 'forward_errors' => array( |
| 570 | 'title' => __( 'Send errors to Monokulo', 'monokulo' ), |
| 571 | 'type' => 'checkbox', |
| 572 | 'label' => __( 'Also send this plugin\'s warnings and errors to Monokulo\'s Logs page', 'monokulo' ), |
| 573 | 'description' => __( 'They stay in WooCommerce\'s own logs as well. Sent with your store\'s secret key, next to Monokulo\'s own lines for the same request. Monokulo keeps them only while Diagnostics is turned on in this store\'s settings there.', 'monokulo' ), |
| 574 | 'default' => 'no', |
| 575 | 'desc_tip' => true, |
| 576 | ), |
| 577 | 'secret_token' => array( |
| 578 | 'title' => __( 'Store secret key', 'monokulo' ), |
| 579 | 'type' => 'password', |
| 580 | 'description' => __( 'Your Monokulo store\'s secret key (starts with sk_). Filled in automatically by Connect above. Used to create orders from this site\'s server; never shown to customers.', 'monokulo' ), |
| 581 | 'default' => '', |
| 582 | 'placeholder' => 'sk_...', |
| 583 | 'desc_tip' => true, |
| 584 | ), |
| 585 | ); |
| 586 | } |
| 587 | |
| 588 | /** |
| 589 | * Extends `WC_Payment_Gateway::is_available()` (which only checks |
| 590 | * `enabled` and a currency restriction): the gateway is only offered at |
| 591 | * checkout while it has a complete, current connection. Without one |
| 592 | * `create_monokulo_order()` would fail for the first customer to try it, |
| 593 | * so it is better not to offer it at all - including for an install |
| 594 | * connected by an older plugin version, which must reconnect first (see |
| 595 | * `CONNECTION_VERSION`). |
| 596 | * |
| 597 | * @return bool |
| 598 | */ |
| 599 | public function is_available() { |
| 600 | if ( ! parent::is_available() ) { |
| 601 | return false; |
| 602 | } |
| 603 | |
| 604 | return $this->has_credentials() && ! $this->needs_reconnect(); |
| 605 | } |
| 606 | |
| 607 | /** |
| 608 | * Whether this gateway has everything order creation needs: Monokulo's |
| 609 | * address, the store's public key and its secret key. |
| 610 | * |
| 611 | * @return bool |
| 612 | */ |
| 613 | private function has_credentials() { |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 615 | } |
| 616 | |
| 617 | /** |
| 618 | * Whether this install holds a connection made before the current |
| 619 | * `CONNECTION_VERSION` and must reconnect. See that constant. |
| 620 | * |
| 621 | * @return bool |
| 622 | */ |
| 623 | public function needs_reconnect() { |
| 624 | return self::settings_need_reconnect( $this->settings ); |
| 625 | } |
| 626 | |
| 627 | /** |
| 628 | * `needs_reconnect()` for a raw settings array (the |
| 629 | * `woocommerce_monokulo_settings` option), so `monokulo.php`'s admin |
| 630 | * notice can ask without building a gateway. A store that has never |
| 631 | * connected at all doesn't "need a reconnect"; it just isn't connected. |
| 632 | * |
| 633 | * @param mixed $settings The saved settings array, or anything else if |
| 634 | * nothing was ever saved. |
| 635 | * @return bool |
| 636 | */ |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 639 | return false; |
| 640 | } |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 646 | |
| 647 | /** |
| 648 | * Shows every WooCommerce manager, on every admin screen, that the |
| 649 | * store must reconnect before it can take Monero payments again. Hooked |
| 650 | * on `admin_notices` from `monokulo.php`. |
| 651 | */ |
| 652 | public static function render_reconnect_notice() { |
| 653 | if ( ! current_user_can( 'manage_woocommerce' ) ) { |
| 654 | return; |
| 655 | } |
| 656 | if ( ! self::settings_need_reconnect( get_option( 'woocommerce_monokulo_settings' ) ) ) { |
| 657 | return; |
| 658 | } |
| 659 | $settings_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=monokulo' ); |
| 660 | echo '<div class="notice notice-warning"><p><strong>' . |
| 661 | esc_html__( 'Monokulo: please reconnect your Monero wallet.', 'monokulo' ) . |
| 662 | '</strong> ' . |
| 663 | esc_html__( 'This store was connected by an older version of the Monokulo plugin, which can no longer create orders. Monero payments are hidden at checkout until you reconnect.', 'monokulo' ) . |
| 664 | ' <a href="' . esc_url( $settings_url ) . '">' . |
| 665 | esc_html__( 'Reconnect now', 'monokulo' ) . |
| 666 | '</a></p></div>'; |
| 667 | } |
| 668 | |
| 669 | /** |
| 670 | * WooCommerce calls this - and only this - when a customer places an |
| 671 | * order with this gateway selected. Checked directly against |
| 672 | * WooCommerce's own real source, not assumed from memory: |
| 673 | * |
| 674 | * - `WC_Payment_Gateway::process_payment( $order_id )`'s own doc comment |
| 675 | * (`includes/abstracts/abstract-wc-payment-gateway.php`) states the |
| 676 | * contract in full: "When implemented, this should return the success |
| 677 | * and redirect in an array", e.g. |
| 678 | * `array( 'result' => 'success', 'redirect' => $url )`. |
| 679 | * - `WC_Gateway_BACS::process_payment()` and `WC_Gateway_COD:: |
| 680 | * process_payment()` (`includes/gateways/{bacs,cod}/class-wc-gateway- |
| 681 | * {bacs,cod}.php`) are WooCommerce's own reference implementations of |
| 682 | * that contract - both return exactly |
| 683 | * `array( 'result' => 'success', 'redirect' => $this->get_return_url( $order ) )`. |
| 684 | * This gateway deliberately does **not** call `get_return_url()` |
| 685 | * (WooCommerce's own thank-you page) the way those two do - both of |
| 686 | * them consider the order paid the instant `process_payment()` runs, |
| 687 | * which is never true here. `redirect` only has to be a URL |
| 688 | * WooCommerce's checkout JS will send the browser to, not specifically |
| 689 | * the thank-you page - confirmed by reading `WC_Checkout:: |
| 690 | * process_order_payment()` (`includes/class-wc-checkout.php`) directly: |
| 691 | * it reads `$result['redirect']` verbatim, either via `wp_redirect()` |
| 692 | * (classic, non-AJAX submit) or as the `redirect` field of the JSON |
| 693 | * `wp_send_json( $result )` sends back (AJAX/Blocks checkout) - neither |
| 694 | * path constrains the URL's shape or origin. So `redirect` here is |
| 695 | * Monokulo's checkout page, `GET {endpoint}/pay/{pk}/orders/{order_id}`, |
| 696 | * off-site by design. |
| 697 | * - Failure is signaled by *throwing*, not by returning |
| 698 | * `array( 'result' => 'fail' )` - confirmed against `WC_Checkout:: |
| 699 | * process_checkout()` directly: it calls `process_order_payment()` |
| 700 | * (and therefore this method) from inside its own top-level |
| 701 | * `try { ... } catch ( Exception $e ) { wc_add_notice( $e->getMessage(), |
| 702 | * 'error' ); }` block, so a thrown `Exception` becomes exactly the |
| 703 | * customer-facing checkout error WooCommerce already knows how to |
| 704 | * render - no bundled gateway happens to demonstrate this path (BACS/ |
| 705 | * COD never fail), but it is the real, source-confirmed mechanism this |
| 706 | * method relies on for every failure branch below. |
| 707 | * |
| 708 | * Deliberately does **not** call `$order->payment_complete()` or change |
| 709 | * the order's status - the order WooCommerce just created is already |
| 710 | * `pending`/awaiting payment, and it should stay exactly that until a |
| 711 | * real payment is actually observed on-chain. Marking it paid here, |
| 712 | * before the customer has even reached Monokulo's checkout page, |
| 713 | * would be simply false. That transition is WBS 1.5.4's job (the webhook |
| 714 | * receiver + status mapping), not this step's - this method's only job |
| 715 | * is "hand off to Monokulo, and tell WooCommerce where to send the |
| 716 | * customer next." |
| 717 | * |
| 718 | * @param int $order_id Order ID. |
| 719 | * @return array{result: string, redirect: string} |
| 720 | * @throws Exception If the order can't be loaded, this gateway isn't |
| 721 | * configured, or Monokulo's order-creation call |
| 722 | * fails or returns something unexpected - see |
| 723 | * `WC_Checkout::process_checkout()`'s own catch block |
| 724 | * above for what WooCommerce does with it. |
| 725 | */ |
| 726 | public function process_payment( $order_id ) { |
| 727 | $order = wc_get_order( $order_id ); |
| 728 | if ( ! $order instanceof WC_Order ) { |
| 729 | throw new Exception( __( 'Could not load this order to start the Monero payment.', 'monokulo' ) ); |
| 730 | } |
| 731 | |
| 732 | $monokulo_order = $this->create_monokulo_order( $order ); |
| 733 | |
| 734 | // The one point in this gateway's whole flow that ever sees the |
| 735 | // mapping between a WC order and Monokulo's order_id - recorded |
| 736 | // now, while it's in hand. The webhook receiver needs exactly this |
| 737 | // lookup (an incoming delivery carries an order_id, not a WC order |
| 738 | // id) to know which order to update. |
| 739 | $order->update_meta_data( self::META_ORDER_ID, $monokulo_order['order_id'] ); |
| 740 | $order->add_order_note( |
| 741 | sprintf( |
| 742 | /* translators: %s: Monokulo order_id */ |
| 743 | __( 'Customer redirected to Monokulo checkout for order_id %s.', 'monokulo' ), |
| 744 | $monokulo_order['order_id'] |
| 745 | ) |
| 746 | ); |
| 747 | $order->save(); |
| 748 | |
| 749 | return array( |
| 750 | 'result' => 'success', |
| 751 | 'redirect' => $this->get_checkout_url( $monokulo_order['order_id'] ), |
| 752 | ); |
| 753 | } |
| 754 | |
| 755 | /** |
| 756 | * Creates the order on Monokulo: `POST {endpoint}/pay/{pk}/orders` |
| 757 | * (`crates/monokulo/src/http/pay.rs::create_order`) with |
| 758 | * `Authorization: Bearer {secret key}`, `Monokulo-Client: woocommerce/…` |
| 759 | * (so the order shows as the plugin's) and a JSON body of |
| 760 | * `{amount, currency, merchant_order_id}`. Monokulo prices the amount into |
| 761 | * XMR with its own exchange rate and returns |
| 762 | * `{order_id, address, xmr_amount_piconero, amount, currency, |
| 763 | * merchant_order_id, expires_at}`. |
| 764 | * |
| 765 | * Every failure throws a plain, customer-safe `Exception` (see |
| 766 | * `process_payment()` for why throwing is the contract); the detail - |
| 767 | * status code and Monokulo's own error message - is logged for the |
| 768 | * merchant via `self::log()`, never shown to the customer. |
| 769 | * |
| 770 | * @param WC_Order $order The order to create a Monokulo order for. |
| 771 | * @return array{order_id: string, address: string, xmr_amount_piconero: int, |
| 772 | * amount: string, currency: string, expires_at: int} |
| 773 | * @throws Exception See above. |
| 774 | */ |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 777 | // Reachable even though `is_available()` should normally have |
| 778 | // kept an unusable gateway off the checkout entirely - e.g. an |
| 779 | // already-rendered checkout page submitted after the connection |
| 780 | // changed in another tab. |
| 781 | throw new Exception( |
| 782 | __( 'Monero payments are not fully configured for this store yet. Please contact the store owner.', 'monokulo' ) |
| 783 | ); |
| 784 | } |
| 785 | |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 824 | $this->log( |
| 825 | sprintf( 'Order creation request to %s failed: %s', $request_url, $response->get_error_message() ), |
| 826 | 'error' |
| 827 | ); |
| 828 | throw new Exception( |
| 829 | __( 'Could not reach Monokulo to start this payment. Please try again shortly.', 'monokulo' ) |
| 830 | ); |
| 831 | } |
| 832 | |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 837 | $decoded_error = json_decode( $raw_body, true ); |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 839 | $this->log( |
| 840 | sprintf( |
| 841 | 'Order creation request to %s returned HTTP %d: %s%s', |
| 842 | $request_url, |
| 843 | $status_code, |
| 844 | $detail, |
| 845 | self::hint_for_status( $status_code ) |
| 846 | ), |
| 847 | 'error' |
| 848 | ); |
| 849 | throw new Exception( self::customer_message_for_status( $status_code ) ); |
| 850 | } |
| 851 | |
| 852 | $decoded = json_decode( $raw_body, true ); |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 854 | $this->log( |
| 855 | sprintf( 'Order creation response from %s was not the expected shape: %s', $request_url, $raw_body ), |
| 856 | 'error' |
| 857 | ); |
| 858 | throw new Exception( |
| 859 | __( 'Monokulo returned an unexpected response. Please contact the store.', 'monokulo' ) |
| 860 | ); |
| 861 | } |
| 862 | |
| 863 | return $decoded; |
| 864 | } |
| 865 | |
| 866 | /** |
| 867 | * The idempotency key for one WooCommerce order at one total: what |
| 868 | * makes a retried order creation return the order already made. |
| 869 | * |
| 870 | * @param int|string $order_id The WooCommerce order id. |
| 871 | * @param string $amount The amount sent, as formatted. |
| 872 | * @param string $currency The currency code. |
| 873 | * @return string |
| 874 | */ |
| 875 | public static function idempotency_key( $order_id, $amount, $currency ) { |
| 876 | return sprintf( 'wc:%s:%s:%s', $order_id, $amount, $currency ); |
| 877 | } |
| 878 | |
| 879 | /** |
| 880 | * The order total as the plain decimal string Monokulo parses: `.` as |
| 881 | * the decimal point, no thousands separator, whatever the site's locale. |
| 882 | * Fiat amounts carry two decimal places (Monokulo refuses more); an XMR |
| 883 | * amount keeps up to XMR's own twelve. |
| 884 | * |
| 885 | * @param string|float $total The order total. |
| 886 | * @param string $currency The order currency. |
| 887 | * @return string |
| 888 | */ |
| 889 | private static function format_amount( $total, $currency ) { |
| 890 | $decimals = 2; |
| 891 | if ( 'XMR' === strtoupper( $currency ) ) { |
| 892 | $decimals = function_exists( 'wc_get_price_decimals' ) ? min( 12, max( 2, (int) wc_get_price_decimals() ) ) : 12; |
| 893 | } |
| 894 | return number_format( (float) $total, $decimals, '.', '' ); |
| 895 | } |
| 896 | |
| 897 | /** |
| 898 | * What the customer sees when Monokulo refuses an order - kept vague on |
| 899 | * purpose (the logged detail is for the merchant). |
| 900 | * |
| 901 | * @param int $status_code Monokulo's HTTP status. |
| 902 | * @return string |
| 903 | */ |
| 904 | private static function customer_message_for_status( $status_code ) { |
| 905 | if ( 429 === $status_code ) { |
| 906 | return __( 'Monero payments are busy right now. Please wait a moment and try again.', 'monokulo' ); |
| 907 | } |
| 908 | if ( 401 === $status_code || 403 === $status_code ) { |
| 909 | return __( 'Monero payments are not available for this store right now. Please contact the store owner.', 'monokulo' ); |
| 910 | } |
| 911 | return __( 'Monokulo could not start this payment. Please contact the store or try again.', 'monokulo' ); |
| 912 | } |
| 913 | |
| 914 | /** |
| 915 | * A pointer for the merchant reading the log, for the statuses whose |
| 916 | * fix is on their side. |
| 917 | * |
| 918 | * @param int $status_code Monokulo's HTTP status. |
| 919 | * @return string |
| 920 | */ |
| 921 | private static function hint_for_status( $status_code ) { |
| 922 | switch ( $status_code ) { |
| 923 | case 401: |
| 924 | return ' (the store secret key was rejected - reconnect the plugin from its settings page)'; |
| 925 | case 403: |
| 926 | return ' (Monokulo refused the order - check the store\'s verified-domain settings on Monokulo)'; |
| 927 | case 404: |
| 928 | return ' (Monokulo does not know this store\'s public key - reconnect the plugin)'; |
| 929 | case 429: |
| 930 | return ' (rate limited - Monokulo is receiving too many orders from this store)'; |
| 931 | default: |
| 932 | return ''; |
| 933 | } |
| 934 | } |
| 935 | |
| 936 | /** |
| 937 | * `{endpoint}/pay/{pk}/orders` - Monokulo's order-creation route. |
| 938 | * `rtrim()` absorbs a trailing slash on the stored address. |
| 939 | * |
| 940 | * @return string |
| 941 | */ |
| 942 | private function get_orders_endpoint_url() { |
| 943 | return sprintf( |
| 944 | '%s/pay/%s/orders', |
| 945 | rtrim( $this->api_base_url, '/' ), |
| 946 | rawurlencode( $this->tenant_public_key ) |
| 947 | ); |
| 948 | } |
| 949 | |
| 950 | /** |
| 951 | * `{endpoint}/pay/{pk}/orders/{order_id}` - Monokulo's checkout page for |
| 952 | * the order, where the customer is sent to pay. `rawurlencode()` on |
| 953 | * `order_id` is cheap defence against its shape ever changing. |
| 954 | * |
| 955 | * @param string $order_id Monokulo's order id, from |
| 956 | * `create_monokulo_order()`'s response. |
| 957 | * @return string |
| 958 | */ |
| 959 | private function get_checkout_url( $order_id ) { |
| 960 | return sprintf( |
| 961 | '%s/pay/%s/orders/%s', |
| 962 | rtrim( $this->api_base_url, '/' ), |
| 963 | rawurlencode( $this->tenant_public_key ), |
| 964 | rawurlencode( $order_id ) |
| 965 | ); |
| 966 | } |
| 967 | |
| 968 | /** |
| 969 | * The control plane's own base URL, run through |
| 970 | * `monokulo_control_plane_base_url` - a filter, not just the bare |
| 971 | * constant, for two concrete reasons: it's what lets this plugin's own |
| 972 | * tests point `process_connect_return()`/`generate_monokulo_connect_html()` |
| 973 | * at a fake control plane (`pre_http_request` alone can't substitute for |
| 974 | * the *browser-facing* `GET /connect/...` URL a test never actually |
| 975 | * fetches, only asserts the shape of - so the base URL itself has to be |
| 976 | * swappable, not just the HTTP call), and it's the one real, sanctioned |
| 977 | * escape hatch for anyone running their own control plane instance |
| 978 | * (a self-hoster who wants the one-click flow against infrastructure |
| 979 | * they run themselves, rather than ours) without forking this file. |
| 980 | * |
| 981 | * @return string |
| 982 | */ |
| 983 | private function get_control_plane_base_url() { |
| 984 | return apply_filters( 'monokulo_control_plane_base_url', self::CONTROL_PLANE_BASE_URL ); |
| 985 | } |
| 986 | |
| 987 | /** |
| 988 | * The WordPress mechanism chosen for `return_url` - the URL the control |
| 989 | * plane's browser redirect (step 3 of `connect.rs`'s own module doc |
| 990 | * comment) has to land on, on *this* site, in a shape this plugin can |
| 991 | * recognize and handle. |
| 992 | * |
| 993 | * Reasoned from WordPress's own documented conventions for exactly this |
| 994 | * shape of problem ("an external service redirects the browser back into |
| 995 | * a plugin's own admin screen, plugin does some server-side work, then |
| 996 | * sends the browser on"), not invented fresh - three real WordPress |
| 997 | * mechanisms fit: |
| 998 | * |
| 999 | * 1. **A dedicated `admin-post.php?action=...` handler** (chosen here). |
| 1000 | * WordPress core's own documented pattern for "a link or button |
| 1001 | * triggers a server-side action, which redirects somewhere when |
| 1002 | * done" - `wp-admin/admin-post.php` fires `do_action( |
| 1003 | * "admin_post_{$_GET['action']}" )` for a logged-in user (the exact |
| 1004 | * case here - the merchant is coming back into a wp-admin session |
| 1005 | * they were already in when they clicked "Connect"), runs inside the |
| 1006 | * ordinary wp-admin bootstrap (so `current_user_can()`, `admin_url()`, |
| 1007 | * `wp_safe_redirect()` all just work with no extra setup), and needs |
| 1008 | * no new URL-routing surface beyond a plain `add_action()` call - the |
| 1009 | * same shape this class already uses for |
| 1010 | * `woocommerce_update_options_payment_gateways_{$this->id}`. |
| 1011 | * 2. **A query var checked on this gateway's own settings-page render.** |
| 1012 | * Rejected: it would mean the connect flow's actual credential-saving |
| 1013 | * side effect happens as an incidental part of *rendering a page* |
| 1014 | * (`generate_monokulo_connect_html()`/`admin_options()`), which is |
| 1015 | * the wrong place for a state-changing action to live - a page render |
| 1016 | * can be triggered more than once (a refresh, a prefetch) in ways a |
| 1017 | * dedicated action handler isn't, and mixing "display the form" with |
| 1018 | * "consume a single-use token" is exactly the kind of GET-with-side- |
| 1019 | * effects WordPress's own admin-notices/admin-post split exists to |
| 1020 | * avoid. |
| 1021 | * 3. **A REST API route** (`register_rest_route`). A legitimate |
| 1022 | * alternative real WooCommerce/WordPress extensions do use for this - |
| 1023 | * rejected only because it's strictly more machinery than this step |
| 1024 | * needs (a new namespace, permission callback, route registration) |
| 1025 | * for a URL that only ever has to be recognized by *this* site's own |
| 1026 | * logged-in browser session, which `admin-post.php` already handles |
| 1027 | * with a single `add_action()` call. |
| 1028 | * |
| 1029 | * This is also exactly the shape real "Connect" style WooCommerce |
| 1030 | * gateway plugins are documented to use for their own OAuth-return |
| 1031 | * handlers (Stripe's and PayPal's own WooCommerce extensions both route |
| 1032 | * their connect callbacks through an `admin-post.php` action rather than |
| 1033 | * a REST route or a settings-page query var) - reasoned here from |
| 1034 | * WordPress's own documented `admin-post.php` mechanism directly, per |
| 1035 | * this step's own brief, since this environment has no way to fetch |
| 1036 | * their source live to confirm byte-for-byte. |
| 1037 | * |
| 1038 | * @return string |
| 1039 | */ |
| 1040 | private function get_connect_return_url() { |
| 1041 | return admin_url( 'admin-post.php?action=' . self::CONNECT_RETURN_ACTION ); |
| 1042 | } |
| 1043 | |
| 1044 | /** |
| 1045 | * The transient key this gateway's own single-use connect nonce is |
| 1046 | * stored under between `generate_monokulo_connect_html()` (which mints |
| 1047 | * it) and `process_connect_return()` (which consumes it exactly once). |
| 1048 | * A WordPress transient, not a plain `option`: it's meant to expire on |
| 1049 | * its own (`10 * MINUTE_IN_SECONDS`, chosen to match `connect.rs`'s own |
| 1050 | * `CONNECT_TOKEN_TTL_SECONDS` exactly - the nonce only ever needs to |
| 1051 | * survive the same round trip the connect token itself does, so there's |
| 1052 | * no reason for it to outlive that token), which is precisely what |
| 1053 | * `set_transient()`'s own expiry argument is for and a plain `option` |
| 1054 | * has no built-in equivalent of. |
| 1055 | * |
| 1056 | * @return string |
| 1057 | */ |
| 1058 | private function connect_nonce_transient_key() { |
| 1059 | return 'monokulo_connect_nonce_' . $this->id; |
| 1060 | } |
| 1061 | |
| 1062 | /** |
| 1063 | * Builds the real `GET {control_plane_base_url}/connect/{platform}?...` |
| 1064 | * URL (step 1 of `connect.rs`'s own module doc comment) this gateway |
| 1065 | * sends the merchant's browser to. |
| 1066 | * |
| 1067 | * @param string $nonce A freshly generated, single-use nonce - the |
| 1068 | * caller is responsible for also having stored it |
| 1069 | * (`connect_nonce_transient_key()`) before handing |
| 1070 | * this URL out, so `process_connect_return()` has |
| 1071 | * something real to check the redirect-back |
| 1072 | * `nonce` against. |
| 1073 | * @return string |
| 1074 | */ |
| 1075 | private function build_connect_start_url( $nonce ) { |
| 1076 | return sprintf( |
| 1077 | '%s/connect/%s?site_url=%s&return_url=%s&nonce=%s', |
| 1078 | rtrim( $this->get_control_plane_base_url(), '/' ), |
| 1079 | rawurlencode( self::CONNECT_PLATFORM ), |
| 1080 | rawurlencode( home_url( '/' ) ), |
| 1081 | rawurlencode( $this->get_connect_return_url() ), |
| 1082 | rawurlencode( $nonce ) |
| 1083 | ); |
| 1084 | } |
| 1085 | |
| 1086 | /** |
| 1087 | * Renders the "Connect your Monero wallet" settings-screen field - a |
| 1088 | * genuinely custom `WC_Settings_API` field type (`'monokulo_connect'`, |
| 1089 | * dispatched to this method by `generate_settings_html()`'s own |
| 1090 | * `method_exists( $this, 'generate_' . $type . '_html' )` check, read |
| 1091 | * directly against `abstract-wc-settings-api.php` before writing this), |
| 1092 | * not a reuse of the built-in `'title'` type: `generate_title_html()` |
| 1093 | * renders fixed, static markup from `$data['title']`/`$data['description']` |
| 1094 | * alone, but this field has to build a real `<a href>` fresh on every |
| 1095 | * render (a new nonce, a new transient, a URL that depends on whether a |
| 1096 | * wallet is already connected) - none of which a static type could do. |
| 1097 | * |
| 1098 | * Mints a fresh nonce **on every render of this field**, deliberately |
| 1099 | * not in the constructor (which every gateway instantiation on every |
| 1100 | * front-end/admin request also runs, via `WC_Payment_Gateways::init()` |
| 1101 | * on `woocommerce_init` - minting and overwriting the one live nonce on |
| 1102 | * every unrelated page load would routinely invalidate an |
| 1103 | * already-in-flight connect attempt before the merchant even got back). |
| 1104 | * This method only ever runs when this gateway's own settings screen is |
| 1105 | * actually being rendered (`WC_Settings_Page::output()` calling |
| 1106 | * `$gateway->admin_options()` for the matching `section`), which is |
| 1107 | * exactly the point a fresh nonce is actually needed. |
| 1108 | * |
| 1109 | * @param string $key Field key (`'connect'`). |
| 1110 | * @param array $data Field data from `init_form_fields()`. |
| 1111 | * @return string |
| 1112 | */ |
| 1113 | public function generate_monokulo_connect_html( $key, $data ) { |
| 1114 | $field_key = $this->get_field_key( $key ); |
| 1115 | |
| 1116 | // Same default-filling `generate_text_html()` itself does (read |
| 1117 | // directly) before ever touching `$data['desc_tip']`/ |
| 1118 | // `$data['description']` - `init_form_fields()`'s own `'connect'` |
| 1119 | // entry only sets `type`/`description`, and `get_description_html()` |
| 1120 | // below indexes `$data['desc_tip']` unconditionally, which would be |
| 1121 | // an undefined-array-key warning on every real render without this. |
| 1122 | $data = wp_parse_args( |
| 1123 | $data, |
| 1124 | array( |
| 1125 | 'desc_tip' => false, |
| 1126 | 'description' => '', |
| 1127 | ) |
| 1128 | ); |
| 1129 | |
| 1130 | $nonce = bin2hex( random_bytes( 16 ) ); |
| 1131 | set_transient( $this->connect_nonce_transient_key(), $nonce, 10 * MINUTE_IN_SECONDS ); |
| 1132 | $connect_url = $this->build_connect_start_url( $nonce ); |
| 1133 | |
| 1134 | ob_start(); |
| 1135 | ?> |
| 1136 | <tr valign="top"> |
| 1137 | <th scope="row" class="titledesc"> |
| 1138 | <label for="<?php echo esc_attr( $field_key ); ?>"><?php esc_html_e( 'Connect your Monero wallet', 'monokulo' ); ?></label> |
| 1139 | </th> |
| 1140 | <td class="forminp"> |
| 1141 | <?php if ( $this->needs_reconnect() ) : ?> |
| 1142 | <p><strong><?php esc_html_e( 'Reconnect needed: this store was connected by an older plugin version, so Monero payments are hidden at checkout until you reconnect.', 'monokulo' ); ?></strong></p> |
| 1143 | <?php elseif ( $this->has_credentials() ) : ?> |
| 1144 | <p> |
| 1145 | <?php esc_html_e( 'Connected as', 'monokulo' ); ?> |
| 1146 | <code><?php echo esc_html( $this->tenant_public_key ); ?></code> |
| 1147 | </p> |
| 1148 | <?php endif; ?> |
| 1149 | <a href="<?php echo esc_url( $connect_url ); ?>" id="<?php echo esc_attr( $field_key ); ?>" class="button button-primary"> |
| 1150 | <?php |
| 1151 | echo $this->has_credentials() |
| 1152 | ? esc_html__( 'Reconnect your Monero wallet', 'monokulo' ) |
| 1153 | : esc_html__( 'Connect your Monero wallet', 'monokulo' ); |
| 1154 | ?> |
| 1155 | </a> |
| 1156 | <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> |
| 1157 | </td> |
| 1158 | </tr> |
| 1159 | <?php |
| 1160 | return ob_get_clean(); |
| 1161 | } |
| 1162 | |
| 1163 | /** |
| 1164 | * `'connect'`'s own `type` (`monokulo_connect`) has no real form input |
| 1165 | * for `process_admin_options()` to read back on save - it only ever |
| 1166 | * renders a link. Without this method, `get_field_value()` would fall |
| 1167 | * through to `validate_text_field()` on a `null` POST value (checked |
| 1168 | * directly, `abstract-wc-settings-api.php::get_field_value()`), which is |
| 1169 | * harmless but pointless (nothing should ever read a |
| 1170 | * `monokulo_connect` settings key) and, on PHP 8.1+, a `null`-to-string |
| 1171 | * coercion warning waiting to happen the moment that fallback path's own |
| 1172 | * implementation changes. Returning `''` directly is the explicit, |
| 1173 | * intentional no-op this field type actually needs - the *real* save |
| 1174 | * path for this field's effect is `process_connect_return()` below, not |
| 1175 | * WooCommerce's ordinary settings-form POST at all. |
| 1176 | * |
| 1177 | * @param string $key Field key. |
| 1178 | * @param mixed $value Posted value (always unused here). |
| 1179 | * @return string Always `''`. |
| 1180 | */ |
| 1181 | public function validate_monokulo_connect_field( $key, $value ) { |
| 1182 | return ''; |
| 1183 | } |
| 1184 | |
| 1185 | /** |
| 1186 | * The real `admin_post_{action}` handler WordPress core dispatches to |
| 1187 | * (see `get_connect_return_url()`'s own doc comment for why |
| 1188 | * `admin-post.php` is the mechanism at all). Deliberately just two |
| 1189 | * lines: everything that can actually be tested without WordPress's own |
| 1190 | * `exit` getting in the way lives in `process_connect_return()` below, |
| 1191 | * which this method calls and then acts on. `wp_safe_redirect()` + |
| 1192 | * `exit` immediately after is WordPress core's own documented way to end |
| 1193 | * an `admin-post.php` handler - `exit` specifically because a handler |
| 1194 | * that fell through to WordPress's own further output after redirecting |
| 1195 | * would corrupt the response, not because of anything specific to this |
| 1196 | * plugin. |
| 1197 | * |
| 1198 | * The `current_user_can( 'manage_woocommerce' )` guard exists because |
| 1199 | * `admin_post_{action}` (unlike this gateway's own settings screen, |
| 1200 | * which WooCommerce's own menu registration already restricts to that |
| 1201 | * capability) fires for **any** logged-in user, regardless of role - |
| 1202 | * defense in depth on top of the nonce check in |
| 1203 | * `process_connect_return()`, which is this leg's real CSRF defense |
| 1204 | * (see that method's own doc comment), against a logged-in |
| 1205 | * low-privilege account (e.g. a customer with a WordPress login) hitting |
| 1206 | * this URL directly rather than a genuine admin who initiated the flow. |
| 1207 | */ |
| 1208 | public function handle_connect_return() { |
| 1209 | if ( ! current_user_can( 'manage_woocommerce' ) ) { |
| 1210 | wp_die( esc_html__( 'You do not have permission to do this.', 'monokulo' ), '', array( 'response' => 403 ) ); |
| 1211 | } |
| 1212 | |
| 1213 | $redirect_url = $this->process_connect_return( wp_unslash( $_GET ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 1214 | wp_safe_redirect( $redirect_url ); |
| 1215 | exit; |
| 1216 | } |
| 1217 | |
| 1218 | /** |
| 1219 | * The real logic behind steps 3-4 of `connect.rs`'s own module doc |
| 1220 | * comment: the merchant's browser has just landed back on this site |
| 1221 | * carrying `token`/`nonce` query params, and this method has to decide |
| 1222 | * whether to trust them, redeem the token for real credentials, and (on |
| 1223 | * success) save them and enable the gateway - the WBS's own stated |
| 1224 | * outcome for this step ("the gateway becomes enabled"), not left for |
| 1225 | * the merchant to separately toggle afterward. |
| 1226 | * |
| 1227 | * **Split out of `handle_connect_return()` above specifically so this is |
| 1228 | * directly unit-testable**: no `wp_safe_redirect()`/`exit` in here, just |
| 1229 | * a pure(-ish) function of "the query args a request carried" -> |
| 1230 | * "the URL to send the browser to next" (plus the real side effect of |
| 1231 | * saving settings on success) - `tests/ConnectFlowTest.php` calls this |
| 1232 | * method directly, the same way `tests/ProcessPaymentTest.php` calls |
| 1233 | * `process_payment()` directly rather than driving a real HTTP request |
| 1234 | * through WordPress's own router. |
| 1235 | * |
| 1236 | * **The nonce check - the actual CSRF defense this whole leg has**: |
| 1237 | * `return_url` is a public, guessable URL (it's this exact method's own |
| 1238 | * `admin-post.php` address); anyone could, in principle, cause a |
| 1239 | * logged-in merchant's browser to `GET` it with an arbitrary `token`. |
| 1240 | * What they can't do is know the *nonce* this plugin itself generated |
| 1241 | * and stored server-side in `connect_nonce_transient_key()` at the |
| 1242 | * moment the *real* connect attempt started - so a mismatched, missing, |
| 1243 | * or already-consumed (the transient is deleted immediately, win or |
| 1244 | * lose - single use, not single check) nonce means this request did not |
| 1245 | * originate from a connect flow this plugin itself kicked off, and is |
| 1246 | * rejected before ever calling `/finish` at all, exactly per this |
| 1247 | * step's own brief ("nonce mismatch must be rejected, not silently |
| 1248 | * accepted"). `hash_equals()`, not `===`, for the same constant-time |
| 1249 | * reasoning `docs/WOOCOMMERCE_WBS.md` already calls out for WBS 1.5.4's |
| 1250 | * webhook signature check - a nonce is shorter-lived and lower-value |
| 1251 | * than an HMAC key, but there's no real cost to using the safe |
| 1252 | * comparison here too, and no argument for `===` being the least bit |
| 1253 | * simpler. |
| 1254 | * |
| 1255 | * **On a failed `/finish` call**: no setting is touched at all - an |
| 1256 | * already-connected merchant whose *reconnect* attempt fails (expired |
| 1257 | * token, control plane hiccup, etc.) keeps whatever credentials they had |
| 1258 | * before, rather than this method clobbering good settings with a |
| 1259 | * failure. |
| 1260 | * |
| 1261 | * @param array $query_args The request's GET query parameters (`token`, |
| 1262 | * `nonce`), already `wp_unslash()`ed by the |
| 1263 | * caller - accepted as a plain array, not read |
| 1264 | * from `$_GET` directly, purely so a test can |
| 1265 | * hand this method an arbitrary array without |
| 1266 | * needing to fake superglobals. |
| 1267 | * @return string The URL to send the merchant's browser to next - always |
| 1268 | * this gateway's own settings screen, with a |
| 1269 | * `monokulo_connected` or |
| 1270 | * `monokulo_connect_error` flag appended for |
| 1271 | * `maybe_render_connect_notice()` below to read. |
| 1272 | */ |
| 1273 | public function process_connect_return( array $query_args ) { |
| 1274 | $token = isset( $query_args['token'] ) ? sanitize_text_field( $query_args['token'] ) : ''; |
| 1275 | $nonce = isset( $query_args['nonce'] ) ? sanitize_text_field( $query_args['nonce'] ) : ''; |
| 1276 | |
| 1277 | $stored_nonce = get_transient( $this->connect_nonce_transient_key() ); |
| 1278 | // Single-use regardless of outcome, per this method's own doc |
| 1279 | // comment - a stale or already-checked nonce must never be |
| 1280 | // re-checkable. |
| 1281 | delete_transient( $this->connect_nonce_transient_key() ); |
| 1282 | |
| 1283 | if ( '' === $token || '' === $nonce || false === $stored_nonce || ! hash_equals( (string) $stored_nonce, $nonce ) ) { |
| 1284 | $this->log( 'Connect return rejected: missing token, or the nonce did not match what this site generated.', 'warning' ); |
| 1285 | return $this->build_settings_url( array( 'monokulo_connect_error' => 'nonce' ) ); |
| 1286 | } |
| 1287 | |
| 1288 | $finish_status = 0; |
| 1289 | $finish = $this->call_connect_finish( $token, $finish_status ); |
| 1290 | if ( null === $finish ) { |
| 1291 | // 503: Monokulo isn't ready to connect plugins (no public address |
| 1292 | // yet) - worth its own message, since retrying later can work. |
| 1293 | $reason = 503 === $finish_status ? 'unavailable' : 'finish'; |
| 1294 | return $this->build_settings_url( array( 'monokulo_connect_error' => $reason ) ); |
| 1295 | } |
| 1296 | |
| 1297 | $this->update_option( 'endpoint', $finish['endpoint'] ); |
| 1298 | $this->update_option( 'public_key', $finish['public_key'] ); |
| 1299 | $this->update_option( 'secret_token', $finish['secret_token'] ); |
| 1300 | if ( ! empty( $finish['webhook_signing_secret'] ) ) { |
| 1301 | // Deliberately not a declared `form_fields` entry - see |
| 1302 | // `init_form_fields()`'s own comment on why. `update_option()` |
| 1303 | // (checked directly, `WC_Settings_API::update_option()`) writes |
| 1304 | // into `$this->settings[$key]` by plain string key regardless of |
| 1305 | // whether that key was ever declared, so this is a real, |
| 1306 | // persisted write, not a no-op. |
| 1307 | $this->update_option( 'webhook_signing_secret', $finish['webhook_signing_secret'] ); |
| 1308 | } |
| 1309 | // The WBS's own stated outcome for this step: connecting a wallet |
| 1310 | // enables the gateway outright, not just fills in fields for the |
| 1311 | // merchant to separately flip a switch on afterward. |
| 1312 | $this->update_option( 'enabled', 'yes' ); |
| 1313 | // Marks this connection as made by this plugin version, whose |
| 1314 | // `endpoint` is Monokulo's own address - see `CONNECTION_VERSION`. |
| 1315 | $this->update_option( 'connection_version', self::CONNECTION_VERSION ); |
| 1316 | |
| 1317 | return $this->build_settings_url( array( 'monokulo_connected' => '1' ) ); |
| 1318 | } |
| 1319 | |
| 1320 | /** |
| 1321 | * `POST {control_plane_base_url}/connect/{platform}/finish` (step 4-5 of |
| 1322 | * `connect.rs`'s own module doc comment / its `finish` handler, read |
| 1323 | * directly): redeems the single-use connect token for real credentials. |
| 1324 | * |
| 1325 | * **`webhook_url` is sent, deliberately, even though this plugin has no |
| 1326 | * webhook-receiving route yet** (that's WBS 1.5.4's job entirely - see |
| 1327 | * `get_webhook_receiver_url()`'s own doc comment). Chosen over omitting |
| 1328 | * it, for a reason parallel to why `$secret_token` is stored now despite |
| 1329 | * nothing reading it yet: `mock-woocommerce::run_connect_flow` - "1.4.2's |
| 1330 | * already-proven logic" this step's own brief explicitly says to mirror |
| 1331 | * - always registers a webhook as part of the very same connect flow |
| 1332 | * (confirmed directly, `mock-woocommerce/src/lib.rs::run_connect_flow_with`, |
| 1333 | * not assumed); there is no "connect without a webhook" variant of the |
| 1334 | * already-proven protocol to mirror instead. Registering now, pointed at |
| 1335 | * a URL nothing yet answers, means WBS 1.5.4 only has to *build the |
| 1336 | * receiver* - not also force every merchant who already connected under |
| 1337 | * 1.5.3 back through the whole browser-redirect flow a second time just |
| 1338 | * to register a webhook that could have been registered the first time |
| 1339 | * for free. This is safe to do before 1.5.4 exists: checked directly |
| 1340 | * against `src/http/admin.rs::create_webhook` - registration only |
| 1341 | * validates the URL's scheme (`http`/`https`), never reachability (SSRF |
| 1342 | * validation is explicitly deferred to delivery time, per that |
| 1343 | * handler's own comment), so a webhook pointed at a route that |
| 1344 | * doesn't respond yet fails to *register* for. Deliveries against it |
| 1345 | * will simply fail (and retry, per the engine's own delivery-worker |
| 1346 | * policy) until 1.5.4 lands - an acceptable, self-healing gap for a |
| 1347 | * feature whose receiver is being built in the very next step of the |
| 1348 | * same project, not a real production outage window. |
| 1349 | * |
| 1350 | * Returns `null` on any failure (transport error, non-200, or a response |
| 1351 | * missing any of the three fields this plugin actually needs) - this |
| 1352 | * method never throws, unlike `create_monokulo_order()`: this isn't inside |
| 1353 | * WooCommerce's own checkout `try`/`catch`, so there's no framework |
| 1354 | * mechanism here to catch an `Exception` and turn it into a user-facing |
| 1355 | * message; the caller (`process_connect_return()`) is responsible for |
| 1356 | * turning a `null` into the right redirect + notice instead. |
| 1357 | * |
| 1358 | * @param string $token The single-use connect token from the |
| 1359 | * `return_url` redirect's own `token` query |
| 1360 | * param. |
| 1361 | * @param int $status_code Set to the HTTP status `/finish` answered |
| 1362 | * with (0 on a transport failure), so the |
| 1363 | * caller can tell "not ready yet" (503) apart. |
| 1364 | * @return array{public_key: string, secret_token: string, endpoint: string, |
| 1365 | * webhook_signing_secret?: string}|null |
| 1366 | */ |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1394 | $this->log( sprintf( 'Connect finish request to %s failed: %s', $url, $response->get_error_message() ), 'error' ); |
| 1395 | return null; |
| 1396 | } |
| 1397 | |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1408 | $this->log( sprintf( 'Connect finish request to %s returned HTTP %d: %s', $url, $status_code, $raw_body ), 'error' ); |
| 1409 | return null; |
| 1410 | } |
| 1411 | |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1414 | $this->log( sprintf( 'Connect finish response from %s was not the expected shape: %s', $url, $raw_body ), 'error' ); |
| 1415 | return null; |
| 1416 | } |
| 1417 | |
| 1418 | return $decoded; |
| 1419 | } |
| 1420 | |
| 1421 | /** |
| 1422 | * The webhook URL this plugin's own (not-yet-built, WBS 1.5.4) receiver |
| 1423 | * will eventually answer on - `WC()->api_request_url( $this->id )`, |
| 1424 | * WooCommerce's own real, documented mechanism for a plugin's |
| 1425 | * `woocommerce_api_{id}` endpoint (confirmed directly, |
| 1426 | * `class-woocommerce.php::api_request_url()`, before relying on it - it |
| 1427 | * resolves to `{site}/wc-api/{id}/` with pretty permalinks or |
| 1428 | * `{site}/?wc-api={id}` without, never anything this plugin has to |
| 1429 | * construct by hand). Used only by `call_connect_finish()` above - see |
| 1430 | * that method's own doc comment for why registering it now, before |
| 1431 | * 1.5.4 builds anything to answer it, is deliberate and safe. |
| 1432 | * |
| 1433 | * @return string |
| 1434 | */ |
| 1435 | private function get_webhook_receiver_url() { |
| 1436 | return WC()->api_request_url( $this->id ); |
| 1437 | } |
| 1438 | |
| 1439 | /** |
| 1440 | * The real `woocommerce_api_{id}` hook target (see the constructor's own |
| 1441 | * comment on why this specific mechanism, checked directly against |
| 1442 | * WooCommerce's installed source) - WordPress core dispatches to this |
| 1443 | * for every incoming delivery to `get_webhook_receiver_url()`'s own URL. |
| 1444 | * |
| 1445 | * Kept to the same "two lines, all real logic elsewhere" shape |
| 1446 | * `handle_connect_return()` above already established, for the identical |
| 1447 | * reason: `process_webhook_request()` below is directly unit-testable |
| 1448 | * (no PHP superglobals, no `exit`), which this thin wrapper is not - see |
| 1449 | * `tests/WebhookReceiverTest.php`'s own class doc comment for how it |
| 1450 | * drives this hook's real logic without going through an actual HTTP |
| 1451 | * request. |
| 1452 | * |
| 1453 | * Reads the request body via `file_get_contents( 'php://input' )` - |
| 1454 | * **never** `$_POST` - because the signature this method hands to |
| 1455 | * `process_webhook_request()` has to be verified against the *exact raw |
| 1456 | * bytes* the engine signed (`shared/src/webhook_sign.rs::sign_payload` |
| 1457 | * hashes `payload: &[u8]` as sent, read directly). `$_POST` doesn't even |
| 1458 | * apply here (the engine sends a raw JSON body, not |
| 1459 | * `application/x-www-form-urlencoded`/multipart form fields, so PHP |
| 1460 | * never populates `$_POST` for this request at all) - but even if it |
| 1461 | * did, going through it would mean verifying a signature against a |
| 1462 | * *re-serialized* body PHP itself reconstructed, which can differ from |
| 1463 | * the original byte-for-byte (key order, whitespace, numeric formatting) |
| 1464 | * even when it decodes to the same logical JSON - exactly the "even |
| 1465 | * whitespace-identical-looking JSON can byte-differ after a decode/ |
| 1466 | * re-encode round trip" failure mode this step's own brief calls out by |
| 1467 | * name. `php://input` is the one source that can't have been touched by |
| 1468 | * anything in between. |
| 1469 | * |
| 1470 | * The signature header is read from `$_SERVER` (see |
| 1471 | * `WEBHOOK_SIGNATURE_SERVER_KEY`'s own doc comment for the |
| 1472 | * `X-Monokulo-Signature` -> `HTTP_X_MONOKULO_SIGNATURE` transform) and |
| 1473 | * `wp_unslash()`-ed - the same treatment `handle_connect_return()` above |
| 1474 | * already gives `$_GET`, for the identical reason: WordPress's own |
| 1475 | * `wp_magic_quotes()` (`wp-includes/load.php`, read directly) backslash- |
| 1476 | * escapes `$_SERVER` exactly like `$_GET`/`$_POST`/`$_COOKIE`, not just |
| 1477 | * the superglobals an HTML form would populate. |
| 1478 | * |
| 1479 | * `status_header()`, not `wp_die()`: unlike `handle_connect_return()`'s |
| 1480 | * `wp_die()` (whose HTML error page is for a *human* who followed a bad |
| 1481 | * link), the only consumer of this response is the engine's own |
| 1482 | * delivery worker, which reads nothing but the HTTP status code - |
| 1483 | * `attempt_delivery()` in `src/webhook_delivery.rs` (read directly) |
| 1484 | * decides `delivered: status.is_success()` and otherwise reschedules a |
| 1485 | * retry with backoff, never inspecting the body at all. A bare status |
| 1486 | * code is therefore the entire real contract this endpoint has to |
| 1487 | * honor; `wp_die()`'s HTML-page machinery would be pure overhead no |
| 1488 | * caller of this endpoint ever looks at. |
| 1489 | */ |
| 1490 | public function handle_webhook() { |
| 1491 | $raw_body = (string) file_get_contents( 'php://input' ); |
| 1492 | $signature = isset( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) |
| 1493 | ? (string) wp_unslash( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash |
| 1494 | : ''; |
| 1495 | // The engine sends the delivery attempt's trace; the lines this |
| 1496 | // webhook logs join it. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1498 | |
| 1499 | status_header( $this->process_webhook_request( $raw_body, $signature ) ); |
| 1500 | exit; |
| 1501 | } |
| 1502 | |
| 1503 | /** |
| 1504 | * The real logic behind an incoming webhook delivery: verify, decode, |
| 1505 | * find the order, dedupe, apply. Split out of `handle_webhook()` above |
| 1506 | * for the same reason `process_connect_return()` is split out of |
| 1507 | * `handle_connect_return()` - directly testable, no request |
| 1508 | * superglobals or `exit` involved, a pure(-ish) function of "the raw |
| 1509 | * body and header this request carried" -> "the HTTP status code to |
| 1510 | * respond with" (plus the real side effect, on success, of updating the |
| 1511 | * matched order). |
| 1512 | * |
| 1513 | * **The four response codes, and why each one specifically** (per this |
| 1514 | * step's own brief: "decide the right status code... and document why"): |
| 1515 | * |
| 1516 | * - **200**: signature verified, payload well-formed, a matching order |
| 1517 | * was found, and the event was applied (or was already applied before |
| 1518 | * - see `event_already_applied()`'s own doc comment for why a |
| 1519 | * duplicate delivery is *also* a 200, not some other code). This is |
| 1520 | * the only status `run_delivery_tick` in `src/webhook_delivery.rs` |
| 1521 | * treats as `delivered: true` - anything else means the engine's |
| 1522 | * delivery worker will retry with backoff (`backoff_seconds()`, read |
| 1523 | * directly) until `max_attempts` is reached, per that file's own doc |
| 1524 | * comment on `run_delivery_tick`. |
| 1525 | * - **401 Unauthorized**: signature missing or invalid. Mirrors this |
| 1526 | * *engine's own* convention for exactly this situation - `ApiError:: |
| 1527 | * Unauthorized => (StatusCode::UNAUTHORIZED, ...)` in `src/http/ |
| 1528 | * mod.rs`, read directly - rather than inventing a different |
| 1529 | * convention on the PHP side for what is, semantically, the identical |
| 1530 | * fact ("this request did not prove it knows the shared secret"). |
| 1531 | * **No order lookup or mutation ever happens before this check passes** |
| 1532 | * - the whole point of verifying first is that everything downstream |
| 1533 | * can trust `order_id` came from the engine, not from anyone who |
| 1534 | * found this URL. |
| 1535 | * - **400 Bad Request**: the signature is valid but the body isn't the |
| 1536 | * envelope shape `enqueue_webhook_event()` in `src/scanner.rs` always |
| 1537 | * produces (`event`/`event_id`/`order_id`, read directly - every |
| 1538 | * event this engine ever sends has all three). A signed-but-malformed |
| 1539 | * body is a client error on the sender's side, not a "we don't |
| 1540 | * recognize this order" case (404) or an auth failure (401) - 400 is |
| 1541 | * the one of the three that actually means "your request, not our |
| 1542 | * data, is the problem," matching ordinary REST convention. |
| 1543 | * - **404 Not Found**: a well-formed, correctly-signed event for a |
| 1544 | * `order_id` with no matching order *on this WordPress site*. |
| 1545 | * Mirrors the engine's own `ApiError::NotFound => StatusCode:: |
| 1546 | * NOT_FOUND` convention for "the referenced resource doesn't exist |
| 1547 | * here" (`src/http/mod.rs`, same file as above) - chosen deliberately |
| 1548 | * over a 2xx "swallow it silently" response, because an unknown |
| 1549 | * `order_id` reaching a *correctly-signed* request (it already |
| 1550 | * passed the 401 check, so this webhook's `signing_secret` really is |
| 1551 | * this site's) is a real operational fact worth an operator noticing |
| 1552 | * in logs - a stale webhook registration surviving a database reset, |
| 1553 | * or (in principle) a site's webhook secret having leaked - not |
| 1554 | * something to hide by pretending success. Also deliberately not |
| 1555 | * treated as a reason to retry forever: since `process_payment()` |
| 1556 | * always creates the engine-side order and records its `order_id` |
| 1557 | * (`META_ORDER_ID`) *before* that order can possibly generate any |
| 1558 | * webhook event at all, "the order doesn't exist yet, try again |
| 1559 | * later" is not a real race this endpoint has to accommodate - an |
| 1560 | * unknown `order_id` here means "never will," not "not yet." |
| 1561 | * |
| 1562 | * @param string $raw_body The exact raw request body bytes, unmodified. |
| 1563 | * @param string $signature The `X-Monokulo-Signature` header value, or |
| 1564 | * `''` if the header was absent. |
| 1565 | * @return int The HTTP status code to respond with. |
| 1566 | */ |
| 1567 | public function process_webhook_request( $raw_body, $signature ) { |
| 1568 | if ( ! $this->verify_webhook_signature( $raw_body, $signature ) ) { |
| 1569 | $this->log( 'Webhook rejected: missing or invalid X-Monokulo-Signature.', 'warning' ); |
| 1570 | return 401; |
| 1571 | } |
| 1572 | |
| 1573 | $event = json_decode( $raw_body, true ); |
| 1574 | if ( ! is_array( $event ) |
| 1575 | || empty( $event['event'] ) || ! is_string( $event['event'] ) |
| 1576 | || empty( $event['event_id'] ) || ! is_string( $event['event_id'] ) |
| 1577 | || empty( $event['order_id'] ) || ! is_string( $event['order_id'] ) |
| 1578 | ) { |
| 1579 | $this->log( sprintf( 'Webhook rejected: correctly signed but not the expected envelope shape: %s', $raw_body ), 'error' ); |
| 1580 | return 400; |
| 1581 | } |
| 1582 | |
| 1583 | $order = $this->find_order_by_order_id( $event['order_id'] ); |
| 1584 | if ( ! $order instanceof WC_Order ) { |
| 1585 | $this->log( |
| 1586 | sprintf( 'Webhook for order_id %s (event %s) has no matching order on this site.', $event['order_id'], $event['event_id'] ), |
| 1587 | 'warning' |
| 1588 | ); |
| 1589 | return 404; |
| 1590 | } |
| 1591 | |
| 1592 | if ( $this->event_already_applied( $order, $event['event_id'] ) ) { |
| 1593 | // Already handled - see event_already_applied()'s own doc |
| 1594 | // comment for why this is still a 200, not a rejection. |
| 1595 | return 200; |
| 1596 | } |
| 1597 | |
| 1598 | $this->apply_webhook_event( $order, $event ); |
| 1599 | $this->mark_event_applied( $order, $event['event_id'] ); |
| 1600 | $order->save(); |
| 1601 | |
| 1602 | return 200; |
| 1603 | } |
| 1604 | |
| 1605 | /** |
| 1606 | * How far a delivery's signed timestamp may be from this site's clock, |
| 1607 | * either way, for it to be accepted: the same five minutes as |
| 1608 | * `SIGNATURE_TOLERANCE_SECS` in `shared/src/webhook_sign.rs`. |
| 1609 | */ |
| 1610 | const SIGNATURE_TOLERANCE_SECS = 300; |
| 1611 | |
| 1612 | /** |
| 1613 | * Verifies an `X-Monokulo-Signature` header, `t=<unix time>,v1=<hex>`: |
| 1614 | * the time must be within `SIGNATURE_TOLERANCE_SECS` of now, and the |
| 1615 | * tag must be HMAC-SHA256, keyed by this gateway's stored |
| 1616 | * `webhook_signing_secret`, of `"<time>.<raw body>"`. The time is signed |
| 1617 | * with the body, so a captured delivery can't be replayed later. |
| 1618 | * |
| 1619 | * **`hash_equals()`, never `===`** - this step's own brief states the |
| 1620 | * requirement explicitly, and the reasoning is worth restating here, in |
| 1621 | * this codebase's own words, rather than only citing the rule: PHP's |
| 1622 | * `===` string comparison (like Rust's plain `String`/`&str` equality) |
| 1623 | * short-circuits at the first differing byte, so the *time* a rejection |
| 1624 | * takes leaks *how many leading bytes were already correct* - the |
| 1625 | * textbook byte-at-a-time forgery oracle. `shared/src/webhook_sign.rs:: |
| 1626 | * verify_signature`'s own doc comment (read directly) states this exact |
| 1627 | * concern for the Rust side, which uses `Mac::verify_slice` (backed by |
| 1628 | * `subtle`'s constant-time `ct_eq`) for the identical reason; `hash_ |
| 1629 | * equals()` is PHP's own standard-library equivalent - a comparison |
| 1630 | * whose running time does not depend on where or whether the two inputs |
| 1631 | * first differ, specifically documented (php.net) as existing for |
| 1632 | * "comparing a string to a value the user is not supposed to know", |
| 1633 | * which is exactly this situation (an attacker submitting a guessed |
| 1634 | * signature against this public endpoint). |
| 1635 | * |
| 1636 | * **Compared as hex strings, not decoded to raw bytes first** - a |
| 1637 | * deliberate difference from the Rust side, not an inconsistency: |
| 1638 | * `verify_signature` in `shared/src/webhook_sign.rs` decodes the |
| 1639 | * presented hex to raw tag bytes before `verify_slice`, because Rust's |
| 1640 | * standard library has no built-in constant-time string comparison to |
| 1641 | * reach for directly. PHP's `hash_equals()` *is* a constant-time byte |
| 1642 | * comparison, and works identically whether the two strings being |
| 1643 | * compared happen to be hex text or raw bytes - comparing the lowercase |
| 1644 | * hex `hash_hmac()` already returns is the direct, standard, officially |
| 1645 | * documented PHP idiom for verifying an HMAC (the `hash_equals()` manual |
| 1646 | * page's own canonical example does exactly this), so there is no |
| 1647 | * decode-first step to add here that would buy anything Rust's own |
| 1648 | * decode-then-compare doesn't already get for free out of `hash_ |
| 1649 | * equals()`. |
| 1650 | * |
| 1651 | * Cross-checked against `shared/src/webhook_sign.rs`'s own fixed |
| 1652 | * known-vector test constants in `tests/WebhookSignatureTest.php` - see |
| 1653 | * that file for the exact secret/payload/time/header this method must |
| 1654 | * accept. |
| 1655 | * |
| 1656 | * @param string $raw_body The exact raw request body bytes. |
| 1657 | * @param string $signature The presented `X-Monokulo-Signature` value. |
| 1658 | * @param int|null $now The current unix time; `time()` if null. |
| 1659 | * @return bool |
| 1660 | */ |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1664 | // No configured secret (gateway never connected, or an admin |
| 1665 | // blanked the field by hand), or no well-formed header: an |
| 1666 | // unauthenticated request, rejected before any HMAC runs. |
| 1667 | return false; |
| 1668 | } |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1670 | // Signed too long ago (a replay) or too far ahead. |
| 1671 | return false; |
| 1672 | } |
| 1673 | |
| 1674 | $expected = hash_hmac( 'sha256', $parts[1] . '.' . $raw_body, $this->webhook_signing_secret ); |
| 1675 | |
| 1676 | return hash_equals( $expected, strtolower( $parts[2] ) ); |
| 1677 | } |
| 1678 | |
| 1679 | /** |
| 1680 | * Finds the `WC_Order` an incoming webhook's `order_id` refers to, by |
| 1681 | * querying for the `META_ORDER_ID` meta key `process_payment()` |
| 1682 | * writes - via `wc_get_orders()` (WooCommerce's own real order-query |
| 1683 | * abstraction, `WC_Order_Query` under the hood), **not** a raw SQL query |
| 1684 | * against `wp_postmeta`/the HPOS order-meta tables directly, because |
| 1685 | * WooCommerce's order storage backend is itself pluggable (the legacy |
| 1686 | * custom-post-type store, or High-Performance Order Storage's own |
| 1687 | * dedicated tables) and only its own query API is guaranteed to work |
| 1688 | * against whichever one a given site actually has active. |
| 1689 | * |
| 1690 | * **The exact `meta_key`/`meta_value` args used here, confirmed to work |
| 1691 | * on *both* backends by reading the real installed source, not |
| 1692 | * assumed**: `WC_Data_Store_WP::get_wp_query_args()` |
| 1693 | * (`includes/data-stores/class-wc-data-store-wp.php`) passes any |
| 1694 | * unrecognized top-level query var - `meta_key`/`meta_value` included - |
| 1695 | * straight through to `WP_Query` verbatim, which is what the legacy |
| 1696 | * CPT store's `query()` ultimately runs; that store's own `query()` |
| 1697 | * (`includes/data-stores/class-wc-order-data-store-cpt.php`) explicitly |
| 1698 | * flags a *`meta_query`*-shaped arg (the array form) as `$unsupported_ |
| 1699 | * args` and fires a `doing_it_wrong()` notice for it - so the array form |
| 1700 | * would have been the wrong choice here even though it also happens to |
| 1701 | * work on HPOS. On the HPOS side, `OrdersTableQuery::` (`src/Internal/ |
| 1702 | * DataStores/Orders/OrdersTableQuery.php`) builds its own internal |
| 1703 | * meta-query mechanism from exactly this same top-level `meta_key`/ |
| 1704 | * `meta_value`/`meta_compare` "shortcut" shape (read directly - it |
| 1705 | * folds `$this->args['meta_key']`/`['meta_value']` into a `$shortcut_ |
| 1706 | * meta_query` before handing off to `OrdersTableMetaQuery`), the same |
| 1707 | * WP_Query-style convention the legacy store also understands. The |
| 1708 | * plain `meta_key`/`meta_value` pair used below is therefore the one |
| 1709 | * shape genuinely portable across both backends - not a guess, and not |
| 1710 | * the `meta_query` array shape WooCommerce's own CPT store explicitly |
| 1711 | * warns is CPT-unsupported. |
| 1712 | * |
| 1713 | * @param string $order_id The engine's own order id from the webhook |
| 1714 | * payload. |
| 1715 | * @return WC_Order|null |
| 1716 | */ |
| 1717 | private function find_order_by_order_id( $order_id ) { |
| 1718 | $orders = wc_get_orders( |
| 1719 | array( |
| 1720 | 'meta_key' => self::META_ORDER_ID, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key |
| 1721 | 'meta_value' => $order_id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value |
| 1722 | 'limit' => 1, |
| 1723 | 'return' => 'objects', |
| 1724 | ) |
| 1725 | ); |
| 1726 | |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1728 | } |
| 1729 | |
| 1730 | /** |
| 1731 | * Whether `$event_id` has already been applied to `$order` - |
| 1732 | * `META_APPLIED_EVENT_IDS` order meta, a JSON-encoded array checked with |
| 1733 | * `in_array()`. |
| 1734 | * |
| 1735 | * **Why this exists at all**: `run_delivery_tick` in `src/webhook_ |
| 1736 | * delivery.rs` (read directly) retries a delivery on anything short of |
| 1737 | * a 2xx response, with exponential backoff, up to `max_attempts` - and, |
| 1738 | * separately, any real HTTP delivery can also succeed on the engine's |
| 1739 | * side while its response is lost in transit (a network blip between |
| 1740 | * this server sending `200` and the engine receiving it), which the |
| 1741 | * engine's own retry logic cannot distinguish from an outright failure. |
| 1742 | * Either way, this receiver *will* see the same `event_id` more than |
| 1743 | * once for a perfectly healthy delivery, not just a broken one - and |
| 1744 | * `enqueue_webhook_event()` in `src/scanner.rs` (read directly) mints |
| 1745 | * exactly one fresh `event_id` per real status *transition*, baking it |
| 1746 | * into the stored payload once so every retry of that delivery resends |
| 1747 | * the identical id under the identical signature. Skipping a |
| 1748 | * already-seen `event_id` is what makes re-applying it a no-op instead |
| 1749 | * of, for example, appending the same order note twice or re-running |
| 1750 | * `payment_complete()`'s stock-reduction side effects a second time. |
| 1751 | * |
| 1752 | * @param WC_Order $order The order to check. |
| 1753 | * @param string $event_id The webhook envelope's own `event_id`. |
| 1754 | * @return bool |
| 1755 | */ |
| 1756 | private function event_already_applied( WC_Order $order, $event_id ) { |
| 1757 | $applied = json_decode( (string) $order->get_meta( self::META_APPLIED_EVENT_IDS ), true ); |
| 1758 | return is_array( $applied ) && in_array( $event_id, $applied, true ); |
| 1759 | } |
| 1760 | |
| 1761 | /** |
| 1762 | * Records `$event_id` as applied to `$order` - see |
| 1763 | * `event_already_applied()`'s own doc comment for why this exists. |
| 1764 | * Appends to the existing list rather than replacing it (an order sees |
| 1765 | * at most a handful of transitions over its real lifetime - the seven |
| 1766 | * `order.<status>` events plus, rarely, the two double-spend ones - so |
| 1767 | * unbounded growth is not a real concern here the way it might be for a |
| 1768 | * key logging every request an endpoint ever received). |
| 1769 | * |
| 1770 | * Deliberately does **not** call `$order->save()` - the caller |
| 1771 | * (`process_webhook_request()`) saves once, after every mutation this |
| 1772 | * event implies has been applied to the in-memory order object, |
| 1773 | * matching this class's existing pattern elsewhere (e.g. |
| 1774 | * `process_payment()`'s own single `$order->save()` after both |
| 1775 | * `update_meta_data()` and `add_order_note()`). |
| 1776 | * |
| 1777 | * @param WC_Order $order The order to record against. |
| 1778 | * @param string $event_id The webhook envelope's own `event_id`. |
| 1779 | */ |
| 1780 | private function mark_event_applied( WC_Order $order, $event_id ) { |
| 1781 | $applied = json_decode( (string) $order->get_meta( self::META_APPLIED_EVENT_IDS ), true ); |
| 1782 | $applied = is_array( $applied ) ? $applied : array(); |
| 1783 | $applied[] = $event_id; |
| 1784 | $order->update_meta_data( self::META_APPLIED_EVENT_IDS, wp_json_encode( array_values( array_unique( $applied ) ) ) ); |
| 1785 | } |
| 1786 | |
| 1787 | /** |
| 1788 | * Applies one already-verified, already-deduped webhook event to |
| 1789 | * `$order` - the real status-mapping/note-writing decision for every |
| 1790 | * event type this engine actually sends. |
| 1791 | * |
| 1792 | * **The real event catalog, checked directly against every call site in |
| 1793 | * `src/scanner.rs`** (grepped for `"order\.` rather than assumed from |
| 1794 | * this step's own brief alone) - `enqueue_webhook_event()` is called |
| 1795 | * from exactly two places: `recompute_and_notify_in_tx()` (one |
| 1796 | * `order.<status>` event per actual status *transition*, for all seven |
| 1797 | * `OrderStatus` variants - `src/status.rs`, read directly, confirmed |
| 1798 | * exhaustive) and `void_and_notify()`/`unvoid_as_false_positive()` |
| 1799 | * (the two double-spend events). Nine event types total; this method |
| 1800 | * handles all nine, not just `order.paid`. |
| 1801 | * |
| 1802 | * **Why the two double-spend events never set a WC status themselves**: |
| 1803 | * both `void_and_notify()` and `unvoid_as_false_positive()` (read |
| 1804 | * directly, `src/scanner.rs`) call `recompute_and_notify_in_tx()` - |
| 1805 | * which enqueues its own `order.<status>` event under its own fresh |
| 1806 | * `event_id` whenever the status genuinely changed - *before* enqueueing |
| 1807 | * their own `order.double_spend_detected`/`order.double_spend_reversed` |
| 1808 | * event, in the same database transaction. That means any real status |
| 1809 | * consequence of a void or an un-void is **already** announced, |
| 1810 | * correctly, by its own paired `order.<status>` event - handled by |
| 1811 | * `apply_order_status_event()` below like any other transition. A |
| 1812 | * double-spend event forcing some *second*, independently-guessed |
| 1813 | * status change here would either duplicate that (if the two events are |
| 1814 | * both delivered and processed) or actively fight it (since delivery |
| 1815 | * order between two independently-retried webhook rows is not |
| 1816 | * guaranteed - this receiver could see `double_spend_reversed` before |
| 1817 | * or after its paired `order.<status>` event). This is also exactly |
| 1818 | * what makes "recomputed, not hardcoded back to processing regardless |
| 1819 | * of amount" (this step's own brief, for `double_spend_reversed` |
| 1820 | * specifically) true *for free*: the real recomputation already |
| 1821 | * happened engine-side, against the engine's own authoritative ledger, |
| 1822 | * and is delivered as an honest, independent event - this method's only |
| 1823 | * job for both double-spend events is to make sure the merchant |
| 1824 | * *notices* (a prominent order note), never to re-derive or second-guess |
| 1825 | * a status this receiver has no authoritative data to recompute anyway |
| 1826 | * (the double-spend payloads carry no amount/confirmation data at all - |
| 1827 | * only `order_id`, plus `txid` for the reversal). |
| 1828 | * |
| 1829 | * @param WC_Order $order The already-matched order. |
| 1830 | * @param array $event The decoded webhook envelope (`event`, |
| 1831 | * `event_id`, `order_id`, plus whatever |
| 1832 | * event-specific fields that event type carries). |
| 1833 | */ |
| 1834 | private function apply_webhook_event( WC_Order $order, array $event ) { |
| 1835 | $event_type = $event['event']; |
| 1836 | |
| 1837 | $status_by_event_type = array( |
| 1838 | 'order.pending' => 'pending', |
| 1839 | 'order.unconfirmed' => 'unconfirmed', |
| 1840 | 'order.confirming' => 'confirming', |
| 1841 | 'order.paid' => 'paid', |
| 1842 | 'order.partial' => 'partial', |
| 1843 | 'order.overpaid' => 'overpaid', |
| 1844 | 'order.expired' => 'expired', |
| 1845 | ); |
| 1846 | |
| 1847 | if ( isset( $status_by_event_type[ $event_type ] ) ) { |
| 1848 | $this->apply_order_status_event( $order, $status_by_event_type[ $event_type ] ); |
| 1849 | return; |
| 1850 | } |
| 1851 | |
| 1852 | if ( 'order.double_spend_detected' === $event_type ) { |
| 1853 | // No status change here - see this method's own doc comment for |
| 1854 | // why. Just the loud note a merchant needs to actually notice |
| 1855 | // the fraud case happened, per this step's own brief. |
| 1856 | $order->add_order_note( |
| 1857 | __( |
| 1858 | 'Monokulo: FRAUD ALERT - a payment previously credited to this order was proven to be a double-spend and has been voided by the engine. If this changed the order\'s payment status, that change was announced separately. Please review this order.', |
| 1859 | 'monokulo' |
| 1860 | ) |
| 1861 | ); |
| 1862 | return; |
| 1863 | } |
| 1864 | |
| 1865 | if ( 'order.double_spend_reversed' === $event_type ) { |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1867 | // Likewise no status change here - see this method's own doc |
| 1868 | // comment: any real status consequence of the reversal was |
| 1869 | // already announced via its own order.<status> event. |
| 1870 | $order->add_order_note( |
| 1871 | sprintf( |
| 1872 | /* translators: %s: the transaction id the earlier double-spend accusation was about */ |
| 1873 | __( |
| 1874 | 'Monokulo: an earlier double-spend accusation against this order (transaction %s) was a false positive and has been reversed by the engine. If this changed the order\'s payment status, that change was announced separately.', |
| 1875 | 'monokulo' |
| 1876 | ), |
| 1877 | $txid |
| 1878 | ) |
| 1879 | ); |
| 1880 | return; |
| 1881 | } |
| 1882 | |
| 1883 | // An event type this receiver doesn't recognize - logged, never |
| 1884 | // fatal. Forward-compatible with a future engine version adding a |
| 1885 | // new event type this plugin hasn't been updated for yet; the |
| 1886 | // delivery still gets a 200 (it was received and understood as far |
| 1887 | // as the envelope goes) rather than looping the engine's retry |
| 1888 | // worker forever over something a retry can never fix. |
| 1889 | $this->log( sprintf( 'Webhook: unrecognized event type "%s" - envelope accepted, no action taken.', $event_type ), 'warning' ); |
| 1890 | } |
| 1891 | |
| 1892 | /** |
| 1893 | * Applies one `order.<status>` transition event - the engine -> WooCommerce |
| 1894 | * status mapping this whole step exists to build, and the reasoning |
| 1895 | * behind every row of it. |
| 1896 | * |
| 1897 | * **`paid`/`overpaid` -> `WC_Order::payment_complete()`, not a plain |
| 1898 | * `update_status()` call** (confirmed directly against the installed |
| 1899 | * `includes/class-wc-order.php::payment_complete()`, not assumed from |
| 1900 | * its name): this is WooCommerce's own canonical, real "a payment has |
| 1901 | * been received" mechanism - every bundled and third-party gateway that |
| 1902 | * actually receives real money calls this, not a raw status setter. It |
| 1903 | * does meaningfully more than set a string: it sets `date_paid` (once, |
| 1904 | * the first time it's ever called - `maybe_set_date_paid()`, read |
| 1905 | * directly), reduces stock, records a transaction id, fires `woocommerce_ |
| 1906 | * payment_complete` for every other plugin that hooks it (subscriptions |
| 1907 | * renewals, stock-alert plugins, accounting integrations), and - |
| 1908 | * critically - **decides `processing` vs. `completed` itself**, via |
| 1909 | * `$this->needs_processing()`: an order containing only virtual/ |
| 1910 | * downloadable items goes straight to `completed` (nothing left for the |
| 1911 | * merchant to fulfill), while anything else goes to `processing`. This |
| 1912 | * plugin has no principled way to know, order by order, whether that |
| 1913 | * distinction applies - hardcoding `processing` unconditionally (which |
| 1914 | * this step's own outcome text literally says, "ends with the WC order |
| 1915 | * in processing/completed") would be *wrong* for exactly the |
| 1916 | * downloadable-goods case that same outcome text's "/completed" already |
| 1917 | * anticipates. `payment_complete()` is also idempotent by construction |
| 1918 | * (gated on `$this->has_status( OrderStatus::PAYMENT_COMPLETE_STATUSES )` |
| 1919 | * - `pending`/`on-hold`/`failed`/`cancelled` - read directly), so a |
| 1920 | * duplicate call (which `event_already_applied()`'s dedupe should |
| 1921 | * already prevent, but this is real defense in depth, not redundant |
| 1922 | * caution) on an order already `processing`/`completed` is a safe no-op, |
| 1923 | * not a second stock reduction or a second `date_paid` write. |
| 1924 | * |
| 1925 | * **`unconfirmed`/`confirming` -> `on-hold`**: both mean "the engine has |
| 1926 | * seen the full amount, but doesn't yet trust it enough to treat the |
| 1927 | * order as paid" (`src/status.rs::derive_status`, read directly) - |
| 1928 | * `unconfirmed` is mempool-only, `confirming` is mined but below the |
| 1929 | * tenant's required confirmation depth. WooCommerce's own `on-hold` |
| 1930 | * status ("Awaiting payment confirmation" per its own `wc_get_order_ |
| 1931 | * statuses()` label) is a near-verbatim match for both, and - just as |
| 1932 | * importantly - `on-hold` does **not** reduce stock the way `processing`/ |
| 1933 | * `completed` do (confirmed via `payment_complete()`'s own gate above, |
| 1934 | * which only that method's success path ever reaches), which is exactly |
| 1935 | * right: nothing should be reserved as sold for a payment this server |
| 1936 | * doesn't yet trust. Collapsing both into the same WC bucket rather than |
| 1937 | * inventing a distinct status for each is deliberate - a merchant acts |
| 1938 | * on these identically ("wait"), and WooCommerce has no built-in status |
| 1939 | * more specific than `on-hold` to distinguish them with anyway. |
| 1940 | * |
| 1941 | * **`partial` -> `on-hold`**, not `pending`: `pending` (a fresh order |
| 1942 | * that's never received a single piconero) and `partial` (real, |
| 1943 | * on-chain-or-mempool funds have arrived, just not enough of them) are |
| 1944 | * genuinely different situations for a merchant - the former needs |
| 1945 | * nothing from anyone yet, the latter may need a human to eventually |
| 1946 | * decide what to do about an underpayment (there is no automated refund |
| 1947 | * path anywhere in this system - `docs/DESIGN.md` §3, and `status.rs`'s |
| 1948 | * own module doc comment cites the same fact). `on-hold` is |
| 1949 | * WooCommerce's own general "needs a look" bucket for exactly this kind |
| 1950 | * of ambiguity, and the order note added below says the actual reason, |
| 1951 | * so a merchant scanning their on-hold queue isn't left guessing which |
| 1952 | * of several possible causes put a given order there. |
| 1953 | * |
| 1954 | * **`expired` -> `cancelled`**: WooCommerce's own status for "this order |
| 1955 | * did not complete and is not going to" - the direct match for an order |
| 1956 | * whose payment window has closed with insufficient funds. Per `status. |
| 1957 | * rs`'s own comment (read directly): "even a partial payment past the |
| 1958 | * deadline surfaces as expired - the funds still exist at the address |
| 1959 | * and require manual merchant handling, since no automated refund path |
| 1960 | * exists" - restated in this transition's own order note below so that |
| 1961 | * fact isn't buried in a status label alone. |
| 1962 | * |
| 1963 | * **`pending`**: maps to WooCommerce's own `pending` - normally a no-op |
| 1964 | * (a fresh order is already `pending`), but reachable as a genuine |
| 1965 | * *regression* if a double-spend void removes an order's only payment |
| 1966 | * entirely (`derive_status`'s own `total == 0` branch). This is |
| 1967 | * deliberately not special-cased away: the webhook is reporting the |
| 1968 | * engine's own current, authoritative truth, and an order that no |
| 1969 | * longer has any valid payment genuinely isn't paid anymore, however |
| 1970 | * unusual that regression is in practice. |
| 1971 | * |
| 1972 | * Every branch's `$note` is handed to `WC_Order::update_status( $status, |
| 1973 | * $note )`, not a separate `add_order_note()` call - confirmed directly |
| 1974 | * (`includes/class-wc-order.php::add_status_transition_note()`) that |
| 1975 | * WooCommerce folds this text into the *same* order note as its own |
| 1976 | * auto-generated "Order status changed from X to Y." (`trim( $transition |
| 1977 | * ['note'] . ' ' . $note )`), and skips adding any note at all when the |
| 1978 | * status doesn't actually change (e.g. `unconfirmed` following |
| 1979 | * `confirming`, both mapping to `on-hold`) - exactly right, since a |
| 1980 | * same-bucket transition has nothing new to tell the merchant. |
| 1981 | * |
| 1982 | * @param WC_Order $order The already-matched order. |
| 1983 | * @param string $engine_status One of `src/status.rs::OrderStatus`'s |
| 1984 | * seven `as_str()` values. |
| 1985 | */ |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1988 | if ( 'overpaid' === $engine_status ) { |
| 1989 | // payment_complete() has no note parameter of its own (it |
| 1990 | // writes a fixed "Payment via ..." note) - this is added as |
| 1991 | // a separate note specifically to flag the one fact that |
| 1992 | // method can't express: the customer sent more than was |
| 1993 | // due, and nothing in this plugin or the engine refunds the |
| 1994 | // excess automatically. |
| 1995 | $order->add_order_note( |
| 1996 | __( |
| 1997 | 'Monokulo: the customer sent more Monero than the amount due. The excess is not automatically refunded - handle any refund manually.', |
| 1998 | 'monokulo' |
| 1999 | ) |
| 2000 | ); |
| 2001 | } |
| 2002 | $order->payment_complete(); |
| 2003 | return; |
| 2004 | } |
| 2005 | |
| 2006 | if ( ! isset( self::STATUS_MAP[ $engine_status ] ) ) { |
| 2007 | // Defensive only - every OrderStatus variant is covered by |
| 2008 | // either this branch or the paid/overpaid one above, so this |
| 2009 | // should be unreachable for a real engine payload. Logged, not |
| 2010 | // fatal, for the same forward-compatibility reason unrecognized |
| 2011 | // event types are in apply_webhook_event() above. |
| 2012 | $this->log( sprintf( 'Webhook: unrecognized engine status "%s" - no WooCommerce status change applied.', $engine_status ), 'warning' ); |
| 2013 | return; |
| 2014 | } |
| 2015 | |
| 2016 | $note = ''; |
| 2017 | if ( 'partial' === $engine_status ) { |
| 2018 | $note = __( 'Monokulo: a partial payment was received - the full amount due has not yet arrived.', 'monokulo' ); |
| 2019 | } elseif ( 'expired' === $engine_status ) { |
| 2020 | $note = __( |
| 2021 | 'Monokulo: this order\'s payment window has closed without full payment. If any funds were received, they remain at the payment address - there is no automatic refund; handle manually.', |
| 2022 | 'monokulo' |
| 2023 | ); |
| 2024 | } |
| 2025 | |
| 2026 | $order->update_status( self::STATUS_MAP[ $engine_status ], $note ); |
| 2027 | } |
| 2028 | |
| 2029 | /** |
| 2030 | * This gateway's own settings screen URL |
| 2031 | * (`admin.php?page=wc-settings&tab=checkout§ion={id}` - |
| 2032 | * WooCommerce's standard, stable routing for a single gateway's settings |
| 2033 | * panel), with `$extra_args` appended - used by `process_connect_return()` |
| 2034 | * to build the redirect target that also carries the |
| 2035 | * success/error flag `maybe_render_connect_notice()` reads. |
| 2036 | * |
| 2037 | * @param array $extra_args Query args to append, e.g. |
| 2038 | * `array( 'monokulo_connected' => '1' )`. |
| 2039 | * @return string |
| 2040 | */ |
| 2041 | private function build_settings_url( array $extra_args ) { |
| 2042 | return add_query_arg( |
| 2043 | $extra_args, |
| 2044 | admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=' . $this->id ) |
| 2045 | ); |
| 2046 | } |
| 2047 | |
| 2048 | /** |
| 2049 | * Flashes a one-line success/error notice on this gateway's own settings |
| 2050 | * screen right after a connect attempt redirects back to it - reads the |
| 2051 | * plain query-string flags `process_connect_return()` appends |
| 2052 | * (`build_settings_url()`) rather than using `WC_Admin_Settings:: |
| 2053 | * add_error()`, which only accumulates messages within the single |
| 2054 | * request that renders the settings form and has no mechanism to carry |
| 2055 | * a message across the separate `admin-post.php` request/redirect this |
| 2056 | * callback is. This is the same "flag in the redirect target, read back |
| 2057 | * on the next page load" pattern real WordPress admin screens |
| 2058 | * (including WooCommerce's own core settings pages) use for exactly |
| 2059 | * this situation. |
| 2060 | * |
| 2061 | * Scoped to this gateway's own settings screen specifically (checked via |
| 2062 | * `$_GET['page']`/`$_GET['section']`) so this notice never appears on an |
| 2063 | * unrelated admin screen just because these query flags happen to still |
| 2064 | * be present in the URL (e.g. a bookmarked/shared link). |
| 2065 | */ |
| 2066 | public function maybe_render_connect_notice() { |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2068 | return; |
| 2069 | } |
| 2070 | |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2072 | echo '<div class="notice notice-success is-dismissible"><p>' . |
| 2073 | esc_html__( 'Your Monero wallet is connected - Monero payments are now enabled.', 'monokulo' ) . |
| 2074 | '</p></div>'; |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2078 | '</p></div>'; |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2080 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2085 | |
| 2086 | /** |
| 2087 | * This PHP request's trace id, made up on first use. |
| 2088 | * |
| 2089 | * @return string 32 lowercase hex characters. |
| 2090 | */ |
| 2091 | public static function trace_id() { |
| 2092 | if ( null === self::$trace_id ) { |
| 2093 | self::$trace_id = bin2hex( random_bytes( 16 ) ); |
| 2094 | } |
| 2095 | return self::$trace_id; |
| 2096 | } |
| 2097 | |
| 2098 | /** |
| 2099 | * Joins the trace named by an incoming `traceparent` header (version 00 |
| 2100 | * only, the one this plugin also sends). Anything else is ignored and |
| 2101 | * the request keeps its own trace. |
| 2102 | * |
| 2103 | * @param string $traceparent The header value, `''` if absent. |
| 2104 | * @return bool Whether the trace was adopted. |
| 2105 | */ |
| 2106 | public static function adopt_traceparent( $traceparent ) { |
| 2107 | if ( ! preg_match( '/^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/D', trim( (string) $traceparent ), $parts ) ) { |
| 2108 | return false; |
| 2109 | } |
| 2110 | if ( str_repeat( '0', 32 ) === $parts[1] || str_repeat( '0', 16 ) === $parts[2] ) { |
| 2111 | return false; |
| 2112 | } |
| 2113 | self::$trace_id = $parts[1]; |
| 2114 | return true; |
| 2115 | } |
| 2116 | |
| 2117 | /** |
| 2118 | * A `traceparent` header value for one outgoing call: this request's |
| 2119 | * trace, a new span id for the call, sampled. |
| 2120 | * |
| 2121 | * @return string |
| 2122 | */ |
| 2123 | public static function traceparent() { |
| 2124 | return '00-' . self::trace_id() . '-' . bin2hex( random_bytes( 8 ) ) . '-01'; |
| 2125 | } |
| 2126 | |
| 2127 | /** |
| 2128 | * Thin wrapper around `wc_get_logger()` - guarded by `function_exists()` |
| 2129 | * only for the same reason the rest of this class never fatals outside a |
| 2130 | * real WooCommerce context, not because a live WooCommerce install (the |
| 2131 | * only place `process_payment()` can actually run) might lack it. |
| 2132 | * |
| 2133 | * @param string $message Log message. Deliberately never includes raw |
| 2134 | * customer/order PII beyond what's already an |
| 2135 | * order note-worthy fact (order id, order_id). |
| 2136 | * @param string $level A `WC_Log_Levels` level ('info'|'error'|...). |
| 2137 | */ |
| 2138 | private function log( $message, $level = 'info' ) { |
| 2139 | $this->maybe_forward( $message, $level ); |
| 2140 | if ( function_exists( 'wc_get_logger' ) ) { |
| 2141 | // The trace id goes in the message too: WooCommerce's file log |
| 2142 | // handler writes only the message, not the context. |
| 2143 | $trace_id = self::trace_id(); |
| 2144 | wc_get_logger()->log( |
| 2145 | $level, |
| 2146 | $message . ' [trace ' . $trace_id . ']', |
| 2147 | array( |
| 2148 | 'source' => 'monokulo', |
| 2149 | 'trace_id' => $trace_id, |
| 2150 | ) |
| 2151 | ); |
| 2152 | } |
| 2153 | } |
| 2154 | |
| 2155 | /** |
| 2156 | * Queues a warning or error for Monokulo when "Send errors to |
| 2157 | * Monokulo" is on and the store is connected, and registers the send |
| 2158 | * for the end of this PHP request (once), so a page never waits on it. |
| 2159 | * |
| 2160 | * @param string $message Log message. |
| 2161 | * @param string $level A `WC_Log_Levels` level. |
| 2162 | */ |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2165 | return; |
| 2166 | } |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2168 | return; |
| 2169 | } |
| 2170 | self::$forward_queue[] = array( |
| 2171 | 'level' => $level, |
| 2172 | 'message' => (string) $message, |
| 2173 | 'trace_id' => self::trace_id(), |
| 2174 | ); |
| 2175 | if ( ! self::$forward_registered ) { |
| 2176 | self::$forward_registered = true; |
| 2177 | $url = rtrim( $this->api_base_url, '/' ) . '/pay/' . rawurlencode( $this->tenant_public_key ) . '/logs'; |
| 2178 | $secret = $this->secret_token; |
| 2179 | register_shutdown_function( |
| 2180 | function () use ( $url, $secret ) { |
| 2181 | WC_Gateway_Monokulo::flush_forwarded( $url, $secret ); |
| 2182 | } |
| 2183 | ); |
| 2184 | } |
| 2185 | } |
| 2186 | |
| 2187 | /** |
| 2188 | * Sends the queued entries in one request that isn't waited for. A |
| 2189 | * failure is ignored: logging it would only queue another entry. |
| 2190 | * |
| 2191 | * @param string $url Monokulo's `/pay/{pk}/logs` URL. |
| 2192 | * @param string $secret The store's secret key. |
| 2193 | * @return array|null The request arguments sent (for tests), or null if nothing was queued. |
| 2194 | */ |
| 2195 | public static function flush_forwarded( $url, $secret ) { |
| 2196 | if ( empty( self::$forward_queue ) ) { |
| 2197 | return null; |
| 2198 | } |
| 2199 | $args = array( |
| 2200 | 'headers' => array( |
| 2201 | 'Content-Type' => 'application/json', |
| 2202 | 'Authorization' => 'Bearer ' . $secret, |
| 2203 | 'traceparent' => self::traceparent(), |
| 2204 | ), |
| 2205 | 'body' => wp_json_encode( array( 'entries' => self::$forward_queue ) ), |
| 2206 | 'timeout' => 3, |
| 2207 | 'blocking' => false, |
| 2208 | ); |
| 2209 | self::$forward_queue = array(); |
| 2210 | wp_remote_post( $url, $args ); |
| 2211 | return $args; |
| 2212 | } |
| 2213 | } |
Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not
necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once.
Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 300 | $this->id = 'monokulo'; |
| 301 | |
| 302 | // The Monokulo mark beside the payment method at checkout: the square |
| 303 | // version, shipped with the plugin so the shopper's browser loads it |
| 304 | // from the store, not from monokulo. Written by `cargo xtask logo`. |
| 305 | $this->icon = plugins_url( 'assets/monokulo-icon.svg', dirname( __DIR__ ) . '/monokulo.php' ); |
| 306 | |
| 307 | // This gateway never renders its own fields inline at WooCommerce's |
| 308 | // checkout - per `docs/WOOCOMMERCE_ROADMAP.md` Stage 7's explicit |
| 309 | // integration choice, paying with Monero means redirecting the |
| 310 | // customer to Monokulo's own checkout page |
| 311 | // (`/pay/{pk}/orders/{order_id}`), not embedding a new widget into |
| 312 | // WooCommerce's checkout form. `has_fields = false` is |
| 313 | // what tells WooCommerce's checkout template not to reserve any |
| 314 | // inline space for this gateway. |
| 315 | $this->has_fields = false; |
| 316 | |
| 317 | // `method_title`/`method_description` are what a merchant sees on |
| 318 | // WooCommerce's own "Payments" settings list (Settings > Payments) - |
| 319 | // distinct from `title`/`description` below, which is what the |
| 320 | // *customer* sees at checkout. Conflating the two is a common |
| 321 | // mistake in WooCommerce gateway plugins; kept deliberately separate |
| 322 | // here even though nothing yet enforces the distinction functionally, |
| 323 | // because getting it right from the first version avoids a later |
| 324 | // "why does the merchant admin screen say something meant for |
| 325 | // customers" bug report. |
| 326 | $this->method_title = __( 'Monokulo', 'monokulo' ); |
| 327 | $this->method_description = __( |
| 328 | 'Accept Monero at checkout without ever holding customer funds or your own spend key - Monokulo watches the chain for you and only ever needs a view key. Connect your wallet to enable this gateway for real.', |
| 329 | 'monokulo' |
| 330 | ); |
| 331 | |
| 332 | // `supports` is WooCommerce's own mechanism for a gateway to declare |
| 333 | // which of its many optional checkout/order features it implements |
| 334 | // (refunds, subscriptions, pre-orders, tokenization, etc.) - |
| 335 | // WooCommerce's core checkout and admin-order-screen code both |
| 336 | // consult this array before offering a feature (e.g. it won't show |
| 337 | // a "Refund" button for a gateway that hasn't declared `refunds`), |
| 338 | // rather than assuming every gateway implements everything. |
| 339 | // `'products'` is the baseline every ordinary one-time-payment |
| 340 | // gateway declares (WooCommerce's own bundled gateways all include |
| 341 | // it) - it means "this gateway works for a cart containing ordinary |
| 342 | // products", as opposed to gateways that only make sense for |
| 343 | // recurring subscriptions. Nothing else is declared here |
| 344 | // deliberately: refunds specifically would be a false claim right |
| 345 | // now (there is no refund flow) and no other `supports` value is |
| 346 | // developed as of WBS 1.5.1's own scope. |
| 347 | $this->supports = array( 'products' ); |
| 348 | |
| 349 | // Populates `$this->form_fields`, the schema `init_settings()` (next) |
| 350 | // reads defaults from and WooCommerce's own settings-page renderer |
| 351 | // walks to draw the actual HTML form. |
| 352 | $this->init_form_fields(); |
| 353 | |
| 354 | // Loads whatever's actually been saved in `wp_options` into |
| 355 | // `$this->settings`, falling back to each field's own `default` from |
| 356 | // `init_form_fields()` above for anything never saved yet (e.g. a |
| 357 | // brand new install). This has to run *after* `init_form_fields()` |
| 358 | // - there'd be nothing to load defaults from otherwise - and |
| 359 | // *before* the `get_option()` calls immediately following it, which |
| 360 | // read out of the `$this->settings` array this call populates. |
| 361 | $this->init_settings(); |
| 362 | |
| 363 | // `title`/`description` are the customer-facing checkout strings - |
| 364 | // pulled from saved settings when present, otherwise the same |
| 365 | // literal WBS 1.5.1's own acceptance outcome names verbatim |
| 366 | // ("Monero (via Monokulo)"), so a fresh install already shows |
| 367 | // the right label with zero configuration, while still letting a |
| 368 | // merchant override the wording later without a code change. |
| 369 | $this->title = $this->get_option( 'title', __( 'Monero (via Monokulo)', 'monokulo' ) ); |
| 370 | $this->description = $this->get_option( |
| 371 | 'description', |
| 372 | __( 'Pay with Monero. You will be redirected to a secure Monokulo payment page to complete your purchase.', 'monokulo' ) |
| 373 | ); |
| 374 | |
| 375 | // The connection the connect flow saved. `trim()`, not just a raw |
| 376 | // `get_option()` read: a value pasted by hand into the settings form |
| 377 | // notoriously picks up leading/trailing whitespace, and an |
| 378 | // un-trimmed `pk_...\n` would fail `create_monokulo_order()`'s |
| 379 | // outbound call in a way that looks identical to a genuinely wrong |
| 380 | // key. |
| 381 | $this->api_base_url = trim( (string) $this->get_option( 'endpoint', '' ) ); |
| 382 | $this->tenant_public_key = trim( (string) $this->get_option( 'public_key', '' ) ); |
| 383 | $this->secret_token = trim( (string) $this->get_option( 'secret_token', '' ) ); |
| 384 | $this->webhook_signing_secret = trim( (string) $this->get_option( 'webhook_signing_secret', '' ) ); |
| 385 | |
| 386 | // No explicit `$this->enabled = $this->get_option( 'enabled' )` line |
| 387 | // here, deliberately - checked directly against WooCommerce's own |
| 388 | // source (`WC_Payment_Gateway::init_settings()`, |
| 389 | // includes/abstracts/abstract-wc-payment-gateway.php) rather than |
| 390 | // assumed: that method, called just above, already sets |
| 391 | // `$this->enabled` itself from `$this->settings['enabled']` |
| 392 | // (`'yes' === $this->settings['enabled'] ? 'yes' : 'no'`) - a second |
| 393 | // assignment here would be pure duplication, not a safety net. This |
| 394 | // matches the pattern WooCommerce's own bundled gateways use (e.g. |
| 395 | // `WC_Gateway_BACS`'s constructor never touches `$this->enabled` |
| 396 | // either, for the identical reason - confirmed by reading |
| 397 | // `includes/gateways/bacs/class-wc-gateway-bacs.php` directly). |
| 398 | // |
| 399 | // What actually makes this gateway disabled by default is the |
| 400 | // `enabled` field's own `'default' => 'no'` in `init_form_fields()` |
| 401 | // below, which `init_settings()` falls back to for a fresh install |
| 402 | // with nothing saved yet - and that default is not an oversight this |
| 403 | // step left for later, it is WBS 1.5.1's own stated outcome |
| 404 | // ("disabled state is fine at this step"). A gateway with no |
| 405 | // connected wallet has no `pk_`/`sk_`/`endpoint` to call yet (that |
| 406 | // only exists once WBS 1.5.3's connect flow runs), so registering it |
| 407 | // pre-enabled would let a merchant offer a payment method that WBS |
| 408 | // 1.5.2's `process_payment()` cannot yet honor - exactly the failure |
| 409 | // mode a disabled-by-default gateway avoids. |
| 410 | |
| 411 | // The standard WooCommerce hook a gateway registers so that |
| 412 | // submitting its own settings form (Settings > Payments > this |
| 413 | // gateway) actually persists - WooCommerce fires |
| 414 | // `woocommerce_update_options_payment_gateways_{$this->id}` on save, |
| 415 | // and `process_admin_options()` (inherited from `WC_Payment_Gateway`, |
| 416 | // not overridden here - its default behavior of validating and |
| 417 | // saving `$this->form_fields` against submitted POST data is exactly |
| 418 | // right for this step's plain checkbox/text fields) is what actually |
| 419 | // writes the new values back to `wp_options`. Without this line, the |
| 420 | // settings form would render correctly but silently discard every |
| 421 | // change on submit. |
| 422 | add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) ); |
| 423 | |
| 424 | // The real connect-flow callback (WBS 1.5.3) - see |
| 425 | // `get_connect_return_url()`'s own doc comment for why |
| 426 | // `admin_post_{action}` specifically is the right WordPress |
| 427 | // mechanism for this, and `handle_connect_return()`'s for why |
| 428 | // registering it here in the constructor is safe (identical |
| 429 | // reasoning to the `woocommerce_update_options_...` registration |
| 430 | // immediately above: this constructor only ever runs once |
| 431 | // WooCommerce's own `WC_Payment_Gateways::init()` has already fired, |
| 432 | // which happens on every request including the one `admin-post.php` |
| 433 | // itself bootstraps). |
| 434 | add_action( 'admin_post_' . self::CONNECT_RETURN_ACTION, array( $this, 'handle_connect_return' ) ); |
| 435 | |
| 436 | // The real webhook receiver (WBS 1.5.4) - WooCommerce's own real, |
| 437 | // documented mechanism for a plugin's webhook endpoint, confirmed |
| 438 | // directly against the *installed* WooCommerce source before relying |
| 439 | // on it (not just its doc comments): `WC()->api_request_url( $id )` |
| 440 | // (already used by `get_webhook_receiver_url()` above, since 1.5.3) |
| 441 | // builds a URL carrying a `wc-api={id}` query var (or `/wc-api/{id}/` |
| 442 | // under pretty permalinks); `src/Internal/Utilities/ |
| 443 | // LegacyRestApiStub.php::maybe_process_wc_api_query_var()` (hooked on |
| 444 | // `parse_request`, read directly) is what actually dispatches that |
| 445 | // var, firing `do_action( 'woocommerce_api_' . $api_request )` for |
| 446 | // *any* site that has this hook registered - regardless of whether |
| 447 | // the separate, full "WooCommerce Legacy REST API" extension is |
| 448 | // installed. That file's own doc comment explains why the stub |
| 449 | // exists at all: the versioned `/wc-api/v1-3/...` REST endpoints |
| 450 | // were genuinely removed from core in WC 9.0, but the plain |
| 451 | // `woocommerce_api_{id}` gateway-callback mechanism (what PayPal's |
| 452 | // own legacy IPN handler still uses too, per |
| 453 | // `wc-deprecated-functions.php::woocommerce_legacy_paypal_ipn()`, |
| 454 | // read directly) was deliberately kept working without it - this is |
| 455 | // real, current, installed-source-confirmed behavior on this |
| 456 | // environment's WooCommerce 11.1.0, not an assumption carried over |
| 457 | // from an older WooCommerce version where the whole legacy API was |
| 458 | // still in core wholesale. |
| 459 | add_action( 'woocommerce_api_' . $this->id, array( $this, 'handle_webhook' ) ); |
| 460 | |
| 461 | // Flashes a one-line success/error notice on this gateway's own |
| 462 | // settings screen after a connect attempt redirects back to it - see |
| 463 | // `maybe_render_connect_notice()`'s own doc comment for why a plain |
| 464 | // query-string flag is used here rather than `WC_Admin_Settings:: |
| 465 | // add_error()` (which only works within the same request that |
| 466 | // renders the settings form, and the connect callback is a separate |
| 467 | // request). |
| 468 | add_action( 'admin_notices', array( $this, 'maybe_render_connect_notice' ) ); |
| 469 | // The site-wide "please reconnect" notice is registered in |
| 470 | // `monokulo.php` instead, so it shows on every admin screen whether |
| 471 | // or not WooCommerce has built its gateways on that request. |
| 472 | } |
| 2106 | public static function adopt_traceparent( $traceparent ) { |
| 2107 | if ( ! preg_match( '/^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/D', trim( (string) $traceparent ), $parts ) ) { |
| 2108 | return false; |
| 2106 | public static function adopt_traceparent( $traceparent ) { |
| 2107 | if ( ! preg_match( '/^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/D', trim( (string) $traceparent ), $parts ) ) { |
| 2110 | if ( str_repeat( '0', 32 ) === $parts[1] || str_repeat( '0', 16 ) === $parts[2] ) { |
| 2110 | if ( str_repeat( '0', 32 ) === $parts[1] || str_repeat( '0', 16 ) === $parts[2] ) { |
| 2110 | if ( str_repeat( '0', 32 ) === $parts[1] || str_repeat( '0', 16 ) === $parts[2] ) { |
| 2111 | return false; |
| 2106 | public static function adopt_traceparent( $traceparent ) { |
| 2107 | if ( ! preg_match( '/^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/D', trim( (string) $traceparent ), $parts ) ) { |
| 2110 | if ( str_repeat( '0', 32 ) === $parts[1] || str_repeat( '0', 16 ) === $parts[2] ) { |
| 2110 | if ( str_repeat( '0', 32 ) === $parts[1] || str_repeat( '0', 16 ) === $parts[2] ) { |
| 2110 | if ( str_repeat( '0', 32 ) === $parts[1] || str_repeat( '0', 16 ) === $parts[2] ) { |
| 2113 | self::$trace_id = $parts[1]; |
| 2114 | return true; |
| 2115 | } |
| 2106 | public static function adopt_traceparent( $traceparent ) { |
| 2107 | if ( ! preg_match( '/^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/D', trim( (string) $traceparent ), $parts ) ) { |
| 2110 | if ( str_repeat( '0', 32 ) === $parts[1] || str_repeat( '0', 16 ) === $parts[2] ) { |
| 2110 | if ( str_repeat( '0', 32 ) === $parts[1] || str_repeat( '0', 16 ) === $parts[2] ) { |
| 2111 | return false; |
| 2106 | public static function adopt_traceparent( $traceparent ) { |
| 2107 | if ( ! preg_match( '/^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/D', trim( (string) $traceparent ), $parts ) ) { |
| 2110 | if ( str_repeat( '0', 32 ) === $parts[1] || str_repeat( '0', 16 ) === $parts[2] ) { |
| 2110 | if ( str_repeat( '0', 32 ) === $parts[1] || str_repeat( '0', 16 ) === $parts[2] ) { |
| 2113 | self::$trace_id = $parts[1]; |
| 2114 | return true; |
| 2115 | } |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1988 | if ( 'overpaid' === $engine_status ) { |
| 1995 | $order->add_order_note( |
| 1996 | __( |
| 1997 | 'Monokulo: the customer sent more Monero than the amount due. The excess is not automatically refunded - handle any refund manually.', |
| 1998 | 'monokulo' |
| 1999 | ) |
| 2000 | ); |
| 2001 | } |
| 2002 | $order->payment_complete(); |
| 2002 | $order->payment_complete(); |
| 2003 | return; |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1988 | if ( 'overpaid' === $engine_status ) { |
| 2002 | $order->payment_complete(); |
| 2003 | return; |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 2006 | if ( ! isset( self::STATUS_MAP[ $engine_status ] ) ) { |
| 2012 | $this->log( sprintf( 'Webhook: unrecognized engine status "%s" - no WooCommerce status change applied.', $engine_status ), 'warning' ); |
| 2013 | return; |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 2006 | if ( ! isset( self::STATUS_MAP[ $engine_status ] ) ) { |
| 2016 | $note = ''; |
| 2017 | if ( 'partial' === $engine_status ) { |
| 2017 | if ( 'partial' === $engine_status ) { |
| 2018 | $note = __( 'Monokulo: a partial payment was received - the full amount due has not yet arrived.', 'monokulo' ); |
| 2026 | $order->update_status( self::STATUS_MAP[ $engine_status ], $note ); |
| 2027 | } |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 2006 | if ( ! isset( self::STATUS_MAP[ $engine_status ] ) ) { |
| 2016 | $note = ''; |
| 2017 | if ( 'partial' === $engine_status ) { |
| 2019 | } elseif ( 'expired' === $engine_status ) { |
| 2020 | $note = __( |
| 2021 | 'Monokulo: this order\'s payment window has closed without full payment. If any funds were received, they remain at the payment address - there is no automatic refund; handle manually.', |
| 2022 | 'monokulo' |
| 2023 | ); |
| 2024 | } |
| 2025 | |
| 2026 | $order->update_status( self::STATUS_MAP[ $engine_status ], $note ); |
| 2026 | $order->update_status( self::STATUS_MAP[ $engine_status ], $note ); |
| 2027 | } |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 2006 | if ( ! isset( self::STATUS_MAP[ $engine_status ] ) ) { |
| 2016 | $note = ''; |
| 2017 | if ( 'partial' === $engine_status ) { |
| 2019 | } elseif ( 'expired' === $engine_status ) { |
| 2026 | $order->update_status( self::STATUS_MAP[ $engine_status ], $note ); |
| 2027 | } |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1988 | if ( 'overpaid' === $engine_status ) { |
| 1995 | $order->add_order_note( |
| 1996 | __( |
| 1997 | 'Monokulo: the customer sent more Monero than the amount due. The excess is not automatically refunded - handle any refund manually.', |
| 1998 | 'monokulo' |
| 1999 | ) |
| 2000 | ); |
| 2001 | } |
| 2002 | $order->payment_complete(); |
| 2002 | $order->payment_complete(); |
| 2003 | return; |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1988 | if ( 'overpaid' === $engine_status ) { |
| 2002 | $order->payment_complete(); |
| 2003 | return; |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 2006 | if ( ! isset( self::STATUS_MAP[ $engine_status ] ) ) { |
| 2012 | $this->log( sprintf( 'Webhook: unrecognized engine status "%s" - no WooCommerce status change applied.', $engine_status ), 'warning' ); |
| 2013 | return; |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 2006 | if ( ! isset( self::STATUS_MAP[ $engine_status ] ) ) { |
| 2016 | $note = ''; |
| 2017 | if ( 'partial' === $engine_status ) { |
| 2017 | if ( 'partial' === $engine_status ) { |
| 2018 | $note = __( 'Monokulo: a partial payment was received - the full amount due has not yet arrived.', 'monokulo' ); |
| 2026 | $order->update_status( self::STATUS_MAP[ $engine_status ], $note ); |
| 2027 | } |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 2006 | if ( ! isset( self::STATUS_MAP[ $engine_status ] ) ) { |
| 2016 | $note = ''; |
| 2017 | if ( 'partial' === $engine_status ) { |
| 2019 | } elseif ( 'expired' === $engine_status ) { |
| 2020 | $note = __( |
| 2021 | 'Monokulo: this order\'s payment window has closed without full payment. If any funds were received, they remain at the payment address - there is no automatic refund; handle manually.', |
| 2022 | 'monokulo' |
| 2023 | ); |
| 2024 | } |
| 2025 | |
| 2026 | $order->update_status( self::STATUS_MAP[ $engine_status ], $note ); |
| 2026 | $order->update_status( self::STATUS_MAP[ $engine_status ], $note ); |
| 2027 | } |
| 1986 | private function apply_order_status_event( WC_Order $order, $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 1987 | if ( 'paid' === $engine_status || 'overpaid' === $engine_status ) { |
| 2006 | if ( ! isset( self::STATUS_MAP[ $engine_status ] ) ) { |
| 2016 | $note = ''; |
| 2017 | if ( 'partial' === $engine_status ) { |
| 2019 | } elseif ( 'expired' === $engine_status ) { |
| 2026 | $order->update_status( self::STATUS_MAP[ $engine_status ], $note ); |
| 2027 | } |
| 1834 | private function apply_webhook_event( WC_Order $order, array $event ) { |
| 1835 | $event_type = $event['event']; |
| 1836 | |
| 1837 | $status_by_event_type = array( |
| 1838 | 'order.pending' => 'pending', |
| 1839 | 'order.unconfirmed' => 'unconfirmed', |
| 1840 | 'order.confirming' => 'confirming', |
| 1841 | 'order.paid' => 'paid', |
| 1842 | 'order.partial' => 'partial', |
| 1843 | 'order.overpaid' => 'overpaid', |
| 1844 | 'order.expired' => 'expired', |
| 1845 | ); |
| 1846 | |
| 1847 | if ( isset( $status_by_event_type[ $event_type ] ) ) { |
| 1848 | $this->apply_order_status_event( $order, $status_by_event_type[ $event_type ] ); |
| 1849 | return; |
| 1834 | private function apply_webhook_event( WC_Order $order, array $event ) { |
| 1835 | $event_type = $event['event']; |
| 1836 | |
| 1837 | $status_by_event_type = array( |
| 1838 | 'order.pending' => 'pending', |
| 1839 | 'order.unconfirmed' => 'unconfirmed', |
| 1840 | 'order.confirming' => 'confirming', |
| 1841 | 'order.paid' => 'paid', |
| 1842 | 'order.partial' => 'partial', |
| 1843 | 'order.overpaid' => 'overpaid', |
| 1844 | 'order.expired' => 'expired', |
| 1845 | ); |
| 1846 | |
| 1847 | if ( isset( $status_by_event_type[ $event_type ] ) ) { |
| 1852 | if ( 'order.double_spend_detected' === $event_type ) { |
| 1856 | $order->add_order_note( |
| 1857 | __( |
| 1858 | 'Monokulo: FRAUD ALERT - a payment previously credited to this order was proven to be a double-spend and has been voided by the engine. If this changed the order\'s payment status, that change was announced separately. Please review this order.', |
| 1859 | 'monokulo' |
| 1860 | ) |
| 1861 | ); |
| 1862 | return; |
| 1834 | private function apply_webhook_event( WC_Order $order, array $event ) { |
| 1835 | $event_type = $event['event']; |
| 1836 | |
| 1837 | $status_by_event_type = array( |
| 1838 | 'order.pending' => 'pending', |
| 1839 | 'order.unconfirmed' => 'unconfirmed', |
| 1840 | 'order.confirming' => 'confirming', |
| 1841 | 'order.paid' => 'paid', |
| 1842 | 'order.partial' => 'partial', |
| 1843 | 'order.overpaid' => 'overpaid', |
| 1844 | 'order.expired' => 'expired', |
| 1845 | ); |
| 1846 | |
| 1847 | if ( isset( $status_by_event_type[ $event_type ] ) ) { |
| 1852 | if ( 'order.double_spend_detected' === $event_type ) { |
| 1865 | if ( 'order.double_spend_reversed' === $event_type ) { |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1867 | // Likewise no status change here - see this method's own doc |
| 1868 | // comment: any real status consequence of the reversal was |
| 1869 | // already announced via its own order.<status> event. |
| 1870 | $order->add_order_note( |
| 1871 | sprintf( |
| 1872 | /* translators: %s: the transaction id the earlier double-spend accusation was about */ |
| 1873 | __( |
| 1874 | 'Monokulo: an earlier double-spend accusation against this order (transaction %s) was a false positive and has been reversed by the engine. If this changed the order\'s payment status, that change was announced separately.', |
| 1875 | 'monokulo' |
| 1876 | ), |
| 1877 | $txid |
| 1878 | ) |
| 1879 | ); |
| 1880 | return; |
| 1834 | private function apply_webhook_event( WC_Order $order, array $event ) { |
| 1835 | $event_type = $event['event']; |
| 1836 | |
| 1837 | $status_by_event_type = array( |
| 1838 | 'order.pending' => 'pending', |
| 1839 | 'order.unconfirmed' => 'unconfirmed', |
| 1840 | 'order.confirming' => 'confirming', |
| 1841 | 'order.paid' => 'paid', |
| 1842 | 'order.partial' => 'partial', |
| 1843 | 'order.overpaid' => 'overpaid', |
| 1844 | 'order.expired' => 'expired', |
| 1845 | ); |
| 1846 | |
| 1847 | if ( isset( $status_by_event_type[ $event_type ] ) ) { |
| 1852 | if ( 'order.double_spend_detected' === $event_type ) { |
| 1865 | if ( 'order.double_spend_reversed' === $event_type ) { |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1867 | // Likewise no status change here - see this method's own doc |
| 1868 | // comment: any real status consequence of the reversal was |
| 1869 | // already announced via its own order.<status> event. |
| 1870 | $order->add_order_note( |
| 1871 | sprintf( |
| 1872 | /* translators: %s: the transaction id the earlier double-spend accusation was about */ |
| 1873 | __( |
| 1874 | 'Monokulo: an earlier double-spend accusation against this order (transaction %s) was a false positive and has been reversed by the engine. If this changed the order\'s payment status, that change was announced separately.', |
| 1875 | 'monokulo' |
| 1876 | ), |
| 1877 | $txid |
| 1878 | ) |
| 1879 | ); |
| 1880 | return; |
| 1834 | private function apply_webhook_event( WC_Order $order, array $event ) { |
| 1835 | $event_type = $event['event']; |
| 1836 | |
| 1837 | $status_by_event_type = array( |
| 1838 | 'order.pending' => 'pending', |
| 1839 | 'order.unconfirmed' => 'unconfirmed', |
| 1840 | 'order.confirming' => 'confirming', |
| 1841 | 'order.paid' => 'paid', |
| 1842 | 'order.partial' => 'partial', |
| 1843 | 'order.overpaid' => 'overpaid', |
| 1844 | 'order.expired' => 'expired', |
| 1845 | ); |
| 1846 | |
| 1847 | if ( isset( $status_by_event_type[ $event_type ] ) ) { |
| 1852 | if ( 'order.double_spend_detected' === $event_type ) { |
| 1865 | if ( 'order.double_spend_reversed' === $event_type ) { |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1867 | // Likewise no status change here - see this method's own doc |
| 1868 | // comment: any real status consequence of the reversal was |
| 1869 | // already announced via its own order.<status> event. |
| 1870 | $order->add_order_note( |
| 1871 | sprintf( |
| 1872 | /* translators: %s: the transaction id the earlier double-spend accusation was about */ |
| 1873 | __( |
| 1874 | 'Monokulo: an earlier double-spend accusation against this order (transaction %s) was a false positive and has been reversed by the engine. If this changed the order\'s payment status, that change was announced separately.', |
| 1875 | 'monokulo' |
| 1876 | ), |
| 1877 | $txid |
| 1878 | ) |
| 1879 | ); |
| 1880 | return; |
| 1834 | private function apply_webhook_event( WC_Order $order, array $event ) { |
| 1835 | $event_type = $event['event']; |
| 1836 | |
| 1837 | $status_by_event_type = array( |
| 1838 | 'order.pending' => 'pending', |
| 1839 | 'order.unconfirmed' => 'unconfirmed', |
| 1840 | 'order.confirming' => 'confirming', |
| 1841 | 'order.paid' => 'paid', |
| 1842 | 'order.partial' => 'partial', |
| 1843 | 'order.overpaid' => 'overpaid', |
| 1844 | 'order.expired' => 'expired', |
| 1845 | ); |
| 1846 | |
| 1847 | if ( isset( $status_by_event_type[ $event_type ] ) ) { |
| 1852 | if ( 'order.double_spend_detected' === $event_type ) { |
| 1865 | if ( 'order.double_spend_reversed' === $event_type ) { |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1866 | $txid = isset( $event['txid'] ) && is_string( $event['txid'] ) ? $event['txid'] : __( 'unknown', 'monokulo' ); |
| 1867 | // Likewise no status change here - see this method's own doc |
| 1868 | // comment: any real status consequence of the reversal was |
| 1869 | // already announced via its own order.<status> event. |
| 1870 | $order->add_order_note( |
| 1871 | sprintf( |
| 1872 | /* translators: %s: the transaction id the earlier double-spend accusation was about */ |
| 1873 | __( |
| 1874 | 'Monokulo: an earlier double-spend accusation against this order (transaction %s) was a false positive and has been reversed by the engine. If this changed the order\'s payment status, that change was announced separately.', |
| 1875 | 'monokulo' |
| 1876 | ), |
| 1877 | $txid |
| 1878 | ) |
| 1879 | ); |
| 1880 | return; |
| 1834 | private function apply_webhook_event( WC_Order $order, array $event ) { |
| 1835 | $event_type = $event['event']; |
| 1836 | |
| 1837 | $status_by_event_type = array( |
| 1838 | 'order.pending' => 'pending', |
| 1839 | 'order.unconfirmed' => 'unconfirmed', |
| 1840 | 'order.confirming' => 'confirming', |
| 1841 | 'order.paid' => 'paid', |
| 1842 | 'order.partial' => 'partial', |
| 1843 | 'order.overpaid' => 'overpaid', |
| 1844 | 'order.expired' => 'expired', |
| 1845 | ); |
| 1846 | |
| 1847 | if ( isset( $status_by_event_type[ $event_type ] ) ) { |
| 1852 | if ( 'order.double_spend_detected' === $event_type ) { |
| 1865 | if ( 'order.double_spend_reversed' === $event_type ) { |
| 1889 | $this->log( sprintf( 'Webhook: unrecognized event type "%s" - envelope accepted, no action taken.', $event_type ), 'warning' ); |
| 1890 | } |
| 1075 | private function build_connect_start_url( $nonce ) { |
| 1076 | return sprintf( |
| 1077 | '%s/connect/%s?site_url=%s&return_url=%s&nonce=%s', |
| 1078 | rtrim( $this->get_control_plane_base_url(), '/' ), |
| 1079 | rawurlencode( self::CONNECT_PLATFORM ), |
| 1080 | rawurlencode( home_url( '/' ) ), |
| 1081 | rawurlencode( $this->get_connect_return_url() ), |
| 1082 | rawurlencode( $nonce ) |
| 1083 | ); |
| 1084 | } |
| 2041 | private function build_settings_url( array $extra_args ) { |
| 2042 | return add_query_arg( |
| 2043 | $extra_args, |
| 2044 | admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=' . $this->id ) |
| 2045 | ); |
| 2046 | } |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1394 | $this->log( sprintf( 'Connect finish request to %s failed: %s', $url, $response->get_error_message() ), 'error' ); |
| 1395 | return null; |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1408 | $this->log( sprintf( 'Connect finish request to %s returned HTTP %d: %s', $url, $status_code, $raw_body ), 'error' ); |
| 1409 | return null; |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1414 | $this->log( sprintf( 'Connect finish response from %s was not the expected shape: %s', $url, $raw_body ), 'error' ); |
| 1415 | return null; |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1418 | return $decoded; |
| 1419 | } |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1414 | $this->log( sprintf( 'Connect finish response from %s was not the expected shape: %s', $url, $raw_body ), 'error' ); |
| 1415 | return null; |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1418 | return $decoded; |
| 1419 | } |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1414 | $this->log( sprintf( 'Connect finish response from %s was not the expected shape: %s', $url, $raw_body ), 'error' ); |
| 1415 | return null; |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1418 | return $decoded; |
| 1419 | } |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1414 | $this->log( sprintf( 'Connect finish response from %s was not the expected shape: %s', $url, $raw_body ), 'error' ); |
| 1415 | return null; |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1418 | return $decoded; |
| 1419 | } |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1414 | $this->log( sprintf( 'Connect finish response from %s was not the expected shape: %s', $url, $raw_body ), 'error' ); |
| 1415 | return null; |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1418 | return $decoded; |
| 1419 | } |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1414 | $this->log( sprintf( 'Connect finish response from %s was not the expected shape: %s', $url, $raw_body ), 'error' ); |
| 1415 | return null; |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1418 | return $decoded; |
| 1419 | } |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1414 | $this->log( sprintf( 'Connect finish response from %s was not the expected shape: %s', $url, $raw_body ), 'error' ); |
| 1415 | return null; |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1418 | return $decoded; |
| 1419 | } |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1414 | $this->log( sprintf( 'Connect finish response from %s was not the expected shape: %s', $url, $raw_body ), 'error' ); |
| 1415 | return null; |
| 1367 | private function call_connect_finish( $token, &$status_code = 0 ) { |
| 1368 | $url = rtrim( $this->get_control_plane_base_url(), '/' ) . '/connect/' . self::CONNECT_PLATFORM . '/finish'; |
| 1369 | |
| 1370 | $response = wp_remote_post( |
| 1371 | $url, |
| 1372 | array( |
| 1373 | 'headers' => array( |
| 1374 | 'Content-Type' => 'application/json', |
| 1375 | // Monokulo records the connection with this plugin's version. |
| 1376 | 'Monokulo-Client' => self::CLIENT, |
| 1377 | 'traceparent' => self::traceparent(), |
| 1378 | ), |
| 1379 | 'body' => wp_json_encode( |
| 1380 | array( |
| 1381 | 'token' => $token, |
| 1382 | 'webhook_url' => $this->get_webhook_receiver_url(), |
| 1383 | ) |
| 1384 | ), |
| 1385 | // Same generous, untuned headroom `create_monokulo_order()` |
| 1386 | // above uses, for the same reason: an ordinary HTTP round |
| 1387 | // trip with no reason to be slow, not a value chosen from |
| 1388 | // measurement. |
| 1389 | 'timeout' => 30, |
| 1390 | ) |
| 1391 | ); |
| 1392 | |
| 1393 | if ( is_wp_error( $response ) ) { |
| 1398 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 1399 | $raw_body = wp_remote_retrieve_body( $response ); |
| 1400 | |
| 1401 | // A bare 401 is the shape of every token-related failure |
| 1402 | // `connect.rs::finish` can produce (unknown/expired/already-consumed |
| 1403 | // token, a rejected webhook URL, an internal error). The one other |
| 1404 | // status is 503 with a JSON `error`: Monokulo has no public address |
| 1405 | // configured yet, so it refuses to hand out one. The caller turns |
| 1406 | // that into its own notice; the message is logged either way. |
| 1407 | if ( 200 !== $status_code ) { |
| 1412 | $decoded = json_decode( $raw_body, true ); |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1413 | if ( ! is_array( $decoded ) || empty( $decoded['public_key'] ) || empty( $decoded['secret_token'] ) || empty( $decoded['endpoint'] ) ) { |
| 1418 | return $decoded; |
| 1419 | } |
| 1059 | return 'monokulo_connect_nonce_' . $this->id; |
| 1060 | } |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 781 | throw new Exception( |
| 782 | __( 'Monero payments are not fully configured for this store yet. Please contact the store owner.', 'monokulo' ) |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 824 | $this->log( |
| 825 | sprintf( 'Order creation request to %s failed: %s', $request_url, $response->get_error_message() ), |
| 826 | 'error' |
| 827 | ); |
| 828 | throw new Exception( |
| 829 | __( 'Could not reach Monokulo to start this payment. Please try again shortly.', 'monokulo' ) |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 837 | $decoded_error = json_decode( $raw_body, true ); |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 839 | $this->log( |
| 840 | sprintf( |
| 841 | 'Order creation request to %s returned HTTP %d: %s%s', |
| 842 | $request_url, |
| 843 | $status_code, |
| 844 | $detail, |
| 845 | self::hint_for_status( $status_code ) |
| 846 | ), |
| 847 | 'error' |
| 848 | ); |
| 849 | throw new Exception( self::customer_message_for_status( $status_code ) ); |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 837 | $decoded_error = json_decode( $raw_body, true ); |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 839 | $this->log( |
| 840 | sprintf( |
| 841 | 'Order creation request to %s returned HTTP %d: %s%s', |
| 842 | $request_url, |
| 843 | $status_code, |
| 844 | $detail, |
| 845 | self::hint_for_status( $status_code ) |
| 846 | ), |
| 847 | 'error' |
| 848 | ); |
| 849 | throw new Exception( self::customer_message_for_status( $status_code ) ); |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 837 | $decoded_error = json_decode( $raw_body, true ); |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 839 | $this->log( |
| 840 | sprintf( |
| 841 | 'Order creation request to %s returned HTTP %d: %s%s', |
| 842 | $request_url, |
| 843 | $status_code, |
| 844 | $detail, |
| 845 | self::hint_for_status( $status_code ) |
| 846 | ), |
| 847 | 'error' |
| 848 | ); |
| 849 | throw new Exception( self::customer_message_for_status( $status_code ) ); |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 837 | $decoded_error = json_decode( $raw_body, true ); |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 839 | $this->log( |
| 840 | sprintf( |
| 841 | 'Order creation request to %s returned HTTP %d: %s%s', |
| 842 | $request_url, |
| 843 | $status_code, |
| 844 | $detail, |
| 845 | self::hint_for_status( $status_code ) |
| 846 | ), |
| 847 | 'error' |
| 848 | ); |
| 849 | throw new Exception( self::customer_message_for_status( $status_code ) ); |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 852 | $decoded = json_decode( $raw_body, true ); |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 854 | $this->log( |
| 855 | sprintf( 'Order creation response from %s was not the expected shape: %s', $request_url, $raw_body ), |
| 856 | 'error' |
| 857 | ); |
| 858 | throw new Exception( |
| 859 | __( 'Monokulo returned an unexpected response. Please contact the store.', 'monokulo' ) |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 852 | $decoded = json_decode( $raw_body, true ); |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 863 | return $decoded; |
| 864 | } |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 852 | $decoded = json_decode( $raw_body, true ); |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 854 | $this->log( |
| 855 | sprintf( 'Order creation response from %s was not the expected shape: %s', $request_url, $raw_body ), |
| 856 | 'error' |
| 857 | ); |
| 858 | throw new Exception( |
| 859 | __( 'Monokulo returned an unexpected response. Please contact the store.', 'monokulo' ) |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 852 | $decoded = json_decode( $raw_body, true ); |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 863 | return $decoded; |
| 864 | } |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 781 | throw new Exception( |
| 782 | __( 'Monero payments are not fully configured for this store yet. Please contact the store owner.', 'monokulo' ) |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 824 | $this->log( |
| 825 | sprintf( 'Order creation request to %s failed: %s', $request_url, $response->get_error_message() ), |
| 826 | 'error' |
| 827 | ); |
| 828 | throw new Exception( |
| 829 | __( 'Could not reach Monokulo to start this payment. Please try again shortly.', 'monokulo' ) |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 837 | $decoded_error = json_decode( $raw_body, true ); |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 839 | $this->log( |
| 840 | sprintf( |
| 841 | 'Order creation request to %s returned HTTP %d: %s%s', |
| 842 | $request_url, |
| 843 | $status_code, |
| 844 | $detail, |
| 845 | self::hint_for_status( $status_code ) |
| 846 | ), |
| 847 | 'error' |
| 848 | ); |
| 849 | throw new Exception( self::customer_message_for_status( $status_code ) ); |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 837 | $decoded_error = json_decode( $raw_body, true ); |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 839 | $this->log( |
| 840 | sprintf( |
| 841 | 'Order creation request to %s returned HTTP %d: %s%s', |
| 842 | $request_url, |
| 843 | $status_code, |
| 844 | $detail, |
| 845 | self::hint_for_status( $status_code ) |
| 846 | ), |
| 847 | 'error' |
| 848 | ); |
| 849 | throw new Exception( self::customer_message_for_status( $status_code ) ); |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 837 | $decoded_error = json_decode( $raw_body, true ); |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 839 | $this->log( |
| 840 | sprintf( |
| 841 | 'Order creation request to %s returned HTTP %d: %s%s', |
| 842 | $request_url, |
| 843 | $status_code, |
| 844 | $detail, |
| 845 | self::hint_for_status( $status_code ) |
| 846 | ), |
| 847 | 'error' |
| 848 | ); |
| 849 | throw new Exception( self::customer_message_for_status( $status_code ) ); |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 837 | $decoded_error = json_decode( $raw_body, true ); |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 838 | $detail = is_array( $decoded_error ) && isset( $decoded_error['error'] ) ? (string) $decoded_error['error'] : $raw_body; |
| 839 | $this->log( |
| 840 | sprintf( |
| 841 | 'Order creation request to %s returned HTTP %d: %s%s', |
| 842 | $request_url, |
| 843 | $status_code, |
| 844 | $detail, |
| 845 | self::hint_for_status( $status_code ) |
| 846 | ), |
| 847 | 'error' |
| 848 | ); |
| 849 | throw new Exception( self::customer_message_for_status( $status_code ) ); |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 852 | $decoded = json_decode( $raw_body, true ); |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 854 | $this->log( |
| 855 | sprintf( 'Order creation response from %s was not the expected shape: %s', $request_url, $raw_body ), |
| 856 | 'error' |
| 857 | ); |
| 858 | throw new Exception( |
| 859 | __( 'Monokulo returned an unexpected response. Please contact the store.', 'monokulo' ) |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 852 | $decoded = json_decode( $raw_body, true ); |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 863 | return $decoded; |
| 864 | } |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 852 | $decoded = json_decode( $raw_body, true ); |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 854 | $this->log( |
| 855 | sprintf( 'Order creation response from %s was not the expected shape: %s', $request_url, $raw_body ), |
| 856 | 'error' |
| 857 | ); |
| 858 | throw new Exception( |
| 859 | __( 'Monokulo returned an unexpected response. Please contact the store.', 'monokulo' ) |
| 775 | private function create_monokulo_order( WC_Order $order ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 776 | if ( ! $this->has_credentials() || $this->needs_reconnect() ) { |
| 786 | $request_url = $this->get_orders_endpoint_url(); |
| 787 | $currency = $order->get_currency(); |
| 788 | $amount = self::format_amount( $order->get_total(), $currency ); |
| 789 | |
| 790 | $body = array( |
| 791 | 'amount' => $amount, |
| 792 | 'currency' => $currency, |
| 793 | // The order's own numeric id, not `get_order_number()` - the |
| 794 | // latter is filterable (some plugins prefix it, e.g. "WC-1042") |
| 795 | // and only ever meant for human display, whereas this value |
| 796 | // exists so a merchant can match a Monokulo order back to this |
| 797 | // store's order later, and shows on Monokulo's order page. |
| 798 | 'merchant_order_id' => (string) $order->get_id(), |
| 799 | // One key per order and total: a retry after a timeout or a lost |
| 800 | // answer gets the Monokulo order the first attempt made, never a |
| 801 | // second one with a second address. A changed total is a new |
| 802 | // purchase, so it gets a new key. |
| 803 | 'idempotency_key' => self::idempotency_key( $order->get_id(), $amount, $currency ), |
| 804 | ); |
| 805 | |
| 806 | $response = wp_remote_post( |
| 807 | $request_url, |
| 808 | array( |
| 809 | 'headers' => array( |
| 810 | 'Content-Type' => 'application/json', |
| 811 | 'Authorization' => 'Bearer ' . $this->secret_token, |
| 812 | 'Monokulo-Client' => self::CLIENT, |
| 813 | 'traceparent' => self::traceparent(), |
| 814 | ), |
| 815 | 'body' => wp_json_encode( $body ), |
| 816 | // An ordinary HTTP round trip (Monokulo prices the order and |
| 817 | // asks its engine for an address); 30s is generous headroom, |
| 818 | // not a tuned value. |
| 819 | 'timeout' => 30, |
| 820 | ) |
| 821 | ); |
| 822 | |
| 823 | if ( is_wp_error( $response ) ) { |
| 833 | $status_code = (int) wp_remote_retrieve_response_code( $response ); |
| 834 | $raw_body = wp_remote_retrieve_body( $response ); |
| 835 | |
| 836 | if ( 200 !== $status_code ) { |
| 852 | $decoded = json_decode( $raw_body, true ); |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 853 | if ( ! is_array( $decoded ) || empty( $decoded['order_id'] ) ) { |
| 863 | return $decoded; |
| 864 | } |
| 904 | private static function customer_message_for_status( $status_code ) { |
| 905 | if ( 429 === $status_code ) { |
| 906 | return __( 'Monero payments are busy right now. Please wait a moment and try again.', 'monokulo' ); |
| 904 | private static function customer_message_for_status( $status_code ) { |
| 905 | if ( 429 === $status_code ) { |
| 908 | if ( 401 === $status_code || 403 === $status_code ) { |
| 908 | if ( 401 === $status_code || 403 === $status_code ) { |
| 908 | if ( 401 === $status_code || 403 === $status_code ) { |
| 909 | return __( 'Monero payments are not available for this store right now. Please contact the store owner.', 'monokulo' ); |
| 904 | private static function customer_message_for_status( $status_code ) { |
| 905 | if ( 429 === $status_code ) { |
| 908 | if ( 401 === $status_code || 403 === $status_code ) { |
| 908 | if ( 401 === $status_code || 403 === $status_code ) { |
| 908 | if ( 401 === $status_code || 403 === $status_code ) { |
| 911 | return __( 'Monokulo could not start this payment. Please contact the store or try again.', 'monokulo' ); |
| 912 | } |
| 904 | private static function customer_message_for_status( $status_code ) { |
| 905 | if ( 429 === $status_code ) { |
| 908 | if ( 401 === $status_code || 403 === $status_code ) { |
| 908 | if ( 401 === $status_code || 403 === $status_code ) { |
| 909 | return __( 'Monero payments are not available for this store right now. Please contact the store owner.', 'monokulo' ); |
| 904 | private static function customer_message_for_status( $status_code ) { |
| 905 | if ( 429 === $status_code ) { |
| 908 | if ( 401 === $status_code || 403 === $status_code ) { |
| 908 | if ( 401 === $status_code || 403 === $status_code ) { |
| 911 | return __( 'Monokulo could not start this payment. Please contact the store or try again.', 'monokulo' ); |
| 912 | } |
| 1756 | private function event_already_applied( WC_Order $order, $event_id ) { |
| 1757 | $applied = json_decode( (string) $order->get_meta( self::META_APPLIED_EVENT_IDS ), true ); |
| 1758 | return is_array( $applied ) && in_array( $event_id, $applied, true ); |
| 1758 | return is_array( $applied ) && in_array( $event_id, $applied, true ); |
| 1758 | return is_array( $applied ) && in_array( $event_id, $applied, true ); |
| 1759 | } |
| 1756 | private function event_already_applied( WC_Order $order, $event_id ) { |
| 1757 | $applied = json_decode( (string) $order->get_meta( self::META_APPLIED_EVENT_IDS ), true ); |
| 1758 | return is_array( $applied ) && in_array( $event_id, $applied, true ); |
| 1758 | return is_array( $applied ) && in_array( $event_id, $applied, true ); |
| 1759 | } |
| 1717 | private function find_order_by_order_id( $order_id ) { |
| 1718 | $orders = wc_get_orders( |
| 1719 | array( |
| 1720 | 'meta_key' => self::META_ORDER_ID, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key |
| 1721 | 'meta_value' => $order_id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value |
| 1722 | 'limit' => 1, |
| 1723 | 'return' => 'objects', |
| 1724 | ) |
| 1725 | ); |
| 1726 | |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1728 | } |
| 1717 | private function find_order_by_order_id( $order_id ) { |
| 1718 | $orders = wc_get_orders( |
| 1719 | array( |
| 1720 | 'meta_key' => self::META_ORDER_ID, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key |
| 1721 | 'meta_value' => $order_id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value |
| 1722 | 'limit' => 1, |
| 1723 | 'return' => 'objects', |
| 1724 | ) |
| 1725 | ); |
| 1726 | |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1728 | } |
| 1717 | private function find_order_by_order_id( $order_id ) { |
| 1718 | $orders = wc_get_orders( |
| 1719 | array( |
| 1720 | 'meta_key' => self::META_ORDER_ID, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key |
| 1721 | 'meta_value' => $order_id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value |
| 1722 | 'limit' => 1, |
| 1723 | 'return' => 'objects', |
| 1724 | ) |
| 1725 | ); |
| 1726 | |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1728 | } |
| 1717 | private function find_order_by_order_id( $order_id ) { |
| 1718 | $orders = wc_get_orders( |
| 1719 | array( |
| 1720 | 'meta_key' => self::META_ORDER_ID, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key |
| 1721 | 'meta_value' => $order_id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value |
| 1722 | 'limit' => 1, |
| 1723 | 'return' => 'objects', |
| 1724 | ) |
| 1725 | ); |
| 1726 | |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1727 | return isset( $orders[0] ) && $orders[0] instanceof WC_Order ? $orders[0] : null; |
| 1728 | } |
| 2195 | public static function flush_forwarded( $url, $secret ) { |
| 2196 | if ( empty( self::$forward_queue ) ) { |
| 2197 | return null; |
| 2195 | public static function flush_forwarded( $url, $secret ) { |
| 2196 | if ( empty( self::$forward_queue ) ) { |
| 2201 | 'Content-Type' => 'application/json', |
| 2202 | 'Authorization' => 'Bearer ' . $secret, |
| 2203 | 'traceparent' => self::traceparent(), |
| 2204 | ), |
| 2205 | 'body' => wp_json_encode( array( 'entries' => self::$forward_queue ) ), |
| 2206 | 'timeout' => 3, |
| 2207 | 'blocking' => false, |
| 2208 | ); |
| 2209 | self::$forward_queue = array(); |
| 2210 | wp_remote_post( $url, $args ); |
| 2211 | return $args; |
| 2212 | } |
| 889 | private static function format_amount( $total, $currency ) { |
| 890 | $decimals = 2; |
| 891 | if ( 'XMR' === strtoupper( $currency ) ) { |
| 892 | $decimals = function_exists( 'wc_get_price_decimals' ) ? min( 12, max( 2, (int) wc_get_price_decimals() ) ) : 12; |
| 892 | $decimals = function_exists( 'wc_get_price_decimals' ) ? min( 12, max( 2, (int) wc_get_price_decimals() ) ) : 12; |
| 892 | $decimals = function_exists( 'wc_get_price_decimals' ) ? min( 12, max( 2, (int) wc_get_price_decimals() ) ) : 12; |
| 893 | } |
| 894 | return number_format( (float) $total, $decimals, '.', '' ); |
| 894 | return number_format( (float) $total, $decimals, '.', '' ); |
| 895 | } |
| 889 | private static function format_amount( $total, $currency ) { |
| 890 | $decimals = 2; |
| 891 | if ( 'XMR' === strtoupper( $currency ) ) { |
| 892 | $decimals = function_exists( 'wc_get_price_decimals' ) ? min( 12, max( 2, (int) wc_get_price_decimals() ) ) : 12; |
| 892 | $decimals = function_exists( 'wc_get_price_decimals' ) ? min( 12, max( 2, (int) wc_get_price_decimals() ) ) : 12; |
| 892 | $decimals = function_exists( 'wc_get_price_decimals' ) ? min( 12, max( 2, (int) wc_get_price_decimals() ) ) : 12; |
| 893 | } |
| 894 | return number_format( (float) $total, $decimals, '.', '' ); |
| 894 | return number_format( (float) $total, $decimals, '.', '' ); |
| 895 | } |
| 889 | private static function format_amount( $total, $currency ) { |
| 890 | $decimals = 2; |
| 891 | if ( 'XMR' === strtoupper( $currency ) ) { |
| 894 | return number_format( (float) $total, $decimals, '.', '' ); |
| 895 | } |
| 1113 | public function generate_monokulo_connect_html( $key, $data ) { |
| 1114 | $field_key = $this->get_field_key( $key ); |
| 1115 | |
| 1116 | // Same default-filling `generate_text_html()` itself does (read |
| 1117 | // directly) before ever touching `$data['desc_tip']`/ |
| 1118 | // `$data['description']` - `init_form_fields()`'s own `'connect'` |
| 1119 | // entry only sets `type`/`description`, and `get_description_html()` |
| 1120 | // below indexes `$data['desc_tip']` unconditionally, which would be |
| 1121 | // an undefined-array-key warning on every real render without this. |
| 1122 | $data = wp_parse_args( |
| 1123 | $data, |
| 1124 | array( |
| 1125 | 'desc_tip' => false, |
| 1126 | 'description' => '', |
| 1127 | ) |
| 1128 | ); |
| 1129 | |
| 1130 | $nonce = bin2hex( random_bytes( 16 ) ); |
| 1131 | set_transient( $this->connect_nonce_transient_key(), $nonce, 10 * MINUTE_IN_SECONDS ); |
| 1132 | $connect_url = $this->build_connect_start_url( $nonce ); |
| 1133 | |
| 1134 | ob_start(); |
| 1135 | ?> |
| 1136 | <tr valign="top"> |
| 1137 | <th scope="row" class="titledesc"> |
| 1138 | <label for="<?php echo esc_attr( $field_key ); ?>"><?php esc_html_e( 'Connect your Monero wallet', 'monokulo' ); ?></label> |
| 1139 | </th> |
| 1140 | <td class="forminp"> |
| 1141 | <?php if ( $this->needs_reconnect() ) : ?> |
| 1141 | <?php if ( $this->needs_reconnect() ) : ?> |
| 1142 | <p><strong><?php esc_html_e( 'Reconnect needed: this store was connected by an older plugin version, so Monero payments are hidden at checkout until you reconnect.', 'monokulo' ); ?></strong></p> |
| 1149 | <a href="<?php echo esc_url( $connect_url ); ?>" id="<?php echo esc_attr( $field_key ); ?>" class="button button-primary"> |
| 1150 | <?php |
| 1151 | echo $this->has_credentials() |
| 1152 | ? esc_html__( 'Reconnect your Monero wallet', 'monokulo' ) |
| 1153 | : esc_html__( 'Connect your Monero wallet', 'monokulo' ); |
| 1154 | ?> |
| 1155 | </a> |
| 1156 | <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> |
| 1157 | </td> |
| 1158 | </tr> |
| 1159 | <?php |
| 1160 | return ob_get_clean(); |
| 1161 | } |
| 1113 | public function generate_monokulo_connect_html( $key, $data ) { |
| 1114 | $field_key = $this->get_field_key( $key ); |
| 1115 | |
| 1116 | // Same default-filling `generate_text_html()` itself does (read |
| 1117 | // directly) before ever touching `$data['desc_tip']`/ |
| 1118 | // `$data['description']` - `init_form_fields()`'s own `'connect'` |
| 1119 | // entry only sets `type`/`description`, and `get_description_html()` |
| 1120 | // below indexes `$data['desc_tip']` unconditionally, which would be |
| 1121 | // an undefined-array-key warning on every real render without this. |
| 1122 | $data = wp_parse_args( |
| 1123 | $data, |
| 1124 | array( |
| 1125 | 'desc_tip' => false, |
| 1126 | 'description' => '', |
| 1127 | ) |
| 1128 | ); |
| 1129 | |
| 1130 | $nonce = bin2hex( random_bytes( 16 ) ); |
| 1131 | set_transient( $this->connect_nonce_transient_key(), $nonce, 10 * MINUTE_IN_SECONDS ); |
| 1132 | $connect_url = $this->build_connect_start_url( $nonce ); |
| 1133 | |
| 1134 | ob_start(); |
| 1135 | ?> |
| 1136 | <tr valign="top"> |
| 1137 | <th scope="row" class="titledesc"> |
| 1138 | <label for="<?php echo esc_attr( $field_key ); ?>"><?php esc_html_e( 'Connect your Monero wallet', 'monokulo' ); ?></label> |
| 1139 | </th> |
| 1140 | <td class="forminp"> |
| 1141 | <?php if ( $this->needs_reconnect() ) : ?> |
| 1141 | <?php if ( $this->needs_reconnect() ) : ?> |
| 1142 | <p><strong><?php esc_html_e( 'Reconnect needed: this store was connected by an older plugin version, so Monero payments are hidden at checkout until you reconnect.', 'monokulo' ); ?></strong></p> |
| 1149 | <a href="<?php echo esc_url( $connect_url ); ?>" id="<?php echo esc_attr( $field_key ); ?>" class="button button-primary"> |
| 1150 | <?php |
| 1151 | echo $this->has_credentials() |
| 1153 | : esc_html__( 'Connect your Monero wallet', 'monokulo' ); |
| 1153 | : esc_html__( 'Connect your Monero wallet', 'monokulo' ); |
| 1154 | ?> |
| 1155 | </a> |
| 1156 | <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> |
| 1157 | </td> |
| 1158 | </tr> |
| 1159 | <?php |
| 1160 | return ob_get_clean(); |
| 1161 | } |
| 1113 | public function generate_monokulo_connect_html( $key, $data ) { |
| 1114 | $field_key = $this->get_field_key( $key ); |
| 1115 | |
| 1116 | // Same default-filling `generate_text_html()` itself does (read |
| 1117 | // directly) before ever touching `$data['desc_tip']`/ |
| 1118 | // `$data['description']` - `init_form_fields()`'s own `'connect'` |
| 1119 | // entry only sets `type`/`description`, and `get_description_html()` |
| 1120 | // below indexes `$data['desc_tip']` unconditionally, which would be |
| 1121 | // an undefined-array-key warning on every real render without this. |
| 1122 | $data = wp_parse_args( |
| 1123 | $data, |
| 1124 | array( |
| 1125 | 'desc_tip' => false, |
| 1126 | 'description' => '', |
| 1127 | ) |
| 1128 | ); |
| 1129 | |
| 1130 | $nonce = bin2hex( random_bytes( 16 ) ); |
| 1131 | set_transient( $this->connect_nonce_transient_key(), $nonce, 10 * MINUTE_IN_SECONDS ); |
| 1132 | $connect_url = $this->build_connect_start_url( $nonce ); |
| 1133 | |
| 1134 | ob_start(); |
| 1135 | ?> |
| 1136 | <tr valign="top"> |
| 1137 | <th scope="row" class="titledesc"> |
| 1138 | <label for="<?php echo esc_attr( $field_key ); ?>"><?php esc_html_e( 'Connect your Monero wallet', 'monokulo' ); ?></label> |
| 1139 | </th> |
| 1140 | <td class="forminp"> |
| 1141 | <?php if ( $this->needs_reconnect() ) : ?> |
| 1143 | <?php elseif ( $this->has_credentials() ) : ?> |
| 1144 | <p> |
| 1145 | <?php esc_html_e( 'Connected as', 'monokulo' ); ?> |
| 1146 | <code><?php echo esc_html( $this->tenant_public_key ); ?></code> |
| 1147 | </p> |
| 1148 | <?php endif; ?> |
| 1149 | <a href="<?php echo esc_url( $connect_url ); ?>" id="<?php echo esc_attr( $field_key ); ?>" class="button button-primary"> |
| 1149 | <a href="<?php echo esc_url( $connect_url ); ?>" id="<?php echo esc_attr( $field_key ); ?>" class="button button-primary"> |
| 1150 | <?php |
| 1151 | echo $this->has_credentials() |
| 1152 | ? esc_html__( 'Reconnect your Monero wallet', 'monokulo' ) |
| 1153 | : esc_html__( 'Connect your Monero wallet', 'monokulo' ); |
| 1154 | ?> |
| 1155 | </a> |
| 1156 | <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> |
| 1157 | </td> |
| 1158 | </tr> |
| 1159 | <?php |
| 1160 | return ob_get_clean(); |
| 1161 | } |
| 1113 | public function generate_monokulo_connect_html( $key, $data ) { |
| 1114 | $field_key = $this->get_field_key( $key ); |
| 1115 | |
| 1116 | // Same default-filling `generate_text_html()` itself does (read |
| 1117 | // directly) before ever touching `$data['desc_tip']`/ |
| 1118 | // `$data['description']` - `init_form_fields()`'s own `'connect'` |
| 1119 | // entry only sets `type`/`description`, and `get_description_html()` |
| 1120 | // below indexes `$data['desc_tip']` unconditionally, which would be |
| 1121 | // an undefined-array-key warning on every real render without this. |
| 1122 | $data = wp_parse_args( |
| 1123 | $data, |
| 1124 | array( |
| 1125 | 'desc_tip' => false, |
| 1126 | 'description' => '', |
| 1127 | ) |
| 1128 | ); |
| 1129 | |
| 1130 | $nonce = bin2hex( random_bytes( 16 ) ); |
| 1131 | set_transient( $this->connect_nonce_transient_key(), $nonce, 10 * MINUTE_IN_SECONDS ); |
| 1132 | $connect_url = $this->build_connect_start_url( $nonce ); |
| 1133 | |
| 1134 | ob_start(); |
| 1135 | ?> |
| 1136 | <tr valign="top"> |
| 1137 | <th scope="row" class="titledesc"> |
| 1138 | <label for="<?php echo esc_attr( $field_key ); ?>"><?php esc_html_e( 'Connect your Monero wallet', 'monokulo' ); ?></label> |
| 1139 | </th> |
| 1140 | <td class="forminp"> |
| 1141 | <?php if ( $this->needs_reconnect() ) : ?> |
| 1143 | <?php elseif ( $this->has_credentials() ) : ?> |
| 1144 | <p> |
| 1145 | <?php esc_html_e( 'Connected as', 'monokulo' ); ?> |
| 1146 | <code><?php echo esc_html( $this->tenant_public_key ); ?></code> |
| 1147 | </p> |
| 1148 | <?php endif; ?> |
| 1149 | <a href="<?php echo esc_url( $connect_url ); ?>" id="<?php echo esc_attr( $field_key ); ?>" class="button button-primary"> |
| 1149 | <a href="<?php echo esc_url( $connect_url ); ?>" id="<?php echo esc_attr( $field_key ); ?>" class="button button-primary"> |
| 1150 | <?php |
| 1151 | echo $this->has_credentials() |
| 1153 | : esc_html__( 'Connect your Monero wallet', 'monokulo' ); |
| 1153 | : esc_html__( 'Connect your Monero wallet', 'monokulo' ); |
| 1154 | ?> |
| 1155 | </a> |
| 1156 | <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> |
| 1157 | </td> |
| 1158 | </tr> |
| 1159 | <?php |
| 1160 | return ob_get_clean(); |
| 1161 | } |
| 1113 | public function generate_monokulo_connect_html( $key, $data ) { |
| 1114 | $field_key = $this->get_field_key( $key ); |
| 1115 | |
| 1116 | // Same default-filling `generate_text_html()` itself does (read |
| 1117 | // directly) before ever touching `$data['desc_tip']`/ |
| 1118 | // `$data['description']` - `init_form_fields()`'s own `'connect'` |
| 1119 | // entry only sets `type`/`description`, and `get_description_html()` |
| 1120 | // below indexes `$data['desc_tip']` unconditionally, which would be |
| 1121 | // an undefined-array-key warning on every real render without this. |
| 1122 | $data = wp_parse_args( |
| 1123 | $data, |
| 1124 | array( |
| 1125 | 'desc_tip' => false, |
| 1126 | 'description' => '', |
| 1127 | ) |
| 1128 | ); |
| 1129 | |
| 1130 | $nonce = bin2hex( random_bytes( 16 ) ); |
| 1131 | set_transient( $this->connect_nonce_transient_key(), $nonce, 10 * MINUTE_IN_SECONDS ); |
| 1132 | $connect_url = $this->build_connect_start_url( $nonce ); |
| 1133 | |
| 1134 | ob_start(); |
| 1135 | ?> |
| 1136 | <tr valign="top"> |
| 1137 | <th scope="row" class="titledesc"> |
| 1138 | <label for="<?php echo esc_attr( $field_key ); ?>"><?php esc_html_e( 'Connect your Monero wallet', 'monokulo' ); ?></label> |
| 1139 | </th> |
| 1140 | <td class="forminp"> |
| 1141 | <?php if ( $this->needs_reconnect() ) : ?> |
| 1143 | <?php elseif ( $this->has_credentials() ) : ?> |
| 1149 | <a href="<?php echo esc_url( $connect_url ); ?>" id="<?php echo esc_attr( $field_key ); ?>" class="button button-primary"> |
| 1150 | <?php |
| 1151 | echo $this->has_credentials() |
| 1152 | ? esc_html__( 'Reconnect your Monero wallet', 'monokulo' ) |
| 1153 | : esc_html__( 'Connect your Monero wallet', 'monokulo' ); |
| 1154 | ?> |
| 1155 | </a> |
| 1156 | <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> |
| 1157 | </td> |
| 1158 | </tr> |
| 1159 | <?php |
| 1160 | return ob_get_clean(); |
| 1161 | } |
| 1113 | public function generate_monokulo_connect_html( $key, $data ) { |
| 1114 | $field_key = $this->get_field_key( $key ); |
| 1115 | |
| 1116 | // Same default-filling `generate_text_html()` itself does (read |
| 1117 | // directly) before ever touching `$data['desc_tip']`/ |
| 1118 | // `$data['description']` - `init_form_fields()`'s own `'connect'` |
| 1119 | // entry only sets `type`/`description`, and `get_description_html()` |
| 1120 | // below indexes `$data['desc_tip']` unconditionally, which would be |
| 1121 | // an undefined-array-key warning on every real render without this. |
| 1122 | $data = wp_parse_args( |
| 1123 | $data, |
| 1124 | array( |
| 1125 | 'desc_tip' => false, |
| 1126 | 'description' => '', |
| 1127 | ) |
| 1128 | ); |
| 1129 | |
| 1130 | $nonce = bin2hex( random_bytes( 16 ) ); |
| 1131 | set_transient( $this->connect_nonce_transient_key(), $nonce, 10 * MINUTE_IN_SECONDS ); |
| 1132 | $connect_url = $this->build_connect_start_url( $nonce ); |
| 1133 | |
| 1134 | ob_start(); |
| 1135 | ?> |
| 1136 | <tr valign="top"> |
| 1137 | <th scope="row" class="titledesc"> |
| 1138 | <label for="<?php echo esc_attr( $field_key ); ?>"><?php esc_html_e( 'Connect your Monero wallet', 'monokulo' ); ?></label> |
| 1139 | </th> |
| 1140 | <td class="forminp"> |
| 1141 | <?php if ( $this->needs_reconnect() ) : ?> |
| 1143 | <?php elseif ( $this->has_credentials() ) : ?> |
| 1149 | <a href="<?php echo esc_url( $connect_url ); ?>" id="<?php echo esc_attr( $field_key ); ?>" class="button button-primary"> |
| 1150 | <?php |
| 1151 | echo $this->has_credentials() |
| 1153 | : esc_html__( 'Connect your Monero wallet', 'monokulo' ); |
| 1153 | : esc_html__( 'Connect your Monero wallet', 'monokulo' ); |
| 1154 | ?> |
| 1155 | </a> |
| 1156 | <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?> |
| 1157 | </td> |
| 1158 | </tr> |
| 1159 | <?php |
| 1160 | return ob_get_clean(); |
| 1161 | } |
| 959 | private function get_checkout_url( $order_id ) { |
| 960 | return sprintf( |
| 961 | '%s/pay/%s/orders/%s', |
| 962 | rtrim( $this->api_base_url, '/' ), |
| 963 | rawurlencode( $this->tenant_public_key ), |
| 964 | rawurlencode( $order_id ) |
| 965 | ); |
| 966 | } |
| 1041 | return admin_url( 'admin-post.php?action=' . self::CONNECT_RETURN_ACTION ); |
| 1042 | } |
| 984 | return apply_filters( 'monokulo_control_plane_base_url', self::CONTROL_PLANE_BASE_URL ); |
| 985 | } |
| 945 | rtrim( $this->api_base_url, '/' ), |
| 946 | rawurlencode( $this->tenant_public_key ) |
| 947 | ); |
| 948 | } |
| 1436 | return WC()->api_request_url( $this->id ); |
| 1437 | } |
| 1209 | if ( ! current_user_can( 'manage_woocommerce' ) ) { |
| 1210 | wp_die( esc_html__( 'You do not have permission to do this.', 'monokulo' ), '', array( 'response' => 403 ) ); |
| 1211 | } |
| 1212 | |
| 1213 | $redirect_url = $this->process_connect_return( wp_unslash( $_GET ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 1213 | $redirect_url = $this->process_connect_return( wp_unslash( $_GET ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 1214 | wp_safe_redirect( $redirect_url ); |
| 1215 | exit; |
| 1216 | } |
| 1209 | if ( ! current_user_can( 'manage_woocommerce' ) ) { |
| 1213 | $redirect_url = $this->process_connect_return( wp_unslash( $_GET ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 1214 | wp_safe_redirect( $redirect_url ); |
| 1215 | exit; |
| 1216 | } |
| 1491 | $raw_body = (string) file_get_contents( 'php://input' ); |
| 1492 | $signature = isset( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) |
| 1493 | ? (string) wp_unslash( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash |
| 1492 | $signature = isset( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) |
| 1493 | ? (string) wp_unslash( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash |
| 1494 | : ''; |
| 1495 | // The engine sends the delivery attempt's trace; the lines this |
| 1496 | // webhook logs join it. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1498 | |
| 1499 | status_header( $this->process_webhook_request( $raw_body, $signature ) ); |
| 1500 | exit; |
| 1501 | } |
| 1491 | $raw_body = (string) file_get_contents( 'php://input' ); |
| 1492 | $signature = isset( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) |
| 1493 | ? (string) wp_unslash( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash |
| 1492 | $signature = isset( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) |
| 1493 | ? (string) wp_unslash( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash |
| 1494 | : ''; |
| 1495 | // The engine sends the delivery attempt's trace; the lines this |
| 1496 | // webhook logs join it. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1498 | |
| 1499 | status_header( $this->process_webhook_request( $raw_body, $signature ) ); |
| 1500 | exit; |
| 1501 | } |
| 1491 | $raw_body = (string) file_get_contents( 'php://input' ); |
| 1492 | $signature = isset( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) |
| 1492 | $signature = isset( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) |
| 1493 | ? (string) wp_unslash( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash |
| 1494 | : ''; |
| 1492 | $signature = isset( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) |
| 1493 | ? (string) wp_unslash( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash |
| 1494 | : ''; |
| 1495 | // The engine sends the delivery attempt's trace; the lines this |
| 1496 | // webhook logs join it. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1498 | |
| 1499 | status_header( $this->process_webhook_request( $raw_body, $signature ) ); |
| 1500 | exit; |
| 1501 | } |
| 1491 | $raw_body = (string) file_get_contents( 'php://input' ); |
| 1492 | $signature = isset( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) |
| 1492 | $signature = isset( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) |
| 1493 | ? (string) wp_unslash( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash |
| 1494 | : ''; |
| 1492 | $signature = isset( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) |
| 1493 | ? (string) wp_unslash( $_SERVER[ self::WEBHOOK_SIGNATURE_SERVER_KEY ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash |
| 1494 | : ''; |
| 1495 | // The engine sends the delivery attempt's trace; the lines this |
| 1496 | // webhook logs join it. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1497 | self::adopt_traceparent( isset( $_SERVER['HTTP_TRACEPARENT'] ) ? (string) wp_unslash( $_SERVER['HTTP_TRACEPARENT'] ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- checked against a strict pattern. |
| 1498 | |
| 1499 | status_header( $this->process_webhook_request( $raw_body, $signature ) ); |
| 1500 | exit; |
| 1501 | } |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 615 | } |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 615 | } |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 615 | } |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 614 | return '' !== $this->api_base_url && '' !== $this->tenant_public_key && '' !== $this->secret_token; |
| 615 | } |
| 921 | private static function hint_for_status( $status_code ) { |
| 922 | switch ( $status_code ) { |
| 924 | return ' (the store secret key was rejected - reconnect the plugin from its settings page)'; |
| 921 | private static function hint_for_status( $status_code ) { |
| 922 | switch ( $status_code ) { |
| 926 | return ' (Monokulo refused the order - check the store\'s verified-domain settings on Monokulo)'; |
| 921 | private static function hint_for_status( $status_code ) { |
| 922 | switch ( $status_code ) { |
| 928 | return ' (Monokulo does not know this store\'s public key - reconnect the plugin)'; |
| 921 | private static function hint_for_status( $status_code ) { |
| 922 | switch ( $status_code ) { |
| 930 | return ' (rate limited - Monokulo is receiving too many orders from this store)'; |
| 921 | private static function hint_for_status( $status_code ) { |
| 922 | switch ( $status_code ) { |
| 932 | return ''; |
| 933 | } |
| 934 | } |
| 921 | private static function hint_for_status( $status_code ) { |
| 922 | switch ( $status_code ) { |
| 923 | case 401: |
| 925 | case 403: |
| 927 | case 404: |
| 929 | case 429: |
| 929 | case 429: |
| 932 | return ''; |
| 933 | } |
| 934 | } |
| 921 | private static function hint_for_status( $status_code ) { |
| 922 | switch ( $status_code ) { |
| 923 | case 401: |
| 925 | case 403: |
| 927 | case 404: |
| 929 | case 429: |
| 930 | return ' (rate limited - Monokulo is receiving too many orders from this store)'; |
| 921 | private static function hint_for_status( $status_code ) { |
| 922 | switch ( $status_code ) { |
| 923 | case 401: |
| 925 | case 403: |
| 927 | case 404: |
| 928 | return ' (Monokulo does not know this store\'s public key - reconnect the plugin)'; |
| 921 | private static function hint_for_status( $status_code ) { |
| 922 | switch ( $status_code ) { |
| 923 | case 401: |
| 925 | case 403: |
| 926 | return ' (Monokulo refused the order - check the store\'s verified-domain settings on Monokulo)'; |
| 921 | private static function hint_for_status( $status_code ) { |
| 922 | switch ( $status_code ) { |
| 923 | case 401: |
| 924 | return ' (the store secret key was rejected - reconnect the plugin from its settings page)'; |
| 875 | public static function idempotency_key( $order_id, $amount, $currency ) { |
| 876 | return sprintf( 'wc:%s:%s:%s', $order_id, $amount, $currency ); |
| 877 | } |
| 507 | 'title' => __( 'Enable/Disable', 'monokulo' ), |
| 508 | 'type' => 'checkbox', |
| 509 | 'label' => __( 'Enable Monero payments via Monokulo', 'monokulo' ), |
| 510 | // See the constructor's own comment on `$this->enabled` - |
| 511 | // this default is what makes "disabled state is fine at |
| 512 | // this step" the actual out-of-the-box behavior, not just |
| 513 | // documentation of an intention. WBS 1.5.3's own connect |
| 514 | // callback (`process_connect_return()` below) is now a real |
| 515 | // second way this flips to `'yes'` - see that method's own |
| 516 | // doc comment for why flipping it there, not just leaving it |
| 517 | // for the merchant to toggle afterward, is the WBS's own |
| 518 | // stated outcome, not an extra liberty this step is taking. |
| 519 | 'default' => 'no', |
| 520 | ), |
| 521 | 'title' => array( |
| 522 | 'title' => __( 'Title', 'monokulo' ), |
| 523 | 'type' => 'text', |
| 524 | 'description' => __( 'The label the customer sees for this payment method at checkout.', 'monokulo' ), |
| 525 | 'default' => __( 'Monero (via Monokulo)', 'monokulo' ), |
| 526 | 'desc_tip' => true, |
| 527 | ), |
| 528 | 'description' => array( |
| 529 | 'title' => __( 'Description', 'monokulo' ), |
| 530 | 'type' => 'textarea', |
| 531 | 'description' => __( 'The explanatory text the customer sees for this payment method at checkout.', 'monokulo' ), |
| 532 | 'default' => __( 'Pay with Monero. You will be redirected to a secure Monokulo payment page to complete your purchase.', 'monokulo' ), |
| 533 | 'desc_tip' => true, |
| 534 | ), |
| 535 | |
| 536 | 'connection' => array( |
| 537 | 'title' => __( 'Connection', 'monokulo' ), |
| 538 | 'type' => 'title', |
| 539 | 'description' => __( 'Click below to connect your Monero wallet through Monokulo - this fills in everything below automatically and enables this gateway.', 'monokulo' ), |
| 540 | ), |
| 541 | |
| 542 | // The real WBS 1.5.3 button - see `generate_monokulo_connect_html()`'s |
| 543 | // own doc comment for exactly what it renders and why this needs |
| 544 | // a genuinely custom field type rather than reusing `'title'` |
| 545 | // (whose own `generate_title_html()` renders fixed markup this |
| 546 | // step needs to deviate from: a real `<a href=...>` built fresh |
| 547 | // on every render, not static text). |
| 548 | 'connect' => array( |
| 549 | 'type' => 'monokulo_connect', |
| 550 | 'description' => __( 'You will be sent to Monokulo to sign in (or sign up) and confirm the connection, then returned here automatically.', 'monokulo' ), |
| 551 | ), |
| 552 | |
| 553 | 'endpoint' => array( |
| 554 | 'title' => __( 'Monokulo address', 'monokulo' ), |
| 555 | 'type' => 'text', |
| 556 | 'description' => __( 'The public address of the Monokulo instance this store uses. Filled in automatically by Connect above.', 'monokulo' ), |
| 557 | 'default' => '', |
| 558 | 'placeholder' => 'https://pay.example.com', |
| 559 | 'desc_tip' => true, |
| 560 | ), |
| 561 | 'public_key' => array( |
| 562 | 'title' => __( 'Store public key', 'monokulo' ), |
| 563 | 'type' => 'text', |
| 564 | 'description' => __( 'Your Monokulo store\'s public key (starts with pk_). Filled in automatically by Connect above.', 'monokulo' ), |
| 565 | 'default' => '', |
| 566 | 'placeholder' => 'pk_...', |
| 567 | 'desc_tip' => true, |
| 568 | ), |
| 569 | 'forward_errors' => array( |
| 570 | 'title' => __( 'Send errors to Monokulo', 'monokulo' ), |
| 571 | 'type' => 'checkbox', |
| 572 | 'label' => __( 'Also send this plugin\'s warnings and errors to Monokulo\'s Logs page', 'monokulo' ), |
| 573 | 'description' => __( 'They stay in WooCommerce\'s own logs as well. Sent with your store\'s secret key, next to Monokulo\'s own lines for the same request. Monokulo keeps them only while Diagnostics is turned on in this store\'s settings there.', 'monokulo' ), |
| 574 | 'default' => 'no', |
| 575 | 'desc_tip' => true, |
| 576 | ), |
| 577 | 'secret_token' => array( |
| 578 | 'title' => __( 'Store secret key', 'monokulo' ), |
| 579 | 'type' => 'password', |
| 580 | 'description' => __( 'Your Monokulo store\'s secret key (starts with sk_). Filled in automatically by Connect above. Used to create orders from this site\'s server; never shown to customers.', 'monokulo' ), |
| 581 | 'default' => '', |
| 582 | 'placeholder' => 'sk_...', |
| 583 | 'desc_tip' => true, |
| 584 | ), |
| 585 | ); |
| 586 | } |
| 600 | if ( ! parent::is_available() ) { |
| 601 | return false; |
| 600 | if ( ! parent::is_available() ) { |
| 604 | return $this->has_credentials() && ! $this->needs_reconnect(); |
| 604 | return $this->has_credentials() && ! $this->needs_reconnect(); |
| 604 | return $this->has_credentials() && ! $this->needs_reconnect(); |
| 605 | } |
| 600 | if ( ! parent::is_available() ) { |
| 604 | return $this->has_credentials() && ! $this->needs_reconnect(); |
| 604 | return $this->has_credentials() && ! $this->needs_reconnect(); |
| 605 | } |
| 2138 | private function log( $message, $level = 'info' ) { |
| 2139 | $this->maybe_forward( $message, $level ); |
| 2140 | if ( function_exists( 'wc_get_logger' ) ) { |
| 2143 | $trace_id = self::trace_id(); |
| 2144 | wc_get_logger()->log( |
| 2145 | $level, |
| 2146 | $message . ' [trace ' . $trace_id . ']', |
| 2147 | array( |
| 2148 | 'source' => 'monokulo', |
| 2149 | 'trace_id' => $trace_id, |
| 2150 | ) |
| 2151 | ); |
| 2152 | } |
| 2153 | } |
| 2153 | } |
| 2138 | private function log( $message, $level = 'info' ) { |
| 2139 | $this->maybe_forward( $message, $level ); |
| 2140 | if ( function_exists( 'wc_get_logger' ) ) { |
| 2153 | } |
| 1780 | private function mark_event_applied( WC_Order $order, $event_id ) { |
| 1781 | $applied = json_decode( (string) $order->get_meta( self::META_APPLIED_EVENT_IDS ), true ); |
| 1782 | $applied = is_array( $applied ) ? $applied : array(); |
| 1782 | $applied = is_array( $applied ) ? $applied : array(); |
| 1782 | $applied = is_array( $applied ) ? $applied : array(); |
| 1783 | $applied[] = $event_id; |
| 1784 | $order->update_meta_data( self::META_APPLIED_EVENT_IDS, wp_json_encode( array_values( array_unique( $applied ) ) ) ); |
| 1785 | } |
| 1780 | private function mark_event_applied( WC_Order $order, $event_id ) { |
| 1781 | $applied = json_decode( (string) $order->get_meta( self::META_APPLIED_EVENT_IDS ), true ); |
| 1782 | $applied = is_array( $applied ) ? $applied : array(); |
| 1782 | $applied = is_array( $applied ) ? $applied : array(); |
| 1782 | $applied = is_array( $applied ) ? $applied : array(); |
| 1783 | $applied[] = $event_id; |
| 1784 | $order->update_meta_data( self::META_APPLIED_EVENT_IDS, wp_json_encode( array_values( array_unique( $applied ) ) ) ); |
| 1785 | } |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2165 | return; |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2168 | return; |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2171 | 'level' => $level, |
| 2172 | 'message' => (string) $message, |
| 2173 | 'trace_id' => self::trace_id(), |
| 2174 | ); |
| 2175 | if ( ! self::$forward_registered ) { |
| 2176 | self::$forward_registered = true; |
| 2177 | $url = rtrim( $this->api_base_url, '/' ) . '/pay/' . rawurlencode( $this->tenant_public_key ) . '/logs'; |
| 2178 | $secret = $this->secret_token; |
| 2179 | register_shutdown_function( |
| 2180 | function () use ( $url, $secret ) { |
| 2181 | WC_Gateway_Monokulo::flush_forwarded( $url, $secret ); |
| 2182 | } |
| 2183 | ); |
| 2184 | } |
| 2185 | } |
| 2185 | } |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2171 | 'level' => $level, |
| 2172 | 'message' => (string) $message, |
| 2173 | 'trace_id' => self::trace_id(), |
| 2174 | ); |
| 2175 | if ( ! self::$forward_registered ) { |
| 2185 | } |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2168 | return; |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2171 | 'level' => $level, |
| 2172 | 'message' => (string) $message, |
| 2173 | 'trace_id' => self::trace_id(), |
| 2174 | ); |
| 2175 | if ( ! self::$forward_registered ) { |
| 2176 | self::$forward_registered = true; |
| 2177 | $url = rtrim( $this->api_base_url, '/' ) . '/pay/' . rawurlencode( $this->tenant_public_key ) . '/logs'; |
| 2178 | $secret = $this->secret_token; |
| 2179 | register_shutdown_function( |
| 2180 | function () use ( $url, $secret ) { |
| 2181 | WC_Gateway_Monokulo::flush_forwarded( $url, $secret ); |
| 2182 | } |
| 2183 | ); |
| 2184 | } |
| 2185 | } |
| 2185 | } |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2171 | 'level' => $level, |
| 2172 | 'message' => (string) $message, |
| 2173 | 'trace_id' => self::trace_id(), |
| 2174 | ); |
| 2175 | if ( ! self::$forward_registered ) { |
| 2185 | } |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2165 | return; |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2168 | return; |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2171 | 'level' => $level, |
| 2172 | 'message' => (string) $message, |
| 2173 | 'trace_id' => self::trace_id(), |
| 2174 | ); |
| 2175 | if ( ! self::$forward_registered ) { |
| 2176 | self::$forward_registered = true; |
| 2177 | $url = rtrim( $this->api_base_url, '/' ) . '/pay/' . rawurlencode( $this->tenant_public_key ) . '/logs'; |
| 2178 | $secret = $this->secret_token; |
| 2179 | register_shutdown_function( |
| 2180 | function () use ( $url, $secret ) { |
| 2181 | WC_Gateway_Monokulo::flush_forwarded( $url, $secret ); |
| 2182 | } |
| 2183 | ); |
| 2184 | } |
| 2185 | } |
| 2185 | } |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2171 | 'level' => $level, |
| 2172 | 'message' => (string) $message, |
| 2173 | 'trace_id' => self::trace_id(), |
| 2174 | ); |
| 2175 | if ( ! self::$forward_registered ) { |
| 2185 | } |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2168 | return; |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2171 | 'level' => $level, |
| 2172 | 'message' => (string) $message, |
| 2173 | 'trace_id' => self::trace_id(), |
| 2174 | ); |
| 2175 | if ( ! self::$forward_registered ) { |
| 2176 | self::$forward_registered = true; |
| 2177 | $url = rtrim( $this->api_base_url, '/' ) . '/pay/' . rawurlencode( $this->tenant_public_key ) . '/logs'; |
| 2178 | $secret = $this->secret_token; |
| 2179 | register_shutdown_function( |
| 2180 | function () use ( $url, $secret ) { |
| 2181 | WC_Gateway_Monokulo::flush_forwarded( $url, $secret ); |
| 2182 | } |
| 2183 | ); |
| 2184 | } |
| 2185 | } |
| 2185 | } |
| 2163 | private function maybe_forward( $message, $level ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2164 | if ( 'yes' !== $this->get_option( 'forward_errors', 'no' ) || ! $this->has_credentials() ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2167 | if ( ! in_array( $level, self::FORWARDED_LEVELS, true ) || count( self::$forward_queue ) >= self::MAX_FORWARDED ) { |
| 2171 | 'level' => $level, |
| 2172 | 'message' => (string) $message, |
| 2173 | 'trace_id' => self::trace_id(), |
| 2174 | ); |
| 2175 | if ( ! self::$forward_registered ) { |
| 2185 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2068 | return; |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2072 | echo '<div class="notice notice-success is-dismissible"><p>' . |
| 2073 | esc_html__( 'Your Monero wallet is connected - Monero payments are now enabled.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2068 | return; |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2072 | echo '<div class="notice notice-success is-dismissible"><p>' . |
| 2073 | esc_html__( 'Your Monero wallet is connected - Monero payments are now enabled.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2068 | return; |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2072 | echo '<div class="notice notice-success is-dismissible"><p>' . |
| 2073 | esc_html__( 'Your Monero wallet is connected - Monero payments are now enabled.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2068 | return; |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2072 | echo '<div class="notice notice-success is-dismissible"><p>' . |
| 2073 | esc_html__( 'Your Monero wallet is connected - Monero payments are now enabled.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2068 | return; |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2072 | echo '<div class="notice notice-success is-dismissible"><p>' . |
| 2073 | esc_html__( 'Your Monero wallet is connected - Monero payments are now enabled.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2068 | return; |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2072 | echo '<div class="notice notice-success is-dismissible"><p>' . |
| 2073 | esc_html__( 'Your Monero wallet is connected - Monero payments are now enabled.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2068 | return; |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2072 | echo '<div class="notice notice-success is-dismissible"><p>' . |
| 2073 | esc_html__( 'Your Monero wallet is connected - Monero payments are now enabled.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2068 | return; |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2072 | echo '<div class="notice notice-success is-dismissible"><p>' . |
| 2073 | esc_html__( 'Your Monero wallet is connected - Monero payments are now enabled.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2076 | echo '<div class="notice notice-error is-dismissible"><p>' . |
| 2077 | esc_html__( 'Monokulo is not ready to connect plugins yet: its operator has not set its public address. Please try again later.', 'monokulo' ) . |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2081 | esc_html__( 'Could not connect your Monero wallet. Please try again.', 'monokulo' ) . |
| 2082 | '</p></div>'; |
| 2083 | } |
| 2084 | } |
| 2084 | } |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2067 | if ( ! isset( $_GET['page'], $_GET['section'] ) || 'wc-settings' !== $_GET['page'] || $this->id !== $_GET['section'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2071 | if ( isset( $_GET['monokulo_connected'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2075 | } elseif ( isset( $_GET['monokulo_connect_error'] ) && 'unavailable' === $_GET['monokulo_connect_error'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2079 | } elseif ( isset( $_GET['monokulo_connect_error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 2084 | } |
| 624 | return self::settings_need_reconnect( $this->settings ); |
| 625 | } |
| 726 | public function process_payment( $order_id ) { |
| 727 | $order = wc_get_order( $order_id ); |
| 728 | if ( ! $order instanceof WC_Order ) { |
| 729 | throw new Exception( __( 'Could not load this order to start the Monero payment.', 'monokulo' ) ); |
| 726 | public function process_payment( $order_id ) { |
| 727 | $order = wc_get_order( $order_id ); |
| 728 | if ( ! $order instanceof WC_Order ) { |
| 732 | $monokulo_order = $this->create_monokulo_order( $order ); |
| 733 | |
| 734 | // The one point in this gateway's whole flow that ever sees the |
| 735 | // mapping between a WC order and Monokulo's order_id - recorded |
| 736 | // now, while it's in hand. The webhook receiver needs exactly this |
| 737 | // lookup (an incoming delivery carries an order_id, not a WC order |
| 738 | // id) to know which order to update. |
| 739 | $order->update_meta_data( self::META_ORDER_ID, $monokulo_order['order_id'] ); |
| 740 | $order->add_order_note( |
| 741 | sprintf( |
| 742 | /* translators: %s: Monokulo order_id */ |
| 743 | __( 'Customer redirected to Monokulo checkout for order_id %s.', 'monokulo' ), |
| 744 | $monokulo_order['order_id'] |
| 745 | ) |
| 746 | ); |
| 747 | $order->save(); |
| 748 | |
| 749 | return array( |
| 750 | 'result' => 'success', |
| 751 | 'redirect' => $this->get_checkout_url( $monokulo_order['order_id'] ), |
| 752 | ); |
| 753 | } |
| 653 | if ( ! current_user_can( 'manage_woocommerce' ) ) { |
| 654 | return; |
| 653 | if ( ! current_user_can( 'manage_woocommerce' ) ) { |
| 656 | if ( ! self::settings_need_reconnect( get_option( 'woocommerce_monokulo_settings' ) ) ) { |
| 657 | return; |
| 653 | if ( ! current_user_can( 'manage_woocommerce' ) ) { |
| 656 | if ( ! self::settings_need_reconnect( get_option( 'woocommerce_monokulo_settings' ) ) ) { |
| 659 | $settings_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=monokulo' ); |
| 660 | echo '<div class="notice notice-warning"><p><strong>' . |
| 661 | esc_html__( 'Monokulo: please reconnect your Monero wallet.', 'monokulo' ) . |
| 662 | '</strong> ' . |
| 663 | esc_html__( 'This store was connected by an older version of the Monokulo plugin, which can no longer create orders. Monero payments are hidden at checkout until you reconnect.', 'monokulo' ) . |
| 664 | ' <a href="' . esc_url( $settings_url ) . '">' . |
| 665 | esc_html__( 'Reconnect now', 'monokulo' ) . |
| 666 | '</a></p></div>'; |
| 667 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 639 | return false; |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 637 | public static function settings_need_reconnect( $settings ) { |
| 638 | if ( ! is_array( $settings ) ) { |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 641 | $connected = ! empty( $settings['endpoint'] ) || ! empty( $settings['public_key'] ) || ! empty( $settings['secret_token'] ); |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 642 | $version = isset( $settings['connection_version'] ) ? (string) $settings['connection_version'] : ''; |
| 643 | |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 644 | return $connected && self::CONNECTION_VERSION !== $version; |
| 645 | } |
| 2092 | if ( null === self::$trace_id ) { |
| 2093 | self::$trace_id = bin2hex( random_bytes( 16 ) ); |
| 2094 | } |
| 2095 | return self::$trace_id; |
| 2095 | return self::$trace_id; |
| 2096 | } |
| 2092 | if ( null === self::$trace_id ) { |
| 2095 | return self::$trace_id; |
| 2096 | } |
| 2124 | return '00-' . self::trace_id() . '-' . bin2hex( random_bytes( 8 ) ) . '-01'; |
| 2125 | } |
| 1181 | public function validate_monokulo_connect_field( $key, $value ) { |
| 1182 | return ''; |
| 1183 | } |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1667 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1671 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1674 | $expected = hash_hmac( 'sha256', $parts[1] . '.' . $raw_body, $this->webhook_signing_secret ); |
| 1675 | |
| 1676 | return hash_equals( $expected, strtolower( $parts[2] ) ); |
| 1677 | } |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1671 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1674 | $expected = hash_hmac( 'sha256', $parts[1] . '.' . $raw_body, $this->webhook_signing_secret ); |
| 1675 | |
| 1676 | return hash_equals( $expected, strtolower( $parts[2] ) ); |
| 1677 | } |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1667 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1671 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1674 | $expected = hash_hmac( 'sha256', $parts[1] . '.' . $raw_body, $this->webhook_signing_secret ); |
| 1675 | |
| 1676 | return hash_equals( $expected, strtolower( $parts[2] ) ); |
| 1677 | } |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1671 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1674 | $expected = hash_hmac( 'sha256', $parts[1] . '.' . $raw_body, $this->webhook_signing_secret ); |
| 1675 | |
| 1676 | return hash_equals( $expected, strtolower( $parts[2] ) ); |
| 1677 | } |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1667 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1671 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1674 | $expected = hash_hmac( 'sha256', $parts[1] . '.' . $raw_body, $this->webhook_signing_secret ); |
| 1675 | |
| 1676 | return hash_equals( $expected, strtolower( $parts[2] ) ); |
| 1677 | } |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1671 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1674 | $expected = hash_hmac( 'sha256', $parts[1] . '.' . $raw_body, $this->webhook_signing_secret ); |
| 1675 | |
| 1676 | return hash_equals( $expected, strtolower( $parts[2] ) ); |
| 1677 | } |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1667 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1671 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1674 | $expected = hash_hmac( 'sha256', $parts[1] . '.' . $raw_body, $this->webhook_signing_secret ); |
| 1675 | |
| 1676 | return hash_equals( $expected, strtolower( $parts[2] ) ); |
| 1677 | } |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1671 | return false; |
| 1661 | private function verify_webhook_signature( $raw_body, $signature, $now = null ) { |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1662 | if ( '' === $this->webhook_signing_secret || ! is_string( $signature ) |
| 1663 | || ! preg_match( '/\At=(\d{1,18}),v1=([0-9a-fA-F]{64})\z/', $signature, $parts ) ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1669 | if ( abs( ( null === $now ? time() : (int) $now ) - (int) $parts[1] ) > self::SIGNATURE_TOLERANCE_SECS ) { |
| 1674 | $expected = hash_hmac( 'sha256', $parts[1] . '.' . $raw_body, $this->webhook_signing_secret ); |
| 1675 | |
| 1676 | return hash_equals( $expected, strtolower( $parts[2] ) ); |
| 1677 | } |
| 2180 | function () use ( $url, $secret ) { |
| 2181 | WC_Gateway_Monokulo::flush_forwarded( $url, $secret ); |
| 2182 | } |