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

490 lines 16.2 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 $redirect_helper->handle_error( 'payment_failed' );
253 die();
254 }
255
256 $customer_has_been_charged = ! empty( $subscription->latest_invoice->charge );
257 $atts['charge'] = FrmStrpLiteSubscriptionHelper::prepare_charge_object_for_subscription( $subscription, $amount );
258 $new_payment_values = array();
259
260 if ( $customer_has_been_charged ) {
261 $charge = $subscription->latest_invoice->charge;
262 $new_payment_values['receipt_id'] = $charge->id;
263 $new_payment_values['status'] = 'pending' === $charge->status ? 'processing' : 'complete';
264
265 $new_payment_values['expire_date'] = '0000-00-00';
266 foreach ( $subscription->latest_invoice->lines->data as $line ) {
267 $new_payment_values['expire_date'] = gmdate( 'Y-m-d', $line->period->end );
268 }
269 } elseif ( $trial_end ) {
270 $new_payment_values['amount'] = 0;
271 $new_payment_values['begin_date'] = gmdate( 'Y-m-d', time() );
272 $new_payment_values['expire_date'] = gmdate( 'Y-m-d', $trial_end );
273 }
274
275 $new_payment_values['sub_id'] = FrmStrpLiteSubscriptionHelper::create_new_subscription( $atts );
276
277 $frm_payment->update( $payment->id, $new_payment_values );
278
279 if ( $customer_has_been_charged ) {
280 // Set the payment to complete.
281 $status = 'complete';
282 FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
283
284 // Update the next billing date.
285 $next_bill_date = gmdate( 'Y-m-d' );
286 foreach ( $subscription->latest_invoice->lines->data as $line ) {
287 $next_bill_date = gmdate( 'Y-m-d', $line->period->end );
288 }
289
290 $frm_sub = new FrmTransLiteSubscription();
291 $frm_sub->update(
292 $new_payment_values['sub_id'],
293 array( 'next_bill_date' => $next_bill_date )
294 );
295 }
296
297 $redirect_helper->handle_success( $entry, isset( $charge ) ? $charge->id : '' );
298 die();
299 }
300
301 /**
302 * Check for a link payment method associated with a customer for a Stripe link recurring payment/subscription.
303 * This gets created on Stripe's end after confirmSetup is called client-side in the Stripe add on.
304 * This is required in order to associate a payment method with the subscription that gets created.
305 *
306 * @since 6.5, introduced in v3.0 of the Stripe add on.
307 *
308 * @param object $setup_intent
309 * @return string|false
310 */
311 private static function get_link_payment_method( $setup_intent ) {
312 if ( is_object( $setup_intent->latest_attempt ) && ! empty( $setup_intent->latest_attempt->payment_method_details ) ) {
313 $payment_method_details = $setup_intent->latest_attempt->payment_method_details;
314 foreach ( array( 'ideal', 'sofort', 'bancontact' ) as $payment_method_type ) {
315 if ( ! empty( $payment_method_details->$payment_method_type ) ) {
316 return $payment_method_details->$payment_method_type->generated_sepa_debit;
317 }
318 }
319 }
320
321 if ( ! empty( $setup_intent->payment_method ) ) {
322 return $setup_intent->payment_method;
323 }
324
325 return false;
326 }
327
328 /**
329 * Create a pending Stripe link payment on entry creation.
330 * Stripe link uses confirmPayment with a return URL which gets called after this.
331 * The payment is then updated from pending status later in another request, either when the return URL is loaded or with a webhook.
332 *
333 * @since 6.5, introduced in v3.0 of the Stripe add on.
334 *
335 * @param array $atts {
336 * The details needs to create a payment.
337 *
338 * @type stdClass $form
339 * @type stdClass $entry
340 * @type WP_Post $action
341 * @type string $amount
342 * @type object $customer
343 * }
344 * @return void
345 */
346 public static function create_pending_stripe_link_payment( $atts ) {
347 if ( empty( $atts['form'] ) || empty( $atts['entry'] ) || empty( $atts['action'] ) || ! isset( $atts['amount'] ) || empty( $atts['customer'] ) ) {
348 return;
349 }
350
351 $form = $atts['form'];
352 $intent_id = self::verify_intent( $form->id );
353
354 if ( ! $intent_id ) {
355 return;
356 }
357
358 $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
359 $entry = $atts['entry'];
360 $action = $atts['action'];
361 $amount = $atts['amount'];
362 $customer = $atts['customer'];
363
364 if ( ! $is_setup_intent ) {
365 // Update the amount and set the customer before confirming the payment.
366 FrmStrpLiteAppHelper::call_stripe_helper_class(
367 'update_intent',
368 $intent_id,
369 array(
370 'amount' => $amount,
371 'customer' => $customer->id,
372 )
373 );
374 }
375
376 self::add_temporary_referer_meta( (int) $entry->id );
377
378 $frm_payment = new FrmTransLitePayment();
379 $frm_payment->create(
380 array(
381 'paysys' => 'stripe',
382 'amount' => FrmTransLiteAppHelper::get_formatted_amount_for_currency( $amount, $action ),
383 'status' => 'pending',
384 'item_id' => $entry->id,
385 'action_id' => $action->ID,
386 'receipt_id' => $intent_id,
387 'sub_id' => '',
388 'test' => 'test' === FrmStrpLiteAppHelper::active_mode() ? 1 : 0,
389 )
390 );
391 }
392
393 /**
394 * Verify a payment intent or setup intent client secret is in the POST data and is valid.
395 *
396 * @since 6.5, introduced in v3.0 of the Stripe add on.
397 *
398 * @param string|int $form_id
399 * @return string|false String intent id on success, False if intent is missing or cannot be verified.
400 */
401 private static function verify_intent( $form_id ) {
402 $client_secrets = FrmAppHelper::get_post_param( 'frmintent' . $form_id, array(), 'sanitize_text_field' );
403 if ( ! $client_secrets ) {
404 return false;
405 }
406
407 $client_secret = reset( $client_secrets );
408 list( $prefix, $intent_id ) = explode( '_', $client_secret );
409 $intent_id = $prefix . '_' . $intent_id;
410
411 $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
412
413 $function_name = $is_setup_intent ? 'get_setup_intent' : 'get_intent';
414 $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( $function_name, $intent_id );
415
416 if ( ! $intent || $intent->client_secret !== $client_secret ) {
417 return false;
418 }
419
420 return $intent_id;
421 }
422
423 /**
424 * Set the referer URL as field ID 0 in entry meta.
425 * This is required for iDEAL, sofort, and other payment methods that include an additional redirect step.
426 * It is used for the redirect in FrmStrpLinkRedirectHelper.
427 * It is deleted after the redirect happens.
428 *
429 * @param int $entry_id
430 * @return void
431 */
432 private static function add_temporary_referer_meta( $entry_id ) {
433 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
434 $query_args_to_strip_from_referer = array(
435 'frm_link_error',
436 'payment_intent',
437 'payment_intent_client_secret',
438 'setup_intent',
439 'setup_intent_client_secret',
440 );
441 foreach ( $query_args_to_strip_from_referer as $arg ) {
442 $referer = remove_query_arg( $arg, $referer );
443 }
444
445 $meta_value = json_encode( compact( 'referer' ) );
446 FrmEntryMeta::add_entry_meta( $entry_id, 0, '', $meta_value );
447 }
448
449 /**
450 * Flag a form with the frm_stripe_link_form class so it is identifiable when initializing in JavaScript.
451 *
452 * @since 6.5, introduced in v3.0 of the Stripe add on.
453 *
454 * @param stdClass $form
455 * @return void
456 */
457 public static function add_form_classes( $form ) {
458 if ( false === FrmStrpLiteActionsController::get_stripe_link_action( $form->id ) ) {
459 return;
460 }
461
462 echo ' frm_stripe_link_form ';
463 }
464
465 /**
466 * We need to force AJAX submit with Stripe link to avoid the page reloading before confirmPayment is called after entry creation.
467 *
468 * @since 6.5, introduced in v3.0 of the Stripe add on.
469 *
470 * @param mixed $form
471 * @return mixed
472 */
473 public static function force_ajax_submit_for_stripe_link( $form ) {
474 if ( ! is_object( $form ) ) {
475 return $form;
476 }
477
478 if ( ! empty( $form->options['ajax_submit'] ) ) {
479 // AJAX is already on so we can exit early.
480 return $form;
481 }
482
483 if ( false !== FrmStrpLiteActionsController::get_stripe_link_action( $form->id ) ) {
484 $form->options['ajax_submit'] = '1';
485 }
486
487 return $form;
488 }
489 }
490