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

716 lines 20.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 * @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_success_action( $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
167 /**
168 * Insert a message/error where the form styling will be applied.
169 *
170 * @since 6.5, introduced in v2.0 of the Stripe add on.
171 */
172 private static function insert_error_message( $message, &$form ) {
173 $add_after = '<fieldset>';
174 $pos = strpos( $form, $add_after );
175 if ( $pos !== false ) {
176 $form = substr_replace( $form, $add_after . $message, $pos, strlen( $add_after ) );
177 }
178 }
179
180 /**
181 * Include the token if going between pages.
182 *
183 * @param object $form The form being submitted.
184 * @return void
185 */
186 public static function add_hidden_token_field( $form ) {
187 $posted_form = FrmAppHelper::get_param( 'form_id', 0, 'post', 'absint' );
188 if ( $posted_form != $form->id || FrmFormsController::just_created_entry( $form->id ) ) {
189 // Check to make sure the correct form was submitted.
190 // Was an entry already created and the form should be loaded fresh?
191
192 $intents = self::maybe_create_intents( $form->id );
193 self::include_intents_in_form( $intents, $form );
194
195 return;
196 }
197
198 $intents = self::get_payment_intents( 'frmintent' . $form->id );
199 if ( ! empty( $intents ) ) {
200 self::update_intent_pricing( $form->id, $intents );
201 } else {
202 $intents = self::maybe_create_intents( $form->id );
203 }
204
205 self::include_intents_in_form( $intents, $form );
206 }
207
208 /**
209 * Include hidden fields with payment intent IDs in the form.
210 *
211 * @since 6.5, introduced in v2.02 of the Stripe add on.
212 *
213 * @param array $intents
214 * @param stdClass $form
215 * @return void
216 */
217 private static function include_intents_in_form( $intents, $form ) {
218 foreach ( $intents as $intent ) {
219 if ( is_array( $intent ) ) {
220 $id = $intent['id'];
221 $action = $intent['action'];
222 } else {
223 $id = $intent;
224 $action = '';
225 }
226
227 echo '<input type="hidden" name="frmintent' . esc_attr( $form->id ) . '[]" value="' . esc_attr( $id ) . '" data-action="' . esc_attr( $action ) . '" />';
228 }
229 }
230
231 /**
232 * Check POST data for payment intents.
233 *
234 * @since 6.5, introduced in v2.0 of the Stripe add on.
235 *
236 * @param string $name
237 * @return mixed
238 */
239 public static function get_payment_intents( $name ) {
240 // phpcs:ignore WordPress.Security.NonceVerification.Missing
241 if ( ! isset( $_POST[ $name ] ) ) {
242 return array();
243 }
244 $intents = $_POST[ $name ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
245 FrmAppHelper::sanitize_value( 'sanitize_text_field', $intents );
246 return $intents;
247 }
248
249 /**
250 * Update pricing before authorizing.
251 *
252 * @since 6.5, introduced in v2.0 of the Stripe add on.
253 *
254 * @return void
255 */
256 public static function update_intent_ajax() {
257 check_ajax_referer( 'frm_strp_ajax', 'nonce' );
258
259 if ( empty( $_POST['form'] ) ) {
260 wp_die();
261 }
262
263 $form = json_decode( stripslashes( $_POST['form'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
264 if ( ! is_array( $form ) ) {
265 wp_die();
266 }
267
268 self::format_form_data( $form );
269
270 $form_id = absint( $form['form_id'] );
271 $intents = isset( $form[ 'frmintent' . $form_id ] ) ? $form[ 'frmintent' . $form_id ] : array();
272
273 if ( empty( $intents ) ) {
274 wp_die();
275 }
276
277 if ( ! is_array( $intents ) ) {
278 $intents = array( $intents );
279 } else {
280 foreach ( $intents as $k => $intent ) {
281 if ( is_array( $intent ) && isset( $intent[ $k ] ) ) {
282 $intents[ $k ] = $intent[ $k ];
283 }
284 }
285 }
286
287 $_POST = $form;
288 self::update_intent_pricing( $form_id, $intents );
289
290 wp_die();
291 }
292
293 /**
294 * Update pricing on page turn and non-ajax validation.
295 *
296 * @since 6.5, introduced in v2.0 of the Stripe add on.
297 * @param int $form_id
298 * @param array $intents
299 * @return void
300 */
301 private static function update_intent_pricing( $form_id, &$intents ) {
302 // phpcs:ignore WordPress.Security.NonceVerification.Missing
303 if ( ! isset( $_POST['form_id'] ) || absint( $_POST['form_id'] ) != $form_id ) {
304 return;
305 }
306
307 $actions = FrmStrpLiteActionsController::get_actions_before_submit( $form_id );
308 if ( empty( $actions ) || empty( $intents ) ) {
309 return;
310 }
311
312 $form = FrmForm::getOne( $form_id );
313
314 try {
315 if ( ! FrmStrpLiteAppHelper::call_stripe_helper_class( 'initialize_api' ) ) {
316 return;
317 }
318 } catch ( Exception $e ) {
319 // Intent was not created.
320 return;
321 }
322
323 foreach ( $intents as $k => $intent ) {
324 $intent_id = explode( '_secret_', $intent )[0];
325 $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
326 if ( $is_setup_intent ) {
327 continue;
328 }
329
330 $saved = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_intent', $intent_id );
331 foreach ( $actions as $action ) {
332 if ( $saved->metadata->action != $action->ID ) {
333 continue;
334 }
335 $intents[ $k ] = array(
336 'id' => $intent,
337 'action' => $action->ID,
338 );
339
340 $amount = $action->post_content['amount'];
341 if ( strpos( $amount, '[' ) === false ) {
342 // The amount is static, so it doesn't need an update.
343 continue;
344 }
345
346 // Update amount based on field shortcodes.
347 $entry = self::generate_false_entry();
348 $amount = FrmStrpLiteActionsController::prepare_amount( $amount, compact( 'form', 'entry', 'action' ) );
349 if ( $saved->amount == $amount || $amount == '000' ) {
350 continue;
351 }
352
353 FrmStrpLiteAppHelper::call_stripe_helper_class( 'update_intent', $intent_id, array( 'amount' => $amount ) );
354 }//end foreach
355 }//end foreach
356 }
357
358 /**
359 * Create an entry object with posted values.
360 *
361 * @since 6.5, introduced in v2.0 of the Stripe add on.
362 * @return stdClass
363 */
364 private static function generate_false_entry() {
365 $entry = new stdClass();
366 $entry->post_id = 0;
367 $entry->id = 0;
368 $entry->metas = array();
369
370 // phpcs:ignore WordPress.Security.NonceVerification.Missing
371 foreach ( $_POST as $k => $v ) {
372 $k = sanitize_text_field( stripslashes( $k ) );
373 $v = wp_unslash( $v );
374
375 if ( $k === 'item_meta' ) {
376 foreach ( $v as $f => $value ) {
377 FrmAppHelper::sanitize_value( 'wp_kses_post', $value );
378 $entry->metas[ absint( $f ) ] = $value;
379 }
380 } else {
381 FrmAppHelper::sanitize_value( 'wp_kses_post', $v );
382 $entry->{$k} = $v;
383 }
384 }
385
386 return $entry;
387 }
388
389 /**
390 * Reformat the form data in name => value array.
391 *
392 * @since 6.5, introduced in v2.0 of the Stripe add on.
393 *
394 * @param array $form
395 * @return void
396 */
397 private static function format_form_data( &$form ) {
398 $formatted = array();
399
400 foreach ( $form as $input ) {
401 $key = $input['name'];
402 if ( isset( $formatted[ $key ] ) ) {
403 if ( is_array( $formatted[ $key ] ) ) {
404 $formatted[ $key ][] = $input['value'];
405 } else {
406 $formatted[ $key ] = array( $formatted[ $key ], $input['value'] );
407 }
408 } else {
409 $formatted[ $key ] = $input['value'];
410 }
411 }
412
413 parse_str( http_build_query( $formatted ), $form );
414 }
415
416 /**
417 * Create intents on form load when required.
418 * This only happens in two cases: For stripe link, and when processing a one-time payment before the entry is created.
419 *
420 * @since 6.5, introduced in v2.0 of the Stripe add on.
421 *
422 * @param int|string $form_id
423 * @return array
424 */
425 private static function maybe_create_intents( $form_id ) {
426 $intents = array();
427
428 $details = self::check_request_params( $form_id );
429 if ( is_array( $details ) ) {
430 $payment = $details['payment'];
431 $intent = $details['intent'];
432 $payment_failed = self::payment_failed( $payment, $intent );
433
434 // Exit early if the request params are set.
435 // This way an extra payment intent isn't created for Stripe Link.
436 if ( ! $payment_failed ) {
437 return $intents;
438 }
439 }
440
441 if ( ! FrmStrpLiteAppHelper::call_stripe_helper_class( 'initialize_api' ) ) {
442 // Stripe is not configured, so don't create intents.
443 return $intents;
444 }
445
446 $actions = FrmStrpLiteActionsController::get_actions_before_submit( $form_id );
447 self::add_amount_to_actions( $form_id, $actions );
448
449 foreach ( $actions as $action ) {
450 if ( is_array( $details ) && self::intent_has_failed_status( $details['intent'] ) ) {
451 $intents[] = array(
452 'id' => $details['intent']->client_secret,
453 'action' => $action->ID,
454 );
455 continue;
456 }
457
458 $intent = self::create_intent( $action );
459 if ( ! is_object( $intent ) ) {
460 // A non-object is a string error message.
461 // The error gets logged to results.log so we can just skip it.
462 // Reasons it could fail is because a payment method type was specified that will not work.
463 // A payment method type may not work because of a currency conflict, or because it isn't enabled.
464 // Or the payment method type could be an incorrect value.
465 // When using Stripe Connect, the error will just say "Unable to create intent".
466 // In this case, you can find the full error message in the Stripe dashboard.
467 continue;
468 }
469
470 $intents[] = array(
471 'id' => $intent->client_secret,
472 'action' => $action->ID,
473 );
474 }//end foreach
475
476 return $intents;
477 }
478
479 /**
480 * Create a payment intent for Stripe link or when processing a payment before the entry is created.
481 *
482 * @since 3.0 This code was moved out of self::maybe_create_intents into a new function.
483 *
484 * @param WP_Post $action
485 * @return mixed
486 */
487 private static function create_intent( $action ) {
488 $amount = $action->post_content['amount'];
489 if ( $amount == '000' ) {
490 // Create the intent when the form loads.
491 $amount = 100;
492 }
493
494 if ( 'recurring' === $action->post_content['type'] ) {
495 $payment_method_types = FrmStrpLitePaymentTypeHandler::get_payment_method_types( $action );
496 return self::create_setup_intent( $payment_method_types );
497 }
498
499 $new_charge = array(
500 'amount' => $amount,
501 'currency' => $action->post_content['currency'],
502 'metadata' => array( 'action' => $action->ID ),
503 );
504
505 if ( FrmStrpLitePaymentTypeHandler::should_use_automatic_payment_methods( $action ) ) {
506 $new_charge['automatic_payment_methods'] = array( 'enabled' => true );
507 } else {
508 $payment_method_types = FrmStrpLitePaymentTypeHandler::get_payment_method_types( $action );
509 $new_charge['payment_method_types'] = $payment_method_types;
510 }
511
512 return FrmStrpLiteAppHelper::call_stripe_helper_class( 'create_intent', $new_charge );
513 }
514
515 /**
516 * Create a customer and an associated setup intent for a recurring Stripe link payment.
517 *
518 * @since 6.5, introduced in v3.0 of the Stripe add on.
519 *
520 * @param array $payment_method_types
521 * @return false|object
522 */
523 private static function create_setup_intent( $payment_method_types ) {
524 $payment_info = array(
525 'user_id' => FrmTransLiteAppHelper::get_user_id_for_current_payment(),
526 );
527
528 // We need to add a customer to support subscriptions with link.
529 $customer = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_customer', $payment_info );
530 if ( ! is_object( $customer ) ) {
531 return false;
532 }
533
534 return FrmStrpLiteAppHelper::call_stripe_helper_class( 'create_setup_intent', $customer->id, $payment_method_types );
535 }
536
537 /**
538 * @since 6.5, introduced in v2.0 of the Stripe add on.
539 *
540 * @param int|string $form_id
541 * @param array $actions
542 * @return void
543 */
544 private static function add_amount_to_actions( $form_id, &$actions ) {
545 if ( empty( $actions ) ) {
546 return;
547 }
548 $form = FrmForm::getOne( $form_id );
549
550 foreach ( $actions as $k => $action ) {
551 $amount = self::get_amount_before_submit( compact( 'action', 'form' ) );
552 $actions[ $k ]->post_content['amount'] = $amount;
553 }
554 }
555
556 /**
557 * @since 6.5, introduced in v2.0 of the Stripe add on.
558 *
559 * @param array $atts
560 * @return string
561 */
562 private static function get_amount_before_submit( $atts ) {
563 $amount = $atts['action']->post_content['amount'];
564 return FrmStrpLiteActionsController::prepare_amount( $atts['action']->post_content['amount'], $atts );
565 }
566
567 /**
568 * Get the URL to return to after a payment is complete.
569 * This may either use the success URL on redirect, or the message on success.
570 * It shouldn't be confused for the Stripe link return URL. It isn't used for that. That uses the frmstrplinkreturn AJAX action instead.
571 *
572 * @since 6.5, introduced in v2.0 of the Stripe add on.
573 *
574 * @param array $atts
575 * @return string
576 */
577 public static function return_url( $atts ) {
578 $atts = array(
579 'entry' => $atts['entry'],
580 );
581 self::prepare_success_atts( $atts );
582
583 if ( $atts['conf_method'] === 'redirect' ) {
584 $redirect = self::get_redirect_url( $atts );
585 } else {
586 $redirect = self::get_message_url( $atts );
587 }
588
589 return $redirect;
590 }
591
592 /**
593 * If the form should redirect, get the url to redirect to.
594 *
595 * @since 6.5, introduced in v2.0 of the Stripe add on.
596 *
597 * @param array $atts {
598 * The form and entry details.
599 *
600 * @type stdClass $form
601 * @type stdClass $entry
602 * }
603 * @return string
604 */
605 private static function get_redirect_url( $atts ) {
606 $actions = FrmFormsController::get_met_on_submit_actions( $atts );
607 if ( $actions ) {
608 $success_url = reset( $actions )->post_content['success_url'];
609 }
610
611 if ( empty( $success_url ) ) {
612 $success_url = $atts['form']->options['success_url'];
613 }
614
615 $success_url = trim( $atts['form']->options['success_url'] );
616 $success_url = apply_filters( 'frm_content', $success_url, $atts['form'], $atts['entry'] );
617 $success_url = do_shortcode( $success_url );
618 $atts['id'] = $atts['entry']->id;
619
620 add_filter( 'frm_redirect_url', 'FrmEntriesController::prepare_redirect_url' );
621 return apply_filters( 'frm_redirect_url', $success_url, $atts['form'], $atts );
622 }
623
624 /**
625 * If the form should should a message, append it to the success url.
626 *
627 * @since 6.5, introduced in v2.0 of the Stripe add on.
628 *
629 * @param array $atts
630 */
631 private static function get_message_url( $atts ) {
632 $url = self::get_referer_url( $atts['entry_id'], false );
633 if ( false === $url ) {
634 $url = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
635 }
636 return add_query_arg( array( 'frmstrp' => $atts['entry_id'] ), $url );
637 }
638
639 /**
640 * @since 6.5
641 *
642 * @param int|string $entry_id
643 * @param bool $delete_meta
644 * @return false|string
645 */
646 public static function get_referer_url( $entry_id, $delete_meta = true ) {
647 $row = FrmDb::get_row(
648 'frm_item_metas',
649 array(
650 'field_id' => 0,
651 'item_id' => $entry_id,
652 'meta_value LIKE' => '{"referer":',
653 ),
654 'id, meta_value'
655 );
656 if ( ! $row ) {
657 return false;
658 }
659
660 $meta = $row->meta_value;
661 $meta = json_decode( $meta, true );
662
663 if ( ! is_array( $meta ) || empty( $meta['referer'] ) ) {
664 return false;
665 }
666
667 self::delete_temporary_referer_meta( (int) $row->id );
668 return $meta['referer'];
669 }
670
671 /**
672 * Delete the referer meta as we'll no longer need it.
673 *
674 * @param int $row_id
675 * @return void
676 */
677 private static function delete_temporary_referer_meta( $row_id ) {
678 global $wpdb;
679 $wpdb->delete( $wpdb->prefix . 'frm_item_metas', array( 'id' => $row_id ) );
680 }
681
682 /**
683 * Check if a payment or setup intent has failed.
684 *
685 * @since 6.5.1
686 *
687 * @param object $intent
688 * @return bool
689 */
690 private static function intent_has_failed_status( $intent ) {
691 return in_array( $intent->status, array( 'requires_source', 'requires_payment_method', 'canceled' ), true );
692 }
693
694 /**
695 * Check if a payment failed.
696 *
697 * @since 6.8
698 *
699 * @param object $payment
700 * @param object $intent
701 * @return bool
702 */
703 public static function payment_failed( $payment, $intent ) {
704 if ( self::intent_has_failed_status( $intent ) ) {
705 return true;
706 }
707
708 // The $intent will be "succeeded" with a failed payment when testing with the 4000000000000341 credit card.
709 if ( 'payment_failed' === FrmAppHelper::simple_get( 'frm_link_error' ) && 'failed' === $payment->status ) {
710 return true;
711 }
712
713 return false;
714 }
715 }
716