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

685 lines 19.8 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 && isset( $filter ) ) {
75 remove_filter( 'frm_content', $filter );
76 }
77
78 return $message;
79 }
80
81 /**
82 * @param string|int $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 int|false 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 }
355 }
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 string|int $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 ) && ! self::intent_has_failed_status( $details['intent'] ) ) {
430 // Exit early if the request params are set.
431 // This way an extra payment intent isn't created for Stripe Link.
432 return $intents;
433 }
434
435 if ( ! FrmStrpLiteAppHelper::call_stripe_helper_class( 'initialize_api' ) ) {
436 // Stripe is not configured, so don't create intents.
437 return $intents;
438 }
439
440 $actions = FrmStrpLiteActionsController::get_actions_before_submit( $form_id );
441 self::add_amount_to_actions( $form_id, $actions );
442
443 foreach ( $actions as $action ) {
444 if ( is_array( $details ) && self::intent_has_failed_status( $details['intent'] ) ) {
445 $intents[] = array(
446 'id' => $details['intent']->client_secret,
447 'action' => $action->ID,
448 );
449 continue;
450 }
451
452 $intent = self::create_intent( $action );
453 if ( ! is_object( $intent ) ) {
454 // A non-object is a string error message.
455 // The error gets logged to results.log so we can just skip it.
456 // Reasons it could fail is because a payment method type was specified that will not work.
457 // A payment method type may not work because of a currency conflict, or because it isn't enabled.
458 // Or the payment method type could be an incorrect value.
459 // When using Stripe Connect, the error will just say "Unable to create intent".
460 // In this case, you can find the full error message in the Stripe dashboard.
461 continue;
462 }
463
464 $intents[] = array(
465 'id' => $intent->client_secret,
466 'action' => $action->ID,
467 );
468 }
469
470 return $intents;
471 }
472
473 /**
474 * Create a payment intent for Stripe link or when processing a payment before the entry is created.
475 *
476 * @since 3.0 This code was moved out of self::maybe_create_intents into a new function.
477 *
478 * @param WP_Post $action
479 * @return mixed
480 */
481 private static function create_intent( $action ) {
482 $amount = $action->post_content['amount'];
483 if ( $amount == '000' ) {
484 $amount = 100; // Create the intent when the form loads.
485 }
486
487 if ( 'recurring' === $action->post_content['type'] ) {
488 $payment_method_types = FrmStrpLitePaymentTypeHandler::get_payment_method_types( $action );
489 return self::create_setup_intent( $payment_method_types );
490 }
491
492 $new_charge = array(
493 'amount' => $amount,
494 'currency' => $action->post_content['currency'],
495 'metadata' => array( 'action' => $action->ID ),
496 );
497
498 if ( FrmStrpLitePaymentTypeHandler::should_use_automatic_payment_methods( $action ) ) {
499 $new_charge['automatic_payment_methods'] = array( 'enabled' => true );
500 } else {
501 $payment_method_types = FrmStrpLitePaymentTypeHandler::get_payment_method_types( $action );
502 $new_charge['payment_method_types'] = $payment_method_types;
503 }
504
505 return FrmStrpLiteAppHelper::call_stripe_helper_class( 'create_intent', $new_charge );
506 }
507
508 /**
509 * Create a customer and an associated setup intent for a recurring Stripe link payment.
510 *
511 * @since 6.5, introduced in v3.0 of the Stripe add on.
512 *
513 * @param array $payment_method_types
514 * @return object|false
515 */
516 private static function create_setup_intent( $payment_method_types ) {
517 $payment_info = array(
518 'user_id' => FrmTransLiteAppHelper::get_user_id_for_current_payment(),
519 );
520
521 // We need to add a customer to support subscriptions with link.
522 $customer = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_customer', $payment_info );
523 if ( ! is_object( $customer ) ) {
524 return false;
525 }
526
527 return FrmStrpLiteAppHelper::call_stripe_helper_class( 'create_setup_intent', $customer->id, $payment_method_types );
528 }
529
530 /**
531 * @since 6.5, introduced in v2.0 of the Stripe add on.
532 *
533 * @param string|int $form_id
534 * @param array $actions
535 * @return void
536 */
537 private static function add_amount_to_actions( $form_id, &$actions ) {
538 if ( empty( $actions ) ) {
539 return;
540 }
541 $form = FrmForm::getOne( $form_id );
542
543 foreach ( $actions as $k => $action ) {
544 $amount = self::get_amount_before_submit( compact( 'action', 'form' ) );
545 $actions[ $k ]->post_content['amount'] = $amount;
546 }
547 }
548
549 /**
550 * @since 6.5, introduced in v2.0 of the Stripe add on.
551 *
552 * @param array $atts
553 * @return string
554 */
555 private static function get_amount_before_submit( $atts ) {
556 $amount = $atts['action']->post_content['amount'];
557 return FrmStrpLiteActionsController::prepare_amount( $atts['action']->post_content['amount'], $atts );
558 }
559
560 /**
561 * Get the URL to return to after a payment is complete.
562 * This may either use the success URL on redirect, or the message on success.
563 * It shouldn't be confused for the Stripe link return URL. It isn't used for that. That uses the frmstrplinkreturn AJAX action instead.
564 *
565 * @since 6.5, introduced in v2.0 of the Stripe add on.
566 *
567 * @param array $atts
568 * @return string
569 */
570 public static function return_url( $atts ) {
571 $atts = array(
572 'entry' => $atts['entry'],
573 );
574 self::prepare_success_atts( $atts );
575
576 if ( $atts['conf_method'] === 'redirect' ) {
577 $redirect = self::get_redirect_url( $atts );
578 } else {
579 $redirect = self::get_message_url( $atts );
580 }
581
582 return $redirect;
583 }
584
585 /**
586 * If the form should redirect, get the url to redirect to.
587 *
588 * @since 6.5, introduced in v2.0 of the Stripe add on.
589 *
590 * @param array $atts {
591 * @type stdClass $form
592 * @type stdClass $entry
593 * }
594 * @return string
595 */
596 private static function get_redirect_url( $atts ) {
597 $actions = FrmFormsController::get_met_on_submit_actions( $atts );
598 if ( $actions ) {
599 $success_url = reset( $actions )->post_content['success_url'];
600 }
601
602 if ( empty( $success_url ) ) {
603 $success_url = $atts['form']->options['success_url'];
604 }
605
606 $success_url = trim( $atts['form']->options['success_url'] );
607 $success_url = apply_filters( 'frm_content', $success_url, $atts['form'], $atts['entry'] );
608 $success_url = do_shortcode( $success_url );
609 $atts['id'] = $atts['entry']->id;
610
611 add_filter( 'frm_redirect_url', 'FrmEntriesController::prepare_redirect_url' );
612 return apply_filters( 'frm_redirect_url', $success_url, $atts['form'], $atts );
613 }
614
615 /**
616 * If the form should should a message, apend it to the success url.
617 *
618 * @since 6.5, introduced in v2.0 of the Stripe add on.
619 *
620 * @param array $atts
621 */
622 private static function get_message_url( $atts ) {
623 $url = self::get_referer_url( $atts['entry_id'], false );
624 if ( false === $url ) {
625 $url = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
626 }
627 return add_query_arg( array( 'frmstrp' => $atts['entry_id'] ), $url );
628 }
629
630 /**
631 * @since 6.5
632 *
633 * @param string|int $entry_id
634 * @param bool $delete_meta
635 * @return string|false
636 */
637 public static function get_referer_url( $entry_id, $delete_meta = true ) {
638 $row = FrmDb::get_row(
639 'frm_item_metas',
640 array(
641 'field_id' => 0,
642 'item_id' => $entry_id,
643 'meta_value LIKE' => '{"referer":',
644 ),
645 'id, meta_value'
646 );
647 if ( ! $row ) {
648 return false;
649 }
650
651 $meta = $row->meta_value;
652 $meta = json_decode( $meta, true );
653
654 if ( ! is_array( $meta ) || empty( $meta['referer'] ) ) {
655 return false;
656 }
657
658 self::delete_temporary_referer_meta( (int) $row->id );
659 return $meta['referer'];
660 }
661
662 /**
663 * Delete the referer meta as we'll no longer need it.
664 *
665 * @param int $row_id
666 * @return void
667 */
668 private static function delete_temporary_referer_meta( $row_id ) {
669 global $wpdb;
670 $wpdb->delete( $wpdb->prefix . 'frm_item_metas', array( 'id' => $row_id ) );
671 }
672
673 /**
674 * Check if a payment or setup intent has failed.
675 *
676 * @since 6.5.1
677 *
678 * @param object $intent
679 * @return bool
680 */
681 private static function intent_has_failed_status( $intent ) {
682 return in_array( $intent->status, array( 'requires_source', 'requires_payment_method', 'canceled' ), true );
683 }
684 }
685