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

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

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