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 / FrmStrpLiteLinkController.php

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

524 lines 16.7 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 /**
7 * @since 6.5, introduced in v3.0 of the Stripe add on.
8 */
9 class FrmStrpLiteLinkController {
10
11 /**
12 * Process the form input and call handle_one_time_stripe_link_return_url if all of the required data is being submitted.
13 *
14 * @since 6.5, introduced in v3.0 of the Stripe add on.
15 *
16 * @return void
17 */
18 public static function handle_return_url() {
19 $intent_id = FrmAppHelper::simple_get( 'payment_intent' );
20 $client_secret = FrmAppHelper::simple_get( 'payment_intent_client_secret' );
21 $status = FrmAppHelper::simple_get( 'redirect_status' );
22
23 /**
24 * "succeeded" is used for cards and Link.
25 * "pending" happens for bank redirect types (iDEAL, Bancontact, SOFORT).
26 * No redirect status is valid as well (for Affirm payments).
27 */
28 if ( $intent_id && $client_secret && in_array( $status, array( 'pending', 'succeeded', 'failed', '' ), true ) ) {
29 self::handle_one_time_stripe_link_return_url( $intent_id, $client_secret );
30 die();
31 }
32
33 $setup_id = FrmAppHelper::simple_get( 'setup_intent' );
34 $client_secret = FrmAppHelper::simple_get( 'setup_intent_client_secret' );
35
36 if ( $setup_id && $client_secret && in_array( $status, array( 'succeeded', 'failed' ), true ) ) {
37 self::handle_recurring_stripe_link_return_url( $setup_id, $client_secret );
38 die();
39 }
40
41 wp_die();
42 }
43
44 /**
45 * Redirect the user after they return to the return URL based on the status of the payment intent information passed with the request.
46 * This will redirect and get handled by FrmStrpLiteAuth::maybe_show_message or possibly by redirected if there is a success URL set.
47 * If the setup intent is completed, the payment will be changed from pending as well.
48 *
49 * @since 6.5, introduced in v3.0 of the Stripe add on.
50 *
51 * @param string $intent_id
52 * @param string $client_secret
53 *
54 * @return void
55 */
56 private static function handle_one_time_stripe_link_return_url( $intent_id, $client_secret ) {
57 $redirect_helper = new FrmStrpLiteLinkRedirectHelper( $intent_id, $client_secret );
58 $frm_payment = new FrmTransLitePayment();
59
60 $payment = $frm_payment->get_one_by( $intent_id, 'receipt_id' );
61
62 if ( ! $payment ) {
63 $redirect_helper->handle_error( 'no_payment_record' );
64 die();
65 }
66
67 $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_intent', $intent_id );
68
69 if ( ! is_object( $intent ) ) {
70 $redirect_helper->handle_error( 'intent_does_not_exist' );
71 die();
72 }
73
74 if ( $client_secret !== $intent->client_secret ) {
75 // Do an extra check against the client secret so the request isn't as easy to spoof.
76 $redirect_helper->handle_error( 'unable_to_verify' );
77 die();
78 }
79
80 $status = 'succeeded' === $intent->status ? 'complete' : 'authorized';
81 $new_payment_values = (array) $payment;
82 $new_payment_values['status'] = $status;
83
84 if ( 'complete' === $status ) {
85 $charge = reset( $intent->charges->data );
86 $new_payment_values['receipt_id'] = $charge->id;
87 }
88
89 $entry_id = $payment->item_id;
90 $entry = FrmEntry::getOne( $entry_id );
91
92 if ( ! $entry ) {
93 $redirect_helper->handle_error( 'no_entry_found' );
94 die();
95 }
96
97 $redirect_helper->set_entry_id( $entry->id );
98
99 $action = FrmStrpLiteActionsController::get_stripe_link_action( $entry->form_id );
100
101 if ( ! $action ) {
102 $redirect_helper->handle_error( 'no_stripe_link_action' );
103 die();
104 }
105
106 if ( 'succeeded' !== $intent->status ) {
107 if ( 'processing' === $intent->status ) {
108 FrmTransLitePaymentsController::change_payment_status( $payment, 'processing' );
109 $redirect_helper->handle_success( $entry, '' );
110 die();
111 }
112
113 FrmTransLitePaymentsController::change_payment_status( $payment, 'failed' );
114
115 $payment_failed = 'requires_payment_method' === $intent->status && 'payment_intent_authentication_failure' === $intent->last_payment_error->code;
116 $error_type = $payment_failed ? 'payment_failed' : 'did_not_complete';
117
118 $redirect_helper->handle_error( $error_type );
119 die();
120 }
121
122 $status = 'succeeded' === $intent->status ? 'complete' : 'authorized';
123 $new_payment_values = compact( 'status' );
124
125 if ( 'complete' === $status ) {
126 $charge = reset( $intent->charges->data );
127 $new_payment_values['receipt_id'] = $charge->id;
128 }
129
130 self::maybe_update_intent( $intent, $action, $entry );
131
132 $frm_payment->update( $payment->id, $new_payment_values );
133 FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
134
135 $redirect_helper->handle_success( $entry, isset( $charge ) ? $charge->id : '' );
136 die();
137 }
138
139 /**
140 * Try to add the description to a Stripe link payment after it was confirmed.
141 *
142 * @param object $intent
143 * @param stdClass|WP_Post $action
144 * @param stdClass $entry
145 *
146 * @return void
147 */
148 private static function maybe_update_intent( $intent, $action, $entry ) {
149 if ( empty( $action->post_content['description'] ) ) {
150 return;
151 }
152
153 $shortcode_atts = array(
154 'entry' => $entry,
155 'form' => $entry->form_id,
156 'value' => $action->post_content['description'],
157 );
158 $new_values = array( 'description' => FrmTransLiteAppHelper::process_shortcodes( $shortcode_atts ) );
159 FrmStrpLiteAppHelper::call_stripe_helper_class( 'update_intent', $intent->id, $new_values );
160 }
161
162 /**
163 * Handle return URL for a stripe link recurring payment which uses setup intents.
164 * This will redirect and get handled by FrmStrpLiteAuth::maybe_show_message or possibly by redirected if there is a success URL set.
165 * If the setup intent is completed, the subscription will be created as well.
166 *
167 * @since 6.5, introduced in v3.0 of the Stripe add on.
168 *
169 * @param string $setup_id
170 * @param string $client_secret
171 *
172 * @return void
173 */
174 private static function handle_recurring_stripe_link_return_url( $setup_id, $client_secret ) {
175 $redirect_helper = new FrmStrpLiteLinkRedirectHelper( $setup_id, $client_secret );
176 $frm_payment = new FrmTransLitePayment();
177 $payment = $frm_payment->get_one_by( $setup_id, 'receipt_id' );
178
179 if ( ! is_object( $payment ) ) {
180 $redirect_helper->handle_error( 'no_payment_record' );
181 die();
182 }
183
184 // Verify the setup intent.
185 $setup_intent = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_setup_intent', $setup_id );
186
187 if ( ! is_object( $setup_intent ) ) {
188 $redirect_helper->handle_error( 'intent_does_not_exist' );
189 die();
190 }
191
192 // Verify the client secret.
193 if ( $setup_intent->client_secret !== $client_secret ) {
194 $redirect_helper->handle_error( 'unable_to_verify' );
195 die();
196 }
197
198 // Verify the entry.
199 $entry = FrmEntry::getOne( $payment->item_id );
200
201 if ( ! is_object( $entry ) ) {
202 $redirect_helper->handle_error( 'no_entry_found' );
203 die();
204 }
205
206 $redirect_helper->set_entry_id( $entry->id );
207
208 // Verify it's an action with Stripe link enabled.
209 $action = FrmStrpLiteActionsController::get_stripe_link_action( $entry->form_id );
210
211 if ( ! is_object( $action ) ) {
212 $redirect_helper->handle_error( 'no_stripe_link_action' );
213 die();
214 }
215
216 $customer_id = $setup_intent->customer;
217 $payment_method_id = self::get_link_payment_method( $setup_intent );
218
219 if ( ! $payment_method_id ) {
220 FrmTransLitePaymentsController::change_payment_status( $payment, 'failed' );
221 $redirect_helper->handle_error( 'did_not_complete' );
222 die();
223 }
224
225 $amount = $payment->amount * 100;
226 $new_charge = array(
227 'customer' => $customer_id,
228 'default_payment_method' => $payment_method_id,
229 'plan' => FrmStrpLiteSubscriptionHelper::get_plan_from_atts(
230 array(
231 'action' => $action,
232 'amount' => $amount,
233 )
234 ),
235 'expand' => array( 'latest_invoice.charge' ),
236 );
237
238 if ( ! FrmStrpLitePaymentTypeHandler::should_use_automatic_payment_methods( $action ) ) {
239 $new_charge['payment_settings'] = array(
240 'payment_method_types' => FrmStrpLitePaymentTypeHandler::get_payment_method_types( $action ),
241 );
242 }
243
244 $atts = array(
245 'action' => $action,
246 'entry' => $entry,
247 );
248
249 $trial_end = FrmStrpLiteActionsController::get_trial_end_time( $atts );
250
251 if ( $trial_end ) {
252 $new_charge['trial_end'] = $trial_end;
253 }
254
255 $subscription = FrmStrpLiteAppHelper::call_stripe_helper_class( 'create_subscription', $new_charge );
256 $subscription = FrmStrpLiteSubscriptionHelper::maybe_create_missing_plan_and_create_subscription( $subscription, $new_charge, $action, $amount );
257
258 if ( ! is_object( $subscription ) ) {
259 $redirect_helper->handle_error( 'create_subscription_failed' );
260 die();
261 }
262
263 if ( 'succeeded' !== $setup_intent->status ) {
264 FrmTransLitePaymentsController::change_payment_status( $payment, 'failed' );
265 $redirect_helper->handle_error( 'payment_failed' );
266 die();
267 }
268
269 $customer_has_been_charged = ! empty( $subscription->latest_invoice->charge );
270 $atts['charge'] = FrmStrpLiteSubscriptionHelper::prepare_charge_object_for_subscription( $subscription, $amount );
271 $new_payment_values = (array) $payment;
272
273 if ( $customer_has_been_charged ) {
274 $charge = $subscription->latest_invoice->charge;
275 $new_payment_values['receipt_id'] = $charge->id;
276
277 if ( 'failed' === $charge->status ) {
278 FrmTransLitePaymentsController::change_payment_status( $payment, 'failed' );
279
280 $new_payment_values['receipt_id'] = $charge->id;
281 $frm_payment->update( $payment->id, $new_payment_values );
282
283 $redirect_helper->handle_error( 'payment_failed', $charge->id );
284 }
285
286 $new_payment_values['status'] = 'pending' === $charge->status ? 'processing' : 'complete';
287
288 $new_payment_values['expire_date'] = '0000-00-00';
289
290 foreach ( $subscription->latest_invoice->lines->data as $line ) {
291 $new_payment_values['expire_date'] = gmdate( 'Y-m-d', $line->period->end );
292 }
293 } elseif ( $trial_end ) {
294 $new_payment_values['amount'] = 0;
295 $new_payment_values['begin_date'] = gmdate( 'Y-m-d', time() );
296 $new_payment_values['expire_date'] = gmdate( 'Y-m-d', $trial_end );
297 }//end if
298
299 $new_payment_values['sub_id'] = FrmStrpLiteSubscriptionHelper::create_new_subscription( $atts );
300
301 $frm_payment->update( $payment->id, $new_payment_values );
302
303 if ( $customer_has_been_charged ) {
304 // Set the payment to complete.
305 $status = 'complete';
306 FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
307
308 // Update the next billing date.
309 $next_bill_date = gmdate( 'Y-m-d' );
310
311 foreach ( $subscription->latest_invoice->lines->data as $line ) {
312 $next_bill_date = gmdate( 'Y-m-d', $line->period->end );
313 }
314
315 $frm_sub = new FrmTransLiteSubscription();
316 $frm_sub->update(
317 $new_payment_values['sub_id'],
318 array( 'next_bill_date' => $next_bill_date )
319 );
320 }
321
322 $redirect_helper->handle_success( $entry, isset( $charge ) ? $charge->id : '' );
323 die();
324 }
325
326 /**
327 * Check for a link payment method associated with a customer for a Stripe link recurring payment/subscription.
328 * This gets created on Stripe's end after confirmSetup is called client-side in the Stripe add on.
329 * This is required in order to associate a payment method with the subscription that gets created.
330 *
331 * @since 6.5, introduced in v3.0 of the Stripe add on.
332 *
333 * @param object $setup_intent
334 *
335 * @return false|string
336 */
337 private static function get_link_payment_method( $setup_intent ) {
338 if ( is_object( $setup_intent->latest_attempt ) && ! empty( $setup_intent->latest_attempt->payment_method_details ) ) {
339 $payment_method_details = $setup_intent->latest_attempt->payment_method_details;
340
341 foreach ( array( 'ideal', 'sofort', 'bancontact' ) as $payment_method_type ) {
342 if ( ! empty( $payment_method_details->$payment_method_type ) ) {
343 return $payment_method_details->$payment_method_type->generated_sepa_debit;
344 }
345 }
346 }
347
348 if ( ! empty( $setup_intent->payment_method ) ) {
349 return $setup_intent->payment_method;
350 }
351
352 return false;
353 }
354
355 /**
356 * Create a pending Stripe link payment on entry creation.
357 * Stripe link uses confirmPayment with a return URL which gets called after this.
358 * The payment is then updated from pending status later in another request, either when the return URL is loaded or with a webhook.
359 *
360 * @since 6.5, introduced in v3.0 of the Stripe add on.
361 *
362 * @param array $atts {
363 * The details needs to create a payment.
364 *
365 * @type stdClass $form
366 * @type stdClass $entry
367 * @type WP_Post $action
368 * @type string $amount
369 * @type object $customer
370 * }
371 *
372 * @return void
373 */
374 public static function create_pending_stripe_link_payment( $atts ) {
375 if ( empty( $atts['form'] ) || empty( $atts['entry'] ) || empty( $atts['action'] ) || ! isset( $atts['amount'] ) || empty( $atts['customer'] ) ) {
376 return;
377 }
378
379 $form = $atts['form'];
380 $intent_id = self::verify_intent( $form->id );
381
382 if ( ! $intent_id ) {
383 return;
384 }
385
386 $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
387 $entry = $atts['entry'];
388 $action = $atts['action'];
389 $amount = $atts['amount'];
390 $customer = $atts['customer'];
391
392 if ( ! $is_setup_intent ) {
393 // Update the amount and set the customer before confirming the payment.
394 FrmStrpLiteAppHelper::call_stripe_helper_class(
395 'update_intent',
396 $intent_id,
397 array(
398 'amount' => $amount,
399 'customer' => $customer->id,
400 )
401 );
402 }
403
404 self::add_temporary_referer_meta( (int) $entry->id );
405
406 $frm_payment = new FrmTransLitePayment();
407 $frm_payment->create(
408 array(
409 'paysys' => 'stripe',
410 'amount' => FrmTransLiteAppHelper::get_formatted_amount_for_currency( $amount, $action ),
411 'status' => 'pending',
412 'item_id' => $entry->id,
413 'action_id' => $action->ID,
414 'receipt_id' => $intent_id,
415 'sub_id' => '',
416 'test' => 'test' === FrmStrpLiteAppHelper::active_mode() ? 1 : 0,
417 )
418 );
419 }
420
421 /**
422 * Verify a payment intent or setup intent client secret is in the POST data and is valid.
423 *
424 * @since 6.5, introduced in v3.0 of the Stripe add on.
425 *
426 * @param int|string $form_id
427 *
428 * @return false|string String intent id on success, False if intent is missing or cannot be verified.
429 */
430 private static function verify_intent( $form_id ) {
431 $client_secrets = FrmAppHelper::get_post_param( 'frmintent' . $form_id, array(), 'sanitize_text_field' );
432
433 if ( ! $client_secrets ) {
434 return false;
435 }
436
437 $client_secret = reset( $client_secrets );
438 list( $prefix, $intent_id ) = explode( '_', $client_secret );
439 $intent_id = $prefix . '_' . $intent_id;
440
441 $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
442
443 $function_name = $is_setup_intent ? 'get_setup_intent' : 'get_intent';
444 $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( $function_name, $intent_id );
445
446 if ( ! $intent || $intent->client_secret !== $client_secret ) {
447 return false;
448 }
449
450 return $intent_id;
451 }
452
453 /**
454 * Set the referer URL as field ID 0 in entry meta.
455 * This is required for iDEAL, sofort, and other payment methods that include an additional redirect step.
456 * It is used for the redirect in FrmStrpLinkRedirectHelper.
457 * It is deleted after the redirect happens.
458 *
459 * @param int $entry_id
460 *
461 * @return void
462 */
463 private static function add_temporary_referer_meta( $entry_id ) {
464 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
465 $query_args_to_strip_from_referer = array(
466 'frm_link_error',
467 'payment_intent',
468 'payment_intent_client_secret',
469 'setup_intent',
470 'setup_intent_client_secret',
471 );
472
473 foreach ( $query_args_to_strip_from_referer as $arg ) {
474 $referer = remove_query_arg( $arg, $referer );
475 }
476
477 $meta_value = json_encode( compact( 'referer' ) );
478 FrmEntryMeta::add_entry_meta( $entry_id, 0, '', $meta_value );
479 }
480
481 /**
482 * Flag a form with the frm_stripe_link_form class so it is identifiable when initializing in JavaScript.
483 *
484 * @since 6.5, introduced in v3.0 of the Stripe add on.
485 *
486 * @param stdClass $form
487 *
488 * @return void
489 */
490 public static function add_form_classes( $form ) {
491 if ( false === FrmStrpLiteActionsController::get_stripe_link_action( $form->id ) ) {
492 return;
493 }
494
495 echo ' frm_stripe_link_form ';
496 }
497
498 /**
499 * We need to force AJAX submit with Stripe link to avoid the page reloading before confirmPayment is called after entry creation.
500 *
501 * @since 6.5, introduced in v3.0 of the Stripe add on.
502 *
503 * @param mixed $form
504 *
505 * @return mixed
506 */
507 public static function force_ajax_submit_for_stripe_link( $form ) {
508 if ( ! is_object( $form ) ) {
509 return $form;
510 }
511
512 if ( ! empty( $form->options['ajax_submit'] ) ) {
513 // AJAX is already on so we can exit early.
514 return $form;
515 }
516
517 if ( false !== FrmStrpLiteActionsController::get_stripe_link_action( $form->id ) ) {
518 $form->options['ajax_submit'] = '1';
519 }
520
521 return $form;
522 }
523 }
524