PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
← All changes | stripe/controllers/FrmStrpLiteEventsController.php +105 -33 6.25 → trunk View file →
@@ -9,11 +9,26 @@
9 9 * @var string
10 10 */
11 11 public static $events_to_skip_option_name = 'frm_strp_events_to_skip';
12 12
13 + /**
14 + * @var object|null
15 + */
13 16 private $event;
17 +
18 + /**
19 + * @var object|null
20 + */
14 21 private $invoice;
22 +
23 + /**
24 + * @var string|null
25 + */
15 26 private $charge;
27 +
28 + /**
29 + * @var string|null
30 + */
16 31 private $status;
17 32
18 33 /**
19 34 * @return void
@@ -68,9 +83,11 @@
68 83 }
69 84
70 85 FrmTransLiteAppHelper::add_note_to_payment( $payment_values, $note );
71 86
72 - $u = $frm_payment->update( $payment->id, $payment_values );
87 + // Read the status again right before the update, in case another request has already changed it.
88 + $payment_status_still_does_not_match = $this->payment_status_still_does_not_match( $payment->id );
89 + $updated = $frm_payment->update( $payment->id, $payment_values );
73 90
74 91 echo json_encode(
75 92 array(
76 93 'response' => 'Payment ' . $payment->id . ' was updated',
@@ -76,9 +93,10 @@
76 93 'response' => 'Payment ' . $payment->id . ' was updated',
77 94 'success' => true,
78 95 )
79 96 );
80 - if ( ! $is_partial_refund ) {
97 +
98 + if ( ! $is_partial_refund && $payment_status_still_does_not_match && $updated ) {
81 99 $run_triggers = true;
82 100 }
83 101 }//end if
84 102
@@ -92,8 +110,26 @@
92 110 }
93 111 }
94 112
95 113 /**
114 + * Double check that the payment status has not changed.
115 + * This is to avoid running actions twice by mistake, since a Stripe Link
116 + * return URL and a webhook event can both process the same payment.
117 + *
118 + * @since 6.35
119 + *
120 + * @param int $payment_id The id of the payment to check.
121 + *
122 + * @return bool
123 + */
124 + private function payment_status_still_does_not_match( $payment_id ) {
125 + $frm_payment = new FrmTransLitePayment();
126 + $payment = $frm_payment->get_one( $payment_id );
127 +
128 + return $payment && $payment->status !== $this->status;
129 + }
130 +
131 + /**
96 132 * Skip updating the payment object for the first recurring payment.
97 133 * This is to prevent double notifications because the first recurring payment creates an invoice and that invoice triggers the payment events.
98 134 *
99 135 * @since 6.5, introduced in v2.07 of the Stripe add on.
@@ -98,8 +134,9 @@
98 134 *
99 135 * @since 6.5, introduced in v2.07 of the Stripe add on.
100 136 *
101 137 * @param stdClass $payment
138 + *
102 139 * @return bool
103 140 */
104 141 private function should_skip_status_update_for_first_recurring_payment( $payment ) {
105 142 if ( ! in_array( $this->event->type, array( 'payment_intent.succeeded', 'payment_intent.payment_failed' ), true ) ) {
@@ -146,14 +183,16 @@
146 183 /**
147 184 * When a customer is deleted in Stripe, remove the link to a user.
148 185 *
149 186 * @since 6.5, introduced in v2.01 of the Stripe add on.
187 + *
150 188 * @return void
151 189 */
152 190 private function reset_customer() {
153 191 global $wpdb;
154 192 $customer_id = $this->invoice->id;
155 - if ( empty( $customer_id ) ) {
193 +
194 + if ( ! $customer_id ) {
156 195 return;
157 196 }
158 197 $wpdb->query(
159 198 $wpdb->prepare(
@@ -167,8 +206,9 @@
167 206 /**
168 207 * @return void
169 208 */
170 209 private function maybe_subscription_canceled() {
210 + // phpcs:ignore Universal.Operators.StrictComparisons
171 211 if ( $this->invoice->cancel_at_period_end == true ) {
172 212 $this->subscription_canceled( 'future_cancel' );
173 213 }
174 214 }
@@ -174,12 +214,14 @@
174 214 }
175 215
176 216 /**
177 217 * @param string $status
218 + *
178 219 * @return bool
179 220 */
180 221 private function subscription_canceled( $status = 'canceled' ) {
181 222 $sub = $this->get_subscription( $this->invoice->id );
223 +
182 224 if ( ! $sub ) {
183 225 return false;
184 226 }
185 227
@@ -202,8 +244,11 @@
202 244 );
203 245 return true;
204 246 }
205 247
248 + /**
249 + * @return false|object
250 + */
206 251 private function prepare_from_invoice() {
207 252 if ( empty( $this->invoice->subscription ) ) {
208 253 // This isn't a subscription.
209 254 echo json_encode(
@@ -215,8 +260,9 @@
215 260 return false;
216 261 }
217 262
218 263 $sub = $this->get_subscription( $this->invoice->subscription );
264 +
219 265 if ( ! $sub ) {
220 266 return false;
221 267 }
222 268
@@ -238,11 +284,9 @@
238 284 }
239 285
240 286 $this->maybe_cancel_subscription( $sub );
241 287 $this->update_next_bill_date( $sub, $payment_values );
242 -
243 - $payment = $frm_payment->get_one( $payment_id );
244 - return $payment;
288 + return $frm_payment->get_one( $payment_id );
245 289 }
246 290
247 291 /**
248 292 * Check if a subscription has reached its payment limit.
@@ -250,12 +294,14 @@
250 294 *
251 295 * @since 6.11
252 296 *
253 297 * @param object $sub
298 + *
254 299 * @return void
255 300 */
256 301 private function maybe_cancel_subscription( $sub ) {
257 302 $action = FrmFormAction::get_single_action_type( $sub->action_id, 'payment' );
303 +
258 304 // @phpstan-ignore-next-line
259 305 if ( ! is_object( $action ) || empty( $action->post_content['payment_limit'] ) ) {
260 306 return;
261 307 }
@@ -265,8 +311,9 @@
265 311 // Form ID.
266 312 (int) $action->menu_order,
267 313 (int) $sub->item_id
268 314 );
315 +
269 316 if ( is_wp_error( $payment_limit ) ) {
270 317 FrmTransLiteLog::log_message( 'Invalid payment limit value', $payment_limit->get_error_message() );
271 318 return;
272 319 }
@@ -282,9 +329,12 @@
282 329 return true;
283 330 };
284 331
285 332 add_filter( $hook, $filter, 99 );
286 - $cancelled = FrmStrpLiteApiHelper::cancel_subscription( $sub->sub_id );
333 +
334 + // There is no logged in user when a webhook event is processed, so the customer check has to be skipped here.
335 + $cancelled = FrmStrpLiteAppHelper::call_stripe_helper_class( 'cancel_subscription_without_customer_check', $sub->sub_id );
336 +
287 337 if ( $cancelled ) {
288 338 FrmTransLiteSubscriptionsController::change_subscription_status(
289 339 array(
290 340 'status' => 'future_cancel',
@@ -290,9 +340,12 @@
290 340 'status' => 'future_cancel',
291 341 'sub' => $sub,
292 342 )
293 343 );
344 + } else {
345 + FrmTransLiteLog::log_message( 'Stripe Webhook Message', 'Unable to cancel subscription ' . $sub->sub_id . ' after it reached its payment limit.' );
294 346 }
347 +
295 348 remove_filter( $hook, $filter, 99 );
296 349 }
297 350
298 351 /**
@@ -300,16 +353,16 @@
300 353 *
301 354 * @since 6.11
302 355 *
303 356 * @param string $sub_id Stripe subscriptino id prefixed with 'sub_'.
357 + *
304 358 * @return int
305 359 */
306 360 private function get_payments_count( $sub_id ) {
307 361 $frm_payment = new FrmTransLitePayment();
308 362 $all_payments = $frm_payment->get_all_by( $sub_id, 'sub_id' );
309 - $count = FrmTransLiteAppHelper::count_completed_payments( $all_payments );
310 363
311 - return $count;
364 + return FrmTransLiteAppHelper::count_completed_payments( $all_payments );
312 365 }
313 366
314 367 /**
315 368 * @since 6.5, introduced in v2.07 of the Stripe add on.
@@ -314,17 +367,24 @@
314 367 /**
315 368 * @since 6.5, introduced in v2.07 of the Stripe add on.
316 369 *
317 370 * @param stdClass $payment
371 + *
318 372 * @return bool
319 373 */
320 374 private function is_first_payment( $payment ) {
321 - return ! $payment->receipt_id || 0 === strpos( $payment->receipt_id, 'pi_' );
375 + return ! $payment->receipt_id || str_starts_with( $payment->receipt_id, 'pi_' );
322 376 }
323 377
378 + /**
379 + * @param string $sub_id
380 + *
381 + * @return object|null
382 + */
324 383 private function get_subscription( $sub_id ) {
325 384 $frm_sub = new FrmTransLiteSubscription();
326 385 $sub = $frm_sub->get_one_by( $sub_id, 'sub_id' );
386 +
327 387 if ( ! $sub ) {
328 388 // If this isn't an existing subscription, it must be a charge for another site/plugin.
329 389 FrmTransLiteLog::log_message( 'Stripe Webhook Message', 'No action taken since there is not a matching subscription for ' . $sub_id );
330 390 echo json_encode(
@@ -337,8 +397,13 @@
337 397
338 398 return $sub;
339 399 }
340 400
401 + /**
402 + * @param string $sub_id
403 + *
404 + * @return object|null
405 + */
341 406 private function get_payment_for_sub( $sub_id ) {
342 407 $frm_payment = new FrmTransLitePayment();
343 408 return $frm_payment->get_one_by( $sub_id, 'sub_id' );
344 409 }
@@ -344,8 +409,9 @@
344 409 }
345 410
346 411 /**
347 412 * @param array $payment_values
413 + *
348 414 * @return void
349 415 */
350 416 private function set_payment_values( &$payment_values ) {
351 417 $payment_values['begin_date'] = gmdate( 'Y-m-d' );
@@ -367,12 +433,14 @@
367 433
368 434 /**
369 435 * @param object $sub
370 436 * @param array $payment
437 + *
371 438 * @return void
372 439 */
373 440 private function update_next_bill_date( $sub, $payment ) {
374 441 $frm_sub = new FrmTransLiteSubscription();
442 +
375 443 if ( $payment['status'] === 'complete' ) {
376 444 $frm_sub->update( $sub->id, array( 'next_bill_date' => $payment['expire_date'] ) );
377 445 } elseif ( $payment['status'] === 'refunded' ) {
378 446 $frm_sub->update( $sub->id, array( 'next_bill_date' => $payment['begin_date'] ) );
@@ -382,19 +450,20 @@
382 450 /**
383 451 * @return bool
384 452 */
385 453 private function is_partial_refund() {
386 - $partial = false;
387 - if ( $this->status === 'refunded' ) {
388 - $amount = $this->invoice->amount;
389 - $amount_refunded = $this->invoice->amount_refunded;
390 - $partial = $amount != $amount_refunded;
454 + if ( $this->status !== 'refunded' ) {
455 + return false;
391 456 }
392 - return $partial;
457 +
458 + $amount = $this->invoice->amount;
459 + $amount_refunded = $this->invoice->amount_refunded;
460 + return $amount !== $amount_refunded;
393 461 }
394 462
395 463 /**
396 464 * @param array $payment_values
465 + *
397 466 * @return void
398 467 */
399 468 private function set_partial_refund( &$payment_values ) {
400 469 $payment_values['amount'] = $this->invoice->amount - $this->invoice->amount_refunded;
@@ -407,11 +476,13 @@
407 476 public function process_connect_events() {
408 477 $this->flush_response();
409 478
410 479 $unprocessed_event_ids = FrmStrpLiteConnectHelper::get_unprocessed_event_ids();
480 +
411 481 if ( $unprocessed_event_ids ) {
412 482 $this->process_event_ids( $unprocessed_event_ids );
413 483 }
484 +
414 485 wp_send_json_success();
415 486 }
416 487
417 488 /**
@@ -417,8 +488,9 @@
417 488 /**
418 489 * @since 6.5, introduced in v2.07 of the Stripe add on.
419 490 *
420 491 * @param array<string> $event_ids
492 + *
421 493 * @return void
422 494 */
423 495 private function process_event_ids( $event_ids ) {
424 496 foreach ( $event_ids as $event_id ) {
@@ -428,15 +500,17 @@
428 500
429 501 set_transient( 'frm_last_process_' . $event_id, time(), 60 );
430 502
431 503 $this->event = FrmStrpLiteConnectHelper::get_event( $event_id );
432 - if ( is_object( $this->event ) ) {
433 - $this->handle_event();
434 - $this->track_handled_event( $event_id );
435 - FrmStrpLiteConnectHelper::process_event( $event_id );
436 - } else {
504 +
505 + if ( ! is_object( $this->event ) ) {
437 506 $this->count_failed_event( $event_id );
507 + continue;
438 508 }
509 +
510 + $this->handle_event();
511 + $this->track_handled_event( $event_id );
512 + FrmStrpLiteConnectHelper::process_event( $event_id );
439 513 }
440 514 }
441 515
442 516 /**
@@ -442,8 +516,9 @@
442 516 /**
443 517 * @since 6.5, introduced in v2.07 of the Stripe add on.
444 518 *
445 519 * @param string $event_id
520 + *
446 521 * @return bool True if the event should be skipped.
447 522 */
448 523 private function should_skip_event( $event_id ) {
449 524 if ( $this->last_attempt_to_process_event_is_too_recent( $event_id ) ) {
@@ -450,17 +525,15 @@
450 525 return true;
451 526 }
452 527
453 528 $option = get_option( self::$events_to_skip_option_name );
454 - if ( ! is_array( $option ) ) {
455 - return false;
456 - }
457 529
458 - return in_array( $event_id, $option, true );
530 + return is_array( $option ) && in_array( $event_id, $option, true );
459 531 }
460 532
461 533 /**
462 534 * @param string $event_id
535 + *
463 536 * @return bool
464 537 */
465 538 private function last_attempt_to_process_event_is_too_recent( $event_id ) {
466 539 $last_process_attempt = get_transient( 'frm_last_process_' . $event_id );
@@ -470,20 +543,17 @@
470 543 /**
471 544 * @since 6.5, introduced in v2.07 of the Stripe add on.
472 545 *
473 546 * @param string $event_id
547 + *
474 548 * @return void
475 549 */
476 550 private function count_failed_event( $event_id ) {
477 - $transient_name = 'frm_failed_event_' . $event_id;
478 - $transient = get_transient( $transient_name );
479 - if ( is_int( $transient ) ) {
480 - $failed_count = $transient + 1;
481 - } else {
482 - $failed_count = 1;
483 - }
551 + $transient_name = 'frm_failed_event_' . $event_id;
552 + $transient = get_transient( $transient_name );
553 + $failed_count = is_int( $transient ) ? $transient + 1 : 1;
554 + $maximum_retries = 3;
484 555
485 - $maximum_retries = 3;
486 556 if ( $failed_count >= $maximum_retries ) {
487 557 $this->track_handled_event( $event_id );
488 558 } else {
489 559 set_transient( $transient_name, $failed_count, 4 * DAY_IN_SECONDS );
@@ -496,8 +566,9 @@
496 566 *
497 567 * @since 6.5, introduced in v2.07 of the Stripe add on.
498 568 *
499 569 * @param string $event_id
570 + *
500 571 * @return void
501 572 */
502 573 private function track_handled_event( $event_id ) {
503 574 $option = get_option( self::$events_to_skip_option_name );
@@ -520,8 +591,9 @@
520 591 */
521 592 private function handle_event() {
522 593 $this->invoice = $this->event->data->object;
523 594 $this->charge = $this->invoice->charge ?? false;
595 +
524 596 if ( ! $this->charge && $this->invoice->object === 'payment_intent' ) {
525 597 $this->charge = $this->invoice->id;
526 598 }
527 599