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/FrmStrpLiteLinkController.php +92 -11 6.27 → trunk View file →
@@ -101,8 +101,26 @@
101 101 $redirect_helper->handle_error( 'no_stripe_link_action' );
102 102 die();
103 103 }
104 104
105 + $currency = FrmTransLiteAppHelper::get_action_setting( 'currency', array( 'payment' => $payment ) );
106 + $currency = FrmCurrencyHelper::get_currency( $currency );
107 + $actual_amount = intval( $intent->amount );
108 + $expected_amount = round( floatval( $payment->amount ), 2 );
109 +
110 + if ( 0 !== $currency['decimals'] ) {
111 + // Convert 10 to 1000 for example for Stripe.
112 + // But avoid for this a 0-decimal currency like JPY.
113 + $expected_amount *= 100;
114 + }
115 +
116 + $expected_amount = intval( round( $expected_amount ) );
117 +
118 + if ( $expected_amount !== $actual_amount ) {
119 + $redirect_helper->handle_error( 'amount_mismatch' );
120 + die();
121 + }
122 +
105 123 if ( 'succeeded' !== $intent->status ) {
106 124 if ( 'processing' === $intent->status ) {
107 125 FrmTransLitePaymentsController::change_payment_status( $payment, 'processing' );
108 126 $redirect_helper->handle_success( $entry, '' );
@@ -127,16 +145,39 @@
127 145 }
128 146
129 147 self::maybe_update_intent( $intent, $action, $entry );
130 148
131 - $frm_payment->update( $payment->id, $new_payment_values );
132 - FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
149 + // A webhook event may have already updated this payment, so check the status again before running triggers.
150 + $needs_triggers = $status !== $payment->status && self::payment_status_still_needs_to_update( $payment->id, $status );
151 + $updated = $frm_payment->update( $payment->id, $new_payment_values );
133 152
153 + if ( $needs_triggers && $updated ) {
154 + FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
155 + }
156 +
134 157 $redirect_helper->handle_success( $entry, isset( $charge ) ? $charge->id : '' );
135 158 die();
136 159 }
137 160
138 161 /**
162 + * Check that the payment status has not been updated by another request already.
163 + * This is to avoid running the payment actions twice.
164 + *
165 + * @since 6.35
166 + *
167 + * @param int $payment_id The id of the payment to check.
168 + * @param string $status The status the payment is about to be updated to.
169 + *
170 + * @return bool
171 + */
172 + private static function payment_status_still_needs_to_update( $payment_id, $status ) {
173 + $frm_payment = new FrmTransLitePayment();
174 + $payment = $frm_payment->get_one( $payment_id );
175 +
176 + return $payment && $payment->status !== $status;
177 + }
178 +
179 + /**
139 180 * Try to add the description to a Stripe link payment after it was confirmed.
140 181 *
141 182 * @param object $intent
142 183 * @param stdClass|WP_Post $action
@@ -367,31 +408,31 @@
367 408 * @type string $amount
368 409 * @type object $customer
369 410 * }
370 411 *
371 - * @return void
412 + * @return bool True on success, false on failure.
372 413 */
373 414 public static function create_pending_stripe_link_payment( $atts ) {
374 415 if ( empty( $atts['form'] ) || empty( $atts['entry'] ) || empty( $atts['action'] ) || ! isset( $atts['amount'] ) || empty( $atts['customer'] ) ) {
375 - return;
416 + return false;
376 417 }
377 418
378 419 $form = $atts['form'];
379 - $intent_id = self::verify_intent( $form->id );
420 + $action = $atts['action'];
421 + $intent_id = self::verify_intent( $form->id, $action );
380 422
381 423 if ( ! $intent_id ) {
382 - return;
424 + return false;
383 425 }
384 426
385 427 $is_setup_intent = str_starts_with( $intent_id, 'seti_' );
386 428 $entry = $atts['entry'];
387 - $action = $atts['action'];
388 429 $amount = $atts['amount'];
389 430 $customer = $atts['customer'];
390 431
391 432 if ( ! $is_setup_intent ) {
392 433 // Update the amount and set the customer before confirming the payment.
393 - FrmStrpLiteAppHelper::call_stripe_helper_class(
434 + $updated = FrmStrpLiteAppHelper::call_stripe_helper_class(
394 435 'update_intent',
395 436 $intent_id,
396 437 array(
397 438 'amount' => $amount,
@@ -397,14 +438,18 @@
397 438 'amount' => $amount,
398 439 'customer' => $customer->id,
399 440 )
400 441 );
442 +
443 + if ( ! $updated ) {
444 + return false;
445 + }
401 446 }
402 447
403 448 self::add_temporary_referer_meta( (int) $entry->id );
404 449
405 450 $frm_payment = new FrmTransLitePayment();
406 - $frm_payment->create(
451 + $payment_id = $frm_payment->create(
407 452 array(
408 453 'paysys' => 'stripe',
409 454 'amount' => FrmTransLiteAppHelper::get_formatted_amount_for_currency( $amount, $action ),
410 455 'status' => 'pending',
@@ -414,8 +459,10 @@
414 459 'sub_id' => '',
415 460 'test' => 'test' === FrmStrpLiteAppHelper::active_mode() ? 1 : 0,
416 461 )
417 462 );
463 +
464 + return (bool) $payment_id;
418 465 }
419 466
420 467 /**
421 468 * Verify a payment intent or setup intent client secret is in the POST data and is valid.
@@ -422,12 +469,13 @@
422 469 *
423 470 * @since 6.5, introduced in v3.0 of the Stripe add on.
424 471 *
425 472 * @param int|string $form_id
473 + * @param WP_Post $action
426 474 *
427 475 * @return false|string String intent id on success, False if intent is missing or cannot be verified.
428 476 */
429 - private static function verify_intent( $form_id ) {
477 + private static function verify_intent( $form_id, $action ) {
430 478 $client_secrets = FrmAppHelper::get_post_param( 'frmintent' . $form_id, array(), 'sanitize_text_field' );
431 479
432 480 if ( ! $client_secrets ) {
433 481 return false;
@@ -439,13 +487,46 @@
439 487 $is_setup_intent = str_starts_with( $intent_id, 'seti_' );
440 488 $function_name = $is_setup_intent ? 'get_setup_intent' : 'get_intent';
441 489 $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( $function_name, $intent_id );
442 490
443 - if ( ! $intent || $intent->client_secret !== $client_secret ) {
491 + if ( ! $intent || $intent->client_secret !== $client_secret || ! self::intent_matches_form_action( $intent, $action ) ) {
444 492 return false;
445 493 }
446 494
495 + if ( isset( $intent->charges ) && is_object( $intent->charges ) && ! empty( $intent->charges->data ) ) {
496 + // The intent should not have any charges yet.
497 + // If it does, the intent is invalid.
498 + return false;
499 + }
500 +
501 + $frm_payment = new FrmTransLitePayment();
502 + $payment = $frm_payment->get_one_by( $intent_id, 'receipt_id' );
503 +
504 + if ( $payment ) {
505 + // A duplicate payment should not exist.
506 + return false;
507 + }
508 +
447 509 return $intent_id;
510 + }
511 +
512 + /**
513 + * Check if an intent matches a form action.
514 + *
515 + * @since 6.29
516 + *
517 + * @param object $intent
518 + * @param WP_Post $action
519 + *
520 + * @return bool
521 + */
522 + private static function intent_matches_form_action( $intent, $action ) {
523 + if ( ! isset( $intent->metadata ) || ! is_object( $intent->metadata ) || empty( $intent->metadata->action ) ) {
524 + // Avoid false positive if the intent is missing metadata.
525 + return true;
526 + }
527 +
528 + return (int) $intent->metadata->action === $action->ID;
448 529 }
449 530
450 531 /**
451 532 * Set the referer URL as field ID 0 in entry meta.