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

487 lines 16.0 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 * @type stdClass $form
337 * @type stdClass $entry
338 * @type WP_Post $action
339 * @type string $amount
340 * @type object $customer
341 * }
342 * @return void
343 */
344 public static function create_pending_stripe_link_payment( $atts ) {
345 if ( empty( $atts['form'] ) || empty( $atts['entry'] ) || empty( $atts['action'] ) || ! isset( $atts['amount'] ) || empty( $atts['customer'] ) ) {
346 return;
347 }
348
349 $form = $atts['form'];
350 $intent_id = self::verify_intent( $form->id );
351
352 if ( ! $intent_id ) {
353 return;
354 }
355
356 $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
357 $entry = $atts['entry'];
358 $action = $atts['action'];
359 $amount = $atts['amount'];
360 $customer = $atts['customer'];
361
362 if ( ! $is_setup_intent ) {
363 // Update the amount and set the customer before confirming the payment.
364 FrmStrpLiteAppHelper::call_stripe_helper_class(
365 'update_intent',
366 $intent_id,
367 array(
368 'amount' => $amount,
369 'customer' => $customer->id,
370 )
371 );
372 }
373
374 self::add_temporary_referer_meta( (int) $entry->id );
375
376 $frm_payment = new FrmTransLitePayment();
377 $frm_payment->create(
378 array(
379 'paysys' => 'stripe',
380 'amount' => FrmTransLiteAppHelper::get_formatted_amount_for_currency( $amount, $action ),
381 'status' => 'pending',
382 'item_id' => $entry->id,
383 'action_id' => $action->ID,
384 'receipt_id' => $intent_id,
385 'sub_id' => '',
386 )
387 );
388 }
389
390 /**
391 * Verify a payment intent or setup intent client secret is in the POST data and is valid.
392 *
393 * @since 6.5, introduced in v3.0 of the Stripe add on.
394 *
395 * @param string|int $form_id
396 * @return string|false String intent id on success, False if intent is missing or cannot be verified.
397 */
398 private static function verify_intent( $form_id ) {
399 $client_secrets = FrmAppHelper::get_post_param( 'frmintent' . $form_id, array(), 'sanitize_text_field' );
400 if ( ! $client_secrets ) {
401 return false;
402 }
403
404 $client_secret = reset( $client_secrets );
405 list( $prefix, $intent_id ) = explode( '_', $client_secret );
406 $intent_id = $prefix . '_' . $intent_id;
407
408 $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
409
410 $function_name = $is_setup_intent ? 'get_setup_intent' : 'get_intent';
411 $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( $function_name, $intent_id );
412
413 if ( ! $intent || $intent->client_secret !== $client_secret ) {
414 return false;
415 }
416
417 return $intent_id;
418 }
419
420 /**
421 * Set the referer URL as field ID 0 in entry meta.
422 * This is required for iDEAL, sofort, and other payment methods that include an additional redirect step.
423 * It is used for the redirect in FrmStrpLinkRedirectHelper.
424 * It is deleted after the redirect happens.
425 *
426 * @param int $entry_id
427 * @return void
428 */
429 private static function add_temporary_referer_meta( $entry_id ) {
430 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
431 $query_args_to_strip_from_referer = array(
432 'frm_link_error',
433 'payment_intent',
434 'payment_intent_client_secret',
435 'setup_intent',
436 'setup_intent_client_secret',
437 );
438 foreach ( $query_args_to_strip_from_referer as $arg ) {
439 $referer = remove_query_arg( $arg, $referer );
440 }
441
442 $meta_value = json_encode( compact( 'referer' ) );
443 FrmEntryMeta::add_entry_meta( $entry_id, 0, '', $meta_value );
444 }
445
446 /**
447 * Flag a form with the frm_stripe_link_form class so it is identifiable when initializing in JavaScript.
448 *
449 * @since 6.5, introduced in v3.0 of the Stripe add on.
450 *
451 * @param stdClass $form
452 * @return void
453 */
454 public static function add_form_classes( $form ) {
455 if ( false === FrmStrpLiteActionsController::get_stripe_link_action( $form->id ) ) {
456 return;
457 }
458
459 echo ' frm_stripe_link_form ';
460 }
461
462 /**
463 * We need to force AJAX submit with Stripe link to avoid the page reloading before confirmPayment is called after entry creation.
464 *
465 * @since 6.5, introduced in v3.0 of the Stripe add on.
466 *
467 * @param mixed $form
468 * @return mixed
469 */
470 public static function force_ajax_submit_for_stripe_link( $form ) {
471 if ( ! is_object( $form ) ) {
472 return $form;
473 }
474
475 if ( ! empty( $form->options['ajax_submit'] ) ) {
476 // AJAX is already on so we can exit early.
477 return $form;
478 }
479
480 if ( false !== FrmStrpLiteActionsController::get_stripe_link_action( $form->id ) ) {
481 $form->options['ajax_submit'] = '1';
482 }
483
484 return $form;
485 }
486 }
487