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

496 lines 14.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 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' ),
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 $response = FrmStrpLiteActionsController::trigger_gateway( $action, $entry, $form );
89 if ( ! $response['success'] && $response['show_errors'] ) {
90 // the payment failed
91 self::show_failed_message( compact( 'action', 'entry', 'form', 'response' ) );
92 }
93 }
94
95 /**
96 * @since 6.10
97 *
98 * @param array $args
99 * @return void
100 */
101 private static function show_failed_message( $args ) {
102 global $frm_vars;
103 $frm_vars['frm_trans'] = array(
104 'pay_entry' => $args['entry'],
105 'error' => isset( $args['response']['error'] ) ? $args['response']['error'] : '',
106 );
107
108 add_filter( 'frm_success_filter', 'FrmTransLiteActionsController::force_message_after_create' );
109 add_filter( 'frm_pre_display_form', 'FrmTransLiteActionsController::include_form_with_success' );
110 add_filter( 'frm_main_feedback', 'FrmTransLiteActionsController::replace_success_message', 5 );
111 add_filter( 'frm_setup_new_fields_vars', 'FrmTransLiteActionsController::fill_entry_from_previous', 20, 2 );
112 }
113
114 /**
115 * @since 6.10
116 *
117 * @param stdClass $form
118 * @return stdClass
119 */
120 public static function include_form_with_success( $form ) {
121 $form->options['show_form'] = 1;
122 return $form;
123 }
124
125 /**
126 * @return string
127 */
128 public static function replace_success_message() {
129 global $frm_vars;
130 $message = isset( $frm_vars['frm_trans']['error'] ) ? $frm_vars['frm_trans']['error'] : '';
131 if ( empty( $message ) ) {
132 $message = __( 'There was an error processing your payment.', 'formidable' );
133 }
134
135 $message = '<div class="frm_error_style">' . $message . '</div>';
136
137 return $message;
138 }
139
140 /**
141 * @param WP_Post $action
142 * @param stdClass $entry
143 * @param mixed $form
144 * @return array
145 */
146 public static function trigger_gateway( $action, $entry, $form ) {
147 // This function must be overridden in a subclass.
148 return array(
149 'success' => false,
150 'run_triggers' => false,
151 'show_errors' => true,
152 );
153 }
154
155 /**
156 * @return string
157 */
158 public static function force_message_after_create() {
159 return 'message';
160 }
161
162 /**
163 * @since 6.5, introduced in v1.12 of the Payments submodule.
164 *
165 * @param object $sub
166 * @return void
167 */
168 public static function trigger_subscription_status_change( $sub ) {
169 $frm_payment = new FrmTransLitePayment();
170 $payment = $frm_payment->get_one_by( $sub->id, 'sub_id' );
171
172 if ( $payment && $payment->action_id ) {
173 self::trigger_payment_status_change(
174 array(
175 'status' => $sub->status,
176 'payment' => $payment,
177 )
178 );
179 }
180 }
181
182 /**
183 * @param array $atts
184 * @return void
185 */
186 public static function trigger_payment_status_change( $atts ) {
187 $entry_id = isset( $atts['entry'] ) ? $atts['entry']->id : $atts['payment']->item_id;
188 $atts = array(
189 'trigger' => $atts['status'],
190 'entry_id' => $entry_id,
191 );
192
193 if ( ! isset( $atts['payment'] ) ) {
194 $frm_payment = new FrmTransLitePayment();
195 $atts['payment'] = $frm_payment->get_one_by( $entry_id, 'item_id' );
196 }
197
198 if ( ! isset( $atts['trigger'] ) ) {
199 $atts['trigger'] = $atts['status'];
200 }
201
202 // Set future-cancel as trigger when applicable.
203 $atts['trigger'] = str_replace( '_', '-', $atts['trigger'] );
204
205 if ( $atts['payment'] ) {
206 self::trigger_actions_after_payment( $atts['payment'], $atts );
207 }
208 }
209
210 /**
211 * Maybe trigger payment-success or payment-failed event after payment so actions (like emails) can run.
212 *
213 * @param object $payment
214 * @param array $atts
215 * @return void
216 */
217 public static function trigger_actions_after_payment( $payment, $atts = array() ) {
218 if ( 'pending' === $payment->status ) {
219 // 3D Secure has a delayed payment status, so avoid sending a payment failed email for a pending payment.
220 return;
221 }
222
223 $entry = FrmEntry::getOne( $payment->item_id );
224
225 if ( isset( $atts['trigger'] ) ) {
226 $trigger_event = 'payment-' . $atts['trigger'];
227 } else {
228 $trigger_event = 'payment-' . $payment->status;
229 }
230
231 $allowed_triggers = array_keys( self::add_payment_trigger( array() ) );
232 if ( ! in_array( $trigger_event, $allowed_triggers, true ) ) {
233 $trigger_event = $payment->status === 'complete' ? 'payment-success' : 'payment-failed';
234 }
235 FrmFormActionsController::trigger_actions( $trigger_event, $entry->form_id, $entry->id );
236 }
237
238 /**
239 * Filter fields in description.
240 *
241 * @param WP_Post $action
242 * @param array $atts
243 * @return void
244 */
245 public static function prepare_description( &$action, $atts ) {
246 $description = $action->post_content['description'];
247 if ( ! empty( $description ) ) {
248 $atts['value'] = $description;
249 $description = FrmTransLiteAppHelper::process_shortcodes( $atts );
250 $action->post_content['description'] = $description;
251 }
252 }
253
254 /**
255 * Convert the amount into 10.00.
256 *
257 * @param mixed $amount
258 * @param array $atts
259 * @return string
260 */
261 public static function prepare_amount( $amount, $atts = array() ) {
262 if ( isset( $atts['form'] ) ) {
263 $atts['value'] = $amount;
264 $amount = FrmTransLiteAppHelper::process_shortcodes( $atts );
265 }
266
267 if ( is_string( $amount ) && strlen( $amount ) >= 2 && $amount[0] === '[' && substr( $amount, -1 ) === ']' ) {
268 // Make sure we don't use a field id as the amount.
269 $amount = 0;
270 }
271
272 $currency = self::get_currency_for_action( $atts );
273
274 $total = 0;
275 foreach ( (array) $amount as $a ) {
276 $this_amount = self::get_amount_from_string( $a );
277 self::maybe_use_decimal( $this_amount, $currency );
278 self::normalize_number( $this_amount, $currency );
279
280 $total += $this_amount;
281 unset( $a, $this_amount );
282 }
283
284 return number_format( $total, $currency['decimals'], '.', '' );
285 }
286
287 /**
288 * Get currency to use when preparing amount.
289 *
290 * @param array $atts
291 * @return array
292 */
293 public static function get_currency_for_action( $atts ) {
294 $currency = 'usd';
295 if ( isset( $atts['form'] ) ) {
296 $currency = $atts['action']->post_content['currency'];
297 } elseif ( isset( $atts['currency'] ) ) {
298 $currency = $atts['currency'];
299 }
300
301 return FrmCurrencyHelper::get_currency( $currency );
302 }
303
304 /**
305 * @param string $amount
306 *
307 * @return string
308 */
309 private static function get_amount_from_string( $amount ) {
310 $amount = html_entity_decode( $amount );
311 $amount = trim( $amount );
312 preg_match_all( '/[0-9,.]*\.?\,?[0-9]+/', $amount, $matches );
313 $amount = $matches ? end( $matches[0] ) : 0;
314 return $amount;
315 }
316
317 /**
318 * @param string $amount
319 * @param array $currency
320 * @return void
321 */
322 private static function maybe_use_decimal( &$amount, $currency ) {
323 if ( $currency['thousand_separator'] !== '.' ) {
324 return;
325 }
326
327 $amount_parts = explode( '.', $amount );
328 if ( 2 !== count( $amount_parts ) ) {
329 return;
330 }
331
332 $strlen = strlen( $amount_parts[1] );
333 $used_for_decimal = $strlen === 1 || $strlen === 2;
334
335 if ( $used_for_decimal ) {
336 $amount = str_replace( '.', $currency['decimal_separator'], $amount );
337 }
338 }
339
340 /**
341 * @param string $amount
342 * @param array $currency
343 * @return void
344 */
345 private static function normalize_number( &$amount, $currency ) {
346 $amount = str_replace( $currency['thousand_separator'], '', $amount );
347 $amount = str_replace( $currency['decimal_separator'], '.', $amount );
348 $amount = number_format( (float) $amount, $currency['decimals'], '.', '' );
349 }
350
351 /**
352 * These settings are included in frm_stripe_vars.settings global JavaScript object on Stripe forms.
353 *
354 * @param int $form_id
355 * @return array
356 */
357 public static function prepare_settings_for_js( $form_id ) {
358 $payment_actions = self::get_actions_for_form( $form_id );
359 $action_settings = array();
360 foreach ( $payment_actions as $payment_action ) {
361 $settings_for_action = array(
362 'id' => $payment_action->ID,
363 'first_name' => $payment_action->post_content['billing_first_name'],
364 'last_name' => $payment_action->post_content['billing_last_name'],
365 'gateways' => $payment_action->post_content['gateway'],
366 'fields' => self::get_fields_for_price( $payment_action ),
367 'one' => $payment_action->post_content['type'],
368 'email' => $payment_action->post_content['email'],
369 );
370
371 /**
372 * @param array $settings_for_action
373 * @param WP_Post $payment_action
374 */
375 $settings_for_action = apply_filters( 'frm_trans_settings_for_js', $settings_for_action, $payment_action );
376 $action_settings[] = $settings_for_action;
377 }
378
379 return $action_settings;
380 }
381
382 /**
383 * Include the price field ids to pass to the javascript.
384 *
385 * @since 6.5, introduced in v2.0 of the Payments submodule.
386 *
387 * @param WP_Post $action
388 * @return array|int
389 */
390 private static function get_fields_for_price( $action ) {
391 $amount = $action->post_content['amount'];
392 $shortcodes = FrmFieldsHelper::get_shortcodes( $amount, $action->menu_order );
393 return isset( $shortcodes[2] ) ? $shortcodes[2] : -1;
394 }
395
396 /**
397 * Get all published payment actions.
398 *
399 * @param int|string $form_id
400 * @return array
401 */
402 public static function get_actions_for_form( $form_id ) {
403 $action_status = array(
404 'post_status' => 'publish',
405 );
406 $payment_actions = FrmFormAction::get_action_for_form( $form_id, 'payment', $action_status );
407 if ( ! $payment_actions ) {
408 $payment_actions = array();
409 }
410 return $payment_actions;
411 }
412
413 /**
414 * Make sure a gateway field is hidden on the front end.
415 *
416 * @param array $values
417 * @param stdClass $field
418 * @return array
419 */
420 public static function hide_gateway_field_on_front_end( $values, $field ) {
421 if ( $field->type !== 'gateway' ) {
422 return $values;
423 }
424
425 // This is also called from the frm_enqueue_form_scripts hook.
426 // With this here, the value of frm_stripe_vars.settings[0].fields is -1
427 // This is because the amount value is processed and a shortcode is not found in '000'.
428 FrmStrpLiteActionsController::load_scripts( (int) $field->form_id );
429
430 $values['type'] = 'hidden';
431 return $values;
432 }
433
434 /**
435 * Entries are deleted on payment failure so set the form values after an error from the entry data that gets deleted.
436 *
437 * @since 6.5.1
438 *
439 * @param array $values
440 * @param stdClass $field
441 * @return array
442 */
443 public static function fill_entry_from_previous( $values, $field ) {
444 global $frm_vars;
445 $previous_entry = isset( $frm_vars['frm_trans']['pay_entry'] ) ? $frm_vars['frm_trans']['pay_entry'] : false;
446 if ( empty( $previous_entry ) || $previous_entry->form_id != $field->form_id ) {
447 return $values;
448 }
449
450 if ( is_array( $previous_entry->metas ) && isset( $previous_entry->metas[ $field->id ] ) ) {
451 $values['value'] = $previous_entry->metas[ $field->id ];
452 }
453
454 $frm_vars['trans_filled'] = true;
455
456 $previous_entry_id = $previous_entry->id;
457 self::destroy_entry_later( $previous_entry_id );
458
459 return $values;
460 }
461
462 /**
463 * Destroy an entry, but delay it to happen when the form is displayed.
464 * It needs to happen late enough that FrmProNestedFormsController::display_single_iteration_of_nested_form is able to fill in data for repeater fields.
465 * See Formidable Stripe issue #136 for more information.
466 *
467 * @since 6.5.1
468 *
469 * @param int|string $entry_id
470 * @return void
471 */
472 private static function destroy_entry_later( $entry_id ) {
473 if ( in_array( (int) $entry_id, self::$entry_ids_to_destroy_later, true ) ) {
474 // Avoid trying to delete this multiple times as fill_entry_from_previous is called more than once.
475 return;
476 }
477
478 $destroy_callback =
479 /**
480 * Destroy an entry and remove this action so it only tries to destroy the entry once.
481 *
482 * @param int|string $entry_id
483 * @param Closure $destroy_callback
484 * @return void
485 */
486 function () use ( $entry_id, &$destroy_callback ) {
487 FrmEntry::destroy( $entry_id );
488 // Only call this once.
489 remove_action( 'frm_entry_form', $destroy_callback );
490 };
491 add_action( 'frm_entry_form', $destroy_callback );
492
493 self::$entry_ids_to_destroy_later[] = (int) $entry_id;
494 }
495 }
496