PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
charitable / includes / square / admin / class-charitable-square-webhooksmanager.php

class-charitable-square-webhooksmanager.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at includes/square/admin/class-charitable-square-webhooksmanager.php

398 lines 12.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The class responsible for adding & saving extra settings in the Charitable admin.
4 *
5 * @package Charitable Square/Classes/Charitable_Square_WebhooksManager
6 * @author David Bisset
7 * @copyright Copyright (c) 2021-2022, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.8.7
10 */
11
12 // Exit if accessed directly.
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 if ( ! class_exists( 'Charitable_Square_WebhooksManager' ) ) :
18
19 /**
20 * Webhooks Manager.
21 *
22 * @since 1.8.7
23 */
24 class Charitable_Square_WebhooksManager {
25
26 /**
27 * Square client.
28 *
29 * @since 1.8.7
30 *
31 * @var SquareClient
32 */
33 private $client;
34
35 /**
36 * Create webhook endpoint.
37 * Retrieve the existing one when the endpoint already exists.
38 *
39 * @since 1.8.7
40 */
41 public function connect() {
42
43 if ( charitable_is_debug( 'webhook' ) ) {
44 // phpcs:disable
45 error_log( 'testing for reval - Charitable_Square_WebhooksManager::connect' );
46 // phpcs:enable
47 }
48
49 // Security and permissions check.
50 if (
51 ! check_ajax_referer( 'charitable-admin', 'nonce', false ) ||
52 ! current_user_can( 'manage_options' )
53 ) {
54 wp_send_json_error( array( 'message ' => esc_html__( 'You are not allowed to perform this action', 'charitable' ) ) );
55 }
56
57 $personal_access_token = ! empty( $_POST['token'] ) ? sanitize_text_field( wp_unslash( $_POST['token'] ) ) : '';
58
59 if ( empty( $personal_access_token ) ) {
60 wp_send_json_error( array( 'message' => esc_html__( 'Personal access token is required.', 'charitable' ) ) );
61 }
62
63 $webhook = $this->create( $personal_access_token );
64
65 // Register webhook health check.
66 ( new Charitable_Square_WebhooksHealthCheck() )->maybe_schedule_check();
67
68 // Store endpoint ID and secret.
69 if ( ! empty( $webhook ) ) {
70 $this->save_settings( $webhook );
71
72 wp_send_json_success( array( 'message' => esc_html__( 'Webhook created successfully!', 'charitable' ) ) );
73 }
74
75 wp_send_json_error( array( 'message' => esc_html__( 'Failed to create webhook.', 'charitable' ) ) );
76 }
77
78 /**
79 * Create or update a webhook endpoint.
80 *
81 * @since 1.8.7
82 *
83 * @param string $personal_access_token Personal access token.
84 *
85 * @return array Endpoint ID and secret.
86 */
87 private function create( string $personal_access_token ): array {
88
89 $this->client = new \Square\SquareClient(
90 token: $personal_access_token,
91 options: array(
92 'baseUrl' => charitable_get_option( 'test_mode' ) ? \Square\Environments::Sandbox->value : \Square\Environments::Production->value,
93 )
94 );
95
96 // Check if the webhook already exists.
97 $existing_webhook = $this->webhook_exists();
98
99 if ( charitable_is_debug( 'webhook' ) ) {
100 // phpcs:disable
101 error_log( 'testing for reval - Charitable_Square_WebhooksManager::create' );
102 error_log( 'creating webhook - existing webhook: ' . print_r( $existing_webhook, true ) );
103 // phpcs:enable
104 }
105
106 // Prepare a webhook subscription object.
107 $webhook_subscription = new \Square\Types\WebhookSubscription();
108 $webhook_subscription->setName( sprintf( 'WPCharitable endpoint (%1$s mode)', charitable_get_option( 'test_mode' ) ? 'test' : 'live' ) );
109 $webhook_subscription->setNotificationUrl( Charitable_Square_Helpers::get_webhook_url() );
110 // $webhook_subscription->setEventTypes( WebhookRoute::get_webhooks_events_list() );
111
112 // Updated to include all events.
113 $webhook_subscription->setEventTypes(
114 array(
115 'payment.created',
116 'payment.updated',
117 'refund.updated',
118 'subscription.created',
119 'subscription.updated',
120 'invoice.payment_made',
121 'oauth.authorization.revoked',
122 )
123 );
124
125 if ( $existing_webhook ) {
126 try {
127 // Create an update request and set the subscription payload.
128 $request = new \Square\Webhooks\Subscriptions\Requests\UpdateWebhookSubscriptionRequest(
129 array(
130 'subscriptionId' => $existing_webhook['id'],
131 'subscription' => $webhook_subscription,
132 )
133 );
134
135 // Update the existing webhook subscription.
136 $response = $this->client->webhooks->subscriptions->update( $request );
137
138 // Check for errors instead of using isSuccess().
139 if ( empty( $response->getErrors() ) ) {
140 // Get subscription directly from the response.
141 $subscription = $response->getSubscription();
142
143 // Check if subscription is not null before accessing methods.
144 if ( $subscription ) {
145 return array(
146 'id' => $subscription->getId(),
147 'signature_key' => $existing_webhook['signature_key'] ?? '', // getSignatureKey() isn't available in the update response, fall back to the existing webhook's signature key.
148 );
149 }
150 }
151 // If the update fails or subscription is null, return the existing webhook details.
152 return $existing_webhook;
153 } catch ( Exception $e ) {
154 // Log the exception or handle it appropriately.
155 if ( charitable_is_debug( 'webhook' ) ) {
156 // phpcs:disable
157 error_log( 'Error updating Square webhook: ' . $e->getMessage() );
158 // phpcs:enable
159 }
160 return $existing_webhook;
161 }
162 }
163
164 // Create a new webhook subscription if none exists.
165 $request = new \Square\Webhooks\Subscriptions\Requests\CreateWebhookSubscriptionRequest(
166 array(
167 'subscription' => $webhook_subscription,
168 'idempotencyKey' => uniqid(),
169 )
170 );
171
172 try {
173 // Create the webhook subscription.
174 $response = $this->client->webhooks->subscriptions->create( $request );
175
176 // Check for errors instead of using isSuccess().
177 if ( empty( $response->getErrors() ) ) {
178 // Get subscription directly from the response.
179 $subscription = $response->getSubscription();
180
181 // Check if subscription is not null before accessing methods.
182 if ( $subscription ) {
183 return array(
184 'id' => $subscription->getId(),
185 'signature_key' => $subscription->getSignatureKey(),
186 );
187 }
188 }
189
190 // Log error if webhook creation failed or subscription is null.
191 if ( charitable_is_debug( 'webhook' ) ) {
192 // phpcs:disable
193 error_log( 'Failed to create Square webhook or subscription data is missing. Errors: ' . print_r( $response->getErrors(), true ) );
194 // phpcs:enable
195 }
196 return array();
197 } catch ( Exception $e ) {
198 // Log the exception or handle it appropriately.
199 if ( charitable_is_debug( 'webhook' ) ) {
200 // phpcs:disable
201 error_log( 'Error creating Square webhook: ' . $e->getMessage() );
202 // phpcs:enable
203 }
204 return array();
205 }
206 }
207
208 /**
209 * Check if webhook already exists.
210 *
211 * @since 1.8.7
212 *
213 * @return array
214 */
215 private function webhook_exists(): array {
216
217 try {
218 $pager = $this->client->webhooks->subscriptions->list();
219
220 // Get all subscriptions from the pager.
221 $subscriptions = array();
222 foreach ( $pager as $subscription ) {
223 $subscriptions[] = $subscription;
224 }
225
226 /*
227 (
228 [0] => Square\Types\WebhookSubscription Object
229 (
230 [__additionalProperties:Square\Core\Json\JsonSerializableType:private] => Array
231 (
232 )
233
234 [id:Square\Types\WebhookSubscription:private] => wbhk_341dd1cb2984412e806931f760732883
235 [name:Square\Types\WebhookSubscription:private] => testing-gamma-long-url
236 [enabled:Square\Types\WebhookSubscription:private] => 1
237 [eventTypes:Square\Types\WebhookSubscription:private] => Array
238 (
239 [0] => customer.created
240 [1] => customer.deleted
241 [2] => customer.updated
242 [3] => invoice.canceled
243 [4] => invoice.created
244 [5] => invoice.deleted
245 [6] => invoice.payment_made
246 [7] => invoice.published
247 [8] => invoice.refunded
248 [9] => invoice.scheduled_charge_failed
249 [10] => invoice.updated
250 [11] => order.created
251 [12] => order.updated
252 [13] => payment.created
253 [14] => payment.updated
254 [15] => refund.created
255 [16] => refund.updated
256 [17] => subscription.created
257 [18] => subscription.updated
258 )
259
260 [notificationUrl:Square\Types\WebhookSubscription:private] => https://a9c6-2600-1700-d30-876f-c8b3-3e76-41fe-961d.ngrok-free.app?charitable-listener=square
261 [apiVersion:Square\Types\WebhookSubscription:private] => 2025-03-19
262 [signatureKey:Square\Types\WebhookSubscription:private] =>
263 [createdAt:Square\Types\WebhookSubscription:private] => 2025-04-14 15:49:23 +0000 UTC
264 [updatedAt:Square\Types\WebhookSubscription:private] => 2025-04-16 15:52:08 +0000 UTC
265 )
266
267 )
268 */
269
270 if ( empty( $subscriptions ) ) {
271 return array();
272 }
273
274 $endpoint_urls = $this->get_possible_endpoint_urls();
275
276 foreach ( $subscriptions as $subscription ) {
277 if ( ! in_array( $subscription->getNotificationUrl(), $endpoint_urls, true ) ) {
278 continue;
279 }
280
281 // Found existing webhook matching one of the possible URLs.
282 return array(
283 'id' => $subscription->getId(),
284 'signature_key' => $subscription->getSignatureKey(),
285 );
286 }
287 } catch ( Exception $e ) {
288 // Log the exception or handle it appropriately.
289 if ( charitable_is_debug() ) {
290 error_log( 'Error listing Square webhooks: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
291 }
292 return array();
293 }
294
295 return array();
296 }
297
298 /**
299 * Remove a webhook.
300 *
301 * @since 1.8.7
302 *
303 * @param string $personal_access_token Personal access token.
304 * @param string $webhook_id Webhook ID.
305 *
306 * @return bool True if webhook was successfully deleted, false otherwise.
307 */
308 public function remove_webhook( string $personal_access_token, string $webhook_id ) {
309 try {
310 $this->client = new \Square\SquareClient(
311 token: $personal_access_token,
312 options: array(
313 'baseUrl' => charitable_get_option( 'test_mode' ) ? \Square\Environments::Sandbox->value : \Square\Environments::Production->value,
314 )
315 );
316
317 // Create delete request with the webhook subscription ID.
318 $request = new \Square\Webhooks\Subscriptions\Requests\DeleteSubscriptionsRequest(
319 array(
320 'subscriptionId' => $webhook_id,
321 )
322 );
323
324 // Send the delete request.
325 $response = $this->client->webhooks->subscriptions->delete( $request );
326
327 // Check if there were any errors.
328 if ( empty( $response->getErrors() ) ) {
329 return true;
330 }
331
332 // Log errors if any occurred.
333 if ( charitable_is_debug( 'square' ) ) {
334 error_log( 'Error deleting Square webhook: ' . print_r( $response->getErrors(), true ) ); // phpcs:ignore
335 }
336 return false;
337
338 } catch ( Exception $e ) {
339 // Log any exceptions that might occur.
340 if ( charitable_is_debug( 'square' ) ) {
341 error_log( 'Exception deleting Square webhook: ' . $e->getMessage() ); // phpcs:ignore
342 }
343 return false;
344 }
345 }
346
347 /**
348 * Save webhook settings.
349 *
350 * @since 1.8.7
351 *
352 * @param array $webhook Webhook endpoint.
353 */
354 private function save_settings( array $webhook ) {
355
356 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
357 $settings = (array) get_option( 'charitable_settings', array() );
358
359 // Save webhooks endpoint ID and secret.
360 $settings['gateways_square'][ $mode ]['webhooks-id'] = sanitize_text_field( $webhook['id'] );
361 $settings['gateways_square'][ $mode ]['webhooks-secret'] = sanitize_text_field( $webhook['signature_key'] );
362 // Ensure webhooks are enabled.
363 $settings['gateways_square']['square_enable_webhooks'] = 1;
364
365 ( new Charitable_Square_WebhooksHealthCheck() )->save_status( Charitable_Square_WebhooksHealthCheck::ENDPOINT_OPTION, Charitable_Square_WebhooksHealthCheck::STATUS_OK );
366
367 update_option( 'charitable_settings', $settings );
368 }
369
370 /**
371 * Return all possible webhook URLs.
372 *
373 * @since 1.8.7
374 *
375 * @return string[]
376 */
377 private function get_possible_endpoint_urls() {
378 $home_url = home_url();
379
380 if ( charitable_is_debug( 'webhook' ) ) {
381 // phpcs:disable
382 error_log( 'get_possible_endpoint_urls in the webhooks manager' );
383 // phpcs:enable
384 }
385
386 return apply_filters(
387 'charitable_square_webhook_urls',
388 array(
389 sprintf( '%s/charitable-listener/%s', untrailingslashit( $home_url ), Charitable_Gateway_Square::ID ),
390 esc_url_raw( add_query_arg( array( 'charitable-listener' => Charitable_Gateway_Square::ID ), trailingslashit( $home_url ) ) ),
391 esc_url_raw( add_query_arg( array( 'charitable-listener' => Charitable_Gateway_Square::ID ), untrailingslashit( $home_url ) ) ),
392 )
393 );
394 }
395 }
396
397 endif;
398