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

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