PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.28
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.28
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 / FrmTransLiteActionsController.php

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

647 lines 17.2 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 FrmTransLiteActionsController {
7
8 /**
9 * Track the entry IDs we're destroying so we don't attempt to delete an entry more than once.
10 * Set in self::destroy_entry_later.
11 *
12 * @var array
13 */
14 private static $entry_ids_to_destroy_later = array();
15
16 /**
17 * Register payment action type.
18 *
19 * @param array $actions
20 *
21 * @return array
22 */
23 public static function register_actions( $actions ) {
24 $actions['payment'] = 'FrmTransLiteAction';
25 return $actions;
26 }
27
28 /**
29 * Include scripts for handling payments at an administrative level.
30 * This includes handling the after payment settings for Stripe actions.
31 * It also handles refunds and canceling subscriptions.
32 *
33 * @return void
34 */
35 public static function actions_js() {
36 wp_enqueue_script(
37 'frmtrans_admin',
38 FrmTransLiteAppHelper::plugin_url() . '/js/frmtrans_admin.js',
39 array( 'jquery', 'wp-hooks' ),
40 FrmAppHelper::plugin_version()
41 );
42 wp_localize_script(
43 'frmtrans_admin',
44 'frm_trans_vars',
45 array(
46 'nonce' => wp_create_nonce( 'frm_trans_ajax' ),
47 )
48 );
49 }
50
51 /**
52 * Add event types for actions so an email can trigger on a successful payment.
53 *
54 * @param array $triggers
55 *
56 * @return array
57 */
58 public static function add_payment_trigger( $triggers ) {
59 $triggers['payment-success'] = __( 'Successful Payment', 'formidable' );
60 $triggers['payment-failed'] = __( 'Failed Payment', 'formidable' );
61 $triggers['payment-refunded'] = __( 'Refunded Payment', 'formidable' );
62 $triggers['payment-processing'] = __( 'Processing Payment', 'formidable' );
63 $triggers['payment-future-cancel'] = __( 'Canceled Subscription', 'formidable' );
64 $triggers['payment-canceled'] = __( 'Subscription Canceled and Expired', 'formidable' );
65 return $triggers;
66 }
67
68 /**
69 * @param array $options
70 *
71 * @return array
72 */
73 public static function add_trigger_to_action( $options ) {
74 $options['event'][] = 'payment-success';
75 $options['event'][] = 'payment-failed';
76 $options['event'][] = 'payment-processing';
77 $options['event'][] = 'payment-future-cancel';
78 $options['event'][] = 'payment-canceled';
79 $options['event'][] = 'payment-refunded';
80 return $options;
81 }
82
83 /**
84 * @param WP_Post $action
85 * @param stdClass $entry
86 * @param mixed $form
87 *
88 * @return void
89 */
90 public static function trigger_action( $action, $entry, $form ) {
91 self::prepare_description( $action, compact( 'entry', 'form' ) );
92
93 $gateway = self::get_gateway_for_action( $action );
94
95 if ( ! $gateway ) {
96 return;
97 }
98
99 $class_name = FrmTransLiteAppHelper::get_setting_for_gateway( $gateway, 'class' );
100
101 if ( ! $class_name ) {
102 return;
103 }
104
105 $class_name = 'Frm' . $class_name . 'ActionsController';
106 $response = $class_name::trigger_gateway( $action, $entry, $form );
107
108 if ( ! $response['success'] && $response['show_errors'] ) {
109 // The payment failed
110 self::show_failed_message( compact( 'action', 'entry', 'form', 'response' ) );
111 }
112 }
113
114 /**
115 * @param WP_Post $action
116 *
117 * @return array|string
118 */
119 private static function get_gateway_for_action( $action ) {
120 return $action->post_content['gateway'] ?? 'stripe';
121 }
122
123 /**
124 * @since 6.10
125 *
126 * @param array $args
127 *
128 * @return void
129 */
130 private static function show_failed_message( $args ) {
131 global $frm_vars;
132 $frm_vars['frm_trans'] = array(
133 'pay_entry' => $args['entry'],
134 'error' => $args['response']['error'] ?? '',
135 );
136
137 add_filter( 'frm_success_filter', 'FrmTransLiteActionsController::force_message_after_create' );
138 add_filter( 'frm_pre_display_form', 'FrmTransLiteActionsController::include_form_with_success' );
139 add_filter( 'frm_main_feedback', 'FrmTransLiteActionsController::replace_success_message', 5 );
140 add_filter( 'frm_setup_new_fields_vars', 'FrmTransLiteActionsController::fill_entry_from_previous', 20, 2 );
141 }
142
143 /**
144 * @since 6.10
145 *
146 * @param stdClass $form
147 *
148 * @return stdClass
149 */
150 public static function include_form_with_success( $form ) {
151 $form->options['show_form'] = 1;
152 return $form;
153 }
154
155 /**
156 * @return string
157 */
158 public static function replace_success_message() {
159 global $frm_vars;
160 $message = $frm_vars['frm_trans']['error'] ?? '';
161
162 if ( ! $message ) {
163 $message = __( 'There was an error processing your payment.', 'formidable' );
164 }
165
166 return '<div class="frm_error_style">' . $message . '</div>';
167 }
168
169 /**
170 * @param WP_Post $action
171 * @param stdClass $entry
172 * @param mixed $form
173 *
174 * @return array
175 */
176 public static function trigger_gateway( $action, $entry, $form ) {
177 // This function must be overridden in a subclass.
178 return array(
179 'success' => false,
180 'run_triggers' => false,
181 'show_errors' => true,
182 );
183 }
184
185 /**
186 * @return string
187 */
188 public static function force_message_after_create() {
189 return 'message';
190 }
191
192 /**
193 * @since 6.5, introduced in v1.12 of the Payments submodule.
194 *
195 * @param object $sub
196 *
197 * @return void
198 */
199 public static function trigger_subscription_status_change( $sub ) {
200 $frm_payment = new FrmTransLitePayment();
201 $payment = $frm_payment->get_one_by( $sub->id, 'sub_id' );
202
203 if ( $payment && $payment->action_id ) {
204 self::trigger_payment_status_change(
205 array(
206 'status' => $sub->status,
207 'payment' => $payment,
208 )
209 );
210 }
211 }
212
213 /**
214 * @param array $atts
215 *
216 * @return void
217 */
218 public static function trigger_payment_status_change( $atts ) {
219 $entry_id = isset( $atts['entry'] ) ? $atts['entry']->id : $atts['payment']->item_id;
220 $atts = array(
221 'trigger' => $atts['status'],
222 'entry_id' => $entry_id,
223 );
224
225 if ( ! isset( $atts['payment'] ) ) {
226 $frm_payment = new FrmTransLitePayment();
227 $atts['payment'] = $frm_payment->get_one_by( $entry_id, 'item_id' );
228 }
229
230 if ( ! isset( $atts['trigger'] ) ) {
231 $atts['trigger'] = $atts['status'];
232 }
233
234 // Set future-cancel as trigger when applicable.
235 $atts['trigger'] = str_replace( '_', '-', $atts['trigger'] );
236
237 /**
238 * Trigger various hooks including frm_payment_status_complete.
239 *
240 * @since 6.25 This was included in the payments submodule, but not included in Lite until 6.25.
241 *
242 * @param array $atts
243 */
244 do_action( 'frm_payment_status_' . $atts['trigger'], $atts );
245
246 if ( $atts['payment'] ) {
247 self::trigger_actions_after_payment( $atts['payment'], $atts );
248 }
249 }
250
251 /**
252 * Maybe trigger payment-success or payment-failed event after payment so actions (like emails) can run.
253 *
254 * @param object $payment
255 * @param array $atts
256 *
257 * @return void
258 */
259 public static function trigger_actions_after_payment( $payment, $atts = array() ) {
260 if ( 'pending' === $payment->status ) {
261 // 3D Secure has a delayed payment status, so avoid sending a payment failed email for a pending payment.
262 return;
263 }
264
265 $entry = FrmEntry::getOne( $payment->item_id );
266 $trigger_event = isset( $atts['trigger'] ) ? 'payment-' . $atts['trigger'] : 'payment-' . $payment->status;
267 $allowed_triggers = array_keys( self::add_payment_trigger( array() ) );
268
269 if ( ! in_array( $trigger_event, $allowed_triggers, true ) ) {
270 $trigger_event = $payment->status === 'complete' ? 'payment-success' : 'payment-failed';
271 }
272
273 FrmFormActionsController::trigger_actions( $trigger_event, $entry->form_id, $entry->id );
274 }
275
276 /**
277 * Filter fields in description.
278 *
279 * @param WP_Post $action
280 * @param array $atts
281 *
282 * @return void
283 */
284 public static function prepare_description( &$action, $atts ) {
285 $description = $action->post_content['description'];
286
287 if ( ! $description ) {
288 return;
289 }
290
291 $atts['value'] = $description;
292 $description = FrmTransLiteAppHelper::process_shortcodes( $atts );
293 $action->post_content['description'] = $description;
294 }
295
296 /**
297 * Convert the amount into 10.00.
298 *
299 * @param mixed $amount
300 * @param array $atts
301 *
302 * @return string
303 */
304 public static function prepare_amount( $amount, $atts = array() ) {
305 if ( isset( $atts['form'] ) ) {
306 $atts['value'] = $amount;
307 $amount = FrmTransLiteAppHelper::process_shortcodes( $atts );
308 }
309
310 if ( is_string( $amount ) && strlen( $amount ) >= 2 && $amount[0] === '[' && str_ends_with( $amount, ']' ) ) {
311 // Make sure we don't use a field id as the amount.
312 $amount = 0;
313 }
314
315 $currency = self::get_currency_for_action( $atts );
316 $total = 0;
317
318 foreach ( (array) $amount as $a ) {
319 $this_amount = self::get_amount_from_string( $a );
320 self::maybe_use_decimal( $this_amount, $currency );
321 self::normalize_number( $this_amount, $currency );
322
323 $total += $this_amount;
324 unset( $a, $this_amount );
325 }
326
327 return number_format( $total, $currency['decimals'], '.', '' );
328 }
329
330 /**
331 * Get currency to use when preparing amount.
332 *
333 * @param array $atts
334 *
335 * @return array
336 */
337 public static function get_currency_for_action( $atts ) {
338 $currency = 'usd';
339
340 if ( isset( $atts['form'] ) ) {
341 $currency = $atts['action']->post_content['currency'];
342 } elseif ( isset( $atts['currency'] ) ) {
343 $currency = $atts['currency'];
344 }
345
346 return FrmCurrencyHelper::get_currency( $currency );
347 }
348
349 /**
350 * @param string $amount
351 *
352 * @return string
353 */
354 private static function get_amount_from_string( $amount ) {
355 $amount = html_entity_decode( $amount );
356 $amount = trim( $amount );
357 preg_match_all( '/[0-9,.]*\.?\,?[0-9]+/', $amount, $matches );
358 return $matches ? end( $matches[0] ) : 0;
359 }
360
361 /**
362 * @param string $amount
363 * @param array $currency
364 *
365 * @return void
366 */
367 private static function maybe_use_decimal( &$amount, $currency ) {
368 if ( $currency['thousand_separator'] !== '.' ) {
369 return;
370 }
371
372 $amount_parts = explode( '.', $amount );
373
374 if ( 2 !== count( $amount_parts ) ) {
375 return;
376 }
377
378 $strlen = strlen( $amount_parts[1] );
379 $used_for_decimal = $strlen === 1 || $strlen === 2;
380
381 if ( $used_for_decimal ) {
382 $amount = str_replace( '.', $currency['decimal_separator'], $amount );
383 }
384 }
385
386 /**
387 * @param string $amount
388 * @param array $currency
389 *
390 * @return void
391 */
392 private static function normalize_number( &$amount, $currency ) {
393 $amount = str_replace( $currency['thousand_separator'], '', $amount );
394 $amount = str_replace( $currency['decimal_separator'], '.', $amount );
395 $amount = number_format( (float) $amount, $currency['decimals'], '.', '' );
396 }
397
398 /**
399 * These settings are included in frm_stripe_vars.settings global JavaScript object on Stripe forms.
400 *
401 * @param int $form_id
402 *
403 * @return array
404 */
405 public static function prepare_settings_for_js( $form_id ) {
406 $payment_actions = self::get_actions_for_form( $form_id );
407 $action_settings = array();
408
409 foreach ( $payment_actions as $payment_action ) {
410 $settings_for_action = array(
411 'id' => $payment_action->ID,
412 'first_name' => $payment_action->post_content['billing_first_name'],
413 'last_name' => $payment_action->post_content['billing_last_name'],
414 'gateways' => $payment_action->post_content['gateway'],
415 'fields' => self::get_fields_for_price( $payment_action ),
416 'one' => $payment_action->post_content['type'],
417 'email' => $payment_action->post_content['email'],
418 'layout' => $payment_action->post_content['layout'] ?? '',
419 );
420
421 /**
422 * @param array $settings_for_action
423 * @param WP_Post $payment_action
424 */
425 $settings_for_action = apply_filters( 'frm_trans_settings_for_js', $settings_for_action, $payment_action );
426 $action_settings[] = $settings_for_action;
427 }
428
429 return $action_settings;
430 }
431
432 /**
433 * Include the price field ids to pass to the javascript.
434 *
435 * @since 6.5, introduced in v2.0 of the Payments submodule.
436 *
437 * @param WP_Post $action
438 *
439 * @return array|int
440 */
441 private static function get_fields_for_price( $action ) {
442 $amount = $action->post_content['amount'];
443 $shortcodes = FrmFieldsHelper::get_shortcodes( $amount, $action->menu_order );
444 return $shortcodes[2] ?? -1;
445 }
446
447 /**
448 * Get all published payment actions.
449 *
450 * @param int|string $form_id
451 *
452 * @return array
453 */
454 public static function get_actions_for_form( $form_id ) {
455 $action_status = array(
456 'post_status' => 'publish',
457 );
458 $payment_actions = FrmFormAction::get_action_for_form( $form_id, 'payment', $action_status );
459
460 if ( ! $payment_actions ) {
461 return array();
462 }
463
464 return $payment_actions;
465 }
466
467 /**
468 * Make sure a gateway field is hidden on the front end.
469 *
470 * @param array $values
471 * @param stdClass $field
472 *
473 * @return array
474 */
475 public static function hide_gateway_field_on_front_end( $values, $field ) {
476 if ( $field->type !== 'gateway' ) {
477 return $values;
478 }
479
480 // This is also called from the frm_enqueue_form_scripts hook.
481 // With this here, the value of frm_stripe_vars.settings[0].fields is -1
482 // This is because the amount value is processed and a shortcode is not found in '000'.
483 FrmStrpLiteActionsController::load_scripts( (int) $field->form_id );
484 FrmSquareLiteActionsController::load_scripts( (int) $field->form_id );
485
486 $values['type'] = 'hidden';
487 return $values;
488 }
489
490 /**
491 * Entries are deleted on payment failure so set the form values after an error from the entry data that gets deleted.
492 *
493 * @since 6.5.1
494 *
495 * @param array $values
496 * @param stdClass $field
497 *
498 * @return array
499 */
500 public static function fill_entry_from_previous( $values, $field ) {
501 global $frm_vars;
502 $previous_entry = $frm_vars['frm_trans']['pay_entry'] ?? false;
503
504 // phpcs:ignore Universal.Operators.StrictComparisons
505 if ( ! $previous_entry || $previous_entry->form_id != $field->form_id ) {
506 return $values;
507 }
508
509 if ( is_array( $previous_entry->metas ) && isset( $previous_entry->metas[ $field->id ] ) ) {
510 $values['value'] = $previous_entry->metas[ $field->id ];
511 }
512
513 $frm_vars['trans_filled'] = true;
514 $previous_entry_id = $previous_entry->id;
515 self::destroy_entry_later( $previous_entry_id );
516
517 return $values;
518 }
519
520 /**
521 * Destroy an entry, but delay it to happen when the form is displayed.
522 * It needs to happen late enough that FrmProNestedFormsController::display_single_iteration_of_nested_form is able to fill in data for repeater fields.
523 * See Formidable Stripe issue #136 for more information.
524 *
525 * @since 6.5.1
526 *
527 * @param int|string $entry_id
528 *
529 * @return void
530 */
531 private static function destroy_entry_later( $entry_id ) {
532 if ( in_array( (int) $entry_id, self::$entry_ids_to_destroy_later, true ) ) {
533 // Avoid trying to delete this multiple times as fill_entry_from_previous is called more than once.
534 return;
535 }
536
537 $destroy_callback =
538 /**
539 * Destroy an entry and remove this action so it only tries to destroy the entry once.
540 *
541 * @param int|string $entry_id
542 * @param Closure $destroy_callback
543 *
544 * @return void
545 */
546 function () use ( $entry_id, &$destroy_callback ) {
547 FrmEntry::destroy( $entry_id );
548 // Only call this once.
549 remove_action( 'frm_entry_form', $destroy_callback );
550 };
551 add_action( 'frm_entry_form', $destroy_callback );
552
553 self::$entry_ids_to_destroy_later[] = (int) $entry_id;
554 }
555
556 /**
557 * Filter payment action on save.
558 *
559 * @since 6.22
560 *
561 * @param array $settings
562 * @param array $action
563 *
564 * @return array
565 */
566 public static function before_save_settings( $settings, $action ) {
567 $settings['gateway'] = ! empty( $settings['gateway'] ) ? (array) $settings['gateway'] : array( 'stripe' );
568
569 if ( in_array( 'square', $settings['gateway'], true ) ) {
570 $currency = FrmSquareLiteConnectHelper::get_merchant_currency();
571 $settings['currency'] = false !== $currency ? strtolower( $currency ) : 'usd';
572 } else {
573 $settings['currency'] = strtolower( $settings['currency'] );
574 }
575
576 $form_id = absint( $action['menu_order'] );
577
578 if ( empty( $settings['credit_card'] ) ) {
579 $credit_card_field_id = FrmDb::get_var(
580 'frm_fields',
581 array(
582 'type' => 'credit_card',
583 'form_id' => $form_id,
584 )
585 );
586
587 if ( ! $credit_card_field_id ) {
588 $credit_card_field_id = self::add_a_credit_card_field( $form_id );
589 }
590
591 if ( $credit_card_field_id ) {
592 $settings['credit_card'] = $credit_card_field_id;
593 }
594 }
595
596 $gateway_field_id = FrmDb::get_var(
597 'frm_fields',
598 array(
599 'type' => 'gateway',
600 'form_id' => $form_id,
601 )
602 );
603
604 if ( ! $gateway_field_id ) {
605 self::add_a_gateway_field( $form_id );
606 }
607
608 return $settings;
609 }
610
611 /**
612 * A credit card field is added automatically if missing before a Stripe action is updated.
613 *
614 * @param int $form_id
615 *
616 * @return false|int
617 */
618 protected static function add_a_credit_card_field( $form_id ) {
619 return self::add_a_field( $form_id, 'credit_card', __( 'Payment', 'formidable' ) );
620 }
621
622 /**
623 * A gateway field is added automatically for compatibility with the Stripe add on.
624 * The gateway field is not important for the Stripe Lite implementation.
625 *
626 * @param int $form_id
627 *
628 * @return false|int
629 */
630 protected static function add_a_gateway_field( $form_id ) {
631 return self::add_a_field( $form_id, 'gateway', __( 'Payment Method', 'formidable' ) );
632 }
633
634 /**
635 * @param int $form_id
636 * @param string $field_type
637 * @param string $field_name
638 *
639 * @return false|int
640 */
641 protected static function add_a_field( $form_id, $field_type, $field_name ) {
642 $new_values = FrmFieldsHelper::setup_new_vars( $field_type, $form_id );
643 $new_values['name'] = $field_name;
644 return FrmField::create( $new_values );
645 }
646 }
647