PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.26
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.26
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.26, at stripe/controllers/FrmTransLiteActionsController.php

659 lines 17.3 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 ( empty( $message ) ) {
163 $message = __( 'There was an error processing your payment.', 'formidable' );
164 }
165
166 $message = '<div class="frm_error_style">' . $message . '</div>';
167
168 return $message;
169 }
170
171 /**
172 * @param WP_Post $action
173 * @param stdClass $entry
174 * @param mixed $form
175 *
176 * @return array
177 */
178 public static function trigger_gateway( $action, $entry, $form ) {
179 // This function must be overridden in a subclass.
180 return array(
181 'success' => false,
182 'run_triggers' => false,
183 'show_errors' => true,
184 );
185 }
186
187 /**
188 * @return string
189 */
190 public static function force_message_after_create() {
191 return 'message';
192 }
193
194 /**
195 * @since 6.5, introduced in v1.12 of the Payments submodule.
196 *
197 * @param object $sub
198 *
199 * @return void
200 */
201 public static function trigger_subscription_status_change( $sub ) {
202 $frm_payment = new FrmTransLitePayment();
203 $payment = $frm_payment->get_one_by( $sub->id, 'sub_id' );
204
205 if ( $payment && $payment->action_id ) {
206 self::trigger_payment_status_change(
207 array(
208 'status' => $sub->status,
209 'payment' => $payment,
210 )
211 );
212 }
213 }
214
215 /**
216 * @param array $atts
217 *
218 * @return void
219 */
220 public static function trigger_payment_status_change( $atts ) {
221 $entry_id = isset( $atts['entry'] ) ? $atts['entry']->id : $atts['payment']->item_id;
222 $atts = array(
223 'trigger' => $atts['status'],
224 'entry_id' => $entry_id,
225 );
226
227 if ( ! isset( $atts['payment'] ) ) {
228 $frm_payment = new FrmTransLitePayment();
229 $atts['payment'] = $frm_payment->get_one_by( $entry_id, 'item_id' );
230 }
231
232 if ( ! isset( $atts['trigger'] ) ) {
233 $atts['trigger'] = $atts['status'];
234 }
235
236 // Set future-cancel as trigger when applicable.
237 $atts['trigger'] = str_replace( '_', '-', $atts['trigger'] );
238
239 /**
240 * Trigger various hooks including frm_payment_status_complete.
241 *
242 * @since 6.25 This was included in the payments submodule, but not included in Lite until 6.25.
243 *
244 * @param array $atts
245 */
246 do_action( 'frm_payment_status_' . $atts['trigger'], $atts );
247
248 if ( $atts['payment'] ) {
249 self::trigger_actions_after_payment( $atts['payment'], $atts );
250 }
251 }
252
253 /**
254 * Maybe trigger payment-success or payment-failed event after payment so actions (like emails) can run.
255 *
256 * @param object $payment
257 * @param array $atts
258 *
259 * @return void
260 */
261 public static function trigger_actions_after_payment( $payment, $atts = array() ) {
262 if ( 'pending' === $payment->status ) {
263 // 3D Secure has a delayed payment status, so avoid sending a payment failed email for a pending payment.
264 return;
265 }
266
267 $entry = FrmEntry::getOne( $payment->item_id );
268
269 if ( isset( $atts['trigger'] ) ) {
270 $trigger_event = 'payment-' . $atts['trigger'];
271 } else {
272 $trigger_event = 'payment-' . $payment->status;
273 }
274
275 $allowed_triggers = array_keys( self::add_payment_trigger( array() ) );
276
277 if ( ! in_array( $trigger_event, $allowed_triggers, true ) ) {
278 $trigger_event = $payment->status === 'complete' ? 'payment-success' : 'payment-failed';
279 }
280 FrmFormActionsController::trigger_actions( $trigger_event, $entry->form_id, $entry->id );
281 }
282
283 /**
284 * Filter fields in description.
285 *
286 * @param WP_Post $action
287 * @param array $atts
288 *
289 * @return void
290 */
291 public static function prepare_description( &$action, $atts ) {
292 $description = $action->post_content['description'];
293
294 if ( ! empty( $description ) ) {
295 $atts['value'] = $description;
296 $description = FrmTransLiteAppHelper::process_shortcodes( $atts );
297 $action->post_content['description'] = $description;
298 }
299 }
300
301 /**
302 * Convert the amount into 10.00.
303 *
304 * @param mixed $amount
305 * @param array $atts
306 *
307 * @return string
308 */
309 public static function prepare_amount( $amount, $atts = array() ) {
310 if ( isset( $atts['form'] ) ) {
311 $atts['value'] = $amount;
312 $amount = FrmTransLiteAppHelper::process_shortcodes( $atts );
313 }
314
315 if ( is_string( $amount ) && strlen( $amount ) >= 2 && $amount[0] === '[' && substr( $amount, -1 ) === ']' ) {
316 // Make sure we don't use a field id as the amount.
317 $amount = 0;
318 }
319
320 $currency = self::get_currency_for_action( $atts );
321
322 $total = 0;
323
324 foreach ( (array) $amount as $a ) {
325 $this_amount = self::get_amount_from_string( $a );
326 self::maybe_use_decimal( $this_amount, $currency );
327 self::normalize_number( $this_amount, $currency );
328
329 $total += $this_amount;
330 unset( $a, $this_amount );
331 }
332
333 return number_format( $total, $currency['decimals'], '.', '' );
334 }
335
336 /**
337 * Get currency to use when preparing amount.
338 *
339 * @param array $atts
340 *
341 * @return array
342 */
343 public static function get_currency_for_action( $atts ) {
344 $currency = 'usd';
345
346 if ( isset( $atts['form'] ) ) {
347 $currency = $atts['action']->post_content['currency'];
348 } elseif ( isset( $atts['currency'] ) ) {
349 $currency = $atts['currency'];
350 }
351
352 return FrmCurrencyHelper::get_currency( $currency );
353 }
354
355 /**
356 * @param string $amount
357 *
358 * @return string
359 */
360 private static function get_amount_from_string( $amount ) {
361 $amount = html_entity_decode( $amount );
362 $amount = trim( $amount );
363 preg_match_all( '/[0-9,.]*\.?\,?[0-9]+/', $amount, $matches );
364 $amount = $matches ? end( $matches[0] ) : 0;
365 return $amount;
366 }
367
368 /**
369 * @param string $amount
370 * @param array $currency
371 *
372 * @return void
373 */
374 private static function maybe_use_decimal( &$amount, $currency ) {
375 if ( $currency['thousand_separator'] !== '.' ) {
376 return;
377 }
378
379 $amount_parts = explode( '.', $amount );
380
381 if ( 2 !== count( $amount_parts ) ) {
382 return;
383 }
384
385 $strlen = strlen( $amount_parts[1] );
386 $used_for_decimal = $strlen === 1 || $strlen === 2;
387
388 if ( $used_for_decimal ) {
389 $amount = str_replace( '.', $currency['decimal_separator'], $amount );
390 }
391 }
392
393 /**
394 * @param string $amount
395 * @param array $currency
396 *
397 * @return void
398 */
399 private static function normalize_number( &$amount, $currency ) {
400 $amount = str_replace( $currency['thousand_separator'], '', $amount );
401 $amount = str_replace( $currency['decimal_separator'], '.', $amount );
402 $amount = number_format( (float) $amount, $currency['decimals'], '.', '' );
403 }
404
405 /**
406 * These settings are included in frm_stripe_vars.settings global JavaScript object on Stripe forms.
407 *
408 * @param int $form_id
409 *
410 * @return array
411 */
412 public static function prepare_settings_for_js( $form_id ) {
413 $payment_actions = self::get_actions_for_form( $form_id );
414 $action_settings = array();
415
416 foreach ( $payment_actions as $payment_action ) {
417 $settings_for_action = array(
418 'id' => $payment_action->ID,
419 'first_name' => $payment_action->post_content['billing_first_name'],
420 'last_name' => $payment_action->post_content['billing_last_name'],
421 'gateways' => $payment_action->post_content['gateway'],
422 'fields' => self::get_fields_for_price( $payment_action ),
423 'one' => $payment_action->post_content['type'],
424 'email' => $payment_action->post_content['email'],
425 'layout' => $payment_action->post_content['layout'] ?? '',
426 );
427
428 /**
429 * @param array $settings_for_action
430 * @param WP_Post $payment_action
431 */
432 $settings_for_action = apply_filters( 'frm_trans_settings_for_js', $settings_for_action, $payment_action );
433 $action_settings[] = $settings_for_action;
434 }
435
436 return $action_settings;
437 }
438
439 /**
440 * Include the price field ids to pass to the javascript.
441 *
442 * @since 6.5, introduced in v2.0 of the Payments submodule.
443 *
444 * @param WP_Post $action
445 *
446 * @return array|int
447 */
448 private static function get_fields_for_price( $action ) {
449 $amount = $action->post_content['amount'];
450 $shortcodes = FrmFieldsHelper::get_shortcodes( $amount, $action->menu_order );
451 return $shortcodes[2] ?? -1;
452 }
453
454 /**
455 * Get all published payment actions.
456 *
457 * @param int|string $form_id
458 *
459 * @return array
460 */
461 public static function get_actions_for_form( $form_id ) {
462 $action_status = array(
463 'post_status' => 'publish',
464 );
465 $payment_actions = FrmFormAction::get_action_for_form( $form_id, 'payment', $action_status );
466
467 if ( ! $payment_actions ) {
468 $payment_actions = array();
469 }
470 return $payment_actions;
471 }
472
473 /**
474 * Make sure a gateway field is hidden on the front end.
475 *
476 * @param array $values
477 * @param stdClass $field
478 *
479 * @return array
480 */
481 public static function hide_gateway_field_on_front_end( $values, $field ) {
482 if ( $field->type !== 'gateway' ) {
483 return $values;
484 }
485
486 // This is also called from the frm_enqueue_form_scripts hook.
487 // With this here, the value of frm_stripe_vars.settings[0].fields is -1
488 // This is because the amount value is processed and a shortcode is not found in '000'.
489 FrmStrpLiteActionsController::load_scripts( (int) $field->form_id );
490 FrmSquareLiteActionsController::load_scripts( (int) $field->form_id );
491
492 $values['type'] = 'hidden';
493 return $values;
494 }
495
496 /**
497 * Entries are deleted on payment failure so set the form values after an error from the entry data that gets deleted.
498 *
499 * @since 6.5.1
500 *
501 * @param array $values
502 * @param stdClass $field
503 *
504 * @return array
505 */
506 public static function fill_entry_from_previous( $values, $field ) {
507 global $frm_vars;
508 $previous_entry = $frm_vars['frm_trans']['pay_entry'] ?? false;
509
510 if ( empty( $previous_entry ) || $previous_entry->form_id != $field->form_id ) {
511 return $values;
512 }
513
514 if ( is_array( $previous_entry->metas ) && isset( $previous_entry->metas[ $field->id ] ) ) {
515 $values['value'] = $previous_entry->metas[ $field->id ];
516 }
517
518 $frm_vars['trans_filled'] = true;
519
520 $previous_entry_id = $previous_entry->id;
521 self::destroy_entry_later( $previous_entry_id );
522
523 return $values;
524 }
525
526 /**
527 * Destroy an entry, but delay it to happen when the form is displayed.
528 * It needs to happen late enough that FrmProNestedFormsController::display_single_iteration_of_nested_form is able to fill in data for repeater fields.
529 * See Formidable Stripe issue #136 for more information.
530 *
531 * @since 6.5.1
532 *
533 * @param int|string $entry_id
534 *
535 * @return void
536 */
537 private static function destroy_entry_later( $entry_id ) {
538 if ( in_array( (int) $entry_id, self::$entry_ids_to_destroy_later, true ) ) {
539 // Avoid trying to delete this multiple times as fill_entry_from_previous is called more than once.
540 return;
541 }
542
543 $destroy_callback =
544 /**
545 * Destroy an entry and remove this action so it only tries to destroy the entry once.
546 *
547 * @param int|string $entry_id
548 * @param Closure $destroy_callback
549 *
550 * @return void
551 */
552 function () use ( $entry_id, &$destroy_callback ) {
553 FrmEntry::destroy( $entry_id );
554 // Only call this once.
555 remove_action( 'frm_entry_form', $destroy_callback );
556 };
557 add_action( 'frm_entry_form', $destroy_callback );
558
559 self::$entry_ids_to_destroy_later[] = (int) $entry_id;
560 }
561
562 /**
563 * Filter payment action on save.
564 *
565 * @since 6.22
566 *
567 * @param array $settings
568 * @param array $action
569 *
570 * @return array
571 */
572 public static function before_save_settings( $settings, $action ) {
573 $settings['gateway'] = ! empty( $settings['gateway'] ) ? (array) $settings['gateway'] : array( 'stripe' );
574
575 if ( in_array( 'square', $settings['gateway'] ) ) {
576 $currency = FrmSquareLiteConnectHelper::get_merchant_currency();
577
578 if ( false !== $currency ) {
579 $settings['currency'] = strtolower( $currency );
580 } else {
581 $settings['currency'] = 'usd';
582 }
583 } else {
584 $settings['currency'] = strtolower( $settings['currency'] );
585 }
586
587 $form_id = absint( $action['menu_order'] );
588
589 if ( empty( $settings['credit_card'] ) ) {
590 $credit_card_field_id = FrmDb::get_var(
591 'frm_fields',
592 array(
593 'type' => 'credit_card',
594 'form_id' => $form_id,
595 )
596 );
597
598 if ( ! $credit_card_field_id ) {
599 $credit_card_field_id = self::add_a_credit_card_field( $form_id );
600 }
601
602 if ( $credit_card_field_id ) {
603 $settings['credit_card'] = $credit_card_field_id;
604 }
605 }
606
607 $gateway_field_id = FrmDb::get_var(
608 'frm_fields',
609 array(
610 'type' => 'gateway',
611 'form_id' => $form_id,
612 )
613 );
614
615 if ( ! $gateway_field_id ) {
616 self::add_a_gateway_field( $form_id );
617 }
618
619 return $settings;
620 }
621
622 /**
623 * A credit card field is added automatically if missing before a Stripe action is updated.
624 *
625 * @param int $form_id
626 *
627 * @return false|int
628 */
629 protected static function add_a_credit_card_field( $form_id ) {
630 return self::add_a_field( $form_id, 'credit_card', __( 'Payment', 'formidable' ) );
631 }
632
633 /**
634 * A gateway field is added automatically for compatibility with the Stripe add on.
635 * The gateway field is not important for the Stripe Lite implementation.
636 *
637 * @param int $form_id
638 *
639 * @return false|int
640 */
641 protected static function add_a_gateway_field( $form_id ) {
642 return self::add_a_field( $form_id, 'gateway', __( 'Payment Method', 'formidable' ) );
643 }
644
645 /**
646 * @param int $form_id
647 * @param string $field_type
648 * @param string $field_name
649 *
650 * @return false|int
651 */
652 protected static function add_a_field( $form_id, $field_type, $field_name ) {
653 $new_values = FrmFieldsHelper::setup_new_vars( $field_type, $form_id );
654 $new_values['name'] = $field_name;
655 $field_id = FrmField::create( $new_values );
656 return $field_id;
657 }
658 }
659