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

445 lines 14.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 $redirect_helper->handle_error( 'did_not_complete' );
210 die();
211 }
212
213 $amount = $payment->amount * 100;
214 $new_charge = array(
215 'customer' => $customer_id,
216 'default_payment_method' => $payment_method_id,
217 'plan' => FrmStrpLiteSubscriptionHelper::get_plan_from_atts(
218 array(
219 'action' => $action,
220 'amount' => $amount,
221 )
222 ),
223 'expand' => array( 'latest_invoice.charge' ),
224 );
225
226 if ( ! FrmStrpLitePaymentTypeHandler::should_use_automatic_payment_methods( $action ) ) {
227 $new_charge['payment_settings'] = array(
228 'payment_method_types' => FrmStrpLitePaymentTypeHandler::get_payment_method_types( $action ),
229 );
230 }
231
232 $atts = array(
233 'action' => $action,
234 'entry' => $entry,
235 );
236
237 $trial_end = FrmStrpLiteActionsController::get_trial_end_time( $atts );
238 if ( $trial_end ) {
239 $new_charge['trial_end'] = $trial_end;
240 }
241
242 $subscription = FrmStrpLiteAppHelper::call_stripe_helper_class( 'create_subscription', $new_charge );
243 if ( ! is_object( $subscription ) ) {
244 $redirect_helper->handle_error( 'create_subscription_failed' );
245 die();
246 }
247
248 if ( 'succeeded' !== $setup_intent->status ) {
249 $redirect_helper->handle_error( 'payment_failed' );
250 die();
251 }
252
253 $customer_has_been_charged = ! empty( $subscription->latest_invoice->charge );
254 $atts['charge'] = FrmStrpLiteSubscriptionHelper::prepare_charge_object_for_subscription( $subscription, $amount );
255 $new_payment_values = array();
256
257 if ( $customer_has_been_charged ) {
258 $charge = $subscription->latest_invoice->charge;
259 $new_payment_values['receipt_id'] = $charge->id;
260 $new_payment_values['status'] = 'pending' === $charge->status ? 'processing' : 'complete';
261 } elseif ( $trial_end ) {
262 $new_payment_values['amount'] = 0;
263 $new_payment_values['begin_date'] = gmdate( 'Y-m-d', time() );
264 $new_payment_values['expire_date'] = gmdate( 'Y-m-d', $trial_end );
265 }
266
267 $new_payment_values['sub_id'] = FrmStrpLiteSubscriptionHelper::create_new_subscription( $atts );
268
269 $frm_payment->update( $payment->id, $new_payment_values );
270
271 if ( $customer_has_been_charged ) {
272 $status = 'complete';
273 FrmTransLiteActionsController::trigger_payment_status_change( compact( 'status', 'payment' ) );
274 }
275
276 $redirect_helper->handle_success( $entry, isset( $charge ) ? $charge->id : '' );
277 die();
278 }
279
280 /**
281 * Check for a link payment method associated with a customer for a Stripe link recurring payment/subscription.
282 * This gets created on Stripe's end after confirmSetup is called client-side in the Stripe add on.
283 * This is required in order to associate a payment method with the subscription that gets created.
284 *
285 * @since 6.5, introduced in v3.0 of the Stripe add on.
286 *
287 * @param object $setup_intent
288 * @return string|false
289 */
290 private static function get_link_payment_method( $setup_intent ) {
291 if ( ! empty( $setup_intent->payment_method ) ) {
292 return $setup_intent->payment_method;
293 }
294 return false;
295 }
296
297 /**
298 * Create a pending Stripe link payment on entry creation.
299 * Stripe link uses confirmPayment with a return URL which gets called after this.
300 * The payment is then updated from pending status later in another request, either when the return URL is loaded or with a webhook.
301 *
302 * @since 6.5, introduced in v3.0 of the Stripe add on.
303 *
304 * @param array $atts {
305 * @type stdClass $form
306 * @type stdClass $entry
307 * @type WP_Post $action
308 * @type string $amount
309 * @type object $customer
310 * }
311 * @return void
312 */
313 public static function create_pending_stripe_link_payment( $atts ) {
314 if ( empty( $atts['form'] ) || empty( $atts['entry'] ) || empty( $atts['action'] ) || ! isset( $atts['amount'] ) || empty( $atts['customer'] ) ) {
315 return;
316 }
317
318 $form = $atts['form'];
319 $intent_id = self::verify_intent( $form->id );
320
321 if ( ! $intent_id ) {
322 return;
323 }
324
325 $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
326 $entry = $atts['entry'];
327 $action = $atts['action'];
328 $amount = $atts['amount'];
329 $customer = $atts['customer'];
330
331 if ( ! $is_setup_intent ) {
332 // Update the amount and set the customer before confirming the payment.
333 FrmStrpLiteAppHelper::call_stripe_helper_class(
334 'update_intent',
335 $intent_id,
336 array(
337 'amount' => $amount,
338 'customer' => $customer->id,
339 )
340 );
341 }
342
343 self::add_temporary_referer_meta( (int) $entry->id );
344
345 $frm_payment = new FrmTransLitePayment();
346 $frm_payment->create(
347 array(
348 'paysys' => 'stripe',
349 'amount' => FrmTransLiteAppHelper::get_formatted_amount_for_currency( $amount, $action ),
350 'status' => 'pending',
351 'item_id' => $entry->id,
352 'action_id' => $action->ID,
353 'receipt_id' => $intent_id,
354 'sub_id' => '',
355 )
356 );
357 }
358
359 /**
360 * Verify a payment intent or setup intent client secret is in the POST data and is valid.
361 *
362 * @since 6.5, introduced in v3.0 of the Stripe add on.
363 *
364 * @param string|int $form_id
365 * @return string|false String intent id on success, False if intent is missing or cannot be verified.
366 */
367 private static function verify_intent( $form_id ) {
368 $client_secrets = FrmAppHelper::get_post_param( 'frmintent' . $form_id, array(), 'sanitize_text_field' );
369 if ( ! $client_secrets ) {
370 return false;
371 }
372
373 $client_secret = reset( $client_secrets );
374 list( $prefix, $intent_id ) = explode( '_', $client_secret );
375 $intent_id = $prefix . '_' . $intent_id;
376
377 $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
378
379 $function_name = $is_setup_intent ? 'get_setup_intent' : 'get_intent';
380 $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( $function_name, $intent_id );
381
382 if ( ! $intent || $intent->client_secret !== $client_secret ) {
383 return false;
384 }
385
386 return $intent_id;
387 }
388
389 /**
390 * Set the referer URL as field ID 0 in entry meta.
391 * This is required for iDEAL, sofort, and other payment methods that include an additional redirect step.
392 * It is used for the redirect in FrmStrpLinkRedirectHelper.
393 * It is deleted after the redirect happens.
394 *
395 * @param int $entry_id
396 * @return void
397 */
398 private static function add_temporary_referer_meta( $entry_id ) {
399 $referer = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
400 $meta_value = json_encode( compact( 'referer' ) );
401 FrmEntryMeta::add_entry_meta( $entry_id, 0, '', $meta_value );
402 }
403
404 /**
405 * Flag a form with the frm_stripe_link_form class so it is identifiable when initializing in JavaScript.
406 *
407 * @since 6.5, introduced in v3.0 of the Stripe add on.
408 *
409 * @param stdClass $form
410 * @return void
411 */
412 public static function add_form_classes( $form ) {
413 if ( false === FrmStrpLiteActionsController::get_stripe_link_action( $form->id ) ) {
414 return;
415 }
416
417 echo ' frm_stripe_link_form ';
418 }
419
420 /**
421 * We need to force AJAX submit with Stripe link to avoid the page reloading before confirmPayment is called after entry creation.
422 *
423 * @since 6.5, introduced in v3.0 of the Stripe add on.
424 *
425 * @param mixed $form
426 * @return mixed
427 */
428 public static function force_ajax_submit_for_stripe_link( $form ) {
429 if ( ! is_object( $form ) ) {
430 return $form;
431 }
432
433 if ( ! empty( $form->options['ajax_submit'] ) ) {
434 // AJAX is already on so we can exit early.
435 return $form;
436 }
437
438 if ( false !== FrmStrpLiteActionsController::get_stripe_link_action( $form->id ) ) {
439 $form->options['ajax_submit'] = '1';
440 }
441
442 return $form;
443 }
444 }
445