PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.26
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.26
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
formidable / stripe / models / FrmStrpLiteAuth.php

FrmStrpLiteAuth.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.26, at stripe/models/FrmStrpLiteAuth.php

884 lines 23.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmStrpLiteAuth {
7
8 /**
9 * All of the form IDs with payment details in the URL params will be included in this array.
10 *
11 * @var array
12 */
13 private static $form_ids = array();
14
15 /**
16 * If returning from Stripe to authorize a payment, show the message.
17 * This is used for 3D secure and for Stripe link.
18 *
19 * @since 6.5, introduced in v2.0 of the Stripe add on.
20 *
21 * @param string $html Form HTML that gets filtered through frm_filter_final_form.
22 *
23 * @return string
24 */
25 public static function maybe_show_message( $html ) {
26 $link_error = FrmAppHelper::simple_get( 'frm_link_error' );
27
28 if ( $link_error ) {
29 $message = '<div class="frm_error_style">' . self::get_message_for_stripe_link_code( $link_error ) . '</div>';
30 self::insert_error_message( $message, $html );
31 return $html;
32 }
33
34 $form_id = self::check_html_for_form_id_match( $html );
35
36 if ( false === $form_id ) {
37 return $html;
38 }
39
40 $details = FrmStrpLiteUrlParamHelper::get_details_for_form( $form_id );
41
42 if ( ! is_array( $details ) ) {
43 return $html;
44 }
45
46 $atts = array(
47 'fields' => FrmFieldsHelper::get_form_fields( $form_id ),
48 'entry' => $details['entry'],
49 );
50 self::prepare_success_atts( $atts );
51
52 $intent = $details['intent'];
53
54 if ( self::intent_has_failed_status( $intent ) ) {
55 $message = '<div class="frm_error_style">' . $intent->last_payment_error->message . '</div>';
56 self::insert_error_message( $message, $html );
57 return $html;
58 }
59
60 $intent_is_processing = 'processing' === $intent->status;
61
62 if ( $intent_is_processing ) {
63 // Append an additional processing message to the end of the success message.
64 $filter = function ( $message ) {
65 $stripe_settings = FrmStrpLiteAppHelper::get_settings();
66 $message .= '<p>' . esc_html( $stripe_settings->settings->processing_message ) . '</p>';
67 return $message;
68 };
69 add_filter( 'frm_content', $filter );
70 }
71
72 ob_start();
73 FrmFormsController::run_on_submit_actions( $atts );
74 $message = ob_get_contents();
75 ob_end_clean();
76
77 // Clean up the filter we added above so no other success messages get altered if there are multiple forms.
78 if ( $intent_is_processing ) {
79 remove_filter( 'frm_content', $filter );
80 }
81
82 return $message;
83 }
84
85 /**
86 * @param int|string $form_id
87 *
88 * @return array|false
89 */
90 private static function check_request_params( $form_id ) {
91 if ( ! FrmStrpLiteAppHelper::stripe_is_configured() ) {
92 return false;
93 }
94
95 $details = FrmStrpLiteUrlParamHelper::get_details_for_form( $form_id );
96
97 if ( ! is_array( $details ) ) {
98 return false;
99 }
100
101 self::$form_ids[] = $form_id;
102
103 return $details;
104 }
105
106 /**
107 * The frm_filter_final_form filter only passes form HTML as a string.
108 * To determine which form is being filtered, this function checks for the
109 * hidden form_id input. If there is a match, it returns the matching form id.
110 *
111 * @since 6.5
112 *
113 * @param string $html
114 *
115 * @return false|int Matching form id or false if there is no match.
116 */
117 private static function check_html_for_form_id_match( $html ) {
118 foreach ( self::$form_ids as $form_id ) {
119 $substring = '<input type="hidden" name="form_id" value="' . $form_id . '"';
120
121 if ( strpos( $html, $substring ) ) {
122 return $form_id;
123 }
124 }
125
126 return false;
127 }
128
129 /**
130 * Translate an error code into a readable message for the front end.
131 * FrmStrpLiteLinkRedirectHelper uses these codes to redirect errors that are then handled in self::maybe_show_message.
132 *
133 * @since 6.5, introduced in v3.0 of the Stripe add on.
134 *
135 * @param string $code
136 *
137 * @return string
138 */
139 private static function get_message_for_stripe_link_code( $code ) {
140 switch ( $code ) {
141 case 'intent_does_not_exist':
142 return __( 'Payment intent does not exist.', 'formidable' );
143 case 'unable_to_verify':
144 return __( 'Unable to verify payment intent.', 'formidable' );
145 case 'did_not_complete':
146 return __( 'Payment did not complete.', 'formidable' );
147 case 'no_payment_record':
148 return __( 'Unable to find record of payment.', 'formidable' );
149 case 'no_entry_found':
150 return __( 'This form submission does not exist.', 'formidable' );
151 case 'no_stripe_link_action':
152 return __( 'This form is not configured for Stripe link payments.', 'formidable' );
153 case 'create_subscription_failed':
154 return __( 'Something went wrong when trying to create a subscription.', 'formidable' );
155 case 'payment_failed':
156 return __( 'Payment was not successfully processed.', 'formidable' );
157 }
158 return '';
159 }
160
161 /**
162 * Add the parameters the receiving functions are expecting.
163 *
164 * @since 6.5, introduced in v2.0 of the Stripe add on.
165 *
166 * @param array $atts
167 *
168 * @return void
169 */
170 private static function prepare_success_atts( &$atts ) {
171 $atts['form'] = FrmForm::getOne( $atts['entry']->form_id );
172 $atts['entry_id'] = $atts['entry']->id;
173 $opt = 'success_action';
174 $atts['conf_method'] = ! empty( $atts['form']->options[ $opt ] ) ? $atts['form']->options[ $opt ] : 'message';
175
176 $actions = FrmFormsController::get_met_on_submit_actions( $atts, 'create' );
177
178 if ( $actions ) {
179 $action = reset( $actions );
180
181 if ( ! empty( $action->post_content['success_action'] ) && 'message' === $action->post_content['success_action'] ) {
182 $atts['conf_method'] = $action->post_content['success_action'];
183 }
184 }
185 }
186
187 /**
188 * Insert a message/error where the form styling will be applied.
189 *
190 * @since 6.5, introduced in v2.0 of the Stripe add on.
191 *
192 * @param string $message Message.
193 * @param string $form Form.
194 *
195 * @return void
196 */
197 private static function insert_error_message( $message, &$form ) {
198 $add_after = '<fieldset>';
199 $pos = strpos( $form, $add_after );
200
201 if ( $pos !== false ) {
202 $form = substr_replace( $form, $add_after . $message, $pos, strlen( $add_after ) );
203 }
204 }
205
206 /**
207 * Include the token if going between pages.
208 *
209 * @param object $form The form being submitted.
210 *
211 * @return void
212 */
213 public static function add_hidden_token_field( $form ) {
214 $posted_form = FrmAppHelper::get_param( 'form_id', 0, 'post', 'absint' );
215
216 if ( $posted_form != $form->id || FrmFormsController::just_created_entry( $form->id ) ) {
217 // Check to make sure the correct form was submitted.
218 // Was an entry already created and the form should be loaded fresh?
219
220 $intents = self::maybe_create_intents( $form->id );
221 self::include_intents_in_form( $intents, $form );
222
223 return;
224 }
225
226 $intents = self::get_payment_intents( 'frmintent' . $form->id );
227
228 if ( ! empty( $intents ) ) {
229 self::update_intent_pricing( $form->id, $intents );
230 } else {
231 $intents = self::maybe_create_intents( $form->id );
232 }
233
234 self::include_intents_in_form( $intents, $form );
235 }
236
237 /**
238 * Include hidden fields with payment intent IDs in the form.
239 *
240 * @since 6.5, introduced in v2.02 of the Stripe add on.
241 *
242 * @param array $intents
243 * @param stdClass $form
244 *
245 * @return void
246 */
247 private static function include_intents_in_form( $intents, $form ) {
248 foreach ( $intents as $intent ) {
249 if ( is_array( $intent ) ) {
250 $id = $intent['id'];
251 $action = $intent['action'];
252 } else {
253 $id = $intent;
254 $action = '';
255 }
256
257 echo '<input type="hidden" name="frmintent' . esc_attr( $form->id ) . '[]" value="' . esc_attr( $id ) . '" data-action="' . esc_attr( $action ) . '" />';
258 }
259 }
260
261 /**
262 * Check POST data for payment intents.
263 *
264 * @since 6.5, introduced in v2.0 of the Stripe add on.
265 *
266 * @param string $name
267 *
268 * @return mixed
269 */
270 public static function get_payment_intents( $name ) {
271 // phpcs:ignore WordPress.Security.NonceVerification.Missing
272 if ( ! isset( $_POST[ $name ] ) ) {
273 return array();
274 }
275
276 $intents = $_POST[ $name ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
277 FrmAppHelper::sanitize_value( 'sanitize_text_field', $intents );
278 return $intents;
279 }
280
281 /**
282 * Update pricing before authorizing.
283 *
284 * @since 6.5, introduced in v2.0 of the Stripe add on.
285 *
286 * @return void
287 */
288 public static function update_intent_ajax() {
289 check_ajax_referer( 'frm_strp_ajax', 'nonce' );
290
291 if ( empty( $_POST['form'] ) ) {
292 wp_die();
293 }
294
295 $form = json_decode( stripslashes( $_POST['form'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
296
297 if ( ! is_array( $form ) ) {
298 wp_die();
299 }
300
301 self::format_form_data( $form );
302
303 $form_id = absint( $form['form_id'] );
304 $intents = $form[ 'frmintent' . $form_id ] ?? array();
305
306 if ( empty( $intents ) ) {
307 wp_die();
308 }
309
310 if ( ! is_array( $intents ) ) {
311 $intents = array( $intents );
312 } else {
313 foreach ( $intents as $k => $intent ) {
314 if ( is_array( $intent ) && isset( $intent[ $k ] ) ) {
315 $intents[ $k ] = $intent[ $k ];
316 }
317 }
318 }
319
320 $_POST = $form;
321 self::update_intent_pricing( $form_id, $intents );
322
323 wp_die();
324 }
325
326 /**
327 * Update pricing on page turn and non-ajax validation.
328 *
329 * @since 6.5, introduced in v2.0 of the Stripe add on.
330 *
331 * @param int $form_id
332 * @param array $intents
333 *
334 * @return void
335 */
336 private static function update_intent_pricing( $form_id, &$intents ) {
337 // phpcs:ignore WordPress.Security.NonceVerification.Missing
338 if ( ! isset( $_POST['form_id'] ) || absint( $_POST['form_id'] ) != $form_id ) {
339 return;
340 }
341
342 $actions = FrmStrpLiteActionsController::get_actions_before_submit( $form_id );
343
344 if ( empty( $actions ) || empty( $intents ) ) {
345 return;
346 }
347
348 $form = FrmForm::getOne( $form_id );
349
350 try {
351 if ( ! FrmStrpLiteAppHelper::call_stripe_helper_class( 'initialize_api' ) ) {
352 return;
353 }
354 } catch ( Exception $e ) {
355 // Intent was not created.
356 return;
357 }
358
359 foreach ( $intents as $k => $intent ) {
360 $intent_id = explode( '_secret_', $intent )[0];
361 $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
362
363 if ( $is_setup_intent ) {
364 continue;
365 }
366
367 $saved = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_intent', $intent_id );
368
369 if ( empty( $saved->metadata->action ) ) {
370 continue;
371 }
372
373 foreach ( $actions as $action ) {
374 if ( $saved->metadata->action != $action->ID ) {
375 continue;
376 }
377 $intents[ $k ] = array(
378 'id' => $intent,
379 'action' => $action->ID,
380 );
381
382 $amount = $action->post_content['amount'];
383
384 if ( strpos( $amount, '[' ) === false ) {
385 // The amount is static, so it doesn't need an update.
386 continue;
387 }
388
389 // Update amount based on field shortcodes.
390 $entry = self::generate_false_entry();
391 $amount = FrmStrpLiteActionsController::prepare_amount( $amount, compact( 'form', 'entry', 'action' ) );
392
393 if ( $saved->amount == $amount || $amount == '000' ) {
394 continue;
395 }
396
397 FrmStrpLiteAppHelper::call_stripe_helper_class( 'update_intent', $intent_id, array( 'amount' => $amount ) );
398 }//end foreach
399 }//end foreach
400 }
401
402 /**
403 * Create an entry object with posted values.
404 *
405 * @since 6.5, introduced in v2.0 of the Stripe add on.
406 *
407 * @return stdClass
408 */
409 private static function generate_false_entry() {
410 $entry = new stdClass();
411 $entry->post_id = 0;
412 $entry->id = 0;
413 $entry->item_key = '';
414 $entry->metas = array();
415
416 // phpcs:ignore WordPress.Security.NonceVerification.Missing
417 foreach ( $_POST as $k => $v ) {
418 $k = sanitize_text_field( stripslashes( $k ) );
419 $v = wp_unslash( $v );
420
421 if ( $k === 'item_meta' ) {
422 foreach ( $v as $f => $value ) {
423 FrmAppHelper::sanitize_value( 'wp_kses_post', $value );
424 $entry->metas[ absint( $f ) ] = $value;
425 }
426 } else {
427 FrmAppHelper::sanitize_value( 'wp_kses_post', $v );
428 $entry->{$k} = $v;
429 }
430 }
431
432 return $entry;
433 }
434
435 /**
436 * Reformat the form data in name => value array.
437 *
438 * @since 6.5, introduced in v2.0 of the Stripe add on.
439 *
440 * @param array $form
441 *
442 * @return void
443 */
444 private static function format_form_data( &$form ) {
445 $formatted = array();
446
447 foreach ( $form as $input ) {
448 $key = $input['name'];
449
450 if ( isset( $formatted[ $key ] ) ) {
451 if ( is_array( $formatted[ $key ] ) ) {
452 $formatted[ $key ][] = $input['value'];
453 } else {
454 $formatted[ $key ] = array( $formatted[ $key ], $input['value'] );
455 }
456 } else {
457 $formatted[ $key ] = $input['value'];
458 }
459 }
460
461 parse_str( http_build_query( $formatted ), $form );
462 }
463
464 /**
465 * Create intents on form load when required.
466 * This only happens in two cases: For stripe link, and when processing a one-time payment before the entry is created.
467 *
468 * @since 6.5, introduced in v2.0 of the Stripe add on.
469 *
470 * @param int|string $form_id
471 *
472 * @return array
473 */
474 private static function maybe_create_intents( $form_id ) {
475 $intents = array();
476
477 $details = self::check_request_params( $form_id );
478
479 if ( is_array( $details ) ) {
480 $payment = $details['payment'];
481 $intent = $details['intent'];
482 $payment_failed = self::payment_failed( $payment, $intent );
483
484 // Exit early if the request params are set.
485 // This way an extra payment intent isn't created for Stripe Link.
486 if ( ! $payment_failed ) {
487 return $intents;
488 }
489 }
490
491 if ( ! FrmStrpLiteAppHelper::call_stripe_helper_class( 'initialize_api' ) ) {
492 // Stripe is not configured, so don't create intents.
493 return $intents;
494 }
495
496 $actions = FrmStrpLiteActionsController::get_actions_before_submit( $form_id );
497 self::add_amount_to_actions( $form_id, $actions );
498
499 foreach ( $actions as $action ) {
500 if ( is_array( $details ) && self::intent_has_failed_status( $details['intent'] ) ) {
501 $intents[] = array(
502 'id' => $details['intent']->client_secret,
503 'action' => $action->ID,
504 );
505 continue;
506 }
507
508 $intent = self::create_intent( $action );
509
510 if ( ! is_object( $intent ) ) {
511 // A non-object is a string error message.
512 // The error gets logged to results.log so we can just skip it.
513 // Reasons it could fail is because a payment method type was specified that will not work.
514 // A payment method type may not work because of a currency conflict, or because it isn't enabled.
515 // Or the payment method type could be an incorrect value.
516 // When using Stripe Connect, the error will just say "Unable to create intent".
517 // In this case, you can find the full error message in the Stripe dashboard.
518 continue;
519 }
520
521 $intents[] = array(
522 'id' => $intent->client_secret,
523 'action' => $action->ID,
524 );
525 }//end foreach
526
527 return $intents;
528 }
529
530 /**
531 * Create a payment intent for Stripe link or when processing a payment before the entry is created.
532 *
533 * @since 3.0 This code was moved out of self::maybe_create_intents into a new function.
534 *
535 * @param WP_Post $action
536 *
537 * @return mixed
538 */
539 private static function create_intent( $action ) {
540 $amount = $action->post_content['amount'];
541 $currency = $action->post_content['currency'];
542
543 if ( $amount == '000' ) {
544 // Create the intent when the form loads.
545 $amount = in_array( strtolower( $currency ), array( 'aud', 'cad', 'eur', 'gbp', 'usd' ), true ) ? 100 : 1000;
546 }
547
548 if ( 'recurring' === $action->post_content['type'] ) {
549 $payment_method_types = FrmStrpLitePaymentTypeHandler::get_payment_method_types( $action );
550 return self::create_setup_intent( $payment_method_types );
551 }
552
553 $new_charge = array(
554 'amount' => $amount,
555 'currency' => $currency,
556 'metadata' => array( 'action' => $action->ID ),
557 );
558
559 $new_charge = self::maybe_add_statement_descriptor( $new_charge );
560
561 if ( FrmStrpLitePaymentTypeHandler::should_use_automatic_payment_methods( $action ) ) {
562 $new_charge['automatic_payment_methods'] = array( 'enabled' => true );
563 } else {
564 $payment_method_types = FrmStrpLitePaymentTypeHandler::get_payment_method_types( $action );
565 $new_charge['payment_method_types'] = $payment_method_types;
566 }
567
568 return FrmStrpLiteAppHelper::call_stripe_helper_class( 'create_intent', $new_charge );
569 }
570
571 /**
572 * Add the statement descriptor to the intent data, if it is valid.
573 *
574 * @param array $intent_data
575 *
576 * @return array
577 */
578 private static function maybe_add_statement_descriptor( $intent_data ) {
579 $statement_descriptor = self::get_statement_descriptor();
580
581 if ( false !== $statement_descriptor ) {
582 $intent_data['statement_descriptor'] = $statement_descriptor;
583 }
584
585 return $intent_data;
586 }
587
588 /**
589 * Get the statement descriptor for a payment intent.
590 *
591 * @since 6.23
592 *
593 * @return false|string False if the statement descriptor is not valid.
594 */
595 private static function get_statement_descriptor() {
596 $name = get_bloginfo( 'name' );
597
598 /**
599 * Filters the statement descriptor for a payment intent.
600 * This way a site can use the name they want on their statements.
601 *
602 * @since 6.23
603 *
604 * @param string $name The name of the site.
605 */
606 $name = apply_filters( 'frm_stripe_statement_descriptor', $name );
607
608 if ( ! is_string( $name ) ) {
609 return false;
610 }
611
612 $name = self::strip_special_characters_from_statement_descriptor( $name );
613
614 if ( ! self::statement_descriptor_is_valid( $name ) ) {
615 return false;
616 }
617
618 return $name;
619 }
620
621 /**
622 * Remove the special characters that Stripe doesn't allow in statement descriptors, in case any exist.
623 *
624 * @since 6.23
625 *
626 * @param string $name The name of the site.
627 *
628 * @return string The name with special characters removed.
629 */
630 private static function strip_special_characters_from_statement_descriptor( $name ) {
631 $special_characters = array(
632 '<',
633 '>',
634 '\\',
635 "'",
636 '"',
637 '*',
638 );
639 return str_replace( $special_characters, '', $name );
640 }
641
642 /**
643 * Stripe includes requirements at https://docs.stripe.com/get-started/account/statement-descriptors
644 * We need to make sure that the descriptor contains only Latin characters, and that it is between 5 and 22 characters long.
645 *
646 * @since 6.23
647 *
648 * @param string $name Passed by reference, as this is updated if it is too long.
649 *
650 * @return bool
651 */
652 private static function statement_descriptor_is_valid( &$name ) {
653 if ( strlen( $name ) < 5 ) {
654 return false;
655 }
656
657 if ( strlen( $name ) > 22 ) {
658 $name = substr( $name, 0, 22 );
659 }
660
661 if ( ! preg_match( '/^[a-zA-Z0-9\s\p{P}]+$/', $name ) ) {
662 return false;
663 }
664
665 return true;
666 }
667
668 /**
669 * Create a customer and an associated setup intent for a recurring Stripe link payment.
670 *
671 * @since 6.5, introduced in v3.0 of the Stripe add on.
672 *
673 * @param array $payment_method_types
674 *
675 * @return false|object
676 */
677 private static function create_setup_intent( $payment_method_types ) {
678 $payment_info = array(
679 'user_id' => FrmTransLiteAppHelper::get_user_id_for_current_payment(),
680 );
681
682 // We need to add a customer to support subscriptions with link.
683 $customer = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_customer', $payment_info );
684
685 if ( ! is_object( $customer ) ) {
686 return false;
687 }
688
689 return FrmStrpLiteAppHelper::call_stripe_helper_class( 'create_setup_intent', $customer->id, $payment_method_types );
690 }
691
692 /**
693 * @since 6.5, introduced in v2.0 of the Stripe add on.
694 *
695 * @param int|string $form_id
696 * @param array $actions
697 *
698 * @return void
699 */
700 private static function add_amount_to_actions( $form_id, &$actions ) {
701 if ( empty( $actions ) ) {
702 return;
703 }
704
705 $form = FrmForm::getOne( $form_id );
706
707 foreach ( $actions as $k => $action ) {
708 $amount = self::get_amount_before_submit( compact( 'action', 'form' ) );
709 $actions[ $k ]->post_content['amount'] = $amount;
710 }
711 }
712
713 /**
714 * @since 6.5, introduced in v2.0 of the Stripe add on.
715 *
716 * @param array $atts
717 *
718 * @return string
719 */
720 private static function get_amount_before_submit( $atts ) {
721 return FrmStrpLiteActionsController::prepare_amount( $atts['action']->post_content['amount'], $atts );
722 }
723
724 /**
725 * Get the URL to return to after a payment is complete.
726 * This may either use the success URL on redirect, or the message on success.
727 * It shouldn't be confused for the Stripe link return URL. It isn't used for that. That uses the frmstrplinkreturn AJAX action instead.
728 *
729 * @since 6.5, introduced in v2.0 of the Stripe add on.
730 *
731 * @param array $atts
732 *
733 * @return string
734 */
735 public static function return_url( $atts ) {
736 $atts = array(
737 'entry' => $atts['entry'],
738 );
739 self::prepare_success_atts( $atts );
740
741 if ( $atts['conf_method'] === 'redirect' ) {
742 $redirect = self::get_redirect_url( $atts );
743 } else {
744 $redirect = self::get_message_url( $atts );
745 }
746
747 return $redirect;
748 }
749
750 /**
751 * If the form should redirect, get the url to redirect to.
752 *
753 * @since 6.5, introduced in v2.0 of the Stripe add on.
754 *
755 * @param array $atts {
756 * The form and entry details.
757 *
758 * @type stdClass $form
759 * @type stdClass $entry
760 * }
761 *
762 * @return string
763 */
764 private static function get_redirect_url( $atts ) {
765 $actions = FrmFormsController::get_met_on_submit_actions( $atts );
766
767 if ( $actions ) {
768 $success_url = reset( $actions )->post_content['success_url'];
769 }
770
771 if ( empty( $success_url ) ) {
772 $success_url = $atts['form']->options['success_url'];
773 }
774
775 $success_url = trim( $atts['form']->options['success_url'] );
776 $success_url = apply_filters( 'frm_content', $success_url, $atts['form'], $atts['entry'] );
777 $success_url = do_shortcode( $success_url );
778 $atts['id'] = $atts['entry']->id;
779
780 add_filter( 'frm_redirect_url', 'FrmEntriesController::prepare_redirect_url' );
781 return apply_filters( 'frm_redirect_url', $success_url, $atts['form'], $atts );
782 }
783
784 /**
785 * If the form should should a message, append it to the success url.
786 *
787 * @since 6.5, introduced in v2.0 of the Stripe add on.
788 *
789 * @param array $atts
790 *
791 * @return string
792 */
793 private static function get_message_url( $atts ) {
794 $url = self::get_referer_url( $atts['entry_id'], false );
795
796 if ( false === $url ) {
797 $url = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
798 }
799 return add_query_arg( array( 'frmstrp' => $atts['entry_id'] ), $url );
800 }
801
802 /**
803 * @since 6.5
804 *
805 * @param int|string $entry_id
806 * @param bool $delete_meta
807 *
808 * @return false|string
809 */
810 public static function get_referer_url( $entry_id, $delete_meta = true ) {
811 $row = FrmDb::get_row(
812 'frm_item_metas',
813 array(
814 'field_id' => 0,
815 'item_id' => $entry_id,
816 'meta_value LIKE' => '{"referer":',
817 ),
818 'id, meta_value'
819 );
820
821 if ( ! $row ) {
822 return false;
823 }
824
825 $meta = $row->meta_value;
826 $meta = json_decode( $meta, true );
827
828 if ( ! is_array( $meta ) || empty( $meta['referer'] ) ) {
829 return false;
830 }
831
832 self::delete_temporary_referer_meta( (int) $row->id );
833 return $meta['referer'];
834 }
835
836 /**
837 * Delete the referer meta as we'll no longer need it.
838 *
839 * @param int $row_id
840 *
841 * @return void
842 */
843 private static function delete_temporary_referer_meta( $row_id ) {
844 global $wpdb;
845 $wpdb->delete( $wpdb->prefix . 'frm_item_metas', array( 'id' => $row_id ) );
846 }
847
848 /**
849 * Check if a payment or setup intent has failed.
850 *
851 * @since 6.5.1
852 *
853 * @param object $intent
854 *
855 * @return bool
856 */
857 private static function intent_has_failed_status( $intent ) {
858 return in_array( $intent->status, array( 'requires_source', 'requires_payment_method', 'canceled' ), true );
859 }
860
861 /**
862 * Check if a payment failed.
863 *
864 * @since 6.8
865 *
866 * @param object $payment
867 * @param object $intent
868 *
869 * @return bool
870 */
871 public static function payment_failed( $payment, $intent ) {
872 if ( self::intent_has_failed_status( $intent ) ) {
873 return true;
874 }
875
876 // The $intent will be "succeeded" with a failed payment when testing with the 4000000000000341 credit card.
877 if ( 'payment_failed' === FrmAppHelper::simple_get( 'frm_link_error' ) && 'failed' === $payment->status ) {
878 return true;
879 }
880
881 return false;
882 }
883 }
884