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

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