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 +116 -16 6.25.1 → trunk View file →
@@ -49,15 +49,16 @@
49 49 * @since 6.5, introduced in v3.0 of the Stripe add on.
50 50 *
51 51 * @param string $intent_id
52 52 * @param string $client_secret
53 + *
53 54 * @return void
54 55 */
55 56 private static function handle_one_time_stripe_link_return_url( $intent_id, $client_secret ) {
56 57 $redirect_helper = new FrmStrpLiteLinkRedirectHelper( $intent_id, $client_secret );
57 58 $frm_payment = new FrmTransLitePayment();
59 + $payment = $frm_payment->get_one_by( $intent_id, 'receipt_id' );
58 60
59 - $payment = $frm_payment->get_one_by( $intent_id, 'receipt_id' );
60 61 if ( ! $payment ) {
61 62 $redirect_helper->handle_error( 'no_payment_record' );
62 63 die();
63 64 }
@@ -62,8 +63,9 @@
62 63 die();
63 64 }
64 65
65 66 $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_intent', $intent_id );
67 +
66 68 if ( ! is_object( $intent ) ) {
67 69 $redirect_helper->handle_error( 'intent_does_not_exist' );
68 70 die();
69 71 }
@@ -93,13 +95,32 @@
93 95
94 96 $redirect_helper->set_entry_id( $entry->id );
95 97
96 98 $action = FrmStrpLiteActionsController::get_stripe_link_action( $entry->form_id );
99 +
97 100 if ( ! $action ) {
98 101 $redirect_helper->handle_error( 'no_stripe_link_action' );
99 102 die();
100 103 }
101 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 +
102 123 if ( 'succeeded' !== $intent->status ) {
103 124 if ( 'processing' === $intent->status ) {
104 125 FrmTransLitePaymentsController::change_payment_status( $payment, 'processing' );
105 126 $redirect_helper->handle_success( $entry, '' );
@@ -124,21 +145,45 @@
124 145 }
125 146
126 147 self::maybe_update_intent( $intent, $action, $entry );
127 148
128 - $frm_payment->update( $payment->id, $new_payment_values );
129 - 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 );
130 152
153 + if ( $needs_triggers && $updated ) {
154 + FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
155 + }
156 +
131 157 $redirect_helper->handle_success( $entry, isset( $charge ) ? $charge->id : '' );
132 158 die();
133 159 }
134 160
135 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 + /**
136 180 * Try to add the description to a Stripe link payment after it was confirmed.
137 181 *
138 182 * @param object $intent
139 183 * @param stdClass|WP_Post $action
140 184 * @param stdClass $entry
185 + *
141 186 * @return void
142 187 */
143 188 private static function maybe_update_intent( $intent, $action, $entry ) {
144 189 if ( empty( $action->post_content['description'] ) ) {
@@ -162,8 +207,9 @@
162 207 * @since 6.5, introduced in v3.0 of the Stripe add on.
163 208 *
164 209 * @param string $setup_id
165 210 * @param string $client_secret
211 + *
166 212 * @return void
167 213 */
168 214 private static function handle_recurring_stripe_link_return_url( $setup_id, $client_secret ) {
169 215 $redirect_helper = new FrmStrpLiteLinkRedirectHelper( $setup_id, $client_secret );
@@ -176,8 +222,9 @@
176 222 }
177 223
178 224 // Verify the setup intent.
179 225 $setup_intent = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_setup_intent', $setup_id );
226 +
180 227 if ( ! is_object( $setup_intent ) ) {
181 228 $redirect_helper->handle_error( 'intent_does_not_exist' );
182 229 die();
183 230 }
@@ -189,8 +236,9 @@
189 236 }
190 237
191 238 // Verify the entry.
192 239 $entry = FrmEntry::getOne( $payment->item_id );
240 +
193 241 if ( ! is_object( $entry ) ) {
194 242 $redirect_helper->handle_error( 'no_entry_found' );
195 243 die();
196 244 }
@@ -198,8 +246,9 @@
198 246 $redirect_helper->set_entry_id( $entry->id );
199 247
200 248 // Verify it's an action with Stripe link enabled.
201 249 $action = FrmStrpLiteActionsController::get_stripe_link_action( $entry->form_id );
250 +
202 251 if ( ! is_object( $action ) ) {
203 252 $redirect_helper->handle_error( 'no_stripe_link_action' );
204 253 die();
205 254 }
@@ -205,8 +254,9 @@
205 254 }
206 255
207 256 $customer_id = $setup_intent->customer;
208 257 $payment_method_id = self::get_link_payment_method( $setup_intent );
258 +
209 259 if ( ! $payment_method_id ) {
210 260 FrmTransLitePaymentsController::change_payment_status( $payment, 'failed' );
211 261 $redirect_helper->handle_error( 'did_not_complete' );
212 262 die();
@@ -236,8 +286,9 @@
236 286 'entry' => $entry,
237 287 );
238 288
239 289 $trial_end = FrmStrpLiteActionsController::get_trial_end_time( $atts );
290 +
240 291 if ( $trial_end ) {
241 292 $new_charge['trial_end'] = $trial_end;
242 293 }
243 294
@@ -274,8 +325,9 @@
274 325
275 326 $new_payment_values['status'] = 'pending' === $charge->status ? 'processing' : 'complete';
276 327
277 328 $new_payment_values['expire_date'] = '0000-00-00';
329 +
278 330 foreach ( $subscription->latest_invoice->lines->data as $line ) {
279 331 $new_payment_values['expire_date'] = gmdate( 'Y-m-d', $line->period->end );
280 332 }
281 333 } elseif ( $trial_end ) {
@@ -294,8 +346,9 @@
294 346 FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
295 347
296 348 // Update the next billing date.
297 349 $next_bill_date = gmdate( 'Y-m-d' );
350 +
298 351 foreach ( $subscription->latest_invoice->lines->data as $line ) {
299 352 $next_bill_date = gmdate( 'Y-m-d', $line->period->end );
300 353 }
301 354
@@ -317,13 +370,15 @@
317 370 *
318 371 * @since 6.5, introduced in v3.0 of the Stripe add on.
319 372 *
320 373 * @param object $setup_intent
374 + *
321 375 * @return false|string
322 376 */
323 377 private static function get_link_payment_method( $setup_intent ) {
324 378 if ( is_object( $setup_intent->latest_attempt ) && ! empty( $setup_intent->latest_attempt->payment_method_details ) ) {
325 379 $payment_method_details = $setup_intent->latest_attempt->payment_method_details;
380 +
326 381 foreach ( array( 'ideal', 'sofort', 'bancontact' ) as $payment_method_type ) {
327 382 if ( ! empty( $payment_method_details->$payment_method_type ) ) {
328 383 return $payment_method_details->$payment_method_type->generated_sepa_debit;
329 384 }
@@ -352,31 +407,32 @@
352 407 * @type WP_Post $action
353 408 * @type string $amount
354 409 * @type object $customer
355 410 * }
356 - * @return void
411 + *
412 + * @return bool True on success, false on failure.
357 413 */
358 414 public static function create_pending_stripe_link_payment( $atts ) {
359 415 if ( empty( $atts['form'] ) || empty( $atts['entry'] ) || empty( $atts['action'] ) || ! isset( $atts['amount'] ) || empty( $atts['customer'] ) ) {
360 - return;
416 + return false;
361 417 }
362 418
363 419 $form = $atts['form'];
364 - $intent_id = self::verify_intent( $form->id );
420 + $action = $atts['action'];
421 + $intent_id = self::verify_intent( $form->id, $action );
365 422
366 423 if ( ! $intent_id ) {
367 - return;
424 + return false;
368 425 }
369 426
370 - $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
427 + $is_setup_intent = str_starts_with( $intent_id, 'seti_' );
371 428 $entry = $atts['entry'];
372 - $action = $atts['action'];
373 429 $amount = $atts['amount'];
374 430 $customer = $atts['customer'];
375 431
376 432 if ( ! $is_setup_intent ) {
377 433 // Update the amount and set the customer before confirming the payment.
378 - FrmStrpLiteAppHelper::call_stripe_helper_class(
434 + $updated = FrmStrpLiteAppHelper::call_stripe_helper_class(
379 435 'update_intent',
380 436 $intent_id,
381 437 array(
382 438 'amount' => $amount,
@@ -382,14 +438,18 @@
382 438 'amount' => $amount,
383 439 'customer' => $customer->id,
384 440 )
385 441 );
442 +
443 + if ( ! $updated ) {
444 + return false;
445 + }
386 446 }
387 447
388 448 self::add_temporary_referer_meta( (int) $entry->id );
389 449
390 450 $frm_payment = new FrmTransLitePayment();
391 - $frm_payment->create(
451 + $payment_id = $frm_payment->create(
392 452 array(
393 453 'paysys' => 'stripe',
394 454 'amount' => FrmTransLiteAppHelper::get_formatted_amount_for_currency( $amount, $action ),
395 455 'status' => 'pending',
@@ -399,8 +459,10 @@
399 459 'sub_id' => '',
400 460 'test' => 'test' === FrmStrpLiteAppHelper::active_mode() ? 1 : 0,
401 461 )
402 462 );
463 +
464 + return (bool) $payment_id;
403 465 }
404 466
405 467 /**
406 468 * Verify a payment intent or setup intent client secret is in the POST data and is valid.
@@ -407,12 +469,15 @@
407 469 *
408 470 * @since 6.5, introduced in v3.0 of the Stripe add on.
409 471 *
410 472 * @param int|string $form_id
473 + * @param WP_Post $action
474 + *
411 475 * @return false|string String intent id on success, False if intent is missing or cannot be verified.
412 476 */
413 - private static function verify_intent( $form_id ) {
477 + private static function verify_intent( $form_id, $action ) {
414 478 $client_secrets = FrmAppHelper::get_post_param( 'frmintent' . $form_id, array(), 'sanitize_text_field' );
479 +
415 480 if ( ! $client_secrets ) {
416 481 return false;
417 482 }
418 483
@@ -418,15 +483,27 @@
418 483
419 484 $client_secret = reset( $client_secrets );
420 485 list( $prefix, $intent_id ) = explode( '_', $client_secret );
421 486 $intent_id = $prefix . '_' . $intent_id;
487 + $is_setup_intent = str_starts_with( $intent_id, 'seti_' );
488 + $function_name = $is_setup_intent ? 'get_setup_intent' : 'get_intent';
489 + $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( $function_name, $intent_id );
422 490
423 - $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
491 + if ( ! $intent || $intent->client_secret !== $client_secret || ! self::intent_matches_form_action( $intent, $action ) ) {
492 + return false;
493 + }
424 494
425 - $function_name = $is_setup_intent ? 'get_setup_intent' : 'get_intent';
426 - $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( $function_name, $intent_id );
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 + }
427 500
428 - if ( ! $intent || $intent->client_secret !== $client_secret ) {
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.
429 506 return false;
430 507 }
431 508
432 509 return $intent_id;
@@ -432,8 +509,27 @@
432 509 return $intent_id;
433 510 }
434 511
435 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;
529 + }
530 +
531 + /**
436 532 * Set the referer URL as field ID 0 in entry meta.
437 533 * This is required for iDEAL, sofort, and other payment methods that include an additional redirect step.
438 534 * It is used for the redirect in FrmStrpLinkRedirectHelper.
439 535 * It is deleted after the redirect happens.
@@ -438,8 +534,9 @@
438 534 * It is used for the redirect in FrmStrpLinkRedirectHelper.
439 535 * It is deleted after the redirect happens.
440 536 *
441 537 * @param int $entry_id
538 + *
442 539 * @return void
443 540 */
444 541 private static function add_temporary_referer_meta( $entry_id ) {
445 542 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
@@ -449,8 +546,9 @@
449 546 'payment_intent_client_secret',
450 547 'setup_intent',
451 548 'setup_intent_client_secret',
452 549 );
550 +
453 551 foreach ( $query_args_to_strip_from_referer as $arg ) {
454 552 $referer = remove_query_arg( $arg, $referer );
455 553 }
456 554
@@ -463,8 +561,9 @@
463 561 *
464 562 * @since 6.5, introduced in v3.0 of the Stripe add on.
465 563 *
466 564 * @param stdClass $form
565 + *
467 566 * @return void
468 567 */
469 568 public static function add_form_classes( $form ) {
470 569 if ( false === FrmStrpLiteActionsController::get_stripe_link_action( $form->id ) ) {
@@ -479,8 +578,9 @@
479 578 *
480 579 * @since 6.5, introduced in v3.0 of the Stripe add on.
481 580 *
482 581 * @param mixed $form
582 + *
483 583 * @return mixed
484 584 */
485 585 public static function force_ajax_submit_for_stripe_link( $form ) {
486 586 if ( ! is_object( $form ) ) {