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

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