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

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