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

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