PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.35
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.35
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 / controllers / FrmStrpLiteActionsController.php

FrmStrpLiteActionsController.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.35, at stripe/controllers/FrmStrpLiteActionsController.php

731 lines 20.1 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 FrmStrpLiteActionsController extends FrmTransLiteActionsController {
7
8 /**
9 * @var object|string|null A memoized value for the current Stripe customer object.
10 */
11 private static $customer;
12
13 /**
14 * @since 6.22
15 *
16 * @param string $callback
17 * @param array|false|object $field
18 *
19 * @return string
20 */
21 public static function maybe_show_card( $callback, $field = false ) {
22 if ( false === $field ) {
23 // Pro isn't up to date.
24 // Fallback to Stripe Lite if we do not know the form.
25 // This way we do not display the default credit card field
26 // when Pro is not up to version 6.21.
27 return self::class . '::show_card';
28 }
29
30 $form_id = is_object( $field ) ? $field->form_id : $field['form_id'];
31 $actions = self::get_actions_before_submit( $form_id );
32
33 return $actions ? self::class . '::show_card' : $callback;
34 }
35
36 /**
37 * Override the credit card field HTML if there is a Stripe action.
38 *
39 * @since 6.5, introduced in v2.0 of the Stripe add on.
40 *
41 * @param array $field
42 * @param string $field_name
43 * @param array $atts
44 *
45 * @return void
46 */
47 public static function show_card( $field, $field_name, $atts ) {
48 $actions = self::get_actions_before_submit( $field['form_id'] );
49
50 // Use the Pro function when there are no Stripe actions.
51 // This is required for other gateways like Authorize.Net.
52 if ( ! $actions && is_callable( 'FrmProCreditCardsController::show_in_form' ) ) {
53 FrmProCreditCardsController::show_in_form( $field, $field_name, $atts );
54 }
55
56 $html_id = $atts['html_id'];
57 include FrmStrpLiteAppHelper::plugin_path() . '/views/payments/card-field.php';
58 }
59
60 /**
61 * Get all published payment actions with the Stripe gateway that have an amount set.
62 *
63 * @since 6.5, introduced in v2.0 of the Stripe add on.
64 *
65 * @param int|string $form_id
66 *
67 * @return array
68 */
69 public static function get_actions_before_submit( $form_id ) {
70 $payment_actions = self::get_actions_for_form( $form_id );
71
72 foreach ( $payment_actions as $k => $payment_action ) {
73 $gateway = $payment_action->post_content['gateway'];
74 $is_stripe = $gateway === 'stripe' || ( is_array( $gateway ) && in_array( 'stripe', $gateway, true ) );
75
76 if ( ! $is_stripe || empty( $payment_action->post_content['amount'] ) ) {
77 unset( $payment_actions[ $k ] );
78 }
79 }
80
81 return $payment_actions;
82 }
83
84 /**
85 * Check the Stripe link action for a form. There should only be one.
86 * We want to ignore $action->post_content['stripe_link'].
87 * This way any Stripe action will fall back to Stripe link when the Stripe add on is unavailable.
88 *
89 * @since 6.5, introduced in v3.0 of the Stripe add on.
90 *
91 * @param int|string $form_id
92 *
93 * @return false|WP_Post
94 */
95 public static function get_stripe_link_action( $form_id ) {
96 $actions = self::get_actions_before_submit( $form_id );
97 return reset( $actions );
98 }
99
100 /**
101 * Trigger a Stripe payment after a form is submitted.
102 * This is called for both one time and recurring payments.
103 * It is also called when Stripe link is active but the Stripe payment is triggered instead with JavaScript.
104 *
105 * @param WP_Post $action
106 * @param stdClass $entry
107 * @param mixed $form
108 *
109 * @return array
110 */
111 public static function trigger_gateway( $action, $entry, $form ) {
112 $response = array(
113 'success' => false,
114 'run_triggers' => false,
115 'show_errors' => true,
116 );
117 $atts = compact( 'action', 'entry', 'form' );
118 $amount = self::prepare_amount( $action->post_content['amount'], $atts );
119
120 // phpcs:ignore Universal.Operators.StrictComparisons
121 if ( ! $amount || $amount == 000 ) {
122 $response['error'] = __( 'Please specify an amount for the payment', 'formidable' );
123 return $response;
124 }
125
126 if ( ! self::stripe_is_configured() ) {
127 $response['error'] = __( 'Stripe still needs to be configured.', 'formidable' );
128 return $response;
129 }
130
131 $customer = self::set_customer_with_token( $atts, self::get_customer_id_from_posted_setup_intents( $form->id ) );
132
133 if ( ! is_object( $customer ) ) {
134 $response['error'] = $customer;
135 return $response;
136 }
137
138 $one_time_payment_args = compact( 'customer', 'form', 'entry', 'action', 'amount' );
139
140 if ( ! FrmStrpLiteLinkController::create_pending_stripe_link_payment( $one_time_payment_args ) ) {
141 $response['error'] = __( 'There was something wrong with the payment data.', 'formidable' );
142 return $response;
143 }
144
145 $response['show_errors'] = false;
146 return $response;
147 }
148
149 /**
150 * Check if either Stripe integration is enabled.
151 *
152 * @return bool true if Stripe Connect is set up.
153 */
154 private static function stripe_is_configured() {
155 return FrmStrpLiteAppHelper::call_stripe_helper_class( 'initialize_api' );
156 }
157
158 /**
159 * Get the customer id from a setup intent that was created for this submission.
160 * A Stripe Link subscription builds its subscription from the setup intent's customer,
161 * so reusing that customer here avoids creating a duplicate customer for guests.
162 *
163 * @since 6.35
164 *
165 * @param int|string $form_id
166 *
167 * @return false|string The Stripe customer id, or false if it can't be found.
168 */
169 public static function get_customer_id_from_posted_setup_intents( $form_id ) {
170 $posted_intents = FrmStrpLiteAuth::get_payment_intents( 'frmintent' . $form_id );
171
172 if ( ! is_array( $posted_intents ) || ! $posted_intents ) {
173 return false;
174 }
175
176 $setup_intent_ids = array_filter(
177 $posted_intents,
178 function ( $intent_id ) {
179 return str_starts_with( $intent_id, 'seti_' );
180 }
181 );
182
183 if ( ! $setup_intent_ids ) {
184 return false;
185 }
186
187 $first_setup_intent_id = reset( $setup_intent_ids );
188 $first_setup_intent_id = explode( '_secret_', $first_setup_intent_id )[0];
189 $setup_intent = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_setup_intent', $first_setup_intent_id );
190
191 if ( ! is_object( $setup_intent ) || empty( $setup_intent->customer ) ) {
192 return false;
193 }
194
195 return $setup_intent->customer;
196 }
197
198 /**
199 * Set a customer object to $_POST['customer'] to use later.
200 *
201 * @param array $atts The action, entry, and form for the payment.
202 * @param false|string $customer_id The Stripe customer id when it is already known.
203 *
204 * @return object|string
205 */
206 private static function set_customer_with_token( $atts, $customer_id = false ) {
207 if ( isset( self::$customer ) ) {
208 // It's an object if this isn't the first Stripe action running.
209 return self::$customer;
210 }
211
212 $payment_info = array(
213 'user_id' => FrmTransLiteAppHelper::get_user_id_for_current_payment(),
214 );
215
216 if ( $customer_id ) {
217 $payment_info['customer_id'] = $customer_id;
218 }
219
220 if ( ! empty( $atts['action']->post_content['email'] ) ) {
221 $payment_info['email'] = apply_filters( 'frm_content', $atts['action']->post_content['email'], $atts['form'], $atts['entry'] );
222 $payment_info['email'] = self::replace_email_shortcode( $payment_info['email'] );
223 }
224
225 self::add_customer_name( $atts, $payment_info );
226
227 $customer = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_customer', $payment_info );
228 // Set for later use.
229 self::$customer = $customer;
230
231 return $customer;
232 }
233
234 /**
235 * Replace an [email] shortcode with the current user email.
236 *
237 * @param string $email
238 *
239 * @return string
240 */
241 private static function replace_email_shortcode( $email ) {
242 if ( ! str_contains( $email, '[email]' ) ) {
243 return $email;
244 }
245
246 global $current_user;
247 return str_replace(
248 '[email]',
249 ! empty( $current_user->user_email ) ? $current_user->user_email : '',
250 $email
251 );
252 }
253
254 /**
255 * Set the customer name based on the mapped first and last name fields in the Stripe action.
256 *
257 * @since 6.5, introduced in v2.02 of the Stripe add on.
258 *
259 * @param array $atts
260 * @param array $payment_info
261 *
262 * @return void
263 */
264 private static function add_customer_name( $atts, &$payment_info ) {
265 if ( empty( $atts['action']->post_content['billing_first_name'] ) ) {
266 return;
267 }
268
269 $name = '[' . $atts['action']->post_content['billing_first_name'] . ' show="first"]';
270
271 if ( ! empty( $atts['action']->post_content['billing_last_name'] ) ) {
272 $name .= ' [' . $atts['action']->post_content['billing_last_name'] . ' show="last"]';
273 }
274
275 $payment_info['name'] = apply_filters( 'frm_content', $name, $atts['form'], $atts['entry'] );
276 }
277
278 /**
279 * Get the trial period from the settings or from the connected entry.
280 *
281 * @since 6.5, introduced in v1.16 of the Stripe add on.
282 *
283 * @param array $atts Includes 'customer', 'entry', 'action', 'amount'.
284 *
285 * @return int The timestamp when the trial should end. 0 for no trial
286 */
287 public static function get_trial_end_time( $atts ) {
288 $settings = $atts['action']->post_content;
289
290 if ( empty( $settings['trial_interval_count'] ) ) {
291 return 0;
292 }
293
294 $trial = $settings['trial_interval_count'];
295
296 if ( ! is_numeric( $trial ) ) {
297 $trial = FrmTransLiteAppHelper::process_shortcodes(
298 array(
299 'value' => $trial,
300 'entry' => $atts['entry'],
301 )
302 );
303 }
304
305 if ( ! $trial ) {
306 return 0;
307 }
308
309 return strtotime( '+' . absint( $trial ) . ' days' );
310 }
311
312 /**
313 * Convert the amount from 10.00 to 1000.
314 *
315 * @param mixed $amount
316 * @param array $atts
317 *
318 * @return string
319 */
320 public static function prepare_amount( $amount, $atts = array() ) {
321 $amount = parent::prepare_amount( $amount, $atts );
322 $currency = self::get_currency_for_action( $atts );
323 return number_format( $amount, $currency['decimals'], '', '' );
324 }
325
326 /**
327 * Add defaults for additional Stripe action options.
328 *
329 * @param array $defaults
330 *
331 * @return array
332 */
333 public static function add_action_defaults( $defaults ) {
334 // Stripe action options.
335 $defaults['plan_id'] = '';
336 $defaults['capture'] = '';
337 $defaults['stripe_link'] = '';
338
339 // PayPal action options.
340 $defaults['product_name'] = '';
341 $defaults['pay_later'] = '';
342
343 return $defaults;
344 }
345
346 /**
347 * Print additional options for Stripe action settings.
348 *
349 * @param array $atts
350 *
351 * @return void
352 */
353 public static function add_action_options( $atts ) {
354 $form_action = $atts['form_action'];
355 $action_control = $atts['action_control'];
356 include FrmStrpLiteAppHelper::plugin_path() . '/views/action-settings/options.php';
357 }
358
359 /**
360 * Filter Stripe action on save.
361 *
362 * @param array $settings
363 * @param array $action
364 *
365 * @return array
366 */
367 public static function before_save_settings( $settings, $action ) {
368 $settings['currency'] = strtolower( $settings['currency'] );
369
370 // Gateway is a radio button but it should always be an array in the database for
371 // compatibility with the payments submodule where it is a checkbox (when Authorize.Net is active).
372 $settings['gateway'] = ! empty( $settings['gateway'] ) ? (array) $settings['gateway'] : array( 'stripe' );
373
374 $is_stripe = in_array( 'stripe', $settings['gateway'], true );
375
376 if ( ! $is_stripe ) {
377 return $settings;
378 }
379
380 // In Lite Stripe link is always used.
381 $settings['stripe_link'] = 1;
382
383 return self::create_plans( $settings );
384 }
385
386 /**
387 * Create any required Stripe plans, used for subscriptions.
388 *
389 * @param array $settings
390 *
391 * @return array
392 */
393 public static function create_plans( $settings ) {
394 if ( $settings['type'] !== 'recurring' || strpos( $settings['amount'], ']' ) ) {
395 // Don't create a plan for one time payments, or for actions with shortcodes in the value.
396 $settings['plan_id'] = '';
397 return $settings;
398 }
399
400 $plan_opts = FrmStrpLiteSubscriptionHelper::prepare_plan_options( $settings );
401
402 // phpcs:ignore Universal.Operators.StrictComparisons
403 if ( $plan_opts['id'] != $settings['plan_id'] ) {
404 $settings['plan_id'] = FrmStrpLiteSubscriptionHelper::maybe_create_plan( $plan_opts );
405 }
406
407 return $settings;
408 }
409
410 /**
411 * Include settings in the plan description in order to make sure the correct plan is used.
412 *
413 * @param array $settings
414 *
415 * @return string
416 */
417 public static function create_plan_id( $settings ) {
418 $amount = self::prepare_amount( $settings['amount'], $settings );
419 $parts = array(
420 sanitize_title_with_dashes( $settings['description'] ),
421 $amount,
422 $settings['interval_count'] . $settings['interval'],
423 $settings['currency'],
424 );
425
426 if ( isset( $settings['trial_interval_count'] ) && '' !== $settings['trial_interval_count'] ) {
427 // Include the trial so two actions that differ only by trial length don't share a plan.
428 $parts[] = $settings['trial_interval_count'];
429 }
430
431 return implode( '_', $parts );
432 }
433
434 /**
435 * If this form submits with ajax, load the scripts on the first page.
436 *
437 * @param array $params
438 *
439 * @return void
440 */
441 public static function maybe_load_scripts( $params ) {
442 // phpcs:ignore Universal.Operators.StrictComparisons
443 if ( $params['form_id'] == $params['posted_form_id'] ) {
444 // This form has already been posted, so we aren't on the first page.
445 return;
446 }
447
448 $form = FrmForm::getOne( $params['form_id'] );
449
450 if ( ! $form ) {
451 return;
452 }
453
454 $credit_card_field = FrmField::getAll(
455 array(
456 'fi.form_id' => $form->id,
457 'type' => 'credit_card',
458 )
459 );
460
461 if ( ! $credit_card_field ) {
462 return;
463 }
464
465 self::load_scripts( (int) $form->id );
466 }
467
468 /**
469 * Load front end JavaScript for a Stripe form.
470 *
471 * @param int $form_id
472 *
473 * @return void
474 */
475 public static function load_scripts( $form_id ) {
476 if ( FrmAppHelper::is_admin_page( 'formidable-entries' ) ) {
477 return;
478 }
479
480 if ( wp_script_is( 'formidable-stripe', 'enqueued' ) ) {
481 return;
482 }
483
484 $stripe_connect_is_setup = FrmStrpLiteConnectHelper::stripe_connect_is_setup();
485
486 if ( ! $stripe_connect_is_setup ) {
487 return;
488 }
489
490 if ( ! $form_id || ! is_int( $form_id ) ) {
491 _doing_it_wrong( __METHOD__, '$form_id parameter must be a non-zero integer', '6.5' );
492 return;
493 }
494
495 $settings = FrmStrpLiteAppHelper::get_settings();
496 $publishable = $settings->get_active_publishable_key();
497
498 wp_register_script(
499 'stripe',
500 'https://js.stripe.com/v3/',
501 array(),
502 '3.0',
503 false
504 );
505
506 $suffix = FrmAppHelper::js_suffix();
507 $dependencies = array( 'stripe', 'formidable' );
508
509 if ( '.min' === $suffix && is_readable( FrmAppHelper::plugin_path() . '/js/frmstrp.min.js' ) ) {
510 // Use the combined file if it is available.
511 $script_url = FrmAppHelper::plugin_url() . '/js/frmstrp.min.js';
512 } else {
513 if ( ! $suffix && ! is_readable( FrmStrpLiteAppHelper::plugin_path() . 'js/frmstrp.js' ) ) {
514 // The unminified file is not included in releases so force the minified script.
515 $suffix = '.min';
516 }
517
518 $script_url = FrmStrpLiteAppHelper::plugin_url() . 'js/frmstrp' . $suffix . '.js';
519 }
520
521 if ( class_exists( 'FrmProStrpLiteController' ) && ( ! $suffix || ! FrmProAppController::has_combo_js_file() ) ) {
522 $dependencies[] = 'formidablepro';
523 }
524
525 $action_settings = self::prepare_settings_for_js( $form_id );
526 $found_gateway = false;
527
528 foreach ( $action_settings as &$action ) {
529 $gateways = $action['gateways'];
530
531 if ( ! $gateways || in_array( 'stripe', (array) $gateways, true ) ) {
532 $found_gateway = true;
533 }
534
535 if ( ! empty( $action['layout'] ) && ! in_array( $action['layout'], array( 'accordion', 'tabs' ), true ) ) {
536 $action['layout'] = '';
537 }
538 }
539 unset( $action );
540
541 if ( ! $found_gateway ) {
542 return;
543 }
544
545 wp_enqueue_script(
546 'formidable-stripe',
547 $script_url,
548 $dependencies,
549 FrmAppHelper::plugin_version(),
550 false
551 );
552
553 $style_settings = self::get_style_settings_for_form( $form_id );
554 $stripe_vars = array(
555 'publishable_key' => $publishable,
556 'form_id' => $form_id,
557 'nonce' => wp_create_nonce( 'frm_strp_ajax' ),
558 'ajax' => esc_url_raw( FrmAppHelper::get_ajax_url() ),
559 'settings' => $action_settings,
560 'locale' => self::get_locale(),
561 'baseFontSize' => $style_settings['field_font_size'],
562 'appearanceRules' => self::get_appearance_rules( $style_settings ),
563 'account_id' => FrmStrpLiteConnectHelper::get_account_id(),
564 );
565
566 wp_localize_script( 'formidable-stripe', 'frm_stripe_vars', $stripe_vars );
567 }
568
569 /**
570 * Get and format the style settings for JavaScript to use with the get_appearance_rules function.
571 *
572 * @since 6.5, introduced in v3.0 of the Stripe add on.
573 *
574 * @param mixed $form_id
575 *
576 * @return array
577 */
578 private static function get_style_settings_for_form( $form_id ) {
579 if ( ! $form_id ) {
580 return array();
581 }
582
583 $style = FrmStylesController::get_form_style( $form_id );
584
585 if ( ! $style ) {
586 return array();
587 }
588
589 $settings = FrmStylesHelper::get_settings_for_output( $style );
590 $disallowed = array( ';', ':', '!important' );
591
592 foreach ( $settings as $k => $s ) {
593 if ( is_string( $s ) ) {
594 $settings[ $k ] = str_replace( $disallowed, '', $s );
595 }
596 }
597
598 return $settings;
599 }
600
601 /**
602 * Get the style rules for Stripe link Authentication and Payment elements.
603 * These settings get set to frm_stripe_vars.appearanceRules.
604 * They're in the format that Stripe accepts.
605 * Documentation for Appearance rules can be found at https://stripe.com/docs/elements/appearance-api?platform=web#rules
606 *
607 * @since 6.5, introduced in v3.0 of the Stripe add on.
608 *
609 * @param array $settings
610 *
611 * @return array
612 */
613 private static function get_appearance_rules( $settings ) {
614 $rules = array(
615 '.Input' => array(
616 'fontFamily' => $settings['font'],
617 'color' => $settings['text_color'],
618 'backgroundColor' => $settings['bg_color'],
619 'padding' => $settings['field_pad'],
620 'lineHeight' => 1.3,
621 'borderColor' => $settings['border_color'],
622 'borderWidth' => self::get_border_width( $settings ),
623 'borderStyle' => $settings['field_border_style'],
624 'borderRadius' => self::get_border_radius( $settings ),
625 'fontWeight' => $settings['field_weight'],
626 ),
627 '.Input::placeholder' => array(
628 'color' => $settings['text_color_disabled'],
629 ),
630 '.Input:focus' => array(
631 'backgroundColor' => $settings['bg_color_active'],
632 ),
633 '.Label' => array(
634 'color' => $settings['label_color'],
635 'fontSize' => $settings['font_size'],
636 'fontWeight' => $settings['weight'],
637 'padding' => $settings['label_padding'],
638 'marginBottom' => 0,
639 ),
640 '.Error' => array(
641 'color' => $settings['border_color_error'],
642 ),
643 );
644
645 /*
646 * Filters the appearance rules for Stripe elements.
647 *
648 * @since 6.21
649 *
650 * @param array $rules
651 * @param array $settings
652 */
653 return apply_filters( 'frm_stripe_appearance_rules', $rules, $settings );
654 }
655
656 /**
657 * Get the border width for Stripe elements.
658 *
659 * @since 6.21
660 *
661 * @param array $settings
662 *
663 * @return string
664 */
665 private static function get_border_width( $settings ) {
666 if ( ! empty( $settings['field_shape_type'] ) && 'underline' === $settings['field_shape_type'] ) {
667 return '0 0 ' . $settings['field_border_width'] . ' 0';
668 }
669 return $settings['field_border_width'];
670 }
671
672 /**
673 * Get the border radius for Stripe elements.
674 *
675 * @since 6.21
676 *
677 * @param array $settings
678 *
679 * @return string
680 */
681 private static function get_border_radius( $settings ) {
682 if ( ! empty( $settings['field_shape_type'] ) ) {
683 switch ( $settings['field_shape_type'] ) {
684 case 'underline':
685 case 'regular':
686 return '0px';
687 case 'circle':
688 return '30px';
689 }
690 }
691 return $settings['border_radius'];
692 }
693
694 /**
695 * Get the language to use for Stripe elements.
696 *
697 * @since 6.5, introduced in v2.0 of the Stripe add on.
698 *
699 * @return string
700 */
701 private static function get_locale() {
702 $allowed = array( 'ar', 'da', 'de', 'en', 'es', 'fi', 'fr', 'he', 'it', 'ja', 'nl', 'no', 'pl', 'ru', 'sv', 'zh' );
703 $current = get_locale();
704 $parts = explode( '_', $current );
705 $part = strtolower( $parts[0] );
706 return in_array( $part, $allowed, true ) ? $part : 'auto';
707 }
708
709 /**
710 * If the names are being used on the CC fields,
711 * make sure it doesn't prevent the submission if Stripe has approved.
712 *
713 * @since 6.7.1
714 *
715 * @param array $errors
716 * @param stdClass $field
717 * @param array $values
718 *
719 * @return array
720 */
721 public static function remove_cc_validation( $errors, $field, $values ) {
722 $has_processed = isset( $_POST[ 'frmintent' . $field->form_id ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
723
724 if ( ! $has_processed ) {
725 return $errors;
726 }
727
728 return FrmTransLiteActionsController::remove_cc_errors( $errors, $field );
729 }
730 }
731