PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.7.0.9
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.7.0.9
1.8.13.1 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 All 212 releases
charitable / includes / stripe / gateway / class-charitable-stripe-webhook-api.php

class-charitable-stripe-webhook-api.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.7.0.9, at includes/stripe/gateway/class-charitable-stripe-webhook-api.php

422 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Get, update and deactivate webhooks.
4 *
5 * @package Charitable Stripe/Classes/Charitable_Stripe_Webhook_API
6 * @author Eric Daams
7 * @copyright Copyright (c) 2021-2022, Studio 164a
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.3.0
10 * @version 1.3.0
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Stripe_Webhook_API' ) ) :
19
20 /**
21 * Charitable_Stripe_Webhook_API
22 *
23 * @since 1.3.0
24 */
25 class Charitable_Stripe_Webhook_API {
26
27 /**
28 * Secret key.
29 *
30 * @since 1.3.0
31 *
32 * @var string
33 */
34 private $secret_key;
35
36 /**
37 * The webhook setting key, based on whether we're in test mode
38 * and whether this is for the Connect webhook.
39 *
40 * @since 1.3.0
41 *
42 * @var string
43 */
44 private $setting_key;
45
46 /**
47 * Whether this is for the Connect webhook.
48 *
49 * @since 1.3.0
50 *
51 * @var boolean
52 */
53 private $connect_application;
54
55 /**
56 * Gateway helper.
57 *
58 * @since 1.3.0
59 *
60 * @var Charitable_Gateway_Stripe_AM
61 */
62 private $gateway;
63
64 /**
65 * Create class object.
66 *
67 * @since 1.3.0
68 *
69 * @param boolean|null $test_mode Whether to use test mode or not. If left as
70 * null, the site test mode setting will be used.
71 * @param boolean|null $secret_key The api login id to use. If left as null, the
72 * stored setting will be used.
73 * @param boolean $connect_application Whether we want the Connect webhook.
74 */
75 public function __construct( $test_mode = null, $secret_key = null, $connect_application = false ) {
76 $this->test_mode = is_null( $test_mode ) ? charitable_get_option( 'test_mode', false ) : $test_mode;
77 $this->connect_application = $connect_application;
78 $this->secret_key = is_null( $secret_key ) ? $this->parse_secret_key() : $secret_key;
79 $this->setting_key = $this->parse_setting_key();
80 $this->gateway = new Charitable_Gateway_Stripe_AM();
81 }
82
83 /**
84 * Returns class properties.
85 *
86 * @since 1.3.0
87 *
88 * @param string $prop The property to return.
89 * @return mixed
90 */
91 public function __get( $prop ) {
92 return isset( $this->$prop ) ? $this->$prop : null;
93 }
94
95 /**
96 * Return the set of webhook event types we need to subscribe to.
97 *
98 * @since 1.3.0
99 *
100 * @return array
101 */
102 public function get_webhook_events() {
103 /**
104 * Filter the events that the webhook will be notified about.
105 *
106 * @since 1.3.0
107 *
108 * @param array $events The events that the webhook will be notified about.
109 */
110 return apply_filters(
111 'charitable_stripe_webhook_events',
112 [
113 'charge.refunded',
114 'invoice.created',
115 'invoice.payment_failed',
116 'invoice.payment_succeeded',
117 'invoice.payment_action_required',
118 'customer.subscription.updated',
119 'customer.subscription.deleted',
120 'payment_intent.payment_failed',
121 'payment_intent.succeeded',
122 'checkout.session.completed',
123 ],
124 $this->connect_application
125 );
126 }
127
128 /**
129 * Return the webhook listener endpoint.
130 *
131 * @since 1.3.0
132 *
133 * @return string
134 */
135 public function get_webhook_listener() {
136 return charitable_get_ipn_url( Charitable_Gateway_Stripe_AM::ID );
137 }
138
139 /**
140 * Add a new webhook.
141 *
142 * @since 1.3.0
143 *
144 * @return string The webhook id.
145 */
146 public function add_webhook() {
147 /**
148 * First check whether the webhook has already been added.
149 */
150 $webhook = $this->get_webhook();
151
152 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
153 error_log( 'add_webook' );
154 error_log( print_r( $webhook, true ) );
155 }
156
157 if ( false === $webhook ) {
158 try {
159
160 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
161 error_log( 'add_webook false' );
162 error_log( print_r( $this->secret_key, true ) );
163 }
164
165 $this->gateway->setup_api( $this->secret_key );
166
167 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
168 error_log( 'add_webook after setup_api' );
169 }
170
171 $webhook = \Stripe\WebhookEndpoint::create(
172 [
173 'url' => $this->get_webhook_listener(),
174 'enabled_events' => $this->get_webhook_events(),
175 'api_version' => Charitable_Gateway_Stripe_AM::STRIPE_API_VERSION,
176 'connect' => $this->connect_application,
177 ]
178 );
179
180 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
181 error_log( 'add_webook after WebhookEndpoint' );
182 error_log( print_r( $webhook, true ) );
183 }
184 // Override the signing secret, for teseting purposes (perhaps with the Stripe API CLI)
185 if ( defined( 'CHARITABLE_WEBHOOK_SIGNING_SECRET' ) && CHARITABLE_WEBHOOK_SIGNING_SECRET ) {
186 $webhook->secret = CHARITABLE_WEBHOOK_SIGNING_SECRET;
187 }
188 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
189 error_log( 'add_webook after WebhookEndpoint UPDATED FOR CLI' );
190 error_log( print_r( $webhook, true ) );
191 }
192
193 } catch ( Exception $e ) {
194 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
195 error_log(
196 sprintf(
197 __( 'Error creating Stripe webhook: %s', 'charitable-stripe' ),
198 $e->getMessage()
199 )
200 );
201 }
202
203 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
204 error_log( 'add_webhook ERROR' );
205 error_log( print_r( $e->getMessage(), true ) );
206 }
207
208 return 'invalid_request';
209 }
210 }
211
212 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
213 error_log( 'add_webook returning $webhook->id' );
214 error_log( print_r( $webhook->id, true ) );
215 }
216
217 return $webhook->id;
218 }
219
220 /**
221 * Returns the WebhookEndpoint object, or false if one doesn't exist yet.
222 *
223 * @since 1.3.0
224 *
225 * @return false|\Stripe\WebhookEndpoint
226 */
227 public function get_webhook() {
228 $webhook_id = charitable_get_option( [ 'gateways_stripe', $this->setting_key ], '' );
229
230 if ( ! $webhook_id ) {
231 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
232 error_log( 'get_webhook ! $webhook_id' );
233 }
234 return $this->has_webhook();
235 }
236
237 try {
238 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
239 error_log( 'get_webhook try setup_api using secret key ' . $this->secret_key );
240 }
241 $this->gateway->setup_api( $this->secret_key );
242
243 return \Stripe\WebhookEndpoint::retrieve( $webhook_id );
244 } catch ( Exception $e ) {
245 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
246 error_log( 'get_webhook try setup_api catch' );
247 }
248 return false;
249 }
250 }
251
252 /**
253 * Checks whether a matching webhook already exists within Stripe.
254 *
255 * @since 1.3.0
256 *
257 * @return false|\Stripe\WebhookEndpoint If a webhook exists, returns it.
258 * Otherwise, returns false.
259 */
260 public function has_webhook() {
261 try {
262 $this->gateway->setup_api( $this->secret_key );
263
264 $endpoints = \Stripe\WebhookEndpoint::all( [ 'limit' => 100 ] );
265
266 $endpoint_urls = $this->get_possible_endpoint_urls();
267
268 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
269 error_log( 'has_webhook endpoint_urls' );
270 error_log( print_r( $endpoint_urls, true ) );
271 error_log( print_r( $endpoints, true ) );
272 }
273
274 foreach ( $endpoints->data as $webhook ) {
275 if ( ! in_array( $webhook->url, $endpoint_urls ) ) {
276 continue;
277 }
278
279 /**
280 * If we're looking for a Connect application webhook, check that the application
281 * property is not null. Otherwise, make sure it is null.
282 */
283 if ( $this->connect_application ? is_null( $webhook->application ) : ! is_null( $webhook->application ) ) {
284 continue;
285 }
286
287 return $webhook;
288 }
289 } catch ( Exception $e ) {
290 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
291 error_log( var_export( $e, true ) );
292 }
293
294 return false;
295 }
296
297 return false;
298 }
299
300 /**
301 * Checks if a webhook needs an update.
302 *
303 * @since 1.3.0
304 *
305 * @param \Stripe\WebhookEndpoint $webhook The webhook endpoint object.
306 * @return boolean
307 */
308 public function webhook_needs_update( $webhook ) {
309 /* The webhook is not enabled, so it needs an update. */
310 if ( 'enabled' != $webhook->status ) {
311 return true;
312 }
313
314 /* The webhook is not sending some events we need, so we need to update it. */
315 if ( ! in_array( '*', $webhook->enabled_events ) && ! empty( array_diff( $this->get_webhook_events(), $webhook->enabled_events ) ) ) {
316 return true;
317 }
318
319 return false;
320 }
321
322 /**
323 * Update the webhook.
324 *
325 * @since 1.3.0
326 *
327 * @return boolean
328 */
329 public function update_webhook() {
330 $webhook = $this->get_webhook();
331
332 if ( ! $webhook ) {
333 return false;
334 }
335
336 try {
337 $this->gateway->setup_api( $this->secret_key );
338
339 $webhook = \Stripe\WebhookEndpoint::retrieve( $webhook->id );
340 $webhook->disabled = false;
341 $webhook->enabled_events = $this->get_webhook_events();
342 $webhook->save();
343
344 return true;
345 } catch ( Exception $e ) {
346 return false;
347 }
348 }
349
350 /**
351 * Deactivate the webhook.
352 *
353 * @since 1.3.0
354 *
355 * @return boolean
356 */
357 public function deactivate_webhook() {
358 $webhook = $this->get_webhook();
359
360 if ( ! $webhook ) {
361 return false;
362 }
363
364 try {
365 $this->gateway->setup_api( $this->secret_key );
366
367 $webhook = \Stripe\WebhookEndpoint::retrieve( $webhook->id );
368 $webhook->disabled = true;
369 $webhook->save();
370 } catch ( Exception $e ) {
371 return false;
372 }
373 }
374
375 /**
376 * Returns the setting key to use based on whether it's test mode and whether it's for the Connect webhook.
377 *
378 * @since 1.3.0
379 *
380 * @return string
381 */
382 private function parse_setting_key() {
383 if ( $this->test_mode ) {
384 return $this->connect_application ? 'test_connect_webhook_id' : 'test_webhook_id';
385 }
386
387 return $this->connect_application ? 'live_connect_webhook_id' : 'live_webhook_id';
388 }
389
390 /**
391 * Return the secret key to use based on whether it's test mode.
392 *
393 * @since 1.3.0
394 *
395 * @return string
396 */
397 private function parse_secret_key() {
398 $setting = $this->test_mode ? 'test_secret_key' : 'live_secret_key';
399
400 return charitable_get_option( [ 'gateways_stripe', $setting ] );
401 }
402
403 /**
404 * Return all possible webhook URLs.
405 *
406 * @since 1.3.0
407 *
408 * @return string[]
409 */
410 private function get_possible_endpoint_urls() {
411 $home_url = home_url();
412
413 return [
414 sprintf( '%s/charitable-listener/%s', untrailingslashit( $home_url ), Charitable_Gateway_Stripe_AM::ID ),
415 esc_url_raw( add_query_arg( [ 'charitable-listener' => Charitable_Gateway_Stripe_AM::ID ], trailingslashit( $home_url ) ) ),
416 esc_url_raw( add_query_arg( [ 'charitable-listener' => Charitable_Gateway_Stripe_AM::ID ], untrailingslashit( $home_url ) ) ),
417 ];
418 }
419 }
420
421 endif;
422